From 5b70be97784bd0bb95afaa5cceeed97d118af90d Mon Sep 17 00:00:00 2001 From: "munkyu.im" Date: Mon, 16 May 2011 10:27:56 +0900 Subject: [PATCH] initialize to 0.14.1 qemu --- CODING_STYLE | 81 + COPYING | 339 + COPYING.LIB | 504 ++ Changelog | 574 ++ HACKING | 125 + LICENSE | 18 + MAINTAINERS | 489 + Makefile | 369 + Makefile.dis | 23 + Makefile.hw | 24 + Makefile.objs | 319 + Makefile.target | 380 + Makefile.user | 23 + QMP/README | 88 + QMP/qmp-events.txt | 266 + QMP/qmp-shell | 259 + QMP/qmp-spec.txt | 272 + QMP/qmp.py | 131 + README | 3 + TODO | 37 + VERSION | 1 + a.out.h | 430 + acl.c | 185 + acl.h | 74 + aes.c | 1314 +++ aes.h | 26 + aio.c | 230 + alpha-dis.c | 1920 ++++ alpha.ld | 127 + arch_init.c | 726 ++ arch_init.h | 34 + arm-dis.c | 4136 +++++++++ arm-semi.c | 488 + arm.ld | 153 + async.c | 216 + audio/alsaaudio.c | 1260 +++ audio/audio.c | 2066 +++++ audio/audio.h | 166 + audio/audio_int.h | 282 + audio/audio_pt_int.c | 175 + audio/audio_pt_int.h | 22 + audio/audio_template.h | 560 ++ audio/audio_win_int.c | 108 + audio/audio_win_int.h | 10 + audio/coreaudio.c | 549 ++ audio/dsound_template.h | 293 + audio/dsoundaudio.c | 1030 +++ audio/esdaudio.c | 557 ++ audio/fmodaudio.c | 687 ++ audio/mixeng.c | 360 + audio/mixeng.h | 51 + audio/mixeng_template.h | 152 + audio/noaudio.c | 173 + audio/ossaudio.c | 944 ++ audio/paaudio.c | 525 ++ audio/rate_template.h | 111 + audio/sdlaudio.c | 459 + audio/spiceaudio.c | 345 + audio/wavaudio.c | 262 + audio/wavcapture.c | 161 + audio/winwaveaudio.c | 723 ++ balloon.c | 148 + balloon.h | 33 + block-migration.c | 732 ++ block-migration.h | 23 + block.c | 2946 +++++++ block.h | 299 + block/blkdebug.c | 471 + block/blkverify.c | 383 + block/bochs.c | 230 + block/cloop.c | 171 + block/cow.c | 324 + block/curl.c | 564 ++ block/dmg.c | 312 + block/nbd.c | 221 + block/parallels.c | 157 + block/qcow.c | 975 ++ block/qcow2-cache.c | 314 + block/qcow2-cluster.c | 975 ++ block/qcow2-refcount.c | 1166 +++ block/qcow2-snapshot.c | 451 + block/qcow2.c | 1397 +++ block/qcow2.h | 242 + block/qed-check.c | 210 + block/qed-cluster.c | 154 + block/qed-gencb.c | 32 + block/qed-l2-cache.c | 173 + block/qed-table.c | 319 + block/qed.c | 1372 +++ block/qed.h | 301 + block/raw-posix-aio.h | 43 + block/raw-posix.c | 1444 +++ block/raw-win32.c | 430 + block/raw.c | 156 + block/rbd.c | 1059 +++ block/rbd_types.h | 71 + block/sheepdog.c | 2037 +++++ block/vdi.c | 961 ++ block/vmdk.c | 870 ++ block/vpc.c | 654 ++ block/vvfat.c | 2888 ++++++ block_int.h | 265 + blockdev.c | 787 ++ blockdev.h | 68 + bsd-user/bsd-mman.h | 121 + bsd-user/bsdload.c | 202 + bsd-user/elfload.c | 1577 ++++ bsd-user/errno_defs.h | 149 + bsd-user/freebsd/strace.list | 171 + bsd-user/freebsd/syscall_nr.h | 373 + bsd-user/i386/syscall.h | 161 + bsd-user/i386/target_signal.h | 20 + bsd-user/main.c | 1131 +++ bsd-user/mmap.c | 559 ++ bsd-user/netbsd/strace.list | 145 + bsd-user/netbsd/syscall_nr.h | 373 + bsd-user/openbsd/strace.list | 187 + bsd-user/openbsd/syscall_nr.h | 225 + bsd-user/qemu-types.h | 24 + bsd-user/qemu.h | 394 + bsd-user/signal.c | 38 + bsd-user/sparc/syscall.h | 9 + bsd-user/sparc/target_signal.h | 27 + bsd-user/sparc64/syscall.h | 10 + bsd-user/sparc64/target_signal.h | 27 + bsd-user/strace.c | 191 + bsd-user/syscall.c | 560 ++ bsd-user/syscall_defs.h | 114 + bsd-user/uaccess.c | 65 + bsd-user/x86_64/syscall.h | 116 + bsd-user/x86_64/target_signal.h | 19 + bswap.h | 240 + bt-host.c | 199 + bt-host.h | 9 + bt-vhci.c | 168 + buffered_file.c | 282 + buffered_file.h | 30 + cache-utils.c | 97 + cache-utils.h | 41 + check-qdict.c | 401 + check-qfloat.c | 76 + check-qint.c | 113 + check-qjson.c | 794 ++ check-qlist.c | 153 + check-qstring.c | 134 + cmd.c | 610 ++ cmd.h | 79 + compatfd.c | 117 + compatfd.h | 43 + config.h | 2 + configure | 3294 +++++++ console.c | 1707 ++++ console.h | 381 + cpu-all.h | 972 ++ cpu-common.h | 143 + cpu-defs.h | 215 + cpu-exec.c | 1271 +++ cpus.c | 989 +++ cpus.h | 21 + cris-dis.c | 2893 ++++++ cursor.c | 210 + cursor_hidden.xpm | 37 + cursor_left_ptr.xpm | 39 + cutils.c | 411 + darwin-user/commpage.c | 357 + darwin-user/ioctls.h | 4 + darwin-user/ioctls_types.h | 1 + darwin-user/machload.c | 902 ++ darwin-user/main.c | 1015 +++ darwin-user/mmap.c | 409 + darwin-user/qemu.h | 178 + darwin-user/signal.c | 457 + darwin-user/syscall.c | 1566 ++++ darwin-user/syscalls.h | 384 + def-helper.h | 240 + default-configs/alpha-linux-user.mak | 1 + default-configs/arm-linux-user.mak | 3 + default-configs/arm-softmmu.mak | 28 + default-configs/armeb-linux-user.mak | 3 + default-configs/cris-linux-user.mak | 1 + default-configs/cris-softmmu.mak | 5 + default-configs/i386-bsd-user.mak | 1 + default-configs/i386-darwin-user.mak | 1 + default-configs/i386-linux-user.mak | 1 + default-configs/i386-softmmu.mak | 20 + default-configs/m68k-linux-user.mak | 3 + default-configs/m68k-softmmu.mak | 5 + default-configs/microblaze-linux-user.mak | 1 + default-configs/microblaze-softmmu.mak | 4 + default-configs/mips-linux-user.mak | 1 + default-configs/mips-softmmu.mak | 28 + default-configs/mips64-softmmu.mak | 28 + default-configs/mips64el-softmmu.mak | 30 + default-configs/mipsel-linux-user.mak | 1 + default-configs/mipsel-softmmu.mak | 28 + default-configs/pci.mak | 16 + default-configs/ppc-darwin-user.mak | 3 + default-configs/ppc-linux-user.mak | 3 + default-configs/ppc-softmmu.mak | 33 + default-configs/ppc64-linux-user.mak | 3 + default-configs/ppc64-softmmu.mak | 33 + default-configs/ppc64abi32-linux-user.mak | 3 + default-configs/ppcemb-softmmu.mak | 33 + default-configs/s390x-softmmu.mak | 1 + default-configs/sh4-linux-user.mak | 1 + default-configs/sh4-softmmu.mak | 7 + default-configs/sh4eb-linux-user.mak | 1 + default-configs/sh4eb-softmmu.mak | 7 + default-configs/sparc-bsd-user.mak | 1 + default-configs/sparc-linux-user.mak | 1 + default-configs/sparc-softmmu.mak | 10 + default-configs/sparc32plus-linux-user.mak | 1 + default-configs/sparc64-bsd-user.mak | 1 + default-configs/sparc64-linux-user.mak | 1 + default-configs/sparc64-softmmu.mak | 13 + default-configs/x86_64-bsd-user.mak | 1 + default-configs/x86_64-linux-user.mak | 1 + default-configs/x86_64-softmmu.mak | 20 + device_tree.c | 110 + device_tree.h | 26 + dis-asm.h | 476 + disas.c | 432 + disas.h | 42 + dma-helpers.c | 184 + dma.h | 41 + docs/blkverify.txt | 69 + docs/bootindex.txt | 43 + docs/migration.txt | 303 + docs/qdev-device-use.txt | 368 + docs/specs/acpi_pci_hotplug.txt | 37 + docs/specs/ivshmem_device_spec.txt | 96 + docs/specs/qed_spec.txt | 130 + docs/tracing.txt | 189 + dyngen-exec.h | 84 + elf.h | 1240 +++ envlist.c | 246 + envlist.h | 22 + exec-all.h | 349 + exec.c | 4316 +++++++++ fpu/softfloat-macros.h | 719 ++ fpu/softfloat-native.c | 514 ++ fpu/softfloat-native.h | 492 ++ fpu/softfloat-specialize.h | 755 ++ fpu/softfloat.c | 6119 +++++++++++++ fpu/softfloat.h | 583 ++ fsdev/qemu-fsdev.c | 100 + fsdev/qemu-fsdev.h | 55 + gdb-xml/arm-core.xml | 31 + gdb-xml/arm-neon.xml | 88 + gdb-xml/arm-vfp.xml | 29 + gdb-xml/arm-vfp3.xml | 45 + gdb-xml/cf-core.xml | 29 + gdb-xml/cf-fp.xml | 22 + gdb-xml/power-altivec.xml | 57 + gdb-xml/power-core.xml | 49 + gdb-xml/power-fpu.xml | 44 + gdb-xml/power-spe.xml | 45 + gdb-xml/power64-core.xml | 49 + gdbstub.c | 2694 ++++++ gdbstub.h | 44 + gen-icount.h | 48 + hmp-commands.hx | 1369 +++ host-utils.c | 105 + host-utils.h | 236 + hpet.h | 22 + hppa-dis.c | 2831 ++++++ hppa.ld | 213 + hw/9p.h | 24 + hw/a9mpcore.c | 29 + hw/ac97.c | 1351 +++ hw/acpi.c | 199 + hw/acpi.h | 78 + hw/acpi_piix4.c | 672 ++ hw/adb.c | 480 + hw/adlib.c | 338 + hw/ads7846.c | 170 + hw/alpha_palcode.c | 1048 +++ hw/an5206.c | 101 + hw/apb_pci.c | 482 + hw/apb_pci.h | 9 + hw/apic.c | 1033 +++ hw/apic.h | 27 + hw/apm.c | 86 + hw/apm.h | 22 + hw/applesmc.c | 241 + hw/arm-misc.h | 46 + hw/arm11mpcore.c | 112 + hw/arm_boot.c | 283 + hw/arm_gic.c | 749 ++ hw/arm_pic.c | 49 + hw/arm_sysctl.c | 264 + hw/arm_timer.c | 357 + hw/armv7m.c | 263 + hw/armv7m_nvic.c | 409 + hw/audiodev.h | 20 + hw/axis_dev88.c | 357 + hw/baum.c | 642 ++ hw/baum.h | 26 + hw/bitbang_i2c.c | 228 + hw/bitbang_i2c.h | 14 + hw/blizzard.c | 998 +++ hw/blizzard_template.h | 136 + hw/boards.h | 38 + hw/bonito.c | 813 ++ hw/bt-hci-csr.c | 454 + hw/bt-hci.c | 2221 +++++ hw/bt-hid.c | 571 ++ hw/bt-l2cap.c | 1362 +++ hw/bt-sdp.c | 967 ++ hw/bt.c | 121 + hw/bt.h | 2183 +++++ hw/cbus.c | 618 ++ hw/cdrom.c | 155 + hw/cirrus_vga.c | 3153 +++++++ hw/cirrus_vga_rop.h | 208 + hw/cirrus_vga_rop2.h | 281 + hw/cris-boot.c | 97 + hw/cris-boot.h | 11 + hw/cris_pic_cpu.c | 50 + hw/cs4231.c | 175 + hw/cs4231a.c | 686 ++ hw/cuda.c | 769 ++ hw/debugcon.c | 107 + hw/dec_pci.c | 133 + hw/dec_pci.h | 8 + hw/device-hotplug.c | 46 + hw/devices.h | 70 + hw/dma.c | 555 ++ hw/dp8393x.c | 914 ++ hw/ds1225y.c | 182 + hw/ds1338.c | 132 + hw/dummy_m68k.c | 80 + hw/e1000.c | 1183 +++ hw/e1000_hw.h | 864 ++ hw/ecc.c | 88 + hw/eccmemctl.c | 332 + hw/eepro100.c | 2066 +++++ hw/eeprom93xx.c | 337 + hw/eeprom93xx.h | 40 + hw/elf_ops.h | 297 + hw/empty_slot.c | 93 + hw/empty_slot.h | 2 + hw/es1370.c | 1053 +++ hw/escc.c | 961 ++ hw/escc.h | 8 + hw/esp.c | 751 ++ hw/esp.h | 13 + hw/etraxfs.c | 160 + hw/etraxfs.h | 28 + hw/etraxfs_dma.c | 756 ++ hw/etraxfs_dma.h | 22 + hw/etraxfs_eth.c | 613 ++ hw/etraxfs_pic.c | 169 + hw/etraxfs_ser.c | 220 + hw/etraxfs_timer.c | 336 + hw/event_notifier.c | 62 + hw/event_notifier.h | 16 + hw/fdc.c | 2110 +++++ hw/fdc.h | 16 + hw/file-op-9p.h | 107 + hw/firmware_abi.h | 73 + hw/flash.h | 54 + hw/fmopl.c | 1391 +++ hw/fmopl.h | 174 + hw/framebuffer.c | 116 + hw/framebuffer.h | 22 + hw/fw_cfg.c | 407 + hw/fw_cfg.h | 70 + hw/g364fb.c | 615 ++ hw/grackle_pci.c | 147 + hw/grlib.h | 126 + hw/grlib_apbuart.c | 187 + hw/grlib_gptimer.c | 395 + hw/grlib_irqmp.c | 376 + hw/gt64xxx.c | 1174 +++ hw/gumstix.c | 141 + hw/gus.c | 322 + hw/gusemu.h | 105 + hw/gusemu_hal.c | 554 ++ hw/gusemu_mixer.c | 240 + hw/gustate.h | 132 + hw/hda-audio.c | 926 ++ hw/heathrow_pic.c | 230 + hw/hpet.c | 747 ++ hw/hpet_emul.h | 71 + hw/hw.h | 824 ++ hw/i2c.c | 196 + hw/i2c.h | 82 + hw/i8254.c | 517 ++ hw/i8259.c | 539 ++ hw/ide.h | 31 + hw/ide/ahci.c | 1152 +++ hw/ide/ahci.h | 333 + hw/ide/cmd646.c | 298 + hw/ide/core.c | 2813 ++++++ hw/ide/ich.c | 148 + hw/ide/internal.h | 571 ++ hw/ide/isa.c | 120 + hw/ide/macio.c | 329 + hw/ide/microdrive.c | 551 ++ hw/ide/mmio.c | 141 + hw/ide/pci.c | 467 + hw/ide/pci.h | 48 + hw/ide/piix.c | 214 + hw/ide/qdev.c | 172 + hw/ide/via.c | 200 + hw/integratorcp.c | 545 ++ hw/intel-hda-defs.h | 717 ++ hw/intel-hda.c | 1308 +++ hw/intel-hda.h | 62 + hw/ioapic.c | 358 + hw/ioapic.h | 20 + hw/ioh3420.c | 246 + hw/ioh3420.h | 10 + hw/irq.c | 77 + hw/irq.h | 35 + hw/isa-bus.c | 194 + hw/isa.h | 52 + hw/isa_mmio.c | 82 + hw/ivshmem.c | 829 ++ hw/jazz_led.c | 328 + hw/lan9118.c | 1190 +++ hw/lance.c | 160 + hw/leon3.c | 217 + hw/lm832x.c | 512 ++ hw/loader.c | 795 ++ hw/loader.h | 48 + hw/lsi53c895a.c | 2276 +++++ hw/m48t59.c | 762 ++ hw/mac_dbdma.c | 852 ++ hw/mac_dbdma.h | 43 + hw/mac_nvram.c | 195 + hw/macio.c | 118 + hw/mainstone.c | 158 + hw/mainstone.h | 38 + hw/marvell_88w8618_audio.c | 299 + hw/max111x.c | 188 + hw/max7310.c | 220 + hw/mc146818rtc.c | 657 ++ hw/mc146818rtc.h | 12 + hw/mcf.h | 21 + hw/mcf5206.c | 541 ++ hw/mcf5208.c | 306 + hw/mcf_fec.c | 481 + hw/mcf_intc.c | 157 + hw/mcf_uart.c | 310 + hw/microblaze_pic_cpu.c | 50 + hw/mips-bios.h | 8 + hw/mips.h | 40 + hw/mips_addr.c | 34 + hw/mips_cpudevs.h | 15 + hw/mips_fulong2e.c | 408 + hw/mips_int.c | 65 + hw/mips_jazz.c | 314 + hw/mips_malta.c | 978 ++ hw/mips_mipssim.c | 212 + hw/mips_r4k.c | 318 + hw/mips_timer.c | 147 + hw/mipsnet.c | 288 + hw/mpcore.c | 286 + hw/msi.c | 344 + hw/msi.h | 41 + hw/msix.c | 420 + hw/msix.h | 36 + hw/msmouse.c | 78 + hw/msmouse.h | 2 + hw/mst_fpga.c | 240 + hw/multiboot.c | 339 + hw/multiboot.h | 12 + hw/musicpal.c | 1665 ++++ hw/nand.c | 661 ++ hw/ne2000-isa.c | 125 + hw/ne2000.c | 781 ++ hw/ne2000.h | 39 + hw/nseries.c | 1417 +++ hw/nvram.h | 42 + hw/omap.h | 1141 +++ hw/omap1.c | 3894 ++++++++ hw/omap2.c | 2616 ++++++ hw/omap_clk.c | 1260 +++ hw/omap_dma.c | 2082 +++++ hw/omap_dss.c | 1068 +++ hw/omap_gpio.c | 725 ++ hw/omap_gpmc.c | 419 + hw/omap_gptimer.c | 484 + hw/omap_i2c.c | 470 + hw/omap_intc.c | 598 ++ hw/omap_l4.c | 272 + hw/omap_lcd_template.h | 175 + hw/omap_lcdc.c | 461 + hw/omap_mmc.c | 641 ++ hw/omap_sdrc.c | 165 + hw/omap_spi.c | 346 + hw/omap_sx1.c | 254 + hw/omap_synctimer.c | 96 + hw/omap_tap.c | 112 + hw/omap_uart.c | 196 + hw/onenand.c | 661 ++ hw/openpic.c | 1686 ++++ hw/openpic.h | 18 + hw/palm.c | 289 + hw/parallel.c | 609 ++ hw/pc.c | 1153 +++ hw/pc.h | 175 + hw/pc_piix.c | 389 + hw/pci-hotplug.c | 291 + hw/pci-stub.c | 50 + hw/pci.c | 2117 +++++ hw/pci.h | 469 + hw/pci_bridge.c | 275 + hw/pci_bridge.h | 66 + hw/pci_host.c | 159 + hw/pci_host.h | 53 + hw/pci_ids.h | 110 + hw/pci_internals.h | 47 + hw/pci_regs.h | 665 ++ hw/pcie.c | 544 ++ hw/pcie.h | 132 + hw/pcie_aer.c | 1031 +++ hw/pcie_aer.h | 106 + hw/pcie_host.c | 177 + hw/pcie_host.h | 49 + hw/pcie_port.c | 124 + hw/pcie_port.h | 51 + hw/pcie_regs.h | 156 + hw/pckbd.c | 499 ++ hw/pcmcia.h | 51 + hw/pcnet-pci.c | 346 + hw/pcnet.c | 1748 ++++ hw/pcnet.h | 42 + hw/pcspk.c | 147 + hw/petalogix_s3adsp1800_mmu.c | 225 + hw/pflash_cfi01.c | 726 ++ hw/pflash_cfi02.c | 741 ++ hw/piix4.c | 127 + hw/piix_pci.c | 380 + hw/pixel_ops.h | 53 + hw/pl011.c | 332 + hw/pl022.c | 312 + hw/pl031.c | 237 + hw/pl050.c | 187 + hw/pl061.c | 317 + hw/pl080.c | 407 + hw/pl110.c | 422 + hw/pl110_template.h | 307 + hw/pl181.c | 473 + hw/pl190.c | 278 + hw/pm_smbus.c | 176 + hw/pm_smbus.h | 21 + hw/ppc.c | 1288 +++ hw/ppc.h | 56 + hw/ppc405.h | 76 + hw/ppc405_boards.c | 653 ++ hw/ppc405_uc.c | 2544 ++++++ hw/ppc440.c | 100 + hw/ppc440.h | 21 + hw/ppc440_bamboo.c | 203 + hw/ppc4xx.h | 60 + hw/ppc4xx_devs.c | 691 ++ hw/ppc4xx_pci.c | 393 + hw/ppc_mac.h | 113 + hw/ppc_newworld.c | 429 + hw/ppc_oldworld.c | 332 + hw/ppc_prep.c | 771 ++ hw/ppce500.h | 22 + hw/ppce500_mpc8544ds.c | 297 + hw/ppce500_pci.c | 326 + hw/prep_pci.c | 144 + hw/prep_pci.h | 8 + hw/primecell.h | 14 + hw/ps2.c | 614 ++ hw/ps2.h | 9 + hw/ptimer.c | 244 + hw/pxa.h | 212 + hw/pxa2xx.c | 2292 +++++ hw/pxa2xx_dma.c | 549 ++ hw/pxa2xx_gpio.c | 340 + hw/pxa2xx_keypad.c | 336 + hw/pxa2xx_lcd.c | 982 +++ hw/pxa2xx_mmci.c | 547 ++ hw/pxa2xx_pcmcia.c | 215 + hw/pxa2xx_pic.c | 313 + hw/pxa2xx_template.h | 435 + hw/pxa2xx_timer.c | 490 + hw/qdev-addr.c | 32 + hw/qdev-addr.h | 5 + hw/qdev-properties.c | 777 ++ hw/qdev.c | 926 ++ hw/qdev.h | 328 + hw/qxl-logger.c | 248 + hw/qxl-render.c | 226 + hw/qxl.c | 1531 ++++ hw/qxl.h | 108 + hw/r2d.c | 341 + hw/rc4030.c | 830 ++ hw/realview.c | 459 + hw/realview_gic.c | 79 + hw/rtl8139.c | 3431 +++++++ hw/s390-virtio-bus.c | 409 + hw/s390-virtio-bus.h | 69 + hw/s390-virtio.c | 289 + hw/sb16.c | 1420 +++ hw/sbi.c | 148 + hw/scsi-bus.c | 547 ++ hw/scsi-defs.h | 166 + hw/scsi-disk.c | 1309 +++ hw/scsi-generic.c | 576 ++ hw/scsi.h | 110 + hw/sd.c | 1660 ++++ hw/sd.h | 79 + hw/serial.c | 991 +++ hw/sh.h | 54 + hw/sh7750.c | 818 ++ hw/sh7750_regnames.c | 97 + hw/sh7750_regnames.h | 6 + hw/sh7750_regs.h | 1277 +++ hw/sh_intc.c | 481 + hw/sh_intc.h | 80 + hw/sh_pci.c | 161 + hw/sh_serial.c | 401 + hw/sh_timer.c | 327 + hw/sharpsl.h | 17 + hw/shix.c | 104 + hw/slavio_intctl.c | 463 + hw/slavio_misc.c | 499 ++ hw/slavio_timer.c | 424 + hw/sm501.c | 1457 +++ hw/sm501_template.h | 145 + hw/smbios.c | 239 + hw/smbios.h | 162 + hw/smbus.c | 318 + hw/smbus.h | 68 + hw/smbus_eeprom.c | 127 + hw/smc91c111.c | 798 ++ hw/soc_dma.c | 366 + hw/soc_dma.h | 113 + hw/sparc32_dma.c | 305 + hw/sparc32_dma.h | 12 + hw/spitz.c | 1117 +++ hw/ssd0303.c | 312 + hw/ssd0323.c | 355 + hw/ssi-sd.c | 256 + hw/ssi.c | 70 + hw/ssi.h | 45 + hw/stellaris.c | 1474 ++++ hw/stellaris_enet.c | 443 + hw/stellaris_input.c | 91 + hw/sun4c_intctl.c | 224 + hw/sun4m.c | 1821 ++++ hw/sun4m.h | 36 + hw/sun4m_iommu.c | 378 + hw/sun4u.c | 942 ++ hw/syborg.c | 112 + hw/syborg.h | 18 + hw/syborg_fb.c | 551 ++ hw/syborg_interrupt.c | 238 + hw/syborg_keyboard.c | 244 + hw/syborg_pointer.c | 243 + hw/syborg_rtc.c | 150 + hw/syborg_serial.c | 359 + hw/syborg_timer.c | 245 + hw/syborg_virtio.c | 314 + hw/sysbus.c | 203 + hw/sysbus.h | 67 + hw/tc58128.c | 183 + hw/tc6393xb.c | 608 ++ hw/tc6393xb_template.h | 67 + hw/tcx.c | 651 ++ hw/tmp105.c | 244 + hw/tosa.c | 277 + hw/tsc2005.c | 593 ++ hw/tsc210x.c | 1293 +++ hw/tusb6010.c | 766 ++ hw/twl92230.c | 876 ++ hw/unin_pci.c | 391 + hw/usb-bt.c | 568 ++ hw/usb-bus.c | 394 + hw/usb-desc.c | 406 + hw/usb-desc.h | 92 + hw/usb-hid.c | 1006 +++ hw/usb-hub.c | 590 ++ hw/usb-msd.c | 621 ++ hw/usb-musb.c | 1495 ++++ hw/usb-net.c | 1441 +++ hw/usb-ohci.c | 1792 ++++ hw/usb-ohci.h | 9 + hw/usb-serial.c | 611 ++ hw/usb-uhci.c | 1229 +++ hw/usb-uhci.h | 10 + hw/usb-wacom.c | 370 + hw/usb.c | 261 + hw/usb.h | 367 + hw/versatile_pci.c | 160 + hw/versatilepb.c | 362 + hw/vga-isa-mm.c | 128 + hw/vga-isa.c | 49 + hw/vga-pci.c | 123 + hw/vga.c | 2398 +++++ hw/vga_int.h | 232 + hw/vga_template.h | 525 ++ hw/vhost.c | 727 ++ hw/vhost.h | 50 + hw/vhost_net.c | 229 + hw/vhost_net.h | 20 + hw/virtex_ml507.c | 276 + hw/virtio-9p-debug.c | 645 ++ hw/virtio-9p-debug.h | 6 + hw/virtio-9p-local.c | 563 ++ hw/virtio-9p-posix-acl.c | 140 + hw/virtio-9p-xattr-user.c | 109 + hw/virtio-9p-xattr.c | 159 + hw/virtio-9p-xattr.h | 102 + hw/virtio-9p.c | 3744 ++++++++ hw/virtio-9p.h | 507 ++ hw/virtio-balloon.c | 283 + hw/virtio-balloon.h | 55 + hw/virtio-blk.c | 588 ++ hw/virtio-blk.h | 106 + hw/virtio-console.c | 141 + hw/virtio-net.c | 1077 +++ hw/virtio-net.h | 189 + hw/virtio-pci.c | 942 ++ hw/virtio-serial-bus.c | 908 ++ hw/virtio-serial.h | 202 + hw/virtio.c | 882 ++ hw/virtio.h | 229 + hw/vmmouse.c | 291 + hw/vmport.c | 111 + hw/vmware_vga.c | 1330 +++ hw/vmware_vga.h | 9 + hw/vt82c686.c | 594 ++ hw/vt82c686.h | 11 + hw/watchdog.c | 147 + hw/watchdog.h | 43 + hw/wdt_i6300esb.c | 448 + hw/wdt_ib700.c | 137 + hw/wm8750.c | 707 ++ hw/xen.h | 21 + hw/xen_backend.c | 714 ++ hw/xen_backend.h | 106 + hw/xen_blkif.h | 103 + hw/xen_common.h | 34 + hw/xen_console.c | 269 + hw/xen_devconfig.c | 173 + hw/xen_disk.c | 788 ++ hw/xen_domainbuild.c | 300 + hw/xen_domainbuild.h | 13 + hw/xen_machine_pv.c | 124 + hw/xen_nic.c | 421 + hw/xenfb.c | 1013 +++ hw/xilinx.h | 50 + hw/xilinx_ethlite.c | 254 + hw/xilinx_intc.c | 176 + hw/xilinx_timer.c | 235 + hw/xilinx_uartlite.c | 220 + hw/xio3130_downstream.c | 211 + hw/xio3130_downstream.h | 11 + hw/xio3130_upstream.c | 186 + hw/xio3130_upstream.h | 10 + hw/zaurus.c | 268 + i386-dis.c | 6562 ++++++++++++++ i386.ld | 153 + ia64-dis.c | 10599 ++++++++++++++++++++++ ia64.ld | 209 + input.c | 288 + ioport-user.c | 60 + ioport.c | 306 + ioport.h | 55 + iorange.h | 30 + iov.c | 70 + iov.h | 19 + json-lexer.c | 339 + json-lexer.h | 50 + json-parser.c | 577 ++ json-parser.h | 22 + json-streamer.c | 88 + json-streamer.h | 39 + kvm-all.c | 1367 +++ kvm-stub.c | 153 + kvm.h | 201 + libfdt_env.h | 40 + linux-aio.c | 261 + linux-user/alpha/syscall.h | 253 + linux-user/alpha/syscall_nr.h | 421 + linux-user/alpha/target_signal.h | 56 + linux-user/alpha/termbits.h | 264 + linux-user/arm/nwfpe/double_cpdo.c | 295 + linux-user/arm/nwfpe/extended_cpdo.c | 272 + linux-user/arm/nwfpe/fpa11.c | 237 + linux-user/arm/nwfpe/fpa11.h | 130 + linux-user/arm/nwfpe/fpa11.inl | 50 + linux-user/arm/nwfpe/fpa11_cpdo.c | 112 + linux-user/arm/nwfpe/fpa11_cpdt.c | 381 + linux-user/arm/nwfpe/fpa11_cprt.c | 283 + linux-user/arm/nwfpe/fpopcode.c | 112 + linux-user/arm/nwfpe/fpopcode.h | 390 + linux-user/arm/nwfpe/fpsr.h | 107 + linux-user/arm/nwfpe/single_cpdo.c | 252 + linux-user/arm/syscall.h | 42 + linux-user/arm/syscall_nr.h | 367 + linux-user/arm/target_signal.h | 29 + linux-user/arm/termbits.h | 216 + linux-user/cpu-uname.c | 72 + linux-user/cpu-uname.h | 1 + linux-user/cris/syscall.h | 36 + linux-user/cris/syscall_nr.h | 335 + linux-user/cris/target_signal.h | 29 + linux-user/cris/termbits.h | 213 + linux-user/elfload.c | 2496 ++++++ linux-user/errno_defs.h | 141 + linux-user/flat.h | 67 + linux-user/flatload.c | 815 ++ linux-user/i386/syscall.h | 146 + linux-user/i386/syscall_nr.h | 337 + linux-user/i386/target_signal.h | 29 + linux-user/i386/termbits.h | 226 + linux-user/ioctls.h | 332 + linux-user/linux_loop.h | 95 + linux-user/linuxload.c | 207 + linux-user/m68k-sim.c | 170 + linux-user/m68k/syscall.h | 21 + linux-user/m68k/syscall_nr.h | 330 + linux-user/m68k/target_signal.h | 29 + linux-user/m68k/termbits.h | 227 + linux-user/main.c | 3375 +++++++ linux-user/microblaze/syscall.h | 45 + linux-user/microblaze/syscall_nr.h | 369 + linux-user/microblaze/target_signal.h | 29 + linux-user/microblaze/termbits.h | 213 + linux-user/mips/syscall.h | 227 + linux-user/mips/syscall_nr.h | 334 + linux-user/mips/target_signal.h | 29 + linux-user/mips/termbits.h | 245 + linux-user/mips64/syscall.h | 221 + linux-user/mips64/syscall_nr.h | 293 + linux-user/mips64/target_signal.h | 29 + linux-user/mips64/termbits.h | 245 + linux-user/mipsn32/syscall.h | 221 + linux-user/mipsn32/syscall_nr.h | 297 + linux-user/mipsn32/target_signal.h | 29 + linux-user/mipsn32/termbits.h | 245 + linux-user/mmap.c | 759 ++ linux-user/ppc/syscall.h | 64 + linux-user/ppc/syscall_nr.h | 334 + linux-user/ppc/target_signal.h | 29 + linux-user/ppc/termbits.h | 236 + linux-user/qemu-types.h | 24 + linux-user/qemu.h | 440 + linux-user/sh4/syscall.h | 12 + linux-user/sh4/syscall_nr.h | 336 + linux-user/sh4/target_signal.h | 29 + linux-user/sh4/termbits.h | 274 + linux-user/signal.c | 4981 +++++++++++ linux-user/socket.h | 147 + linux-user/sparc/syscall.h | 9 + linux-user/sparc/syscall_nr.h | 287 + linux-user/sparc/target_signal.h | 36 + linux-user/sparc/termbits.h | 279 + linux-user/sparc64/syscall.h | 10 + linux-user/sparc64/syscall_nr.h | 324 + linux-user/sparc64/target_signal.h | 36 + linux-user/sparc64/termbits.h | 279 + linux-user/strace.c | 1356 +++ linux-user/strace.list | 1526 ++++ linux-user/syscall.c | 7514 ++++++++++++++++ linux-user/syscall_defs.h | 2207 +++++ linux-user/syscall_types.h | 183 + linux-user/uaccess.c | 65 + linux-user/vm86.c | 481 + linux-user/x86_64/syscall.h | 98 + linux-user/x86_64/syscall_nr.h | 295 + linux-user/x86_64/target_signal.h | 29 + linux-user/x86_64/termbits.h | 247 + m68k-dis.c | 5051 +++++++++++ m68k-semi.c | 407 + m68k.ld | 175 + microblaze-dis.c | 1100 +++ migration-exec.c | 145 + migration-fd.c | 130 + migration-tcp.c | 204 + migration-unix.c | 215 + migration.c | 482 + migration.h | 142 + mips-dis.c | 4873 ++++++++++ mips.ld | 224 + module.c | 80 + module.h | 38 + monitor.c | 5233 +++++++++++ monitor.h | 65 + nbd.c | 765 ++ nbd.h | 64 + net-checksum.c | 86 + net.c | 1439 +++ net.h | 184 + net/checksum.c | 85 + net/checksum.h | 29 + net/dump.c | 159 + net/dump.h | 33 + net/queue.c | 260 + net/queue.h | 71 + net/slirp.c | 771 ++ net/slirp.h | 51 + net/socket.c | 600 ++ net/socket.h | 33 + net/tap-aix.c | 61 + net/tap-bsd.c | 131 + net/tap-haiku.c | 61 + net/tap-linux.c | 195 + net/tap-linux.h | 63 + net/tap-solaris.c | 227 + net/tap-win32.c | 751 ++ net/tap.c | 525 ++ net/tap.h | 60 + net/util.c | 60 + net/util.h | 32 + net/vde.c | 131 + net/vde.h | 36 + notify.c | 39 + notify.h | 43 + os-posix.c | 384 + os-win32.c | 266 + osdep.c | 169 + osdep.h | 130 + oslib-posix.c | 157 + oslib-win32.c | 121 + path.c | 165 + pc-bios/Makefile | 19 + pc-bios/README | 29 + pc-bios/bamboo.dtb | Bin 0 -> 3179 bytes pc-bios/bamboo.dts | 234 + pc-bios/bios.bin | Bin 0 -> 131072 bytes pc-bios/gpxe-eepro100-80861209.rom | Bin 0 -> 56832 bytes pc-bios/keymaps/ar | 98 + pc-bios/keymaps/bepo | 333 + pc-bios/keymaps/common | 157 + pc-bios/keymaps/da | 120 + pc-bios/keymaps/de | 114 + pc-bios/keymaps/de-ch | 169 + pc-bios/keymaps/en-gb | 119 + pc-bios/keymaps/en-us | 35 + pc-bios/keymaps/es | 105 + pc-bios/keymaps/et | 85 + pc-bios/keymaps/fi | 124 + pc-bios/keymaps/fo | 76 + pc-bios/keymaps/fr | 181 + pc-bios/keymaps/fr-be | 134 + pc-bios/keymaps/fr-ca | 50 + pc-bios/keymaps/fr-ch | 114 + pc-bios/keymaps/hr | 125 + pc-bios/keymaps/hu | 115 + pc-bios/keymaps/is | 139 + pc-bios/keymaps/it | 115 + pc-bios/keymaps/ja | 109 + pc-bios/keymaps/lt | 57 + pc-bios/keymaps/lv | 128 + pc-bios/keymaps/mk | 101 + pc-bios/keymaps/modifiers | 18 + pc-bios/keymaps/nl | 59 + pc-bios/keymaps/nl-be | 3 + pc-bios/keymaps/no | 119 + pc-bios/keymaps/pl | 122 + pc-bios/keymaps/pt | 113 + pc-bios/keymaps/pt-br | 69 + pc-bios/keymaps/ru | 109 + pc-bios/keymaps/sl | 110 + pc-bios/keymaps/sv | 81 + pc-bios/keymaps/th | 131 + pc-bios/keymaps/tr | 123 + pc-bios/linuxboot.bin | Bin 0 -> 1025 bytes pc-bios/mpc8544ds.dtb | Bin 0 -> 12288 bytes pc-bios/mpc8544ds.dts | 122 + pc-bios/multiboot.bin | Bin 0 -> 1024 bytes pc-bios/ohw.diff | 1843 ++++ pc-bios/openbios-ppc | Bin 0 -> 729876 bytes pc-bios/openbios-sparc32 | Bin 0 -> 377388 bytes pc-bios/openbios-sparc64 | Bin 0 -> 1598328 bytes pc-bios/optionrom/Makefile | 29 + pc-bios/optionrom/linuxboot.S | 137 + pc-bios/optionrom/multiboot.S | 184 + pc-bios/optionrom/optionrom.h | 126 + pc-bios/petalogix-s3adsp1800.dtb | Bin 0 -> 8259 bytes pc-bios/ppc_rom.bin | Bin 0 -> 524288 bytes pc-bios/pxe-e1000.bin | Bin 0 -> 72192 bytes pc-bios/pxe-ne2k_pci.bin | Bin 0 -> 56320 bytes pc-bios/pxe-pcnet.bin | Bin 0 -> 56832 bytes pc-bios/pxe-rtl8139.bin | Bin 0 -> 56320 bytes pc-bios/pxe-virtio.bin | Bin 0 -> 56320 bytes pc-bios/s390-zipl.rom | Bin 0 -> 3336 bytes pc-bios/vgabios-cirrus.bin | Bin 0 -> 35840 bytes pc-bios/vgabios-qxl.bin | Bin 0 -> 40448 bytes pc-bios/vgabios-stdvga.bin | Bin 0 -> 40448 bytes pc-bios/vgabios-vmware.bin | Bin 0 -> 40448 bytes pc-bios/vgabios.bin | Bin 0 -> 40448 bytes pci-ids.txt | 31 + pflib.c | 213 + pflib.h | 20 + poison.h | 50 + posix-aio-compat.c | 661 ++ ppc-dis.c | 5412 ++++++++++++ ppc.ld | 227 + ppc64.ld | 220 + qbool.c | 68 + qbool.h | 29 + qdict-test-data.txt | 4999 +++++++++++ qdict.c | 456 + qdict.h | 67 + qemu-aio.h | 59 + qemu-barrier.h | 10 + qemu-char.c | 2629 ++++++ qemu-char.h | 123 + qemu-common.h | 368 + qemu-config.c | 673 ++ qemu-config.h | 19 + qemu-doc.texi | 2713 ++++++ qemu-error.c | 209 + qemu-error.h | 40 + qemu-img-cmds.hx | 59 + qemu-img.c | 1528 ++++ qemu-img.texi | 266 + qemu-io.c | 1831 ++++ qemu-lock.h | 237 + qemu-log.h | 93 + qemu-malloc.c | 98 + qemu-nbd.c | 499 ++ qemu-nbd.texi | 66 + qemu-objects.h | 25 + qemu-option.c | 977 ++ qemu-option.h | 135 + qemu-options.h | 41 + qemu-options.hx | 2346 +++++ qemu-os-posix.h | 54 + qemu-os-win32.h | 63 + qemu-queue.h | 449 + qemu-sockets.c | 675 ++ qemu-tech.texi | 697 ++ qemu-thread.c | 192 + qemu-thread.h | 44 + qemu-timer-common.c | 63 + qemu-timer.c | 1121 +++ qemu-timer.h | 313 + qemu-tool.c | 113 + qemu-x509.h | 9 + qemu.sasl | 34 + qemu_socket.h | 62 + qerror.c | 463 + qerror.h | 174 + qfloat.c | 68 + qfloat.h | 29 + qint.c | 67 + qint.h | 28 + qjson.c | 294 + qjson.h | 28 + qlist.c | 157 + qlist.h | 53 + qmp-commands.hx | 1776 ++++ qobject.h | 112 + qstring.c | 141 + qstring.h | 35 + range.h | 29 + readline.c | 476 + readline.h | 55 + roms/seabios/.gitignore | 2 + roms/seabios/COPYING | 674 ++ roms/seabios/COPYING.LESSER | 165 + roms/seabios/Makefile | 202 + roms/seabios/README | 190 + roms/seabios/TODO | 29 + roms/seabios/src/acpi-dsdt.dsl | 860 ++ roms/seabios/src/acpi-dsdt.hex | 1030 +++ roms/seabios/src/acpi.c | 694 ++ roms/seabios/src/acpi.h | 101 + roms/seabios/src/apm.c | 232 + roms/seabios/src/asm-offsets.c | 31 + roms/seabios/src/ata.c | 1042 +++ roms/seabios/src/ata.h | 151 + roms/seabios/src/biosvar.h | 332 + roms/seabios/src/block.c | 342 + roms/seabios/src/blockcmd.c | 78 + roms/seabios/src/blockcmd.h | 77 + roms/seabios/src/boot.c | 508 ++ roms/seabios/src/boot.h | 48 + roms/seabios/src/bootsplash.c | 258 + roms/seabios/src/bregs.h | 96 + roms/seabios/src/cdrom.c | 380 + roms/seabios/src/clock.c | 651 ++ roms/seabios/src/cmos.h | 74 + roms/seabios/src/config.h | 230 + roms/seabios/src/coreboot.c | 607 ++ roms/seabios/src/dev-i440fx.c | 116 + roms/seabios/src/dev-i440fx.h | 14 + roms/seabios/src/disk.c | 864 ++ roms/seabios/src/disk.h | 258 + roms/seabios/src/entryfuncs.S | 173 + roms/seabios/src/farptr.h | 204 + roms/seabios/src/floppy.c | 614 ++ roms/seabios/src/font.c | 139 + roms/seabios/src/gen-defs.h | 19 + roms/seabios/src/ioport.h | 134 + roms/seabios/src/jpeg.c | 1041 +++ roms/seabios/src/jpeg.h | 11 + roms/seabios/src/kbd.c | 573 ++ roms/seabios/src/lzmadecode.c | 398 + roms/seabios/src/lzmadecode.h | 67 + roms/seabios/src/memmap.c | 134 + roms/seabios/src/memmap.h | 33 + roms/seabios/src/misc.c | 190 + roms/seabios/src/mouse.c | 344 + roms/seabios/src/mptable.c | 206 + roms/seabios/src/mptable.h | 80 + roms/seabios/src/mtrr.c | 102 + roms/seabios/src/optionroms.c | 464 + roms/seabios/src/output.c | 556 ++ roms/seabios/src/paravirt.c | 359 + roms/seabios/src/paravirt.h | 101 + roms/seabios/src/pci.c | 217 + roms/seabios/src/pci.h | 138 + roms/seabios/src/pci_ids.h | 2610 ++++++ roms/seabios/src/pci_regs.h | 556 ++ roms/seabios/src/pcibios.c | 234 + roms/seabios/src/pciinit.c | 420 + roms/seabios/src/pic.c | 52 + roms/seabios/src/pic.h | 97 + roms/seabios/src/pirtable.c | 105 + roms/seabios/src/pmm.c | 602 ++ roms/seabios/src/pnpbios.c | 103 + roms/seabios/src/post.c | 274 + roms/seabios/src/ps2port.c | 460 + roms/seabios/src/ps2port.h | 64 + roms/seabios/src/ramdisk.c | 100 + roms/seabios/src/resume.c | 125 + roms/seabios/src/romlayout.S | 582 ++ roms/seabios/src/serial.c | 315 + roms/seabios/src/shadow.c | 145 + roms/seabios/src/smbios.c | 514 ++ roms/seabios/src/smbios.h | 166 + roms/seabios/src/smm.c | 127 + roms/seabios/src/smp.c | 137 + roms/seabios/src/ssdt-proc.dsl | 50 + roms/seabios/src/stacks.c | 404 + roms/seabios/src/system.c | 365 + roms/seabios/src/types.h | 138 + roms/seabios/src/usb-ehci.c | 760 ++ roms/seabios/src/usb-ehci.h | 173 + roms/seabios/src/usb-hid.c | 427 + roms/seabios/src/usb-hid.h | 32 + roms/seabios/src/usb-hub.c | 185 + roms/seabios/src/usb-hub.h | 60 + roms/seabios/src/usb-msc.c | 252 + roms/seabios/src/usb-msc.h | 27 + roms/seabios/src/usb-ohci.c | 516 ++ roms/seabios/src/usb-ohci.h | 141 + roms/seabios/src/usb-uhci.c | 603 ++ roms/seabios/src/usb-uhci.h | 128 + roms/seabios/src/usb.c | 471 + roms/seabios/src/usb.h | 214 + roms/seabios/src/util.c | 296 + roms/seabios/src/util.h | 455 + roms/seabios/src/vgahooks.c | 317 + roms/seabios/src/virtio-blk.c | 190 + roms/seabios/src/virtio-blk.h | 43 + roms/seabios/src/virtio-pci.c | 69 + roms/seabios/src/virtio-pci.h | 104 + roms/seabios/src/virtio-ring.c | 151 + roms/seabios/src/virtio-ring.h | 131 + roms/seabios/tools/buildrom.py | 43 + roms/seabios/tools/checkrom.py | 61 + roms/seabios/tools/checkstack.py | 225 + roms/seabios/tools/checksum.py | 16 + roms/seabios/tools/gen-offsets.sh | 17 + roms/seabios/tools/layoutrom.py | 436 + roms/seabios/tools/readserial.py | 129 + roms/seabios/tools/test-gcc.sh | 88 + roms/seabios/tools/transdump.py | 50 + roms/seabios/vgasrc/clext.c | 390 + roms/seabios/vgasrc/vga.c | 1387 +++ roms/seabios/vgasrc/vgaentry.S | 48 + roms/seabios/vgasrc/vgafb.c | 512 ++ roms/seabios/vgasrc/vgafonts.c | 785 ++ roms/seabios/vgasrc/vgaio.c | 555 ++ roms/seabios/vgasrc/vgalayout.lds.S | 24 + roms/seabios/vgasrc/vgatables.c | 438 + roms/seabios/vgasrc/vgatables.h | 217 + roms/vgabios/.cvsignore | 1 + roms/vgabios/BUGS | 3 + roms/vgabios/COPYING | 504 ++ roms/vgabios/ChangeLog | 1311 +++ roms/vgabios/Makefile | 103 + roms/vgabios/Notes | 11 + roms/vgabios/README | 226 + roms/vgabios/TODO | 26 + roms/vgabios/biossums.c | 282 + roms/vgabios/clext.c | 1641 ++++ roms/vgabios/dataseghack | 23 + roms/vgabios/tests/lfbprof/Makefile | 5 + roms/vgabios/tests/lfbprof/lfbprof.c | 594 ++ roms/vgabios/tests/lfbprof/lfbprof.h | 149 + roms/vgabios/tests/testbios.c | 353 + roms/vgabios/vbe.c | 1456 +++ roms/vgabios/vbe.h | 315 + roms/vgabios/vbe_display_api.txt | 237 + roms/vgabios/vbetables-gen.c | 261 + roms/vgabios/vgabios.c | 3923 +++++++++ roms/vgabios/vgabios.h | 47 + roms/vgabios/vgafonts.h | 784 ++ roms/vgabios/vgatables.h | 622 ++ rules.mak | 64 + rwhandler.c | 87 + rwhandler.h | 27 + s390-dis.c | 1796 ++++ s390.ld | 201 + savevm.c | 2149 +++++ scripts/checkpatch.pl | 2910 ++++++ scripts/create_config | 103 + scripts/feature_to_c.sh | 78 + scripts/hxtool | 102 + scripts/make_device_config.sh | 28 + scripts/qemu-binfmt-conf.sh | 66 + scripts/signrom.sh | 45 + scripts/simpletrace.py | 93 + scripts/texi2pod.pl | 477 + scripts/tracetool | 629 ++ sh4-dis.c | 2077 +++++ simpletrace.c | 255 + simpletrace.h | 40 + slirp/COPYRIGHT | 61 + slirp/bootp.c | 314 + slirp/bootp.h | 122 + slirp/cksum.c | 139 + slirp/debug.h | 34 + slirp/if.c | 209 + slirp/if.h | 25 + slirp/ip.h | 253 + slirp/ip_icmp.c | 351 + slirp/ip_icmp.h | 161 + slirp/ip_input.c | 682 ++ slirp/ip_output.c | 172 + slirp/libslirp.h | 56 + slirp/main.h | 46 + slirp/mbuf.c | 218 + slirp/mbuf.h | 127 + slirp/misc.c | 406 + slirp/misc.h | 72 + slirp/sbuf.c | 181 + slirp/sbuf.h | 30 + slirp/slirp.c | 1113 +++ slirp/slirp.h | 320 + slirp/slirp_config.h | 188 + slirp/socket.c | 726 ++ slirp/socket.h | 95 + slirp/tcp.h | 164 + slirp/tcp_input.c | 1487 ++++ slirp/tcp_output.c | 492 ++ slirp/tcp_subr.c | 915 ++ slirp/tcp_timer.c | 292 + slirp/tcp_timer.h | 127 + slirp/tcp_var.h | 161 + slirp/tcpip.h | 77 + slirp/tftp.c | 422 + slirp/tftp.h | 43 + slirp/udp.c | 384 + slirp/udp.h | 86 + softmmu-semi.h | 70 + softmmu_defs.h | 22 + softmmu_exec.h | 153 + softmmu_header.h | 198 + softmmu_template.h | 332 + sparc-dis.c | 3275 +++++++ sparc.ld | 150 + sparc64.ld | 138 + spice-qemu-char.c | 190 + sysconfigs/target/target-x86_64.conf | 86 + sysemu.h | 192 + target-alpha/STATUS | 28 + target-alpha/cpu.h | 538 ++ target-alpha/exec.h | 61 + target-alpha/helper.c | 570 ++ target-alpha/helper.h | 126 + target-alpha/op_helper.c | 1385 +++ target-alpha/translate.c | 3374 +++++++ target-arm/cpu.h | 508 ++ target-arm/exec.h | 58 + target-arm/helper.c | 2687 ++++++ target-arm/helpers.h | 458 + target-arm/iwmmxt_helper.c | 681 ++ target-arm/machine.c | 211 + target-arm/neon_helper.c | 1603 ++++ target-arm/op_addsub.h | 103 + target-arm/op_helper.c | 426 + target-arm/translate.c | 9409 ++++++++++++++++++++ target-cris/cpu.h | 268 + target-cris/crisv10-decode.h | 107 + target-cris/crisv32-decode.h | 128 + target-cris/exec.h | 53 + target-cris/helper.c | 271 + target-cris/helper.h | 26 + target-cris/machine.c | 90 + target-cris/mmu.c | 368 + target-cris/mmu.h | 17 + target-cris/op_helper.c | 655 ++ target-cris/opcode-cris.h | 365 + target-cris/translate.c | 3611 ++++++++ target-cris/translate_v10.c | 1245 +++ target-i386/TODO | 32 + target-i386/cpu.h | 987 +++ target-i386/cpuid.c | 1241 +++ target-i386/exec.h | 335 + target-i386/helper.c | 1251 +++ target-i386/helper.h | 220 + target-i386/helper_template.h | 334 + target-i386/kvm.c | 1921 ++++ target-i386/kvm_x86.h | 25 + target-i386/machine.c | 515 ++ target-i386/op_helper.c | 5669 ++++++++++++ target-i386/ops_sse.h | 2075 +++++ target-i386/ops_sse_header.h | 349 + target-i386/svm.h | 222 + target-i386/translate.c | 7915 +++++++++++++++++ target-m68k/cpu.h | 257 + target-m68k/exec.h | 50 + target-m68k/helper.c | 924 ++ target-m68k/helpers.h | 54 + target-m68k/m68k-qreg.h | 11 + target-m68k/machine.c | 0 target-m68k/op_helper.c | 225 + target-m68k/qregs.def | 13 + target-m68k/translate.c | 3120 +++++++ target-microblaze/cpu.h | 336 + target-microblaze/exec.h | 52 + target-microblaze/helper.c | 282 + target-microblaze/helper.h | 36 + target-microblaze/machine.c | 11 + target-microblaze/microblaze-decode.h | 52 + target-microblaze/mmu.c | 303 + target-microblaze/mmu.h | 91 + target-microblaze/op_helper.c | 491 ++ target-microblaze/translate.c | 1906 ++++ target-mips/TODO | 52 + target-mips/cpu.h | 659 ++ target-mips/exec.h | 98 + target-mips/helper.c | 696 ++ target-mips/helper.h | 290 + target-mips/machine.c | 308 + target-mips/mips-defs.h | 72 + target-mips/op_helper.c | 3044 +++++++ target-mips/translate.c | 12746 +++++++++++++++++++++++++++ target-mips/translate_init.c | 594 ++ target-ppc/STATUS | 559 ++ target-ppc/cpu.h | 1626 ++++ target-ppc/exec.h | 57 + target-ppc/helper.c | 2821 ++++++ target-ppc/helper.h | 404 + target-ppc/helper_regs.h | 111 + target-ppc/kvm.c | 406 + target-ppc/kvm_ppc.c | 105 + target-ppc/kvm_ppc.h | 33 + target-ppc/machine.c | 179 + target-ppc/mfrom_table.c | 79 + target-ppc/mfrom_table_gen.c | 33 + target-ppc/op_helper.c | 4172 +++++++++ target-ppc/translate.c | 9141 +++++++++++++++++++ target-ppc/translate_init.c | 9768 ++++++++++++++++++++ target-s390x/cpu.h | 269 + target-s390x/exec.h | 53 + target-s390x/helper.c | 84 + target-s390x/kvm.c | 507 ++ target-s390x/machine.c | 30 + target-s390x/op_helper.c | 73 + target-s390x/translate.c | 61 + target-sh4/README.sh4 | 150 + target-sh4/cpu.h | 364 + target-sh4/exec.h | 56 + target-sh4/helper.c | 843 ++ target-sh4/helper.h | 54 + target-sh4/machine.c | 0 target-sh4/op_helper.c | 817 ++ target-sh4/translate.c | 2077 +++++ target-sparc/TODO | 88 + target-sparc/cpu.h | 659 ++ target-sparc/exec.h | 41 + target-sparc/helper.c | 1546 ++++ target-sparc/helper.h | 166 + target-sparc/machine.c | 199 + target-sparc/op_helper.c | 4535 ++++++++++ target-sparc/translate.c | 5106 +++++++++++ targphys.h | 21 + tcg-runtime.c | 85 + tcg/LICENSE | 3 + tcg/README | 511 ++ tcg/TODO | 14 + tcg/arm/tcg-target.c | 1843 ++++ tcg/arm/tcg-target.h | 93 + tcg/hppa/tcg-target.c | 1683 ++++ tcg/hppa/tcg-target.h | 119 + tcg/i386/tcg-target.c | 1982 +++++ tcg/i386/tcg-target.h | 126 + tcg/ia64/tcg-target.c | 2390 +++++ tcg/ia64/tcg-target.h | 156 + tcg/mips/tcg-target.c | 1533 ++++ tcg/mips/tcg-target.h | 110 + tcg/ppc/tcg-target.c | 1922 ++++ tcg/ppc/tcg-target.h | 98 + tcg/ppc64/tcg-target.c | 1699 ++++ tcg/ppc64/tcg-target.h | 109 + tcg/s390/tcg-target.c | 2324 +++++ tcg/s390/tcg-target.h | 109 + tcg/sparc/tcg-target.c | 1569 ++++ tcg/sparc/tcg-target.h | 150 + tcg/tcg-op.h | 2533 ++++++ tcg/tcg-opc.h | 310 + tcg/tcg-runtime.h | 18 + tcg/tcg.c | 2175 +++++ tcg/tcg.h | 489 + tests/Makefile | 147 + tests/alpha/Makefile | 35 + tests/alpha/crt.s | 26 + tests/alpha/hello-alpha.c | 5 + tests/alpha/test-cond.c | 87 + tests/alpha/test-ovf.c | 29 + tests/cris/Makefile | 155 + tests/cris/README | 1 + tests/cris/check_abs.c | 40 + tests/cris/check_addc.c | 58 + tests/cris/check_addcm.c | 85 + tests/cris/check_addi.s | 57 + tests/cris/check_addiv32.s | 62 + tests/cris/check_addm.s | 96 + tests/cris/check_addo.c | 125 + tests/cris/check_addoq.c | 44 + tests/cris/check_addq.s | 47 + tests/cris/check_addr.s | 96 + tests/cris/check_addxc.s | 91 + tests/cris/check_addxm.s | 106 + tests/cris/check_addxr.s | 96 + tests/cris/check_andc.s | 80 + tests/cris/check_andm.s | 90 + tests/cris/check_andq.s | 46 + tests/cris/check_andr.s | 95 + tests/cris/check_asr.s | 230 + tests/cris/check_ba.s | 93 + tests/cris/check_bas.s | 102 + tests/cris/check_bcc.s | 197 + tests/cris/check_bound.c | 142 + tests/cris/check_boundc.s | 101 + tests/cris/check_boundr.s | 125 + tests/cris/check_btst.s | 96 + tests/cris/check_clearfv32.s | 19 + tests/cris/check_clrjmp1.s | 36 + tests/cris/check_cmp-2.s | 15 + tests/cris/check_cmpc.s | 86 + tests/cris/check_cmpm.s | 96 + tests/cris/check_cmpq.s | 75 + tests/cris/check_cmpr.s | 102 + tests/cris/check_cmpxc.s | 92 + tests/cris/check_cmpxm.s | 106 + tests/cris/check_dstep.s | 42 + tests/cris/check_ftag.c | 37 + tests/cris/check_gcctorture_pr28634-1.c | 15 + tests/cris/check_gcctorture_pr28634.c | 15 + tests/cris/check_glibc_kernelversion.c | 116 + tests/cris/check_hello.c | 7 + tests/cris/check_int64.c | 47 + tests/cris/check_jsr.s | 85 + tests/cris/check_lapc.s | 78 + tests/cris/check_lsl.s | 217 + tests/cris/check_lsr.s | 218 + tests/cris/check_lz.c | 49 + tests/cris/check_mapbrk.c | 39 + tests/cris/check_mcp.s | 49 + tests/cris/check_mmap1.c | 48 + tests/cris/check_mmap2.c | 48 + tests/cris/check_mmap3.c | 33 + tests/cris/check_movdelsr1.s | 33 + tests/cris/check_movecr.s | 37 + tests/cris/check_movei.s | 50 + tests/cris/check_movemr.s | 78 + tests/cris/check_movemrv32.s | 96 + tests/cris/check_moveq.c | 51 + tests/cris/check_mover.s | 28 + tests/cris/check_moverm.s | 45 + tests/cris/check_movmp.s | 131 + tests/cris/check_movpmv32.s | 35 + tests/cris/check_movpr.s | 28 + tests/cris/check_movprv32.s | 21 + tests/cris/check_movscr.s | 29 + tests/cris/check_movsm.s | 44 + tests/cris/check_movsr.s | 46 + tests/cris/check_movucr.s | 33 + tests/cris/check_movum.s | 40 + tests/cris/check_movur.s | 45 + tests/cris/check_mulv32.s | 51 + tests/cris/check_mulx.s | 246 + tests/cris/check_neg.s | 104 + tests/cris/check_not.s | 31 + tests/cris/check_openpf1.c | 38 + tests/cris/check_openpf2.c | 16 + tests/cris/check_openpf3.c | 49 + tests/cris/check_openpf4.c | 5 + tests/cris/check_openpf5.c | 56 + tests/cris/check_orc.s | 71 + tests/cris/check_orm.s | 75 + tests/cris/check_orq.s | 41 + tests/cris/check_orr.s | 84 + tests/cris/check_ret.s | 25 + tests/cris/check_scc.s | 95 + tests/cris/check_settls1.c | 45 + tests/cris/check_sigalrm.c | 26 + tests/cris/check_stat1.c | 16 + tests/cris/check_stat2.c | 20 + tests/cris/check_stat3.c | 25 + tests/cris/check_stat4.c | 27 + tests/cris/check_subc.s | 87 + tests/cris/check_subm.s | 96 + tests/cris/check_subq.s | 52 + tests/cris/check_subr.s | 102 + tests/cris/check_swap.c | 76 + tests/cris/check_time1.c | 46 + tests/cris/check_time2.c | 18 + tests/cris/check_xarith.s | 72 + tests/cris/crisutils.h | 71 + tests/cris/crt.s | 13 + tests/cris/sys.c | 51 + tests/cris/sys.h | 16 + tests/cris/testutils.inc | 117 + tests/hello-arm.c | 113 + tests/hello-i386.c | 26 + tests/hello-mips.c | 64 + tests/linux-test.c | 535 ++ tests/pi_10.com | Bin 0 -> 54 bytes tests/qruncom.c | 284 + tests/runcom.c | 192 + tests/sha1.c | 240 + tests/test-arm-iwmmxt.s | 49 + tests/test-i386-code16.S | 79 + tests/test-i386-muldiv.h | 76 + tests/test-i386-shift.h | 185 + tests/test-i386-ssse3.c | 57 + tests/test-i386-vm86.S | 103 + tests/test-i386.c | 2764 ++++++ tests/test-i386.h | 152 + tests/test-mmap.c | 476 + tests/test_path.c | 160 + tests/testthread.c | 51 + thunk.c | 288 + thunk.h | 161 + trace-events | 256 + translate-all.c | 167 + uboot_image.h | 158 + ui/cocoa.m | 1014 +++ ui/curses.c | 368 + ui/curses_keys.h | 509 ++ ui/d3des.c | 424 + ui/d3des.h | 51 + ui/keymaps.c | 210 + ui/keymaps.h | 77 + ui/qemu-spice.h | 57 + ui/sdl.c | 885 ++ ui/sdl_keysym.h | 277 + ui/sdl_zoom.c | 95 + ui/sdl_zoom.h | 25 + ui/sdl_zoom_template.h | 225 + ui/spice-core.c | 672 ++ ui/spice-display.c | 412 + ui/spice-display.h | 68 + ui/spice-input.c | 217 + ui/vnc-auth-sasl.c | 639 ++ ui/vnc-auth-sasl.h | 74 + ui/vnc-auth-vencrypt.c | 175 + ui/vnc-auth-vencrypt.h | 33 + ui/vnc-enc-hextile-template.h | 211 + ui/vnc-enc-hextile.c | 116 + ui/vnc-enc-tight.c | 1718 ++++ ui/vnc-enc-tight.h | 183 + ui/vnc-enc-zlib.c | 152 + ui/vnc-jobs-async.c | 335 + ui/vnc-jobs-sync.c | 73 + ui/vnc-jobs.h | 87 + ui/vnc-palette.c | 136 + ui/vnc-palette.h | 63 + ui/vnc-tls.c | 445 + ui/vnc-tls.h | 76 + ui/vnc.c | 2768 ++++++ ui/vnc.h | 502 ++ ui/vnc_keysym.h | 324 + ui/x_keymap.c | 168 + ui/x_keymap.h | 32 + usb-bsd.c | 643 ++ usb-linux.c | 1722 ++++ usb-stub.c | 52 + version.rc | 28 + vgafont.h | 4611 ++++++++++ vl.c | 3143 +++++++ x86_64.ld | 182 + 1592 files changed, 723063 insertions(+) create mode 100644 CODING_STYLE create mode 100644 COPYING create mode 100644 COPYING.LIB create mode 100644 Changelog create mode 100644 HACKING create mode 100644 LICENSE create mode 100644 MAINTAINERS create mode 100644 Makefile create mode 100644 Makefile.dis create mode 100644 Makefile.hw create mode 100644 Makefile.objs create mode 100644 Makefile.target create mode 100644 Makefile.user create mode 100644 QMP/README create mode 100644 QMP/qmp-events.txt create mode 100644 QMP/qmp-shell create mode 100644 QMP/qmp-spec.txt create mode 100644 QMP/qmp.py create mode 100644 README create mode 100644 TODO create mode 100644 VERSION create mode 100644 a.out.h create mode 100644 acl.c create mode 100644 acl.h create mode 100644 aes.c create mode 100644 aes.h create mode 100644 aio.c create mode 100644 alpha-dis.c create mode 100644 alpha.ld create mode 100644 arch_init.c create mode 100644 arch_init.h create mode 100644 arm-dis.c create mode 100644 arm-semi.c create mode 100644 arm.ld create mode 100644 async.c create mode 100644 audio/alsaaudio.c create mode 100644 audio/audio.c create mode 100644 audio/audio.h create mode 100644 audio/audio_int.h create mode 100644 audio/audio_pt_int.c create mode 100644 audio/audio_pt_int.h create mode 100644 audio/audio_template.h create mode 100644 audio/audio_win_int.c create mode 100644 audio/audio_win_int.h create mode 100644 audio/coreaudio.c create mode 100644 audio/dsound_template.h create mode 100644 audio/dsoundaudio.c create mode 100644 audio/esdaudio.c create mode 100644 audio/fmodaudio.c create mode 100644 audio/mixeng.c create mode 100644 audio/mixeng.h create mode 100644 audio/mixeng_template.h create mode 100644 audio/noaudio.c create mode 100644 audio/ossaudio.c create mode 100644 audio/paaudio.c create mode 100644 audio/rate_template.h create mode 100644 audio/sdlaudio.c create mode 100644 audio/spiceaudio.c create mode 100644 audio/wavaudio.c create mode 100644 audio/wavcapture.c create mode 100644 audio/winwaveaudio.c create mode 100644 balloon.c create mode 100644 balloon.h create mode 100644 block-migration.c create mode 100644 block-migration.h create mode 100644 block.c create mode 100644 block.h create mode 100644 block/blkdebug.c create mode 100644 block/blkverify.c create mode 100644 block/bochs.c create mode 100644 block/cloop.c create mode 100644 block/cow.c create mode 100644 block/curl.c create mode 100644 block/dmg.c create mode 100644 block/nbd.c create mode 100644 block/parallels.c create mode 100644 block/qcow.c create mode 100644 block/qcow2-cache.c create mode 100644 block/qcow2-cluster.c create mode 100644 block/qcow2-refcount.c create mode 100644 block/qcow2-snapshot.c create mode 100644 block/qcow2.c create mode 100644 block/qcow2.h create mode 100644 block/qed-check.c create mode 100644 block/qed-cluster.c create mode 100644 block/qed-gencb.c create mode 100644 block/qed-l2-cache.c create mode 100644 block/qed-table.c create mode 100644 block/qed.c create mode 100644 block/qed.h create mode 100644 block/raw-posix-aio.h create mode 100644 block/raw-posix.c create mode 100644 block/raw-win32.c create mode 100644 block/raw.c create mode 100644 block/rbd.c create mode 100644 block/rbd_types.h create mode 100644 block/sheepdog.c create mode 100644 block/vdi.c create mode 100644 block/vmdk.c create mode 100644 block/vpc.c create mode 100644 block/vvfat.c create mode 100644 block_int.h create mode 100644 blockdev.c create mode 100644 blockdev.h create mode 100644 bsd-user/bsd-mman.h create mode 100644 bsd-user/bsdload.c create mode 100644 bsd-user/elfload.c create mode 100644 bsd-user/errno_defs.h create mode 100644 bsd-user/freebsd/strace.list create mode 100644 bsd-user/freebsd/syscall_nr.h create mode 100644 bsd-user/i386/syscall.h create mode 100644 bsd-user/i386/target_signal.h create mode 100644 bsd-user/main.c create mode 100644 bsd-user/mmap.c create mode 100644 bsd-user/netbsd/strace.list create mode 100644 bsd-user/netbsd/syscall_nr.h create mode 100644 bsd-user/openbsd/strace.list create mode 100644 bsd-user/openbsd/syscall_nr.h create mode 100644 bsd-user/qemu-types.h create mode 100644 bsd-user/qemu.h create mode 100644 bsd-user/signal.c create mode 100644 bsd-user/sparc/syscall.h create mode 100644 bsd-user/sparc/target_signal.h create mode 100644 bsd-user/sparc64/syscall.h create mode 100644 bsd-user/sparc64/target_signal.h create mode 100644 bsd-user/strace.c create mode 100644 bsd-user/syscall.c create mode 100644 bsd-user/syscall_defs.h create mode 100644 bsd-user/uaccess.c create mode 100644 bsd-user/x86_64/syscall.h create mode 100644 bsd-user/x86_64/target_signal.h create mode 100644 bswap.h create mode 100644 bt-host.c create mode 100644 bt-host.h create mode 100644 bt-vhci.c create mode 100644 buffered_file.c create mode 100644 buffered_file.h create mode 100644 cache-utils.c create mode 100644 cache-utils.h create mode 100644 check-qdict.c create mode 100644 check-qfloat.c create mode 100644 check-qint.c create mode 100644 check-qjson.c create mode 100644 check-qlist.c create mode 100644 check-qstring.c create mode 100644 cmd.c create mode 100644 cmd.h create mode 100644 compatfd.c create mode 100644 compatfd.h create mode 100644 config.h create mode 100644 configure create mode 100644 console.c create mode 100644 console.h create mode 100644 cpu-all.h create mode 100644 cpu-common.h create mode 100644 cpu-defs.h create mode 100644 cpu-exec.c create mode 100644 cpus.c create mode 100644 cpus.h create mode 100644 cris-dis.c create mode 100644 cursor.c create mode 100644 cursor_hidden.xpm create mode 100644 cursor_left_ptr.xpm create mode 100644 cutils.c create mode 100644 darwin-user/commpage.c create mode 100644 darwin-user/ioctls.h create mode 100644 darwin-user/ioctls_types.h create mode 100644 darwin-user/machload.c create mode 100644 darwin-user/main.c create mode 100644 darwin-user/mmap.c create mode 100644 darwin-user/qemu.h create mode 100644 darwin-user/signal.c create mode 100644 darwin-user/syscall.c create mode 100644 darwin-user/syscalls.h create mode 100644 def-helper.h create mode 100644 default-configs/alpha-linux-user.mak create mode 100644 default-configs/arm-linux-user.mak create mode 100644 default-configs/arm-softmmu.mak create mode 100644 default-configs/armeb-linux-user.mak create mode 100644 default-configs/cris-linux-user.mak create mode 100644 default-configs/cris-softmmu.mak create mode 100644 default-configs/i386-bsd-user.mak create mode 100644 default-configs/i386-darwin-user.mak create mode 100644 default-configs/i386-linux-user.mak create mode 100644 default-configs/i386-softmmu.mak create mode 100644 default-configs/m68k-linux-user.mak create mode 100644 default-configs/m68k-softmmu.mak create mode 100644 default-configs/microblaze-linux-user.mak create mode 100644 default-configs/microblaze-softmmu.mak create mode 100644 default-configs/mips-linux-user.mak create mode 100644 default-configs/mips-softmmu.mak create mode 100644 default-configs/mips64-softmmu.mak create mode 100644 default-configs/mips64el-softmmu.mak create mode 100644 default-configs/mipsel-linux-user.mak create mode 100644 default-configs/mipsel-softmmu.mak create mode 100644 default-configs/pci.mak create mode 100644 default-configs/ppc-darwin-user.mak create mode 100644 default-configs/ppc-linux-user.mak create mode 100644 default-configs/ppc-softmmu.mak create mode 100644 default-configs/ppc64-linux-user.mak create mode 100644 default-configs/ppc64-softmmu.mak create mode 100644 default-configs/ppc64abi32-linux-user.mak create mode 100644 default-configs/ppcemb-softmmu.mak create mode 100644 default-configs/s390x-softmmu.mak create mode 100644 default-configs/sh4-linux-user.mak create mode 100644 default-configs/sh4-softmmu.mak create mode 100644 default-configs/sh4eb-linux-user.mak create mode 100644 default-configs/sh4eb-softmmu.mak create mode 100644 default-configs/sparc-bsd-user.mak create mode 100644 default-configs/sparc-linux-user.mak create mode 100644 default-configs/sparc-softmmu.mak create mode 100644 default-configs/sparc32plus-linux-user.mak create mode 100644 default-configs/sparc64-bsd-user.mak create mode 100644 default-configs/sparc64-linux-user.mak create mode 100644 default-configs/sparc64-softmmu.mak create mode 100644 default-configs/x86_64-bsd-user.mak create mode 100644 default-configs/x86_64-linux-user.mak create mode 100644 default-configs/x86_64-softmmu.mak create mode 100644 device_tree.c create mode 100644 device_tree.h create mode 100644 dis-asm.h create mode 100644 disas.c create mode 100644 disas.h create mode 100644 dma-helpers.c create mode 100644 dma.h create mode 100644 docs/blkverify.txt create mode 100644 docs/bootindex.txt create mode 100644 docs/migration.txt create mode 100644 docs/qdev-device-use.txt create mode 100644 docs/specs/acpi_pci_hotplug.txt create mode 100644 docs/specs/ivshmem_device_spec.txt create mode 100644 docs/specs/qed_spec.txt create mode 100644 docs/tracing.txt create mode 100644 dyngen-exec.h create mode 100644 elf.h create mode 100644 envlist.c create mode 100644 envlist.h create mode 100644 exec-all.h create mode 100644 exec.c create mode 100644 fpu/softfloat-macros.h create mode 100644 fpu/softfloat-native.c create mode 100644 fpu/softfloat-native.h create mode 100644 fpu/softfloat-specialize.h create mode 100644 fpu/softfloat.c create mode 100644 fpu/softfloat.h create mode 100644 fsdev/qemu-fsdev.c create mode 100644 fsdev/qemu-fsdev.h create mode 100644 gdb-xml/arm-core.xml create mode 100644 gdb-xml/arm-neon.xml create mode 100644 gdb-xml/arm-vfp.xml create mode 100644 gdb-xml/arm-vfp3.xml create mode 100644 gdb-xml/cf-core.xml create mode 100644 gdb-xml/cf-fp.xml create mode 100644 gdb-xml/power-altivec.xml create mode 100644 gdb-xml/power-core.xml create mode 100644 gdb-xml/power-fpu.xml create mode 100644 gdb-xml/power-spe.xml create mode 100644 gdb-xml/power64-core.xml create mode 100644 gdbstub.c create mode 100644 gdbstub.h create mode 100644 gen-icount.h create mode 100644 hmp-commands.hx create mode 100644 host-utils.c create mode 100644 host-utils.h create mode 100644 hpet.h create mode 100644 hppa-dis.c create mode 100644 hppa.ld create mode 100644 hw/9p.h create mode 100644 hw/a9mpcore.c create mode 100644 hw/ac97.c create mode 100644 hw/acpi.c create mode 100644 hw/acpi.h create mode 100644 hw/acpi_piix4.c create mode 100644 hw/adb.c create mode 100644 hw/adlib.c create mode 100644 hw/ads7846.c create mode 100644 hw/alpha_palcode.c create mode 100644 hw/an5206.c create mode 100644 hw/apb_pci.c create mode 100644 hw/apb_pci.h create mode 100644 hw/apic.c create mode 100644 hw/apic.h create mode 100644 hw/apm.c create mode 100644 hw/apm.h create mode 100644 hw/applesmc.c create mode 100644 hw/arm-misc.h create mode 100644 hw/arm11mpcore.c create mode 100644 hw/arm_boot.c create mode 100644 hw/arm_gic.c create mode 100644 hw/arm_pic.c create mode 100644 hw/arm_sysctl.c create mode 100644 hw/arm_timer.c create mode 100644 hw/armv7m.c create mode 100644 hw/armv7m_nvic.c create mode 100644 hw/audiodev.h create mode 100644 hw/axis_dev88.c create mode 100644 hw/baum.c create mode 100644 hw/baum.h create mode 100644 hw/bitbang_i2c.c create mode 100644 hw/bitbang_i2c.h create mode 100644 hw/blizzard.c create mode 100644 hw/blizzard_template.h create mode 100644 hw/boards.h create mode 100644 hw/bonito.c create mode 100644 hw/bt-hci-csr.c create mode 100644 hw/bt-hci.c create mode 100644 hw/bt-hid.c create mode 100644 hw/bt-l2cap.c create mode 100644 hw/bt-sdp.c create mode 100644 hw/bt.c create mode 100644 hw/bt.h create mode 100644 hw/cbus.c create mode 100644 hw/cdrom.c create mode 100644 hw/cirrus_vga.c create mode 100644 hw/cirrus_vga_rop.h create mode 100644 hw/cirrus_vga_rop2.h create mode 100644 hw/cris-boot.c create mode 100644 hw/cris-boot.h create mode 100644 hw/cris_pic_cpu.c create mode 100644 hw/cs4231.c create mode 100644 hw/cs4231a.c create mode 100644 hw/cuda.c create mode 100644 hw/debugcon.c create mode 100644 hw/dec_pci.c create mode 100644 hw/dec_pci.h create mode 100644 hw/device-hotplug.c create mode 100644 hw/devices.h create mode 100644 hw/dma.c create mode 100644 hw/dp8393x.c create mode 100644 hw/ds1225y.c create mode 100644 hw/ds1338.c create mode 100644 hw/dummy_m68k.c create mode 100644 hw/e1000.c create mode 100644 hw/e1000_hw.h create mode 100644 hw/ecc.c create mode 100644 hw/eccmemctl.c create mode 100644 hw/eepro100.c create mode 100644 hw/eeprom93xx.c create mode 100644 hw/eeprom93xx.h create mode 100644 hw/elf_ops.h create mode 100644 hw/empty_slot.c create mode 100644 hw/empty_slot.h create mode 100644 hw/es1370.c create mode 100644 hw/escc.c create mode 100644 hw/escc.h create mode 100644 hw/esp.c create mode 100644 hw/esp.h create mode 100644 hw/etraxfs.c create mode 100644 hw/etraxfs.h create mode 100644 hw/etraxfs_dma.c create mode 100644 hw/etraxfs_dma.h create mode 100644 hw/etraxfs_eth.c create mode 100644 hw/etraxfs_pic.c create mode 100644 hw/etraxfs_ser.c create mode 100644 hw/etraxfs_timer.c create mode 100644 hw/event_notifier.c create mode 100644 hw/event_notifier.h create mode 100644 hw/fdc.c create mode 100644 hw/fdc.h create mode 100644 hw/file-op-9p.h create mode 100644 hw/firmware_abi.h create mode 100644 hw/flash.h create mode 100644 hw/fmopl.c create mode 100644 hw/fmopl.h create mode 100644 hw/framebuffer.c create mode 100644 hw/framebuffer.h create mode 100644 hw/fw_cfg.c create mode 100644 hw/fw_cfg.h create mode 100644 hw/g364fb.c create mode 100644 hw/grackle_pci.c create mode 100644 hw/grlib.h create mode 100644 hw/grlib_apbuart.c create mode 100644 hw/grlib_gptimer.c create mode 100644 hw/grlib_irqmp.c create mode 100644 hw/gt64xxx.c create mode 100644 hw/gumstix.c create mode 100644 hw/gus.c create mode 100644 hw/gusemu.h create mode 100644 hw/gusemu_hal.c create mode 100644 hw/gusemu_mixer.c create mode 100644 hw/gustate.h create mode 100644 hw/hda-audio.c create mode 100644 hw/heathrow_pic.c create mode 100644 hw/hpet.c create mode 100644 hw/hpet_emul.h create mode 100644 hw/hw.h create mode 100644 hw/i2c.c create mode 100644 hw/i2c.h create mode 100644 hw/i8254.c create mode 100644 hw/i8259.c create mode 100644 hw/ide.h create mode 100644 hw/ide/ahci.c create mode 100644 hw/ide/ahci.h create mode 100644 hw/ide/cmd646.c create mode 100644 hw/ide/core.c create mode 100644 hw/ide/ich.c create mode 100644 hw/ide/internal.h create mode 100644 hw/ide/isa.c create mode 100644 hw/ide/macio.c create mode 100644 hw/ide/microdrive.c create mode 100644 hw/ide/mmio.c create mode 100644 hw/ide/pci.c create mode 100644 hw/ide/pci.h create mode 100644 hw/ide/piix.c create mode 100644 hw/ide/qdev.c create mode 100644 hw/ide/via.c create mode 100644 hw/integratorcp.c create mode 100644 hw/intel-hda-defs.h create mode 100644 hw/intel-hda.c create mode 100644 hw/intel-hda.h create mode 100644 hw/ioapic.c create mode 100644 hw/ioapic.h create mode 100644 hw/ioh3420.c create mode 100644 hw/ioh3420.h create mode 100644 hw/irq.c create mode 100644 hw/irq.h create mode 100644 hw/isa-bus.c create mode 100644 hw/isa.h create mode 100644 hw/isa_mmio.c create mode 100644 hw/ivshmem.c create mode 100644 hw/jazz_led.c create mode 100644 hw/lan9118.c create mode 100644 hw/lance.c create mode 100644 hw/leon3.c create mode 100644 hw/lm832x.c create mode 100644 hw/loader.c create mode 100644 hw/loader.h create mode 100644 hw/lsi53c895a.c create mode 100644 hw/m48t59.c create mode 100644 hw/mac_dbdma.c create mode 100644 hw/mac_dbdma.h create mode 100644 hw/mac_nvram.c create mode 100644 hw/macio.c create mode 100644 hw/mainstone.c create mode 100644 hw/mainstone.h create mode 100644 hw/marvell_88w8618_audio.c create mode 100644 hw/max111x.c create mode 100644 hw/max7310.c create mode 100644 hw/mc146818rtc.c create mode 100644 hw/mc146818rtc.h create mode 100644 hw/mcf.h create mode 100644 hw/mcf5206.c create mode 100644 hw/mcf5208.c create mode 100644 hw/mcf_fec.c create mode 100644 hw/mcf_intc.c create mode 100644 hw/mcf_uart.c create mode 100644 hw/microblaze_pic_cpu.c create mode 100644 hw/mips-bios.h create mode 100644 hw/mips.h create mode 100644 hw/mips_addr.c create mode 100644 hw/mips_cpudevs.h create mode 100644 hw/mips_fulong2e.c create mode 100644 hw/mips_int.c create mode 100644 hw/mips_jazz.c create mode 100644 hw/mips_malta.c create mode 100644 hw/mips_mipssim.c create mode 100644 hw/mips_r4k.c create mode 100644 hw/mips_timer.c create mode 100644 hw/mipsnet.c create mode 100644 hw/mpcore.c create mode 100644 hw/msi.c create mode 100644 hw/msi.h create mode 100644 hw/msix.c create mode 100644 hw/msix.h create mode 100644 hw/msmouse.c create mode 100644 hw/msmouse.h create mode 100644 hw/mst_fpga.c create mode 100644 hw/multiboot.c create mode 100644 hw/multiboot.h create mode 100644 hw/musicpal.c create mode 100644 hw/nand.c create mode 100644 hw/ne2000-isa.c create mode 100644 hw/ne2000.c create mode 100644 hw/ne2000.h create mode 100644 hw/nseries.c create mode 100644 hw/nvram.h create mode 100644 hw/omap.h create mode 100644 hw/omap1.c create mode 100644 hw/omap2.c create mode 100644 hw/omap_clk.c create mode 100644 hw/omap_dma.c create mode 100644 hw/omap_dss.c create mode 100644 hw/omap_gpio.c create mode 100644 hw/omap_gpmc.c create mode 100644 hw/omap_gptimer.c create mode 100644 hw/omap_i2c.c create mode 100644 hw/omap_intc.c create mode 100644 hw/omap_l4.c create mode 100644 hw/omap_lcd_template.h create mode 100644 hw/omap_lcdc.c create mode 100644 hw/omap_mmc.c create mode 100644 hw/omap_sdrc.c create mode 100644 hw/omap_spi.c create mode 100644 hw/omap_sx1.c create mode 100644 hw/omap_synctimer.c create mode 100644 hw/omap_tap.c create mode 100644 hw/omap_uart.c create mode 100644 hw/onenand.c create mode 100644 hw/openpic.c create mode 100644 hw/openpic.h create mode 100644 hw/palm.c create mode 100644 hw/parallel.c create mode 100644 hw/pc.c create mode 100644 hw/pc.h create mode 100644 hw/pc_piix.c create mode 100644 hw/pci-hotplug.c create mode 100644 hw/pci-stub.c create mode 100644 hw/pci.c create mode 100644 hw/pci.h create mode 100644 hw/pci_bridge.c create mode 100644 hw/pci_bridge.h create mode 100644 hw/pci_host.c create mode 100644 hw/pci_host.h create mode 100644 hw/pci_ids.h create mode 100644 hw/pci_internals.h create mode 100644 hw/pci_regs.h create mode 100644 hw/pcie.c create mode 100644 hw/pcie.h create mode 100644 hw/pcie_aer.c create mode 100644 hw/pcie_aer.h create mode 100644 hw/pcie_host.c create mode 100644 hw/pcie_host.h create mode 100644 hw/pcie_port.c create mode 100644 hw/pcie_port.h create mode 100644 hw/pcie_regs.h create mode 100644 hw/pckbd.c create mode 100644 hw/pcmcia.h create mode 100644 hw/pcnet-pci.c create mode 100644 hw/pcnet.c create mode 100644 hw/pcnet.h create mode 100644 hw/pcspk.c create mode 100644 hw/petalogix_s3adsp1800_mmu.c create mode 100644 hw/pflash_cfi01.c create mode 100644 hw/pflash_cfi02.c create mode 100644 hw/piix4.c create mode 100644 hw/piix_pci.c create mode 100644 hw/pixel_ops.h create mode 100644 hw/pl011.c create mode 100644 hw/pl022.c create mode 100644 hw/pl031.c create mode 100644 hw/pl050.c create mode 100644 hw/pl061.c create mode 100644 hw/pl080.c create mode 100644 hw/pl110.c create mode 100644 hw/pl110_template.h create mode 100644 hw/pl181.c create mode 100644 hw/pl190.c create mode 100644 hw/pm_smbus.c create mode 100644 hw/pm_smbus.h create mode 100644 hw/ppc.c create mode 100644 hw/ppc.h create mode 100644 hw/ppc405.h create mode 100644 hw/ppc405_boards.c create mode 100644 hw/ppc405_uc.c create mode 100644 hw/ppc440.c create mode 100644 hw/ppc440.h create mode 100644 hw/ppc440_bamboo.c create mode 100644 hw/ppc4xx.h create mode 100644 hw/ppc4xx_devs.c create mode 100644 hw/ppc4xx_pci.c create mode 100644 hw/ppc_mac.h create mode 100644 hw/ppc_newworld.c create mode 100644 hw/ppc_oldworld.c create mode 100644 hw/ppc_prep.c create mode 100644 hw/ppce500.h create mode 100644 hw/ppce500_mpc8544ds.c create mode 100644 hw/ppce500_pci.c create mode 100644 hw/prep_pci.c create mode 100644 hw/prep_pci.h create mode 100644 hw/primecell.h create mode 100644 hw/ps2.c create mode 100644 hw/ps2.h create mode 100644 hw/ptimer.c create mode 100644 hw/pxa.h create mode 100644 hw/pxa2xx.c create mode 100644 hw/pxa2xx_dma.c create mode 100644 hw/pxa2xx_gpio.c create mode 100644 hw/pxa2xx_keypad.c create mode 100644 hw/pxa2xx_lcd.c create mode 100644 hw/pxa2xx_mmci.c create mode 100644 hw/pxa2xx_pcmcia.c create mode 100644 hw/pxa2xx_pic.c create mode 100644 hw/pxa2xx_template.h create mode 100644 hw/pxa2xx_timer.c create mode 100644 hw/qdev-addr.c create mode 100644 hw/qdev-addr.h create mode 100644 hw/qdev-properties.c create mode 100644 hw/qdev.c create mode 100644 hw/qdev.h create mode 100644 hw/qxl-logger.c create mode 100644 hw/qxl-render.c create mode 100644 hw/qxl.c create mode 100644 hw/qxl.h create mode 100644 hw/r2d.c create mode 100644 hw/rc4030.c create mode 100644 hw/realview.c create mode 100644 hw/realview_gic.c create mode 100644 hw/rtl8139.c create mode 100644 hw/s390-virtio-bus.c create mode 100644 hw/s390-virtio-bus.h create mode 100644 hw/s390-virtio.c create mode 100644 hw/sb16.c create mode 100644 hw/sbi.c create mode 100644 hw/scsi-bus.c create mode 100644 hw/scsi-defs.h create mode 100644 hw/scsi-disk.c create mode 100644 hw/scsi-generic.c create mode 100644 hw/scsi.h create mode 100644 hw/sd.c create mode 100644 hw/sd.h create mode 100644 hw/serial.c create mode 100644 hw/sh.h create mode 100644 hw/sh7750.c create mode 100644 hw/sh7750_regnames.c create mode 100644 hw/sh7750_regnames.h create mode 100644 hw/sh7750_regs.h create mode 100644 hw/sh_intc.c create mode 100644 hw/sh_intc.h create mode 100644 hw/sh_pci.c create mode 100644 hw/sh_serial.c create mode 100644 hw/sh_timer.c create mode 100644 hw/sharpsl.h create mode 100644 hw/shix.c create mode 100644 hw/slavio_intctl.c create mode 100644 hw/slavio_misc.c create mode 100644 hw/slavio_timer.c create mode 100644 hw/sm501.c create mode 100644 hw/sm501_template.h create mode 100644 hw/smbios.c create mode 100644 hw/smbios.h create mode 100644 hw/smbus.c create mode 100644 hw/smbus.h create mode 100644 hw/smbus_eeprom.c create mode 100644 hw/smc91c111.c create mode 100644 hw/soc_dma.c create mode 100644 hw/soc_dma.h create mode 100644 hw/sparc32_dma.c create mode 100644 hw/sparc32_dma.h create mode 100644 hw/spitz.c create mode 100644 hw/ssd0303.c create mode 100644 hw/ssd0323.c create mode 100644 hw/ssi-sd.c create mode 100644 hw/ssi.c create mode 100644 hw/ssi.h create mode 100644 hw/stellaris.c create mode 100644 hw/stellaris_enet.c create mode 100644 hw/stellaris_input.c create mode 100644 hw/sun4c_intctl.c create mode 100644 hw/sun4m.c create mode 100644 hw/sun4m.h create mode 100644 hw/sun4m_iommu.c create mode 100644 hw/sun4u.c create mode 100644 hw/syborg.c create mode 100644 hw/syborg.h create mode 100644 hw/syborg_fb.c create mode 100644 hw/syborg_interrupt.c create mode 100644 hw/syborg_keyboard.c create mode 100644 hw/syborg_pointer.c create mode 100644 hw/syborg_rtc.c create mode 100644 hw/syborg_serial.c create mode 100644 hw/syborg_timer.c create mode 100644 hw/syborg_virtio.c create mode 100644 hw/sysbus.c create mode 100644 hw/sysbus.h create mode 100644 hw/tc58128.c create mode 100644 hw/tc6393xb.c create mode 100644 hw/tc6393xb_template.h create mode 100644 hw/tcx.c create mode 100644 hw/tmp105.c create mode 100644 hw/tosa.c create mode 100644 hw/tsc2005.c create mode 100644 hw/tsc210x.c create mode 100644 hw/tusb6010.c create mode 100644 hw/twl92230.c create mode 100644 hw/unin_pci.c create mode 100644 hw/usb-bt.c create mode 100644 hw/usb-bus.c create mode 100644 hw/usb-desc.c create mode 100644 hw/usb-desc.h create mode 100644 hw/usb-hid.c create mode 100644 hw/usb-hub.c create mode 100644 hw/usb-msd.c create mode 100644 hw/usb-musb.c create mode 100644 hw/usb-net.c create mode 100644 hw/usb-ohci.c create mode 100644 hw/usb-ohci.h create mode 100644 hw/usb-serial.c create mode 100644 hw/usb-uhci.c create mode 100644 hw/usb-uhci.h create mode 100644 hw/usb-wacom.c create mode 100644 hw/usb.c create mode 100644 hw/usb.h create mode 100644 hw/versatile_pci.c create mode 100644 hw/versatilepb.c create mode 100644 hw/vga-isa-mm.c create mode 100644 hw/vga-isa.c create mode 100644 hw/vga-pci.c create mode 100644 hw/vga.c create mode 100644 hw/vga_int.h create mode 100644 hw/vga_template.h create mode 100644 hw/vhost.c create mode 100644 hw/vhost.h create mode 100644 hw/vhost_net.c create mode 100644 hw/vhost_net.h create mode 100644 hw/virtex_ml507.c create mode 100644 hw/virtio-9p-debug.c create mode 100644 hw/virtio-9p-debug.h create mode 100644 hw/virtio-9p-local.c create mode 100644 hw/virtio-9p-posix-acl.c create mode 100644 hw/virtio-9p-xattr-user.c create mode 100644 hw/virtio-9p-xattr.c create mode 100644 hw/virtio-9p-xattr.h create mode 100644 hw/virtio-9p.c create mode 100644 hw/virtio-9p.h create mode 100644 hw/virtio-balloon.c create mode 100644 hw/virtio-balloon.h create mode 100644 hw/virtio-blk.c create mode 100644 hw/virtio-blk.h create mode 100644 hw/virtio-console.c create mode 100644 hw/virtio-net.c create mode 100644 hw/virtio-net.h create mode 100644 hw/virtio-pci.c create mode 100644 hw/virtio-serial-bus.c create mode 100644 hw/virtio-serial.h create mode 100644 hw/virtio.c create mode 100644 hw/virtio.h create mode 100644 hw/vmmouse.c create mode 100644 hw/vmport.c create mode 100644 hw/vmware_vga.c create mode 100644 hw/vmware_vga.h create mode 100644 hw/vt82c686.c create mode 100644 hw/vt82c686.h create mode 100644 hw/watchdog.c create mode 100644 hw/watchdog.h create mode 100644 hw/wdt_i6300esb.c create mode 100644 hw/wdt_ib700.c create mode 100644 hw/wm8750.c create mode 100644 hw/xen.h create mode 100644 hw/xen_backend.c create mode 100644 hw/xen_backend.h create mode 100644 hw/xen_blkif.h create mode 100644 hw/xen_common.h create mode 100644 hw/xen_console.c create mode 100644 hw/xen_devconfig.c create mode 100644 hw/xen_disk.c create mode 100644 hw/xen_domainbuild.c create mode 100644 hw/xen_domainbuild.h create mode 100644 hw/xen_machine_pv.c create mode 100644 hw/xen_nic.c create mode 100644 hw/xenfb.c create mode 100644 hw/xilinx.h create mode 100644 hw/xilinx_ethlite.c create mode 100644 hw/xilinx_intc.c create mode 100644 hw/xilinx_timer.c create mode 100644 hw/xilinx_uartlite.c create mode 100644 hw/xio3130_downstream.c create mode 100644 hw/xio3130_downstream.h create mode 100644 hw/xio3130_upstream.c create mode 100644 hw/xio3130_upstream.h create mode 100644 hw/zaurus.c create mode 100644 i386-dis.c create mode 100644 i386.ld create mode 100644 ia64-dis.c create mode 100644 ia64.ld create mode 100644 input.c create mode 100644 ioport-user.c create mode 100644 ioport.c create mode 100644 ioport.h create mode 100644 iorange.h create mode 100644 iov.c create mode 100644 iov.h create mode 100644 json-lexer.c create mode 100644 json-lexer.h create mode 100644 json-parser.c create mode 100644 json-parser.h create mode 100644 json-streamer.c create mode 100644 json-streamer.h create mode 100644 kvm-all.c create mode 100644 kvm-stub.c create mode 100644 kvm.h create mode 100644 libfdt_env.h create mode 100644 linux-aio.c create mode 100644 linux-user/alpha/syscall.h create mode 100644 linux-user/alpha/syscall_nr.h create mode 100644 linux-user/alpha/target_signal.h create mode 100644 linux-user/alpha/termbits.h create mode 100644 linux-user/arm/nwfpe/double_cpdo.c create mode 100644 linux-user/arm/nwfpe/extended_cpdo.c create mode 100644 linux-user/arm/nwfpe/fpa11.c create mode 100644 linux-user/arm/nwfpe/fpa11.h create mode 100644 linux-user/arm/nwfpe/fpa11.inl create mode 100644 linux-user/arm/nwfpe/fpa11_cpdo.c create mode 100644 linux-user/arm/nwfpe/fpa11_cpdt.c create mode 100644 linux-user/arm/nwfpe/fpa11_cprt.c create mode 100644 linux-user/arm/nwfpe/fpopcode.c create mode 100644 linux-user/arm/nwfpe/fpopcode.h create mode 100644 linux-user/arm/nwfpe/fpsr.h create mode 100644 linux-user/arm/nwfpe/single_cpdo.c create mode 100644 linux-user/arm/syscall.h create mode 100644 linux-user/arm/syscall_nr.h create mode 100644 linux-user/arm/target_signal.h create mode 100644 linux-user/arm/termbits.h create mode 100644 linux-user/cpu-uname.c create mode 100644 linux-user/cpu-uname.h create mode 100644 linux-user/cris/syscall.h create mode 100644 linux-user/cris/syscall_nr.h create mode 100644 linux-user/cris/target_signal.h create mode 100644 linux-user/cris/termbits.h create mode 100644 linux-user/elfload.c create mode 100644 linux-user/errno_defs.h create mode 100644 linux-user/flat.h create mode 100644 linux-user/flatload.c create mode 100644 linux-user/i386/syscall.h create mode 100644 linux-user/i386/syscall_nr.h create mode 100644 linux-user/i386/target_signal.h create mode 100644 linux-user/i386/termbits.h create mode 100644 linux-user/ioctls.h create mode 100644 linux-user/linux_loop.h create mode 100644 linux-user/linuxload.c create mode 100644 linux-user/m68k-sim.c create mode 100644 linux-user/m68k/syscall.h create mode 100644 linux-user/m68k/syscall_nr.h create mode 100644 linux-user/m68k/target_signal.h create mode 100644 linux-user/m68k/termbits.h create mode 100644 linux-user/main.c create mode 100644 linux-user/microblaze/syscall.h create mode 100644 linux-user/microblaze/syscall_nr.h create mode 100644 linux-user/microblaze/target_signal.h create mode 100644 linux-user/microblaze/termbits.h create mode 100644 linux-user/mips/syscall.h create mode 100644 linux-user/mips/syscall_nr.h create mode 100644 linux-user/mips/target_signal.h create mode 100644 linux-user/mips/termbits.h create mode 100644 linux-user/mips64/syscall.h create mode 100644 linux-user/mips64/syscall_nr.h create mode 100644 linux-user/mips64/target_signal.h create mode 100644 linux-user/mips64/termbits.h create mode 100644 linux-user/mipsn32/syscall.h create mode 100644 linux-user/mipsn32/syscall_nr.h create mode 100644 linux-user/mipsn32/target_signal.h create mode 100644 linux-user/mipsn32/termbits.h create mode 100644 linux-user/mmap.c create mode 100644 linux-user/ppc/syscall.h create mode 100644 linux-user/ppc/syscall_nr.h create mode 100644 linux-user/ppc/target_signal.h create mode 100644 linux-user/ppc/termbits.h create mode 100644 linux-user/qemu-types.h create mode 100644 linux-user/qemu.h create mode 100644 linux-user/sh4/syscall.h create mode 100644 linux-user/sh4/syscall_nr.h create mode 100644 linux-user/sh4/target_signal.h create mode 100644 linux-user/sh4/termbits.h create mode 100644 linux-user/signal.c create mode 100644 linux-user/socket.h create mode 100644 linux-user/sparc/syscall.h create mode 100644 linux-user/sparc/syscall_nr.h create mode 100644 linux-user/sparc/target_signal.h create mode 100644 linux-user/sparc/termbits.h create mode 100644 linux-user/sparc64/syscall.h create mode 100644 linux-user/sparc64/syscall_nr.h create mode 100644 linux-user/sparc64/target_signal.h create mode 100644 linux-user/sparc64/termbits.h create mode 100644 linux-user/strace.c create mode 100644 linux-user/strace.list create mode 100644 linux-user/syscall.c create mode 100644 linux-user/syscall_defs.h create mode 100644 linux-user/syscall_types.h create mode 100644 linux-user/uaccess.c create mode 100644 linux-user/vm86.c create mode 100644 linux-user/x86_64/syscall.h create mode 100644 linux-user/x86_64/syscall_nr.h create mode 100644 linux-user/x86_64/target_signal.h create mode 100644 linux-user/x86_64/termbits.h create mode 100644 m68k-dis.c create mode 100644 m68k-semi.c create mode 100644 m68k.ld create mode 100644 microblaze-dis.c create mode 100644 migration-exec.c create mode 100644 migration-fd.c create mode 100644 migration-tcp.c create mode 100644 migration-unix.c create mode 100644 migration.c create mode 100644 migration.h create mode 100644 mips-dis.c create mode 100644 mips.ld create mode 100644 module.c create mode 100644 module.h create mode 100644 monitor.c create mode 100644 monitor.h create mode 100644 nbd.c create mode 100644 nbd.h create mode 100644 net-checksum.c create mode 100644 net.c create mode 100644 net.h create mode 100644 net/checksum.c create mode 100644 net/checksum.h create mode 100644 net/dump.c create mode 100644 net/dump.h create mode 100644 net/queue.c create mode 100644 net/queue.h create mode 100644 net/slirp.c create mode 100644 net/slirp.h create mode 100644 net/socket.c create mode 100644 net/socket.h create mode 100644 net/tap-aix.c create mode 100644 net/tap-bsd.c create mode 100644 net/tap-haiku.c create mode 100644 net/tap-linux.c create mode 100644 net/tap-linux.h create mode 100644 net/tap-solaris.c create mode 100644 net/tap-win32.c create mode 100644 net/tap.c create mode 100644 net/tap.h create mode 100644 net/util.c create mode 100644 net/util.h create mode 100644 net/vde.c create mode 100644 net/vde.h create mode 100644 notify.c create mode 100644 notify.h create mode 100644 os-posix.c create mode 100644 os-win32.c create mode 100644 osdep.c create mode 100644 osdep.h create mode 100644 oslib-posix.c create mode 100644 oslib-win32.c create mode 100644 path.c create mode 100644 pc-bios/Makefile create mode 100644 pc-bios/README create mode 100644 pc-bios/bamboo.dtb create mode 100644 pc-bios/bamboo.dts create mode 100644 pc-bios/bios.bin create mode 100644 pc-bios/gpxe-eepro100-80861209.rom create mode 100644 pc-bios/keymaps/ar create mode 100644 pc-bios/keymaps/bepo create mode 100644 pc-bios/keymaps/common create mode 100644 pc-bios/keymaps/da create mode 100644 pc-bios/keymaps/de create mode 100644 pc-bios/keymaps/de-ch create mode 100644 pc-bios/keymaps/en-gb create mode 100644 pc-bios/keymaps/en-us create mode 100644 pc-bios/keymaps/es create mode 100644 pc-bios/keymaps/et create mode 100644 pc-bios/keymaps/fi create mode 100644 pc-bios/keymaps/fo create mode 100644 pc-bios/keymaps/fr create mode 100644 pc-bios/keymaps/fr-be create mode 100644 pc-bios/keymaps/fr-ca create mode 100644 pc-bios/keymaps/fr-ch create mode 100644 pc-bios/keymaps/hr create mode 100644 pc-bios/keymaps/hu create mode 100644 pc-bios/keymaps/is create mode 100644 pc-bios/keymaps/it create mode 100644 pc-bios/keymaps/ja create mode 100644 pc-bios/keymaps/lt create mode 100644 pc-bios/keymaps/lv create mode 100644 pc-bios/keymaps/mk create mode 100644 pc-bios/keymaps/modifiers create mode 100644 pc-bios/keymaps/nl create mode 100644 pc-bios/keymaps/nl-be create mode 100644 pc-bios/keymaps/no create mode 100644 pc-bios/keymaps/pl create mode 100644 pc-bios/keymaps/pt create mode 100644 pc-bios/keymaps/pt-br create mode 100644 pc-bios/keymaps/ru create mode 100644 pc-bios/keymaps/sl create mode 100644 pc-bios/keymaps/sv create mode 100644 pc-bios/keymaps/th create mode 100644 pc-bios/keymaps/tr create mode 100644 pc-bios/linuxboot.bin create mode 100644 pc-bios/mpc8544ds.dtb create mode 100644 pc-bios/mpc8544ds.dts create mode 100644 pc-bios/multiboot.bin create mode 100644 pc-bios/ohw.diff create mode 100644 pc-bios/openbios-ppc create mode 100644 pc-bios/openbios-sparc32 create mode 100644 pc-bios/openbios-sparc64 create mode 100644 pc-bios/optionrom/Makefile create mode 100644 pc-bios/optionrom/linuxboot.S create mode 100644 pc-bios/optionrom/multiboot.S create mode 100644 pc-bios/optionrom/optionrom.h create mode 100644 pc-bios/petalogix-s3adsp1800.dtb create mode 100644 pc-bios/ppc_rom.bin create mode 100644 pc-bios/pxe-e1000.bin create mode 100644 pc-bios/pxe-ne2k_pci.bin create mode 100644 pc-bios/pxe-pcnet.bin create mode 100644 pc-bios/pxe-rtl8139.bin create mode 100644 pc-bios/pxe-virtio.bin create mode 100644 pc-bios/s390-zipl.rom create mode 100644 pc-bios/vgabios-cirrus.bin create mode 100644 pc-bios/vgabios-qxl.bin create mode 100644 pc-bios/vgabios-stdvga.bin create mode 100644 pc-bios/vgabios-vmware.bin create mode 100644 pc-bios/vgabios.bin create mode 100644 pci-ids.txt create mode 100644 pflib.c create mode 100644 pflib.h create mode 100644 poison.h create mode 100644 posix-aio-compat.c create mode 100644 ppc-dis.c create mode 100644 ppc.ld create mode 100644 ppc64.ld create mode 100644 qbool.c create mode 100644 qbool.h create mode 100644 qdict-test-data.txt create mode 100644 qdict.c create mode 100644 qdict.h create mode 100644 qemu-aio.h create mode 100644 qemu-barrier.h create mode 100644 qemu-char.c create mode 100644 qemu-char.h create mode 100644 qemu-common.h create mode 100644 qemu-config.c create mode 100644 qemu-config.h create mode 100644 qemu-doc.texi create mode 100644 qemu-error.c create mode 100644 qemu-error.h create mode 100644 qemu-img-cmds.hx create mode 100644 qemu-img.c create mode 100644 qemu-img.texi create mode 100644 qemu-io.c create mode 100644 qemu-lock.h create mode 100644 qemu-log.h create mode 100644 qemu-malloc.c create mode 100644 qemu-nbd.c create mode 100644 qemu-nbd.texi create mode 100644 qemu-objects.h create mode 100644 qemu-option.c create mode 100644 qemu-option.h create mode 100644 qemu-options.h create mode 100644 qemu-options.hx create mode 100644 qemu-os-posix.h create mode 100644 qemu-os-win32.h create mode 100644 qemu-queue.h create mode 100644 qemu-sockets.c create mode 100644 qemu-tech.texi create mode 100644 qemu-thread.c create mode 100644 qemu-thread.h create mode 100644 qemu-timer-common.c create mode 100644 qemu-timer.c create mode 100644 qemu-timer.h create mode 100644 qemu-tool.c create mode 100644 qemu-x509.h create mode 100644 qemu.sasl create mode 100644 qemu_socket.h create mode 100644 qerror.c create mode 100644 qerror.h create mode 100644 qfloat.c create mode 100644 qfloat.h create mode 100644 qint.c create mode 100644 qint.h create mode 100644 qjson.c create mode 100644 qjson.h create mode 100644 qlist.c create mode 100644 qlist.h create mode 100644 qmp-commands.hx create mode 100644 qobject.h create mode 100644 qstring.c create mode 100644 qstring.h create mode 100644 range.h create mode 100644 readline.c create mode 100644 readline.h create mode 100644 roms/seabios/.gitignore create mode 100644 roms/seabios/COPYING create mode 100644 roms/seabios/COPYING.LESSER create mode 100644 roms/seabios/Makefile create mode 100644 roms/seabios/README create mode 100644 roms/seabios/TODO create mode 100644 roms/seabios/src/acpi-dsdt.dsl create mode 100644 roms/seabios/src/acpi-dsdt.hex create mode 100644 roms/seabios/src/acpi.c create mode 100644 roms/seabios/src/acpi.h create mode 100644 roms/seabios/src/apm.c create mode 100644 roms/seabios/src/asm-offsets.c create mode 100644 roms/seabios/src/ata.c create mode 100644 roms/seabios/src/ata.h create mode 100644 roms/seabios/src/biosvar.h create mode 100644 roms/seabios/src/block.c create mode 100644 roms/seabios/src/blockcmd.c create mode 100644 roms/seabios/src/blockcmd.h create mode 100644 roms/seabios/src/boot.c create mode 100644 roms/seabios/src/boot.h create mode 100644 roms/seabios/src/bootsplash.c create mode 100644 roms/seabios/src/bregs.h create mode 100644 roms/seabios/src/cdrom.c create mode 100644 roms/seabios/src/clock.c create mode 100644 roms/seabios/src/cmos.h create mode 100644 roms/seabios/src/config.h create mode 100644 roms/seabios/src/coreboot.c create mode 100644 roms/seabios/src/dev-i440fx.c create mode 100644 roms/seabios/src/dev-i440fx.h create mode 100644 roms/seabios/src/disk.c create mode 100644 roms/seabios/src/disk.h create mode 100644 roms/seabios/src/entryfuncs.S create mode 100644 roms/seabios/src/farptr.h create mode 100644 roms/seabios/src/floppy.c create mode 100644 roms/seabios/src/font.c create mode 100644 roms/seabios/src/gen-defs.h create mode 100644 roms/seabios/src/ioport.h create mode 100644 roms/seabios/src/jpeg.c create mode 100644 roms/seabios/src/jpeg.h create mode 100644 roms/seabios/src/kbd.c create mode 100644 roms/seabios/src/lzmadecode.c create mode 100644 roms/seabios/src/lzmadecode.h create mode 100644 roms/seabios/src/memmap.c create mode 100644 roms/seabios/src/memmap.h create mode 100644 roms/seabios/src/misc.c create mode 100644 roms/seabios/src/mouse.c create mode 100644 roms/seabios/src/mptable.c create mode 100644 roms/seabios/src/mptable.h create mode 100644 roms/seabios/src/mtrr.c create mode 100644 roms/seabios/src/optionroms.c create mode 100644 roms/seabios/src/output.c create mode 100644 roms/seabios/src/paravirt.c create mode 100644 roms/seabios/src/paravirt.h create mode 100644 roms/seabios/src/pci.c create mode 100644 roms/seabios/src/pci.h create mode 100644 roms/seabios/src/pci_ids.h create mode 100644 roms/seabios/src/pci_regs.h create mode 100644 roms/seabios/src/pcibios.c create mode 100644 roms/seabios/src/pciinit.c create mode 100644 roms/seabios/src/pic.c create mode 100644 roms/seabios/src/pic.h create mode 100644 roms/seabios/src/pirtable.c create mode 100644 roms/seabios/src/pmm.c create mode 100644 roms/seabios/src/pnpbios.c create mode 100644 roms/seabios/src/post.c create mode 100644 roms/seabios/src/ps2port.c create mode 100644 roms/seabios/src/ps2port.h create mode 100644 roms/seabios/src/ramdisk.c create mode 100644 roms/seabios/src/resume.c create mode 100644 roms/seabios/src/romlayout.S create mode 100644 roms/seabios/src/serial.c create mode 100644 roms/seabios/src/shadow.c create mode 100644 roms/seabios/src/smbios.c create mode 100644 roms/seabios/src/smbios.h create mode 100644 roms/seabios/src/smm.c create mode 100644 roms/seabios/src/smp.c create mode 100644 roms/seabios/src/ssdt-proc.dsl create mode 100644 roms/seabios/src/stacks.c create mode 100644 roms/seabios/src/system.c create mode 100644 roms/seabios/src/types.h create mode 100644 roms/seabios/src/usb-ehci.c create mode 100644 roms/seabios/src/usb-ehci.h create mode 100644 roms/seabios/src/usb-hid.c create mode 100644 roms/seabios/src/usb-hid.h create mode 100644 roms/seabios/src/usb-hub.c create mode 100644 roms/seabios/src/usb-hub.h create mode 100644 roms/seabios/src/usb-msc.c create mode 100644 roms/seabios/src/usb-msc.h create mode 100644 roms/seabios/src/usb-ohci.c create mode 100644 roms/seabios/src/usb-ohci.h create mode 100644 roms/seabios/src/usb-uhci.c create mode 100644 roms/seabios/src/usb-uhci.h create mode 100644 roms/seabios/src/usb.c create mode 100644 roms/seabios/src/usb.h create mode 100644 roms/seabios/src/util.c create mode 100644 roms/seabios/src/util.h create mode 100644 roms/seabios/src/vgahooks.c create mode 100644 roms/seabios/src/virtio-blk.c create mode 100644 roms/seabios/src/virtio-blk.h create mode 100644 roms/seabios/src/virtio-pci.c create mode 100644 roms/seabios/src/virtio-pci.h create mode 100644 roms/seabios/src/virtio-ring.c create mode 100644 roms/seabios/src/virtio-ring.h create mode 100644 roms/seabios/tools/buildrom.py create mode 100644 roms/seabios/tools/checkrom.py create mode 100644 roms/seabios/tools/checkstack.py create mode 100644 roms/seabios/tools/checksum.py create mode 100644 roms/seabios/tools/gen-offsets.sh create mode 100644 roms/seabios/tools/layoutrom.py create mode 100644 roms/seabios/tools/readserial.py create mode 100644 roms/seabios/tools/test-gcc.sh create mode 100644 roms/seabios/tools/transdump.py create mode 100644 roms/seabios/vgasrc/clext.c create mode 100644 roms/seabios/vgasrc/vga.c create mode 100644 roms/seabios/vgasrc/vgaentry.S create mode 100644 roms/seabios/vgasrc/vgafb.c create mode 100644 roms/seabios/vgasrc/vgafonts.c create mode 100644 roms/seabios/vgasrc/vgaio.c create mode 100644 roms/seabios/vgasrc/vgalayout.lds.S create mode 100644 roms/seabios/vgasrc/vgatables.c create mode 100644 roms/seabios/vgasrc/vgatables.h create mode 100644 roms/vgabios/.cvsignore create mode 100644 roms/vgabios/BUGS create mode 100644 roms/vgabios/COPYING create mode 100644 roms/vgabios/ChangeLog create mode 100644 roms/vgabios/Makefile create mode 100644 roms/vgabios/Notes create mode 100644 roms/vgabios/README create mode 100644 roms/vgabios/TODO create mode 100644 roms/vgabios/biossums.c create mode 100644 roms/vgabios/clext.c create mode 100644 roms/vgabios/dataseghack create mode 100644 roms/vgabios/tests/lfbprof/Makefile create mode 100644 roms/vgabios/tests/lfbprof/lfbprof.c create mode 100644 roms/vgabios/tests/lfbprof/lfbprof.h create mode 100644 roms/vgabios/tests/testbios.c create mode 100644 roms/vgabios/vbe.c create mode 100644 roms/vgabios/vbe.h create mode 100644 roms/vgabios/vbe_display_api.txt create mode 100644 roms/vgabios/vbetables-gen.c create mode 100644 roms/vgabios/vgabios.c create mode 100644 roms/vgabios/vgabios.h create mode 100644 roms/vgabios/vgafonts.h create mode 100644 roms/vgabios/vgatables.h create mode 100644 rules.mak create mode 100644 rwhandler.c create mode 100644 rwhandler.h create mode 100644 s390-dis.c create mode 100644 s390.ld create mode 100644 savevm.c create mode 100644 scripts/checkpatch.pl create mode 100644 scripts/create_config create mode 100644 scripts/feature_to_c.sh create mode 100644 scripts/hxtool create mode 100644 scripts/make_device_config.sh create mode 100644 scripts/qemu-binfmt-conf.sh create mode 100644 scripts/signrom.sh create mode 100644 scripts/simpletrace.py create mode 100644 scripts/texi2pod.pl create mode 100644 scripts/tracetool create mode 100644 sh4-dis.c create mode 100644 simpletrace.c create mode 100644 simpletrace.h create mode 100644 slirp/COPYRIGHT create mode 100644 slirp/bootp.c create mode 100644 slirp/bootp.h create mode 100644 slirp/cksum.c create mode 100644 slirp/debug.h create mode 100644 slirp/if.c create mode 100644 slirp/if.h create mode 100644 slirp/ip.h create mode 100644 slirp/ip_icmp.c create mode 100644 slirp/ip_icmp.h create mode 100644 slirp/ip_input.c create mode 100644 slirp/ip_output.c create mode 100644 slirp/libslirp.h create mode 100644 slirp/main.h create mode 100644 slirp/mbuf.c create mode 100644 slirp/mbuf.h create mode 100644 slirp/misc.c create mode 100644 slirp/misc.h create mode 100644 slirp/sbuf.c create mode 100644 slirp/sbuf.h create mode 100644 slirp/slirp.c create mode 100644 slirp/slirp.h create mode 100644 slirp/slirp_config.h create mode 100644 slirp/socket.c create mode 100644 slirp/socket.h create mode 100644 slirp/tcp.h create mode 100644 slirp/tcp_input.c create mode 100644 slirp/tcp_output.c create mode 100644 slirp/tcp_subr.c create mode 100644 slirp/tcp_timer.c create mode 100644 slirp/tcp_timer.h create mode 100644 slirp/tcp_var.h create mode 100644 slirp/tcpip.h create mode 100644 slirp/tftp.c create mode 100644 slirp/tftp.h create mode 100644 slirp/udp.c create mode 100644 slirp/udp.h create mode 100644 softmmu-semi.h create mode 100644 softmmu_defs.h create mode 100644 softmmu_exec.h create mode 100644 softmmu_header.h create mode 100644 softmmu_template.h create mode 100644 sparc-dis.c create mode 100644 sparc.ld create mode 100644 sparc64.ld create mode 100644 spice-qemu-char.c create mode 100644 sysconfigs/target/target-x86_64.conf create mode 100644 sysemu.h create mode 100644 target-alpha/STATUS create mode 100644 target-alpha/cpu.h create mode 100644 target-alpha/exec.h create mode 100644 target-alpha/helper.c create mode 100644 target-alpha/helper.h create mode 100644 target-alpha/op_helper.c create mode 100644 target-alpha/translate.c create mode 100644 target-arm/cpu.h create mode 100644 target-arm/exec.h create mode 100644 target-arm/helper.c create mode 100644 target-arm/helpers.h create mode 100644 target-arm/iwmmxt_helper.c create mode 100644 target-arm/machine.c create mode 100644 target-arm/neon_helper.c create mode 100644 target-arm/op_addsub.h create mode 100644 target-arm/op_helper.c create mode 100644 target-arm/translate.c create mode 100644 target-cris/cpu.h create mode 100644 target-cris/crisv10-decode.h create mode 100644 target-cris/crisv32-decode.h create mode 100644 target-cris/exec.h create mode 100644 target-cris/helper.c create mode 100644 target-cris/helper.h create mode 100644 target-cris/machine.c create mode 100644 target-cris/mmu.c create mode 100644 target-cris/mmu.h create mode 100644 target-cris/op_helper.c create mode 100644 target-cris/opcode-cris.h create mode 100644 target-cris/translate.c create mode 100644 target-cris/translate_v10.c create mode 100644 target-i386/TODO create mode 100644 target-i386/cpu.h create mode 100644 target-i386/cpuid.c create mode 100644 target-i386/exec.h create mode 100644 target-i386/helper.c create mode 100644 target-i386/helper.h create mode 100644 target-i386/helper_template.h create mode 100644 target-i386/kvm.c create mode 100644 target-i386/kvm_x86.h create mode 100644 target-i386/machine.c create mode 100644 target-i386/op_helper.c create mode 100644 target-i386/ops_sse.h create mode 100644 target-i386/ops_sse_header.h create mode 100644 target-i386/svm.h create mode 100644 target-i386/translate.c create mode 100644 target-m68k/cpu.h create mode 100644 target-m68k/exec.h create mode 100644 target-m68k/helper.c create mode 100644 target-m68k/helpers.h create mode 100644 target-m68k/m68k-qreg.h create mode 100644 target-m68k/machine.c create mode 100644 target-m68k/op_helper.c create mode 100644 target-m68k/qregs.def create mode 100644 target-m68k/translate.c create mode 100644 target-microblaze/cpu.h create mode 100644 target-microblaze/exec.h create mode 100644 target-microblaze/helper.c create mode 100644 target-microblaze/helper.h create mode 100644 target-microblaze/machine.c create mode 100644 target-microblaze/microblaze-decode.h create mode 100644 target-microblaze/mmu.c create mode 100644 target-microblaze/mmu.h create mode 100644 target-microblaze/op_helper.c create mode 100644 target-microblaze/translate.c create mode 100644 target-mips/TODO create mode 100644 target-mips/cpu.h create mode 100644 target-mips/exec.h create mode 100644 target-mips/helper.c create mode 100644 target-mips/helper.h create mode 100644 target-mips/machine.c create mode 100644 target-mips/mips-defs.h create mode 100644 target-mips/op_helper.c create mode 100644 target-mips/translate.c create mode 100644 target-mips/translate_init.c create mode 100644 target-ppc/STATUS create mode 100644 target-ppc/cpu.h create mode 100644 target-ppc/exec.h create mode 100644 target-ppc/helper.c create mode 100644 target-ppc/helper.h create mode 100644 target-ppc/helper_regs.h create mode 100644 target-ppc/kvm.c create mode 100644 target-ppc/kvm_ppc.c create mode 100644 target-ppc/kvm_ppc.h create mode 100644 target-ppc/machine.c create mode 100644 target-ppc/mfrom_table.c create mode 100644 target-ppc/mfrom_table_gen.c create mode 100644 target-ppc/op_helper.c create mode 100644 target-ppc/translate.c create mode 100644 target-ppc/translate_init.c create mode 100644 target-s390x/cpu.h create mode 100644 target-s390x/exec.h create mode 100644 target-s390x/helper.c create mode 100644 target-s390x/kvm.c create mode 100644 target-s390x/machine.c create mode 100644 target-s390x/op_helper.c create mode 100644 target-s390x/translate.c create mode 100644 target-sh4/README.sh4 create mode 100644 target-sh4/cpu.h create mode 100644 target-sh4/exec.h create mode 100644 target-sh4/helper.c create mode 100644 target-sh4/helper.h create mode 100644 target-sh4/machine.c create mode 100644 target-sh4/op_helper.c create mode 100644 target-sh4/translate.c create mode 100644 target-sparc/TODO create mode 100644 target-sparc/cpu.h create mode 100644 target-sparc/exec.h create mode 100644 target-sparc/helper.c create mode 100644 target-sparc/helper.h create mode 100644 target-sparc/machine.c create mode 100644 target-sparc/op_helper.c create mode 100644 target-sparc/translate.c create mode 100644 targphys.h create mode 100644 tcg-runtime.c create mode 100644 tcg/LICENSE create mode 100644 tcg/README create mode 100644 tcg/TODO create mode 100644 tcg/arm/tcg-target.c create mode 100644 tcg/arm/tcg-target.h create mode 100644 tcg/hppa/tcg-target.c create mode 100644 tcg/hppa/tcg-target.h create mode 100644 tcg/i386/tcg-target.c create mode 100644 tcg/i386/tcg-target.h create mode 100644 tcg/ia64/tcg-target.c create mode 100644 tcg/ia64/tcg-target.h create mode 100644 tcg/mips/tcg-target.c create mode 100644 tcg/mips/tcg-target.h create mode 100644 tcg/ppc/tcg-target.c create mode 100644 tcg/ppc/tcg-target.h create mode 100644 tcg/ppc64/tcg-target.c create mode 100644 tcg/ppc64/tcg-target.h create mode 100644 tcg/s390/tcg-target.c create mode 100644 tcg/s390/tcg-target.h create mode 100644 tcg/sparc/tcg-target.c create mode 100644 tcg/sparc/tcg-target.h create mode 100644 tcg/tcg-op.h create mode 100644 tcg/tcg-opc.h create mode 100644 tcg/tcg-runtime.h create mode 100644 tcg/tcg.c create mode 100644 tcg/tcg.h create mode 100644 tests/Makefile create mode 100644 tests/alpha/Makefile create mode 100644 tests/alpha/crt.s create mode 100644 tests/alpha/hello-alpha.c create mode 100644 tests/alpha/test-cond.c create mode 100644 tests/alpha/test-ovf.c create mode 100644 tests/cris/Makefile create mode 100644 tests/cris/README create mode 100644 tests/cris/check_abs.c create mode 100644 tests/cris/check_addc.c create mode 100644 tests/cris/check_addcm.c create mode 100644 tests/cris/check_addi.s create mode 100644 tests/cris/check_addiv32.s create mode 100644 tests/cris/check_addm.s create mode 100644 tests/cris/check_addo.c create mode 100644 tests/cris/check_addoq.c create mode 100644 tests/cris/check_addq.s create mode 100644 tests/cris/check_addr.s create mode 100644 tests/cris/check_addxc.s create mode 100644 tests/cris/check_addxm.s create mode 100644 tests/cris/check_addxr.s create mode 100644 tests/cris/check_andc.s create mode 100644 tests/cris/check_andm.s create mode 100644 tests/cris/check_andq.s create mode 100644 tests/cris/check_andr.s create mode 100644 tests/cris/check_asr.s create mode 100644 tests/cris/check_ba.s create mode 100644 tests/cris/check_bas.s create mode 100644 tests/cris/check_bcc.s create mode 100644 tests/cris/check_bound.c create mode 100644 tests/cris/check_boundc.s create mode 100644 tests/cris/check_boundr.s create mode 100644 tests/cris/check_btst.s create mode 100644 tests/cris/check_clearfv32.s create mode 100644 tests/cris/check_clrjmp1.s create mode 100644 tests/cris/check_cmp-2.s create mode 100644 tests/cris/check_cmpc.s create mode 100644 tests/cris/check_cmpm.s create mode 100644 tests/cris/check_cmpq.s create mode 100644 tests/cris/check_cmpr.s create mode 100644 tests/cris/check_cmpxc.s create mode 100644 tests/cris/check_cmpxm.s create mode 100644 tests/cris/check_dstep.s create mode 100644 tests/cris/check_ftag.c create mode 100644 tests/cris/check_gcctorture_pr28634-1.c create mode 100644 tests/cris/check_gcctorture_pr28634.c create mode 100644 tests/cris/check_glibc_kernelversion.c create mode 100644 tests/cris/check_hello.c create mode 100644 tests/cris/check_int64.c create mode 100644 tests/cris/check_jsr.s create mode 100644 tests/cris/check_lapc.s create mode 100644 tests/cris/check_lsl.s create mode 100644 tests/cris/check_lsr.s create mode 100644 tests/cris/check_lz.c create mode 100644 tests/cris/check_mapbrk.c create mode 100644 tests/cris/check_mcp.s create mode 100644 tests/cris/check_mmap1.c create mode 100644 tests/cris/check_mmap2.c create mode 100644 tests/cris/check_mmap3.c create mode 100644 tests/cris/check_movdelsr1.s create mode 100644 tests/cris/check_movecr.s create mode 100644 tests/cris/check_movei.s create mode 100644 tests/cris/check_movemr.s create mode 100644 tests/cris/check_movemrv32.s create mode 100644 tests/cris/check_moveq.c create mode 100644 tests/cris/check_mover.s create mode 100644 tests/cris/check_moverm.s create mode 100644 tests/cris/check_movmp.s create mode 100644 tests/cris/check_movpmv32.s create mode 100644 tests/cris/check_movpr.s create mode 100644 tests/cris/check_movprv32.s create mode 100644 tests/cris/check_movscr.s create mode 100644 tests/cris/check_movsm.s create mode 100644 tests/cris/check_movsr.s create mode 100644 tests/cris/check_movucr.s create mode 100644 tests/cris/check_movum.s create mode 100644 tests/cris/check_movur.s create mode 100644 tests/cris/check_mulv32.s create mode 100644 tests/cris/check_mulx.s create mode 100644 tests/cris/check_neg.s create mode 100644 tests/cris/check_not.s create mode 100644 tests/cris/check_openpf1.c create mode 100644 tests/cris/check_openpf2.c create mode 100644 tests/cris/check_openpf3.c create mode 100644 tests/cris/check_openpf4.c create mode 100644 tests/cris/check_openpf5.c create mode 100644 tests/cris/check_orc.s create mode 100644 tests/cris/check_orm.s create mode 100644 tests/cris/check_orq.s create mode 100644 tests/cris/check_orr.s create mode 100644 tests/cris/check_ret.s create mode 100644 tests/cris/check_scc.s create mode 100644 tests/cris/check_settls1.c create mode 100644 tests/cris/check_sigalrm.c create mode 100644 tests/cris/check_stat1.c create mode 100644 tests/cris/check_stat2.c create mode 100644 tests/cris/check_stat3.c create mode 100644 tests/cris/check_stat4.c create mode 100644 tests/cris/check_subc.s create mode 100644 tests/cris/check_subm.s create mode 100644 tests/cris/check_subq.s create mode 100644 tests/cris/check_subr.s create mode 100644 tests/cris/check_swap.c create mode 100644 tests/cris/check_time1.c create mode 100644 tests/cris/check_time2.c create mode 100644 tests/cris/check_xarith.s create mode 100644 tests/cris/crisutils.h create mode 100644 tests/cris/crt.s create mode 100644 tests/cris/sys.c create mode 100644 tests/cris/sys.h create mode 100644 tests/cris/testutils.inc create mode 100644 tests/hello-arm.c create mode 100644 tests/hello-i386.c create mode 100644 tests/hello-mips.c create mode 100644 tests/linux-test.c create mode 100644 tests/pi_10.com create mode 100644 tests/qruncom.c create mode 100644 tests/runcom.c create mode 100644 tests/sha1.c create mode 100644 tests/test-arm-iwmmxt.s create mode 100644 tests/test-i386-code16.S create mode 100644 tests/test-i386-muldiv.h create mode 100644 tests/test-i386-shift.h create mode 100644 tests/test-i386-ssse3.c create mode 100644 tests/test-i386-vm86.S create mode 100644 tests/test-i386.c create mode 100644 tests/test-i386.h create mode 100644 tests/test-mmap.c create mode 100644 tests/test_path.c create mode 100644 tests/testthread.c create mode 100644 thunk.c create mode 100644 thunk.h create mode 100644 trace-events create mode 100644 translate-all.c create mode 100644 uboot_image.h create mode 100644 ui/cocoa.m create mode 100644 ui/curses.c create mode 100644 ui/curses_keys.h create mode 100644 ui/d3des.c create mode 100644 ui/d3des.h create mode 100644 ui/keymaps.c create mode 100644 ui/keymaps.h create mode 100644 ui/qemu-spice.h create mode 100644 ui/sdl.c create mode 100644 ui/sdl_keysym.h create mode 100644 ui/sdl_zoom.c create mode 100644 ui/sdl_zoom.h create mode 100644 ui/sdl_zoom_template.h create mode 100644 ui/spice-core.c create mode 100644 ui/spice-display.c create mode 100644 ui/spice-display.h create mode 100644 ui/spice-input.c create mode 100644 ui/vnc-auth-sasl.c create mode 100644 ui/vnc-auth-sasl.h create mode 100644 ui/vnc-auth-vencrypt.c create mode 100644 ui/vnc-auth-vencrypt.h create mode 100644 ui/vnc-enc-hextile-template.h create mode 100644 ui/vnc-enc-hextile.c create mode 100644 ui/vnc-enc-tight.c create mode 100644 ui/vnc-enc-tight.h create mode 100644 ui/vnc-enc-zlib.c create mode 100644 ui/vnc-jobs-async.c create mode 100644 ui/vnc-jobs-sync.c create mode 100644 ui/vnc-jobs.h create mode 100644 ui/vnc-palette.c create mode 100644 ui/vnc-palette.h create mode 100644 ui/vnc-tls.c create mode 100644 ui/vnc-tls.h create mode 100644 ui/vnc.c create mode 100644 ui/vnc.h create mode 100644 ui/vnc_keysym.h create mode 100644 ui/x_keymap.c create mode 100644 ui/x_keymap.h create mode 100644 usb-bsd.c create mode 100644 usb-linux.c create mode 100644 usb-stub.c create mode 100644 version.rc create mode 100644 vgafont.h create mode 100644 vl.c create mode 100644 x86_64.ld diff --git a/CODING_STYLE b/CODING_STYLE new file mode 100644 index 0000000..5ecfa22 --- /dev/null +++ b/CODING_STYLE @@ -0,0 +1,81 @@ +Qemu Coding Style +================= + +Please use the script checkpatch.pl in the scripts directory to check +patches before submitting. + +1. Whitespace + +Of course, the most important aspect in any coding style is whitespace. +Crusty old coders who have trouble spotting the glasses on their noses +can tell the difference between a tab and eight spaces from a distance +of approximately fifteen parsecs. Many a flamewar have been fought and +lost on this issue. + +QEMU indents are four spaces. Tabs are never used, except in Makefiles +where they have been irreversibly coded into the syntax. +Spaces of course are superior to tabs because: + + - You have just one way to specify whitespace, not two. Ambiguity breeds + mistakes. + - The confusion surrounding 'use tabs to indent, spaces to justify' is gone. + - Tab indents push your code to the right, making your screen seriously + unbalanced. + - Tabs will be rendered incorrectly on editors who are misconfigured not + to use tab stops of eight positions. + - Tabs are rendered badly in patches, causing off-by-one errors in almost + every line. + - It is the QEMU coding style. + +Do not leave whitespace dangling off the ends of lines. + +2. Line width + +Lines are 80 characters; not longer. + +Rationale: + - Some people like to tile their 24" screens with a 6x4 matrix of 80x24 + xterms and use vi in all of them. The best way to punish them is to + let them keep doing it. + - Code and especially patches is much more readable if limited to a sane + line length. Eighty is traditional. + - It is the QEMU coding style. + +3. Naming + +Variables are lower_case_with_underscores; easy to type and read. Structured +type names are in CamelCase; harder to type but standing out. Scalar type +names are lower_case_with_underscores_ending_with_a_t, like the POSIX +uint64_t and family. Note that this last convention contradicts POSIX +and is therefore likely to be changed. + +When wrapping standard library functions, use the prefix qemu_ to alert +readers that they are seeing a wrapped version; otherwise avoid this prefix. + +4. Block structure + +Every indented statement is braced; even if the block contains just one +statement. The opening brace is on the line that contains the control +flow statement that introduces the new block; the closing brace is on the +same line as the else keyword, or on a line by itself if there is no else +keyword. Example: + + if (a == 5) { + printf("a was 5.\n"); + } else if (a == 6) { + printf("a was 6.\n"); + } else { + printf("a was something else entirely.\n"); + } + +An exception is the opening brace for a function; for reasons of tradition +and clarity it comes on a line by itself: + + void a_function(void) + { + do_something(); + } + +Rationale: a consistent (except for functions...) bracing style reduces +ambiguity and avoids needless churn when lines are added or removed. +Furthermore, it is the QEMU coding style. diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..00ccfbb --- /dev/null +++ b/COPYING @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (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) 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., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/COPYING.LIB b/COPYING.LIB new file mode 100644 index 0000000..48afc2e --- /dev/null +++ b/COPYING.LIB @@ -0,0 +1,504 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + 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, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + diff --git a/Changelog b/Changelog new file mode 100644 index 0000000..152feaa --- /dev/null +++ b/Changelog @@ -0,0 +1,574 @@ +version 0.12.0: + + - Update to SeaBIOS 0.5.0 + - e1000: fix device link status in Linux (Anthony Liguori) + - monitor: fix QMP for balloon command (Luiz Capitulino) + - QMP: Return an empty dict by default (Luiz Capitulino) + - QMP: Only handle converted commands (Luiz Capitulino) + - pci: support PCI based option rom loading (Gerd Hoffman/Anthony Liguori) + - Fix backcompat for hotplug of SCSI controllers (Daniel P. Berrange) + - fdc: fix migration from 0.11 (Juan Quintela) + - vmware-vga: fix segv on cursor resize. (Dave Airlie) + - vmware-vga: various fixes (Dave Airlie/Anthony Liguori) + - qdev: improve property error reporting. (Gerd Hoffmann) + - fix vga names in default_list (Gerd Hoffmann) + - usb-host: check mon before using it. (Gerd Hoffmann) + - usb-net: use qdev for -usbdevice (Gerd Hoffmann) + - monitor: Catch printing to non-existent monitor (Luiz Capitulino) + - Avoid permanently disabled QEMU monitor when UNIX migration fails (Daniel P. Berrange) + - Fix loading of ELF multiboot kernels (Kevin Wolf) + - qemu-io: Fix memory leak (Kevin Wolf) + - Fix thinko in linuxboot.S (Paolo Bonzini) + - target-i386: Fix evaluation of DR7 register (Jan Kiszka) + - vnc: hextile: do not generate ForegroundSpecified and SubrectsColoured tiles (Anthony Liguori) + - S390: Bail out without KVM (Alexander Graf) + - S390: Don't tell guest we're updating config space (Alexander Graf) + - target-s390: Fail on unknown instructions (Alexander Graf) + - osdep: Fix runtime failure on older Linux kernels (Andre Przywara) + - Fix a make -j race (Juergen Lock) + - target-alpha: Fix generic ctz64. (Richard Henderson) + - s390: Fix buggy assignment (Stefan Weil) + - target-mips: fix user-mode emulation startup (Nathan Froyd) + - target-i386: Update CPUID feature set for TCG (Andre Przywara) + - s390: fix build on 32 bit host (Michael S. Tsirkin) + +version 0.12.0-rc2: + + - v2: properly save kvm system time msr registers (Glauber Costa) + - convert more monitor commands to qmp (Luiz Capitulino) + - vnc: fix capslock tracking logic. (Gerd Hoffmann) + - QemuOpts: allow larger option values. (Gerd Hoffmann) + - scsi: fix drive hotplug. (Gerd Hoffmann) + - pci: don't hw_error() when no slot is available. (Gerd Hoffmann) + - pci: don't abort() when trying to hotplug with acpi off. (Gerd Hoffmann) + - allow default devices to be implemented in config file (Gerd Hoffman) + - vc: colorize chardev title line with blue background. (Gerd Hoffmann) + - chardev: make chardevs specified in config file work. (Gerd Hoffmann) + - qdev: also match bus name for global properties (Gerd Hoffmann) + - qdev: add command line option to set global defaults for properties. (Gerd Hoffmann) + - kvm: x86: Save/restore exception_index (Jan Kiszka) + - qdev: Replace device names containing whitespace (Markus Armbruster) + - fix rtc-td-hack on host without high-res timers (Gleb Natapov) + - virtio: verify features on load (Michael S. Tsirkin) + - vmware_vga: add rom file so that it boots. (Dave Airlie) + - Do not abort on qemu_malloc(0) in production builds (Anthony Liguori) + - Fix ARM userspace strex implementation. (Paul Brook) + - qemu: delete rule target on error (Michael S. Tsirkin) + - QMP: add human-readable description to error response (Markus Armbruster) + - convert more monitor commands to QError (Markus Armbruster) + - monitor: Fix double-prompt after "change vnc passwd BLA" (Markus Armbruster) + - monitor: do_cont(): Don't ask for passwords (Luiz Capitulino) + - monitor: Introduce 'block_passwd' command (Luiz Capitulino) + - pci: interrupt disable bit support (Michael S. Tsirkin) + - pci: interrupt status bit implementation (Michael S. Tsirkin) + - pci: prepare irq code for interrupt state (Michael S. Tsirkin) + - msix: function mask support (Michael S. Tsirkin) + - msix: macro rename for function mask support (Michael S. Tsirkin) + - cpuid: Fix multicore setup on Intel (Andre Przywara) + - kvm: x86: Fix initial kvm_has_msr_star (Jan Kiszka) + - Update OpenBIOS images to r640 (Aurelien Jarno) + +version 0.10.2: + + - fix savevm/loadvm (Anthony Liguori) + - live migration: fix dirty tracking windows (Glauber Costa) + - live migration: improve error propogation (Glauber Costa) + - qcow2: fix image creation for > ~2TB images (Chris Wright) + - hotplug: fix error handling for if= parameter (Eduardo Habkost) + - qcow2: fix data corruption (Nolan Leake) + - virtio: fix guest oops with 2.6.25 kernels (Rusty Russell) + - SH4: add support for -kernel (Takashi Yoshii, Aurelien Jarno) + - hotplug: fix closing of char devices (Jan Kiszka) + - hotplug: remove incorrect check for device name (Eduardo Habkost) + - enable -k on win32 (Herve Poussineau) + - configure: use LANG=C for grep (Andreas Faerber) + - fix VGA regression (malc) + +version 0.10.1: + + - virtio-net: check right return size on sg list (Alex Williamson) + - Make qemu_announce_self handle holes (live migration after hotplug) + (Marcelo Tosatti) + - Revert r6804-r6808 (qcow2 allocation info). This series of changes added + a high cost to startup for large qcow2 images (Anthony Liguori) + - qemu-img: fix help message (Aurelien Jarno) + - Fix build for non-default installs of SDL (Anthony Liguori) + - Fix race condition in env->interrupt_request. When using TCG and a dynticks + host timer, this condition could cause TCG to get stuck in an infinite + loop (Aurelien Jarno) + - Fix reading encrypted hard disk passwords during early startup (Jan Kiszka) + - Fix encrypted disk reporting in 'info block' (Jan Kiszka) + - Fix console size with tiny displays (MusicPal) (Jan Kiszka) + - Improve error handling in bdrv_open2 (Jan Kiszka) + - Avoid leaking data in mux'ed character devices (Jan Kiszka) + - Fix initial character device reset (no banner in monitor) (Jan Kiszka) + - Fix cpuid KVM crash on i386 host (Lubomir Rintel) + - Fix SLES10sp2 installation by adding ISTAT1 register to LSI SCSI emulation + (Ryan Harper) + +version 0.10.0: + + - TCG support (No longer requires GCC 3.x) + - Kernel Virtual Machine acceleration support + - BSD userspace emulation + - Bluetooth emulation and host passthrough support + - GDB XML register description support + - Intel e1000 emulation + - HPET emulation + - VirtIO paravirtual device support + - Marvell 88w8618 / MusicPal emulation + - Nokia N-series tablet emulation / OMAP2 processor emulation + - PCI hotplug support + - Live migration and new save/restore formats + - Curses display support + - qemu-nbd utility to mount supported block formats + - Altivec support in PPC emulation and new firmware (OpenBIOS) + - Multiple VNC clients are now supported + - TLS encryption is now supported in VNC + - MIPS Magnum R4000 machine (Hervé Poussineau) + - Braille support (Samuel Thibault) + - Freecom MusicPal system emulation (Jan Kiszka) + - OMAP242x and Nokia N800, N810 machines (Andrzej Zaborowski) + - EsounD audio driver (Frederick Reeve) + - Gravis Ultrasound GF1 sound card (Tibor "TS" Schütz) + - Many, many, bug fixes and new features + +version 0.9.1: + + - TFTP booting from host directory (Anthony Liguori, Erwan Velu) + - Tap device emulation for Solaris (Sittichai Palanisong) + - Monitor multiplexing to several I/O channels (Jason Wessel) + - ds1225y nvram support (Herve Poussineau) + - CPU model selection support (J. Mayer, Paul Brook, Herve Poussineau) + - Several Sparc fixes (Aurelien Jarno, Blue Swirl, Robert Reif) + - MIPS 64-bit FPU support (Thiemo Seufer) + - Xscale PDA emulation (Andrzej Zaborowski) + - ColdFire system emulation (Paul Brook) + - Improved SH4 support (Magnus Damm) + - MIPS64 support (Aurelien Jarno, Thiemo Seufer) + - Preliminary Alpha guest support (J. Mayer) + - Read-only support for Parallels disk images (Alex Beregszaszi) + - SVM (x86 virtualization) support (Alexander Graf) + - CRIS emulation (Edgar E. Iglesias) + - SPARC32PLUS execution support (Blue Swirl) + - MIPS mipssim pseudo machine (Thiemo Seufer) + - Strace for Linux userland emulation (Stuart Anderson, Thayne Harbaugh) + - OMAP310 MPU emulation plus Palm T|E machine (Andrzej Zaborowski) + - ARM v6, v7, NEON SIMD and SMP emulation (Paul Brook/CodeSourcery) + - Gumstix boards: connex and verdex emulation (Thorsten Zitterell) + - Intel mainstone II board emulation (Armin Kuster) + - VMware SVGA II graphics card support (Andrzej Zaborowski) + +version 0.9.0: + + - Support for relative paths in backing files for disk images + - Async file I/O API + - New qcow2 disk image format + - Support of multiple VM snapshots + - Linux: specific host CDROM and floppy support + - SMM support + - Moved PCI init, MP table init and ACPI table init to Bochs BIOS + - Support for MIPS32 Release 2 instruction set (Thiemo Seufer) + - MIPS Malta system emulation (Aurelien Jarno, Stefan Weil) + - Darwin userspace emulation (Pierre d'Herbemont) + - m68k user support (Paul Brook) + - several x86 and x86_64 emulation fixes + - Mouse relative offset VNC extension (Anthony Liguori) + - PXE boot support (Anthony Liguori) + - '-daemonize' option (Anthony Liguori) + +version 0.8.2: + + - ACPI support + - PC VGA BIOS fixes + - switch to OpenBios for SPARC targets (Blue Swirl) + - VNC server fixes + - MIPS FPU support (Marius Groeger) + - Solaris/SPARC host support (Juergen Keil) + - PPC breakpoints and single stepping (Jason Wessel) + - USB updates (Paul Brook) + - UDP/TCP/telnet character devices (Jason Wessel) + - Windows sparse file support (Frediano Ziglio) + - RTL8139 NIC TCP segmentation offloading (Igor Kovalenko) + - PCNET NIC support (Antony T Curtis) + - Support for variable frequency host CPUs + - Workaround for win32 SMP hosts + - Support for AMD Flash memories (Jocelyn Mayer) + - Audio capture to WAV files support (malc) + +version 0.8.1: + + - USB tablet support (Brad Campbell, Anthony Liguori) + - win32 host serial support (Kazu) + - PC speaker support (Joachim Henke) + - IDE LBA48 support (Jens Axboe) + - SSE3 support + - Solaris port (Juergen Keil) + - Preliminary SH4 target (Samuel Tardieu) + - VNC server (Anthony Liguori) + - slirp fixes (Ed Swierk et al.) + - USB fixes + - ARM Versatile Platform Baseboard emulation (Paul Brook) + +version 0.8.0: + + - ARM system emulation: Arm Integrator/CP board with an arm1026ej-s + cpu (Paul Brook) + - SMP support + - Mac OS X cocoa improvements (Mike Kronenberg) + - Mac OS X CoreAudio driver (Mike Kronenberg) + - DirectSound driver (malc) + - ALSA audio driver (malc) + - new audio options: '-soundhw' and '-audio-help' (malc) + - ES1370 PCI audio device (malc) + - Initial USB support + - Linux host serial port access + - Linux host low level parallel port access + - New network emulation code supporting VLANs. + - MIPS and MIPSel User Linux emulation + - MIPS fixes to boot Linux (Daniel Jacobowitz) + - NX bit support + - Initial SPARC SMP support (Blue Swirl) + - Major overhaul of the virtual FAT driver for read/write support + (Johannes Schindelin) + +version 0.7.2: + + - x86_64 fixes (Win2000 and Linux 2.6 boot in 32 bit) + - merge self modifying code handling in dirty ram page mecanism. + - MIPS fixes (Ralf Baechle) + - better user net performances + +version 0.7.1: + + - read-only Virtual FAT support (Johannes Schindelin) + - Windows 2000 install disk full hack (original idea from Vladimir + N. Oleynik) + - VMDK disk image creation (Filip Navara) + - SPARC64 progress (Blue Swirl) + - initial MIPS support (Jocelyn mayer) + - MIPS improvements (Ralf Baechle) + - 64 bit fixes in user networking (initial patch by Gwenole Beauchesne) + - IOAPIC support (Filip Navara) + +version 0.7.0: + + - better BIOS translation and HDD geometry auto-detection + - user mode networking bug fix + - undocumented FPU ops support + - Cirrus VGA: support for 1280x1024x[8,15,16] modes + - 'pidfile' option + - .dmg disk image format support (Johannes Schindelin) + - keymaps support (initial patch by Johannes Schindelin) + - big endian ARM support (Lennert Buytenhek) + - added generic 64 bit target support + - x86_64 target support + - initial APIC support + - MMX/SSE/SSE2/PNI support + - PC parallel port support (Mark Jonckheere) + - initial SPARC64 support (Blue Swirl) + - SPARC target boots Linux (Blue Swirl) + - armv5te user mode support (Paul Brook) + - ARM VFP support (Paul Brook) + - ARM "Angel" semihosting syscalls (Paul Brook) + - user mode gdb stub support (Paul Brook) + - Samba 3 support + - initial Cocoa support (Pierre d'Herbemont) + - generic FPU emulation code + - Virtual PC read-only disk image support (Alex Beregszaszi) + +version 0.6.1: + + - Mac OS X port (Pierre d'Herbemont) + - Virtual console support + - Better monitor line edition + - New block device layer + - New 'qcow' growable disk image support with AES encryption and + transparent decompression + - VMware 3 and 4 read-only disk image support (untested) + - Support for up to 4 serial ports + - TFTP server support (Magnus Damm) + - Port redirection support in user mode networking + - Support for not executable data sections + - Compressed loop disk image support (Johannes Schindelin) + - Level triggered IRQ fix (aka NE2000 PCI performance fix) (Steve + Wormley) + - Fixed Fedora Core 2 problems (now you can run qemu without any + LD_ASSUME_KERNEL tricks on FC2) + - DHCP fix for Windows (accept DHCPREQUEST alone) + - SPARC system emulation (Blue Swirl) + - Automatic Samba configuration for host file access from Windows. + - '-loadvm' and '-full-screen' options + - ne2000 savevm support (Johannes Schindelin) + - Ctrl-Alt is now the default grab key. Ctrl-Alt-[0-9] switches to + the virtual consoles. + - BIOS floppy fix for NT4 (Mike Nordell, Derek Fawcus, Volker Ruppert) + - Floppy fixes for NT4 and NT5 (Mike Nordell) + - NT4 IDE fixes (Ben Pfaf, Mike Nordell) + - SDL Audio support and SB16 fixes (malc) + - ENTER instruction bug fix (initial patch by Stefan Kisdaroczi) + - VGA font change fix + - VGA read-only CRTC register fix + +version 0.6.0: + + - minimalist FPU exception support (NetBSD FPU probe fix) + - cr0.ET fix (Win95 boot) + - *BSD port (Markus Niemisto) + - I/O access fix (signaled by Mark Jonckheere) + - IDE drives serial number fix (Mike Nordell) + - int13 CDROM BIOS fix (aka Solaris x86 install CD fix) + - int15, ah=86 BIOS fix (aka Solaris x86 hardware probe hang up fix) + - BSR/BSF "undefined behaviour" fix + - vmdk2raw: convert VMware disk images to raw images + - PCI support + - NE2K PCI support + - dummy VGA PCI support + - VGA font selection fix (Daniel Serpell) + - PIC reset fix (Hidemi KAWAI) + - PIC spurious irq support (aka Solaris install bug) + - added '-localtime' option + - Cirrus CL-GD54xx VGA support (initial patch by Makoto Suzuki (suzu)) + - APM and system shutdown support + - Fixed system reset + - Support for other PC BIOSes + - Initial PowerMac hardware emulation + - PowerMac/PREP OpenFirmware compatible BIOS (Jocelyn Mayer) + - initial IDE BMDMA support (needed for Darwin x86) + - Set the default memory size for PC emulation to 128 MB + +version 0.5.5: + + - SDL full screen support (initial patch by malc) + - VGA support on PowerPC PREP + - VBE fixes (Matthew Mastracci) + - PIT fixes (aka Win98 hardware probe and "VGA slowness" bug) + - IDE master only fixes (aka Win98 CD-ROM probe bug) + - ARM load/store half word fix (Ulrich Hecht) + - FDC fixes for Win98 + +version 0.5.4: + + - qemu-fast fixes + - BIOS area protection fix (aka EMM386.EXE fix) (Mike Nordell) + - keyboard/mouse fix (Mike Nordell) + - IDE fixes (Linux did not recognized slave drivers) + - VM86 EIP masking fix (aka NT5 install fix) (Mike Nordell) + - QEMU can now boot a PowerPC Linux kernel (Jocelyn Mayer) + - User mode network stack + - imul imm8 fix + 0x82 opcode support (Hidemi KAWAI) + - precise self modifying code (aka BeOS install bug) + +version 0.5.3: + + - added Bochs VESA VBE support + - VGA memory map mode 3 access fix (OS/2 install fix) + - IDE fixes (Jens Axboe) + - CPU interrupt fixes + - fixed various TLB invalidation cases (NT install) + - fixed cr0.WP semantics (XP install) + - direct chaining support for SPARC and PowerPC (faster) + - ARM NWFPE support (initial patch by Ulrich Hecht) + - added specific x86 to x86 translator (close to native performance + in qemu-i386 and qemu-fast) + - shm syscalls support (Paul McKerras) + - added accurate CR0.MP/ME/TS emulation + - fixed DMA memory write access (Win95 boot floppy fix) + - graphical x86 linux loader + - command line monitor + - generic removable device support + - support of CD-ROM change + - multiple network interface support + - initial x86-64 host support (Gwenole Beauchesne) + - lret to outer priviledge fix (OS/2 install fix) + - task switch fixes (SkyOS boot) + - VM save/restore commands + - new timer API + - more precise RTC emulation (periodic timers + time updates) + - Win32 port (initial patch by Kazu) + +version 0.5.2: + + - improved soft MMU speed (assembly functions and specializing) + - improved multitasking speed by avoiding flushing TBs when + switching tasks + - improved qemu-fast speed + - improved self modifying code handling (big performance gain in + softmmu mode). + - fixed IO checking + - fixed CD-ROM detection (win98 install CD) + - fixed addseg real mode bug (GRUB boot fix) + - added ROM memory support (win98 boot) + - fixed 'call Ev' in case of paging exception + - updated the script 'qemu-binfmt-conf.sh' to use QEMU automagically + when launching executables for the supported target CPUs. + - PowerPC system emulation update (Jocelyn Mayer) + - PC floppy emulation and DMA fixes (Jocelyn Mayer) + - polled mode for PIC (Jocelyn Mayer) + - fixed PTE dirty bit handling + - fixed xadd same reg bug + - fixed cmpxchg exception safeness + - access to virtual memory in gdb stub + - task gate and NT flag fixes + - eflags optimisation fix for string operations + +version 0.5.1: + + - float access fixes when using soft mmu + - PC emulation support on PowerPC + - A20 support + - IDE CD-ROM emulation + - ARM fixes (Ulrich Hecht) + - SB16 emulation (malc) + - IRET and INT fixes in VM86 mode with IOPL=3 + - Port I/Os use TSS io map + - Full task switching/task gate support + - added verr, verw, arpl, fcmovxx + - PowerPC target support (Jocelyn Mayer) + - Major SPARC target fixes (dynamically linked programs begin to work) + +version 0.5.0: + + - full hardware level VGA emulation + - graphical display with SDL + - added PS/2 mouse and keyboard emulation + - popw (%esp) fix + - mov to/from segment data width fix + - added real mode support + - added Bochs BIOS and LGPL'ed VGA BIOS loader in qemu + - m68k host port (Richard Zidlicky) + - partial soft MMU support for memory mapped I/Os + - multi-target build + - fixed: no error code in hardware interrupts + - fixed: pop ss, mov ss, x and sti disable hardware irqs for the next insn + - correct single stepping thru string operations + - preliminary SPARC target support (Thomas M. Ogrisegg) + - tun-fd option (Rusty Russell) + - automatic IDE geometry detection + - renamed 'vl' to qemu[-fast] and user qemu to qemu-{cpu}. + - added man page + - added full soft mmu mode to launch unpatched OSes. + +version 0.4.3: + + - x86 exception fix in case of nop instruction. + - gcc 3.2.2 bug workaround (RedHat 9 fix) + - sparc and Alpha host fixes + - many ARM target fixes: 'ls' and 'bash' can be launched. + +version 0.4.2: + + - many exception handling fixes (can compile a Linux kernel inside vl) + - IDE emulation support + - initial GDB stub support + - deferred update support for disk images (Rusty Russell) + - accept User Mode Linux Copy On Write disk images + - SMP kernels can at least be booted + +version 0.4.1: + + - more accurate timer support in vl. + - more reliable NE2000 probe in vl. + - added 2.5.66 kernel in vl-test. + - added VLTMPDIR environment variable in vl. + +version 0.4: + + - initial support for ring 0 x86 processor emulation + - fixed signal handling for correct dosemu DPMI emulation + - fast x86 MMU emulation with mmap() + - fixed popl (%esp) case + - Linux kernel can be executed by QEMU with the 'vl' command. + +version 0.3: + + - initial support for ARM emulation + - added fnsave, frstor, fnstenv, fldenv FPU instructions + - added FPU register save in signal emulation + - initial ARM port + - Sparc and Alpha ports work on the regression test + - generic ioctl number conversion + - fixed ioctl type conversion + +version 0.2: + + - PowerPC disassembly and ELF symbols output (Rusty Russell) + - flock support (Rusty Russell) + - ugetrlimit support (Rusty Russell) + - fstat64 fix (Rusty Russell) + - initial Alpha port (Falk Hueffner) + - initial IA64 port (Matt Wilson) + - initial Sparc and Sparc64 port (David S. Miller) + - added HLT instruction + - LRET instruction fix. + - added GPF generation for I/Os. + - added INT3 and TF flag support. + - SHL instruction C flag fix. + - mmap emulation for host page size > 4KB + - self-modifying code support + - better VM86 support (dosemu works on non trivial programs) + - precise exception support (EIP is computed correctly in most cases) + - more precise LDT/GDT/IDT emulation + - faster segment load in vm86 mode + - direct chaining of basic blocks (faster emulation) + +version 0.1.6: + + - automatic library search system. QEMU can now work with unpatched + ELF dynamic loader and libc (Rusty Russell). + - ISO C warning fixes (Alistair Strachan) + - first self-virtualizable version (works only as long as the + translation cache is not flushed) + - RH9 fixes + +version 0.1.5: + + - ppc64 support + personality() patch (Rusty Russell) + - first Alpha CPU patches (Falk Hueffner) + - removed bfd.h dependancy + - fixed shrd, shld, idivl and divl on PowerPC. + - fixed buggy glibc PowerPC rint() function (test-i386 passes now on PowerPC). + +version 0.1.4: + + - more accurate VM86 emulation (can launch small DOS 16 bit + executables in wine). + - fixed push/pop fs/gs + - added iret instruction. + - added times() syscall and SIOCATMARK ioctl. + +version 0.1.3: + + - S390 support (Ulrich Weigand) + - glibc 2.3.x compile fix (Ulrich Weigand) + - socketcall endian fix (Ulrich Weigand) + - struct sockaddr endian fix (Ulrich Weigand) + - sendmsg/recvmsg endian fix (Ulrich Weigand) + - execve endian fix (Ulrich Weigand) + - fdset endian fix (Ulrich Weigand) + - partial setsockopt syscall support (Ulrich Weigand) + - more accurate pushf/popf emulation + - first partial vm86() syscall support (can be used with runcom example). + - added bound, cmpxchg8b, cpuid instructions + - added 16 bit addressing support/override for string operations + - poll() fix + +version 0.1.2: + + - compile fixes + - xlat instruction + - xchg instruction memory lock + - added simple vm86 example (not working with QEMU yet). The 54 byte + DOS executable 'pi_10.com' program was released by Bertram + Felgenhauer (more information at http://www.boo.net/~jasonp/pipage.html). + +version 0.1.1: + + - glibc 2.2 compilation fixes + - added -s and -L options + - binary distribution of x86 glibc and wine + - big endian fixes in ELF loader and getdents. + +version 0.1: + + - initial public release. diff --git a/HACKING b/HACKING new file mode 100644 index 0000000..6ba9d7e --- /dev/null +++ b/HACKING @@ -0,0 +1,125 @@ +1. Preprocessor + +For variadic macros, stick with this C99-like syntax: + +#define DPRINTF(fmt, ...) \ + do { printf("IRQ: " fmt, ## __VA_ARGS__); } while (0) + +2. C types + +It should be common sense to use the right type, but we have collected +a few useful guidelines here. + +2.1. Scalars + +If you're using "int" or "long", odds are good that there's a better type. +If a variable is counting something, it should be declared with an +unsigned type. + +If it's host memory-size related, size_t should be a good choice (use +ssize_t only if required). Guest RAM memory offsets must use ram_addr_t, +but only for RAM, it may not cover whole guest address space. + +If it's file-size related, use off_t. +If it's file-offset related (i.e., signed), use off_t. +If it's just counting small numbers use "unsigned int"; +(on all but oddball embedded systems, you can assume that that +type is at least four bytes wide). + +In the event that you require a specific width, use a standard type +like int32_t, uint32_t, uint64_t, etc. The specific types are +mandatory for VMState fields. + +Don't use Linux kernel internal types like u32, __u32 or __le32. + +Use target_phys_addr_t for guest physical addresses except pcibus_t +for PCI addresses. In addition, ram_addr_t is a QEMU internal address +space that maps guest RAM physical addresses into an intermediate +address space that can map to host virtual address spaces. Generally +speaking, the size of guest memory can always fit into ram_addr_t but +it would not be correct to store an actual guest physical address in a +ram_addr_t. + +Use target_ulong (or abi_ulong) for CPU virtual addresses, however +devices should not need to use target_ulong. + +Of course, take all of the above with a grain of salt. If you're about +to use some system interface that requires a type like size_t, pid_t or +off_t, use matching types for any corresponding variables. + +Also, if you try to use e.g., "unsigned int" as a type, and that +conflicts with the signedness of a related variable, sometimes +it's best just to use the *wrong* type, if "pulling the thread" +and fixing all related variables would be too invasive. + +Finally, while using descriptive types is important, be careful not to +go overboard. If whatever you're doing causes warnings, or requires +casts, then reconsider or ask for help. + +2.2. Pointers + +Ensure that all of your pointers are "const-correct". +Unless a pointer is used to modify the pointed-to storage, +give it the "const" attribute. That way, the reader knows +up-front that this is a read-only pointer. Perhaps more +importantly, if we're diligent about this, when you see a non-const +pointer, you're guaranteed that it is used to modify the storage +it points to, or it is aliased to another pointer that is. + +2.3. Typedefs +Typedefs are used to eliminate the redundant 'struct' keyword. + +2.4. Reserved namespaces in C and POSIX +Underscore capital, double underscore, and underscore 't' suffixes should be +avoided. + +3. Low level memory management + +Use of the malloc/free/realloc/calloc/valloc/memalign/posix_memalign +APIs is not allowed in the QEMU codebase. Instead of these routines, +use the replacement qemu_malloc/qemu_mallocz/qemu_realloc/qemu_free or +qemu_vmalloc/qemu_memalign/qemu_vfree APIs. + +Please note that NULL check for the qemu_malloc result is redundant and +that qemu_malloc() call with zero size is not allowed. + +Memory allocated by qemu_vmalloc or qemu_memalign must be freed with +qemu_vfree, since breaking this will cause problems on Win32 and user +emulators. + +4. String manipulation + +Do not use the strncpy function. According to the man page, it does +*not* guarantee a NULL-terminated buffer, which makes it extremely dangerous +to use. Instead, use functionally equivalent function: +void pstrcpy(char *buf, int buf_size, const char *str) + +Don't use strcat because it can't check for buffer overflows, but: +char *pstrcat(char *buf, int buf_size, const char *s) + +The same limitation exists with sprintf and vsprintf, so use snprintf and +vsnprintf. + +QEMU provides other useful string functions: +int strstart(const char *str, const char *val, const char **ptr) +int stristart(const char *str, const char *val, const char **ptr) +int qemu_strnlen(const char *s, int max_len) + +There are also replacement character processing macros for isxyz and toxyz, +so instead of e.g. isalnum you should use qemu_isalnum. + +Because of the memory management rules, you must use qemu_strdup/qemu_strndup +instead of plain strdup/strndup. + +5. Printf-style functions + +Whenever you add a new printf-style function, i.e., one with a format +string argument and following "..." in its prototype, be sure to use +gcc's printf attribute directive in the prototype. + +This makes it so gcc's -Wformat and -Wformat-security options can do +their jobs and cross-check format strings with the number and types +of arguments. + +Currently many functions in QEMU are not following this rule but +patches to add the attribute would be very much appreciated. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..cbd92c0 --- /dev/null +++ b/LICENSE @@ -0,0 +1,18 @@ +The following points clarify the QEMU license: + +1) QEMU as a whole is released under the GNU General Public License + +2) Parts of QEMU have specific licenses which are compatible with the +GNU General Public License. Hence each source file contains its own +licensing information. + +In particular, the QEMU virtual CPU core library (libqemu.a) is +released under the GNU Lesser General Public License. Many hardware +device emulation sources are released under the BSD license. + +3) The Tiny Code Generator (TCG) is released under the BSD license + (see license headers in files). + +4) QEMU is a trademark of Fabrice Bellard. + +Fabrice Bellard. diff --git a/MAINTAINERS b/MAINTAINERS new file mode 100644 index 0000000..ab48380 --- /dev/null +++ b/MAINTAINERS @@ -0,0 +1,489 @@ +QEMU Maintainers +================ + +The intention of this file is not to establish who owns what portions of the +code base, but to provide a set of names that developers can consult when they +have a question about a particular subset and also to provide a set of names +to be CC'd when submitting a patch to obtain appropriate review. + +In general, if you have a question about inclusion of a patch, you should +consult qemu-devel and not any specific individual privately. + +Descriptions of section entries: + + M: Mail patches to: FullName + L: Mailing list that is relevant to this area + W: Web-page with status/info + Q: Patchwork web based patch tracking system site + T: SCM tree type and location. Type is one of: git, hg, quilt, stgit. + S: Status, one of the following: + Supported: Someone is actually paid to look after this. + Maintained: Someone actually looks after it. + Odd Fixes: It has a maintainer but they don't have time to do + much other than throw the odd patch in. See below.. + Orphan: No current maintainer [but maybe you could take the + role as you write your new code]. + Obsolete: Old code. Something tagged obsolete generally means + it has been replaced by a better system and you + should be using that. + F: Files and directories with wildcard patterns. + A trailing slash includes all files and subdirectory files. + F: drivers/net/ all files in and below drivers/net + F: drivers/net/* all files in drivers/net, but not below + F: */net/* all files in "any top level directory"/net + One pattern per line. Multiple F: lines acceptable. + X: Files and directories that are NOT maintained, same rules as F: + Files exclusions are tested before file matches. + Can be useful for excluding a specific subdirectory, for instance: + F: net/ + X: net/ipv6/ + matches all files in and below net excluding net/ipv6/ + K: Keyword perl extended regex pattern to match content in a + patch or file. For instance: + K: of_get_profile + matches patches or files that contain "of_get_profile" + K: \b(printk|pr_(info|err))\b + matches patches or files that contain one or more of the words + printk, pr_info or pr_err + One regex pattern per line. Multiple K: lines acceptable. + + +General Project Administration +------------------------------ +M: Anthony Liguori +M: Paul Brook + +Guest CPU cores (TCG): +---------------------- +Alpha +M: qemu-devel@nongnu.org +S: Orphan +F: target-alpha/ + +ARM +M: Paul Brook +S: Maintained +F: target-arm/ + +CRIS +M: Edgar E. Iglesias +S: Maintained +F: target-cris/ + +M68K +M: Paul Brook +S: Maintained +F: target-m68k/ + +MicroBlaze +M: Edgar E. Iglesias +S: Maintained +F: target-microblaze/ + +MIPS +M: Aurelien Jarno +S: Maintained +F: target-mips/ + +PowerPC +M: Alexander Graf +S: Maintained +F: target-ppc/ + +S390 +M: Alexander Graf +S: Maintained +F: target-s390x/ + +SH4 +M: Aurelien Jarno +S: Maintained +F: target-sh4/ + +SPARC +M: Blue Swirl +S: Maintained +F: target-sparc/ + +X86 +M: qemu-devel@nongnu.org +S: Odd Fixes +F: target-i386/ + +Guest CPU Cores (KVM): +---------------------- + +Overall +M: Avi Kivity +M: Marcelo Tosatti +L: kvm@vger.kernel.org +S: Supported +F: kvm-* +F: */kvm.* + +PPC +M: Alexander Graf +S: Maintained +F: target-ppc/kvm.c + +S390 +M: Alexander Graf +S: Maintained +F: target-s390x/kvm.c + +X86 +M: Avi Kivity +M: Marcelo Tosatti +L: kvm@vger.kernel.org +S: Supported +F: target-i386/kvm.c + +ARM Machines +------------ +Gumstix +M: qemu-devel@nongnu.org +S: Orphan +F: hw/gumstix.c + +Integrator CP +M: Paul Brook +S: Maintained +F: hw/integratorcp.c + +Mainstone +M: qemu-devel@nongnu.org +S: Orphan +F: hw/mainstone.c + +Musicpal +M: Jan Kiszka +S: Maintained +F: hw/musicpal.c + +nSeries +M: Andrzej Zaborowski +S: Maintained +F: hw/nseries.c + +Palm +M: Andrzej Zaborowski +S: Maintained +F: hw/palm.c + +Real View +M: Paul Brook +S: Maintained +F: hw/realview* + +Spitz +M: Andrzej Zaborowski +S: Maintained +F: hw/spitz.c + +Stellaris +M: Paul Brook +S: Maintained +F: hw/stellaris.c + +Versatile PB +M: Paul Brook +S: Maintained +F: hw/versatilepb.c + +CRIS Machines +------------- +Axis Dev88 +M: Edgar E. Iglesias +S: Maintained +F: hw/axis_dev88.c + +etraxfs +M: Edgar E. Iglesias +S: Maintained +F: hw/etraxfs.c + +M68K Machines +------------- +an5206 +M: Paul Brook +S: Maintained +F: hw/an5206.c + +dummy_m68k +M: Paul Brook +S: Maintained +F: hw/dummy_m68k.c + +mcf5208 +M: Paul Brook +S: Maintained +F: hw/mcf5208.c + +MicroBlaze Machines +------------------- +petalogix_s3adsp1800 +M: Edgar E. Iglesias +S: Maintained +F: hw/petalogix_s3adsp1800.c + +MIPS Machines +------------- +Jazz +M: Hervé Poussineau +S: Maintained +F: hw/mips_jazz.c + +Malta +M: Aurelien Jarno +S: Maintained +F: hw/mips_malta.c + +Mipssim +M: qemu-devel@nongnu.org +S: Orphan +F: hw/mips_mipssim.c + +R4000 +M: Aurelien Jarno +S: Maintained +F: hw/mips_r4k.c + +PowerPC Machines +---------------- +405 +M: Alexander Graf +S: Maintained +F: hw/ppc405_boards.c + +New World +M: Alexander Graf +S: Maintained +F: hw/ppc_newworld.c + +Old World +M: Alexander Graf +S: Maintained +F: hw/ppc_oldworld.c + +Prep +M: qemu-devel@nongnu.org +S: Orphan +F: hw/ppc_prep.c + +SH4 Machines +------------ +R2D +M: Magnus Damm +S: Maintained +F: hw/r2d.c + +Shix +M: Magnus Damm +S: Orphan +F: hw/shix.c + +SPARC Machines +-------------- +Sun4m +M: Blue Swirl +S: Maintained +F: hw/sun4m.c + +Sun4u +M: Blue Swirl +S: Maintained +F: hw/sun4u.c + +S390 Machines +------------- +S390 Virtio +M: Alexander Graf +S: Maintained +F: hw/s390-*.c + +X86 Machines +------------ +PC +M: Anthony Liguori +S: Supported +F: hw/pc.[ch] hw/pc_piix.c + +Devices +------- +IDE +M: Kevin Wolf +S: Odd Fixes +F: hw/ide/ + +PCI +M: Michael S. Tsirkin +S: Supported +F: hw/pci* +F: hw/piix* + +SCSI +M: Paul Brook +M: Kevin Wolf +S: Odd Fixes +F: hw/lsi53c895a.c +F: hw/scsi* + +USB +M: Gerd Hoffmann +S: Maintained +F: hw/usb* + +vhost +M: Michael S. Tsirkin +S: Supported +F: hw/vhost* + +virtio +M: Anthony Liguori +S: Supported +F: hw/virtio* + +virtio-9p +M: Venkateswararao Jujjuri (JV) +S: Supported +F: hw/virtio-9p* + +virtio-blk +M: Kevin Wolf +S: Supported +F: hw/virtio-blk* + +virtio-serial +M: Amit Shah +S: Supported +F: hw/virtio-serial* +F: hw/virtio-console* + +Subsystems +---------- +Audio +M: Vassili Karpov (malc) +S: Maintained +F: audio/ + +Block +M: Kevin Wolf +S: Supported +F: block* +F: block/ + +Character Devices +M: Anthony Liguori +S: Maintained +F: qemu-char.c + +GDB stub +M: qemu-devel@nongnu.org +S: Odd Fixes +F: gdbstub* +F: gdb-xml/ + +SPICE +M: Gerd Hoffmann +S: Supported +F: ui/qemu-spice.h +F: ui/spice-*.c +F: audio/spiceaudio.c +F: hw/qxl* + +Graphics +M: Anthony Liguori +S: Maintained +F: ui/ + +Main loop +M: Anthony Liguori +S: Supported +F: vl.c + +Monitor (QMP/HMP) +M: Luiz Capitulino +M: Markus Armbruster +S: Supported +F: monitor.c + +Network device layer +M: Anthony Liguori +M: Mark McLoughlin +S: Maintained +F: net/ + +SLIRP +M: qemu-devel@nongnu.org +S: Orphan +F: slirp/ + +Usermode Emulation +------------------ +BSD user +M: Blue Swirl +S: Maintained +F: bsd-user/ + +Darwin user +M: qemu-devel@nongnu.org +S: Orphan +F: darwin-user/ + +Linux user +M: Riku Voipio +S: Maintained +F: linux-user/ + +Tiny Code Generator (TCG) +------------------------- +Common code +M: qemu-devel@nongnu.org +S: Maintained +F: tcg/ + +ARM target +M: Andrzej Zaborowski +S: Maintained +F: tcg/arm/ + +HPPA target +M: Richard Henderson +S: Maintained +F: tcg/hppa/ + +i386 target +M: qemu-devel@nongnu.org +S: Maintained +F: tcg/i386/ + +IA64 target +M: Aurelien Jarno +S: Maintained +F: tcg/ia64/ + +MIPS target +M: Aurelien Jarno +S: Maintained +F: tcg/mips/ + +PPC +M: Vassili Karpov (malc) +S: Maintained +F: tcg/ppc/ + +PPC64 target +M: Vassili Karpov (malc) +S: Maintained +F: tcg/ppc64/ + +S390 target +M: Alexander Graf +M: Richard Henderson +S: Maintained +F: tcg/s390/ + +SPARC target +M: Blue Swirl +S: Maintained +F: tcg/sparc/ diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..eca4c76 --- /dev/null +++ b/Makefile @@ -0,0 +1,369 @@ +# Makefile for QEMU. + +GENERATED_HEADERS = config-host.h trace.h qemu-options.def +ifeq ($(TRACE_BACKEND),dtrace) +GENERATED_HEADERS += trace-dtrace.h +endif + +ifneq ($(wildcard config-host.mak),) +# Put the all: rule here so that config-host.mak can contain dependencies. +all: build-all +include config-host.mak +include $(SRC_PATH)/rules.mak +config-host.mak: $(SRC_PATH)/configure + @echo $@ is out-of-date, running configure + @sed -n "/.*Configured with/s/[^:]*: //p" $@ | sh +else +config-host.mak: + @echo "Please call configure before running make!" + @exit 1 +endif + +# Don't try to regenerate Makefile or configure +# We don't generate any of them +Makefile: ; +configure: ; + +.PHONY: all clean cscope distclean dvi html info install install-doc \ + pdf recurse-all speed tar tarbin test build-all + +$(call set-vpath, $(SRC_PATH):$(SRC_PATH)/hw) + +LIBS+=-lz $(LIBS_TOOLS) + +ifdef BUILD_DOCS +DOCS=qemu-doc.html qemu-tech.html qemu.1 qemu-img.1 qemu-nbd.8 QMP/qmp-commands.txt +else +DOCS= +endif + +SUBDIR_MAKEFLAGS=$(if $(V),,--no-print-directory) +SUBDIR_DEVICES_MAK=$(patsubst %, %/config-devices.mak, $(TARGET_DIRS)) +SUBDIR_DEVICES_MAK_DEP=$(patsubst %, %/config-devices.mak.d, $(TARGET_DIRS)) + +config-all-devices.mak: $(SUBDIR_DEVICES_MAK) + $(call quiet-command,cat $(SUBDIR_DEVICES_MAK) | grep =y | sort -u > $@," GEN $@") + +-include $(SUBDIR_DEVICES_MAK_DEP) + +%/config-devices.mak: default-configs/%.mak + $(call quiet-command,$(SHELL) $(SRC_PATH)/scripts/make_device_config.sh $@ $<, " GEN $@") + @if test -f $@; then \ + if cmp -s $@.old $@; then \ + mv $@.tmp $@; \ + cp -p $@ $@.old; \ + else \ + if test -f $@.old; then \ + echo "WARNING: $@ (user modified) out of date.";\ + else \ + echo "WARNING: $@ out of date.";\ + fi; \ + echo "Run \"make defconfig\" to regenerate."; \ + rm $@.tmp; \ + fi; \ + else \ + mv $@.tmp $@; \ + cp -p $@ $@.old; \ + fi + +defconfig: + rm -f config-all-devices.mak $(SUBDIR_DEVICES_MAK) + +-include config-all-devices.mak + +build-all: $(DOCS) $(TOOLS) recurse-all + +config-host.h: config-host.h-timestamp +config-host.h-timestamp: config-host.mak +qemu-options.def: $(SRC_PATH)/qemu-options.hx + $(call quiet-command,sh $(SRC_PATH)/scripts/hxtool -h < $< > $@," GEN $@") + +SUBDIR_RULES=$(patsubst %,subdir-%, $(TARGET_DIRS)) + +subdir-%: $(GENERATED_HEADERS) + $(call quiet-command,$(MAKE) $(SUBDIR_MAKEFLAGS) -C $* V="$(V)" TARGET_DIR="$*/" all,) + +ifneq ($(wildcard config-host.mak),) +include $(SRC_PATH)/Makefile.objs +endif + +$(common-obj-y): $(GENERATED_HEADERS) +$(filter %-softmmu,$(SUBDIR_RULES)): $(trace-obj-y) $(common-obj-y) subdir-libdis + +$(filter %-user,$(SUBDIR_RULES)): $(GENERATED_HEADERS) $(trace-obj-y) subdir-libdis-user subdir-libuser + +ROMSUBDIR_RULES=$(patsubst %,romsubdir-%, $(ROMS)) +romsubdir-%: + $(call quiet-command,$(MAKE) $(SUBDIR_MAKEFLAGS) -C pc-bios/$* V="$(V)" TARGET_DIR="$*/",) + +ALL_SUBDIRS=$(TARGET_DIRS) $(patsubst %,pc-bios/%, $(ROMS)) + +recurse-all: $(SUBDIR_RULES) $(ROMSUBDIR_RULES) + +audio/audio.o audio/fmodaudio.o: QEMU_CFLAGS += $(FMOD_CFLAGS) + +QEMU_CFLAGS+=$(CURL_CFLAGS) + +ui/cocoa.o: ui/cocoa.m + +ui/sdl.o audio/sdlaudio.o ui/sdl_zoom.o baum.o: QEMU_CFLAGS += $(SDL_CFLAGS) + +ui/vnc.o: QEMU_CFLAGS += $(VNC_TLS_CFLAGS) + +bt-host.o: QEMU_CFLAGS += $(BLUEZ_CFLAGS) + +ifeq ($(TRACE_BACKEND),dtrace) +trace.h: trace.h-timestamp trace-dtrace.h +else +trace.h: trace.h-timestamp +endif +trace.h-timestamp: $(SRC_PATH)/trace-events config-host.mak + $(call quiet-command,sh $(SRC_PATH)/scripts/tracetool --$(TRACE_BACKEND) -h < $< > $@," GEN trace.h") + @cmp -s $@ trace.h || cp $@ trace.h + +trace.c: trace.c-timestamp +trace.c-timestamp: $(SRC_PATH)/trace-events config-host.mak + $(call quiet-command,sh $(SRC_PATH)/scripts/tracetool --$(TRACE_BACKEND) -c < $< > $@," GEN trace.c") + @cmp -s $@ trace.c || cp $@ trace.c + +trace.o: trace.c $(GENERATED_HEADERS) + +trace-dtrace.h: trace-dtrace.dtrace + $(call quiet-command,dtrace -o $@ -h -s $<, " GEN trace-dtrace.h") + +# Normal practice is to name DTrace probe file with a '.d' extension +# but that gets picked up by QEMU's Makefile as an external dependancy +# rule file. So we use '.dtrace' instead +trace-dtrace.dtrace: trace-dtrace.dtrace-timestamp +trace-dtrace.dtrace-timestamp: $(SRC_PATH)/trace-events config-host.mak + $(call quiet-command,sh $(SRC_PATH)/scripts/tracetool --$(TRACE_BACKEND) -d < $< > $@," GEN trace-dtrace.dtrace") + @cmp -s $@ trace-dtrace.dtrace || cp $@ trace-dtrace.dtrace + +trace-dtrace.o: trace-dtrace.dtrace $(GENERATED_HEADERS) + $(call quiet-command,dtrace -o $@ -G -s $<, " GEN trace-dtrace.o") + +simpletrace.o: simpletrace.c $(GENERATED_HEADERS) + +version.o: $(SRC_PATH)/version.rc config-host.mak + $(call quiet-command,$(WINDRES) -I. -o $@ $<," RC $(TARGET_DIR)$@") + +version-obj-$(CONFIG_WIN32) += version.o +###################################################################### + +qemu-img.o: qemu-img-cmds.h +qemu-img.o qemu-tool.o qemu-nbd.o qemu-io.o cmd.o: $(GENERATED_HEADERS) + +qemu-img$(EXESUF): qemu-img.o qemu-tool.o qemu-error.o $(oslib-obj-y) $(trace-obj-y) $(block-obj-y) $(qobject-obj-y) $(version-obj-y) qemu-timer-common.o + +qemu-nbd$(EXESUF): qemu-nbd.o qemu-tool.o qemu-error.o $(oslib-obj-y) $(trace-obj-y) $(block-obj-y) $(qobject-obj-y) $(version-obj-y) qemu-timer-common.o + +qemu-io$(EXESUF): qemu-io.o cmd.o qemu-tool.o qemu-error.o $(oslib-obj-y) $(trace-obj-y) $(block-obj-y) $(qobject-obj-y) $(version-obj-y) qemu-timer-common.o + +qemu-img-cmds.h: $(SRC_PATH)/qemu-img-cmds.hx + $(call quiet-command,sh $(SRC_PATH)/scripts/hxtool -h < $< > $@," GEN $@") + +check-qint.o check-qstring.o check-qdict.o check-qlist.o check-qfloat.o check-qjson.o: $(GENERATED_HEADERS) + +CHECK_PROG_DEPS = qemu-malloc.o $(oslib-obj-y) $(trace-obj-y) + +check-qint: check-qint.o qint.o $(CHECK_PROG_DEPS) +check-qstring: check-qstring.o qstring.o $(CHECK_PROG_DEPS) +check-qdict: check-qdict.o qdict.o qfloat.o qint.o qstring.o qbool.o qlist.o $(CHECK_PROG_DEPS) +check-qlist: check-qlist.o qlist.o qint.o $(CHECK_PROG_DEPS) +check-qfloat: check-qfloat.o qfloat.o $(CHECK_PROG_DEPS) +check-qjson: check-qjson.o qfloat.o qint.o qdict.o qstring.o qlist.o qbool.o qjson.o json-streamer.o json-lexer.o json-parser.o $(CHECK_PROG_DEPS) + +clean: +# avoid old build problems by removing potentially incorrect old files + rm -f config.mak op-i386.h opc-i386.h gen-op-i386.h op-arm.h opc-arm.h gen-op-arm.h + rm -f qemu-options.def + rm -f *.o *.d *.a $(TOOLS) TAGS cscope.* *.pod *~ */*~ + rm -f slirp/*.o slirp/*.d audio/*.o audio/*.d block/*.o block/*.d net/*.o net/*.d fsdev/*.o fsdev/*.d ui/*.o ui/*.d + rm -f qemu-img-cmds.h + rm -f trace.c trace.h trace.c-timestamp trace.h-timestamp + rm -f trace-dtrace.dtrace trace-dtrace.dtrace-timestamp + rm -f trace-dtrace.h trace-dtrace.h-timestamp + $(MAKE) -C tests clean + for d in $(ALL_SUBDIRS) libhw32 libhw64 libuser libdis libdis-user; do \ + if test -d $$d; then $(MAKE) -C $$d $@ || exit 1; fi; \ + rm -f $$d/qemu-options.def; \ + done + +distclean: clean + rm -f config-host.mak config-host.h* config-host.ld $(DOCS) qemu-options.texi qemu-img-cmds.texi qemu-monitor.texi + rm -f config-all-devices.mak + rm -f roms/seabios/config.mak roms/vgabios/config.mak + rm -f qemu-doc.info qemu-doc.aux qemu-doc.cp qemu-doc.dvi qemu-doc.fn qemu-doc.info qemu-doc.ky qemu-doc.log qemu-doc.pdf qemu-doc.pg qemu-doc.toc qemu-doc.tp qemu-doc.vr + rm -f qemu-tech.info qemu-tech.aux qemu-tech.cp qemu-tech.dvi qemu-tech.fn qemu-tech.info qemu-tech.ky qemu-tech.log qemu-tech.pdf qemu-tech.pg qemu-tech.toc qemu-tech.tp qemu-tech.vr + for d in $(TARGET_DIRS) libhw32 libhw64 libuser libdis libdis-user; do \ + rm -rf $$d || exit 1 ; \ + done + +KEYMAPS=da en-gb et fr fr-ch is lt modifiers no pt-br sv \ +ar de en-us fi fr-be hr it lv nl pl ru th \ +common de-ch es fo fr-ca hu ja mk nl-be pt sl tr + +ifdef INSTALL_BLOBS +BLOBS=bios.bin vgabios.bin vgabios-cirrus.bin \ +vgabios-stdvga.bin vgabios-vmware.bin vgabios-qxl.bin \ +ppc_rom.bin openbios-sparc32 openbios-sparc64 openbios-ppc \ +gpxe-eepro100-80861209.rom \ +pxe-e1000.bin \ +pxe-ne2k_pci.bin pxe-pcnet.bin \ +pxe-rtl8139.bin pxe-virtio.bin \ +bamboo.dtb petalogix-s3adsp1800.dtb \ +multiboot.bin linuxboot.bin \ +s390-zipl.rom +else +BLOBS= +endif + +install-doc: $(DOCS) + $(INSTALL_DIR) "$(DESTDIR)$(docdir)" + $(INSTALL_DATA) qemu-doc.html qemu-tech.html "$(DESTDIR)$(docdir)" +ifdef CONFIG_POSIX + $(INSTALL_DIR) "$(DESTDIR)$(mandir)/man1" + $(INSTALL_DATA) qemu.1 qemu-img.1 "$(DESTDIR)$(mandir)/man1" + $(INSTALL_DIR) "$(DESTDIR)$(mandir)/man8" + $(INSTALL_DATA) qemu-nbd.8 "$(DESTDIR)$(mandir)/man8" +endif + +install-sysconfig: + $(INSTALL_DIR) "$(DESTDIR)$(sysconfdir)/qemu" + $(INSTALL_DATA) $(SRC_PATH)/sysconfigs/target/target-x86_64.conf "$(DESTDIR)$(sysconfdir)/qemu" + +install: all $(if $(BUILD_DOCS),install-doc) install-sysconfig + $(INSTALL_DIR) "$(DESTDIR)$(bindir)" +ifneq ($(TOOLS),) + $(INSTALL_PROG) $(STRIP_OPT) $(TOOLS) "$(DESTDIR)$(bindir)" +endif +ifneq ($(BLOBS),) + $(INSTALL_DIR) "$(DESTDIR)$(datadir)" + set -e; for x in $(BLOBS); do \ + $(INSTALL_DATA) $(SRC_PATH)/pc-bios/$$x "$(DESTDIR)$(datadir)"; \ + done +endif + $(INSTALL_DIR) "$(DESTDIR)$(datadir)/keymaps" + set -e; for x in $(KEYMAPS); do \ + $(INSTALL_DATA) $(SRC_PATH)/pc-bios/keymaps/$$x "$(DESTDIR)$(datadir)/keymaps"; \ + done + for d in $(TARGET_DIRS); do \ + $(MAKE) -C $$d $@ || exit 1 ; \ + done + +# various test targets +test speed: all + $(MAKE) -C tests $@ + +.PHONY: TAGS +TAGS: + find "$(SRC_PATH)" -name '*.[hc]' -print0 | xargs -0 etags + +cscope: + rm -f ./cscope.* + find . -name "*.[ch]" -print | sed 's,^\./,,' > ./cscope.files + cscope -b + +# documentation +MAKEINFO=makeinfo +MAKEINFOFLAGS=--no-headers --no-split --number-sections +TEXIFLAG=$(if $(V),,--quiet) +%.dvi: %.texi + $(call quiet-command,texi2dvi $(TEXIFLAG) -I . $<," GEN $@") + +%.html: %.texi + $(call quiet-command,$(MAKEINFO) $(MAKEINFOFLAGS) --html $< -o $@, \ + " GEN $@") + +%.info: %.texi + $(call quiet-command,$(MAKEINFO) $< -o $@," GEN $@") + +%.pdf: %.texi + $(call quiet-command,texi2pdf $(TEXIFLAG) -I . $<," GEN $@") + +qemu-options.texi: $(SRC_PATH)/qemu-options.hx + $(call quiet-command,sh $(SRC_PATH)/scripts/hxtool -t < $< > $@," GEN $@") + +qemu-monitor.texi: $(SRC_PATH)/hmp-commands.hx + $(call quiet-command,sh $(SRC_PATH)/scripts/hxtool -t < $< > $@," GEN $@") + +QMP/qmp-commands.txt: $(SRC_PATH)/qmp-commands.hx + $(call quiet-command,sh $(SRC_PATH)/scripts/hxtool -q < $< > $@," GEN $@") + +qemu-img-cmds.texi: $(SRC_PATH)/qemu-img-cmds.hx + $(call quiet-command,sh $(SRC_PATH)/scripts/hxtool -t < $< > $@," GEN $@") + +qemu.1: qemu-doc.texi qemu-options.texi qemu-monitor.texi + $(call quiet-command, \ + perl -Ww -- $(SRC_PATH)/scripts/texi2pod.pl $< qemu.pod && \ + pod2man --section=1 --center=" " --release=" " qemu.pod > $@, \ + " GEN $@") + +qemu-img.1: qemu-img.texi qemu-img-cmds.texi + $(call quiet-command, \ + perl -Ww -- $(SRC_PATH)/scripts/texi2pod.pl $< qemu-img.pod && \ + pod2man --section=1 --center=" " --release=" " qemu-img.pod > $@, \ + " GEN $@") + +qemu-nbd.8: qemu-nbd.texi + $(call quiet-command, \ + perl -Ww -- $(SRC_PATH)/scripts/texi2pod.pl $< qemu-nbd.pod && \ + pod2man --section=8 --center=" " --release=" " qemu-nbd.pod > $@, \ + " GEN $@") + +dvi: qemu-doc.dvi qemu-tech.dvi +html: qemu-doc.html qemu-tech.html +info: qemu-doc.info qemu-tech.info +pdf: qemu-doc.pdf qemu-tech.pdf + +qemu-doc.dvi qemu-doc.html qemu-doc.info qemu-doc.pdf: \ + qemu-img.texi qemu-nbd.texi qemu-options.texi \ + qemu-monitor.texi qemu-img-cmds.texi + +VERSION ?= $(shell cat VERSION) +FILE = qemu-$(VERSION) + +# tar release (use 'make -k tar' on a checkouted tree) +tar: + rm -rf /tmp/$(FILE) + cp -r . /tmp/$(FILE) + cd /tmp && tar zcvf ~/$(FILE).tar.gz $(FILE) --exclude CVS --exclude .git --exclude .svn + rm -rf /tmp/$(FILE) + +SYSTEM_TARGETS=$(filter %-softmmu,$(TARGET_DIRS)) +SYSTEM_PROGS=$(patsubst qemu-system-i386,qemu, \ + $(patsubst %-softmmu,qemu-system-%, \ + $(SYSTEM_TARGETS))) + +USER_TARGETS=$(filter %-user,$(TARGET_DIRS)) +USER_PROGS=$(patsubst %-bsd-user,qemu-%, \ + $(patsubst %-darwin-user,qemu-%, \ + $(patsubst %-linux-user,qemu-%, \ + $(USER_TARGETS)))) + +# generate a binary distribution +tarbin: + cd / && tar zcvf ~/qemu-$(VERSION)-$(ARCH).tar.gz \ + $(patsubst %,$(bindir)/%, $(SYSTEM_PROGS)) \ + $(patsubst %,$(bindir)/%, $(USER_PROGS)) \ + $(bindir)/qemu-img \ + $(bindir)/qemu-nbd \ + $(datadir)/bios.bin \ + $(datadir)/vgabios.bin \ + $(datadir)/vgabios-cirrus.bin \ + $(datadir)/ppc_rom.bin \ + $(datadir)/openbios-sparc32 \ + $(datadir)/openbios-sparc64 \ + $(datadir)/openbios-ppc \ + $(datadir)/pxe-ne2k_pci.bin \ + $(datadir)/pxe-rtl8139.bin \ + $(datadir)/pxe-pcnet.bin \ + $(datadir)/pxe-e1000.bin \ + $(docdir)/qemu-doc.html \ + $(docdir)/qemu-tech.html \ + $(mandir)/man1/qemu.1 \ + $(mandir)/man1/qemu-img.1 \ + $(mandir)/man8/qemu-nbd.8 + +# Include automatically generated dependency files +-include $(wildcard *.d audio/*.d slirp/*.d block/*.d net/*.d ui/*.d) diff --git a/Makefile.dis b/Makefile.dis new file mode 100644 index 0000000..3e1fcaf --- /dev/null +++ b/Makefile.dis @@ -0,0 +1,23 @@ +# Makefile for disassemblers. + +include ../config-host.mak +include config.mak +include $(SRC_PATH)/rules.mak + +.PHONY: all + +$(call set-vpath, $(SRC_PATH)) + +QEMU_CFLAGS+=-I.. + +include $(SRC_PATH)/Makefile.objs + +all: $(libdis-y) +# Dummy command so that make thinks it has done something + @true + +clean: + rm -f *.o *.d *.a *~ + +# Include automatically generated dependency files +-include $(wildcard *.d */*.d) diff --git a/Makefile.hw b/Makefile.hw new file mode 100644 index 0000000..b9181ab --- /dev/null +++ b/Makefile.hw @@ -0,0 +1,24 @@ +# Makefile for qemu target independent devices. + +include ../config-host.mak +include ../config-all-devices.mak +include config.mak +include $(SRC_PATH)/rules.mak + +.PHONY: all + +$(call set-vpath, $(SRC_PATH):$(SRC_PATH)/hw) + +QEMU_CFLAGS+=-I.. -I$(SRC_PATH)/fpu + +include $(SRC_PATH)/Makefile.objs + +all: $(hw-obj-y) +# Dummy command so that make thinks it has done something + @true + +clean: + rm -f *.o */*.o *.d */*.d *.a */*.a *~ */*~ + +# Include automatically generated dependency files +-include $(wildcard *.d */*.d) diff --git a/Makefile.objs b/Makefile.objs new file mode 100644 index 0000000..353b1a8 --- /dev/null +++ b/Makefile.objs @@ -0,0 +1,319 @@ +####################################################################### +# QObject +qobject-obj-y = qint.o qstring.o qdict.o qlist.o qfloat.o qbool.o +qobject-obj-y += qjson.o json-lexer.o json-streamer.o json-parser.o +qobject-obj-y += qerror.o + +####################################################################### +# oslib-obj-y is code depending on the OS (win32 vs posix) +oslib-obj-y = osdep.o +oslib-obj-$(CONFIG_WIN32) += oslib-win32.o +oslib-obj-$(CONFIG_POSIX) += oslib-posix.o + +####################################################################### +# block-obj-y is code used by both qemu system emulation and qemu-img + +block-obj-y = cutils.o cache-utils.o qemu-malloc.o qemu-option.o module.o +block-obj-y += nbd.o block.o aio.o aes.o qemu-config.o +block-obj-$(CONFIG_POSIX) += posix-aio-compat.o +block-obj-$(CONFIG_LINUX_AIO) += linux-aio.o + +block-nested-y += raw.o cow.o qcow.o vdi.o vmdk.o cloop.o dmg.o bochs.o vpc.o vvfat.o +block-nested-y += qcow2.o qcow2-refcount.o qcow2-cluster.o qcow2-snapshot.o qcow2-cache.o +block-nested-y += qed.o qed-gencb.o qed-l2-cache.o qed-table.o qed-cluster.o +block-nested-y += qed-check.o +block-nested-y += parallels.o nbd.o blkdebug.o sheepdog.o blkverify.o +block-nested-$(CONFIG_WIN32) += raw-win32.o +block-nested-$(CONFIG_POSIX) += raw-posix.o +block-nested-$(CONFIG_CURL) += curl.o +block-nested-$(CONFIG_RBD) += rbd.o + +block-obj-y += $(addprefix block/, $(block-nested-y)) + +net-obj-y = net.o +net-nested-y = queue.o checksum.o util.o +net-nested-y += socket.o +net-nested-y += dump.o +net-nested-$(CONFIG_POSIX) += tap.o +net-nested-$(CONFIG_LINUX) += tap-linux.o +net-nested-$(CONFIG_WIN32) += tap-win32.o +net-nested-$(CONFIG_BSD) += tap-bsd.o +net-nested-$(CONFIG_SOLARIS) += tap-solaris.o +net-nested-$(CONFIG_AIX) += tap-aix.o +net-nested-$(CONFIG_HAIKU) += tap-haiku.o +net-nested-$(CONFIG_SLIRP) += slirp.o +net-nested-$(CONFIG_VDE) += vde.o +net-obj-y += $(addprefix net/, $(net-nested-y)) + +ifeq ($(CONFIG_VIRTIO)$(CONFIG_VIRTFS),yy) +# Lots of the fsdev/9pcode is pulled in by vl.c via qemu_fsdev_add. +# only pull in the actual virtio-9p device if we also enabled virtio. +CONFIG_REALLY_VIRTFS=y +endif +fsdev-nested-$(CONFIG_VIRTFS) = qemu-fsdev.o +fsdev-obj-$(CONFIG_VIRTFS) += $(addprefix fsdev/, $(fsdev-nested-y)) + +###################################################################### +# libqemu_common.a: Target independent part of system emulation. The +# long term path is to suppress *all* target specific code in case of +# system emulation, i.e. a single QEMU executable should support all +# CPUs and machines. + +common-obj-y = $(block-obj-y) blockdev.o +common-obj-y += $(net-obj-y) +common-obj-y += $(qobject-obj-y) +common-obj-$(CONFIG_LINUX) += $(fsdev-obj-$(CONFIG_LINUX)) +common-obj-y += readline.o console.o cursor.o async.o qemu-error.o +common-obj-y += $(oslib-obj-y) +common-obj-$(CONFIG_WIN32) += os-win32.o +common-obj-$(CONFIG_POSIX) += os-posix.o + +common-obj-y += tcg-runtime.o host-utils.o +common-obj-y += irq.o ioport.o input.o +common-obj-$(CONFIG_PTIMER) += ptimer.o +common-obj-$(CONFIG_MAX7310) += max7310.o +common-obj-$(CONFIG_WM8750) += wm8750.o +common-obj-$(CONFIG_TWL92230) += twl92230.o +common-obj-$(CONFIG_TSC2005) += tsc2005.o +common-obj-$(CONFIG_LM832X) += lm832x.o +common-obj-$(CONFIG_TMP105) += tmp105.o +common-obj-$(CONFIG_STELLARIS_INPUT) += stellaris_input.o +common-obj-$(CONFIG_SSD0303) += ssd0303.o +common-obj-$(CONFIG_SSD0323) += ssd0323.o +common-obj-$(CONFIG_ADS7846) += ads7846.o +common-obj-$(CONFIG_MAX111X) += max111x.o +common-obj-$(CONFIG_DS1338) += ds1338.o +common-obj-y += i2c.o smbus.o smbus_eeprom.o +common-obj-y += eeprom93xx.o +common-obj-y += scsi-disk.o cdrom.o +common-obj-y += scsi-generic.o scsi-bus.o +common-obj-y += usb.o usb-hub.o usb-$(HOST_USB).o usb-hid.o usb-msd.o usb-wacom.o +common-obj-y += usb-serial.o usb-net.o usb-bus.o usb-desc.o +common-obj-$(CONFIG_SSI) += ssi.o +common-obj-$(CONFIG_SSI_SD) += ssi-sd.o +common-obj-$(CONFIG_SD) += sd.o +common-obj-y += bt.o bt-host.o bt-vhci.o bt-l2cap.o bt-sdp.o bt-hci.o bt-hid.o usb-bt.o +common-obj-y += bt-hci-csr.o +common-obj-y += buffered_file.o migration.o migration-tcp.o qemu-sockets.o +common-obj-y += qemu-char.o savevm.o #aio.o +common-obj-y += msmouse.o ps2.o +common-obj-y += qdev.o qdev-properties.o +common-obj-y += block-migration.o +common-obj-y += pflib.o + +common-obj-$(CONFIG_BRLAPI) += baum.o +common-obj-$(CONFIG_POSIX) += migration-exec.o migration-unix.o migration-fd.o +common-obj-$(CONFIG_WIN32) += version.o + +common-obj-$(CONFIG_SPICE) += ui/spice-core.o ui/spice-input.o ui/spice-display.o spice-qemu-char.o + +audio-obj-y = audio.o noaudio.o wavaudio.o mixeng.o +audio-obj-$(CONFIG_SDL) += sdlaudio.o +audio-obj-$(CONFIG_OSS) += ossaudio.o +audio-obj-$(CONFIG_SPICE) += spiceaudio.o +audio-obj-$(CONFIG_COREAUDIO) += coreaudio.o +audio-obj-$(CONFIG_ALSA) += alsaaudio.o +audio-obj-$(CONFIG_DSOUND) += dsoundaudio.o +audio-obj-$(CONFIG_FMOD) += fmodaudio.o +audio-obj-$(CONFIG_ESD) += esdaudio.o +audio-obj-$(CONFIG_PA) += paaudio.o +audio-obj-$(CONFIG_WINWAVE) += winwaveaudio.o +audio-obj-$(CONFIG_AUDIO_PT_INT) += audio_pt_int.o +audio-obj-$(CONFIG_AUDIO_WIN_INT) += audio_win_int.o +audio-obj-y += wavcapture.o +common-obj-y += $(addprefix audio/, $(audio-obj-y)) + +ui-obj-y += keymaps.o +ui-obj-$(CONFIG_SDL) += sdl.o sdl_zoom.o x_keymap.o +ui-obj-$(CONFIG_CURSES) += curses.o +ui-obj-y += vnc.o d3des.o +ui-obj-y += vnc-enc-zlib.o vnc-enc-hextile.o +ui-obj-y += vnc-enc-tight.o vnc-palette.o +ui-obj-$(CONFIG_VNC_TLS) += vnc-tls.o vnc-auth-vencrypt.o +ui-obj-$(CONFIG_VNC_SASL) += vnc-auth-sasl.o +ui-obj-$(CONFIG_COCOA) += cocoa.o +ifdef CONFIG_VNC_THREAD +ui-obj-y += vnc-jobs-async.o +else +ui-obj-y += vnc-jobs-sync.o +endif +common-obj-y += $(addprefix ui/, $(ui-obj-y)) + +common-obj-y += iov.o acl.o +common-obj-$(CONFIG_THREAD) += qemu-thread.o +common-obj-$(CONFIG_IOTHREAD) += compatfd.o +common-obj-y += notify.o event_notifier.o +common-obj-y += qemu-timer.o qemu-timer-common.o + +slirp-obj-y = cksum.o if.o ip_icmp.o ip_input.o ip_output.o +slirp-obj-y += slirp.o mbuf.o misc.o sbuf.o socket.o tcp_input.o tcp_output.o +slirp-obj-y += tcp_subr.o tcp_timer.o udp.o bootp.o tftp.o +common-obj-$(CONFIG_SLIRP) += $(addprefix slirp/, $(slirp-obj-y)) + +# xen backend driver support +common-obj-$(CONFIG_XEN) += xen_backend.o xen_devconfig.o +common-obj-$(CONFIG_XEN) += xen_console.o xenfb.o xen_disk.o xen_nic.o + +###################################################################### +# libuser + +user-obj-y = +user-obj-y += envlist.o path.o +user-obj-y += tcg-runtime.o host-utils.o +user-obj-y += cutils.o cache-utils.o + +###################################################################### +# libhw + +hw-obj-y = +hw-obj-y += vl.o loader.o +hw-obj-$(CONFIG_VIRTIO) += virtio.o virtio-console.o +hw-obj-y += fw_cfg.o +hw-obj-$(CONFIG_PCI) += pci.o pci_bridge.o +hw-obj-$(CONFIG_PCI) += msix.o msi.o +hw-obj-$(CONFIG_PCI) += pci_host.o pcie_host.o +hw-obj-$(CONFIG_PCI) += ioh3420.o xio3130_upstream.o xio3130_downstream.o +hw-obj-y += watchdog.o +hw-obj-$(CONFIG_ISA_MMIO) += isa_mmio.o +hw-obj-$(CONFIG_ECC) += ecc.o +hw-obj-$(CONFIG_NAND) += nand.o +hw-obj-$(CONFIG_PFLASH_CFI01) += pflash_cfi01.o +hw-obj-$(CONFIG_PFLASH_CFI02) += pflash_cfi02.o + +hw-obj-$(CONFIG_M48T59) += m48t59.o +hw-obj-$(CONFIG_ESCC) += escc.o +hw-obj-$(CONFIG_EMPTY_SLOT) += empty_slot.o + +hw-obj-$(CONFIG_SERIAL) += serial.o +hw-obj-$(CONFIG_PARALLEL) += parallel.o +hw-obj-$(CONFIG_I8254) += i8254.o +hw-obj-$(CONFIG_PCSPK) += pcspk.o +hw-obj-$(CONFIG_PCKBD) += pckbd.o +hw-obj-$(CONFIG_USB_UHCI) += usb-uhci.o +hw-obj-$(CONFIG_USB_OHCI) += usb-ohci.o +hw-obj-$(CONFIG_FDC) += fdc.o +hw-obj-$(CONFIG_ACPI) += acpi.o acpi_piix4.o +hw-obj-$(CONFIG_APM) += pm_smbus.o apm.o +hw-obj-$(CONFIG_DMA) += dma.o + +# PPC devices +hw-obj-$(CONFIG_OPENPIC) += openpic.o +hw-obj-$(CONFIG_PREP_PCI) += prep_pci.o +# Mac shared devices +hw-obj-$(CONFIG_MACIO) += macio.o +hw-obj-$(CONFIG_CUDA) += cuda.o +hw-obj-$(CONFIG_ADB) += adb.o +hw-obj-$(CONFIG_MAC_NVRAM) += mac_nvram.o +hw-obj-$(CONFIG_MAC_DBDMA) += mac_dbdma.o +# OldWorld PowerMac +hw-obj-$(CONFIG_HEATHROW_PIC) += heathrow_pic.o +hw-obj-$(CONFIG_GRACKLE_PCI) += grackle_pci.o +# NewWorld PowerMac +hw-obj-$(CONFIG_UNIN_PCI) += unin_pci.o +hw-obj-$(CONFIG_DEC_PCI) += dec_pci.o +# PowerPC E500 boards +hw-obj-$(CONFIG_PPCE500_PCI) += ppce500_pci.o + +# MIPS devices +hw-obj-$(CONFIG_PIIX4) += piix4.o + +# PCI watchdog devices +hw-obj-$(CONFIG_PCI) += wdt_i6300esb.o + +hw-obj-$(CONFIG_PCI) += pcie.o pcie_aer.o pcie_port.o + +# PCI network cards +hw-obj-$(CONFIG_NE2000_PCI) += ne2000.o +hw-obj-$(CONFIG_EEPRO100_PCI) += eepro100.o +hw-obj-$(CONFIG_PCNET_PCI) += pcnet-pci.o +hw-obj-$(CONFIG_PCNET_COMMON) += pcnet.o +hw-obj-$(CONFIG_E1000_PCI) += e1000.o +hw-obj-$(CONFIG_RTL8139_PCI) += rtl8139.o + +hw-obj-$(CONFIG_SMC91C111) += smc91c111.o +hw-obj-$(CONFIG_LAN9118) += lan9118.o +hw-obj-$(CONFIG_NE2000_ISA) += ne2000-isa.o + +# IDE +hw-obj-$(CONFIG_IDE_CORE) += ide/core.o +hw-obj-$(CONFIG_IDE_QDEV) += ide/qdev.o +hw-obj-$(CONFIG_IDE_PCI) += ide/pci.o +hw-obj-$(CONFIG_IDE_ISA) += ide/isa.o +hw-obj-$(CONFIG_IDE_PIIX) += ide/piix.o +hw-obj-$(CONFIG_IDE_CMD646) += ide/cmd646.o +hw-obj-$(CONFIG_IDE_MACIO) += ide/macio.o +hw-obj-$(CONFIG_IDE_VIA) += ide/via.o +hw-obj-$(CONFIG_AHCI) += ide/ahci.o +hw-obj-$(CONFIG_AHCI) += ide/ich.o + +# SCSI layer +hw-obj-$(CONFIG_LSI_SCSI_PCI) += lsi53c895a.o +hw-obj-$(CONFIG_ESP) += esp.o + +hw-obj-y += dma-helpers.o sysbus.o isa-bus.o +hw-obj-y += qdev-addr.o + +# VGA +hw-obj-$(CONFIG_VGA_PCI) += vga-pci.o +hw-obj-$(CONFIG_VGA_ISA) += vga-isa.o +hw-obj-$(CONFIG_VGA_ISA_MM) += vga-isa-mm.o +hw-obj-$(CONFIG_VMWARE_VGA) += vmware_vga.o + +hw-obj-$(CONFIG_RC4030) += rc4030.o +hw-obj-$(CONFIG_DP8393X) += dp8393x.o +hw-obj-$(CONFIG_DS1225Y) += ds1225y.o +hw-obj-$(CONFIG_MIPSNET) += mipsnet.o + +# Sound +sound-obj-y = +sound-obj-$(CONFIG_SB16) += sb16.o +sound-obj-$(CONFIG_ES1370) += es1370.o +sound-obj-$(CONFIG_AC97) += ac97.o +sound-obj-$(CONFIG_ADLIB) += fmopl.o adlib.o +sound-obj-$(CONFIG_GUS) += gus.o gusemu_hal.o gusemu_mixer.o +sound-obj-$(CONFIG_CS4231A) += cs4231a.o +sound-obj-$(CONFIG_HDA) += intel-hda.o hda-audio.o + +adlib.o fmopl.o: QEMU_CFLAGS += -DBUILD_Y8950=0 +hw-obj-$(CONFIG_SOUND) += $(sound-obj-y) + +hw-obj-$(CONFIG_REALLY_VIRTFS) += virtio-9p-debug.o +hw-obj-$(CONFIG_VIRTFS) += virtio-9p-local.o virtio-9p-xattr.o +hw-obj-$(CONFIG_VIRTFS) += virtio-9p-xattr-user.o virtio-9p-posix-acl.o + +###################################################################### +# libdis +# NOTE: the disassembler code is only needed for debugging + +libdis-y = +libdis-$(CONFIG_ALPHA_DIS) += alpha-dis.o +libdis-$(CONFIG_ARM_DIS) += arm-dis.o +libdis-$(CONFIG_CRIS_DIS) += cris-dis.o +libdis-$(CONFIG_HPPA_DIS) += hppa-dis.o +libdis-$(CONFIG_I386_DIS) += i386-dis.o +libdis-$(CONFIG_IA64_DIS) += ia64-dis.o +libdis-$(CONFIG_M68K_DIS) += m68k-dis.o +libdis-$(CONFIG_MICROBLAZE_DIS) += microblaze-dis.o +libdis-$(CONFIG_MIPS_DIS) += mips-dis.o +libdis-$(CONFIG_PPC_DIS) += ppc-dis.o +libdis-$(CONFIG_S390_DIS) += s390-dis.o +libdis-$(CONFIG_SH4_DIS) += sh4-dis.o +libdis-$(CONFIG_SPARC_DIS) += sparc-dis.o + +###################################################################### +# trace + +ifeq ($(TRACE_BACKEND),dtrace) +trace-obj-y = trace-dtrace.o +else +trace-obj-y = trace.o +ifeq ($(TRACE_BACKEND),simple) +trace-obj-y += simpletrace.o +user-obj-y += qemu-timer-common.o +endif +endif + +vl.o: QEMU_CFLAGS+=$(GPROF_CFLAGS) + +vl.o: QEMU_CFLAGS+=$(SDL_CFLAGS) + diff --git a/Makefile.target b/Makefile.target new file mode 100644 index 0000000..b0ba95f --- /dev/null +++ b/Makefile.target @@ -0,0 +1,380 @@ +# -*- Mode: makefile -*- + +GENERATED_HEADERS = config-target.h +CONFIG_NO_PCI = $(if $(subst n,,$(CONFIG_PCI)),n,y) +CONFIG_NO_KVM = $(if $(subst n,,$(CONFIG_KVM)),n,y) + +include ../config-host.mak +include config-devices.mak +include config-target.mak +include $(SRC_PATH)/rules.mak +ifneq ($(HWDIR),) +include $(HWDIR)/config.mak +endif + +TARGET_PATH=$(SRC_PATH)/target-$(TARGET_BASE_ARCH) +$(call set-vpath, $(SRC_PATH):$(TARGET_PATH):$(SRC_PATH)/hw) +QEMU_CFLAGS+= -I.. -I$(TARGET_PATH) -DNEED_CPU_H + +include $(SRC_PATH)/Makefile.objs + +ifdef CONFIG_USER_ONLY +# user emulator name +QEMU_PROG=qemu-$(TARGET_ARCH2) +else +# system emulator name +ifeq ($(TARGET_ARCH), i386) +QEMU_PROG=qemu$(EXESUF) +else +QEMU_PROG=qemu-system-$(TARGET_ARCH2)$(EXESUF) +endif +endif + +PROGS=$(QEMU_PROG) +STPFILES= + +ifndef CONFIG_HAIKU +LIBS+=-lm +endif + +kvm.o kvm-all.o vhost.o vhost_net.o: QEMU_CFLAGS+=$(KVM_CFLAGS) + +config-target.h: config-target.h-timestamp +config-target.h-timestamp: config-target.mak + +ifdef CONFIG_SYSTEMTAP_TRACE +stap: $(QEMU_PROG).stp + +ifdef CONFIG_USER_ONLY +TARGET_TYPE=user +else +TARGET_TYPE=system +endif + +$(QEMU_PROG).stp: + $(call quiet-command,sh $(SRC_PATH)/scripts/tracetool \ + --$(TRACE_BACKEND) \ + --binary $(bindir)/$(QEMU_PROG) \ + --target-arch $(TARGET_ARCH) \ + --target-type $(TARGET_TYPE) \ + --stap < $(SRC_PATH)/trace-events > $(QEMU_PROG).stp," GEN $(QEMU_PROG).stp") +else +stap: +endif + +all: $(PROGS) stap + +# Dummy command so that make thinks it has done something + @true + +######################################################### +# cpu emulator library +libobj-y = exec.o translate-all.o cpu-exec.o translate.o +libobj-y += tcg/tcg.o +libobj-$(CONFIG_SOFTFLOAT) += fpu/softfloat.o +libobj-$(CONFIG_NOSOFTFLOAT) += fpu/softfloat-native.o +libobj-y += op_helper.o helper.o +ifeq ($(TARGET_BASE_ARCH), i386) +libobj-y += cpuid.o +endif +libobj-$(CONFIG_NEED_MMU) += mmu.o +libobj-$(TARGET_ARM) += neon_helper.o iwmmxt_helper.o + +libobj-y += disas.o + +$(libobj-y): $(GENERATED_HEADERS) + +# libqemu + +translate.o: translate.c cpu.h + +translate-all.o: translate-all.c cpu.h + +tcg/tcg.o: cpu.h + +# HELPER_CFLAGS is used for all the code compiled with static register +# variables +op_helper.o cpu-exec.o: QEMU_CFLAGS += $(HELPER_CFLAGS) + +# Note: this is a workaround. The real fix is to avoid compiling +# cpu_signal_handler() in cpu-exec.c. +signal.o: QEMU_CFLAGS += $(HELPER_CFLAGS) + +######################################################### +# Linux user emulator target + +ifdef CONFIG_LINUX_USER + +$(call set-vpath, $(SRC_PATH)/linux-user:$(SRC_PATH)/linux-user/$(TARGET_ABI_DIR)) + +QEMU_CFLAGS+=-I$(SRC_PATH)/linux-user -I$(SRC_PATH)/linux-user/$(TARGET_ABI_DIR) +obj-y = main.o syscall.o strace.o mmap.o signal.o thunk.o \ + elfload.o linuxload.o uaccess.o gdbstub.o cpu-uname.o \ + qemu-malloc.o $(oslib-obj-y) + +obj-$(TARGET_HAS_BFLT) += flatload.o + +obj-$(TARGET_I386) += vm86.o + +obj-i386-y += ioport-user.o + +nwfpe-obj-y = fpa11.o fpa11_cpdo.o fpa11_cpdt.o fpa11_cprt.o fpopcode.o +nwfpe-obj-y += single_cpdo.o double_cpdo.o extended_cpdo.o +obj-arm-y += $(addprefix nwfpe/, $(nwfpe-obj-y)) +obj-arm-y += arm-semi.o + +obj-m68k-y += m68k-sim.o m68k-semi.o + +$(obj-y) $(obj-$(TARGET_BASE_ARCH)-y): $(GENERATED_HEADERS) + +obj-y += $(addprefix ../libuser/, $(user-obj-y)) +obj-y += $(addprefix ../libdis-user/, $(libdis-y)) +obj-y += $(libobj-y) + +endif #CONFIG_LINUX_USER + +######################################################### +# Darwin user emulator target + +ifdef CONFIG_DARWIN_USER + +$(call set-vpath, $(SRC_PATH)/darwin-user) + +QEMU_CFLAGS+=-I$(SRC_PATH)/darwin-user -I$(SRC_PATH)/darwin-user/$(TARGET_ARCH) + +# Leave some space for the regular program loading zone +LDFLAGS+=-Wl,-segaddr,__STD_PROG_ZONE,0x1000 -image_base 0x0e000000 + +LIBS+=-lmx + +obj-y = main.o commpage.o machload.o mmap.o signal.o syscall.o thunk.o \ + gdbstub.o + +obj-i386-y += ioport-user.o + +$(obj-y) $(obj-$(TARGET_BASE_ARCH)-y): $(GENERATED_HEADERS) + +obj-y += $(addprefix ../libuser/, $(user-obj-y)) +obj-y += $(addprefix ../libdis-user/, $(libdis-y)) +obj-y += $(libobj-y) + +endif #CONFIG_DARWIN_USER + +######################################################### +# BSD user emulator target + +ifdef CONFIG_BSD_USER + +$(call set-vpath, $(SRC_PATH)/bsd-user) + +QEMU_CFLAGS+=-I$(SRC_PATH)/bsd-user -I$(SRC_PATH)/bsd-user/$(TARGET_ARCH) + +obj-y = main.o bsdload.o elfload.o mmap.o signal.o strace.o syscall.o \ + gdbstub.o uaccess.o + +obj-i386-y += ioport-user.o + +$(obj-y) $(obj-$(TARGET_BASE_ARCH)-y): $(GENERATED_HEADERS) + +obj-y += $(addprefix ../libuser/, $(user-obj-y)) +obj-y += $(addprefix ../libdis-user/, $(libdis-y)) +obj-y += $(libobj-y) + +endif #CONFIG_BSD_USER + +######################################################### +# System emulator target +ifdef CONFIG_SOFTMMU + +obj-y = arch_init.o cpus.o monitor.o machine.o gdbstub.o balloon.o +# virtio has to be here due to weird dependency between PCI and virtio-net. +# need to fix this properly +obj-$(CONFIG_NO_PCI) += pci-stub.o +obj-$(CONFIG_VIRTIO) += virtio-blk.o virtio-balloon.o virtio-net.o virtio-serial-bus.o +obj-$(CONFIG_VIRTIO_PCI) += virtio-pci.o +obj-y += vhost_net.o +obj-$(CONFIG_VHOST_NET) += vhost.o +obj-$(CONFIG_REALLY_VIRTFS) += virtio-9p.o +obj-y += rwhandler.o +obj-$(CONFIG_KVM) += kvm.o kvm-all.o +obj-$(CONFIG_NO_KVM) += kvm-stub.o +LIBS+=-lz + +QEMU_CFLAGS += $(VNC_TLS_CFLAGS) +QEMU_CFLAGS += $(VNC_SASL_CFLAGS) +QEMU_CFLAGS += $(VNC_JPEG_CFLAGS) +QEMU_CFLAGS += $(VNC_PNG_CFLAGS) + +# xen backend driver support +obj-$(CONFIG_XEN) += xen_machine_pv.o xen_domainbuild.o + +# Inter-VM PCI shared memory +obj-$(CONFIG_KVM) += ivshmem.o + +# Hardware support +obj-i386-y += vga.o +obj-i386-y += mc146818rtc.o i8259.o pc.o +obj-i386-y += cirrus_vga.o apic.o ioapic.o piix_pci.o +obj-i386-y += vmmouse.o vmport.o hpet.o applesmc.o +obj-i386-y += device-hotplug.o pci-hotplug.o smbios.o wdt_ib700.o +obj-i386-y += debugcon.o multiboot.o +obj-i386-y += pc_piix.o +obj-i386-$(CONFIG_SPICE) += qxl.o qxl-logger.o qxl-render.o + +# shared objects +obj-ppc-y = ppc.o +obj-ppc-y += vga.o +# PREP target +obj-ppc-y += i8259.o mc146818rtc.o +obj-ppc-y += ppc_prep.o +# OldWorld PowerMac +obj-ppc-y += ppc_oldworld.o +# NewWorld PowerMac +obj-ppc-y += ppc_newworld.o +# PowerPC 4xx boards +obj-ppc-y += ppc4xx_devs.o ppc4xx_pci.o ppc405_uc.o ppc405_boards.o +obj-ppc-y += ppc440.o ppc440_bamboo.o +# PowerPC E500 boards +obj-ppc-y += ppce500_mpc8544ds.o +# PowerPC 440 Xilinx ML507 reference board. +obj-ppc-y += virtex_ml507.o +obj-ppc-$(CONFIG_KVM) += kvm_ppc.o +obj-ppc-$(CONFIG_FDT) += device_tree.o + +# Xilinx PPC peripherals +obj-ppc-y += xilinx_intc.o +obj-ppc-y += xilinx_timer.o +obj-ppc-y += xilinx_uartlite.o +obj-ppc-y += xilinx_ethlite.o + +obj-mips-y = mips_r4k.o mips_jazz.o mips_malta.o mips_mipssim.o +obj-mips-y += mips_addr.o mips_timer.o mips_int.o +obj-mips-y += vga.o i8259.o +obj-mips-y += g364fb.o jazz_led.o +obj-mips-y += gt64xxx.o mc146818rtc.o +obj-mips-y += cirrus_vga.o +obj-mips-$(CONFIG_FULONG) += bonito.o vt82c686.o mips_fulong2e.o + +obj-microblaze-y = petalogix_s3adsp1800_mmu.o + +obj-microblaze-y += microblaze_pic_cpu.o +obj-microblaze-y += xilinx_intc.o +obj-microblaze-y += xilinx_timer.o +obj-microblaze-y += xilinx_uartlite.o +obj-microblaze-y += xilinx_ethlite.o + +obj-microblaze-$(CONFIG_FDT) += device_tree.o + +# Boards +obj-cris-y = cris_pic_cpu.o +obj-cris-y += cris-boot.o +obj-cris-y += etraxfs.o axis_dev88.o +obj-cris-y += axis_dev88.o + +# IO blocks +obj-cris-y += etraxfs_dma.o +obj-cris-y += etraxfs_pic.o +obj-cris-y += etraxfs_eth.o +obj-cris-y += etraxfs_timer.o +obj-cris-y += etraxfs_ser.o + +ifeq ($(TARGET_ARCH), sparc64) +obj-sparc-y = sun4u.o apb_pci.o +obj-sparc-y += vga.o +obj-sparc-y += mc146818rtc.o +obj-sparc-y += cirrus_vga.o +else +obj-sparc-y = sun4m.o lance.o tcx.o sun4m_iommu.o slavio_intctl.o +obj-sparc-y += slavio_timer.o slavio_misc.o sparc32_dma.o +obj-sparc-y += cs4231.o eccmemctl.o sbi.o sun4c_intctl.o leon3.o + +# GRLIB +obj-sparc-y += grlib_gptimer.o grlib_irqmp.o grlib_apbuart.o +endif + +obj-arm-y = integratorcp.o versatilepb.o arm_pic.o arm_timer.o +obj-arm-y += arm_boot.o pl011.o pl031.o pl050.o pl080.o pl110.o pl181.o pl190.o +obj-arm-y += versatile_pci.o +obj-arm-y += realview_gic.o realview.o arm_sysctl.o arm11mpcore.o a9mpcore.o +obj-arm-y += armv7m.o armv7m_nvic.o stellaris.o pl022.o stellaris_enet.o +obj-arm-y += pl061.o +obj-arm-y += arm-semi.o +obj-arm-y += pxa2xx.o pxa2xx_pic.o pxa2xx_gpio.o pxa2xx_timer.o pxa2xx_dma.o +obj-arm-y += pxa2xx_lcd.o pxa2xx_mmci.o pxa2xx_pcmcia.o pxa2xx_keypad.o +obj-arm-y += gumstix.o +obj-arm-y += zaurus.o ide/microdrive.o spitz.o tosa.o tc6393xb.o +obj-arm-y += omap1.o omap_lcdc.o omap_dma.o omap_clk.o omap_mmc.o omap_i2c.o \ + omap_gpio.o omap_intc.o omap_uart.o +obj-arm-y += omap2.o omap_dss.o soc_dma.o omap_gptimer.o omap_synctimer.o \ + omap_gpmc.o omap_sdrc.o omap_spi.o omap_tap.o omap_l4.o +obj-arm-y += omap_sx1.o palm.o tsc210x.o +obj-arm-y += nseries.o blizzard.o onenand.o vga.o cbus.o tusb6010.o usb-musb.o +obj-arm-y += mst_fpga.o mainstone.o +obj-arm-y += musicpal.o bitbang_i2c.o marvell_88w8618_audio.o +obj-arm-y += framebuffer.o +obj-arm-y += syborg.o syborg_fb.o syborg_interrupt.o syborg_keyboard.o +obj-arm-y += syborg_serial.o syborg_timer.o syborg_pointer.o syborg_rtc.o +obj-arm-y += syborg_virtio.o + +obj-sh4-y = shix.o r2d.o sh7750.o sh7750_regnames.o tc58128.o +obj-sh4-y += sh_timer.o sh_serial.o sh_intc.o sh_pci.o sm501.o +obj-sh4-y += ide/mmio.o + +obj-m68k-y = an5206.o mcf5206.o mcf_uart.o mcf_intc.o mcf5208.o mcf_fec.o +obj-m68k-y += m68k-semi.o dummy_m68k.o + +obj-s390x-y = s390-virtio-bus.o s390-virtio.o + +obj-alpha-y = alpha_palcode.o + +main.o: QEMU_CFLAGS+=$(GPROF_CFLAGS) + +monitor.o: hmp-commands.h qmp-commands.h + +$(obj-y) $(obj-$(TARGET_BASE_ARCH)-y): $(GENERATED_HEADERS) + +obj-y += $(addprefix ../, $(common-obj-y)) +obj-y += $(addprefix ../libdis/, $(libdis-y)) +obj-y += $(libobj-y) +obj-y += $(addprefix $(HWDIR)/, $(hw-obj-y)) + +endif # CONFIG_SOFTMMU + +obj-y += $(addprefix ../, $(trace-obj-y)) +obj-$(CONFIG_GDBSTUB_XML) += gdbstub-xml.o + +$(QEMU_PROG): $(obj-y) $(obj-$(TARGET_BASE_ARCH)-y) + $(call LINK,$(obj-y) $(obj-$(TARGET_BASE_ARCH)-y)) + + +gdbstub-xml.c: $(TARGET_XML_FILES) $(SRC_PATH)/scripts/feature_to_c.sh + $(call quiet-command,rm -f $@ && $(SHELL) $(SRC_PATH)/scripts/feature_to_c.sh $@ $(TARGET_XML_FILES)," GEN $(TARGET_DIR)$@") + +hmp-commands.h: $(SRC_PATH)/hmp-commands.hx + $(call quiet-command,sh $(SRC_PATH)/scripts/hxtool -h < $< > $@," GEN $(TARGET_DIR)$@") + +qmp-commands.h: $(SRC_PATH)/qmp-commands.hx + $(call quiet-command,sh $(SRC_PATH)/scripts/hxtool -h < $< > $@," GEN $(TARGET_DIR)$@") + +clean: + rm -f *.o *.a *~ $(PROGS) nwfpe/*.o fpu/*.o + rm -f *.d */*.d tcg/*.o ide/*.o + rm -f hmp-commands.h qmp-commands.h gdbstub-xml.c +ifdef CONFIG_SYSTEMTAP_TRACE + rm -f *.stp +endif + +install: all +ifneq ($(PROGS),) + $(INSTALL) -m 755 $(PROGS) "$(DESTDIR)$(bindir)" +ifneq ($(STRIP),) + $(STRIP) $(patsubst %,"$(DESTDIR)$(bindir)/%",$(PROGS)) +endif +endif +ifdef CONFIG_SYSTEMTAP_TRACE + $(INSTALL_DIR) "$(DESTDIR)$(datadir)/../systemtap/tapset" + $(INSTALL_DATA) $(QEMU_PROG).stp "$(DESTDIR)$(datadir)/../systemtap/tapset" +endif + +# Include automatically generated dependency files +-include $(wildcard *.d */*.d) diff --git a/Makefile.user b/Makefile.user new file mode 100644 index 0000000..024b773 --- /dev/null +++ b/Makefile.user @@ -0,0 +1,23 @@ +# Makefile for qemu target independent user files. + +include ../config-host.mak +include $(SRC_PATH)/rules.mak +-include config.mak + +.PHONY: all + +$(call set-vpath, $(SRC_PATH)) + +QEMU_CFLAGS+=-I.. + +include $(SRC_PATH)/Makefile.objs + +all: $(user-obj-y) +# Dummy command so that make thinks it has done something + @true + +clean: + rm -f *.o *.d *.a *~ + +# Include automatically generated dependency files +-include $(wildcard *.d */*.d) diff --git a/QMP/README b/QMP/README new file mode 100644 index 0000000..c95a08c --- /dev/null +++ b/QMP/README @@ -0,0 +1,88 @@ + QEMU Monitor Protocol + ===================== + +Introduction +------------- + +The QEMU Monitor Protocol (QMP) allows applications to communicate with +QEMU's Monitor. + +QMP is JSON[1] based and currently has the following features: + +- Lightweight, text-based, easy to parse data format +- Asynchronous messages support (ie. events) +- Capabilities Negotiation + +For detailed information on QMP's usage, please, refer to the following files: + +o qmp-spec.txt QEMU Monitor Protocol current specification +o qmp-commands.txt QMP supported commands (auto-generated at build-time) +o qmp-events.txt List of available asynchronous events + +There is also a simple Python script called 'qmp-shell' available. + +IMPORTANT: It's strongly recommended to read the 'Stability Considerations' +section in the qmp-commands.txt file before making any serious use of QMP. + + +[1] http://www.json.org + +Usage +----- + +To enable QMP, you need a QEMU monitor instance in "control mode". There are +two ways of doing this. + +The simplest one is using the '-qmp' command-line option. The following +example makes QMP available on localhost port 4444: + + $ qemu [...] -qmp tcp:localhost:4444,server + +However, in order to have more complex combinations, like multiple monitors, +the '-mon' command-line option should be used along with the '-chardev' one. +For instance, the following example creates one user monitor on stdio and one +QMP monitor on localhost port 4444. + + $ qemu [...] -chardev stdio,id=mon0 -mon chardev=mon0,mode=readline \ + -chardev socket,id=mon1,host=localhost,port=4444,server \ + -mon chardev=mon1,mode=control + +Please, refer to QEMU's manpage for more information. + +Simple Testing +-------------- + +To manually test QMP one can connect with telnet and issue commands by hand: + +$ telnet localhost 4444 +Trying 127.0.0.1... +Connected to localhost. +Escape character is '^]'. +{"QMP": {"version": {"qemu": {"micro": 50, "minor": 13, "major": 0}, "package": ""}, "capabilities": []}} +{ "execute": "qmp_capabilities" } +{"return": {}} +{ "execute": "query-version" } +{"return": {"qemu": {"micro": 50, "minor": 13, "major": 0}, "package": ""}} + +Development Process +------------------- + +When changing QMP's interface (by adding new commands, events or modifying +existing ones) it's mandatory to update the relevant documentation, which is +one (or more) of the files listed in the 'Introduction' section*. + +Also, it's strongly recommended to send the documentation patch first, before +doing any code change. This is so because: + + 1. Avoids the code dictating the interface + + 2. Review can improve your interface. Letting that happen before + you implement it can save you work. + +* The qmp-commands.txt file is generated from the qmp-commands.hx one, which + is the file that should be edited. + +Homepage +-------- + +http://wiki.qemu.org/QMP diff --git a/QMP/qmp-events.txt b/QMP/qmp-events.txt new file mode 100644 index 0000000..0ce5d4e --- /dev/null +++ b/QMP/qmp-events.txt @@ -0,0 +1,266 @@ + QEMU Monitor Protocol Events + ============================ + +BLOCK_IO_ERROR +-------------- + +Emitted when a disk I/O error occurs. + +Data: + +- "device": device name (json-string) +- "operation": I/O operation (json-string, "read" or "write") +- "action": action that has been taken, it's one of the following (json-string): + "ignore": error has been ignored + "report": error has been reported to the device + "stop": error caused VM to be stopped + +Example: + +{ "event": "BLOCK_IO_ERROR", + "data": { "device": "ide0-hd1", + "operation": "write", + "action": "stop" }, + "timestamp": { "seconds": 1265044230, "microseconds": 450486 } } + +Note: If action is "stop", a STOP event will eventually follow the +BLOCK_IO_ERROR event. + +RESET +----- + +Emitted when the Virtual Machine is reseted. + +Data: None. + +Example: + +{ "event": "RESET", + "timestamp": { "seconds": 1267041653, "microseconds": 9518 } } + +RESUME +------ + +Emitted when the Virtual Machine resumes execution. + +Data: None. + +Example: + +{ "event": "RESUME", + "timestamp": { "seconds": 1271770767, "microseconds": 582542 } } + +RTC_CHANGE +---------- + +Emitted when the guest changes the RTC time. + +Data: + +- "offset": delta against the host UTC in seconds (json-number) + +Example: + +{ "event": "RTC_CHANGE", + "data": { "offset": 78 }, + "timestamp": { "seconds": 1267020223, "microseconds": 435656 } } + +SHUTDOWN +-------- + +Emitted when the Virtual Machine is powered down. + +Data: None. + +Example: + +{ "event": "SHUTDOWN", + "timestamp": { "seconds": 1267040730, "microseconds": 682951 } } + +Note: If the command-line option "-no-shutdown" has been specified, a STOP +event will eventually follow the SHUTDOWN event. + +STOP +---- + +Emitted when the Virtual Machine is stopped. + +Data: None. + +Example: + +{ "event": "STOP", + "timestamp": { "seconds": 1267041730, "microseconds": 281295 } } + +VNC_CONNECTED +------------- + +Emitted when a VNC client establishes a connection. + +Data: + +- "server": Server information (json-object) + - "host": IP address (json-string) + - "service": port number (json-string) + - "family": address family (json-string, "ipv4" or "ipv6") + - "auth": authentication method (json-string, optional) +- "client": Client information (json-object) + - "host": IP address (json-string) + - "service": port number (json-string) + - "family": address family (json-string, "ipv4" or "ipv6") + +Example: + +{ "event": "VNC_CONNECTED", + "data": { + "server": { "auth": "sasl", "family": "ipv4", + "service": "5901", "host": "0.0.0.0" }, + "client": { "family": "ipv4", "service": "58425", + "host": "127.0.0.1" } }, + "timestamp": { "seconds": 1262976601, "microseconds": 975795 } } + + +Note: This event is emitted before any authentication takes place, thus +the authentication ID is not provided. + +VNC_DISCONNECTED +---------------- + +Emitted when the conection is closed. + +Data: + +- "server": Server information (json-object) + - "host": IP address (json-string) + - "service": port number (json-string) + - "family": address family (json-string, "ipv4" or "ipv6") + - "auth": authentication method (json-string, optional) +- "client": Client information (json-object) + - "host": IP address (json-string) + - "service": port number (json-string) + - "family": address family (json-string, "ipv4" or "ipv6") + - "x509_dname": TLS dname (json-string, optional) + - "sasl_username": SASL username (json-string, optional) + +Example: + +{ "event": "VNC_DISCONNECTED", + "data": { + "server": { "auth": "sasl", "family": "ipv4", + "service": "5901", "host": "0.0.0.0" }, + "client": { "family": "ipv4", "service": "58425", + "host": "127.0.0.1", "sasl_username": "luiz" } }, + "timestamp": { "seconds": 1262976601, "microseconds": 975795 } } + +VNC_INITIALIZED +--------------- + +Emitted after authentication takes place (if any) and the VNC session is +made active. + +Data: + +- "server": Server information (json-object) + - "host": IP address (json-string) + - "service": port number (json-string) + - "family": address family (json-string, "ipv4" or "ipv6") + - "auth": authentication method (json-string, optional) +- "client": Client information (json-object) + - "host": IP address (json-string) + - "service": port number (json-string) + - "family": address family (json-string, "ipv4" or "ipv6") + - "x509_dname": TLS dname (json-string, optional) + - "sasl_username": SASL username (json-string, optional) + +Example: + +{ "event": "VNC_INITIALIZED", + "data": { + "server": { "auth": "sasl", "family": "ipv4", + "service": "5901", "host": "0.0.0.0"}, + "client": { "family": "ipv4", "service": "46089", + "host": "127.0.0.1", "sasl_username": "luiz" } }, + "timestamp": { "seconds": 1263475302, "microseconds": 150772 } } + +SPICE_CONNECTED, SPICE_DISCONNECTED +----------------------------------- + +Emitted when a SPICE client connects or disconnects. + +Data: + +- "server": Server information (json-object) + - "host": IP address (json-string) + - "port": port number (json-string) + - "family": address family (json-string, "ipv4" or "ipv6") +- "client": Client information (json-object) + - "host": IP address (json-string) + - "port": port number (json-string) + - "family": address family (json-string, "ipv4" or "ipv6") + +Example: + +{ "timestamp": {"seconds": 1290688046, "microseconds": 388707}, + "event": "SPICE_CONNECTED", + "data": { + "server": { "port": "5920", "family": "ipv4", "host": "127.0.0.1"}, + "client": {"port": "52873", "family": "ipv4", "host": "127.0.0.1"} +}} + + +SPICE_INITIALIZED +----------------- + +Emitted after initial handshake and authentication takes place (if any) +and the SPICE channel is up'n'running + +Data: + +- "server": Server information (json-object) + - "host": IP address (json-string) + - "port": port number (json-string) + - "family": address family (json-string, "ipv4" or "ipv6") + - "auth": authentication method (json-string, optional) +- "client": Client information (json-object) + - "host": IP address (json-string) + - "port": port number (json-string) + - "family": address family (json-string, "ipv4" or "ipv6") + - "connection-id": spice connection id. All channels with the same id + belong to the same spice session (json-int) + - "channel-type": channel type. "1" is the main control channel, filter for + this one if you want track spice sessions only (json-int) + - "channel-id": channel id. Usually "0", might be different needed when + multiple channels of the same type exist, such as multiple + display channels in a multihead setup (json-int) + - "tls": whevener the channel is encrypted (json-bool) + +Example: + +{ "timestamp": {"seconds": 1290688046, "microseconds": 417172}, + "event": "SPICE_INITIALIZED", + "data": {"server": {"auth": "spice", "port": "5921", + "family": "ipv4", "host": "127.0.0.1"}, + "client": {"port": "49004", "family": "ipv4", "channel-type": 3, + "connection-id": 1804289383, "host": "127.0.0.1", + "channel-id": 0, "tls": true} +}} + + +WATCHDOG +-------- + +Emitted when the watchdog device's timer is expired. + +Data: + +- "action": Action that has been taken, it's one of the following (json-string): + "reset", "shutdown", "poweroff", "pause", "debug", or "none" + +Example: + +{ "event": "WATCHDOG", + "data": { "action": "reset" }, + "timestamp": { "seconds": 1267061043, "microseconds": 959568 } } + +Note: If action is "reset", "shutdown", or "pause" the WATCHDOG event is +followed respectively by the RESET, SHUTDOWN, or STOP events. diff --git a/QMP/qmp-shell b/QMP/qmp-shell new file mode 100644 index 0000000..42dabc8 --- /dev/null +++ b/QMP/qmp-shell @@ -0,0 +1,259 @@ +#!/usr/bin/python +# +# Low-level QEMU shell on top of QMP. +# +# Copyright (C) 2009, 2010 Red Hat Inc. +# +# Authors: +# Luiz Capitulino +# +# This work is licensed under the terms of the GNU GPL, version 2. See +# the COPYING file in the top-level directory. +# +# Usage: +# +# Start QEMU with: +# +# # qemu [...] -qmp unix:./qmp-sock,server +# +# Run the shell: +# +# $ qmp-shell ./qmp-sock +# +# Commands have the following format: +# +# < command-name > [ arg-name1=arg1 ] ... [ arg-nameN=argN ] +# +# For example: +# +# (QEMU) device_add driver=e1000 id=net1 +# {u'return': {}} +# (QEMU) + +import qmp +import readline +import sys + +class QMPCompleter(list): + def complete(self, text, state): + for cmd in self: + if cmd.startswith(text): + if not state: + return cmd + else: + state -= 1 + +class QMPShellError(Exception): + pass + +class QMPShellBadPort(QMPShellError): + pass + +# TODO: QMPShell's interface is a bit ugly (eg. _fill_completion() and +# _execute_cmd()). Let's design a better one. +class QMPShell(qmp.QEMUMonitorProtocol): + def __init__(self, address): + qmp.QEMUMonitorProtocol.__init__(self, self.__get_address(address)) + self._greeting = None + self._completer = None + + def __get_address(self, arg): + """ + Figure out if the argument is in the port:host form, if it's not it's + probably a file path. + """ + addr = arg.split(':') + if len(addr) == 2: + try: + port = int(addr[1]) + except ValueError: + raise QMPShellBadPort + return ( addr[0], port ) + # socket path + return arg + + def _fill_completion(self): + for cmd in self.cmd('query-commands')['return']: + self._completer.append(cmd['name']) + + def __completer_setup(self): + self._completer = QMPCompleter() + self._fill_completion() + readline.set_completer(self._completer.complete) + readline.parse_and_bind("tab: complete") + # XXX: default delimiters conflict with some command names (eg. query-), + # clearing everything as it doesn't seem to matter + readline.set_completer_delims('') + + def __build_cmd(self, cmdline): + """ + Build a QMP input object from a user provided command-line in the + following format: + + < command-name > [ arg-name1=arg1 ] ... [ arg-nameN=argN ] + """ + cmdargs = cmdline.split() + qmpcmd = { 'execute': cmdargs[0], 'arguments': {} } + for arg in cmdargs[1:]: + opt = arg.split('=') + try: + value = int(opt[1]) + except ValueError: + value = opt[1] + qmpcmd['arguments'][opt[0]] = value + return qmpcmd + + def _execute_cmd(self, cmdline): + try: + qmpcmd = self.__build_cmd(cmdline) + except: + print 'command format: ', + print '[arg-name1=arg1] ... [arg-nameN=argN]' + return True + resp = self.cmd_obj(qmpcmd) + if resp is None: + print 'Disconnected' + return False + print resp + return True + + def connect(self): + self._greeting = qmp.QEMUMonitorProtocol.connect(self) + self.__completer_setup() + + def show_banner(self, msg='Welcome to the QMP low-level shell!'): + print msg + version = self._greeting['QMP']['version']['qemu'] + print 'Connected to QEMU %d.%d.%d\n' % (version['major'],version['minor'],version['micro']) + + def read_exec_command(self, prompt): + """ + Read and execute a command. + + @return True if execution was ok, return False if disconnected. + """ + try: + cmdline = raw_input(prompt) + except EOFError: + print + return False + if cmdline == '': + for ev in self.get_events(): + print ev + self.clear_events() + return True + else: + return self._execute_cmd(cmdline) + +class HMPShell(QMPShell): + def __init__(self, address): + QMPShell.__init__(self, address) + self.__cpu_index = 0 + + def __cmd_completion(self): + for cmd in self.__cmd_passthrough('help')['return'].split('\r\n'): + if cmd and cmd[0] != '[' and cmd[0] != '\t': + name = cmd.split()[0] # drop help text + if name == 'info': + continue + if name.find('|') != -1: + # Command in the form 'foobar|f' or 'f|foobar', take the + # full name + opt = name.split('|') + if len(opt[0]) == 1: + name = opt[1] + else: + name = opt[0] + self._completer.append(name) + self._completer.append('help ' + name) # help completion + + def __info_completion(self): + for cmd in self.__cmd_passthrough('info')['return'].split('\r\n'): + if cmd: + self._completer.append('info ' + cmd.split()[1]) + + def __other_completion(self): + # special cases + self._completer.append('help info') + + def _fill_completion(self): + self.__cmd_completion() + self.__info_completion() + self.__other_completion() + + def __cmd_passthrough(self, cmdline, cpu_index = 0): + return self.cmd_obj({ 'execute': 'human-monitor-command', 'arguments': + { 'command-line': cmdline, + 'cpu-index': cpu_index } }) + + def _execute_cmd(self, cmdline): + if cmdline.split()[0] == "cpu": + # trap the cpu command, it requires special setting + try: + idx = int(cmdline.split()[1]) + if not 'return' in self.__cmd_passthrough('info version', idx): + print 'bad CPU index' + return True + self.__cpu_index = idx + except ValueError: + print 'cpu command takes an integer argument' + return True + resp = self.__cmd_passthrough(cmdline, self.__cpu_index) + if resp is None: + print 'Disconnected' + return False + assert 'return' in resp or 'error' in resp + if 'return' in resp: + # Success + if len(resp['return']) > 0: + print resp['return'], + else: + # Error + print '%s: %s' % (resp['error']['class'], resp['error']['desc']) + return True + + def show_banner(self): + QMPShell.show_banner(self, msg='Welcome to the HMP shell!') + +def die(msg): + sys.stderr.write('ERROR: %s\n' % msg) + sys.exit(1) + +def fail_cmdline(option=None): + if option: + sys.stderr.write('ERROR: bad command-line option \'%s\'\n' % option) + sys.stderr.write('qemu-shell [ -H ] < UNIX socket path> | < TCP address:port >\n') + sys.exit(1) + +def main(): + addr = '' + try: + if len(sys.argv) == 2: + qemu = QMPShell(sys.argv[1]) + addr = sys.argv[1] + elif len(sys.argv) == 3: + if sys.argv[1] != '-H': + fail_cmdline(sys.argv[1]) + qemu = HMPShell(sys.argv[2]) + addr = sys.argv[2] + else: + fail_cmdline() + except QMPShellBadPort: + die('bad port number in command-line') + + try: + qemu.connect() + except qmp.QMPConnectError: + die('Didn\'t get QMP greeting message') + except qmp.QMPCapabilitiesError: + die('Could not negotiate capabilities') + except qemu.error: + die('Could not connect to %s' % addr) + + qemu.show_banner() + while qemu.read_exec_command('(QEMU) '): + pass + qemu.close() + +if __name__ == '__main__': + main() diff --git a/QMP/qmp-spec.txt b/QMP/qmp-spec.txt new file mode 100644 index 0000000..9d30a8c --- /dev/null +++ b/QMP/qmp-spec.txt @@ -0,0 +1,272 @@ + QEMU Monitor Protocol Specification - Version 0.1 + +1. Introduction +=============== + +This document specifies the QEMU Monitor Protocol (QMP), a JSON-based protocol +which is available for applications to control QEMU at the machine-level. + +To enable QMP support, QEMU has to be run in "control mode". This is done by +starting QEMU with the appropriate command-line options. Please, refer to the +QEMU manual page for more information. + +2. Protocol Specification +========================= + +This section details the protocol format. For the purpose of this document +"Client" is any application which is communicating with QEMU in control mode, +and "Server" is QEMU itself. + +JSON data structures, when mentioned in this document, are always in the +following format: + + json-DATA-STRUCTURE-NAME + +Where DATA-STRUCTURE-NAME is any valid JSON data structure, as defined by +the JSON standard: + +http://www.ietf.org/rfc/rfc4627.txt + +For convenience, json-object members and json-array elements mentioned in +this document will be in a certain order. However, in real protocol usage +they can be in ANY order, thus no particular order should be assumed. + +2.1 General Definitions +----------------------- + +2.1.1 All interactions transmitted by the Server are json-objects, always + terminating with CRLF + +2.1.2 All json-objects members are mandatory when not specified otherwise + +2.2 Server Greeting +------------------- + +Right when connected the Server will issue a greeting message, which signals +that the connection has been successfully established and that the Server is +ready for capabilities negotiation (for more information refer to section +'4. Capabilities Negotiation'). + +The format is: + +{ "QMP": { "version": json-object, "capabilities": json-array } } + + Where, + +- The "version" member contains the Server's version information (the format + is the same of the 'query-version' command) +- The "capabilities" member specify the availability of features beyond the + baseline specification + +2.3 Issuing Commands +-------------------- + +The format for command execution is: + +{ "execute": json-string, "arguments": json-object, "id": json-value } + + Where, + +- The "execute" member identifies the command to be executed by the Server +- The "arguments" member is used to pass any arguments required for the + execution of the command, it is optional when no arguments are required +- The "id" member is a transaction identification associated with the + command execution, it is optional and will be part of the response if + provided + +2.4 Commands Responses +---------------------- + +There are two possible responses which the Server will issue as the result +of a command execution: success or error. + +2.4.1 success +------------- + +The success response is issued when the command execution has finished +without errors. + +The format is: + +{ "return": json-object, "id": json-value } + + Where, + +- The "return" member contains the command returned data, which is defined + in a per-command basis or an empty json-object if the command does not + return data +- The "id" member contains the transaction identification associated + with the command execution (if issued by the Client) + +2.4.2 error +----------- + +The error response is issued when the command execution could not be +completed because of an error condition. + +The format is: + +{ "error": { "class": json-string, "data": json-object, "desc": json-string }, + "id": json-value } + + Where, + +- The "class" member contains the error class name (eg. "ServiceUnavailable") +- The "data" member contains specific error data and is defined in a + per-command basis, it will be an empty json-object if the error has no data +- The "desc" member is a human-readable error message. Clients should + not attempt to parse this message. +- The "id" member contains the transaction identification associated with + the command execution (if issued by the Client) + +NOTE: Some errors can occur before the Server is able to read the "id" member, +in these cases the "id" member will not be part of the error response, even +if provided by the client. + +2.5 Asynchronous events +----------------------- + +As a result of state changes, the Server may send messages unilaterally +to the Client at any time. They are called 'asynchronous events'. + +The format is: + +{ "event": json-string, "data": json-object, + "timestamp": { "seconds": json-number, "microseconds": json-number } } + + Where, + +- The "event" member contains the event's name +- The "data" member contains event specific data, which is defined in a + per-event basis, it is optional +- The "timestamp" member contains the exact time of when the event occurred + in the Server. It is a fixed json-object with time in seconds and + microseconds + +For a listing of supported asynchronous events, please, refer to the +qmp-events.txt file. + +3. QMP Examples +=============== + +This section provides some examples of real QMP usage, in all of them +'C' stands for 'Client' and 'S' stands for 'Server'. + +3.1 Server greeting +------------------- + +S: {"QMP": {"version": {"qemu": "0.12.50", "package": ""}, "capabilities": []}} + +3.2 Simple 'stop' execution +--------------------------- + +C: { "execute": "stop" } +S: {"return": {}} + +3.3 KVM information +------------------- + +C: { "execute": "query-kvm", "id": "example" } +S: {"return": {"enabled": true, "present": true}, "id": "example"} + +3.4 Parsing error +------------------ + +C: { "execute": } +S: {"error": {"class": "JSONParsing", "desc": "Invalid JSON syntax", "data": +{}}} + +3.5 Powerdown event +------------------- + +S: {"timestamp": {"seconds": 1258551470, "microseconds": 802384}, "event": +"POWERDOWN"} + +4. Capabilities Negotiation +---------------------------- + +When a Client successfully establishes a connection, the Server is in +Capabilities Negotiation mode. + +In this mode only the 'qmp_capabilities' command is allowed to run, all +other commands will return the CommandNotFound error. Asynchronous messages +are not delivered either. + +Clients should use the 'qmp_capabilities' command to enable capabilities +advertised in the Server's greeting (section '2.2 Server Greeting') they +support. + +When the 'qmp_capabilities' command is issued, and if it does not return an +error, the Server enters in Command mode where capabilities changes take +effect, all commands (except 'qmp_capabilities') are allowed and asynchronous +messages are delivered. + +5 Compatibility Considerations +------------------------------ + +All protocol changes or new features which modify the protocol format in an +incompatible way are disabled by default and will be advertised by the +capabilities array (section '2.2 Server Greeting'). Thus, Clients can check +that array and enable the capabilities they support. + +Additionally, Clients must not assume any particular: + +- Size of json-objects or length of json-arrays +- Order of json-object members or json-array elements +- Amount of errors generated by a command, that is, new errors can be added + to any existing command in newer versions of the Server + +6. Downstream extension of QMP +------------------------------ + +We recommend that downstream consumers of QEMU do *not* modify QMP. +Management tools should be able to support both upstream and downstream +versions of QMP without special logic, and downstream extensions are +inherently at odds with that. + +However, we recognize that it is sometimes impossible for downstreams to +avoid modifying QMP. Both upstream and downstream need to take care to +preserve long-term compatibility and interoperability. + +To help with that, QMP reserves JSON object member names beginning with +'__' (double underscore) for downstream use ("downstream names"). This +means upstream will never use any downstream names for its commands, +arguments, errors, asynchronous events, and so forth. + +Any new names downstream wishes to add must begin with '__'. To +ensure compatibility with other downstreams, it is strongly +recommended that you prefix your downstram names with '__RFQDN_' where +RFQDN is a valid, reverse fully qualified domain name which you +control. For example, a qemu-kvm specific monitor command would be: + + (qemu) __org.linux-kvm_enable_irqchip + +Downstream must not change the server greeting (section 2.2) other than +to offer additional capabilities. But see below for why even that is +discouraged. + +Section '5 Compatibility Considerations' applies to downstream as well +as to upstream, obviously. It follows that downstream must behave +exactly like upstream for any input not containing members with +downstream names ("downstream members"), except it may add members +with downstream names to its output. + +Thus, a client should not be able to distinguish downstream from +upstream as long as it doesn't send input with downstream members, and +properly ignores any downstream members in the output it receives. + +Advice on downstream modifications: + +1. Introducing new commands is okay. If you want to extend an existing + command, consider introducing a new one with the new behaviour + instead. + +2. Introducing new asynchronous messages is okay. If you want to extend + an existing message, consider adding a new one instead. + +3. Introducing new errors for use in new commands is okay. Adding new + errors to existing commands counts as extension, so 1. applies. + +4. New capabilities are strongly discouraged. Capabilities are for + evolving the basic protocol, and multiple diverging basic protocol + dialects are most undesirable. diff --git a/QMP/qmp.py b/QMP/qmp.py new file mode 100644 index 0000000..14ce8b0 --- /dev/null +++ b/QMP/qmp.py @@ -0,0 +1,131 @@ +# QEMU Monitor Protocol Python class +# +# Copyright (C) 2009, 2010 Red Hat Inc. +# +# Authors: +# Luiz Capitulino +# +# This work is licensed under the terms of the GNU GPL, version 2. See +# the COPYING file in the top-level directory. + +import json +import errno +import socket + +class QMPError(Exception): + pass + +class QMPConnectError(QMPError): + pass + +class QMPCapabilitiesError(QMPError): + pass + +class QEMUMonitorProtocol: + def __init__(self, address): + """ + Create a QEMUMonitorProtocol class. + + @param address: QEMU address, can be either a unix socket path (string) + or a tuple in the form ( address, port ) for a TCP + connection + @note No connection is established, this is done by the connect() method + """ + self.__events = [] + self.__address = address + self.__sock = self.__get_sock() + self.__sockfile = self.__sock.makefile() + + def __get_sock(self): + if isinstance(self.__address, tuple): + family = socket.AF_INET + else: + family = socket.AF_UNIX + return socket.socket(family, socket.SOCK_STREAM) + + def __json_read(self): + while True: + data = self.__sockfile.readline() + if not data: + return + resp = json.loads(data) + if 'event' in resp: + self.__events.append(resp) + continue + return resp + + error = socket.error + + def connect(self): + """ + Connect to the QMP Monitor and perform capabilities negotiation. + + @return QMP greeting dict + @raise socket.error on socket connection errors + @raise QMPConnectError if the greeting is not received + @raise QMPCapabilitiesError if fails to negotiate capabilities + """ + self.__sock.connect(self.__address) + greeting = self.__json_read() + if greeting is None or not greeting.has_key('QMP'): + raise QMPConnectError + # Greeting seems ok, negotiate capabilities + resp = self.cmd('qmp_capabilities') + if "return" in resp: + return greeting + raise QMPCapabilitiesError + + def cmd_obj(self, qmp_cmd): + """ + Send a QMP command to the QMP Monitor. + + @param qmp_cmd: QMP command to be sent as a Python dict + @return QMP response as a Python dict or None if the connection has + been closed + """ + try: + self.__sock.sendall(json.dumps(qmp_cmd)) + except socket.error, err: + if err[0] == errno.EPIPE: + return + raise socket.error(err) + return self.__json_read() + + def cmd(self, name, args=None, id=None): + """ + Build a QMP command and send it to the QMP Monitor. + + @param name: command name (string) + @param args: command arguments (dict) + @param id: command id (dict, list, string or int) + """ + qmp_cmd = { 'execute': name } + if args: + qmp_cmd['arguments'] = args + if id: + qmp_cmd['id'] = id + return self.cmd_obj(qmp_cmd) + + def get_events(self): + """ + Get a list of available QMP events. + """ + self.__sock.setblocking(0) + try: + self.__json_read() + except socket.error, err: + if err[0] == errno.EAGAIN: + # No data available + pass + self.__sock.setblocking(1) + return self.__events + + def clear_events(self): + """ + Clear current list of pending events. + """ + self.__events = [] + + def close(self): + self.__sock.close() + self.__sockfile.close() diff --git a/README b/README new file mode 100644 index 0000000..dfd56f2 --- /dev/null +++ b/README @@ -0,0 +1,3 @@ +Read the documentation in qemu-doc.html. + +Fabrice Bellard. diff --git a/TODO b/TODO new file mode 100644 index 0000000..1d4c638 --- /dev/null +++ b/TODO @@ -0,0 +1,37 @@ +General: +------- +- cycle counter for all archs +- cpu_interrupt() win32/SMP fix +- merge PIC spurious interrupt patch +- warning for OS/2: must not use 128 MB memory (merge bochs cmos patch ?) +- config file (at least for windows/Mac OS X) +- update doc: PCI infos. +- basic VGA optimizations +- better code fetch +- do not resize vga if invalid size. +- TLB code protection support for PPC +- disable SMC handling for ARM/SPARC/PPC (not finished) +- see undefined flags for BTx insn +- keyboard output buffer filling timing emulation +- tests for each target CPU +- fix all remaining thread lock issues (must put TBs in a specific invalid + state, find a solution for tb_flush()). + +ppc specific: +------------ +- TLB invalidate not needed if msr_pr changes +- enable shift optimizations ? + +linux-user specific: +------------------- +- remove threading support as it cannot work at this point +- improve IPC syscalls +- more syscalls (in particular all 64 bit ones, IPCs, fix 64 bit + issues, fix 16 bit uid issues) +- use kernel traps for unaligned accesses on ARM ? + + +lower priority: +-------------- +- int15 ah=86: use better timing +- use -msoft-float on ARM diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..930e300 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +0.14.1 diff --git a/a.out.h b/a.out.h new file mode 100644 index 0000000..dfc104e --- /dev/null +++ b/a.out.h @@ -0,0 +1,430 @@ +/* a.out.h + + Copyright 1997, 1998, 1999, 2001 Red Hat, Inc. + +This file is part of Cygwin. + +This software is a copyrighted work licensed under the terms of the +Cygwin license. Please consult the file "CYGWIN_LICENSE" for +details. */ + +#ifndef _A_OUT_H_ +#define _A_OUT_H_ + +#ifdef __cplusplus +extern "C" { +#endif +#define COFF_IMAGE_WITH_PE +#define COFF_LONG_SECTION_NAMES + +/*** coff information for Intel 386/486. */ + + +/********************** FILE HEADER **********************/ + +struct external_filehdr { + short f_magic; /* magic number */ + short f_nscns; /* number of sections */ + host_ulong f_timdat; /* time & date stamp */ + host_ulong f_symptr; /* file pointer to symtab */ + host_ulong f_nsyms; /* number of symtab entries */ + short f_opthdr; /* sizeof(optional hdr) */ + short f_flags; /* flags */ +}; + +/* Bits for f_flags: + * F_RELFLG relocation info stripped from file + * F_EXEC file is executable (no unresolved external references) + * F_LNNO line numbers stripped from file + * F_LSYMS local symbols stripped from file + * F_AR32WR file has byte ordering of an AR32WR machine (e.g. vax) + */ + +#define F_RELFLG (0x0001) +#define F_EXEC (0x0002) +#define F_LNNO (0x0004) +#define F_LSYMS (0x0008) + + + +#define I386MAGIC 0x14c +#define I386PTXMAGIC 0x154 +#define I386AIXMAGIC 0x175 + +/* This is Lynx's all-platform magic number for executables. */ + +#define LYNXCOFFMAGIC 0415 + +#define I386BADMAG(x) (((x).f_magic != I386MAGIC) \ + && (x).f_magic != I386AIXMAGIC \ + && (x).f_magic != I386PTXMAGIC \ + && (x).f_magic != LYNXCOFFMAGIC) + +#define FILHDR struct external_filehdr +#define FILHSZ 20 + + +/********************** AOUT "OPTIONAL HEADER"= + **********************/ + + +typedef struct +{ + unsigned short magic; /* type of file */ + unsigned short vstamp; /* version stamp */ + host_ulong tsize; /* text size in bytes, padded to FW bdry*/ + host_ulong dsize; /* initialized data " " */ + host_ulong bsize; /* uninitialized data " " */ + host_ulong entry; /* entry pt. */ + host_ulong text_start; /* base of text used for this file */ + host_ulong data_start; /* base of data used for this file= + */ +} +AOUTHDR; + +#define AOUTSZ 28 +#define AOUTHDRSZ 28 + +#define OMAGIC 0404 /* object files, eg as output */ +#define ZMAGIC 0413 /* demand load format, eg normal ld output */ +#define STMAGIC 0401 /* target shlib */ +#define SHMAGIC 0443 /* host shlib */ + + +/* define some NT default values */ +/* #define NT_IMAGE_BASE 0x400000 moved to internal.h */ +#define NT_SECTION_ALIGNMENT 0x1000 +#define NT_FILE_ALIGNMENT 0x200 +#define NT_DEF_RESERVE 0x100000 +#define NT_DEF_COMMIT 0x1000 + +/********************** SECTION HEADER **********************/ + + +struct external_scnhdr { + char s_name[8]; /* section name */ + host_ulong s_paddr; /* physical address, offset + of last addr in scn */ + host_ulong s_vaddr; /* virtual address */ + host_ulong s_size; /* section size */ + host_ulong s_scnptr; /* file ptr to raw data for section */ + host_ulong s_relptr; /* file ptr to relocation */ + host_ulong s_lnnoptr; /* file ptr to line numbers */ + unsigned short s_nreloc; /* number of relocation entries */ + unsigned short s_nlnno; /* number of line number entries*/ + host_ulong s_flags; /* flags */ +}; + +#define SCNHDR struct external_scnhdr +#define SCNHSZ 40 + +/* + * names of "special" sections + */ +#define _TEXT ".text" +#define _DATA ".data" +#define _BSS ".bss" +#define _COMMENT ".comment" +#define _LIB ".lib" + +/********************** LINE NUMBERS **********************/ + +/* 1 line number entry for every "breakpointable" source line in a section. + * Line numbers are grouped on a per function basis; first entry in a function + * grouping will have l_lnno = 0 and in place of physical address will be the + * symbol table index of the function name. + */ +struct external_lineno { + union { + host_ulong l_symndx; /* function name symbol index, iff l_lnno 0 */ + host_ulong l_paddr; /* (physical) address of line number */ + } l_addr; + unsigned short l_lnno; /* line number */ +}; + +#define LINENO struct external_lineno +#define LINESZ 6 + +/********************** SYMBOLS **********************/ + +#define E_SYMNMLEN 8 /* # characters in a symbol name */ +#define E_FILNMLEN 14 /* # characters in a file name */ +#define E_DIMNUM 4 /* # array dimensions in auxiliary entry */ + +struct __attribute__((packed)) external_syment +{ + union { + char e_name[E_SYMNMLEN]; + struct { + host_ulong e_zeroes; + host_ulong e_offset; + } e; + } e; + host_ulong e_value; + unsigned short e_scnum; + unsigned short e_type; + char e_sclass[1]; + char e_numaux[1]; +}; + +#define N_BTMASK (0xf) +#define N_TMASK (0x30) +#define N_BTSHFT (4) +#define N_TSHIFT (2) + +union external_auxent { + struct { + host_ulong x_tagndx; /* str, un, or enum tag indx */ + union { + struct { + unsigned short x_lnno; /* declaration line number */ + unsigned short x_size; /* str/union/array size */ + } x_lnsz; + host_ulong x_fsize; /* size of function */ + } x_misc; + union { + struct { /* if ISFCN, tag, or .bb */ + host_ulong x_lnnoptr;/* ptr to fcn line # */ + host_ulong x_endndx; /* entry ndx past block end */ + } x_fcn; + struct { /* if ISARY, up to 4 dimen. */ + char x_dimen[E_DIMNUM][2]; + } x_ary; + } x_fcnary; + unsigned short x_tvndx; /* tv index */ + } x_sym; + + union { + char x_fname[E_FILNMLEN]; + struct { + host_ulong x_zeroes; + host_ulong x_offset; + } x_n; + } x_file; + + struct { + host_ulong x_scnlen; /* section length */ + unsigned short x_nreloc; /* # relocation entries */ + unsigned short x_nlinno; /* # line numbers */ + host_ulong x_checksum; /* section COMDAT checksum */ + unsigned short x_associated;/* COMDAT associated section index */ + char x_comdat[1]; /* COMDAT selection number */ + } x_scn; + + struct { + host_ulong x_tvfill; /* tv fill value */ + unsigned short x_tvlen; /* length of .tv */ + char x_tvran[2][2]; /* tv range */ + } x_tv; /* info about .tv section (in auxent of symbol .tv)) */ + +}; + +#define SYMENT struct external_syment +#define SYMESZ 18 +#define AUXENT union external_auxent +#define AUXESZ 18 + +#define _ETEXT "etext" + +/********************** RELOCATION DIRECTIVES **********************/ + +struct external_reloc { + char r_vaddr[4]; + char r_symndx[4]; + char r_type[2]; +}; + +#define RELOC struct external_reloc +#define RELSZ 10 + +/* end of coff/i386.h */ + +/* PE COFF header information */ + +#ifndef _PE_H +#define _PE_H + +/* NT specific file attributes */ +#define IMAGE_FILE_RELOCS_STRIPPED 0x0001 +#define IMAGE_FILE_EXECUTABLE_IMAGE 0x0002 +#define IMAGE_FILE_LINE_NUMS_STRIPPED 0x0004 +#define IMAGE_FILE_LOCAL_SYMS_STRIPPED 0x0008 +#define IMAGE_FILE_BYTES_REVERSED_LO 0x0080 +#define IMAGE_FILE_32BIT_MACHINE 0x0100 +#define IMAGE_FILE_DEBUG_STRIPPED 0x0200 +#define IMAGE_FILE_SYSTEM 0x1000 +#define IMAGE_FILE_DLL 0x2000 +#define IMAGE_FILE_BYTES_REVERSED_HI 0x8000 + +/* additional flags to be set for section headers to allow the NT loader to + read and write to the section data (to replace the addresses of data in + dlls for one thing); also to execute the section in .text's case= + */ +#define IMAGE_SCN_MEM_DISCARDABLE 0x02000000 +#define IMAGE_SCN_MEM_EXECUTE 0x20000000 +#define IMAGE_SCN_MEM_READ 0x40000000 +#define IMAGE_SCN_MEM_WRITE 0x80000000 + +/* + * Section characteristics added for ppc-nt + */ + +#define IMAGE_SCN_TYPE_NO_PAD 0x00000008 /* Reserved. */ + +#define IMAGE_SCN_CNT_CODE 0x00000020 /* Section contains code. */ +#define IMAGE_SCN_CNT_INITIALIZED_DATA 0x00000040 /* Section contains initialized data. */ +#define IMAGE_SCN_CNT_UNINITIALIZED_DATA 0x00000080 /* Section contains uninitialized data. */ + +#define IMAGE_SCN_LNK_OTHER 0x00000100 /* Reserved. */ +#define IMAGE_SCN_LNK_INFO 0x00000200 /* Section contains comments or some other type of information. */ +#define IMAGE_SCN_LNK_REMOVE 0x00000800 /* Section contents will not become part of image. */ +#define IMAGE_SCN_LNK_COMDAT 0x00001000 /* Section contents comdat. */ + +#define IMAGE_SCN_MEM_FARDATA 0x00008000 + +#define IMAGE_SCN_MEM_PURGEABLE 0x00020000 +#define IMAGE_SCN_MEM_16BIT 0x00020000 +#define IMAGE_SCN_MEM_LOCKED 0x00040000 +#define IMAGE_SCN_MEM_PRELOAD 0x00080000 + +#define IMAGE_SCN_ALIGN_1BYTES 0x00100000 +#define IMAGE_SCN_ALIGN_2BYTES 0x00200000 +#define IMAGE_SCN_ALIGN_4BYTES 0x00300000 +#define IMAGE_SCN_ALIGN_8BYTES 0x00400000 +#define IMAGE_SCN_ALIGN_16BYTES 0x00500000 /* Default alignment if no others are specified. */ +#define IMAGE_SCN_ALIGN_32BYTES 0x00600000 +#define IMAGE_SCN_ALIGN_64BYTES 0x00700000 + + +#define IMAGE_SCN_LNK_NRELOC_OVFL 0x01000000 /* Section contains extended relocations. */ +#define IMAGE_SCN_MEM_NOT_CACHED 0x04000000 /* Section is not cachable. */ +#define IMAGE_SCN_MEM_NOT_PAGED 0x08000000 /* Section is not pageable. */ +#define IMAGE_SCN_MEM_SHARED 0x10000000 /* Section is shareable. */ + +/* COMDAT selection codes. */ + +#define IMAGE_COMDAT_SELECT_NODUPLICATES (1) /* Warn if duplicates. */ +#define IMAGE_COMDAT_SELECT_ANY (2) /* No warning. */ +#define IMAGE_COMDAT_SELECT_SAME_SIZE (3) /* Warn if different size. */ +#define IMAGE_COMDAT_SELECT_EXACT_MATCH (4) /* Warn if different. */ +#define IMAGE_COMDAT_SELECT_ASSOCIATIVE (5) /* Base on other section. */ + +/* Magic values that are true for all dos/nt implementations */ +#define DOSMAGIC 0x5a4d +#define NT_SIGNATURE 0x00004550 + +/* NT allows long filenames, we want to accommodate this. This may break + some of the bfd functions */ +#undef FILNMLEN +#define FILNMLEN 18 /* # characters in a file name */ + + +#ifdef COFF_IMAGE_WITH_PE +/* The filehdr is only weired in images */ + +#undef FILHDR +struct external_PE_filehdr +{ + /* DOS header fields */ + unsigned short e_magic; /* Magic number, 0x5a4d */ + unsigned short e_cblp; /* Bytes on last page of file, 0x90 */ + unsigned short e_cp; /* Pages in file, 0x3 */ + unsigned short e_crlc; /* Relocations, 0x0 */ + unsigned short e_cparhdr; /* Size of header in paragraphs, 0x4 */ + unsigned short e_minalloc; /* Minimum extra paragraphs needed, 0x0 */ + unsigned short e_maxalloc; /* Maximum extra paragraphs needed, 0xFFFF */ + unsigned short e_ss; /* Initial (relative) SS value, 0x0 */ + unsigned short e_sp; /* Initial SP value, 0xb8 */ + unsigned short e_csum; /* Checksum, 0x0 */ + unsigned short e_ip; /* Initial IP value, 0x0 */ + unsigned short e_cs; /* Initial (relative) CS value, 0x0 */ + unsigned short e_lfarlc; /* File address of relocation table, 0x40 */ + unsigned short e_ovno; /* Overlay number, 0x0 */ + char e_res[4][2]; /* Reserved words, all 0x0 */ + unsigned short e_oemid; /* OEM identifier (for e_oeminfo), 0x0 */ + unsigned short e_oeminfo; /* OEM information; e_oemid specific, 0x0 */ + char e_res2[10][2]; /* Reserved words, all 0x0 */ + host_ulong e_lfanew; /* File address of new exe header, 0x80 */ + char dos_message[16][4]; /* other stuff, always follow DOS header */ + unsigned int nt_signature; /* required NT signature, 0x4550 */ + + /* From standard header */ + + unsigned short f_magic; /* magic number */ + unsigned short f_nscns; /* number of sections */ + host_ulong f_timdat; /* time & date stamp */ + host_ulong f_symptr; /* file pointer to symtab */ + host_ulong f_nsyms; /* number of symtab entries */ + unsigned short f_opthdr; /* sizeof(optional hdr) */ + unsigned short f_flags; /* flags */ +}; + + +#define FILHDR struct external_PE_filehdr +#undef FILHSZ +#define FILHSZ 152 + +#endif + +typedef struct +{ + unsigned short magic; /* type of file */ + unsigned short vstamp; /* version stamp */ + host_ulong tsize; /* text size in bytes, padded to FW bdry*/ + host_ulong dsize; /* initialized data " " */ + host_ulong bsize; /* uninitialized data " " */ + host_ulong entry; /* entry pt. */ + host_ulong text_start; /* base of text used for this file */ + host_ulong data_start; /* base of all data used for this file */ + + /* NT extra fields; see internal.h for descriptions */ + host_ulong ImageBase; + host_ulong SectionAlignment; + host_ulong FileAlignment; + unsigned short MajorOperatingSystemVersion; + unsigned short MinorOperatingSystemVersion; + unsigned short MajorImageVersion; + unsigned short MinorImageVersion; + unsigned short MajorSubsystemVersion; + unsigned short MinorSubsystemVersion; + char Reserved1[4]; + host_ulong SizeOfImage; + host_ulong SizeOfHeaders; + host_ulong CheckSum; + unsigned short Subsystem; + unsigned short DllCharacteristics; + host_ulong SizeOfStackReserve; + host_ulong SizeOfStackCommit; + host_ulong SizeOfHeapReserve; + host_ulong SizeOfHeapCommit; + host_ulong LoaderFlags; + host_ulong NumberOfRvaAndSizes; + /* IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; */ + char DataDirectory[16][2][4]; /* 16 entries, 2 elements/entry, 4 chars */ + +} PEAOUTHDR; + + +#undef AOUTSZ +#define AOUTSZ (AOUTHDRSZ + 196) + +#undef E_FILNMLEN +#define E_FILNMLEN 18 /* # characters in a file name */ +#endif + +/* end of coff/pe.h */ + +#define DT_NON (0) /* no derived type */ +#define DT_PTR (1) /* pointer */ +#define DT_FCN (2) /* function */ +#define DT_ARY (3) /* array */ + +#define ISPTR(x) (((x) & N_TMASK) == (DT_PTR << N_BTSHFT)) +#define ISFCN(x) (((x) & N_TMASK) == (DT_FCN << N_BTSHFT)) +#define ISARY(x) (((x) & N_TMASK) == (DT_ARY << N_BTSHFT)) + +#ifdef __cplusplus +} +#endif + +#endif /* _A_OUT_H_ */ diff --git a/acl.c b/acl.c new file mode 100644 index 0000000..311dade --- /dev/null +++ b/acl.c @@ -0,0 +1,185 @@ +/* + * QEMU access control list management + * + * Copyright (C) 2009 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. + */ + + +#include "qemu-common.h" +#include "sysemu.h" +#include "acl.h" + +#ifdef CONFIG_FNMATCH +#include +#endif + + +static unsigned int nacls = 0; +static qemu_acl **acls = NULL; + + + +qemu_acl *qemu_acl_find(const char *aclname) +{ + int i; + for (i = 0 ; i < nacls ; i++) { + if (strcmp(acls[i]->aclname, aclname) == 0) + return acls[i]; + } + + return NULL; +} + +qemu_acl *qemu_acl_init(const char *aclname) +{ + qemu_acl *acl; + + acl = qemu_acl_find(aclname); + if (acl) + return acl; + + acl = qemu_malloc(sizeof(*acl)); + acl->aclname = qemu_strdup(aclname); + /* Deny by default, so there is no window of "open + * access" between QEMU starting, and the user setting + * up ACLs in the monitor */ + acl->defaultDeny = 1; + + acl->nentries = 0; + QTAILQ_INIT(&acl->entries); + + acls = qemu_realloc(acls, sizeof(*acls) * (nacls +1)); + acls[nacls] = acl; + nacls++; + + return acl; +} + +int qemu_acl_party_is_allowed(qemu_acl *acl, + const char *party) +{ + qemu_acl_entry *entry; + + QTAILQ_FOREACH(entry, &acl->entries, next) { +#ifdef CONFIG_FNMATCH + if (fnmatch(entry->match, party, 0) == 0) + return entry->deny ? 0 : 1; +#else + /* No fnmatch, so fallback to exact string matching + * instead of allowing wildcards */ + if (strcmp(entry->match, party) == 0) + return entry->deny ? 0 : 1; +#endif + } + + return acl->defaultDeny ? 0 : 1; +} + + +void qemu_acl_reset(qemu_acl *acl) +{ + qemu_acl_entry *entry; + + /* Put back to deny by default, so there is no window + * of "open access" while the user re-initializes the + * access control list */ + acl->defaultDeny = 1; + QTAILQ_FOREACH(entry, &acl->entries, next) { + QTAILQ_REMOVE(&acl->entries, entry, next); + free(entry->match); + free(entry); + } + acl->nentries = 0; +} + + +int qemu_acl_append(qemu_acl *acl, + int deny, + const char *match) +{ + qemu_acl_entry *entry; + + entry = qemu_malloc(sizeof(*entry)); + entry->match = qemu_strdup(match); + entry->deny = deny; + + QTAILQ_INSERT_TAIL(&acl->entries, entry, next); + acl->nentries++; + + return acl->nentries; +} + + +int qemu_acl_insert(qemu_acl *acl, + int deny, + const char *match, + int index) +{ + qemu_acl_entry *entry; + qemu_acl_entry *tmp; + int i = 0; + + if (index <= 0) + return -1; + if (index >= acl->nentries) + return qemu_acl_append(acl, deny, match); + + + entry = qemu_malloc(sizeof(*entry)); + entry->match = qemu_strdup(match); + entry->deny = deny; + + QTAILQ_FOREACH(tmp, &acl->entries, next) { + i++; + if (i == index) { + QTAILQ_INSERT_BEFORE(tmp, entry, next); + acl->nentries++; + break; + } + } + + return i; +} + +int qemu_acl_remove(qemu_acl *acl, + const char *match) +{ + qemu_acl_entry *entry; + int i = 0; + + QTAILQ_FOREACH(entry, &acl->entries, next) { + i++; + if (strcmp(entry->match, match) == 0) { + QTAILQ_REMOVE(&acl->entries, entry, next); + return i; + } + } + return -1; +} + + +/* + * Local variables: + * c-indent-level: 4 + * c-basic-offset: 4 + * tab-width: 8 + * End: + */ diff --git a/acl.h b/acl.h new file mode 100644 index 0000000..0ef7804 --- /dev/null +++ b/acl.h @@ -0,0 +1,74 @@ +/* + * QEMU access control list management + * + * Copyright (C) 2009 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. + */ + +#ifndef __QEMU_ACL_H__ +#define __QEMU_ACL_H__ + +#include "qemu-queue.h" + +typedef struct qemu_acl_entry qemu_acl_entry; +typedef struct qemu_acl qemu_acl; + +struct qemu_acl_entry { + char *match; + int deny; + + QTAILQ_ENTRY(qemu_acl_entry) next; +}; + +struct qemu_acl { + char *aclname; + unsigned int nentries; + QTAILQ_HEAD(,qemu_acl_entry) entries; + int defaultDeny; +}; + +qemu_acl *qemu_acl_init(const char *aclname); + +qemu_acl *qemu_acl_find(const char *aclname); + +int qemu_acl_party_is_allowed(qemu_acl *acl, + const char *party); + +void qemu_acl_reset(qemu_acl *acl); + +int qemu_acl_append(qemu_acl *acl, + int deny, + const char *match); +int qemu_acl_insert(qemu_acl *acl, + int deny, + const char *match, + int index); +int qemu_acl_remove(qemu_acl *acl, + const char *match); + +#endif /* __QEMU_ACL_H__ */ + +/* + * Local variables: + * c-indent-level: 4 + * c-basic-offset: 4 + * tab-width: 8 + * End: + */ diff --git a/aes.c b/aes.c new file mode 100644 index 0000000..eb37adb --- /dev/null +++ b/aes.c @@ -0,0 +1,1314 @@ +/** + * + * aes.c - integrated in QEMU by Fabrice Bellard from the OpenSSL project. + */ +/* + * rijndael-alg-fst.c + * + * @version 3.0 (December 2000) + * + * Optimised ANSI C code for the Rijndael cipher (now AES) + * + * @author Vincent Rijmen + * @author Antoon Bosselaers + * @author Paulo Barreto + * + * This code is hereby placed in the public domain. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ''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 AUTHORS 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 "qemu-common.h" +#include "aes.h" + +#ifndef NDEBUG +#define NDEBUG +#endif + +typedef uint32_t u32; +typedef uint16_t u16; +typedef uint8_t u8; + +/* This controls loop-unrolling in aes_core.c */ +#undef FULL_UNROLL +# define GETU32(pt) (((u32)(pt)[0] << 24) ^ ((u32)(pt)[1] << 16) ^ ((u32)(pt)[2] << 8) ^ ((u32)(pt)[3])) +# define PUTU32(ct, st) { (ct)[0] = (u8)((st) >> 24); (ct)[1] = (u8)((st) >> 16); (ct)[2] = (u8)((st) >> 8); (ct)[3] = (u8)(st); } + +/* +Te0[x] = S [x].[02, 01, 01, 03]; +Te1[x] = S [x].[03, 02, 01, 01]; +Te2[x] = S [x].[01, 03, 02, 01]; +Te3[x] = S [x].[01, 01, 03, 02]; +Te4[x] = S [x].[01, 01, 01, 01]; + +Td0[x] = Si[x].[0e, 09, 0d, 0b]; +Td1[x] = Si[x].[0b, 0e, 09, 0d]; +Td2[x] = Si[x].[0d, 0b, 0e, 09]; +Td3[x] = Si[x].[09, 0d, 0b, 0e]; +Td4[x] = Si[x].[01, 01, 01, 01]; +*/ + +static const u32 Te0[256] = { + 0xc66363a5U, 0xf87c7c84U, 0xee777799U, 0xf67b7b8dU, + 0xfff2f20dU, 0xd66b6bbdU, 0xde6f6fb1U, 0x91c5c554U, + 0x60303050U, 0x02010103U, 0xce6767a9U, 0x562b2b7dU, + 0xe7fefe19U, 0xb5d7d762U, 0x4dababe6U, 0xec76769aU, + 0x8fcaca45U, 0x1f82829dU, 0x89c9c940U, 0xfa7d7d87U, + 0xeffafa15U, 0xb25959ebU, 0x8e4747c9U, 0xfbf0f00bU, + 0x41adadecU, 0xb3d4d467U, 0x5fa2a2fdU, 0x45afafeaU, + 0x239c9cbfU, 0x53a4a4f7U, 0xe4727296U, 0x9bc0c05bU, + 0x75b7b7c2U, 0xe1fdfd1cU, 0x3d9393aeU, 0x4c26266aU, + 0x6c36365aU, 0x7e3f3f41U, 0xf5f7f702U, 0x83cccc4fU, + 0x6834345cU, 0x51a5a5f4U, 0xd1e5e534U, 0xf9f1f108U, + 0xe2717193U, 0xabd8d873U, 0x62313153U, 0x2a15153fU, + 0x0804040cU, 0x95c7c752U, 0x46232365U, 0x9dc3c35eU, + 0x30181828U, 0x379696a1U, 0x0a05050fU, 0x2f9a9ab5U, + 0x0e070709U, 0x24121236U, 0x1b80809bU, 0xdfe2e23dU, + 0xcdebeb26U, 0x4e272769U, 0x7fb2b2cdU, 0xea75759fU, + 0x1209091bU, 0x1d83839eU, 0x582c2c74U, 0x341a1a2eU, + 0x361b1b2dU, 0xdc6e6eb2U, 0xb45a5aeeU, 0x5ba0a0fbU, + 0xa45252f6U, 0x763b3b4dU, 0xb7d6d661U, 0x7db3b3ceU, + 0x5229297bU, 0xdde3e33eU, 0x5e2f2f71U, 0x13848497U, + 0xa65353f5U, 0xb9d1d168U, 0x00000000U, 0xc1eded2cU, + 0x40202060U, 0xe3fcfc1fU, 0x79b1b1c8U, 0xb65b5bedU, + 0xd46a6abeU, 0x8dcbcb46U, 0x67bebed9U, 0x7239394bU, + 0x944a4adeU, 0x984c4cd4U, 0xb05858e8U, 0x85cfcf4aU, + 0xbbd0d06bU, 0xc5efef2aU, 0x4faaaae5U, 0xedfbfb16U, + 0x864343c5U, 0x9a4d4dd7U, 0x66333355U, 0x11858594U, + 0x8a4545cfU, 0xe9f9f910U, 0x04020206U, 0xfe7f7f81U, + 0xa05050f0U, 0x783c3c44U, 0x259f9fbaU, 0x4ba8a8e3U, + 0xa25151f3U, 0x5da3a3feU, 0x804040c0U, 0x058f8f8aU, + 0x3f9292adU, 0x219d9dbcU, 0x70383848U, 0xf1f5f504U, + 0x63bcbcdfU, 0x77b6b6c1U, 0xafdada75U, 0x42212163U, + 0x20101030U, 0xe5ffff1aU, 0xfdf3f30eU, 0xbfd2d26dU, + 0x81cdcd4cU, 0x180c0c14U, 0x26131335U, 0xc3ecec2fU, + 0xbe5f5fe1U, 0x359797a2U, 0x884444ccU, 0x2e171739U, + 0x93c4c457U, 0x55a7a7f2U, 0xfc7e7e82U, 0x7a3d3d47U, + 0xc86464acU, 0xba5d5de7U, 0x3219192bU, 0xe6737395U, + 0xc06060a0U, 0x19818198U, 0x9e4f4fd1U, 0xa3dcdc7fU, + 0x44222266U, 0x542a2a7eU, 0x3b9090abU, 0x0b888883U, + 0x8c4646caU, 0xc7eeee29U, 0x6bb8b8d3U, 0x2814143cU, + 0xa7dede79U, 0xbc5e5ee2U, 0x160b0b1dU, 0xaddbdb76U, + 0xdbe0e03bU, 0x64323256U, 0x743a3a4eU, 0x140a0a1eU, + 0x924949dbU, 0x0c06060aU, 0x4824246cU, 0xb85c5ce4U, + 0x9fc2c25dU, 0xbdd3d36eU, 0x43acacefU, 0xc46262a6U, + 0x399191a8U, 0x319595a4U, 0xd3e4e437U, 0xf279798bU, + 0xd5e7e732U, 0x8bc8c843U, 0x6e373759U, 0xda6d6db7U, + 0x018d8d8cU, 0xb1d5d564U, 0x9c4e4ed2U, 0x49a9a9e0U, + 0xd86c6cb4U, 0xac5656faU, 0xf3f4f407U, 0xcfeaea25U, + 0xca6565afU, 0xf47a7a8eU, 0x47aeaee9U, 0x10080818U, + 0x6fbabad5U, 0xf0787888U, 0x4a25256fU, 0x5c2e2e72U, + 0x381c1c24U, 0x57a6a6f1U, 0x73b4b4c7U, 0x97c6c651U, + 0xcbe8e823U, 0xa1dddd7cU, 0xe874749cU, 0x3e1f1f21U, + 0x964b4bddU, 0x61bdbddcU, 0x0d8b8b86U, 0x0f8a8a85U, + 0xe0707090U, 0x7c3e3e42U, 0x71b5b5c4U, 0xcc6666aaU, + 0x904848d8U, 0x06030305U, 0xf7f6f601U, 0x1c0e0e12U, + 0xc26161a3U, 0x6a35355fU, 0xae5757f9U, 0x69b9b9d0U, + 0x17868691U, 0x99c1c158U, 0x3a1d1d27U, 0x279e9eb9U, + 0xd9e1e138U, 0xebf8f813U, 0x2b9898b3U, 0x22111133U, + 0xd26969bbU, 0xa9d9d970U, 0x078e8e89U, 0x339494a7U, + 0x2d9b9bb6U, 0x3c1e1e22U, 0x15878792U, 0xc9e9e920U, + 0x87cece49U, 0xaa5555ffU, 0x50282878U, 0xa5dfdf7aU, + 0x038c8c8fU, 0x59a1a1f8U, 0x09898980U, 0x1a0d0d17U, + 0x65bfbfdaU, 0xd7e6e631U, 0x844242c6U, 0xd06868b8U, + 0x824141c3U, 0x299999b0U, 0x5a2d2d77U, 0x1e0f0f11U, + 0x7bb0b0cbU, 0xa85454fcU, 0x6dbbbbd6U, 0x2c16163aU, +}; +static const u32 Te1[256] = { + 0xa5c66363U, 0x84f87c7cU, 0x99ee7777U, 0x8df67b7bU, + 0x0dfff2f2U, 0xbdd66b6bU, 0xb1de6f6fU, 0x5491c5c5U, + 0x50603030U, 0x03020101U, 0xa9ce6767U, 0x7d562b2bU, + 0x19e7fefeU, 0x62b5d7d7U, 0xe64dababU, 0x9aec7676U, + 0x458fcacaU, 0x9d1f8282U, 0x4089c9c9U, 0x87fa7d7dU, + 0x15effafaU, 0xebb25959U, 0xc98e4747U, 0x0bfbf0f0U, + 0xec41adadU, 0x67b3d4d4U, 0xfd5fa2a2U, 0xea45afafU, + 0xbf239c9cU, 0xf753a4a4U, 0x96e47272U, 0x5b9bc0c0U, + 0xc275b7b7U, 0x1ce1fdfdU, 0xae3d9393U, 0x6a4c2626U, + 0x5a6c3636U, 0x417e3f3fU, 0x02f5f7f7U, 0x4f83ccccU, + 0x5c683434U, 0xf451a5a5U, 0x34d1e5e5U, 0x08f9f1f1U, + 0x93e27171U, 0x73abd8d8U, 0x53623131U, 0x3f2a1515U, + 0x0c080404U, 0x5295c7c7U, 0x65462323U, 0x5e9dc3c3U, + 0x28301818U, 0xa1379696U, 0x0f0a0505U, 0xb52f9a9aU, + 0x090e0707U, 0x36241212U, 0x9b1b8080U, 0x3ddfe2e2U, + 0x26cdebebU, 0x694e2727U, 0xcd7fb2b2U, 0x9fea7575U, + 0x1b120909U, 0x9e1d8383U, 0x74582c2cU, 0x2e341a1aU, + 0x2d361b1bU, 0xb2dc6e6eU, 0xeeb45a5aU, 0xfb5ba0a0U, + 0xf6a45252U, 0x4d763b3bU, 0x61b7d6d6U, 0xce7db3b3U, + 0x7b522929U, 0x3edde3e3U, 0x715e2f2fU, 0x97138484U, + 0xf5a65353U, 0x68b9d1d1U, 0x00000000U, 0x2cc1ededU, + 0x60402020U, 0x1fe3fcfcU, 0xc879b1b1U, 0xedb65b5bU, + 0xbed46a6aU, 0x468dcbcbU, 0xd967bebeU, 0x4b723939U, + 0xde944a4aU, 0xd4984c4cU, 0xe8b05858U, 0x4a85cfcfU, + 0x6bbbd0d0U, 0x2ac5efefU, 0xe54faaaaU, 0x16edfbfbU, + 0xc5864343U, 0xd79a4d4dU, 0x55663333U, 0x94118585U, + 0xcf8a4545U, 0x10e9f9f9U, 0x06040202U, 0x81fe7f7fU, + 0xf0a05050U, 0x44783c3cU, 0xba259f9fU, 0xe34ba8a8U, + 0xf3a25151U, 0xfe5da3a3U, 0xc0804040U, 0x8a058f8fU, + 0xad3f9292U, 0xbc219d9dU, 0x48703838U, 0x04f1f5f5U, + 0xdf63bcbcU, 0xc177b6b6U, 0x75afdadaU, 0x63422121U, + 0x30201010U, 0x1ae5ffffU, 0x0efdf3f3U, 0x6dbfd2d2U, + 0x4c81cdcdU, 0x14180c0cU, 0x35261313U, 0x2fc3ececU, + 0xe1be5f5fU, 0xa2359797U, 0xcc884444U, 0x392e1717U, + 0x5793c4c4U, 0xf255a7a7U, 0x82fc7e7eU, 0x477a3d3dU, + 0xacc86464U, 0xe7ba5d5dU, 0x2b321919U, 0x95e67373U, + 0xa0c06060U, 0x98198181U, 0xd19e4f4fU, 0x7fa3dcdcU, + 0x66442222U, 0x7e542a2aU, 0xab3b9090U, 0x830b8888U, + 0xca8c4646U, 0x29c7eeeeU, 0xd36bb8b8U, 0x3c281414U, + 0x79a7dedeU, 0xe2bc5e5eU, 0x1d160b0bU, 0x76addbdbU, + 0x3bdbe0e0U, 0x56643232U, 0x4e743a3aU, 0x1e140a0aU, + 0xdb924949U, 0x0a0c0606U, 0x6c482424U, 0xe4b85c5cU, + 0x5d9fc2c2U, 0x6ebdd3d3U, 0xef43acacU, 0xa6c46262U, + 0xa8399191U, 0xa4319595U, 0x37d3e4e4U, 0x8bf27979U, + 0x32d5e7e7U, 0x438bc8c8U, 0x596e3737U, 0xb7da6d6dU, + 0x8c018d8dU, 0x64b1d5d5U, 0xd29c4e4eU, 0xe049a9a9U, + 0xb4d86c6cU, 0xfaac5656U, 0x07f3f4f4U, 0x25cfeaeaU, + 0xafca6565U, 0x8ef47a7aU, 0xe947aeaeU, 0x18100808U, + 0xd56fbabaU, 0x88f07878U, 0x6f4a2525U, 0x725c2e2eU, + 0x24381c1cU, 0xf157a6a6U, 0xc773b4b4U, 0x5197c6c6U, + 0x23cbe8e8U, 0x7ca1ddddU, 0x9ce87474U, 0x213e1f1fU, + 0xdd964b4bU, 0xdc61bdbdU, 0x860d8b8bU, 0x850f8a8aU, + 0x90e07070U, 0x427c3e3eU, 0xc471b5b5U, 0xaacc6666U, + 0xd8904848U, 0x05060303U, 0x01f7f6f6U, 0x121c0e0eU, + 0xa3c26161U, 0x5f6a3535U, 0xf9ae5757U, 0xd069b9b9U, + 0x91178686U, 0x5899c1c1U, 0x273a1d1dU, 0xb9279e9eU, + 0x38d9e1e1U, 0x13ebf8f8U, 0xb32b9898U, 0x33221111U, + 0xbbd26969U, 0x70a9d9d9U, 0x89078e8eU, 0xa7339494U, + 0xb62d9b9bU, 0x223c1e1eU, 0x92158787U, 0x20c9e9e9U, + 0x4987ceceU, 0xffaa5555U, 0x78502828U, 0x7aa5dfdfU, + 0x8f038c8cU, 0xf859a1a1U, 0x80098989U, 0x171a0d0dU, + 0xda65bfbfU, 0x31d7e6e6U, 0xc6844242U, 0xb8d06868U, + 0xc3824141U, 0xb0299999U, 0x775a2d2dU, 0x111e0f0fU, + 0xcb7bb0b0U, 0xfca85454U, 0xd66dbbbbU, 0x3a2c1616U, +}; +static const u32 Te2[256] = { + 0x63a5c663U, 0x7c84f87cU, 0x7799ee77U, 0x7b8df67bU, + 0xf20dfff2U, 0x6bbdd66bU, 0x6fb1de6fU, 0xc55491c5U, + 0x30506030U, 0x01030201U, 0x67a9ce67U, 0x2b7d562bU, + 0xfe19e7feU, 0xd762b5d7U, 0xabe64dabU, 0x769aec76U, + 0xca458fcaU, 0x829d1f82U, 0xc94089c9U, 0x7d87fa7dU, + 0xfa15effaU, 0x59ebb259U, 0x47c98e47U, 0xf00bfbf0U, + 0xadec41adU, 0xd467b3d4U, 0xa2fd5fa2U, 0xafea45afU, + 0x9cbf239cU, 0xa4f753a4U, 0x7296e472U, 0xc05b9bc0U, + 0xb7c275b7U, 0xfd1ce1fdU, 0x93ae3d93U, 0x266a4c26U, + 0x365a6c36U, 0x3f417e3fU, 0xf702f5f7U, 0xcc4f83ccU, + 0x345c6834U, 0xa5f451a5U, 0xe534d1e5U, 0xf108f9f1U, + 0x7193e271U, 0xd873abd8U, 0x31536231U, 0x153f2a15U, + 0x040c0804U, 0xc75295c7U, 0x23654623U, 0xc35e9dc3U, + 0x18283018U, 0x96a13796U, 0x050f0a05U, 0x9ab52f9aU, + 0x07090e07U, 0x12362412U, 0x809b1b80U, 0xe23ddfe2U, + 0xeb26cdebU, 0x27694e27U, 0xb2cd7fb2U, 0x759fea75U, + 0x091b1209U, 0x839e1d83U, 0x2c74582cU, 0x1a2e341aU, + 0x1b2d361bU, 0x6eb2dc6eU, 0x5aeeb45aU, 0xa0fb5ba0U, + 0x52f6a452U, 0x3b4d763bU, 0xd661b7d6U, 0xb3ce7db3U, + 0x297b5229U, 0xe33edde3U, 0x2f715e2fU, 0x84971384U, + 0x53f5a653U, 0xd168b9d1U, 0x00000000U, 0xed2cc1edU, + 0x20604020U, 0xfc1fe3fcU, 0xb1c879b1U, 0x5bedb65bU, + 0x6abed46aU, 0xcb468dcbU, 0xbed967beU, 0x394b7239U, + 0x4ade944aU, 0x4cd4984cU, 0x58e8b058U, 0xcf4a85cfU, + 0xd06bbbd0U, 0xef2ac5efU, 0xaae54faaU, 0xfb16edfbU, + 0x43c58643U, 0x4dd79a4dU, 0x33556633U, 0x85941185U, + 0x45cf8a45U, 0xf910e9f9U, 0x02060402U, 0x7f81fe7fU, + 0x50f0a050U, 0x3c44783cU, 0x9fba259fU, 0xa8e34ba8U, + 0x51f3a251U, 0xa3fe5da3U, 0x40c08040U, 0x8f8a058fU, + 0x92ad3f92U, 0x9dbc219dU, 0x38487038U, 0xf504f1f5U, + 0xbcdf63bcU, 0xb6c177b6U, 0xda75afdaU, 0x21634221U, + 0x10302010U, 0xff1ae5ffU, 0xf30efdf3U, 0xd26dbfd2U, + 0xcd4c81cdU, 0x0c14180cU, 0x13352613U, 0xec2fc3ecU, + 0x5fe1be5fU, 0x97a23597U, 0x44cc8844U, 0x17392e17U, + 0xc45793c4U, 0xa7f255a7U, 0x7e82fc7eU, 0x3d477a3dU, + 0x64acc864U, 0x5de7ba5dU, 0x192b3219U, 0x7395e673U, + 0x60a0c060U, 0x81981981U, 0x4fd19e4fU, 0xdc7fa3dcU, + 0x22664422U, 0x2a7e542aU, 0x90ab3b90U, 0x88830b88U, + 0x46ca8c46U, 0xee29c7eeU, 0xb8d36bb8U, 0x143c2814U, + 0xde79a7deU, 0x5ee2bc5eU, 0x0b1d160bU, 0xdb76addbU, + 0xe03bdbe0U, 0x32566432U, 0x3a4e743aU, 0x0a1e140aU, + 0x49db9249U, 0x060a0c06U, 0x246c4824U, 0x5ce4b85cU, + 0xc25d9fc2U, 0xd36ebdd3U, 0xacef43acU, 0x62a6c462U, + 0x91a83991U, 0x95a43195U, 0xe437d3e4U, 0x798bf279U, + 0xe732d5e7U, 0xc8438bc8U, 0x37596e37U, 0x6db7da6dU, + 0x8d8c018dU, 0xd564b1d5U, 0x4ed29c4eU, 0xa9e049a9U, + 0x6cb4d86cU, 0x56faac56U, 0xf407f3f4U, 0xea25cfeaU, + 0x65afca65U, 0x7a8ef47aU, 0xaee947aeU, 0x08181008U, + 0xbad56fbaU, 0x7888f078U, 0x256f4a25U, 0x2e725c2eU, + 0x1c24381cU, 0xa6f157a6U, 0xb4c773b4U, 0xc65197c6U, + 0xe823cbe8U, 0xdd7ca1ddU, 0x749ce874U, 0x1f213e1fU, + 0x4bdd964bU, 0xbddc61bdU, 0x8b860d8bU, 0x8a850f8aU, + 0x7090e070U, 0x3e427c3eU, 0xb5c471b5U, 0x66aacc66U, + 0x48d89048U, 0x03050603U, 0xf601f7f6U, 0x0e121c0eU, + 0x61a3c261U, 0x355f6a35U, 0x57f9ae57U, 0xb9d069b9U, + 0x86911786U, 0xc15899c1U, 0x1d273a1dU, 0x9eb9279eU, + 0xe138d9e1U, 0xf813ebf8U, 0x98b32b98U, 0x11332211U, + 0x69bbd269U, 0xd970a9d9U, 0x8e89078eU, 0x94a73394U, + 0x9bb62d9bU, 0x1e223c1eU, 0x87921587U, 0xe920c9e9U, + 0xce4987ceU, 0x55ffaa55U, 0x28785028U, 0xdf7aa5dfU, + 0x8c8f038cU, 0xa1f859a1U, 0x89800989U, 0x0d171a0dU, + 0xbfda65bfU, 0xe631d7e6U, 0x42c68442U, 0x68b8d068U, + 0x41c38241U, 0x99b02999U, 0x2d775a2dU, 0x0f111e0fU, + 0xb0cb7bb0U, 0x54fca854U, 0xbbd66dbbU, 0x163a2c16U, +}; +static const u32 Te3[256] = { + + 0x6363a5c6U, 0x7c7c84f8U, 0x777799eeU, 0x7b7b8df6U, + 0xf2f20dffU, 0x6b6bbdd6U, 0x6f6fb1deU, 0xc5c55491U, + 0x30305060U, 0x01010302U, 0x6767a9ceU, 0x2b2b7d56U, + 0xfefe19e7U, 0xd7d762b5U, 0xababe64dU, 0x76769aecU, + 0xcaca458fU, 0x82829d1fU, 0xc9c94089U, 0x7d7d87faU, + 0xfafa15efU, 0x5959ebb2U, 0x4747c98eU, 0xf0f00bfbU, + 0xadadec41U, 0xd4d467b3U, 0xa2a2fd5fU, 0xafafea45U, + 0x9c9cbf23U, 0xa4a4f753U, 0x727296e4U, 0xc0c05b9bU, + 0xb7b7c275U, 0xfdfd1ce1U, 0x9393ae3dU, 0x26266a4cU, + 0x36365a6cU, 0x3f3f417eU, 0xf7f702f5U, 0xcccc4f83U, + 0x34345c68U, 0xa5a5f451U, 0xe5e534d1U, 0xf1f108f9U, + 0x717193e2U, 0xd8d873abU, 0x31315362U, 0x15153f2aU, + 0x04040c08U, 0xc7c75295U, 0x23236546U, 0xc3c35e9dU, + 0x18182830U, 0x9696a137U, 0x05050f0aU, 0x9a9ab52fU, + 0x0707090eU, 0x12123624U, 0x80809b1bU, 0xe2e23ddfU, + 0xebeb26cdU, 0x2727694eU, 0xb2b2cd7fU, 0x75759feaU, + 0x09091b12U, 0x83839e1dU, 0x2c2c7458U, 0x1a1a2e34U, + 0x1b1b2d36U, 0x6e6eb2dcU, 0x5a5aeeb4U, 0xa0a0fb5bU, + 0x5252f6a4U, 0x3b3b4d76U, 0xd6d661b7U, 0xb3b3ce7dU, + 0x29297b52U, 0xe3e33eddU, 0x2f2f715eU, 0x84849713U, + 0x5353f5a6U, 0xd1d168b9U, 0x00000000U, 0xeded2cc1U, + 0x20206040U, 0xfcfc1fe3U, 0xb1b1c879U, 0x5b5bedb6U, + 0x6a6abed4U, 0xcbcb468dU, 0xbebed967U, 0x39394b72U, + 0x4a4ade94U, 0x4c4cd498U, 0x5858e8b0U, 0xcfcf4a85U, + 0xd0d06bbbU, 0xefef2ac5U, 0xaaaae54fU, 0xfbfb16edU, + 0x4343c586U, 0x4d4dd79aU, 0x33335566U, 0x85859411U, + 0x4545cf8aU, 0xf9f910e9U, 0x02020604U, 0x7f7f81feU, + 0x5050f0a0U, 0x3c3c4478U, 0x9f9fba25U, 0xa8a8e34bU, + 0x5151f3a2U, 0xa3a3fe5dU, 0x4040c080U, 0x8f8f8a05U, + 0x9292ad3fU, 0x9d9dbc21U, 0x38384870U, 0xf5f504f1U, + 0xbcbcdf63U, 0xb6b6c177U, 0xdada75afU, 0x21216342U, + 0x10103020U, 0xffff1ae5U, 0xf3f30efdU, 0xd2d26dbfU, + 0xcdcd4c81U, 0x0c0c1418U, 0x13133526U, 0xecec2fc3U, + 0x5f5fe1beU, 0x9797a235U, 0x4444cc88U, 0x1717392eU, + 0xc4c45793U, 0xa7a7f255U, 0x7e7e82fcU, 0x3d3d477aU, + 0x6464acc8U, 0x5d5de7baU, 0x19192b32U, 0x737395e6U, + 0x6060a0c0U, 0x81819819U, 0x4f4fd19eU, 0xdcdc7fa3U, + 0x22226644U, 0x2a2a7e54U, 0x9090ab3bU, 0x8888830bU, + 0x4646ca8cU, 0xeeee29c7U, 0xb8b8d36bU, 0x14143c28U, + 0xdede79a7U, 0x5e5ee2bcU, 0x0b0b1d16U, 0xdbdb76adU, + 0xe0e03bdbU, 0x32325664U, 0x3a3a4e74U, 0x0a0a1e14U, + 0x4949db92U, 0x06060a0cU, 0x24246c48U, 0x5c5ce4b8U, + 0xc2c25d9fU, 0xd3d36ebdU, 0xacacef43U, 0x6262a6c4U, + 0x9191a839U, 0x9595a431U, 0xe4e437d3U, 0x79798bf2U, + 0xe7e732d5U, 0xc8c8438bU, 0x3737596eU, 0x6d6db7daU, + 0x8d8d8c01U, 0xd5d564b1U, 0x4e4ed29cU, 0xa9a9e049U, + 0x6c6cb4d8U, 0x5656faacU, 0xf4f407f3U, 0xeaea25cfU, + 0x6565afcaU, 0x7a7a8ef4U, 0xaeaee947U, 0x08081810U, + 0xbabad56fU, 0x787888f0U, 0x25256f4aU, 0x2e2e725cU, + 0x1c1c2438U, 0xa6a6f157U, 0xb4b4c773U, 0xc6c65197U, + 0xe8e823cbU, 0xdddd7ca1U, 0x74749ce8U, 0x1f1f213eU, + 0x4b4bdd96U, 0xbdbddc61U, 0x8b8b860dU, 0x8a8a850fU, + 0x707090e0U, 0x3e3e427cU, 0xb5b5c471U, 0x6666aaccU, + 0x4848d890U, 0x03030506U, 0xf6f601f7U, 0x0e0e121cU, + 0x6161a3c2U, 0x35355f6aU, 0x5757f9aeU, 0xb9b9d069U, + 0x86869117U, 0xc1c15899U, 0x1d1d273aU, 0x9e9eb927U, + 0xe1e138d9U, 0xf8f813ebU, 0x9898b32bU, 0x11113322U, + 0x6969bbd2U, 0xd9d970a9U, 0x8e8e8907U, 0x9494a733U, + 0x9b9bb62dU, 0x1e1e223cU, 0x87879215U, 0xe9e920c9U, + 0xcece4987U, 0x5555ffaaU, 0x28287850U, 0xdfdf7aa5U, + 0x8c8c8f03U, 0xa1a1f859U, 0x89898009U, 0x0d0d171aU, + 0xbfbfda65U, 0xe6e631d7U, 0x4242c684U, 0x6868b8d0U, + 0x4141c382U, 0x9999b029U, 0x2d2d775aU, 0x0f0f111eU, + 0xb0b0cb7bU, 0x5454fca8U, 0xbbbbd66dU, 0x16163a2cU, +}; +static const u32 Te4[256] = { + 0x63636363U, 0x7c7c7c7cU, 0x77777777U, 0x7b7b7b7bU, + 0xf2f2f2f2U, 0x6b6b6b6bU, 0x6f6f6f6fU, 0xc5c5c5c5U, + 0x30303030U, 0x01010101U, 0x67676767U, 0x2b2b2b2bU, + 0xfefefefeU, 0xd7d7d7d7U, 0xababababU, 0x76767676U, + 0xcacacacaU, 0x82828282U, 0xc9c9c9c9U, 0x7d7d7d7dU, + 0xfafafafaU, 0x59595959U, 0x47474747U, 0xf0f0f0f0U, + 0xadadadadU, 0xd4d4d4d4U, 0xa2a2a2a2U, 0xafafafafU, + 0x9c9c9c9cU, 0xa4a4a4a4U, 0x72727272U, 0xc0c0c0c0U, + 0xb7b7b7b7U, 0xfdfdfdfdU, 0x93939393U, 0x26262626U, + 0x36363636U, 0x3f3f3f3fU, 0xf7f7f7f7U, 0xccccccccU, + 0x34343434U, 0xa5a5a5a5U, 0xe5e5e5e5U, 0xf1f1f1f1U, + 0x71717171U, 0xd8d8d8d8U, 0x31313131U, 0x15151515U, + 0x04040404U, 0xc7c7c7c7U, 0x23232323U, 0xc3c3c3c3U, + 0x18181818U, 0x96969696U, 0x05050505U, 0x9a9a9a9aU, + 0x07070707U, 0x12121212U, 0x80808080U, 0xe2e2e2e2U, + 0xebebebebU, 0x27272727U, 0xb2b2b2b2U, 0x75757575U, + 0x09090909U, 0x83838383U, 0x2c2c2c2cU, 0x1a1a1a1aU, + 0x1b1b1b1bU, 0x6e6e6e6eU, 0x5a5a5a5aU, 0xa0a0a0a0U, + 0x52525252U, 0x3b3b3b3bU, 0xd6d6d6d6U, 0xb3b3b3b3U, + 0x29292929U, 0xe3e3e3e3U, 0x2f2f2f2fU, 0x84848484U, + 0x53535353U, 0xd1d1d1d1U, 0x00000000U, 0xededededU, + 0x20202020U, 0xfcfcfcfcU, 0xb1b1b1b1U, 0x5b5b5b5bU, + 0x6a6a6a6aU, 0xcbcbcbcbU, 0xbebebebeU, 0x39393939U, + 0x4a4a4a4aU, 0x4c4c4c4cU, 0x58585858U, 0xcfcfcfcfU, + 0xd0d0d0d0U, 0xefefefefU, 0xaaaaaaaaU, 0xfbfbfbfbU, + 0x43434343U, 0x4d4d4d4dU, 0x33333333U, 0x85858585U, + 0x45454545U, 0xf9f9f9f9U, 0x02020202U, 0x7f7f7f7fU, + 0x50505050U, 0x3c3c3c3cU, 0x9f9f9f9fU, 0xa8a8a8a8U, + 0x51515151U, 0xa3a3a3a3U, 0x40404040U, 0x8f8f8f8fU, + 0x92929292U, 0x9d9d9d9dU, 0x38383838U, 0xf5f5f5f5U, + 0xbcbcbcbcU, 0xb6b6b6b6U, 0xdadadadaU, 0x21212121U, + 0x10101010U, 0xffffffffU, 0xf3f3f3f3U, 0xd2d2d2d2U, + 0xcdcdcdcdU, 0x0c0c0c0cU, 0x13131313U, 0xececececU, + 0x5f5f5f5fU, 0x97979797U, 0x44444444U, 0x17171717U, + 0xc4c4c4c4U, 0xa7a7a7a7U, 0x7e7e7e7eU, 0x3d3d3d3dU, + 0x64646464U, 0x5d5d5d5dU, 0x19191919U, 0x73737373U, + 0x60606060U, 0x81818181U, 0x4f4f4f4fU, 0xdcdcdcdcU, + 0x22222222U, 0x2a2a2a2aU, 0x90909090U, 0x88888888U, + 0x46464646U, 0xeeeeeeeeU, 0xb8b8b8b8U, 0x14141414U, + 0xdedededeU, 0x5e5e5e5eU, 0x0b0b0b0bU, 0xdbdbdbdbU, + 0xe0e0e0e0U, 0x32323232U, 0x3a3a3a3aU, 0x0a0a0a0aU, + 0x49494949U, 0x06060606U, 0x24242424U, 0x5c5c5c5cU, + 0xc2c2c2c2U, 0xd3d3d3d3U, 0xacacacacU, 0x62626262U, + 0x91919191U, 0x95959595U, 0xe4e4e4e4U, 0x79797979U, + 0xe7e7e7e7U, 0xc8c8c8c8U, 0x37373737U, 0x6d6d6d6dU, + 0x8d8d8d8dU, 0xd5d5d5d5U, 0x4e4e4e4eU, 0xa9a9a9a9U, + 0x6c6c6c6cU, 0x56565656U, 0xf4f4f4f4U, 0xeaeaeaeaU, + 0x65656565U, 0x7a7a7a7aU, 0xaeaeaeaeU, 0x08080808U, + 0xbabababaU, 0x78787878U, 0x25252525U, 0x2e2e2e2eU, + 0x1c1c1c1cU, 0xa6a6a6a6U, 0xb4b4b4b4U, 0xc6c6c6c6U, + 0xe8e8e8e8U, 0xddddddddU, 0x74747474U, 0x1f1f1f1fU, + 0x4b4b4b4bU, 0xbdbdbdbdU, 0x8b8b8b8bU, 0x8a8a8a8aU, + 0x70707070U, 0x3e3e3e3eU, 0xb5b5b5b5U, 0x66666666U, + 0x48484848U, 0x03030303U, 0xf6f6f6f6U, 0x0e0e0e0eU, + 0x61616161U, 0x35353535U, 0x57575757U, 0xb9b9b9b9U, + 0x86868686U, 0xc1c1c1c1U, 0x1d1d1d1dU, 0x9e9e9e9eU, + 0xe1e1e1e1U, 0xf8f8f8f8U, 0x98989898U, 0x11111111U, + 0x69696969U, 0xd9d9d9d9U, 0x8e8e8e8eU, 0x94949494U, + 0x9b9b9b9bU, 0x1e1e1e1eU, 0x87878787U, 0xe9e9e9e9U, + 0xcecececeU, 0x55555555U, 0x28282828U, 0xdfdfdfdfU, + 0x8c8c8c8cU, 0xa1a1a1a1U, 0x89898989U, 0x0d0d0d0dU, + 0xbfbfbfbfU, 0xe6e6e6e6U, 0x42424242U, 0x68686868U, + 0x41414141U, 0x99999999U, 0x2d2d2d2dU, 0x0f0f0f0fU, + 0xb0b0b0b0U, 0x54545454U, 0xbbbbbbbbU, 0x16161616U, +}; +static const u32 Td0[256] = { + 0x51f4a750U, 0x7e416553U, 0x1a17a4c3U, 0x3a275e96U, + 0x3bab6bcbU, 0x1f9d45f1U, 0xacfa58abU, 0x4be30393U, + 0x2030fa55U, 0xad766df6U, 0x88cc7691U, 0xf5024c25U, + 0x4fe5d7fcU, 0xc52acbd7U, 0x26354480U, 0xb562a38fU, + 0xdeb15a49U, 0x25ba1b67U, 0x45ea0e98U, 0x5dfec0e1U, + 0xc32f7502U, 0x814cf012U, 0x8d4697a3U, 0x6bd3f9c6U, + 0x038f5fe7U, 0x15929c95U, 0xbf6d7aebU, 0x955259daU, + 0xd4be832dU, 0x587421d3U, 0x49e06929U, 0x8ec9c844U, + 0x75c2896aU, 0xf48e7978U, 0x99583e6bU, 0x27b971ddU, + 0xbee14fb6U, 0xf088ad17U, 0xc920ac66U, 0x7dce3ab4U, + 0x63df4a18U, 0xe51a3182U, 0x97513360U, 0x62537f45U, + 0xb16477e0U, 0xbb6bae84U, 0xfe81a01cU, 0xf9082b94U, + 0x70486858U, 0x8f45fd19U, 0x94de6c87U, 0x527bf8b7U, + 0xab73d323U, 0x724b02e2U, 0xe31f8f57U, 0x6655ab2aU, + 0xb2eb2807U, 0x2fb5c203U, 0x86c57b9aU, 0xd33708a5U, + 0x302887f2U, 0x23bfa5b2U, 0x02036abaU, 0xed16825cU, + 0x8acf1c2bU, 0xa779b492U, 0xf307f2f0U, 0x4e69e2a1U, + 0x65daf4cdU, 0x0605bed5U, 0xd134621fU, 0xc4a6fe8aU, + 0x342e539dU, 0xa2f355a0U, 0x058ae132U, 0xa4f6eb75U, + 0x0b83ec39U, 0x4060efaaU, 0x5e719f06U, 0xbd6e1051U, + 0x3e218af9U, 0x96dd063dU, 0xdd3e05aeU, 0x4de6bd46U, + 0x91548db5U, 0x71c45d05U, 0x0406d46fU, 0x605015ffU, + 0x1998fb24U, 0xd6bde997U, 0x894043ccU, 0x67d99e77U, + 0xb0e842bdU, 0x07898b88U, 0xe7195b38U, 0x79c8eedbU, + 0xa17c0a47U, 0x7c420fe9U, 0xf8841ec9U, 0x00000000U, + 0x09808683U, 0x322bed48U, 0x1e1170acU, 0x6c5a724eU, + 0xfd0efffbU, 0x0f853856U, 0x3daed51eU, 0x362d3927U, + 0x0a0fd964U, 0x685ca621U, 0x9b5b54d1U, 0x24362e3aU, + 0x0c0a67b1U, 0x9357e70fU, 0xb4ee96d2U, 0x1b9b919eU, + 0x80c0c54fU, 0x61dc20a2U, 0x5a774b69U, 0x1c121a16U, + 0xe293ba0aU, 0xc0a02ae5U, 0x3c22e043U, 0x121b171dU, + 0x0e090d0bU, 0xf28bc7adU, 0x2db6a8b9U, 0x141ea9c8U, + 0x57f11985U, 0xaf75074cU, 0xee99ddbbU, 0xa37f60fdU, + 0xf701269fU, 0x5c72f5bcU, 0x44663bc5U, 0x5bfb7e34U, + 0x8b432976U, 0xcb23c6dcU, 0xb6edfc68U, 0xb8e4f163U, + 0xd731dccaU, 0x42638510U, 0x13972240U, 0x84c61120U, + 0x854a247dU, 0xd2bb3df8U, 0xaef93211U, 0xc729a16dU, + 0x1d9e2f4bU, 0xdcb230f3U, 0x0d8652ecU, 0x77c1e3d0U, + 0x2bb3166cU, 0xa970b999U, 0x119448faU, 0x47e96422U, + 0xa8fc8cc4U, 0xa0f03f1aU, 0x567d2cd8U, 0x223390efU, + 0x87494ec7U, 0xd938d1c1U, 0x8ccaa2feU, 0x98d40b36U, + 0xa6f581cfU, 0xa57ade28U, 0xdab78e26U, 0x3fadbfa4U, + 0x2c3a9de4U, 0x5078920dU, 0x6a5fcc9bU, 0x547e4662U, + 0xf68d13c2U, 0x90d8b8e8U, 0x2e39f75eU, 0x82c3aff5U, + 0x9f5d80beU, 0x69d0937cU, 0x6fd52da9U, 0xcf2512b3U, + 0xc8ac993bU, 0x10187da7U, 0xe89c636eU, 0xdb3bbb7bU, + 0xcd267809U, 0x6e5918f4U, 0xec9ab701U, 0x834f9aa8U, + 0xe6956e65U, 0xaaffe67eU, 0x21bccf08U, 0xef15e8e6U, + 0xbae79bd9U, 0x4a6f36ceU, 0xea9f09d4U, 0x29b07cd6U, + 0x31a4b2afU, 0x2a3f2331U, 0xc6a59430U, 0x35a266c0U, + 0x744ebc37U, 0xfc82caa6U, 0xe090d0b0U, 0x33a7d815U, + 0xf104984aU, 0x41ecdaf7U, 0x7fcd500eU, 0x1791f62fU, + 0x764dd68dU, 0x43efb04dU, 0xccaa4d54U, 0xe49604dfU, + 0x9ed1b5e3U, 0x4c6a881bU, 0xc12c1fb8U, 0x4665517fU, + 0x9d5eea04U, 0x018c355dU, 0xfa877473U, 0xfb0b412eU, + 0xb3671d5aU, 0x92dbd252U, 0xe9105633U, 0x6dd64713U, + 0x9ad7618cU, 0x37a10c7aU, 0x59f8148eU, 0xeb133c89U, + 0xcea927eeU, 0xb761c935U, 0xe11ce5edU, 0x7a47b13cU, + 0x9cd2df59U, 0x55f2733fU, 0x1814ce79U, 0x73c737bfU, + 0x53f7cdeaU, 0x5ffdaa5bU, 0xdf3d6f14U, 0x7844db86U, + 0xcaaff381U, 0xb968c43eU, 0x3824342cU, 0xc2a3405fU, + 0x161dc372U, 0xbce2250cU, 0x283c498bU, 0xff0d9541U, + 0x39a80171U, 0x080cb3deU, 0xd8b4e49cU, 0x6456c190U, + 0x7bcb8461U, 0xd532b670U, 0x486c5c74U, 0xd0b85742U, +}; +static const u32 Td1[256] = { + 0x5051f4a7U, 0x537e4165U, 0xc31a17a4U, 0x963a275eU, + 0xcb3bab6bU, 0xf11f9d45U, 0xabacfa58U, 0x934be303U, + 0x552030faU, 0xf6ad766dU, 0x9188cc76U, 0x25f5024cU, + 0xfc4fe5d7U, 0xd7c52acbU, 0x80263544U, 0x8fb562a3U, + 0x49deb15aU, 0x6725ba1bU, 0x9845ea0eU, 0xe15dfec0U, + 0x02c32f75U, 0x12814cf0U, 0xa38d4697U, 0xc66bd3f9U, + 0xe7038f5fU, 0x9515929cU, 0xebbf6d7aU, 0xda955259U, + 0x2dd4be83U, 0xd3587421U, 0x2949e069U, 0x448ec9c8U, + 0x6a75c289U, 0x78f48e79U, 0x6b99583eU, 0xdd27b971U, + 0xb6bee14fU, 0x17f088adU, 0x66c920acU, 0xb47dce3aU, + 0x1863df4aU, 0x82e51a31U, 0x60975133U, 0x4562537fU, + 0xe0b16477U, 0x84bb6baeU, 0x1cfe81a0U, 0x94f9082bU, + 0x58704868U, 0x198f45fdU, 0x8794de6cU, 0xb7527bf8U, + 0x23ab73d3U, 0xe2724b02U, 0x57e31f8fU, 0x2a6655abU, + 0x07b2eb28U, 0x032fb5c2U, 0x9a86c57bU, 0xa5d33708U, + 0xf2302887U, 0xb223bfa5U, 0xba02036aU, 0x5ced1682U, + 0x2b8acf1cU, 0x92a779b4U, 0xf0f307f2U, 0xa14e69e2U, + 0xcd65daf4U, 0xd50605beU, 0x1fd13462U, 0x8ac4a6feU, + 0x9d342e53U, 0xa0a2f355U, 0x32058ae1U, 0x75a4f6ebU, + 0x390b83ecU, 0xaa4060efU, 0x065e719fU, 0x51bd6e10U, + 0xf93e218aU, 0x3d96dd06U, 0xaedd3e05U, 0x464de6bdU, + 0xb591548dU, 0x0571c45dU, 0x6f0406d4U, 0xff605015U, + 0x241998fbU, 0x97d6bde9U, 0xcc894043U, 0x7767d99eU, + 0xbdb0e842U, 0x8807898bU, 0x38e7195bU, 0xdb79c8eeU, + 0x47a17c0aU, 0xe97c420fU, 0xc9f8841eU, 0x00000000U, + 0x83098086U, 0x48322bedU, 0xac1e1170U, 0x4e6c5a72U, + 0xfbfd0effU, 0x560f8538U, 0x1e3daed5U, 0x27362d39U, + 0x640a0fd9U, 0x21685ca6U, 0xd19b5b54U, 0x3a24362eU, + 0xb10c0a67U, 0x0f9357e7U, 0xd2b4ee96U, 0x9e1b9b91U, + 0x4f80c0c5U, 0xa261dc20U, 0x695a774bU, 0x161c121aU, + 0x0ae293baU, 0xe5c0a02aU, 0x433c22e0U, 0x1d121b17U, + 0x0b0e090dU, 0xadf28bc7U, 0xb92db6a8U, 0xc8141ea9U, + 0x8557f119U, 0x4caf7507U, 0xbbee99ddU, 0xfda37f60U, + 0x9ff70126U, 0xbc5c72f5U, 0xc544663bU, 0x345bfb7eU, + 0x768b4329U, 0xdccb23c6U, 0x68b6edfcU, 0x63b8e4f1U, + 0xcad731dcU, 0x10426385U, 0x40139722U, 0x2084c611U, + 0x7d854a24U, 0xf8d2bb3dU, 0x11aef932U, 0x6dc729a1U, + 0x4b1d9e2fU, 0xf3dcb230U, 0xec0d8652U, 0xd077c1e3U, + 0x6c2bb316U, 0x99a970b9U, 0xfa119448U, 0x2247e964U, + 0xc4a8fc8cU, 0x1aa0f03fU, 0xd8567d2cU, 0xef223390U, + 0xc787494eU, 0xc1d938d1U, 0xfe8ccaa2U, 0x3698d40bU, + 0xcfa6f581U, 0x28a57adeU, 0x26dab78eU, 0xa43fadbfU, + 0xe42c3a9dU, 0x0d507892U, 0x9b6a5fccU, 0x62547e46U, + 0xc2f68d13U, 0xe890d8b8U, 0x5e2e39f7U, 0xf582c3afU, + 0xbe9f5d80U, 0x7c69d093U, 0xa96fd52dU, 0xb3cf2512U, + 0x3bc8ac99U, 0xa710187dU, 0x6ee89c63U, 0x7bdb3bbbU, + 0x09cd2678U, 0xf46e5918U, 0x01ec9ab7U, 0xa8834f9aU, + 0x65e6956eU, 0x7eaaffe6U, 0x0821bccfU, 0xe6ef15e8U, + 0xd9bae79bU, 0xce4a6f36U, 0xd4ea9f09U, 0xd629b07cU, + 0xaf31a4b2U, 0x312a3f23U, 0x30c6a594U, 0xc035a266U, + 0x37744ebcU, 0xa6fc82caU, 0xb0e090d0U, 0x1533a7d8U, + 0x4af10498U, 0xf741ecdaU, 0x0e7fcd50U, 0x2f1791f6U, + 0x8d764dd6U, 0x4d43efb0U, 0x54ccaa4dU, 0xdfe49604U, + 0xe39ed1b5U, 0x1b4c6a88U, 0xb8c12c1fU, 0x7f466551U, + 0x049d5eeaU, 0x5d018c35U, 0x73fa8774U, 0x2efb0b41U, + 0x5ab3671dU, 0x5292dbd2U, 0x33e91056U, 0x136dd647U, + 0x8c9ad761U, 0x7a37a10cU, 0x8e59f814U, 0x89eb133cU, + 0xeecea927U, 0x35b761c9U, 0xede11ce5U, 0x3c7a47b1U, + 0x599cd2dfU, 0x3f55f273U, 0x791814ceU, 0xbf73c737U, + 0xea53f7cdU, 0x5b5ffdaaU, 0x14df3d6fU, 0x867844dbU, + 0x81caaff3U, 0x3eb968c4U, 0x2c382434U, 0x5fc2a340U, + 0x72161dc3U, 0x0cbce225U, 0x8b283c49U, 0x41ff0d95U, + 0x7139a801U, 0xde080cb3U, 0x9cd8b4e4U, 0x906456c1U, + 0x617bcb84U, 0x70d532b6U, 0x74486c5cU, 0x42d0b857U, +}; +static const u32 Td2[256] = { + 0xa75051f4U, 0x65537e41U, 0xa4c31a17U, 0x5e963a27U, + 0x6bcb3babU, 0x45f11f9dU, 0x58abacfaU, 0x03934be3U, + 0xfa552030U, 0x6df6ad76U, 0x769188ccU, 0x4c25f502U, + 0xd7fc4fe5U, 0xcbd7c52aU, 0x44802635U, 0xa38fb562U, + 0x5a49deb1U, 0x1b6725baU, 0x0e9845eaU, 0xc0e15dfeU, + 0x7502c32fU, 0xf012814cU, 0x97a38d46U, 0xf9c66bd3U, + 0x5fe7038fU, 0x9c951592U, 0x7aebbf6dU, 0x59da9552U, + 0x832dd4beU, 0x21d35874U, 0x692949e0U, 0xc8448ec9U, + 0x896a75c2U, 0x7978f48eU, 0x3e6b9958U, 0x71dd27b9U, + 0x4fb6bee1U, 0xad17f088U, 0xac66c920U, 0x3ab47dceU, + 0x4a1863dfU, 0x3182e51aU, 0x33609751U, 0x7f456253U, + 0x77e0b164U, 0xae84bb6bU, 0xa01cfe81U, 0x2b94f908U, + 0x68587048U, 0xfd198f45U, 0x6c8794deU, 0xf8b7527bU, + 0xd323ab73U, 0x02e2724bU, 0x8f57e31fU, 0xab2a6655U, + 0x2807b2ebU, 0xc2032fb5U, 0x7b9a86c5U, 0x08a5d337U, + 0x87f23028U, 0xa5b223bfU, 0x6aba0203U, 0x825ced16U, + 0x1c2b8acfU, 0xb492a779U, 0xf2f0f307U, 0xe2a14e69U, + 0xf4cd65daU, 0xbed50605U, 0x621fd134U, 0xfe8ac4a6U, + 0x539d342eU, 0x55a0a2f3U, 0xe132058aU, 0xeb75a4f6U, + 0xec390b83U, 0xefaa4060U, 0x9f065e71U, 0x1051bd6eU, + + 0x8af93e21U, 0x063d96ddU, 0x05aedd3eU, 0xbd464de6U, + 0x8db59154U, 0x5d0571c4U, 0xd46f0406U, 0x15ff6050U, + 0xfb241998U, 0xe997d6bdU, 0x43cc8940U, 0x9e7767d9U, + 0x42bdb0e8U, 0x8b880789U, 0x5b38e719U, 0xeedb79c8U, + 0x0a47a17cU, 0x0fe97c42U, 0x1ec9f884U, 0x00000000U, + 0x86830980U, 0xed48322bU, 0x70ac1e11U, 0x724e6c5aU, + 0xfffbfd0eU, 0x38560f85U, 0xd51e3daeU, 0x3927362dU, + 0xd9640a0fU, 0xa621685cU, 0x54d19b5bU, 0x2e3a2436U, + 0x67b10c0aU, 0xe70f9357U, 0x96d2b4eeU, 0x919e1b9bU, + 0xc54f80c0U, 0x20a261dcU, 0x4b695a77U, 0x1a161c12U, + 0xba0ae293U, 0x2ae5c0a0U, 0xe0433c22U, 0x171d121bU, + 0x0d0b0e09U, 0xc7adf28bU, 0xa8b92db6U, 0xa9c8141eU, + 0x198557f1U, 0x074caf75U, 0xddbbee99U, 0x60fda37fU, + 0x269ff701U, 0xf5bc5c72U, 0x3bc54466U, 0x7e345bfbU, + 0x29768b43U, 0xc6dccb23U, 0xfc68b6edU, 0xf163b8e4U, + 0xdccad731U, 0x85104263U, 0x22401397U, 0x112084c6U, + 0x247d854aU, 0x3df8d2bbU, 0x3211aef9U, 0xa16dc729U, + 0x2f4b1d9eU, 0x30f3dcb2U, 0x52ec0d86U, 0xe3d077c1U, + 0x166c2bb3U, 0xb999a970U, 0x48fa1194U, 0x642247e9U, + 0x8cc4a8fcU, 0x3f1aa0f0U, 0x2cd8567dU, 0x90ef2233U, + 0x4ec78749U, 0xd1c1d938U, 0xa2fe8ccaU, 0x0b3698d4U, + 0x81cfa6f5U, 0xde28a57aU, 0x8e26dab7U, 0xbfa43fadU, + 0x9de42c3aU, 0x920d5078U, 0xcc9b6a5fU, 0x4662547eU, + 0x13c2f68dU, 0xb8e890d8U, 0xf75e2e39U, 0xaff582c3U, + 0x80be9f5dU, 0x937c69d0U, 0x2da96fd5U, 0x12b3cf25U, + 0x993bc8acU, 0x7da71018U, 0x636ee89cU, 0xbb7bdb3bU, + 0x7809cd26U, 0x18f46e59U, 0xb701ec9aU, 0x9aa8834fU, + 0x6e65e695U, 0xe67eaaffU, 0xcf0821bcU, 0xe8e6ef15U, + 0x9bd9bae7U, 0x36ce4a6fU, 0x09d4ea9fU, 0x7cd629b0U, + 0xb2af31a4U, 0x23312a3fU, 0x9430c6a5U, 0x66c035a2U, + 0xbc37744eU, 0xcaa6fc82U, 0xd0b0e090U, 0xd81533a7U, + 0x984af104U, 0xdaf741ecU, 0x500e7fcdU, 0xf62f1791U, + 0xd68d764dU, 0xb04d43efU, 0x4d54ccaaU, 0x04dfe496U, + 0xb5e39ed1U, 0x881b4c6aU, 0x1fb8c12cU, 0x517f4665U, + 0xea049d5eU, 0x355d018cU, 0x7473fa87U, 0x412efb0bU, + 0x1d5ab367U, 0xd25292dbU, 0x5633e910U, 0x47136dd6U, + 0x618c9ad7U, 0x0c7a37a1U, 0x148e59f8U, 0x3c89eb13U, + 0x27eecea9U, 0xc935b761U, 0xe5ede11cU, 0xb13c7a47U, + 0xdf599cd2U, 0x733f55f2U, 0xce791814U, 0x37bf73c7U, + 0xcdea53f7U, 0xaa5b5ffdU, 0x6f14df3dU, 0xdb867844U, + 0xf381caafU, 0xc43eb968U, 0x342c3824U, 0x405fc2a3U, + 0xc372161dU, 0x250cbce2U, 0x498b283cU, 0x9541ff0dU, + 0x017139a8U, 0xb3de080cU, 0xe49cd8b4U, 0xc1906456U, + 0x84617bcbU, 0xb670d532U, 0x5c74486cU, 0x5742d0b8U, +}; +static const u32 Td3[256] = { + 0xf4a75051U, 0x4165537eU, 0x17a4c31aU, 0x275e963aU, + 0xab6bcb3bU, 0x9d45f11fU, 0xfa58abacU, 0xe303934bU, + 0x30fa5520U, 0x766df6adU, 0xcc769188U, 0x024c25f5U, + 0xe5d7fc4fU, 0x2acbd7c5U, 0x35448026U, 0x62a38fb5U, + 0xb15a49deU, 0xba1b6725U, 0xea0e9845U, 0xfec0e15dU, + 0x2f7502c3U, 0x4cf01281U, 0x4697a38dU, 0xd3f9c66bU, + 0x8f5fe703U, 0x929c9515U, 0x6d7aebbfU, 0x5259da95U, + 0xbe832dd4U, 0x7421d358U, 0xe0692949U, 0xc9c8448eU, + 0xc2896a75U, 0x8e7978f4U, 0x583e6b99U, 0xb971dd27U, + 0xe14fb6beU, 0x88ad17f0U, 0x20ac66c9U, 0xce3ab47dU, + 0xdf4a1863U, 0x1a3182e5U, 0x51336097U, 0x537f4562U, + 0x6477e0b1U, 0x6bae84bbU, 0x81a01cfeU, 0x082b94f9U, + 0x48685870U, 0x45fd198fU, 0xde6c8794U, 0x7bf8b752U, + 0x73d323abU, 0x4b02e272U, 0x1f8f57e3U, 0x55ab2a66U, + 0xeb2807b2U, 0xb5c2032fU, 0xc57b9a86U, 0x3708a5d3U, + 0x2887f230U, 0xbfa5b223U, 0x036aba02U, 0x16825cedU, + 0xcf1c2b8aU, 0x79b492a7U, 0x07f2f0f3U, 0x69e2a14eU, + 0xdaf4cd65U, 0x05bed506U, 0x34621fd1U, 0xa6fe8ac4U, + 0x2e539d34U, 0xf355a0a2U, 0x8ae13205U, 0xf6eb75a4U, + 0x83ec390bU, 0x60efaa40U, 0x719f065eU, 0x6e1051bdU, + 0x218af93eU, 0xdd063d96U, 0x3e05aeddU, 0xe6bd464dU, + 0x548db591U, 0xc45d0571U, 0x06d46f04U, 0x5015ff60U, + 0x98fb2419U, 0xbde997d6U, 0x4043cc89U, 0xd99e7767U, + 0xe842bdb0U, 0x898b8807U, 0x195b38e7U, 0xc8eedb79U, + 0x7c0a47a1U, 0x420fe97cU, 0x841ec9f8U, 0x00000000U, + 0x80868309U, 0x2bed4832U, 0x1170ac1eU, 0x5a724e6cU, + 0x0efffbfdU, 0x8538560fU, 0xaed51e3dU, 0x2d392736U, + 0x0fd9640aU, 0x5ca62168U, 0x5b54d19bU, 0x362e3a24U, + 0x0a67b10cU, 0x57e70f93U, 0xee96d2b4U, 0x9b919e1bU, + 0xc0c54f80U, 0xdc20a261U, 0x774b695aU, 0x121a161cU, + 0x93ba0ae2U, 0xa02ae5c0U, 0x22e0433cU, 0x1b171d12U, + 0x090d0b0eU, 0x8bc7adf2U, 0xb6a8b92dU, 0x1ea9c814U, + 0xf1198557U, 0x75074cafU, 0x99ddbbeeU, 0x7f60fda3U, + 0x01269ff7U, 0x72f5bc5cU, 0x663bc544U, 0xfb7e345bU, + 0x4329768bU, 0x23c6dccbU, 0xedfc68b6U, 0xe4f163b8U, + 0x31dccad7U, 0x63851042U, 0x97224013U, 0xc6112084U, + 0x4a247d85U, 0xbb3df8d2U, 0xf93211aeU, 0x29a16dc7U, + 0x9e2f4b1dU, 0xb230f3dcU, 0x8652ec0dU, 0xc1e3d077U, + 0xb3166c2bU, 0x70b999a9U, 0x9448fa11U, 0xe9642247U, + 0xfc8cc4a8U, 0xf03f1aa0U, 0x7d2cd856U, 0x3390ef22U, + 0x494ec787U, 0x38d1c1d9U, 0xcaa2fe8cU, 0xd40b3698U, + 0xf581cfa6U, 0x7ade28a5U, 0xb78e26daU, 0xadbfa43fU, + 0x3a9de42cU, 0x78920d50U, 0x5fcc9b6aU, 0x7e466254U, + 0x8d13c2f6U, 0xd8b8e890U, 0x39f75e2eU, 0xc3aff582U, + 0x5d80be9fU, 0xd0937c69U, 0xd52da96fU, 0x2512b3cfU, + 0xac993bc8U, 0x187da710U, 0x9c636ee8U, 0x3bbb7bdbU, + 0x267809cdU, 0x5918f46eU, 0x9ab701ecU, 0x4f9aa883U, + 0x956e65e6U, 0xffe67eaaU, 0xbccf0821U, 0x15e8e6efU, + 0xe79bd9baU, 0x6f36ce4aU, 0x9f09d4eaU, 0xb07cd629U, + 0xa4b2af31U, 0x3f23312aU, 0xa59430c6U, 0xa266c035U, + 0x4ebc3774U, 0x82caa6fcU, 0x90d0b0e0U, 0xa7d81533U, + 0x04984af1U, 0xecdaf741U, 0xcd500e7fU, 0x91f62f17U, + 0x4dd68d76U, 0xefb04d43U, 0xaa4d54ccU, 0x9604dfe4U, + 0xd1b5e39eU, 0x6a881b4cU, 0x2c1fb8c1U, 0x65517f46U, + 0x5eea049dU, 0x8c355d01U, 0x877473faU, 0x0b412efbU, + 0x671d5ab3U, 0xdbd25292U, 0x105633e9U, 0xd647136dU, + 0xd7618c9aU, 0xa10c7a37U, 0xf8148e59U, 0x133c89ebU, + 0xa927eeceU, 0x61c935b7U, 0x1ce5ede1U, 0x47b13c7aU, + 0xd2df599cU, 0xf2733f55U, 0x14ce7918U, 0xc737bf73U, + 0xf7cdea53U, 0xfdaa5b5fU, 0x3d6f14dfU, 0x44db8678U, + 0xaff381caU, 0x68c43eb9U, 0x24342c38U, 0xa3405fc2U, + 0x1dc37216U, 0xe2250cbcU, 0x3c498b28U, 0x0d9541ffU, + 0xa8017139U, 0x0cb3de08U, 0xb4e49cd8U, 0x56c19064U, + 0xcb84617bU, 0x32b670d5U, 0x6c5c7448U, 0xb85742d0U, +}; +static const u32 Td4[256] = { + 0x52525252U, 0x09090909U, 0x6a6a6a6aU, 0xd5d5d5d5U, + 0x30303030U, 0x36363636U, 0xa5a5a5a5U, 0x38383838U, + 0xbfbfbfbfU, 0x40404040U, 0xa3a3a3a3U, 0x9e9e9e9eU, + 0x81818181U, 0xf3f3f3f3U, 0xd7d7d7d7U, 0xfbfbfbfbU, + 0x7c7c7c7cU, 0xe3e3e3e3U, 0x39393939U, 0x82828282U, + 0x9b9b9b9bU, 0x2f2f2f2fU, 0xffffffffU, 0x87878787U, + 0x34343434U, 0x8e8e8e8eU, 0x43434343U, 0x44444444U, + 0xc4c4c4c4U, 0xdedededeU, 0xe9e9e9e9U, 0xcbcbcbcbU, + 0x54545454U, 0x7b7b7b7bU, 0x94949494U, 0x32323232U, + 0xa6a6a6a6U, 0xc2c2c2c2U, 0x23232323U, 0x3d3d3d3dU, + 0xeeeeeeeeU, 0x4c4c4c4cU, 0x95959595U, 0x0b0b0b0bU, + 0x42424242U, 0xfafafafaU, 0xc3c3c3c3U, 0x4e4e4e4eU, + 0x08080808U, 0x2e2e2e2eU, 0xa1a1a1a1U, 0x66666666U, + 0x28282828U, 0xd9d9d9d9U, 0x24242424U, 0xb2b2b2b2U, + 0x76767676U, 0x5b5b5b5bU, 0xa2a2a2a2U, 0x49494949U, + 0x6d6d6d6dU, 0x8b8b8b8bU, 0xd1d1d1d1U, 0x25252525U, + 0x72727272U, 0xf8f8f8f8U, 0xf6f6f6f6U, 0x64646464U, + 0x86868686U, 0x68686868U, 0x98989898U, 0x16161616U, + 0xd4d4d4d4U, 0xa4a4a4a4U, 0x5c5c5c5cU, 0xccccccccU, + 0x5d5d5d5dU, 0x65656565U, 0xb6b6b6b6U, 0x92929292U, + 0x6c6c6c6cU, 0x70707070U, 0x48484848U, 0x50505050U, + 0xfdfdfdfdU, 0xededededU, 0xb9b9b9b9U, 0xdadadadaU, + 0x5e5e5e5eU, 0x15151515U, 0x46464646U, 0x57575757U, + 0xa7a7a7a7U, 0x8d8d8d8dU, 0x9d9d9d9dU, 0x84848484U, + 0x90909090U, 0xd8d8d8d8U, 0xababababU, 0x00000000U, + 0x8c8c8c8cU, 0xbcbcbcbcU, 0xd3d3d3d3U, 0x0a0a0a0aU, + 0xf7f7f7f7U, 0xe4e4e4e4U, 0x58585858U, 0x05050505U, + 0xb8b8b8b8U, 0xb3b3b3b3U, 0x45454545U, 0x06060606U, + 0xd0d0d0d0U, 0x2c2c2c2cU, 0x1e1e1e1eU, 0x8f8f8f8fU, + 0xcacacacaU, 0x3f3f3f3fU, 0x0f0f0f0fU, 0x02020202U, + 0xc1c1c1c1U, 0xafafafafU, 0xbdbdbdbdU, 0x03030303U, + 0x01010101U, 0x13131313U, 0x8a8a8a8aU, 0x6b6b6b6bU, + 0x3a3a3a3aU, 0x91919191U, 0x11111111U, 0x41414141U, + 0x4f4f4f4fU, 0x67676767U, 0xdcdcdcdcU, 0xeaeaeaeaU, + 0x97979797U, 0xf2f2f2f2U, 0xcfcfcfcfU, 0xcecececeU, + 0xf0f0f0f0U, 0xb4b4b4b4U, 0xe6e6e6e6U, 0x73737373U, + 0x96969696U, 0xacacacacU, 0x74747474U, 0x22222222U, + 0xe7e7e7e7U, 0xadadadadU, 0x35353535U, 0x85858585U, + 0xe2e2e2e2U, 0xf9f9f9f9U, 0x37373737U, 0xe8e8e8e8U, + 0x1c1c1c1cU, 0x75757575U, 0xdfdfdfdfU, 0x6e6e6e6eU, + 0x47474747U, 0xf1f1f1f1U, 0x1a1a1a1aU, 0x71717171U, + 0x1d1d1d1dU, 0x29292929U, 0xc5c5c5c5U, 0x89898989U, + 0x6f6f6f6fU, 0xb7b7b7b7U, 0x62626262U, 0x0e0e0e0eU, + 0xaaaaaaaaU, 0x18181818U, 0xbebebebeU, 0x1b1b1b1bU, + 0xfcfcfcfcU, 0x56565656U, 0x3e3e3e3eU, 0x4b4b4b4bU, + 0xc6c6c6c6U, 0xd2d2d2d2U, 0x79797979U, 0x20202020U, + 0x9a9a9a9aU, 0xdbdbdbdbU, 0xc0c0c0c0U, 0xfefefefeU, + 0x78787878U, 0xcdcdcdcdU, 0x5a5a5a5aU, 0xf4f4f4f4U, + 0x1f1f1f1fU, 0xddddddddU, 0xa8a8a8a8U, 0x33333333U, + 0x88888888U, 0x07070707U, 0xc7c7c7c7U, 0x31313131U, + 0xb1b1b1b1U, 0x12121212U, 0x10101010U, 0x59595959U, + 0x27272727U, 0x80808080U, 0xececececU, 0x5f5f5f5fU, + 0x60606060U, 0x51515151U, 0x7f7f7f7fU, 0xa9a9a9a9U, + 0x19191919U, 0xb5b5b5b5U, 0x4a4a4a4aU, 0x0d0d0d0dU, + 0x2d2d2d2dU, 0xe5e5e5e5U, 0x7a7a7a7aU, 0x9f9f9f9fU, + 0x93939393U, 0xc9c9c9c9U, 0x9c9c9c9cU, 0xefefefefU, + 0xa0a0a0a0U, 0xe0e0e0e0U, 0x3b3b3b3bU, 0x4d4d4d4dU, + 0xaeaeaeaeU, 0x2a2a2a2aU, 0xf5f5f5f5U, 0xb0b0b0b0U, + 0xc8c8c8c8U, 0xebebebebU, 0xbbbbbbbbU, 0x3c3c3c3cU, + 0x83838383U, 0x53535353U, 0x99999999U, 0x61616161U, + 0x17171717U, 0x2b2b2b2bU, 0x04040404U, 0x7e7e7e7eU, + 0xbabababaU, 0x77777777U, 0xd6d6d6d6U, 0x26262626U, + 0xe1e1e1e1U, 0x69696969U, 0x14141414U, 0x63636363U, + 0x55555555U, 0x21212121U, 0x0c0c0c0cU, 0x7d7d7d7dU, +}; +static const u32 rcon[] = { + 0x01000000, 0x02000000, 0x04000000, 0x08000000, + 0x10000000, 0x20000000, 0x40000000, 0x80000000, + 0x1B000000, 0x36000000, /* for 128-bit blocks, Rijndael never uses more than 10 rcon values */ +}; + +/** + * Expand the cipher key into the encryption key schedule. + */ +int AES_set_encrypt_key(const unsigned char *userKey, const int bits, + AES_KEY *key) { + + u32 *rk; + int i = 0; + u32 temp; + + if (!userKey || !key) + return -1; + if (bits != 128 && bits != 192 && bits != 256) + return -2; + + rk = key->rd_key; + + if (bits==128) + key->rounds = 10; + else if (bits==192) + key->rounds = 12; + else + key->rounds = 14; + + rk[0] = GETU32(userKey ); + rk[1] = GETU32(userKey + 4); + rk[2] = GETU32(userKey + 8); + rk[3] = GETU32(userKey + 12); + if (bits == 128) { + while (1) { + temp = rk[3]; + rk[4] = rk[0] ^ + (Te4[(temp >> 16) & 0xff] & 0xff000000) ^ + (Te4[(temp >> 8) & 0xff] & 0x00ff0000) ^ + (Te4[(temp ) & 0xff] & 0x0000ff00) ^ + (Te4[(temp >> 24) ] & 0x000000ff) ^ + rcon[i]; + rk[5] = rk[1] ^ rk[4]; + rk[6] = rk[2] ^ rk[5]; + rk[7] = rk[3] ^ rk[6]; + if (++i == 10) { + return 0; + } + rk += 4; + } + } + rk[4] = GETU32(userKey + 16); + rk[5] = GETU32(userKey + 20); + if (bits == 192) { + while (1) { + temp = rk[ 5]; + rk[ 6] = rk[ 0] ^ + (Te4[(temp >> 16) & 0xff] & 0xff000000) ^ + (Te4[(temp >> 8) & 0xff] & 0x00ff0000) ^ + (Te4[(temp ) & 0xff] & 0x0000ff00) ^ + (Te4[(temp >> 24) ] & 0x000000ff) ^ + rcon[i]; + rk[ 7] = rk[ 1] ^ rk[ 6]; + rk[ 8] = rk[ 2] ^ rk[ 7]; + rk[ 9] = rk[ 3] ^ rk[ 8]; + if (++i == 8) { + return 0; + } + rk[10] = rk[ 4] ^ rk[ 9]; + rk[11] = rk[ 5] ^ rk[10]; + rk += 6; + } + } + rk[6] = GETU32(userKey + 24); + rk[7] = GETU32(userKey + 28); + if (bits == 256) { + while (1) { + temp = rk[ 7]; + rk[ 8] = rk[ 0] ^ + (Te4[(temp >> 16) & 0xff] & 0xff000000) ^ + (Te4[(temp >> 8) & 0xff] & 0x00ff0000) ^ + (Te4[(temp ) & 0xff] & 0x0000ff00) ^ + (Te4[(temp >> 24) ] & 0x000000ff) ^ + rcon[i]; + rk[ 9] = rk[ 1] ^ rk[ 8]; + rk[10] = rk[ 2] ^ rk[ 9]; + rk[11] = rk[ 3] ^ rk[10]; + if (++i == 7) { + return 0; + } + temp = rk[11]; + rk[12] = rk[ 4] ^ + (Te4[(temp >> 24) ] & 0xff000000) ^ + (Te4[(temp >> 16) & 0xff] & 0x00ff0000) ^ + (Te4[(temp >> 8) & 0xff] & 0x0000ff00) ^ + (Te4[(temp ) & 0xff] & 0x000000ff); + rk[13] = rk[ 5] ^ rk[12]; + rk[14] = rk[ 6] ^ rk[13]; + rk[15] = rk[ 7] ^ rk[14]; + + rk += 8; + } + } + return 0; +} + +/** + * Expand the cipher key into the decryption key schedule. + */ +int AES_set_decrypt_key(const unsigned char *userKey, const int bits, + AES_KEY *key) { + + u32 *rk; + int i, j, status; + u32 temp; + + /* first, start with an encryption schedule */ + status = AES_set_encrypt_key(userKey, bits, key); + if (status < 0) + return status; + + rk = key->rd_key; + + /* invert the order of the round keys: */ + for (i = 0, j = 4*(key->rounds); i < j; i += 4, j -= 4) { + temp = rk[i ]; rk[i ] = rk[j ]; rk[j ] = temp; + temp = rk[i + 1]; rk[i + 1] = rk[j + 1]; rk[j + 1] = temp; + temp = rk[i + 2]; rk[i + 2] = rk[j + 2]; rk[j + 2] = temp; + temp = rk[i + 3]; rk[i + 3] = rk[j + 3]; rk[j + 3] = temp; + } + /* apply the inverse MixColumn transform to all round keys but the first and the last: */ + for (i = 1; i < (key->rounds); i++) { + rk += 4; + rk[0] = + Td0[Te4[(rk[0] >> 24) ] & 0xff] ^ + Td1[Te4[(rk[0] >> 16) & 0xff] & 0xff] ^ + Td2[Te4[(rk[0] >> 8) & 0xff] & 0xff] ^ + Td3[Te4[(rk[0] ) & 0xff] & 0xff]; + rk[1] = + Td0[Te4[(rk[1] >> 24) ] & 0xff] ^ + Td1[Te4[(rk[1] >> 16) & 0xff] & 0xff] ^ + Td2[Te4[(rk[1] >> 8) & 0xff] & 0xff] ^ + Td3[Te4[(rk[1] ) & 0xff] & 0xff]; + rk[2] = + Td0[Te4[(rk[2] >> 24) ] & 0xff] ^ + Td1[Te4[(rk[2] >> 16) & 0xff] & 0xff] ^ + Td2[Te4[(rk[2] >> 8) & 0xff] & 0xff] ^ + Td3[Te4[(rk[2] ) & 0xff] & 0xff]; + rk[3] = + Td0[Te4[(rk[3] >> 24) ] & 0xff] ^ + Td1[Te4[(rk[3] >> 16) & 0xff] & 0xff] ^ + Td2[Te4[(rk[3] >> 8) & 0xff] & 0xff] ^ + Td3[Te4[(rk[3] ) & 0xff] & 0xff]; + } + return 0; +} + +#ifndef AES_ASM +/* + * Encrypt a single block + * in and out can overlap + */ +void AES_encrypt(const unsigned char *in, unsigned char *out, + const AES_KEY *key) { + + const u32 *rk; + u32 s0, s1, s2, s3, t0, t1, t2, t3; +#ifndef FULL_UNROLL + int r; +#endif /* ?FULL_UNROLL */ + + assert(in && out && key); + rk = key->rd_key; + + /* + * map byte array block to cipher state + * and add initial round key: + */ + s0 = GETU32(in ) ^ rk[0]; + s1 = GETU32(in + 4) ^ rk[1]; + s2 = GETU32(in + 8) ^ rk[2]; + s3 = GETU32(in + 12) ^ rk[3]; +#ifdef FULL_UNROLL + /* round 1: */ + t0 = Te0[s0 >> 24] ^ Te1[(s1 >> 16) & 0xff] ^ Te2[(s2 >> 8) & 0xff] ^ Te3[s3 & 0xff] ^ rk[ 4]; + t1 = Te0[s1 >> 24] ^ Te1[(s2 >> 16) & 0xff] ^ Te2[(s3 >> 8) & 0xff] ^ Te3[s0 & 0xff] ^ rk[ 5]; + t2 = Te0[s2 >> 24] ^ Te1[(s3 >> 16) & 0xff] ^ Te2[(s0 >> 8) & 0xff] ^ Te3[s1 & 0xff] ^ rk[ 6]; + t3 = Te0[s3 >> 24] ^ Te1[(s0 >> 16) & 0xff] ^ Te2[(s1 >> 8) & 0xff] ^ Te3[s2 & 0xff] ^ rk[ 7]; + /* round 2: */ + s0 = Te0[t0 >> 24] ^ Te1[(t1 >> 16) & 0xff] ^ Te2[(t2 >> 8) & 0xff] ^ Te3[t3 & 0xff] ^ rk[ 8]; + s1 = Te0[t1 >> 24] ^ Te1[(t2 >> 16) & 0xff] ^ Te2[(t3 >> 8) & 0xff] ^ Te3[t0 & 0xff] ^ rk[ 9]; + s2 = Te0[t2 >> 24] ^ Te1[(t3 >> 16) & 0xff] ^ Te2[(t0 >> 8) & 0xff] ^ Te3[t1 & 0xff] ^ rk[10]; + s3 = Te0[t3 >> 24] ^ Te1[(t0 >> 16) & 0xff] ^ Te2[(t1 >> 8) & 0xff] ^ Te3[t2 & 0xff] ^ rk[11]; + /* round 3: */ + t0 = Te0[s0 >> 24] ^ Te1[(s1 >> 16) & 0xff] ^ Te2[(s2 >> 8) & 0xff] ^ Te3[s3 & 0xff] ^ rk[12]; + t1 = Te0[s1 >> 24] ^ Te1[(s2 >> 16) & 0xff] ^ Te2[(s3 >> 8) & 0xff] ^ Te3[s0 & 0xff] ^ rk[13]; + t2 = Te0[s2 >> 24] ^ Te1[(s3 >> 16) & 0xff] ^ Te2[(s0 >> 8) & 0xff] ^ Te3[s1 & 0xff] ^ rk[14]; + t3 = Te0[s3 >> 24] ^ Te1[(s0 >> 16) & 0xff] ^ Te2[(s1 >> 8) & 0xff] ^ Te3[s2 & 0xff] ^ rk[15]; + /* round 4: */ + s0 = Te0[t0 >> 24] ^ Te1[(t1 >> 16) & 0xff] ^ Te2[(t2 >> 8) & 0xff] ^ Te3[t3 & 0xff] ^ rk[16]; + s1 = Te0[t1 >> 24] ^ Te1[(t2 >> 16) & 0xff] ^ Te2[(t3 >> 8) & 0xff] ^ Te3[t0 & 0xff] ^ rk[17]; + s2 = Te0[t2 >> 24] ^ Te1[(t3 >> 16) & 0xff] ^ Te2[(t0 >> 8) & 0xff] ^ Te3[t1 & 0xff] ^ rk[18]; + s3 = Te0[t3 >> 24] ^ Te1[(t0 >> 16) & 0xff] ^ Te2[(t1 >> 8) & 0xff] ^ Te3[t2 & 0xff] ^ rk[19]; + /* round 5: */ + t0 = Te0[s0 >> 24] ^ Te1[(s1 >> 16) & 0xff] ^ Te2[(s2 >> 8) & 0xff] ^ Te3[s3 & 0xff] ^ rk[20]; + t1 = Te0[s1 >> 24] ^ Te1[(s2 >> 16) & 0xff] ^ Te2[(s3 >> 8) & 0xff] ^ Te3[s0 & 0xff] ^ rk[21]; + t2 = Te0[s2 >> 24] ^ Te1[(s3 >> 16) & 0xff] ^ Te2[(s0 >> 8) & 0xff] ^ Te3[s1 & 0xff] ^ rk[22]; + t3 = Te0[s3 >> 24] ^ Te1[(s0 >> 16) & 0xff] ^ Te2[(s1 >> 8) & 0xff] ^ Te3[s2 & 0xff] ^ rk[23]; + /* round 6: */ + s0 = Te0[t0 >> 24] ^ Te1[(t1 >> 16) & 0xff] ^ Te2[(t2 >> 8) & 0xff] ^ Te3[t3 & 0xff] ^ rk[24]; + s1 = Te0[t1 >> 24] ^ Te1[(t2 >> 16) & 0xff] ^ Te2[(t3 >> 8) & 0xff] ^ Te3[t0 & 0xff] ^ rk[25]; + s2 = Te0[t2 >> 24] ^ Te1[(t3 >> 16) & 0xff] ^ Te2[(t0 >> 8) & 0xff] ^ Te3[t1 & 0xff] ^ rk[26]; + s3 = Te0[t3 >> 24] ^ Te1[(t0 >> 16) & 0xff] ^ Te2[(t1 >> 8) & 0xff] ^ Te3[t2 & 0xff] ^ rk[27]; + /* round 7: */ + t0 = Te0[s0 >> 24] ^ Te1[(s1 >> 16) & 0xff] ^ Te2[(s2 >> 8) & 0xff] ^ Te3[s3 & 0xff] ^ rk[28]; + t1 = Te0[s1 >> 24] ^ Te1[(s2 >> 16) & 0xff] ^ Te2[(s3 >> 8) & 0xff] ^ Te3[s0 & 0xff] ^ rk[29]; + t2 = Te0[s2 >> 24] ^ Te1[(s3 >> 16) & 0xff] ^ Te2[(s0 >> 8) & 0xff] ^ Te3[s1 & 0xff] ^ rk[30]; + t3 = Te0[s3 >> 24] ^ Te1[(s0 >> 16) & 0xff] ^ Te2[(s1 >> 8) & 0xff] ^ Te3[s2 & 0xff] ^ rk[31]; + /* round 8: */ + s0 = Te0[t0 >> 24] ^ Te1[(t1 >> 16) & 0xff] ^ Te2[(t2 >> 8) & 0xff] ^ Te3[t3 & 0xff] ^ rk[32]; + s1 = Te0[t1 >> 24] ^ Te1[(t2 >> 16) & 0xff] ^ Te2[(t3 >> 8) & 0xff] ^ Te3[t0 & 0xff] ^ rk[33]; + s2 = Te0[t2 >> 24] ^ Te1[(t3 >> 16) & 0xff] ^ Te2[(t0 >> 8) & 0xff] ^ Te3[t1 & 0xff] ^ rk[34]; + s3 = Te0[t3 >> 24] ^ Te1[(t0 >> 16) & 0xff] ^ Te2[(t1 >> 8) & 0xff] ^ Te3[t2 & 0xff] ^ rk[35]; + /* round 9: */ + t0 = Te0[s0 >> 24] ^ Te1[(s1 >> 16) & 0xff] ^ Te2[(s2 >> 8) & 0xff] ^ Te3[s3 & 0xff] ^ rk[36]; + t1 = Te0[s1 >> 24] ^ Te1[(s2 >> 16) & 0xff] ^ Te2[(s3 >> 8) & 0xff] ^ Te3[s0 & 0xff] ^ rk[37]; + t2 = Te0[s2 >> 24] ^ Te1[(s3 >> 16) & 0xff] ^ Te2[(s0 >> 8) & 0xff] ^ Te3[s1 & 0xff] ^ rk[38]; + t3 = Te0[s3 >> 24] ^ Te1[(s0 >> 16) & 0xff] ^ Te2[(s1 >> 8) & 0xff] ^ Te3[s2 & 0xff] ^ rk[39]; + if (key->rounds > 10) { + /* round 10: */ + s0 = Te0[t0 >> 24] ^ Te1[(t1 >> 16) & 0xff] ^ Te2[(t2 >> 8) & 0xff] ^ Te3[t3 & 0xff] ^ rk[40]; + s1 = Te0[t1 >> 24] ^ Te1[(t2 >> 16) & 0xff] ^ Te2[(t3 >> 8) & 0xff] ^ Te3[t0 & 0xff] ^ rk[41]; + s2 = Te0[t2 >> 24] ^ Te1[(t3 >> 16) & 0xff] ^ Te2[(t0 >> 8) & 0xff] ^ Te3[t1 & 0xff] ^ rk[42]; + s3 = Te0[t3 >> 24] ^ Te1[(t0 >> 16) & 0xff] ^ Te2[(t1 >> 8) & 0xff] ^ Te3[t2 & 0xff] ^ rk[43]; + /* round 11: */ + t0 = Te0[s0 >> 24] ^ Te1[(s1 >> 16) & 0xff] ^ Te2[(s2 >> 8) & 0xff] ^ Te3[s3 & 0xff] ^ rk[44]; + t1 = Te0[s1 >> 24] ^ Te1[(s2 >> 16) & 0xff] ^ Te2[(s3 >> 8) & 0xff] ^ Te3[s0 & 0xff] ^ rk[45]; + t2 = Te0[s2 >> 24] ^ Te1[(s3 >> 16) & 0xff] ^ Te2[(s0 >> 8) & 0xff] ^ Te3[s1 & 0xff] ^ rk[46]; + t3 = Te0[s3 >> 24] ^ Te1[(s0 >> 16) & 0xff] ^ Te2[(s1 >> 8) & 0xff] ^ Te3[s2 & 0xff] ^ rk[47]; + if (key->rounds > 12) { + /* round 12: */ + s0 = Te0[t0 >> 24] ^ Te1[(t1 >> 16) & 0xff] ^ Te2[(t2 >> 8) & 0xff] ^ Te3[t3 & 0xff] ^ rk[48]; + s1 = Te0[t1 >> 24] ^ Te1[(t2 >> 16) & 0xff] ^ Te2[(t3 >> 8) & 0xff] ^ Te3[t0 & 0xff] ^ rk[49]; + s2 = Te0[t2 >> 24] ^ Te1[(t3 >> 16) & 0xff] ^ Te2[(t0 >> 8) & 0xff] ^ Te3[t1 & 0xff] ^ rk[50]; + s3 = Te0[t3 >> 24] ^ Te1[(t0 >> 16) & 0xff] ^ Te2[(t1 >> 8) & 0xff] ^ Te3[t2 & 0xff] ^ rk[51]; + /* round 13: */ + t0 = Te0[s0 >> 24] ^ Te1[(s1 >> 16) & 0xff] ^ Te2[(s2 >> 8) & 0xff] ^ Te3[s3 & 0xff] ^ rk[52]; + t1 = Te0[s1 >> 24] ^ Te1[(s2 >> 16) & 0xff] ^ Te2[(s3 >> 8) & 0xff] ^ Te3[s0 & 0xff] ^ rk[53]; + t2 = Te0[s2 >> 24] ^ Te1[(s3 >> 16) & 0xff] ^ Te2[(s0 >> 8) & 0xff] ^ Te3[s1 & 0xff] ^ rk[54]; + t3 = Te0[s3 >> 24] ^ Te1[(s0 >> 16) & 0xff] ^ Te2[(s1 >> 8) & 0xff] ^ Te3[s2 & 0xff] ^ rk[55]; + } + } + rk += key->rounds << 2; +#else /* !FULL_UNROLL */ + /* + * Nr - 1 full rounds: + */ + r = key->rounds >> 1; + for (;;) { + t0 = + Te0[(s0 >> 24) ] ^ + Te1[(s1 >> 16) & 0xff] ^ + Te2[(s2 >> 8) & 0xff] ^ + Te3[(s3 ) & 0xff] ^ + rk[4]; + t1 = + Te0[(s1 >> 24) ] ^ + Te1[(s2 >> 16) & 0xff] ^ + Te2[(s3 >> 8) & 0xff] ^ + Te3[(s0 ) & 0xff] ^ + rk[5]; + t2 = + Te0[(s2 >> 24) ] ^ + Te1[(s3 >> 16) & 0xff] ^ + Te2[(s0 >> 8) & 0xff] ^ + Te3[(s1 ) & 0xff] ^ + rk[6]; + t3 = + Te0[(s3 >> 24) ] ^ + Te1[(s0 >> 16) & 0xff] ^ + Te2[(s1 >> 8) & 0xff] ^ + Te3[(s2 ) & 0xff] ^ + rk[7]; + + rk += 8; + if (--r == 0) { + break; + } + + s0 = + Te0[(t0 >> 24) ] ^ + Te1[(t1 >> 16) & 0xff] ^ + Te2[(t2 >> 8) & 0xff] ^ + Te3[(t3 ) & 0xff] ^ + rk[0]; + s1 = + Te0[(t1 >> 24) ] ^ + Te1[(t2 >> 16) & 0xff] ^ + Te2[(t3 >> 8) & 0xff] ^ + Te3[(t0 ) & 0xff] ^ + rk[1]; + s2 = + Te0[(t2 >> 24) ] ^ + Te1[(t3 >> 16) & 0xff] ^ + Te2[(t0 >> 8) & 0xff] ^ + Te3[(t1 ) & 0xff] ^ + rk[2]; + s3 = + Te0[(t3 >> 24) ] ^ + Te1[(t0 >> 16) & 0xff] ^ + Te2[(t1 >> 8) & 0xff] ^ + Te3[(t2 ) & 0xff] ^ + rk[3]; + } +#endif /* ?FULL_UNROLL */ + /* + * apply last round and + * map cipher state to byte array block: + */ + s0 = + (Te4[(t0 >> 24) ] & 0xff000000) ^ + (Te4[(t1 >> 16) & 0xff] & 0x00ff0000) ^ + (Te4[(t2 >> 8) & 0xff] & 0x0000ff00) ^ + (Te4[(t3 ) & 0xff] & 0x000000ff) ^ + rk[0]; + PUTU32(out , s0); + s1 = + (Te4[(t1 >> 24) ] & 0xff000000) ^ + (Te4[(t2 >> 16) & 0xff] & 0x00ff0000) ^ + (Te4[(t3 >> 8) & 0xff] & 0x0000ff00) ^ + (Te4[(t0 ) & 0xff] & 0x000000ff) ^ + rk[1]; + PUTU32(out + 4, s1); + s2 = + (Te4[(t2 >> 24) ] & 0xff000000) ^ + (Te4[(t3 >> 16) & 0xff] & 0x00ff0000) ^ + (Te4[(t0 >> 8) & 0xff] & 0x0000ff00) ^ + (Te4[(t1 ) & 0xff] & 0x000000ff) ^ + rk[2]; + PUTU32(out + 8, s2); + s3 = + (Te4[(t3 >> 24) ] & 0xff000000) ^ + (Te4[(t0 >> 16) & 0xff] & 0x00ff0000) ^ + (Te4[(t1 >> 8) & 0xff] & 0x0000ff00) ^ + (Te4[(t2 ) & 0xff] & 0x000000ff) ^ + rk[3]; + PUTU32(out + 12, s3); +} + +/* + * Decrypt a single block + * in and out can overlap + */ +void AES_decrypt(const unsigned char *in, unsigned char *out, + const AES_KEY *key) { + + const u32 *rk; + u32 s0, s1, s2, s3, t0, t1, t2, t3; +#ifndef FULL_UNROLL + int r; +#endif /* ?FULL_UNROLL */ + + assert(in && out && key); + rk = key->rd_key; + + /* + * map byte array block to cipher state + * and add initial round key: + */ + s0 = GETU32(in ) ^ rk[0]; + s1 = GETU32(in + 4) ^ rk[1]; + s2 = GETU32(in + 8) ^ rk[2]; + s3 = GETU32(in + 12) ^ rk[3]; +#ifdef FULL_UNROLL + /* round 1: */ + t0 = Td0[s0 >> 24] ^ Td1[(s3 >> 16) & 0xff] ^ Td2[(s2 >> 8) & 0xff] ^ Td3[s1 & 0xff] ^ rk[ 4]; + t1 = Td0[s1 >> 24] ^ Td1[(s0 >> 16) & 0xff] ^ Td2[(s3 >> 8) & 0xff] ^ Td3[s2 & 0xff] ^ rk[ 5]; + t2 = Td0[s2 >> 24] ^ Td1[(s1 >> 16) & 0xff] ^ Td2[(s0 >> 8) & 0xff] ^ Td3[s3 & 0xff] ^ rk[ 6]; + t3 = Td0[s3 >> 24] ^ Td1[(s2 >> 16) & 0xff] ^ Td2[(s1 >> 8) & 0xff] ^ Td3[s0 & 0xff] ^ rk[ 7]; + /* round 2: */ + s0 = Td0[t0 >> 24] ^ Td1[(t3 >> 16) & 0xff] ^ Td2[(t2 >> 8) & 0xff] ^ Td3[t1 & 0xff] ^ rk[ 8]; + s1 = Td0[t1 >> 24] ^ Td1[(t0 >> 16) & 0xff] ^ Td2[(t3 >> 8) & 0xff] ^ Td3[t2 & 0xff] ^ rk[ 9]; + s2 = Td0[t2 >> 24] ^ Td1[(t1 >> 16) & 0xff] ^ Td2[(t0 >> 8) & 0xff] ^ Td3[t3 & 0xff] ^ rk[10]; + s3 = Td0[t3 >> 24] ^ Td1[(t2 >> 16) & 0xff] ^ Td2[(t1 >> 8) & 0xff] ^ Td3[t0 & 0xff] ^ rk[11]; + /* round 3: */ + t0 = Td0[s0 >> 24] ^ Td1[(s3 >> 16) & 0xff] ^ Td2[(s2 >> 8) & 0xff] ^ Td3[s1 & 0xff] ^ rk[12]; + t1 = Td0[s1 >> 24] ^ Td1[(s0 >> 16) & 0xff] ^ Td2[(s3 >> 8) & 0xff] ^ Td3[s2 & 0xff] ^ rk[13]; + t2 = Td0[s2 >> 24] ^ Td1[(s1 >> 16) & 0xff] ^ Td2[(s0 >> 8) & 0xff] ^ Td3[s3 & 0xff] ^ rk[14]; + t3 = Td0[s3 >> 24] ^ Td1[(s2 >> 16) & 0xff] ^ Td2[(s1 >> 8) & 0xff] ^ Td3[s0 & 0xff] ^ rk[15]; + /* round 4: */ + s0 = Td0[t0 >> 24] ^ Td1[(t3 >> 16) & 0xff] ^ Td2[(t2 >> 8) & 0xff] ^ Td3[t1 & 0xff] ^ rk[16]; + s1 = Td0[t1 >> 24] ^ Td1[(t0 >> 16) & 0xff] ^ Td2[(t3 >> 8) & 0xff] ^ Td3[t2 & 0xff] ^ rk[17]; + s2 = Td0[t2 >> 24] ^ Td1[(t1 >> 16) & 0xff] ^ Td2[(t0 >> 8) & 0xff] ^ Td3[t3 & 0xff] ^ rk[18]; + s3 = Td0[t3 >> 24] ^ Td1[(t2 >> 16) & 0xff] ^ Td2[(t1 >> 8) & 0xff] ^ Td3[t0 & 0xff] ^ rk[19]; + /* round 5: */ + t0 = Td0[s0 >> 24] ^ Td1[(s3 >> 16) & 0xff] ^ Td2[(s2 >> 8) & 0xff] ^ Td3[s1 & 0xff] ^ rk[20]; + t1 = Td0[s1 >> 24] ^ Td1[(s0 >> 16) & 0xff] ^ Td2[(s3 >> 8) & 0xff] ^ Td3[s2 & 0xff] ^ rk[21]; + t2 = Td0[s2 >> 24] ^ Td1[(s1 >> 16) & 0xff] ^ Td2[(s0 >> 8) & 0xff] ^ Td3[s3 & 0xff] ^ rk[22]; + t3 = Td0[s3 >> 24] ^ Td1[(s2 >> 16) & 0xff] ^ Td2[(s1 >> 8) & 0xff] ^ Td3[s0 & 0xff] ^ rk[23]; + /* round 6: */ + s0 = Td0[t0 >> 24] ^ Td1[(t3 >> 16) & 0xff] ^ Td2[(t2 >> 8) & 0xff] ^ Td3[t1 & 0xff] ^ rk[24]; + s1 = Td0[t1 >> 24] ^ Td1[(t0 >> 16) & 0xff] ^ Td2[(t3 >> 8) & 0xff] ^ Td3[t2 & 0xff] ^ rk[25]; + s2 = Td0[t2 >> 24] ^ Td1[(t1 >> 16) & 0xff] ^ Td2[(t0 >> 8) & 0xff] ^ Td3[t3 & 0xff] ^ rk[26]; + s3 = Td0[t3 >> 24] ^ Td1[(t2 >> 16) & 0xff] ^ Td2[(t1 >> 8) & 0xff] ^ Td3[t0 & 0xff] ^ rk[27]; + /* round 7: */ + t0 = Td0[s0 >> 24] ^ Td1[(s3 >> 16) & 0xff] ^ Td2[(s2 >> 8) & 0xff] ^ Td3[s1 & 0xff] ^ rk[28]; + t1 = Td0[s1 >> 24] ^ Td1[(s0 >> 16) & 0xff] ^ Td2[(s3 >> 8) & 0xff] ^ Td3[s2 & 0xff] ^ rk[29]; + t2 = Td0[s2 >> 24] ^ Td1[(s1 >> 16) & 0xff] ^ Td2[(s0 >> 8) & 0xff] ^ Td3[s3 & 0xff] ^ rk[30]; + t3 = Td0[s3 >> 24] ^ Td1[(s2 >> 16) & 0xff] ^ Td2[(s1 >> 8) & 0xff] ^ Td3[s0 & 0xff] ^ rk[31]; + /* round 8: */ + s0 = Td0[t0 >> 24] ^ Td1[(t3 >> 16) & 0xff] ^ Td2[(t2 >> 8) & 0xff] ^ Td3[t1 & 0xff] ^ rk[32]; + s1 = Td0[t1 >> 24] ^ Td1[(t0 >> 16) & 0xff] ^ Td2[(t3 >> 8) & 0xff] ^ Td3[t2 & 0xff] ^ rk[33]; + s2 = Td0[t2 >> 24] ^ Td1[(t1 >> 16) & 0xff] ^ Td2[(t0 >> 8) & 0xff] ^ Td3[t3 & 0xff] ^ rk[34]; + s3 = Td0[t3 >> 24] ^ Td1[(t2 >> 16) & 0xff] ^ Td2[(t1 >> 8) & 0xff] ^ Td3[t0 & 0xff] ^ rk[35]; + /* round 9: */ + t0 = Td0[s0 >> 24] ^ Td1[(s3 >> 16) & 0xff] ^ Td2[(s2 >> 8) & 0xff] ^ Td3[s1 & 0xff] ^ rk[36]; + t1 = Td0[s1 >> 24] ^ Td1[(s0 >> 16) & 0xff] ^ Td2[(s3 >> 8) & 0xff] ^ Td3[s2 & 0xff] ^ rk[37]; + t2 = Td0[s2 >> 24] ^ Td1[(s1 >> 16) & 0xff] ^ Td2[(s0 >> 8) & 0xff] ^ Td3[s3 & 0xff] ^ rk[38]; + t3 = Td0[s3 >> 24] ^ Td1[(s2 >> 16) & 0xff] ^ Td2[(s1 >> 8) & 0xff] ^ Td3[s0 & 0xff] ^ rk[39]; + if (key->rounds > 10) { + /* round 10: */ + s0 = Td0[t0 >> 24] ^ Td1[(t3 >> 16) & 0xff] ^ Td2[(t2 >> 8) & 0xff] ^ Td3[t1 & 0xff] ^ rk[40]; + s1 = Td0[t1 >> 24] ^ Td1[(t0 >> 16) & 0xff] ^ Td2[(t3 >> 8) & 0xff] ^ Td3[t2 & 0xff] ^ rk[41]; + s2 = Td0[t2 >> 24] ^ Td1[(t1 >> 16) & 0xff] ^ Td2[(t0 >> 8) & 0xff] ^ Td3[t3 & 0xff] ^ rk[42]; + s3 = Td0[t3 >> 24] ^ Td1[(t2 >> 16) & 0xff] ^ Td2[(t1 >> 8) & 0xff] ^ Td3[t0 & 0xff] ^ rk[43]; + /* round 11: */ + t0 = Td0[s0 >> 24] ^ Td1[(s3 >> 16) & 0xff] ^ Td2[(s2 >> 8) & 0xff] ^ Td3[s1 & 0xff] ^ rk[44]; + t1 = Td0[s1 >> 24] ^ Td1[(s0 >> 16) & 0xff] ^ Td2[(s3 >> 8) & 0xff] ^ Td3[s2 & 0xff] ^ rk[45]; + t2 = Td0[s2 >> 24] ^ Td1[(s1 >> 16) & 0xff] ^ Td2[(s0 >> 8) & 0xff] ^ Td3[s3 & 0xff] ^ rk[46]; + t3 = Td0[s3 >> 24] ^ Td1[(s2 >> 16) & 0xff] ^ Td2[(s1 >> 8) & 0xff] ^ Td3[s0 & 0xff] ^ rk[47]; + if (key->rounds > 12) { + /* round 12: */ + s0 = Td0[t0 >> 24] ^ Td1[(t3 >> 16) & 0xff] ^ Td2[(t2 >> 8) & 0xff] ^ Td3[t1 & 0xff] ^ rk[48]; + s1 = Td0[t1 >> 24] ^ Td1[(t0 >> 16) & 0xff] ^ Td2[(t3 >> 8) & 0xff] ^ Td3[t2 & 0xff] ^ rk[49]; + s2 = Td0[t2 >> 24] ^ Td1[(t1 >> 16) & 0xff] ^ Td2[(t0 >> 8) & 0xff] ^ Td3[t3 & 0xff] ^ rk[50]; + s3 = Td0[t3 >> 24] ^ Td1[(t2 >> 16) & 0xff] ^ Td2[(t1 >> 8) & 0xff] ^ Td3[t0 & 0xff] ^ rk[51]; + /* round 13: */ + t0 = Td0[s0 >> 24] ^ Td1[(s3 >> 16) & 0xff] ^ Td2[(s2 >> 8) & 0xff] ^ Td3[s1 & 0xff] ^ rk[52]; + t1 = Td0[s1 >> 24] ^ Td1[(s0 >> 16) & 0xff] ^ Td2[(s3 >> 8) & 0xff] ^ Td3[s2 & 0xff] ^ rk[53]; + t2 = Td0[s2 >> 24] ^ Td1[(s1 >> 16) & 0xff] ^ Td2[(s0 >> 8) & 0xff] ^ Td3[s3 & 0xff] ^ rk[54]; + t3 = Td0[s3 >> 24] ^ Td1[(s2 >> 16) & 0xff] ^ Td2[(s1 >> 8) & 0xff] ^ Td3[s0 & 0xff] ^ rk[55]; + } + } + rk += key->rounds << 2; +#else /* !FULL_UNROLL */ + /* + * Nr - 1 full rounds: + */ + r = key->rounds >> 1; + for (;;) { + t0 = + Td0[(s0 >> 24) ] ^ + Td1[(s3 >> 16) & 0xff] ^ + Td2[(s2 >> 8) & 0xff] ^ + Td3[(s1 ) & 0xff] ^ + rk[4]; + t1 = + Td0[(s1 >> 24) ] ^ + Td1[(s0 >> 16) & 0xff] ^ + Td2[(s3 >> 8) & 0xff] ^ + Td3[(s2 ) & 0xff] ^ + rk[5]; + t2 = + Td0[(s2 >> 24) ] ^ + Td1[(s1 >> 16) & 0xff] ^ + Td2[(s0 >> 8) & 0xff] ^ + Td3[(s3 ) & 0xff] ^ + rk[6]; + t3 = + Td0[(s3 >> 24) ] ^ + Td1[(s2 >> 16) & 0xff] ^ + Td2[(s1 >> 8) & 0xff] ^ + Td3[(s0 ) & 0xff] ^ + rk[7]; + + rk += 8; + if (--r == 0) { + break; + } + + s0 = + Td0[(t0 >> 24) ] ^ + Td1[(t3 >> 16) & 0xff] ^ + Td2[(t2 >> 8) & 0xff] ^ + Td3[(t1 ) & 0xff] ^ + rk[0]; + s1 = + Td0[(t1 >> 24) ] ^ + Td1[(t0 >> 16) & 0xff] ^ + Td2[(t3 >> 8) & 0xff] ^ + Td3[(t2 ) & 0xff] ^ + rk[1]; + s2 = + Td0[(t2 >> 24) ] ^ + Td1[(t1 >> 16) & 0xff] ^ + Td2[(t0 >> 8) & 0xff] ^ + Td3[(t3 ) & 0xff] ^ + rk[2]; + s3 = + Td0[(t3 >> 24) ] ^ + Td1[(t2 >> 16) & 0xff] ^ + Td2[(t1 >> 8) & 0xff] ^ + Td3[(t0 ) & 0xff] ^ + rk[3]; + } +#endif /* ?FULL_UNROLL */ + /* + * apply last round and + * map cipher state to byte array block: + */ + s0 = + (Td4[(t0 >> 24) ] & 0xff000000) ^ + (Td4[(t3 >> 16) & 0xff] & 0x00ff0000) ^ + (Td4[(t2 >> 8) & 0xff] & 0x0000ff00) ^ + (Td4[(t1 ) & 0xff] & 0x000000ff) ^ + rk[0]; + PUTU32(out , s0); + s1 = + (Td4[(t1 >> 24) ] & 0xff000000) ^ + (Td4[(t0 >> 16) & 0xff] & 0x00ff0000) ^ + (Td4[(t3 >> 8) & 0xff] & 0x0000ff00) ^ + (Td4[(t2 ) & 0xff] & 0x000000ff) ^ + rk[1]; + PUTU32(out + 4, s1); + s2 = + (Td4[(t2 >> 24) ] & 0xff000000) ^ + (Td4[(t1 >> 16) & 0xff] & 0x00ff0000) ^ + (Td4[(t0 >> 8) & 0xff] & 0x0000ff00) ^ + (Td4[(t3 ) & 0xff] & 0x000000ff) ^ + rk[2]; + PUTU32(out + 8, s2); + s3 = + (Td4[(t3 >> 24) ] & 0xff000000) ^ + (Td4[(t2 >> 16) & 0xff] & 0x00ff0000) ^ + (Td4[(t1 >> 8) & 0xff] & 0x0000ff00) ^ + (Td4[(t0 ) & 0xff] & 0x000000ff) ^ + rk[3]; + PUTU32(out + 12, s3); +} + +#endif /* AES_ASM */ + +void AES_cbc_encrypt(const unsigned char *in, unsigned char *out, + const unsigned long length, const AES_KEY *key, + unsigned char *ivec, const int enc) +{ + + unsigned long n; + unsigned long len = length; + unsigned char tmp[AES_BLOCK_SIZE]; + + assert(in && out && key && ivec); + + if (enc) { + while (len >= AES_BLOCK_SIZE) { + for(n=0; n < AES_BLOCK_SIZE; ++n) + tmp[n] = in[n] ^ ivec[n]; + AES_encrypt(tmp, out, key); + memcpy(ivec, out, AES_BLOCK_SIZE); + len -= AES_BLOCK_SIZE; + in += AES_BLOCK_SIZE; + out += AES_BLOCK_SIZE; + } + if (len) { + for(n=0; n < len; ++n) + tmp[n] = in[n] ^ ivec[n]; + for(n=len; n < AES_BLOCK_SIZE; ++n) + tmp[n] = ivec[n]; + AES_encrypt(tmp, tmp, key); + memcpy(out, tmp, AES_BLOCK_SIZE); + memcpy(ivec, tmp, AES_BLOCK_SIZE); + } + } else { + while (len >= AES_BLOCK_SIZE) { + memcpy(tmp, in, AES_BLOCK_SIZE); + AES_decrypt(in, out, key); + for(n=0; n < AES_BLOCK_SIZE; ++n) + out[n] ^= ivec[n]; + memcpy(ivec, tmp, AES_BLOCK_SIZE); + len -= AES_BLOCK_SIZE; + in += AES_BLOCK_SIZE; + out += AES_BLOCK_SIZE; + } + if (len) { + memcpy(tmp, in, AES_BLOCK_SIZE); + AES_decrypt(tmp, tmp, key); + for(n=0; n < len; ++n) + out[n] = tmp[n] ^ ivec[n]; + memcpy(ivec, tmp, AES_BLOCK_SIZE); + } + } +} diff --git a/aes.h b/aes.h new file mode 100644 index 0000000..a0167eb --- /dev/null +++ b/aes.h @@ -0,0 +1,26 @@ +#ifndef QEMU_AES_H +#define QEMU_AES_H + +#define AES_MAXNR 14 +#define AES_BLOCK_SIZE 16 + +struct aes_key_st { + uint32_t rd_key[4 *(AES_MAXNR + 1)]; + int rounds; +}; +typedef struct aes_key_st AES_KEY; + +int AES_set_encrypt_key(const unsigned char *userKey, const int bits, + AES_KEY *key); +int AES_set_decrypt_key(const unsigned char *userKey, const int bits, + AES_KEY *key); + +void AES_encrypt(const unsigned char *in, unsigned char *out, + const AES_KEY *key); +void AES_decrypt(const unsigned char *in, unsigned char *out, + const AES_KEY *key); +void AES_cbc_encrypt(const unsigned char *in, unsigned char *out, + const unsigned long length, const AES_KEY *key, + unsigned char *ivec, const int enc); + +#endif diff --git a/aio.c b/aio.c new file mode 100644 index 0000000..2f08655 --- /dev/null +++ b/aio.c @@ -0,0 +1,230 @@ +/* + * QEMU aio implementation + * + * Copyright IBM, Corp. 2008 + * + * Authors: + * Anthony Liguori + * + * 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 "block.h" +#include "qemu-queue.h" +#include "qemu_socket.h" + +typedef struct AioHandler AioHandler; + +/* The list of registered AIO handlers */ +static QLIST_HEAD(, AioHandler) aio_handlers; + +/* This is a simple lock used to protect the aio_handlers list. Specifically, + * it's used to ensure that no callbacks are removed while we're walking and + * dispatching callbacks. + */ +static int walking_handlers; + +struct AioHandler +{ + int fd; + IOHandler *io_read; + IOHandler *io_write; + AioFlushHandler *io_flush; + AioProcessQueue *io_process_queue; + int deleted; + void *opaque; + QLIST_ENTRY(AioHandler) node; +}; + +static AioHandler *find_aio_handler(int fd) +{ + AioHandler *node; + + QLIST_FOREACH(node, &aio_handlers, node) { + if (node->fd == fd) + if (!node->deleted) + return node; + } + + return NULL; +} + +int qemu_aio_set_fd_handler(int fd, + IOHandler *io_read, + IOHandler *io_write, + AioFlushHandler *io_flush, + AioProcessQueue *io_process_queue, + void *opaque) +{ + AioHandler *node; + + node = find_aio_handler(fd); + + /* Are we deleting the fd handler? */ + if (!io_read && !io_write) { + if (node) { + /* If the lock is held, just mark the node as deleted */ + if (walking_handlers) + node->deleted = 1; + else { + /* Otherwise, delete it for real. We can't just mark it as + * deleted because deleted nodes are only cleaned up after + * releasing the walking_handlers lock. + */ + QLIST_REMOVE(node, node); + qemu_free(node); + } + } + } else { + if (node == NULL) { + /* Alloc and insert if it's not already there */ + node = qemu_mallocz(sizeof(AioHandler)); + node->fd = fd; + QLIST_INSERT_HEAD(&aio_handlers, node, node); + } + /* Update handler with latest information */ + node->io_read = io_read; + node->io_write = io_write; + node->io_flush = io_flush; + node->io_process_queue = io_process_queue; + node->opaque = opaque; + } + + qemu_set_fd_handler2(fd, NULL, io_read, io_write, opaque); + + return 0; +} + +void qemu_aio_flush(void) +{ + AioHandler *node; + int ret; + + do { + ret = 0; + + /* + * If there are pending emulated aio start them now so flush + * will be able to return 1. + */ + qemu_aio_wait(); + + QLIST_FOREACH(node, &aio_handlers, node) { + if (node->io_flush) { + ret |= node->io_flush(node->opaque); + } + } + } while (qemu_bh_poll() || ret > 0); +} + +int qemu_aio_process_queue(void) +{ + AioHandler *node; + int ret = 0; + + walking_handlers = 1; + + QLIST_FOREACH(node, &aio_handlers, node) { + if (node->io_process_queue) { + if (node->io_process_queue(node->opaque)) { + ret = 1; + } + } + } + + walking_handlers = 0; + + return ret; +} + +void qemu_aio_wait(void) +{ + int ret; + + if (qemu_bh_poll()) + return; + + /* + * If there are callbacks left that have been queued, we need to call then. + * Return afterwards to avoid waiting needlessly in select(). + */ + if (qemu_aio_process_queue()) + return; + + do { + AioHandler *node; + fd_set rdfds, wrfds; + int max_fd = -1; + + walking_handlers = 1; + + FD_ZERO(&rdfds); + FD_ZERO(&wrfds); + + /* fill fd sets */ + QLIST_FOREACH(node, &aio_handlers, node) { + /* If there aren't pending AIO operations, don't invoke callbacks. + * Otherwise, if there are no AIO requests, qemu_aio_wait() would + * wait indefinitely. + */ + if (node->io_flush && node->io_flush(node->opaque) == 0) + continue; + + if (!node->deleted && node->io_read) { + FD_SET(node->fd, &rdfds); + max_fd = MAX(max_fd, node->fd + 1); + } + if (!node->deleted && node->io_write) { + FD_SET(node->fd, &wrfds); + max_fd = MAX(max_fd, node->fd + 1); + } + } + + walking_handlers = 0; + + /* No AIO operations? Get us out of here */ + if (max_fd == -1) + break; + + /* wait until next event */ + ret = select(max_fd, &rdfds, &wrfds, NULL, NULL); + if (ret == -1 && errno == EINTR) + continue; + + /* if we have any readable fds, dispatch event */ + if (ret > 0) { + walking_handlers = 1; + + /* we have to walk very carefully in case + * qemu_aio_set_fd_handler is called while we're walking */ + node = QLIST_FIRST(&aio_handlers); + while (node) { + AioHandler *tmp; + + if (!node->deleted && + FD_ISSET(node->fd, &rdfds) && + node->io_read) { + node->io_read(node->opaque); + } + if (!node->deleted && + FD_ISSET(node->fd, &wrfds) && + node->io_write) { + node->io_write(node->opaque); + } + + tmp = node; + node = QLIST_NEXT(node, node); + + if (tmp->deleted) { + QLIST_REMOVE(tmp, node); + qemu_free(tmp); + } + } + + walking_handlers = 0; + } + } while (ret == 0); +} diff --git a/alpha-dis.c b/alpha-dis.c new file mode 100644 index 0000000..8a2411e --- /dev/null +++ b/alpha-dis.c @@ -0,0 +1,1920 @@ +/* alpha-dis.c -- Disassemble Alpha AXP instructions + Copyright 1996, 1998, 1999, 2000, 2001 Free Software Foundation, Inc. + Contributed by Richard Henderson , + patterned after the PPC opcode handling written by Ian Lance Taylor. + +This file is part of GDB, GAS, and the GNU binutils. + +GDB, GAS, and the GNU binutils are free software; you can redistribute +them and/or modify them 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. + +GDB, GAS, and the GNU binutils are distributed in the hope that they +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 file; see the file COPYING. If not, see +. */ + +#include +#include "dis-asm.h" + +/* MAX is redefined below, so remove any previous definition. */ +#undef MAX + +/* The opcode table is an array of struct alpha_opcode. */ + +struct alpha_opcode +{ + /* The opcode name. */ + const char *name; + + /* The opcode itself. Those bits which will be filled in with + operands are zeroes. */ + unsigned opcode; + + /* The opcode mask. This is used by the disassembler. This is a + mask containing ones indicating those bits which must match the + opcode field, and zeroes indicating those bits which need not + match (and are presumably filled in by operands). */ + unsigned mask; + + /* One bit flags for the opcode. These are primarily used to + indicate specific processors and environments support the + instructions. The defined values are listed below. */ + unsigned flags; + + /* An array of operand codes. Each code is an index into the + operand table. They appear in the order which the operands must + appear in assembly code, and are terminated by a zero. */ + unsigned char operands[4]; +}; + +/* The table itself is sorted by major opcode number, and is otherwise + in the order in which the disassembler should consider + instructions. */ +extern const struct alpha_opcode alpha_opcodes[]; +extern const unsigned alpha_num_opcodes; + +/* Values defined for the flags field of a struct alpha_opcode. */ + +/* CPU Availability */ +#define AXP_OPCODE_BASE 0x0001 /* Base architecture -- all cpus. */ +#define AXP_OPCODE_EV4 0x0002 /* EV4 specific PALcode insns. */ +#define AXP_OPCODE_EV5 0x0004 /* EV5 specific PALcode insns. */ +#define AXP_OPCODE_EV6 0x0008 /* EV6 specific PALcode insns. */ +#define AXP_OPCODE_BWX 0x0100 /* Byte/word extension (amask bit 0). */ +#define AXP_OPCODE_CIX 0x0200 /* "Count" extension (amask bit 1). */ +#define AXP_OPCODE_MAX 0x0400 /* Multimedia extension (amask bit 8). */ + +#define AXP_OPCODE_NOPAL (~(AXP_OPCODE_EV4|AXP_OPCODE_EV5|AXP_OPCODE_EV6)) + +/* A macro to extract the major opcode from an instruction. */ +#define AXP_OP(i) (((i) >> 26) & 0x3F) + +/* The total number of major opcodes. */ +#define AXP_NOPS 0x40 + + +/* The operands table is an array of struct alpha_operand. */ + +struct alpha_operand +{ + /* The number of bits in the operand. */ + unsigned int bits : 5; + + /* How far the operand is left shifted in the instruction. */ + unsigned int shift : 5; + + /* The default relocation type for this operand. */ + signed int default_reloc : 16; + + /* One bit syntax flags. */ + unsigned int flags : 16; + + /* Insertion function. This is used by the assembler. To insert an + operand value into an instruction, check this field. + + If it is NULL, execute + i |= (op & ((1 << o->bits) - 1)) << o->shift; + (i is the instruction which we are filling in, o is a pointer to + this structure, and op is the opcode value; this assumes twos + complement arithmetic). + + If this field is not NULL, then simply call it with the + instruction and the operand value. It will return the new value + of the instruction. If the ERRMSG argument is not NULL, then if + the operand value is illegal, *ERRMSG will be set to a warning + string (the operand will be inserted in any case). If the + operand value is legal, *ERRMSG will be unchanged (most operands + can accept any value). */ + unsigned (*insert) (unsigned instruction, int op, + const char **errmsg); + + /* Extraction function. This is used by the disassembler. To + extract this operand type from an instruction, check this field. + + If it is NULL, compute + op = ((i) >> o->shift) & ((1 << o->bits) - 1); + if ((o->flags & AXP_OPERAND_SIGNED) != 0 + && (op & (1 << (o->bits - 1))) != 0) + op -= 1 << o->bits; + (i is the instruction, o is a pointer to this structure, and op + is the result; this assumes twos complement arithmetic). + + If this field is not NULL, then simply call it with the + instruction value. It will return the value of the operand. If + the INVALID argument is not NULL, *INVALID will be set to + non-zero if this operand type can not actually be extracted from + this operand (i.e., the instruction does not match). If the + operand is valid, *INVALID will not be changed. */ + int (*extract) (unsigned instruction, int *invalid); +}; + +/* Elements in the table are retrieved by indexing with values from + the operands field of the alpha_opcodes table. */ + +extern const struct alpha_operand alpha_operands[]; +extern const unsigned alpha_num_operands; + +/* Values defined for the flags field of a struct alpha_operand. */ + +/* Mask for selecting the type for typecheck purposes */ +#define AXP_OPERAND_TYPECHECK_MASK \ + (AXP_OPERAND_PARENS | AXP_OPERAND_COMMA | AXP_OPERAND_IR | \ + AXP_OPERAND_FPR | AXP_OPERAND_RELATIVE | AXP_OPERAND_SIGNED | \ + AXP_OPERAND_UNSIGNED) + +/* This operand does not actually exist in the assembler input. This + is used to support extended mnemonics, for which two operands fields + are identical. The assembler should call the insert function with + any op value. The disassembler should call the extract function, + ignore the return value, and check the value placed in the invalid + argument. */ +#define AXP_OPERAND_FAKE 01 + +/* The operand should be wrapped in parentheses rather than separated + from the previous by a comma. This is used for the load and store + instructions which want their operands to look like "Ra,disp(Rb)". */ +#define AXP_OPERAND_PARENS 02 + +/* Used in combination with PARENS, this suppresses the suppression of + the comma. This is used for "jmp Ra,(Rb),hint". */ +#define AXP_OPERAND_COMMA 04 + +/* This operand names an integer register. */ +#define AXP_OPERAND_IR 010 + +/* This operand names a floating point register. */ +#define AXP_OPERAND_FPR 020 + +/* This operand is a relative branch displacement. The disassembler + prints these symbolically if possible. */ +#define AXP_OPERAND_RELATIVE 040 + +/* This operand takes signed values. */ +#define AXP_OPERAND_SIGNED 0100 + +/* This operand takes unsigned values. This exists primarily so that + a flags value of 0 can be treated as end-of-arguments. */ +#define AXP_OPERAND_UNSIGNED 0200 + +/* Suppress overflow detection on this field. This is used for hints. */ +#define AXP_OPERAND_NOOVERFLOW 0400 + +/* Mask for optional argument default value. */ +#define AXP_OPERAND_OPTIONAL_MASK 07000 + +/* This operand defaults to zero. This is used for jump hints. */ +#define AXP_OPERAND_DEFAULT_ZERO 01000 + +/* This operand should default to the first (real) operand and is used + in conjunction with AXP_OPERAND_OPTIONAL. This allows + "and $0,3,$0" to be written as "and $0,3", etc. I don't like + it, but it's what DEC does. */ +#define AXP_OPERAND_DEFAULT_FIRST 02000 + +/* Similarly, this operand should default to the second (real) operand. + This allows "negl $0" instead of "negl $0,$0". */ +#define AXP_OPERAND_DEFAULT_SECOND 04000 + + +/* Register common names */ + +#define AXP_REG_V0 0 +#define AXP_REG_T0 1 +#define AXP_REG_T1 2 +#define AXP_REG_T2 3 +#define AXP_REG_T3 4 +#define AXP_REG_T4 5 +#define AXP_REG_T5 6 +#define AXP_REG_T6 7 +#define AXP_REG_T7 8 +#define AXP_REG_S0 9 +#define AXP_REG_S1 10 +#define AXP_REG_S2 11 +#define AXP_REG_S3 12 +#define AXP_REG_S4 13 +#define AXP_REG_S5 14 +#define AXP_REG_FP 15 +#define AXP_REG_A0 16 +#define AXP_REG_A1 17 +#define AXP_REG_A2 18 +#define AXP_REG_A3 19 +#define AXP_REG_A4 20 +#define AXP_REG_A5 21 +#define AXP_REG_T8 22 +#define AXP_REG_T9 23 +#define AXP_REG_T10 24 +#define AXP_REG_T11 25 +#define AXP_REG_RA 26 +#define AXP_REG_PV 27 +#define AXP_REG_T12 27 +#define AXP_REG_AT 28 +#define AXP_REG_GP 29 +#define AXP_REG_SP 30 +#define AXP_REG_ZERO 31 + +#define bfd_mach_alpha_ev4 0x10 +#define bfd_mach_alpha_ev5 0x20 +#define bfd_mach_alpha_ev6 0x30 + +enum bfd_reloc_code_real { + BFD_RELOC_23_PCREL_S2, + BFD_RELOC_ALPHA_HINT +}; + +/* This file holds the Alpha AXP opcode table. The opcode table includes + almost all of the extended instruction mnemonics. This permits the + disassembler to use them, and simplifies the assembler logic, at the + cost of increasing the table size. The table is strictly constant + data, so the compiler should be able to put it in the text segment. + + This file also holds the operand table. All knowledge about inserting + and extracting operands from instructions is kept in this file. + + The information for the base instruction set was compiled from the + _Alpha Architecture Handbook_, Digital Order Number EC-QD2KB-TE, + version 2. + + The information for the post-ev5 architecture extensions BWX, CIX and + MAX came from version 3 of this same document, which is also available + on-line at http://ftp.digital.com/pub/Digital/info/semiconductor + /literature/alphahb2.pdf + + The information for the EV4 PALcode instructions was compiled from + _DECchip 21064 and DECchip 21064A Alpha AXP Microprocessors Hardware + Reference Manual_, Digital Order Number EC-Q9ZUA-TE, preliminary + revision dated June 1994. + + The information for the EV5 PALcode instructions was compiled from + _Alpha 21164 Microprocessor Hardware Reference Manual_, Digital + Order Number EC-QAEQB-TE, preliminary revision dated April 1995. */ + +/* Local insertion and extraction functions */ + +static unsigned insert_rba (unsigned, int, const char **); +static unsigned insert_rca (unsigned, int, const char **); +static unsigned insert_za (unsigned, int, const char **); +static unsigned insert_zb (unsigned, int, const char **); +static unsigned insert_zc (unsigned, int, const char **); +static unsigned insert_bdisp (unsigned, int, const char **); +static unsigned insert_jhint (unsigned, int, const char **); +static unsigned insert_ev6hwjhint (unsigned, int, const char **); + +static int extract_rba (unsigned, int *); +static int extract_rca (unsigned, int *); +static int extract_za (unsigned, int *); +static int extract_zb (unsigned, int *); +static int extract_zc (unsigned, int *); +static int extract_bdisp (unsigned, int *); +static int extract_jhint (unsigned, int *); +static int extract_ev6hwjhint (unsigned, int *); + + +/* The operands table */ + +const struct alpha_operand alpha_operands[] = +{ + /* The fields are bits, shift, insert, extract, flags */ + /* The zero index is used to indicate end-of-list */ +#define UNUSED 0 + { 0, 0, 0, 0, 0, 0 }, + + /* The plain integer register fields */ +#define RA (UNUSED + 1) + { 5, 21, 0, AXP_OPERAND_IR, 0, 0 }, +#define RB (RA + 1) + { 5, 16, 0, AXP_OPERAND_IR, 0, 0 }, +#define RC (RB + 1) + { 5, 0, 0, AXP_OPERAND_IR, 0, 0 }, + + /* The plain fp register fields */ +#define FA (RC + 1) + { 5, 21, 0, AXP_OPERAND_FPR, 0, 0 }, +#define FB (FA + 1) + { 5, 16, 0, AXP_OPERAND_FPR, 0, 0 }, +#define FC (FB + 1) + { 5, 0, 0, AXP_OPERAND_FPR, 0, 0 }, + + /* The integer registers when they are ZERO */ +#define ZA (FC + 1) + { 5, 21, 0, AXP_OPERAND_FAKE, insert_za, extract_za }, +#define ZB (ZA + 1) + { 5, 16, 0, AXP_OPERAND_FAKE, insert_zb, extract_zb }, +#define ZC (ZB + 1) + { 5, 0, 0, AXP_OPERAND_FAKE, insert_zc, extract_zc }, + + /* The RB field when it needs parentheses */ +#define PRB (ZC + 1) + { 5, 16, 0, AXP_OPERAND_IR|AXP_OPERAND_PARENS, 0, 0 }, + + /* The RB field when it needs parentheses _and_ a preceding comma */ +#define CPRB (PRB + 1) + { 5, 16, 0, + AXP_OPERAND_IR|AXP_OPERAND_PARENS|AXP_OPERAND_COMMA, 0, 0 }, + + /* The RB field when it must be the same as the RA field */ +#define RBA (CPRB + 1) + { 5, 16, 0, AXP_OPERAND_FAKE, insert_rba, extract_rba }, + + /* The RC field when it must be the same as the RB field */ +#define RCA (RBA + 1) + { 5, 0, 0, AXP_OPERAND_FAKE, insert_rca, extract_rca }, + + /* The RC field when it can *default* to RA */ +#define DRC1 (RCA + 1) + { 5, 0, 0, + AXP_OPERAND_IR|AXP_OPERAND_DEFAULT_FIRST, 0, 0 }, + + /* The RC field when it can *default* to RB */ +#define DRC2 (DRC1 + 1) + { 5, 0, 0, + AXP_OPERAND_IR|AXP_OPERAND_DEFAULT_SECOND, 0, 0 }, + + /* The FC field when it can *default* to RA */ +#define DFC1 (DRC2 + 1) + { 5, 0, 0, + AXP_OPERAND_FPR|AXP_OPERAND_DEFAULT_FIRST, 0, 0 }, + + /* The FC field when it can *default* to RB */ +#define DFC2 (DFC1 + 1) + { 5, 0, 0, + AXP_OPERAND_FPR|AXP_OPERAND_DEFAULT_SECOND, 0, 0 }, + + /* The unsigned 8-bit literal of Operate format insns */ +#define LIT (DFC2 + 1) + { 8, 13, -LIT, AXP_OPERAND_UNSIGNED, 0, 0 }, + + /* The signed 16-bit displacement of Memory format insns. From here + we can't tell what relocation should be used, so don't use a default. */ +#define MDISP (LIT + 1) + { 16, 0, -MDISP, AXP_OPERAND_SIGNED, 0, 0 }, + + /* The signed "23-bit" aligned displacement of Branch format insns */ +#define BDISP (MDISP + 1) + { 21, 0, BFD_RELOC_23_PCREL_S2, + AXP_OPERAND_RELATIVE, insert_bdisp, extract_bdisp }, + + /* The 26-bit PALcode function */ +#define PALFN (BDISP + 1) + { 26, 0, -PALFN, AXP_OPERAND_UNSIGNED, 0, 0 }, + + /* The optional signed "16-bit" aligned displacement of the JMP/JSR hint */ +#define JMPHINT (PALFN + 1) + { 14, 0, BFD_RELOC_ALPHA_HINT, + AXP_OPERAND_RELATIVE|AXP_OPERAND_DEFAULT_ZERO|AXP_OPERAND_NOOVERFLOW, + insert_jhint, extract_jhint }, + + /* The optional hint to RET/JSR_COROUTINE */ +#define RETHINT (JMPHINT + 1) + { 14, 0, -RETHINT, + AXP_OPERAND_UNSIGNED|AXP_OPERAND_DEFAULT_ZERO, 0, 0 }, + + /* The 12-bit displacement for the ev[46] hw_{ld,st} (pal1b/pal1f) insns */ +#define EV4HWDISP (RETHINT + 1) +#define EV6HWDISP (EV4HWDISP) + { 12, 0, -EV4HWDISP, AXP_OPERAND_SIGNED, 0, 0 }, + + /* The 5-bit index for the ev4 hw_m[ft]pr (pal19/pal1d) insns */ +#define EV4HWINDEX (EV4HWDISP + 1) + { 5, 0, -EV4HWINDEX, AXP_OPERAND_UNSIGNED, 0, 0 }, + + /* The 8-bit index for the oddly unqualified hw_m[tf]pr insns + that occur in DEC PALcode. */ +#define EV4EXTHWINDEX (EV4HWINDEX + 1) + { 8, 0, -EV4EXTHWINDEX, AXP_OPERAND_UNSIGNED, 0, 0 }, + + /* The 10-bit displacement for the ev5 hw_{ld,st} (pal1b/pal1f) insns */ +#define EV5HWDISP (EV4EXTHWINDEX + 1) + { 10, 0, -EV5HWDISP, AXP_OPERAND_SIGNED, 0, 0 }, + + /* The 16-bit index for the ev5 hw_m[ft]pr (pal19/pal1d) insns */ +#define EV5HWINDEX (EV5HWDISP + 1) + { 16, 0, -EV5HWINDEX, AXP_OPERAND_UNSIGNED, 0, 0 }, + + /* The 16-bit combined index/scoreboard mask for the ev6 + hw_m[ft]pr (pal19/pal1d) insns */ +#define EV6HWINDEX (EV5HWINDEX + 1) + { 16, 0, -EV6HWINDEX, AXP_OPERAND_UNSIGNED, 0, 0 }, + + /* The 13-bit branch hint for the ev6 hw_jmp/jsr (pal1e) insn */ +#define EV6HWJMPHINT (EV6HWINDEX+ 1) + { 8, 0, -EV6HWJMPHINT, + AXP_OPERAND_RELATIVE|AXP_OPERAND_DEFAULT_ZERO|AXP_OPERAND_NOOVERFLOW, + insert_ev6hwjhint, extract_ev6hwjhint } +}; + +const unsigned alpha_num_operands = sizeof(alpha_operands)/sizeof(*alpha_operands); + +/* The RB field when it is the same as the RA field in the same insn. + This operand is marked fake. The insertion function just copies + the RA field into the RB field, and the extraction function just + checks that the fields are the same. */ + +/*ARGSUSED*/ +static unsigned +insert_rba(unsigned insn, int value ATTRIBUTE_UNUSED, const char **errmsg ATTRIBUTE_UNUSED) +{ + return insn | (((insn >> 21) & 0x1f) << 16); +} + +static int +extract_rba(unsigned insn, int *invalid) +{ + if (invalid != (int *) NULL + && ((insn >> 21) & 0x1f) != ((insn >> 16) & 0x1f)) + *invalid = 1; + return 0; +} + + +/* The same for the RC field */ + +/*ARGSUSED*/ +static unsigned +insert_rca(unsigned insn, int value ATTRIBUTE_UNUSED, const char **errmsg ATTRIBUTE_UNUSED) +{ + return insn | ((insn >> 21) & 0x1f); +} + +static int +extract_rca(unsigned insn, int *invalid) +{ + if (invalid != (int *) NULL + && ((insn >> 21) & 0x1f) != (insn & 0x1f)) + *invalid = 1; + return 0; +} + + +/* Fake arguments in which the registers must be set to ZERO */ + +/*ARGSUSED*/ +static unsigned +insert_za(unsigned insn, int value ATTRIBUTE_UNUSED, const char **errmsg ATTRIBUTE_UNUSED) +{ + return insn | (31 << 21); +} + +static int +extract_za(unsigned insn, int *invalid) +{ + if (invalid != (int *) NULL && ((insn >> 21) & 0x1f) != 31) + *invalid = 1; + return 0; +} + +/*ARGSUSED*/ +static unsigned +insert_zb(unsigned insn, int value ATTRIBUTE_UNUSED, const char **errmsg ATTRIBUTE_UNUSED) +{ + return insn | (31 << 16); +} + +static int +extract_zb(unsigned insn, int *invalid) +{ + if (invalid != (int *) NULL && ((insn >> 16) & 0x1f) != 31) + *invalid = 1; + return 0; +} + +/*ARGSUSED*/ +static unsigned +insert_zc(unsigned insn, int value ATTRIBUTE_UNUSED, const char **errmsg ATTRIBUTE_UNUSED) +{ + return insn | 31; +} + +static int +extract_zc(unsigned insn, int *invalid) +{ + if (invalid != (int *) NULL && (insn & 0x1f) != 31) + *invalid = 1; + return 0; +} + + +/* The displacement field of a Branch format insn. */ + +static unsigned +insert_bdisp(unsigned insn, int value, const char **errmsg) +{ + if (errmsg != (const char **)NULL && (value & 3)) + *errmsg = _("branch operand unaligned"); + return insn | ((value / 4) & 0x1FFFFF); +} + +/*ARGSUSED*/ +static int +extract_bdisp(unsigned insn, int *invalid ATTRIBUTE_UNUSED) +{ + return 4 * (((insn & 0x1FFFFF) ^ 0x100000) - 0x100000); +} + + +/* The hint field of a JMP/JSR insn. */ + +static unsigned +insert_jhint(unsigned insn, int value, const char **errmsg) +{ + if (errmsg != (const char **)NULL && (value & 3)) + *errmsg = _("jump hint unaligned"); + return insn | ((value / 4) & 0x3FFF); +} + +/*ARGSUSED*/ +static int +extract_jhint(unsigned insn, int *invalid ATTRIBUTE_UNUSED) +{ + return 4 * (((insn & 0x3FFF) ^ 0x2000) - 0x2000); +} + +/* The hint field of an EV6 HW_JMP/JSR insn. */ + +static unsigned +insert_ev6hwjhint(unsigned insn, int value, const char **errmsg) +{ + if (errmsg != (const char **)NULL && (value & 3)) + *errmsg = _("jump hint unaligned"); + return insn | ((value / 4) & 0x1FFF); +} + +/*ARGSUSED*/ +static int +extract_ev6hwjhint(unsigned insn, int *invalid ATTRIBUTE_UNUSED) +{ + return 4 * (((insn & 0x1FFF) ^ 0x1000) - 0x1000); +} + + +/* Macros used to form opcodes */ + +/* The main opcode */ +#define OP(x) (((x) & 0x3F) << 26) +#define OP_MASK 0xFC000000 + +/* Branch format instructions */ +#define BRA_(oo) OP(oo) +#define BRA_MASK OP_MASK +#define BRA(oo) BRA_(oo), BRA_MASK + +/* Floating point format instructions */ +#define FP_(oo,fff) (OP(oo) | (((fff) & 0x7FF) << 5)) +#define FP_MASK (OP_MASK | 0xFFE0) +#define FP(oo,fff) FP_(oo,fff), FP_MASK + +/* Memory format instructions */ +#define MEM_(oo) OP(oo) +#define MEM_MASK OP_MASK +#define MEM(oo) MEM_(oo), MEM_MASK + +/* Memory/Func Code format instructions */ +#define MFC_(oo,ffff) (OP(oo) | ((ffff) & 0xFFFF)) +#define MFC_MASK (OP_MASK | 0xFFFF) +#define MFC(oo,ffff) MFC_(oo,ffff), MFC_MASK + +/* Memory/Branch format instructions */ +#define MBR_(oo,h) (OP(oo) | (((h) & 3) << 14)) +#define MBR_MASK (OP_MASK | 0xC000) +#define MBR(oo,h) MBR_(oo,h), MBR_MASK + +/* Operate format instructions. The OPRL variant specifies a + literal second argument. */ +#define OPR_(oo,ff) (OP(oo) | (((ff) & 0x7F) << 5)) +#define OPRL_(oo,ff) (OPR_((oo),(ff)) | 0x1000) +#define OPR_MASK (OP_MASK | 0x1FE0) +#define OPR(oo,ff) OPR_(oo,ff), OPR_MASK +#define OPRL(oo,ff) OPRL_(oo,ff), OPR_MASK + +/* Generic PALcode format instructions */ +#define PCD_(oo) OP(oo) +#define PCD_MASK OP_MASK +#define PCD(oo) PCD_(oo), PCD_MASK + +/* Specific PALcode instructions */ +#define SPCD_(oo,ffff) (OP(oo) | ((ffff) & 0x3FFFFFF)) +#define SPCD_MASK 0xFFFFFFFF +#define SPCD(oo,ffff) SPCD_(oo,ffff), SPCD_MASK + +/* Hardware memory (hw_{ld,st}) instructions */ +#define EV4HWMEM_(oo,f) (OP(oo) | (((f) & 0xF) << 12)) +#define EV4HWMEM_MASK (OP_MASK | 0xF000) +#define EV4HWMEM(oo,f) EV4HWMEM_(oo,f), EV4HWMEM_MASK + +#define EV5HWMEM_(oo,f) (OP(oo) | (((f) & 0x3F) << 10)) +#define EV5HWMEM_MASK (OP_MASK | 0xF800) +#define EV5HWMEM(oo,f) EV5HWMEM_(oo,f), EV5HWMEM_MASK + +#define EV6HWMEM_(oo,f) (OP(oo) | (((f) & 0xF) << 12)) +#define EV6HWMEM_MASK (OP_MASK | 0xF000) +#define EV6HWMEM(oo,f) EV6HWMEM_(oo,f), EV6HWMEM_MASK + +#define EV6HWMBR_(oo,h) (OP(oo) | (((h) & 7) << 13)) +#define EV6HWMBR_MASK (OP_MASK | 0xE000) +#define EV6HWMBR(oo,h) EV6HWMBR_(oo,h), EV6HWMBR_MASK + +/* Abbreviations for instruction subsets. */ +#define BASE AXP_OPCODE_BASE +#define EV4 AXP_OPCODE_EV4 +#define EV5 AXP_OPCODE_EV5 +#define EV6 AXP_OPCODE_EV6 +#define BWX AXP_OPCODE_BWX +#define CIX AXP_OPCODE_CIX +#define MAX AXP_OPCODE_MAX + +/* Common combinations of arguments */ +#define ARG_NONE { 0 } +#define ARG_BRA { RA, BDISP } +#define ARG_FBRA { FA, BDISP } +#define ARG_FP { FA, FB, DFC1 } +#define ARG_FPZ1 { ZA, FB, DFC1 } +#define ARG_MEM { RA, MDISP, PRB } +#define ARG_FMEM { FA, MDISP, PRB } +#define ARG_OPR { RA, RB, DRC1 } +#define ARG_OPRL { RA, LIT, DRC1 } +#define ARG_OPRZ1 { ZA, RB, DRC1 } +#define ARG_OPRLZ1 { ZA, LIT, RC } +#define ARG_PCD { PALFN } +#define ARG_EV4HWMEM { RA, EV4HWDISP, PRB } +#define ARG_EV4HWMPR { RA, RBA, EV4HWINDEX } +#define ARG_EV5HWMEM { RA, EV5HWDISP, PRB } +#define ARG_EV6HWMEM { RA, EV6HWDISP, PRB } + +/* The opcode table. + + The format of the opcode table is: + + NAME OPCODE MASK { OPERANDS } + + NAME is the name of the instruction. + + OPCODE is the instruction opcode. + + MASK is the opcode mask; this is used to tell the disassembler + which bits in the actual opcode must match OPCODE. + + OPERANDS is the list of operands. + + The preceding macros merge the text of the OPCODE and MASK fields. + + The disassembler reads the table in order and prints the first + instruction which matches, so this table is sorted to put more + specific instructions before more general instructions. + + Otherwise, it is sorted by major opcode and minor function code. + + There are three classes of not-really-instructions in this table: + + ALIAS is another name for another instruction. Some of + these come from the Architecture Handbook, some + come from the original gas opcode tables. In all + cases, the functionality of the opcode is unchanged. + + PSEUDO a stylized code form endorsed by Chapter A.4 of the + Architecture Handbook. + + EXTRA a stylized code form found in the original gas tables. + + And two annotations: + + EV56 BUT opcodes that are officially introduced as of the ev56, + but with defined results on previous implementations. + + EV56 UNA opcodes that were introduced as of the ev56 with + presumably undefined results on previous implementations + that were not assigned to a particular extension. +*/ + +const struct alpha_opcode alpha_opcodes[] = { + { "halt", SPCD(0x00,0x0000), BASE, ARG_NONE }, + { "draina", SPCD(0x00,0x0002), BASE, ARG_NONE }, + { "bpt", SPCD(0x00,0x0080), BASE, ARG_NONE }, + { "bugchk", SPCD(0x00,0x0081), BASE, ARG_NONE }, + { "callsys", SPCD(0x00,0x0083), BASE, ARG_NONE }, + { "chmk", SPCD(0x00,0x0083), BASE, ARG_NONE }, + { "imb", SPCD(0x00,0x0086), BASE, ARG_NONE }, + { "rduniq", SPCD(0x00,0x009e), BASE, ARG_NONE }, + { "wruniq", SPCD(0x00,0x009f), BASE, ARG_NONE }, + { "gentrap", SPCD(0x00,0x00aa), BASE, ARG_NONE }, + { "call_pal", PCD(0x00), BASE, ARG_PCD }, + { "pal", PCD(0x00), BASE, ARG_PCD }, /* alias */ + + { "lda", MEM(0x08), BASE, { RA, MDISP, ZB } }, /* pseudo */ + { "lda", MEM(0x08), BASE, ARG_MEM }, + { "ldah", MEM(0x09), BASE, { RA, MDISP, ZB } }, /* pseudo */ + { "ldah", MEM(0x09), BASE, ARG_MEM }, + { "ldbu", MEM(0x0A), BWX, ARG_MEM }, + { "unop", MEM_(0x0B) | (30 << 16), + MEM_MASK, BASE, { ZA } }, /* pseudo */ + { "ldq_u", MEM(0x0B), BASE, ARG_MEM }, + { "ldwu", MEM(0x0C), BWX, ARG_MEM }, + { "stw", MEM(0x0D), BWX, ARG_MEM }, + { "stb", MEM(0x0E), BWX, ARG_MEM }, + { "stq_u", MEM(0x0F), BASE, ARG_MEM }, + + { "sextl", OPR(0x10,0x00), BASE, ARG_OPRZ1 }, /* pseudo */ + { "sextl", OPRL(0x10,0x00), BASE, ARG_OPRLZ1 }, /* pseudo */ + { "addl", OPR(0x10,0x00), BASE, ARG_OPR }, + { "addl", OPRL(0x10,0x00), BASE, ARG_OPRL }, + { "s4addl", OPR(0x10,0x02), BASE, ARG_OPR }, + { "s4addl", OPRL(0x10,0x02), BASE, ARG_OPRL }, + { "negl", OPR(0x10,0x09), BASE, ARG_OPRZ1 }, /* pseudo */ + { "negl", OPRL(0x10,0x09), BASE, ARG_OPRLZ1 }, /* pseudo */ + { "subl", OPR(0x10,0x09), BASE, ARG_OPR }, + { "subl", OPRL(0x10,0x09), BASE, ARG_OPRL }, + { "s4subl", OPR(0x10,0x0B), BASE, ARG_OPR }, + { "s4subl", OPRL(0x10,0x0B), BASE, ARG_OPRL }, + { "cmpbge", OPR(0x10,0x0F), BASE, ARG_OPR }, + { "cmpbge", OPRL(0x10,0x0F), BASE, ARG_OPRL }, + { "s8addl", OPR(0x10,0x12), BASE, ARG_OPR }, + { "s8addl", OPRL(0x10,0x12), BASE, ARG_OPRL }, + { "s8subl", OPR(0x10,0x1B), BASE, ARG_OPR }, + { "s8subl", OPRL(0x10,0x1B), BASE, ARG_OPRL }, + { "cmpult", OPR(0x10,0x1D), BASE, ARG_OPR }, + { "cmpult", OPRL(0x10,0x1D), BASE, ARG_OPRL }, + { "addq", OPR(0x10,0x20), BASE, ARG_OPR }, + { "addq", OPRL(0x10,0x20), BASE, ARG_OPRL }, + { "s4addq", OPR(0x10,0x22), BASE, ARG_OPR }, + { "s4addq", OPRL(0x10,0x22), BASE, ARG_OPRL }, + { "negq", OPR(0x10,0x29), BASE, ARG_OPRZ1 }, /* pseudo */ + { "negq", OPRL(0x10,0x29), BASE, ARG_OPRLZ1 }, /* pseudo */ + { "subq", OPR(0x10,0x29), BASE, ARG_OPR }, + { "subq", OPRL(0x10,0x29), BASE, ARG_OPRL }, + { "s4subq", OPR(0x10,0x2B), BASE, ARG_OPR }, + { "s4subq", OPRL(0x10,0x2B), BASE, ARG_OPRL }, + { "cmpeq", OPR(0x10,0x2D), BASE, ARG_OPR }, + { "cmpeq", OPRL(0x10,0x2D), BASE, ARG_OPRL }, + { "s8addq", OPR(0x10,0x32), BASE, ARG_OPR }, + { "s8addq", OPRL(0x10,0x32), BASE, ARG_OPRL }, + { "s8subq", OPR(0x10,0x3B), BASE, ARG_OPR }, + { "s8subq", OPRL(0x10,0x3B), BASE, ARG_OPRL }, + { "cmpule", OPR(0x10,0x3D), BASE, ARG_OPR }, + { "cmpule", OPRL(0x10,0x3D), BASE, ARG_OPRL }, + { "addl/v", OPR(0x10,0x40), BASE, ARG_OPR }, + { "addl/v", OPRL(0x10,0x40), BASE, ARG_OPRL }, + { "negl/v", OPR(0x10,0x49), BASE, ARG_OPRZ1 }, /* pseudo */ + { "negl/v", OPRL(0x10,0x49), BASE, ARG_OPRLZ1 }, /* pseudo */ + { "subl/v", OPR(0x10,0x49), BASE, ARG_OPR }, + { "subl/v", OPRL(0x10,0x49), BASE, ARG_OPRL }, + { "cmplt", OPR(0x10,0x4D), BASE, ARG_OPR }, + { "cmplt", OPRL(0x10,0x4D), BASE, ARG_OPRL }, + { "addq/v", OPR(0x10,0x60), BASE, ARG_OPR }, + { "addq/v", OPRL(0x10,0x60), BASE, ARG_OPRL }, + { "negq/v", OPR(0x10,0x69), BASE, ARG_OPRZ1 }, /* pseudo */ + { "negq/v", OPRL(0x10,0x69), BASE, ARG_OPRLZ1 }, /* pseudo */ + { "subq/v", OPR(0x10,0x69), BASE, ARG_OPR }, + { "subq/v", OPRL(0x10,0x69), BASE, ARG_OPRL }, + { "cmple", OPR(0x10,0x6D), BASE, ARG_OPR }, + { "cmple", OPRL(0x10,0x6D), BASE, ARG_OPRL }, + + { "and", OPR(0x11,0x00), BASE, ARG_OPR }, + { "and", OPRL(0x11,0x00), BASE, ARG_OPRL }, + { "andnot", OPR(0x11,0x08), BASE, ARG_OPR }, /* alias */ + { "andnot", OPRL(0x11,0x08), BASE, ARG_OPRL }, /* alias */ + { "bic", OPR(0x11,0x08), BASE, ARG_OPR }, + { "bic", OPRL(0x11,0x08), BASE, ARG_OPRL }, + { "cmovlbs", OPR(0x11,0x14), BASE, ARG_OPR }, + { "cmovlbs", OPRL(0x11,0x14), BASE, ARG_OPRL }, + { "cmovlbc", OPR(0x11,0x16), BASE, ARG_OPR }, + { "cmovlbc", OPRL(0x11,0x16), BASE, ARG_OPRL }, + { "nop", OPR(0x11,0x20), BASE, { ZA, ZB, ZC } }, /* pseudo */ + { "clr", OPR(0x11,0x20), BASE, { ZA, ZB, RC } }, /* pseudo */ + { "mov", OPR(0x11,0x20), BASE, { ZA, RB, RC } }, /* pseudo */ + { "mov", OPR(0x11,0x20), BASE, { RA, RBA, RC } }, /* pseudo */ + { "mov", OPRL(0x11,0x20), BASE, { ZA, LIT, RC } }, /* pseudo */ + { "or", OPR(0x11,0x20), BASE, ARG_OPR }, /* alias */ + { "or", OPRL(0x11,0x20), BASE, ARG_OPRL }, /* alias */ + { "bis", OPR(0x11,0x20), BASE, ARG_OPR }, + { "bis", OPRL(0x11,0x20), BASE, ARG_OPRL }, + { "cmoveq", OPR(0x11,0x24), BASE, ARG_OPR }, + { "cmoveq", OPRL(0x11,0x24), BASE, ARG_OPRL }, + { "cmovne", OPR(0x11,0x26), BASE, ARG_OPR }, + { "cmovne", OPRL(0x11,0x26), BASE, ARG_OPRL }, + { "not", OPR(0x11,0x28), BASE, ARG_OPRZ1 }, /* pseudo */ + { "not", OPRL(0x11,0x28), BASE, ARG_OPRLZ1 }, /* pseudo */ + { "ornot", OPR(0x11,0x28), BASE, ARG_OPR }, + { "ornot", OPRL(0x11,0x28), BASE, ARG_OPRL }, + { "xor", OPR(0x11,0x40), BASE, ARG_OPR }, + { "xor", OPRL(0x11,0x40), BASE, ARG_OPRL }, + { "cmovlt", OPR(0x11,0x44), BASE, ARG_OPR }, + { "cmovlt", OPRL(0x11,0x44), BASE, ARG_OPRL }, + { "cmovge", OPR(0x11,0x46), BASE, ARG_OPR }, + { "cmovge", OPRL(0x11,0x46), BASE, ARG_OPRL }, + { "eqv", OPR(0x11,0x48), BASE, ARG_OPR }, + { "eqv", OPRL(0x11,0x48), BASE, ARG_OPRL }, + { "xornot", OPR(0x11,0x48), BASE, ARG_OPR }, /* alias */ + { "xornot", OPRL(0x11,0x48), BASE, ARG_OPRL }, /* alias */ + { "amask", OPR(0x11,0x61), BASE, ARG_OPRZ1 }, /* ev56 but */ + { "amask", OPRL(0x11,0x61), BASE, ARG_OPRLZ1 }, /* ev56 but */ + { "cmovle", OPR(0x11,0x64), BASE, ARG_OPR }, + { "cmovle", OPRL(0x11,0x64), BASE, ARG_OPRL }, + { "cmovgt", OPR(0x11,0x66), BASE, ARG_OPR }, + { "cmovgt", OPRL(0x11,0x66), BASE, ARG_OPRL }, + { "implver", OPRL_(0x11,0x6C)|(31<<21)|(1<<13), + 0xFFFFFFE0, BASE, { RC } }, /* ev56 but */ + + { "mskbl", OPR(0x12,0x02), BASE, ARG_OPR }, + { "mskbl", OPRL(0x12,0x02), BASE, ARG_OPRL }, + { "extbl", OPR(0x12,0x06), BASE, ARG_OPR }, + { "extbl", OPRL(0x12,0x06), BASE, ARG_OPRL }, + { "insbl", OPR(0x12,0x0B), BASE, ARG_OPR }, + { "insbl", OPRL(0x12,0x0B), BASE, ARG_OPRL }, + { "mskwl", OPR(0x12,0x12), BASE, ARG_OPR }, + { "mskwl", OPRL(0x12,0x12), BASE, ARG_OPRL }, + { "extwl", OPR(0x12,0x16), BASE, ARG_OPR }, + { "extwl", OPRL(0x12,0x16), BASE, ARG_OPRL }, + { "inswl", OPR(0x12,0x1B), BASE, ARG_OPR }, + { "inswl", OPRL(0x12,0x1B), BASE, ARG_OPRL }, + { "mskll", OPR(0x12,0x22), BASE, ARG_OPR }, + { "mskll", OPRL(0x12,0x22), BASE, ARG_OPRL }, + { "extll", OPR(0x12,0x26), BASE, ARG_OPR }, + { "extll", OPRL(0x12,0x26), BASE, ARG_OPRL }, + { "insll", OPR(0x12,0x2B), BASE, ARG_OPR }, + { "insll", OPRL(0x12,0x2B), BASE, ARG_OPRL }, + { "zap", OPR(0x12,0x30), BASE, ARG_OPR }, + { "zap", OPRL(0x12,0x30), BASE, ARG_OPRL }, + { "zapnot", OPR(0x12,0x31), BASE, ARG_OPR }, + { "zapnot", OPRL(0x12,0x31), BASE, ARG_OPRL }, + { "mskql", OPR(0x12,0x32), BASE, ARG_OPR }, + { "mskql", OPRL(0x12,0x32), BASE, ARG_OPRL }, + { "srl", OPR(0x12,0x34), BASE, ARG_OPR }, + { "srl", OPRL(0x12,0x34), BASE, ARG_OPRL }, + { "extql", OPR(0x12,0x36), BASE, ARG_OPR }, + { "extql", OPRL(0x12,0x36), BASE, ARG_OPRL }, + { "sll", OPR(0x12,0x39), BASE, ARG_OPR }, + { "sll", OPRL(0x12,0x39), BASE, ARG_OPRL }, + { "insql", OPR(0x12,0x3B), BASE, ARG_OPR }, + { "insql", OPRL(0x12,0x3B), BASE, ARG_OPRL }, + { "sra", OPR(0x12,0x3C), BASE, ARG_OPR }, + { "sra", OPRL(0x12,0x3C), BASE, ARG_OPRL }, + { "mskwh", OPR(0x12,0x52), BASE, ARG_OPR }, + { "mskwh", OPRL(0x12,0x52), BASE, ARG_OPRL }, + { "inswh", OPR(0x12,0x57), BASE, ARG_OPR }, + { "inswh", OPRL(0x12,0x57), BASE, ARG_OPRL }, + { "extwh", OPR(0x12,0x5A), BASE, ARG_OPR }, + { "extwh", OPRL(0x12,0x5A), BASE, ARG_OPRL }, + { "msklh", OPR(0x12,0x62), BASE, ARG_OPR }, + { "msklh", OPRL(0x12,0x62), BASE, ARG_OPRL }, + { "inslh", OPR(0x12,0x67), BASE, ARG_OPR }, + { "inslh", OPRL(0x12,0x67), BASE, ARG_OPRL }, + { "extlh", OPR(0x12,0x6A), BASE, ARG_OPR }, + { "extlh", OPRL(0x12,0x6A), BASE, ARG_OPRL }, + { "mskqh", OPR(0x12,0x72), BASE, ARG_OPR }, + { "mskqh", OPRL(0x12,0x72), BASE, ARG_OPRL }, + { "insqh", OPR(0x12,0x77), BASE, ARG_OPR }, + { "insqh", OPRL(0x12,0x77), BASE, ARG_OPRL }, + { "extqh", OPR(0x12,0x7A), BASE, ARG_OPR }, + { "extqh", OPRL(0x12,0x7A), BASE, ARG_OPRL }, + + { "mull", OPR(0x13,0x00), BASE, ARG_OPR }, + { "mull", OPRL(0x13,0x00), BASE, ARG_OPRL }, + { "mulq", OPR(0x13,0x20), BASE, ARG_OPR }, + { "mulq", OPRL(0x13,0x20), BASE, ARG_OPRL }, + { "umulh", OPR(0x13,0x30), BASE, ARG_OPR }, + { "umulh", OPRL(0x13,0x30), BASE, ARG_OPRL }, + { "mull/v", OPR(0x13,0x40), BASE, ARG_OPR }, + { "mull/v", OPRL(0x13,0x40), BASE, ARG_OPRL }, + { "mulq/v", OPR(0x13,0x60), BASE, ARG_OPR }, + { "mulq/v", OPRL(0x13,0x60), BASE, ARG_OPRL }, + + { "itofs", FP(0x14,0x004), CIX, { RA, ZB, FC } }, + { "sqrtf/c", FP(0x14,0x00A), CIX, ARG_FPZ1 }, + { "sqrts/c", FP(0x14,0x00B), CIX, ARG_FPZ1 }, + { "itoff", FP(0x14,0x014), CIX, { RA, ZB, FC } }, + { "itoft", FP(0x14,0x024), CIX, { RA, ZB, FC } }, + { "sqrtg/c", FP(0x14,0x02A), CIX, ARG_FPZ1 }, + { "sqrtt/c", FP(0x14,0x02B), CIX, ARG_FPZ1 }, + { "sqrts/m", FP(0x14,0x04B), CIX, ARG_FPZ1 }, + { "sqrtt/m", FP(0x14,0x06B), CIX, ARG_FPZ1 }, + { "sqrtf", FP(0x14,0x08A), CIX, ARG_FPZ1 }, + { "sqrts", FP(0x14,0x08B), CIX, ARG_FPZ1 }, + { "sqrtg", FP(0x14,0x0AA), CIX, ARG_FPZ1 }, + { "sqrtt", FP(0x14,0x0AB), CIX, ARG_FPZ1 }, + { "sqrts/d", FP(0x14,0x0CB), CIX, ARG_FPZ1 }, + { "sqrtt/d", FP(0x14,0x0EB), CIX, ARG_FPZ1 }, + { "sqrtf/uc", FP(0x14,0x10A), CIX, ARG_FPZ1 }, + { "sqrts/uc", FP(0x14,0x10B), CIX, ARG_FPZ1 }, + { "sqrtg/uc", FP(0x14,0x12A), CIX, ARG_FPZ1 }, + { "sqrtt/uc", FP(0x14,0x12B), CIX, ARG_FPZ1 }, + { "sqrts/um", FP(0x14,0x14B), CIX, ARG_FPZ1 }, + { "sqrtt/um", FP(0x14,0x16B), CIX, ARG_FPZ1 }, + { "sqrtf/u", FP(0x14,0x18A), CIX, ARG_FPZ1 }, + { "sqrts/u", FP(0x14,0x18B), CIX, ARG_FPZ1 }, + { "sqrtg/u", FP(0x14,0x1AA), CIX, ARG_FPZ1 }, + { "sqrtt/u", FP(0x14,0x1AB), CIX, ARG_FPZ1 }, + { "sqrts/ud", FP(0x14,0x1CB), CIX, ARG_FPZ1 }, + { "sqrtt/ud", FP(0x14,0x1EB), CIX, ARG_FPZ1 }, + { "sqrtf/sc", FP(0x14,0x40A), CIX, ARG_FPZ1 }, + { "sqrtg/sc", FP(0x14,0x42A), CIX, ARG_FPZ1 }, + { "sqrtf/s", FP(0x14,0x48A), CIX, ARG_FPZ1 }, + { "sqrtg/s", FP(0x14,0x4AA), CIX, ARG_FPZ1 }, + { "sqrtf/suc", FP(0x14,0x50A), CIX, ARG_FPZ1 }, + { "sqrts/suc", FP(0x14,0x50B), CIX, ARG_FPZ1 }, + { "sqrtg/suc", FP(0x14,0x52A), CIX, ARG_FPZ1 }, + { "sqrtt/suc", FP(0x14,0x52B), CIX, ARG_FPZ1 }, + { "sqrts/sum", FP(0x14,0x54B), CIX, ARG_FPZ1 }, + { "sqrtt/sum", FP(0x14,0x56B), CIX, ARG_FPZ1 }, + { "sqrtf/su", FP(0x14,0x58A), CIX, ARG_FPZ1 }, + { "sqrts/su", FP(0x14,0x58B), CIX, ARG_FPZ1 }, + { "sqrtg/su", FP(0x14,0x5AA), CIX, ARG_FPZ1 }, + { "sqrtt/su", FP(0x14,0x5AB), CIX, ARG_FPZ1 }, + { "sqrts/sud", FP(0x14,0x5CB), CIX, ARG_FPZ1 }, + { "sqrtt/sud", FP(0x14,0x5EB), CIX, ARG_FPZ1 }, + { "sqrts/suic", FP(0x14,0x70B), CIX, ARG_FPZ1 }, + { "sqrtt/suic", FP(0x14,0x72B), CIX, ARG_FPZ1 }, + { "sqrts/suim", FP(0x14,0x74B), CIX, ARG_FPZ1 }, + { "sqrtt/suim", FP(0x14,0x76B), CIX, ARG_FPZ1 }, + { "sqrts/sui", FP(0x14,0x78B), CIX, ARG_FPZ1 }, + { "sqrtt/sui", FP(0x14,0x7AB), CIX, ARG_FPZ1 }, + { "sqrts/suid", FP(0x14,0x7CB), CIX, ARG_FPZ1 }, + { "sqrtt/suid", FP(0x14,0x7EB), CIX, ARG_FPZ1 }, + + { "addf/c", FP(0x15,0x000), BASE, ARG_FP }, + { "subf/c", FP(0x15,0x001), BASE, ARG_FP }, + { "mulf/c", FP(0x15,0x002), BASE, ARG_FP }, + { "divf/c", FP(0x15,0x003), BASE, ARG_FP }, + { "cvtdg/c", FP(0x15,0x01E), BASE, ARG_FPZ1 }, + { "addg/c", FP(0x15,0x020), BASE, ARG_FP }, + { "subg/c", FP(0x15,0x021), BASE, ARG_FP }, + { "mulg/c", FP(0x15,0x022), BASE, ARG_FP }, + { "divg/c", FP(0x15,0x023), BASE, ARG_FP }, + { "cvtgf/c", FP(0x15,0x02C), BASE, ARG_FPZ1 }, + { "cvtgd/c", FP(0x15,0x02D), BASE, ARG_FPZ1 }, + { "cvtgq/c", FP(0x15,0x02F), BASE, ARG_FPZ1 }, + { "cvtqf/c", FP(0x15,0x03C), BASE, ARG_FPZ1 }, + { "cvtqg/c", FP(0x15,0x03E), BASE, ARG_FPZ1 }, + { "addf", FP(0x15,0x080), BASE, ARG_FP }, + { "negf", FP(0x15,0x081), BASE, ARG_FPZ1 }, /* pseudo */ + { "subf", FP(0x15,0x081), BASE, ARG_FP }, + { "mulf", FP(0x15,0x082), BASE, ARG_FP }, + { "divf", FP(0x15,0x083), BASE, ARG_FP }, + { "cvtdg", FP(0x15,0x09E), BASE, ARG_FPZ1 }, + { "addg", FP(0x15,0x0A0), BASE, ARG_FP }, + { "negg", FP(0x15,0x0A1), BASE, ARG_FPZ1 }, /* pseudo */ + { "subg", FP(0x15,0x0A1), BASE, ARG_FP }, + { "mulg", FP(0x15,0x0A2), BASE, ARG_FP }, + { "divg", FP(0x15,0x0A3), BASE, ARG_FP }, + { "cmpgeq", FP(0x15,0x0A5), BASE, ARG_FP }, + { "cmpglt", FP(0x15,0x0A6), BASE, ARG_FP }, + { "cmpgle", FP(0x15,0x0A7), BASE, ARG_FP }, + { "cvtgf", FP(0x15,0x0AC), BASE, ARG_FPZ1 }, + { "cvtgd", FP(0x15,0x0AD), BASE, ARG_FPZ1 }, + { "cvtgq", FP(0x15,0x0AF), BASE, ARG_FPZ1 }, + { "cvtqf", FP(0x15,0x0BC), BASE, ARG_FPZ1 }, + { "cvtqg", FP(0x15,0x0BE), BASE, ARG_FPZ1 }, + { "addf/uc", FP(0x15,0x100), BASE, ARG_FP }, + { "subf/uc", FP(0x15,0x101), BASE, ARG_FP }, + { "mulf/uc", FP(0x15,0x102), BASE, ARG_FP }, + { "divf/uc", FP(0x15,0x103), BASE, ARG_FP }, + { "cvtdg/uc", FP(0x15,0x11E), BASE, ARG_FPZ1 }, + { "addg/uc", FP(0x15,0x120), BASE, ARG_FP }, + { "subg/uc", FP(0x15,0x121), BASE, ARG_FP }, + { "mulg/uc", FP(0x15,0x122), BASE, ARG_FP }, + { "divg/uc", FP(0x15,0x123), BASE, ARG_FP }, + { "cvtgf/uc", FP(0x15,0x12C), BASE, ARG_FPZ1 }, + { "cvtgd/uc", FP(0x15,0x12D), BASE, ARG_FPZ1 }, + { "cvtgq/vc", FP(0x15,0x12F), BASE, ARG_FPZ1 }, + { "addf/u", FP(0x15,0x180), BASE, ARG_FP }, + { "subf/u", FP(0x15,0x181), BASE, ARG_FP }, + { "mulf/u", FP(0x15,0x182), BASE, ARG_FP }, + { "divf/u", FP(0x15,0x183), BASE, ARG_FP }, + { "cvtdg/u", FP(0x15,0x19E), BASE, ARG_FPZ1 }, + { "addg/u", FP(0x15,0x1A0), BASE, ARG_FP }, + { "subg/u", FP(0x15,0x1A1), BASE, ARG_FP }, + { "mulg/u", FP(0x15,0x1A2), BASE, ARG_FP }, + { "divg/u", FP(0x15,0x1A3), BASE, ARG_FP }, + { "cvtgf/u", FP(0x15,0x1AC), BASE, ARG_FPZ1 }, + { "cvtgd/u", FP(0x15,0x1AD), BASE, ARG_FPZ1 }, + { "cvtgq/v", FP(0x15,0x1AF), BASE, ARG_FPZ1 }, + { "addf/sc", FP(0x15,0x400), BASE, ARG_FP }, + { "subf/sc", FP(0x15,0x401), BASE, ARG_FP }, + { "mulf/sc", FP(0x15,0x402), BASE, ARG_FP }, + { "divf/sc", FP(0x15,0x403), BASE, ARG_FP }, + { "cvtdg/sc", FP(0x15,0x41E), BASE, ARG_FPZ1 }, + { "addg/sc", FP(0x15,0x420), BASE, ARG_FP }, + { "subg/sc", FP(0x15,0x421), BASE, ARG_FP }, + { "mulg/sc", FP(0x15,0x422), BASE, ARG_FP }, + { "divg/sc", FP(0x15,0x423), BASE, ARG_FP }, + { "cvtgf/sc", FP(0x15,0x42C), BASE, ARG_FPZ1 }, + { "cvtgd/sc", FP(0x15,0x42D), BASE, ARG_FPZ1 }, + { "cvtgq/sc", FP(0x15,0x42F), BASE, ARG_FPZ1 }, + { "addf/s", FP(0x15,0x480), BASE, ARG_FP }, + { "negf/s", FP(0x15,0x481), BASE, ARG_FPZ1 }, /* pseudo */ + { "subf/s", FP(0x15,0x481), BASE, ARG_FP }, + { "mulf/s", FP(0x15,0x482), BASE, ARG_FP }, + { "divf/s", FP(0x15,0x483), BASE, ARG_FP }, + { "cvtdg/s", FP(0x15,0x49E), BASE, ARG_FPZ1 }, + { "addg/s", FP(0x15,0x4A0), BASE, ARG_FP }, + { "negg/s", FP(0x15,0x4A1), BASE, ARG_FPZ1 }, /* pseudo */ + { "subg/s", FP(0x15,0x4A1), BASE, ARG_FP }, + { "mulg/s", FP(0x15,0x4A2), BASE, ARG_FP }, + { "divg/s", FP(0x15,0x4A3), BASE, ARG_FP }, + { "cmpgeq/s", FP(0x15,0x4A5), BASE, ARG_FP }, + { "cmpglt/s", FP(0x15,0x4A6), BASE, ARG_FP }, + { "cmpgle/s", FP(0x15,0x4A7), BASE, ARG_FP }, + { "cvtgf/s", FP(0x15,0x4AC), BASE, ARG_FPZ1 }, + { "cvtgd/s", FP(0x15,0x4AD), BASE, ARG_FPZ1 }, + { "cvtgq/s", FP(0x15,0x4AF), BASE, ARG_FPZ1 }, + { "addf/suc", FP(0x15,0x500), BASE, ARG_FP }, + { "subf/suc", FP(0x15,0x501), BASE, ARG_FP }, + { "mulf/suc", FP(0x15,0x502), BASE, ARG_FP }, + { "divf/suc", FP(0x15,0x503), BASE, ARG_FP }, + { "cvtdg/suc", FP(0x15,0x51E), BASE, ARG_FPZ1 }, + { "addg/suc", FP(0x15,0x520), BASE, ARG_FP }, + { "subg/suc", FP(0x15,0x521), BASE, ARG_FP }, + { "mulg/suc", FP(0x15,0x522), BASE, ARG_FP }, + { "divg/suc", FP(0x15,0x523), BASE, ARG_FP }, + { "cvtgf/suc", FP(0x15,0x52C), BASE, ARG_FPZ1 }, + { "cvtgd/suc", FP(0x15,0x52D), BASE, ARG_FPZ1 }, + { "cvtgq/svc", FP(0x15,0x52F), BASE, ARG_FPZ1 }, + { "addf/su", FP(0x15,0x580), BASE, ARG_FP }, + { "subf/su", FP(0x15,0x581), BASE, ARG_FP }, + { "mulf/su", FP(0x15,0x582), BASE, ARG_FP }, + { "divf/su", FP(0x15,0x583), BASE, ARG_FP }, + { "cvtdg/su", FP(0x15,0x59E), BASE, ARG_FPZ1 }, + { "addg/su", FP(0x15,0x5A0), BASE, ARG_FP }, + { "subg/su", FP(0x15,0x5A1), BASE, ARG_FP }, + { "mulg/su", FP(0x15,0x5A2), BASE, ARG_FP }, + { "divg/su", FP(0x15,0x5A3), BASE, ARG_FP }, + { "cvtgf/su", FP(0x15,0x5AC), BASE, ARG_FPZ1 }, + { "cvtgd/su", FP(0x15,0x5AD), BASE, ARG_FPZ1 }, + { "cvtgq/sv", FP(0x15,0x5AF), BASE, ARG_FPZ1 }, + + { "adds/c", FP(0x16,0x000), BASE, ARG_FP }, + { "subs/c", FP(0x16,0x001), BASE, ARG_FP }, + { "muls/c", FP(0x16,0x002), BASE, ARG_FP }, + { "divs/c", FP(0x16,0x003), BASE, ARG_FP }, + { "addt/c", FP(0x16,0x020), BASE, ARG_FP }, + { "subt/c", FP(0x16,0x021), BASE, ARG_FP }, + { "mult/c", FP(0x16,0x022), BASE, ARG_FP }, + { "divt/c", FP(0x16,0x023), BASE, ARG_FP }, + { "cvtts/c", FP(0x16,0x02C), BASE, ARG_FPZ1 }, + { "cvttq/c", FP(0x16,0x02F), BASE, ARG_FPZ1 }, + { "cvtqs/c", FP(0x16,0x03C), BASE, ARG_FPZ1 }, + { "cvtqt/c", FP(0x16,0x03E), BASE, ARG_FPZ1 }, + { "adds/m", FP(0x16,0x040), BASE, ARG_FP }, + { "subs/m", FP(0x16,0x041), BASE, ARG_FP }, + { "muls/m", FP(0x16,0x042), BASE, ARG_FP }, + { "divs/m", FP(0x16,0x043), BASE, ARG_FP }, + { "addt/m", FP(0x16,0x060), BASE, ARG_FP }, + { "subt/m", FP(0x16,0x061), BASE, ARG_FP }, + { "mult/m", FP(0x16,0x062), BASE, ARG_FP }, + { "divt/m", FP(0x16,0x063), BASE, ARG_FP }, + { "cvtts/m", FP(0x16,0x06C), BASE, ARG_FPZ1 }, + { "cvttq/m", FP(0x16,0x06F), BASE, ARG_FPZ1 }, + { "cvtqs/m", FP(0x16,0x07C), BASE, ARG_FPZ1 }, + { "cvtqt/m", FP(0x16,0x07E), BASE, ARG_FPZ1 }, + { "adds", FP(0x16,0x080), BASE, ARG_FP }, + { "negs", FP(0x16,0x081), BASE, ARG_FPZ1 }, /* pseudo */ + { "subs", FP(0x16,0x081), BASE, ARG_FP }, + { "muls", FP(0x16,0x082), BASE, ARG_FP }, + { "divs", FP(0x16,0x083), BASE, ARG_FP }, + { "addt", FP(0x16,0x0A0), BASE, ARG_FP }, + { "negt", FP(0x16,0x0A1), BASE, ARG_FPZ1 }, /* pseudo */ + { "subt", FP(0x16,0x0A1), BASE, ARG_FP }, + { "mult", FP(0x16,0x0A2), BASE, ARG_FP }, + { "divt", FP(0x16,0x0A3), BASE, ARG_FP }, + { "cmptun", FP(0x16,0x0A4), BASE, ARG_FP }, + { "cmpteq", FP(0x16,0x0A5), BASE, ARG_FP }, + { "cmptlt", FP(0x16,0x0A6), BASE, ARG_FP }, + { "cmptle", FP(0x16,0x0A7), BASE, ARG_FP }, + { "cvtts", FP(0x16,0x0AC), BASE, ARG_FPZ1 }, + { "cvttq", FP(0x16,0x0AF), BASE, ARG_FPZ1 }, + { "cvtqs", FP(0x16,0x0BC), BASE, ARG_FPZ1 }, + { "cvtqt", FP(0x16,0x0BE), BASE, ARG_FPZ1 }, + { "adds/d", FP(0x16,0x0C0), BASE, ARG_FP }, + { "subs/d", FP(0x16,0x0C1), BASE, ARG_FP }, + { "muls/d", FP(0x16,0x0C2), BASE, ARG_FP }, + { "divs/d", FP(0x16,0x0C3), BASE, ARG_FP }, + { "addt/d", FP(0x16,0x0E0), BASE, ARG_FP }, + { "subt/d", FP(0x16,0x0E1), BASE, ARG_FP }, + { "mult/d", FP(0x16,0x0E2), BASE, ARG_FP }, + { "divt/d", FP(0x16,0x0E3), BASE, ARG_FP }, + { "cvtts/d", FP(0x16,0x0EC), BASE, ARG_FPZ1 }, + { "cvttq/d", FP(0x16,0x0EF), BASE, ARG_FPZ1 }, + { "cvtqs/d", FP(0x16,0x0FC), BASE, ARG_FPZ1 }, + { "cvtqt/d", FP(0x16,0x0FE), BASE, ARG_FPZ1 }, + { "adds/uc", FP(0x16,0x100), BASE, ARG_FP }, + { "subs/uc", FP(0x16,0x101), BASE, ARG_FP }, + { "muls/uc", FP(0x16,0x102), BASE, ARG_FP }, + { "divs/uc", FP(0x16,0x103), BASE, ARG_FP }, + { "addt/uc", FP(0x16,0x120), BASE, ARG_FP }, + { "subt/uc", FP(0x16,0x121), BASE, ARG_FP }, + { "mult/uc", FP(0x16,0x122), BASE, ARG_FP }, + { "divt/uc", FP(0x16,0x123), BASE, ARG_FP }, + { "cvtts/uc", FP(0x16,0x12C), BASE, ARG_FPZ1 }, + { "cvttq/vc", FP(0x16,0x12F), BASE, ARG_FPZ1 }, + { "adds/um", FP(0x16,0x140), BASE, ARG_FP }, + { "subs/um", FP(0x16,0x141), BASE, ARG_FP }, + { "muls/um", FP(0x16,0x142), BASE, ARG_FP }, + { "divs/um", FP(0x16,0x143), BASE, ARG_FP }, + { "addt/um", FP(0x16,0x160), BASE, ARG_FP }, + { "subt/um", FP(0x16,0x161), BASE, ARG_FP }, + { "mult/um", FP(0x16,0x162), BASE, ARG_FP }, + { "divt/um", FP(0x16,0x163), BASE, ARG_FP }, + { "cvtts/um", FP(0x16,0x16C), BASE, ARG_FPZ1 }, + { "cvttq/vm", FP(0x16,0x16F), BASE, ARG_FPZ1 }, + { "adds/u", FP(0x16,0x180), BASE, ARG_FP }, + { "subs/u", FP(0x16,0x181), BASE, ARG_FP }, + { "muls/u", FP(0x16,0x182), BASE, ARG_FP }, + { "divs/u", FP(0x16,0x183), BASE, ARG_FP }, + { "addt/u", FP(0x16,0x1A0), BASE, ARG_FP }, + { "subt/u", FP(0x16,0x1A1), BASE, ARG_FP }, + { "mult/u", FP(0x16,0x1A2), BASE, ARG_FP }, + { "divt/u", FP(0x16,0x1A3), BASE, ARG_FP }, + { "cvtts/u", FP(0x16,0x1AC), BASE, ARG_FPZ1 }, + { "cvttq/v", FP(0x16,0x1AF), BASE, ARG_FPZ1 }, + { "adds/ud", FP(0x16,0x1C0), BASE, ARG_FP }, + { "subs/ud", FP(0x16,0x1C1), BASE, ARG_FP }, + { "muls/ud", FP(0x16,0x1C2), BASE, ARG_FP }, + { "divs/ud", FP(0x16,0x1C3), BASE, ARG_FP }, + { "addt/ud", FP(0x16,0x1E0), BASE, ARG_FP }, + { "subt/ud", FP(0x16,0x1E1), BASE, ARG_FP }, + { "mult/ud", FP(0x16,0x1E2), BASE, ARG_FP }, + { "divt/ud", FP(0x16,0x1E3), BASE, ARG_FP }, + { "cvtts/ud", FP(0x16,0x1EC), BASE, ARG_FPZ1 }, + { "cvttq/vd", FP(0x16,0x1EF), BASE, ARG_FPZ1 }, + { "cvtst", FP(0x16,0x2AC), BASE, ARG_FPZ1 }, + { "adds/suc", FP(0x16,0x500), BASE, ARG_FP }, + { "subs/suc", FP(0x16,0x501), BASE, ARG_FP }, + { "muls/suc", FP(0x16,0x502), BASE, ARG_FP }, + { "divs/suc", FP(0x16,0x503), BASE, ARG_FP }, + { "addt/suc", FP(0x16,0x520), BASE, ARG_FP }, + { "subt/suc", FP(0x16,0x521), BASE, ARG_FP }, + { "mult/suc", FP(0x16,0x522), BASE, ARG_FP }, + { "divt/suc", FP(0x16,0x523), BASE, ARG_FP }, + { "cvtts/suc", FP(0x16,0x52C), BASE, ARG_FPZ1 }, + { "cvttq/svc", FP(0x16,0x52F), BASE, ARG_FPZ1 }, + { "adds/sum", FP(0x16,0x540), BASE, ARG_FP }, + { "subs/sum", FP(0x16,0x541), BASE, ARG_FP }, + { "muls/sum", FP(0x16,0x542), BASE, ARG_FP }, + { "divs/sum", FP(0x16,0x543), BASE, ARG_FP }, + { "addt/sum", FP(0x16,0x560), BASE, ARG_FP }, + { "subt/sum", FP(0x16,0x561), BASE, ARG_FP }, + { "mult/sum", FP(0x16,0x562), BASE, ARG_FP }, + { "divt/sum", FP(0x16,0x563), BASE, ARG_FP }, + { "cvtts/sum", FP(0x16,0x56C), BASE, ARG_FPZ1 }, + { "cvttq/svm", FP(0x16,0x56F), BASE, ARG_FPZ1 }, + { "adds/su", FP(0x16,0x580), BASE, ARG_FP }, + { "negs/su", FP(0x16,0x581), BASE, ARG_FPZ1 }, /* pseudo */ + { "subs/su", FP(0x16,0x581), BASE, ARG_FP }, + { "muls/su", FP(0x16,0x582), BASE, ARG_FP }, + { "divs/su", FP(0x16,0x583), BASE, ARG_FP }, + { "addt/su", FP(0x16,0x5A0), BASE, ARG_FP }, + { "negt/su", FP(0x16,0x5A1), BASE, ARG_FPZ1 }, /* pseudo */ + { "subt/su", FP(0x16,0x5A1), BASE, ARG_FP }, + { "mult/su", FP(0x16,0x5A2), BASE, ARG_FP }, + { "divt/su", FP(0x16,0x5A3), BASE, ARG_FP }, + { "cmptun/su", FP(0x16,0x5A4), BASE, ARG_FP }, + { "cmpteq/su", FP(0x16,0x5A5), BASE, ARG_FP }, + { "cmptlt/su", FP(0x16,0x5A6), BASE, ARG_FP }, + { "cmptle/su", FP(0x16,0x5A7), BASE, ARG_FP }, + { "cvtts/su", FP(0x16,0x5AC), BASE, ARG_FPZ1 }, + { "cvttq/sv", FP(0x16,0x5AF), BASE, ARG_FPZ1 }, + { "adds/sud", FP(0x16,0x5C0), BASE, ARG_FP }, + { "subs/sud", FP(0x16,0x5C1), BASE, ARG_FP }, + { "muls/sud", FP(0x16,0x5C2), BASE, ARG_FP }, + { "divs/sud", FP(0x16,0x5C3), BASE, ARG_FP }, + { "addt/sud", FP(0x16,0x5E0), BASE, ARG_FP }, + { "subt/sud", FP(0x16,0x5E1), BASE, ARG_FP }, + { "mult/sud", FP(0x16,0x5E2), BASE, ARG_FP }, + { "divt/sud", FP(0x16,0x5E3), BASE, ARG_FP }, + { "cvtts/sud", FP(0x16,0x5EC), BASE, ARG_FPZ1 }, + { "cvttq/svd", FP(0x16,0x5EF), BASE, ARG_FPZ1 }, + { "cvtst/s", FP(0x16,0x6AC), BASE, ARG_FPZ1 }, + { "adds/suic", FP(0x16,0x700), BASE, ARG_FP }, + { "subs/suic", FP(0x16,0x701), BASE, ARG_FP }, + { "muls/suic", FP(0x16,0x702), BASE, ARG_FP }, + { "divs/suic", FP(0x16,0x703), BASE, ARG_FP }, + { "addt/suic", FP(0x16,0x720), BASE, ARG_FP }, + { "subt/suic", FP(0x16,0x721), BASE, ARG_FP }, + { "mult/suic", FP(0x16,0x722), BASE, ARG_FP }, + { "divt/suic", FP(0x16,0x723), BASE, ARG_FP }, + { "cvtts/suic", FP(0x16,0x72C), BASE, ARG_FPZ1 }, + { "cvttq/svic", FP(0x16,0x72F), BASE, ARG_FPZ1 }, + { "cvtqs/suic", FP(0x16,0x73C), BASE, ARG_FPZ1 }, + { "cvtqt/suic", FP(0x16,0x73E), BASE, ARG_FPZ1 }, + { "adds/suim", FP(0x16,0x740), BASE, ARG_FP }, + { "subs/suim", FP(0x16,0x741), BASE, ARG_FP }, + { "muls/suim", FP(0x16,0x742), BASE, ARG_FP }, + { "divs/suim", FP(0x16,0x743), BASE, ARG_FP }, + { "addt/suim", FP(0x16,0x760), BASE, ARG_FP }, + { "subt/suim", FP(0x16,0x761), BASE, ARG_FP }, + { "mult/suim", FP(0x16,0x762), BASE, ARG_FP }, + { "divt/suim", FP(0x16,0x763), BASE, ARG_FP }, + { "cvtts/suim", FP(0x16,0x76C), BASE, ARG_FPZ1 }, + { "cvttq/svim", FP(0x16,0x76F), BASE, ARG_FPZ1 }, + { "cvtqs/suim", FP(0x16,0x77C), BASE, ARG_FPZ1 }, + { "cvtqt/suim", FP(0x16,0x77E), BASE, ARG_FPZ1 }, + { "adds/sui", FP(0x16,0x780), BASE, ARG_FP }, + { "negs/sui", FP(0x16,0x781), BASE, ARG_FPZ1 }, /* pseudo */ + { "subs/sui", FP(0x16,0x781), BASE, ARG_FP }, + { "muls/sui", FP(0x16,0x782), BASE, ARG_FP }, + { "divs/sui", FP(0x16,0x783), BASE, ARG_FP }, + { "addt/sui", FP(0x16,0x7A0), BASE, ARG_FP }, + { "negt/sui", FP(0x16,0x7A1), BASE, ARG_FPZ1 }, /* pseudo */ + { "subt/sui", FP(0x16,0x7A1), BASE, ARG_FP }, + { "mult/sui", FP(0x16,0x7A2), BASE, ARG_FP }, + { "divt/sui", FP(0x16,0x7A3), BASE, ARG_FP }, + { "cvtts/sui", FP(0x16,0x7AC), BASE, ARG_FPZ1 }, + { "cvttq/svi", FP(0x16,0x7AF), BASE, ARG_FPZ1 }, + { "cvtqs/sui", FP(0x16,0x7BC), BASE, ARG_FPZ1 }, + { "cvtqt/sui", FP(0x16,0x7BE), BASE, ARG_FPZ1 }, + { "adds/suid", FP(0x16,0x7C0), BASE, ARG_FP }, + { "subs/suid", FP(0x16,0x7C1), BASE, ARG_FP }, + { "muls/suid", FP(0x16,0x7C2), BASE, ARG_FP }, + { "divs/suid", FP(0x16,0x7C3), BASE, ARG_FP }, + { "addt/suid", FP(0x16,0x7E0), BASE, ARG_FP }, + { "subt/suid", FP(0x16,0x7E1), BASE, ARG_FP }, + { "mult/suid", FP(0x16,0x7E2), BASE, ARG_FP }, + { "divt/suid", FP(0x16,0x7E3), BASE, ARG_FP }, + { "cvtts/suid", FP(0x16,0x7EC), BASE, ARG_FPZ1 }, + { "cvttq/svid", FP(0x16,0x7EF), BASE, ARG_FPZ1 }, + { "cvtqs/suid", FP(0x16,0x7FC), BASE, ARG_FPZ1 }, + { "cvtqt/suid", FP(0x16,0x7FE), BASE, ARG_FPZ1 }, + + { "cvtlq", FP(0x17,0x010), BASE, ARG_FPZ1 }, + { "fnop", FP(0x17,0x020), BASE, { ZA, ZB, ZC } }, /* pseudo */ + { "fclr", FP(0x17,0x020), BASE, { ZA, ZB, FC } }, /* pseudo */ + { "fabs", FP(0x17,0x020), BASE, ARG_FPZ1 }, /* pseudo */ + { "fmov", FP(0x17,0x020), BASE, { FA, RBA, FC } }, /* pseudo */ + { "cpys", FP(0x17,0x020), BASE, ARG_FP }, + { "fneg", FP(0x17,0x021), BASE, { FA, RBA, FC } }, /* pseudo */ + { "cpysn", FP(0x17,0x021), BASE, ARG_FP }, + { "cpyse", FP(0x17,0x022), BASE, ARG_FP }, + { "mt_fpcr", FP(0x17,0x024), BASE, { FA, RBA, RCA } }, + { "mf_fpcr", FP(0x17,0x025), BASE, { FA, RBA, RCA } }, + { "fcmoveq", FP(0x17,0x02A), BASE, ARG_FP }, + { "fcmovne", FP(0x17,0x02B), BASE, ARG_FP }, + { "fcmovlt", FP(0x17,0x02C), BASE, ARG_FP }, + { "fcmovge", FP(0x17,0x02D), BASE, ARG_FP }, + { "fcmovle", FP(0x17,0x02E), BASE, ARG_FP }, + { "fcmovgt", FP(0x17,0x02F), BASE, ARG_FP }, + { "cvtql", FP(0x17,0x030), BASE, ARG_FPZ1 }, + { "cvtql/v", FP(0x17,0x130), BASE, ARG_FPZ1 }, + { "cvtql/sv", FP(0x17,0x530), BASE, ARG_FPZ1 }, + + { "trapb", MFC(0x18,0x0000), BASE, ARG_NONE }, + { "draint", MFC(0x18,0x0000), BASE, ARG_NONE }, /* alias */ + { "excb", MFC(0x18,0x0400), BASE, ARG_NONE }, + { "mb", MFC(0x18,0x4000), BASE, ARG_NONE }, + { "wmb", MFC(0x18,0x4400), BASE, ARG_NONE }, + { "fetch", MFC(0x18,0x8000), BASE, { ZA, PRB } }, + { "fetch_m", MFC(0x18,0xA000), BASE, { ZA, PRB } }, + { "rpcc", MFC(0x18,0xC000), BASE, { RA } }, + { "rc", MFC(0x18,0xE000), BASE, { RA } }, + { "ecb", MFC(0x18,0xE800), BASE, { ZA, PRB } }, /* ev56 una */ + { "rs", MFC(0x18,0xF000), BASE, { RA } }, + { "wh64", MFC(0x18,0xF800), BASE, { ZA, PRB } }, /* ev56 una */ + { "wh64en", MFC(0x18,0xFC00), BASE, { ZA, PRB } }, /* ev7 una */ + + { "hw_mfpr", OPR(0x19,0x00), EV4, { RA, RBA, EV4EXTHWINDEX } }, + { "hw_mfpr", OP(0x19), OP_MASK, EV5, { RA, RBA, EV5HWINDEX } }, + { "hw_mfpr", OP(0x19), OP_MASK, EV6, { RA, ZB, EV6HWINDEX } }, + { "hw_mfpr/i", OPR(0x19,0x01), EV4, ARG_EV4HWMPR }, + { "hw_mfpr/a", OPR(0x19,0x02), EV4, ARG_EV4HWMPR }, + { "hw_mfpr/ai", OPR(0x19,0x03), EV4, ARG_EV4HWMPR }, + { "hw_mfpr/p", OPR(0x19,0x04), EV4, ARG_EV4HWMPR }, + { "hw_mfpr/pi", OPR(0x19,0x05), EV4, ARG_EV4HWMPR }, + { "hw_mfpr/pa", OPR(0x19,0x06), EV4, ARG_EV4HWMPR }, + { "hw_mfpr/pai", OPR(0x19,0x07), EV4, ARG_EV4HWMPR }, + { "pal19", PCD(0x19), BASE, ARG_PCD }, + + { "jmp", MBR_(0x1A,0), MBR_MASK | 0x3FFF, /* pseudo */ + BASE, { ZA, CPRB } }, + { "jmp", MBR(0x1A,0), BASE, { RA, CPRB, JMPHINT } }, + { "jsr", MBR(0x1A,1), BASE, { RA, CPRB, JMPHINT } }, + { "ret", MBR_(0x1A,2) | (31 << 21) | (26 << 16) | 1,/* pseudo */ + 0xFFFFFFFF, BASE, { 0 } }, + { "ret", MBR(0x1A,2), BASE, { RA, CPRB, RETHINT } }, + { "jcr", MBR(0x1A,3), BASE, { RA, CPRB, RETHINT } }, /* alias */ + { "jsr_coroutine", MBR(0x1A,3), BASE, { RA, CPRB, RETHINT } }, + + { "hw_ldl", EV4HWMEM(0x1B,0x0), EV4, ARG_EV4HWMEM }, + { "hw_ldl", EV5HWMEM(0x1B,0x00), EV5, ARG_EV5HWMEM }, + { "hw_ldl", EV6HWMEM(0x1B,0x8), EV6, ARG_EV6HWMEM }, + { "hw_ldl/a", EV4HWMEM(0x1B,0x4), EV4, ARG_EV4HWMEM }, + { "hw_ldl/a", EV5HWMEM(0x1B,0x10), EV5, ARG_EV5HWMEM }, + { "hw_ldl/a", EV6HWMEM(0x1B,0xC), EV6, ARG_EV6HWMEM }, + { "hw_ldl/al", EV5HWMEM(0x1B,0x11), EV5, ARG_EV5HWMEM }, + { "hw_ldl/ar", EV4HWMEM(0x1B,0x6), EV4, ARG_EV4HWMEM }, + { "hw_ldl/av", EV5HWMEM(0x1B,0x12), EV5, ARG_EV5HWMEM }, + { "hw_ldl/avl", EV5HWMEM(0x1B,0x13), EV5, ARG_EV5HWMEM }, + { "hw_ldl/aw", EV5HWMEM(0x1B,0x18), EV5, ARG_EV5HWMEM }, + { "hw_ldl/awl", EV5HWMEM(0x1B,0x19), EV5, ARG_EV5HWMEM }, + { "hw_ldl/awv", EV5HWMEM(0x1B,0x1a), EV5, ARG_EV5HWMEM }, + { "hw_ldl/awvl", EV5HWMEM(0x1B,0x1b), EV5, ARG_EV5HWMEM }, + { "hw_ldl/l", EV5HWMEM(0x1B,0x01), EV5, ARG_EV5HWMEM }, + { "hw_ldl/p", EV4HWMEM(0x1B,0x8), EV4, ARG_EV4HWMEM }, + { "hw_ldl/p", EV5HWMEM(0x1B,0x20), EV5, ARG_EV5HWMEM }, + { "hw_ldl/p", EV6HWMEM(0x1B,0x0), EV6, ARG_EV6HWMEM }, + { "hw_ldl/pa", EV4HWMEM(0x1B,0xC), EV4, ARG_EV4HWMEM }, + { "hw_ldl/pa", EV5HWMEM(0x1B,0x30), EV5, ARG_EV5HWMEM }, + { "hw_ldl/pal", EV5HWMEM(0x1B,0x31), EV5, ARG_EV5HWMEM }, + { "hw_ldl/par", EV4HWMEM(0x1B,0xE), EV4, ARG_EV4HWMEM }, + { "hw_ldl/pav", EV5HWMEM(0x1B,0x32), EV5, ARG_EV5HWMEM }, + { "hw_ldl/pavl", EV5HWMEM(0x1B,0x33), EV5, ARG_EV5HWMEM }, + { "hw_ldl/paw", EV5HWMEM(0x1B,0x38), EV5, ARG_EV5HWMEM }, + { "hw_ldl/pawl", EV5HWMEM(0x1B,0x39), EV5, ARG_EV5HWMEM }, + { "hw_ldl/pawv", EV5HWMEM(0x1B,0x3a), EV5, ARG_EV5HWMEM }, + { "hw_ldl/pawvl", EV5HWMEM(0x1B,0x3b), EV5, ARG_EV5HWMEM }, + { "hw_ldl/pl", EV5HWMEM(0x1B,0x21), EV5, ARG_EV5HWMEM }, + { "hw_ldl/pr", EV4HWMEM(0x1B,0xA), EV4, ARG_EV4HWMEM }, + { "hw_ldl/pv", EV5HWMEM(0x1B,0x22), EV5, ARG_EV5HWMEM }, + { "hw_ldl/pvl", EV5HWMEM(0x1B,0x23), EV5, ARG_EV5HWMEM }, + { "hw_ldl/pw", EV5HWMEM(0x1B,0x28), EV5, ARG_EV5HWMEM }, + { "hw_ldl/pwl", EV5HWMEM(0x1B,0x29), EV5, ARG_EV5HWMEM }, + { "hw_ldl/pwv", EV5HWMEM(0x1B,0x2a), EV5, ARG_EV5HWMEM }, + { "hw_ldl/pwvl", EV5HWMEM(0x1B,0x2b), EV5, ARG_EV5HWMEM }, + { "hw_ldl/r", EV4HWMEM(0x1B,0x2), EV4, ARG_EV4HWMEM }, + { "hw_ldl/v", EV5HWMEM(0x1B,0x02), EV5, ARG_EV5HWMEM }, + { "hw_ldl/v", EV6HWMEM(0x1B,0x4), EV6, ARG_EV6HWMEM }, + { "hw_ldl/vl", EV5HWMEM(0x1B,0x03), EV5, ARG_EV5HWMEM }, + { "hw_ldl/w", EV5HWMEM(0x1B,0x08), EV5, ARG_EV5HWMEM }, + { "hw_ldl/w", EV6HWMEM(0x1B,0xA), EV6, ARG_EV6HWMEM }, + { "hw_ldl/wa", EV6HWMEM(0x1B,0xE), EV6, ARG_EV6HWMEM }, + { "hw_ldl/wl", EV5HWMEM(0x1B,0x09), EV5, ARG_EV5HWMEM }, + { "hw_ldl/wv", EV5HWMEM(0x1B,0x0a), EV5, ARG_EV5HWMEM }, + { "hw_ldl/wvl", EV5HWMEM(0x1B,0x0b), EV5, ARG_EV5HWMEM }, + { "hw_ldl_l", EV5HWMEM(0x1B,0x01), EV5, ARG_EV5HWMEM }, + { "hw_ldl_l/a", EV5HWMEM(0x1B,0x11), EV5, ARG_EV5HWMEM }, + { "hw_ldl_l/av", EV5HWMEM(0x1B,0x13), EV5, ARG_EV5HWMEM }, + { "hw_ldl_l/aw", EV5HWMEM(0x1B,0x19), EV5, ARG_EV5HWMEM }, + { "hw_ldl_l/awv", EV5HWMEM(0x1B,0x1b), EV5, ARG_EV5HWMEM }, + { "hw_ldl_l/p", EV5HWMEM(0x1B,0x21), EV5, ARG_EV5HWMEM }, + { "hw_ldl_l/p", EV6HWMEM(0x1B,0x2), EV6, ARG_EV6HWMEM }, + { "hw_ldl_l/pa", EV5HWMEM(0x1B,0x31), EV5, ARG_EV5HWMEM }, + { "hw_ldl_l/pav", EV5HWMEM(0x1B,0x33), EV5, ARG_EV5HWMEM }, + { "hw_ldl_l/paw", EV5HWMEM(0x1B,0x39), EV5, ARG_EV5HWMEM }, + { "hw_ldl_l/pawv", EV5HWMEM(0x1B,0x3b), EV5, ARG_EV5HWMEM }, + { "hw_ldl_l/pv", EV5HWMEM(0x1B,0x23), EV5, ARG_EV5HWMEM }, + { "hw_ldl_l/pw", EV5HWMEM(0x1B,0x29), EV5, ARG_EV5HWMEM }, + { "hw_ldl_l/pwv", EV5HWMEM(0x1B,0x2b), EV5, ARG_EV5HWMEM }, + { "hw_ldl_l/v", EV5HWMEM(0x1B,0x03), EV5, ARG_EV5HWMEM }, + { "hw_ldl_l/w", EV5HWMEM(0x1B,0x09), EV5, ARG_EV5HWMEM }, + { "hw_ldl_l/wv", EV5HWMEM(0x1B,0x0b), EV5, ARG_EV5HWMEM }, + { "hw_ldq", EV4HWMEM(0x1B,0x1), EV4, ARG_EV4HWMEM }, + { "hw_ldq", EV5HWMEM(0x1B,0x04), EV5, ARG_EV5HWMEM }, + { "hw_ldq", EV6HWMEM(0x1B,0x9), EV6, ARG_EV6HWMEM }, + { "hw_ldq/a", EV4HWMEM(0x1B,0x5), EV4, ARG_EV4HWMEM }, + { "hw_ldq/a", EV5HWMEM(0x1B,0x14), EV5, ARG_EV5HWMEM }, + { "hw_ldq/a", EV6HWMEM(0x1B,0xD), EV6, ARG_EV6HWMEM }, + { "hw_ldq/al", EV5HWMEM(0x1B,0x15), EV5, ARG_EV5HWMEM }, + { "hw_ldq/ar", EV4HWMEM(0x1B,0x7), EV4, ARG_EV4HWMEM }, + { "hw_ldq/av", EV5HWMEM(0x1B,0x16), EV5, ARG_EV5HWMEM }, + { "hw_ldq/avl", EV5HWMEM(0x1B,0x17), EV5, ARG_EV5HWMEM }, + { "hw_ldq/aw", EV5HWMEM(0x1B,0x1c), EV5, ARG_EV5HWMEM }, + { "hw_ldq/awl", EV5HWMEM(0x1B,0x1d), EV5, ARG_EV5HWMEM }, + { "hw_ldq/awv", EV5HWMEM(0x1B,0x1e), EV5, ARG_EV5HWMEM }, + { "hw_ldq/awvl", EV5HWMEM(0x1B,0x1f), EV5, ARG_EV5HWMEM }, + { "hw_ldq/l", EV5HWMEM(0x1B,0x05), EV5, ARG_EV5HWMEM }, + { "hw_ldq/p", EV4HWMEM(0x1B,0x9), EV4, ARG_EV4HWMEM }, + { "hw_ldq/p", EV5HWMEM(0x1B,0x24), EV5, ARG_EV5HWMEM }, + { "hw_ldq/p", EV6HWMEM(0x1B,0x1), EV6, ARG_EV6HWMEM }, + { "hw_ldq/pa", EV4HWMEM(0x1B,0xD), EV4, ARG_EV4HWMEM }, + { "hw_ldq/pa", EV5HWMEM(0x1B,0x34), EV5, ARG_EV5HWMEM }, + { "hw_ldq/pal", EV5HWMEM(0x1B,0x35), EV5, ARG_EV5HWMEM }, + { "hw_ldq/par", EV4HWMEM(0x1B,0xF), EV4, ARG_EV4HWMEM }, + { "hw_ldq/pav", EV5HWMEM(0x1B,0x36), EV5, ARG_EV5HWMEM }, + { "hw_ldq/pavl", EV5HWMEM(0x1B,0x37), EV5, ARG_EV5HWMEM }, + { "hw_ldq/paw", EV5HWMEM(0x1B,0x3c), EV5, ARG_EV5HWMEM }, + { "hw_ldq/pawl", EV5HWMEM(0x1B,0x3d), EV5, ARG_EV5HWMEM }, + { "hw_ldq/pawv", EV5HWMEM(0x1B,0x3e), EV5, ARG_EV5HWMEM }, + { "hw_ldq/pawvl", EV5HWMEM(0x1B,0x3f), EV5, ARG_EV5HWMEM }, + { "hw_ldq/pl", EV5HWMEM(0x1B,0x25), EV5, ARG_EV5HWMEM }, + { "hw_ldq/pr", EV4HWMEM(0x1B,0xB), EV4, ARG_EV4HWMEM }, + { "hw_ldq/pv", EV5HWMEM(0x1B,0x26), EV5, ARG_EV5HWMEM }, + { "hw_ldq/pvl", EV5HWMEM(0x1B,0x27), EV5, ARG_EV5HWMEM }, + { "hw_ldq/pw", EV5HWMEM(0x1B,0x2c), EV5, ARG_EV5HWMEM }, + { "hw_ldq/pwl", EV5HWMEM(0x1B,0x2d), EV5, ARG_EV5HWMEM }, + { "hw_ldq/pwv", EV5HWMEM(0x1B,0x2e), EV5, ARG_EV5HWMEM }, + { "hw_ldq/pwvl", EV5HWMEM(0x1B,0x2f), EV5, ARG_EV5HWMEM }, + { "hw_ldq/r", EV4HWMEM(0x1B,0x3), EV4, ARG_EV4HWMEM }, + { "hw_ldq/v", EV5HWMEM(0x1B,0x06), EV5, ARG_EV5HWMEM }, + { "hw_ldq/v", EV6HWMEM(0x1B,0x5), EV6, ARG_EV6HWMEM }, + { "hw_ldq/vl", EV5HWMEM(0x1B,0x07), EV5, ARG_EV5HWMEM }, + { "hw_ldq/w", EV5HWMEM(0x1B,0x0c), EV5, ARG_EV5HWMEM }, + { "hw_ldq/w", EV6HWMEM(0x1B,0xB), EV6, ARG_EV6HWMEM }, + { "hw_ldq/wa", EV6HWMEM(0x1B,0xF), EV6, ARG_EV6HWMEM }, + { "hw_ldq/wl", EV5HWMEM(0x1B,0x0d), EV5, ARG_EV5HWMEM }, + { "hw_ldq/wv", EV5HWMEM(0x1B,0x0e), EV5, ARG_EV5HWMEM }, + { "hw_ldq/wvl", EV5HWMEM(0x1B,0x0f), EV5, ARG_EV5HWMEM }, + { "hw_ldq_l", EV5HWMEM(0x1B,0x05), EV5, ARG_EV5HWMEM }, + { "hw_ldq_l/a", EV5HWMEM(0x1B,0x15), EV5, ARG_EV5HWMEM }, + { "hw_ldq_l/av", EV5HWMEM(0x1B,0x17), EV5, ARG_EV5HWMEM }, + { "hw_ldq_l/aw", EV5HWMEM(0x1B,0x1d), EV5, ARG_EV5HWMEM }, + { "hw_ldq_l/awv", EV5HWMEM(0x1B,0x1f), EV5, ARG_EV5HWMEM }, + { "hw_ldq_l/p", EV5HWMEM(0x1B,0x25), EV5, ARG_EV5HWMEM }, + { "hw_ldq_l/p", EV6HWMEM(0x1B,0x3), EV6, ARG_EV6HWMEM }, + { "hw_ldq_l/pa", EV5HWMEM(0x1B,0x35), EV5, ARG_EV5HWMEM }, + { "hw_ldq_l/pav", EV5HWMEM(0x1B,0x37), EV5, ARG_EV5HWMEM }, + { "hw_ldq_l/paw", EV5HWMEM(0x1B,0x3d), EV5, ARG_EV5HWMEM }, + { "hw_ldq_l/pawv", EV5HWMEM(0x1B,0x3f), EV5, ARG_EV5HWMEM }, + { "hw_ldq_l/pv", EV5HWMEM(0x1B,0x27), EV5, ARG_EV5HWMEM }, + { "hw_ldq_l/pw", EV5HWMEM(0x1B,0x2d), EV5, ARG_EV5HWMEM }, + { "hw_ldq_l/pwv", EV5HWMEM(0x1B,0x2f), EV5, ARG_EV5HWMEM }, + { "hw_ldq_l/v", EV5HWMEM(0x1B,0x07), EV5, ARG_EV5HWMEM }, + { "hw_ldq_l/w", EV5HWMEM(0x1B,0x0d), EV5, ARG_EV5HWMEM }, + { "hw_ldq_l/wv", EV5HWMEM(0x1B,0x0f), EV5, ARG_EV5HWMEM }, + { "hw_ld", EV4HWMEM(0x1B,0x0), EV4, ARG_EV4HWMEM }, + { "hw_ld", EV5HWMEM(0x1B,0x00), EV5, ARG_EV5HWMEM }, + { "hw_ld/a", EV4HWMEM(0x1B,0x4), EV4, ARG_EV4HWMEM }, + { "hw_ld/a", EV5HWMEM(0x1B,0x10), EV5, ARG_EV5HWMEM }, + { "hw_ld/al", EV5HWMEM(0x1B,0x11), EV5, ARG_EV5HWMEM }, + { "hw_ld/aq", EV4HWMEM(0x1B,0x5), EV4, ARG_EV4HWMEM }, + { "hw_ld/aq", EV5HWMEM(0x1B,0x14), EV5, ARG_EV5HWMEM }, + { "hw_ld/aql", EV5HWMEM(0x1B,0x15), EV5, ARG_EV5HWMEM }, + { "hw_ld/aqv", EV5HWMEM(0x1B,0x16), EV5, ARG_EV5HWMEM }, + { "hw_ld/aqvl", EV5HWMEM(0x1B,0x17), EV5, ARG_EV5HWMEM }, + { "hw_ld/ar", EV4HWMEM(0x1B,0x6), EV4, ARG_EV4HWMEM }, + { "hw_ld/arq", EV4HWMEM(0x1B,0x7), EV4, ARG_EV4HWMEM }, + { "hw_ld/av", EV5HWMEM(0x1B,0x12), EV5, ARG_EV5HWMEM }, + { "hw_ld/avl", EV5HWMEM(0x1B,0x13), EV5, ARG_EV5HWMEM }, + { "hw_ld/aw", EV5HWMEM(0x1B,0x18), EV5, ARG_EV5HWMEM }, + { "hw_ld/awl", EV5HWMEM(0x1B,0x19), EV5, ARG_EV5HWMEM }, + { "hw_ld/awq", EV5HWMEM(0x1B,0x1c), EV5, ARG_EV5HWMEM }, + { "hw_ld/awql", EV5HWMEM(0x1B,0x1d), EV5, ARG_EV5HWMEM }, + { "hw_ld/awqv", EV5HWMEM(0x1B,0x1e), EV5, ARG_EV5HWMEM }, + { "hw_ld/awqvl", EV5HWMEM(0x1B,0x1f), EV5, ARG_EV5HWMEM }, + { "hw_ld/awv", EV5HWMEM(0x1B,0x1a), EV5, ARG_EV5HWMEM }, + { "hw_ld/awvl", EV5HWMEM(0x1B,0x1b), EV5, ARG_EV5HWMEM }, + { "hw_ld/l", EV5HWMEM(0x1B,0x01), EV5, ARG_EV5HWMEM }, + { "hw_ld/p", EV4HWMEM(0x1B,0x8), EV4, ARG_EV4HWMEM }, + { "hw_ld/p", EV5HWMEM(0x1B,0x20), EV5, ARG_EV5HWMEM }, + { "hw_ld/pa", EV4HWMEM(0x1B,0xC), EV4, ARG_EV4HWMEM }, + { "hw_ld/pa", EV5HWMEM(0x1B,0x30), EV5, ARG_EV5HWMEM }, + { "hw_ld/pal", EV5HWMEM(0x1B,0x31), EV5, ARG_EV5HWMEM }, + { "hw_ld/paq", EV4HWMEM(0x1B,0xD), EV4, ARG_EV4HWMEM }, + { "hw_ld/paq", EV5HWMEM(0x1B,0x34), EV5, ARG_EV5HWMEM }, + { "hw_ld/paql", EV5HWMEM(0x1B,0x35), EV5, ARG_EV5HWMEM }, + { "hw_ld/paqv", EV5HWMEM(0x1B,0x36), EV5, ARG_EV5HWMEM }, + { "hw_ld/paqvl", EV5HWMEM(0x1B,0x37), EV5, ARG_EV5HWMEM }, + { "hw_ld/par", EV4HWMEM(0x1B,0xE), EV4, ARG_EV4HWMEM }, + { "hw_ld/parq", EV4HWMEM(0x1B,0xF), EV4, ARG_EV4HWMEM }, + { "hw_ld/pav", EV5HWMEM(0x1B,0x32), EV5, ARG_EV5HWMEM }, + { "hw_ld/pavl", EV5HWMEM(0x1B,0x33), EV5, ARG_EV5HWMEM }, + { "hw_ld/paw", EV5HWMEM(0x1B,0x38), EV5, ARG_EV5HWMEM }, + { "hw_ld/pawl", EV5HWMEM(0x1B,0x39), EV5, ARG_EV5HWMEM }, + { "hw_ld/pawq", EV5HWMEM(0x1B,0x3c), EV5, ARG_EV5HWMEM }, + { "hw_ld/pawql", EV5HWMEM(0x1B,0x3d), EV5, ARG_EV5HWMEM }, + { "hw_ld/pawqv", EV5HWMEM(0x1B,0x3e), EV5, ARG_EV5HWMEM }, + { "hw_ld/pawqvl", EV5HWMEM(0x1B,0x3f), EV5, ARG_EV5HWMEM }, + { "hw_ld/pawv", EV5HWMEM(0x1B,0x3a), EV5, ARG_EV5HWMEM }, + { "hw_ld/pawvl", EV5HWMEM(0x1B,0x3b), EV5, ARG_EV5HWMEM }, + { "hw_ld/pl", EV5HWMEM(0x1B,0x21), EV5, ARG_EV5HWMEM }, + { "hw_ld/pq", EV4HWMEM(0x1B,0x9), EV4, ARG_EV4HWMEM }, + { "hw_ld/pq", EV5HWMEM(0x1B,0x24), EV5, ARG_EV5HWMEM }, + { "hw_ld/pql", EV5HWMEM(0x1B,0x25), EV5, ARG_EV5HWMEM }, + { "hw_ld/pqv", EV5HWMEM(0x1B,0x26), EV5, ARG_EV5HWMEM }, + { "hw_ld/pqvl", EV5HWMEM(0x1B,0x27), EV5, ARG_EV5HWMEM }, + { "hw_ld/pr", EV4HWMEM(0x1B,0xA), EV4, ARG_EV4HWMEM }, + { "hw_ld/prq", EV4HWMEM(0x1B,0xB), EV4, ARG_EV4HWMEM }, + { "hw_ld/pv", EV5HWMEM(0x1B,0x22), EV5, ARG_EV5HWMEM }, + { "hw_ld/pvl", EV5HWMEM(0x1B,0x23), EV5, ARG_EV5HWMEM }, + { "hw_ld/pw", EV5HWMEM(0x1B,0x28), EV5, ARG_EV5HWMEM }, + { "hw_ld/pwl", EV5HWMEM(0x1B,0x29), EV5, ARG_EV5HWMEM }, + { "hw_ld/pwq", EV5HWMEM(0x1B,0x2c), EV5, ARG_EV5HWMEM }, + { "hw_ld/pwql", EV5HWMEM(0x1B,0x2d), EV5, ARG_EV5HWMEM }, + { "hw_ld/pwqv", EV5HWMEM(0x1B,0x2e), EV5, ARG_EV5HWMEM }, + { "hw_ld/pwqvl", EV5HWMEM(0x1B,0x2f), EV5, ARG_EV5HWMEM }, + { "hw_ld/pwv", EV5HWMEM(0x1B,0x2a), EV5, ARG_EV5HWMEM }, + { "hw_ld/pwvl", EV5HWMEM(0x1B,0x2b), EV5, ARG_EV5HWMEM }, + { "hw_ld/q", EV4HWMEM(0x1B,0x1), EV4, ARG_EV4HWMEM }, + { "hw_ld/q", EV5HWMEM(0x1B,0x04), EV5, ARG_EV5HWMEM }, + { "hw_ld/ql", EV5HWMEM(0x1B,0x05), EV5, ARG_EV5HWMEM }, + { "hw_ld/qv", EV5HWMEM(0x1B,0x06), EV5, ARG_EV5HWMEM }, + { "hw_ld/qvl", EV5HWMEM(0x1B,0x07), EV5, ARG_EV5HWMEM }, + { "hw_ld/r", EV4HWMEM(0x1B,0x2), EV4, ARG_EV4HWMEM }, + { "hw_ld/rq", EV4HWMEM(0x1B,0x3), EV4, ARG_EV4HWMEM }, + { "hw_ld/v", EV5HWMEM(0x1B,0x02), EV5, ARG_EV5HWMEM }, + { "hw_ld/vl", EV5HWMEM(0x1B,0x03), EV5, ARG_EV5HWMEM }, + { "hw_ld/w", EV5HWMEM(0x1B,0x08), EV5, ARG_EV5HWMEM }, + { "hw_ld/wl", EV5HWMEM(0x1B,0x09), EV5, ARG_EV5HWMEM }, + { "hw_ld/wq", EV5HWMEM(0x1B,0x0c), EV5, ARG_EV5HWMEM }, + { "hw_ld/wql", EV5HWMEM(0x1B,0x0d), EV5, ARG_EV5HWMEM }, + { "hw_ld/wqv", EV5HWMEM(0x1B,0x0e), EV5, ARG_EV5HWMEM }, + { "hw_ld/wqvl", EV5HWMEM(0x1B,0x0f), EV5, ARG_EV5HWMEM }, + { "hw_ld/wv", EV5HWMEM(0x1B,0x0a), EV5, ARG_EV5HWMEM }, + { "hw_ld/wvl", EV5HWMEM(0x1B,0x0b), EV5, ARG_EV5HWMEM }, + { "pal1b", PCD(0x1B), BASE, ARG_PCD }, + + { "sextb", OPR(0x1C, 0x00), BWX, ARG_OPRZ1 }, + { "sextw", OPR(0x1C, 0x01), BWX, ARG_OPRZ1 }, + { "ctpop", OPR(0x1C, 0x30), CIX, ARG_OPRZ1 }, + { "perr", OPR(0x1C, 0x31), MAX, ARG_OPR }, + { "ctlz", OPR(0x1C, 0x32), CIX, ARG_OPRZ1 }, + { "cttz", OPR(0x1C, 0x33), CIX, ARG_OPRZ1 }, + { "unpkbw", OPR(0x1C, 0x34), MAX, ARG_OPRZ1 }, + { "unpkbl", OPR(0x1C, 0x35), MAX, ARG_OPRZ1 }, + { "pkwb", OPR(0x1C, 0x36), MAX, ARG_OPRZ1 }, + { "pklb", OPR(0x1C, 0x37), MAX, ARG_OPRZ1 }, + { "minsb8", OPR(0x1C, 0x38), MAX, ARG_OPR }, + { "minsb8", OPRL(0x1C, 0x38), MAX, ARG_OPRL }, + { "minsw4", OPR(0x1C, 0x39), MAX, ARG_OPR }, + { "minsw4", OPRL(0x1C, 0x39), MAX, ARG_OPRL }, + { "minub8", OPR(0x1C, 0x3A), MAX, ARG_OPR }, + { "minub8", OPRL(0x1C, 0x3A), MAX, ARG_OPRL }, + { "minuw4", OPR(0x1C, 0x3B), MAX, ARG_OPR }, + { "minuw4", OPRL(0x1C, 0x3B), MAX, ARG_OPRL }, + { "maxub8", OPR(0x1C, 0x3C), MAX, ARG_OPR }, + { "maxub8", OPRL(0x1C, 0x3C), MAX, ARG_OPRL }, + { "maxuw4", OPR(0x1C, 0x3D), MAX, ARG_OPR }, + { "maxuw4", OPRL(0x1C, 0x3D), MAX, ARG_OPRL }, + { "maxsb8", OPR(0x1C, 0x3E), MAX, ARG_OPR }, + { "maxsb8", OPRL(0x1C, 0x3E), MAX, ARG_OPRL }, + { "maxsw4", OPR(0x1C, 0x3F), MAX, ARG_OPR }, + { "maxsw4", OPRL(0x1C, 0x3F), MAX, ARG_OPRL }, + { "ftoit", FP(0x1C, 0x70), CIX, { FA, ZB, RC } }, + { "ftois", FP(0x1C, 0x78), CIX, { FA, ZB, RC } }, + + { "hw_mtpr", OPR(0x1D,0x00), EV4, { RA, RBA, EV4EXTHWINDEX } }, + { "hw_mtpr", OP(0x1D), OP_MASK, EV5, { RA, RBA, EV5HWINDEX } }, + { "hw_mtpr", OP(0x1D), OP_MASK, EV6, { ZA, RB, EV6HWINDEX } }, + { "hw_mtpr/i", OPR(0x1D,0x01), EV4, ARG_EV4HWMPR }, + { "hw_mtpr/a", OPR(0x1D,0x02), EV4, ARG_EV4HWMPR }, + { "hw_mtpr/ai", OPR(0x1D,0x03), EV4, ARG_EV4HWMPR }, + { "hw_mtpr/p", OPR(0x1D,0x04), EV4, ARG_EV4HWMPR }, + { "hw_mtpr/pi", OPR(0x1D,0x05), EV4, ARG_EV4HWMPR }, + { "hw_mtpr/pa", OPR(0x1D,0x06), EV4, ARG_EV4HWMPR }, + { "hw_mtpr/pai", OPR(0x1D,0x07), EV4, ARG_EV4HWMPR }, + { "pal1d", PCD(0x1D), BASE, ARG_PCD }, + + { "hw_rei", SPCD(0x1E,0x3FF8000), EV4|EV5, ARG_NONE }, + { "hw_rei_stall", SPCD(0x1E,0x3FFC000), EV5, ARG_NONE }, + { "hw_jmp", EV6HWMBR(0x1E,0x0), EV6, { ZA, PRB, EV6HWJMPHINT } }, + { "hw_jsr", EV6HWMBR(0x1E,0x2), EV6, { ZA, PRB, EV6HWJMPHINT } }, + { "hw_ret", EV6HWMBR(0x1E,0x4), EV6, { ZA, PRB } }, + { "hw_jcr", EV6HWMBR(0x1E,0x6), EV6, { ZA, PRB } }, + { "hw_coroutine", EV6HWMBR(0x1E,0x6), EV6, { ZA, PRB } }, /* alias */ + { "hw_jmp/stall", EV6HWMBR(0x1E,0x1), EV6, { ZA, PRB, EV6HWJMPHINT } }, + { "hw_jsr/stall", EV6HWMBR(0x1E,0x3), EV6, { ZA, PRB, EV6HWJMPHINT } }, + { "hw_ret/stall", EV6HWMBR(0x1E,0x5), EV6, { ZA, PRB } }, + { "hw_jcr/stall", EV6HWMBR(0x1E,0x7), EV6, { ZA, PRB } }, + { "hw_coroutine/stall", EV6HWMBR(0x1E,0x7), EV6, { ZA, PRB } }, /* alias */ + { "pal1e", PCD(0x1E), BASE, ARG_PCD }, + + { "hw_stl", EV4HWMEM(0x1F,0x0), EV4, ARG_EV4HWMEM }, + { "hw_stl", EV5HWMEM(0x1F,0x00), EV5, ARG_EV5HWMEM }, + { "hw_stl", EV6HWMEM(0x1F,0x4), EV6, ARG_EV6HWMEM }, /* ??? 8 */ + { "hw_stl/a", EV4HWMEM(0x1F,0x4), EV4, ARG_EV4HWMEM }, + { "hw_stl/a", EV5HWMEM(0x1F,0x10), EV5, ARG_EV5HWMEM }, + { "hw_stl/a", EV6HWMEM(0x1F,0xC), EV6, ARG_EV6HWMEM }, + { "hw_stl/ac", EV5HWMEM(0x1F,0x11), EV5, ARG_EV5HWMEM }, + { "hw_stl/ar", EV4HWMEM(0x1F,0x6), EV4, ARG_EV4HWMEM }, + { "hw_stl/av", EV5HWMEM(0x1F,0x12), EV5, ARG_EV5HWMEM }, + { "hw_stl/avc", EV5HWMEM(0x1F,0x13), EV5, ARG_EV5HWMEM }, + { "hw_stl/c", EV5HWMEM(0x1F,0x01), EV5, ARG_EV5HWMEM }, + { "hw_stl/p", EV4HWMEM(0x1F,0x8), EV4, ARG_EV4HWMEM }, + { "hw_stl/p", EV5HWMEM(0x1F,0x20), EV5, ARG_EV5HWMEM }, + { "hw_stl/p", EV6HWMEM(0x1F,0x0), EV6, ARG_EV6HWMEM }, + { "hw_stl/pa", EV4HWMEM(0x1F,0xC), EV4, ARG_EV4HWMEM }, + { "hw_stl/pa", EV5HWMEM(0x1F,0x30), EV5, ARG_EV5HWMEM }, + { "hw_stl/pac", EV5HWMEM(0x1F,0x31), EV5, ARG_EV5HWMEM }, + { "hw_stl/pav", EV5HWMEM(0x1F,0x32), EV5, ARG_EV5HWMEM }, + { "hw_stl/pavc", EV5HWMEM(0x1F,0x33), EV5, ARG_EV5HWMEM }, + { "hw_stl/pc", EV5HWMEM(0x1F,0x21), EV5, ARG_EV5HWMEM }, + { "hw_stl/pr", EV4HWMEM(0x1F,0xA), EV4, ARG_EV4HWMEM }, + { "hw_stl/pv", EV5HWMEM(0x1F,0x22), EV5, ARG_EV5HWMEM }, + { "hw_stl/pvc", EV5HWMEM(0x1F,0x23), EV5, ARG_EV5HWMEM }, + { "hw_stl/r", EV4HWMEM(0x1F,0x2), EV4, ARG_EV4HWMEM }, + { "hw_stl/v", EV5HWMEM(0x1F,0x02), EV5, ARG_EV5HWMEM }, + { "hw_stl/vc", EV5HWMEM(0x1F,0x03), EV5, ARG_EV5HWMEM }, + { "hw_stl_c", EV5HWMEM(0x1F,0x01), EV5, ARG_EV5HWMEM }, + { "hw_stl_c/a", EV5HWMEM(0x1F,0x11), EV5, ARG_EV5HWMEM }, + { "hw_stl_c/av", EV5HWMEM(0x1F,0x13), EV5, ARG_EV5HWMEM }, + { "hw_stl_c/p", EV5HWMEM(0x1F,0x21), EV5, ARG_EV5HWMEM }, + { "hw_stl_c/p", EV6HWMEM(0x1F,0x2), EV6, ARG_EV6HWMEM }, + { "hw_stl_c/pa", EV5HWMEM(0x1F,0x31), EV5, ARG_EV5HWMEM }, + { "hw_stl_c/pav", EV5HWMEM(0x1F,0x33), EV5, ARG_EV5HWMEM }, + { "hw_stl_c/pv", EV5HWMEM(0x1F,0x23), EV5, ARG_EV5HWMEM }, + { "hw_stl_c/v", EV5HWMEM(0x1F,0x03), EV5, ARG_EV5HWMEM }, + { "hw_stq", EV4HWMEM(0x1F,0x1), EV4, ARG_EV4HWMEM }, + { "hw_stq", EV5HWMEM(0x1F,0x04), EV5, ARG_EV5HWMEM }, + { "hw_stq", EV6HWMEM(0x1F,0x5), EV6, ARG_EV6HWMEM }, /* ??? 9 */ + { "hw_stq/a", EV4HWMEM(0x1F,0x5), EV4, ARG_EV4HWMEM }, + { "hw_stq/a", EV5HWMEM(0x1F,0x14), EV5, ARG_EV5HWMEM }, + { "hw_stq/a", EV6HWMEM(0x1F,0xD), EV6, ARG_EV6HWMEM }, + { "hw_stq/ac", EV5HWMEM(0x1F,0x15), EV5, ARG_EV5HWMEM }, + { "hw_stq/ar", EV4HWMEM(0x1F,0x7), EV4, ARG_EV4HWMEM }, + { "hw_stq/av", EV5HWMEM(0x1F,0x16), EV5, ARG_EV5HWMEM }, + { "hw_stq/avc", EV5HWMEM(0x1F,0x17), EV5, ARG_EV5HWMEM }, + { "hw_stq/c", EV5HWMEM(0x1F,0x05), EV5, ARG_EV5HWMEM }, + { "hw_stq/p", EV4HWMEM(0x1F,0x9), EV4, ARG_EV4HWMEM }, + { "hw_stq/p", EV5HWMEM(0x1F,0x24), EV5, ARG_EV5HWMEM }, + { "hw_stq/p", EV6HWMEM(0x1F,0x1), EV6, ARG_EV6HWMEM }, + { "hw_stq/pa", EV4HWMEM(0x1F,0xD), EV4, ARG_EV4HWMEM }, + { "hw_stq/pa", EV5HWMEM(0x1F,0x34), EV5, ARG_EV5HWMEM }, + { "hw_stq/pac", EV5HWMEM(0x1F,0x35), EV5, ARG_EV5HWMEM }, + { "hw_stq/par", EV4HWMEM(0x1F,0xE), EV4, ARG_EV4HWMEM }, + { "hw_stq/par", EV4HWMEM(0x1F,0xF), EV4, ARG_EV4HWMEM }, + { "hw_stq/pav", EV5HWMEM(0x1F,0x36), EV5, ARG_EV5HWMEM }, + { "hw_stq/pavc", EV5HWMEM(0x1F,0x37), EV5, ARG_EV5HWMEM }, + { "hw_stq/pc", EV5HWMEM(0x1F,0x25), EV5, ARG_EV5HWMEM }, + { "hw_stq/pr", EV4HWMEM(0x1F,0xB), EV4, ARG_EV4HWMEM }, + { "hw_stq/pv", EV5HWMEM(0x1F,0x26), EV5, ARG_EV5HWMEM }, + { "hw_stq/pvc", EV5HWMEM(0x1F,0x27), EV5, ARG_EV5HWMEM }, + { "hw_stq/r", EV4HWMEM(0x1F,0x3), EV4, ARG_EV4HWMEM }, + { "hw_stq/v", EV5HWMEM(0x1F,0x06), EV5, ARG_EV5HWMEM }, + { "hw_stq/vc", EV5HWMEM(0x1F,0x07), EV5, ARG_EV5HWMEM }, + { "hw_stq_c", EV5HWMEM(0x1F,0x05), EV5, ARG_EV5HWMEM }, + { "hw_stq_c/a", EV5HWMEM(0x1F,0x15), EV5, ARG_EV5HWMEM }, + { "hw_stq_c/av", EV5HWMEM(0x1F,0x17), EV5, ARG_EV5HWMEM }, + { "hw_stq_c/p", EV5HWMEM(0x1F,0x25), EV5, ARG_EV5HWMEM }, + { "hw_stq_c/p", EV6HWMEM(0x1F,0x3), EV6, ARG_EV6HWMEM }, + { "hw_stq_c/pa", EV5HWMEM(0x1F,0x35), EV5, ARG_EV5HWMEM }, + { "hw_stq_c/pav", EV5HWMEM(0x1F,0x37), EV5, ARG_EV5HWMEM }, + { "hw_stq_c/pv", EV5HWMEM(0x1F,0x27), EV5, ARG_EV5HWMEM }, + { "hw_stq_c/v", EV5HWMEM(0x1F,0x07), EV5, ARG_EV5HWMEM }, + { "hw_st", EV4HWMEM(0x1F,0x0), EV4, ARG_EV4HWMEM }, + { "hw_st", EV5HWMEM(0x1F,0x00), EV5, ARG_EV5HWMEM }, + { "hw_st/a", EV4HWMEM(0x1F,0x4), EV4, ARG_EV4HWMEM }, + { "hw_st/a", EV5HWMEM(0x1F,0x10), EV5, ARG_EV5HWMEM }, + { "hw_st/ac", EV5HWMEM(0x1F,0x11), EV5, ARG_EV5HWMEM }, + { "hw_st/aq", EV4HWMEM(0x1F,0x5), EV4, ARG_EV4HWMEM }, + { "hw_st/aq", EV5HWMEM(0x1F,0x14), EV5, ARG_EV5HWMEM }, + { "hw_st/aqc", EV5HWMEM(0x1F,0x15), EV5, ARG_EV5HWMEM }, + { "hw_st/aqv", EV5HWMEM(0x1F,0x16), EV5, ARG_EV5HWMEM }, + { "hw_st/aqvc", EV5HWMEM(0x1F,0x17), EV5, ARG_EV5HWMEM }, + { "hw_st/ar", EV4HWMEM(0x1F,0x6), EV4, ARG_EV4HWMEM }, + { "hw_st/arq", EV4HWMEM(0x1F,0x7), EV4, ARG_EV4HWMEM }, + { "hw_st/av", EV5HWMEM(0x1F,0x12), EV5, ARG_EV5HWMEM }, + { "hw_st/avc", EV5HWMEM(0x1F,0x13), EV5, ARG_EV5HWMEM }, + { "hw_st/c", EV5HWMEM(0x1F,0x01), EV5, ARG_EV5HWMEM }, + { "hw_st/p", EV4HWMEM(0x1F,0x8), EV4, ARG_EV4HWMEM }, + { "hw_st/p", EV5HWMEM(0x1F,0x20), EV5, ARG_EV5HWMEM }, + { "hw_st/pa", EV4HWMEM(0x1F,0xC), EV4, ARG_EV4HWMEM }, + { "hw_st/pa", EV5HWMEM(0x1F,0x30), EV5, ARG_EV5HWMEM }, + { "hw_st/pac", EV5HWMEM(0x1F,0x31), EV5, ARG_EV5HWMEM }, + { "hw_st/paq", EV4HWMEM(0x1F,0xD), EV4, ARG_EV4HWMEM }, + { "hw_st/paq", EV5HWMEM(0x1F,0x34), EV5, ARG_EV5HWMEM }, + { "hw_st/paqc", EV5HWMEM(0x1F,0x35), EV5, ARG_EV5HWMEM }, + { "hw_st/paqv", EV5HWMEM(0x1F,0x36), EV5, ARG_EV5HWMEM }, + { "hw_st/paqvc", EV5HWMEM(0x1F,0x37), EV5, ARG_EV5HWMEM }, + { "hw_st/par", EV4HWMEM(0x1F,0xE), EV4, ARG_EV4HWMEM }, + { "hw_st/parq", EV4HWMEM(0x1F,0xF), EV4, ARG_EV4HWMEM }, + { "hw_st/pav", EV5HWMEM(0x1F,0x32), EV5, ARG_EV5HWMEM }, + { "hw_st/pavc", EV5HWMEM(0x1F,0x33), EV5, ARG_EV5HWMEM }, + { "hw_st/pc", EV5HWMEM(0x1F,0x21), EV5, ARG_EV5HWMEM }, + { "hw_st/pq", EV4HWMEM(0x1F,0x9), EV4, ARG_EV4HWMEM }, + { "hw_st/pq", EV5HWMEM(0x1F,0x24), EV5, ARG_EV5HWMEM }, + { "hw_st/pqc", EV5HWMEM(0x1F,0x25), EV5, ARG_EV5HWMEM }, + { "hw_st/pqv", EV5HWMEM(0x1F,0x26), EV5, ARG_EV5HWMEM }, + { "hw_st/pqvc", EV5HWMEM(0x1F,0x27), EV5, ARG_EV5HWMEM }, + { "hw_st/pr", EV4HWMEM(0x1F,0xA), EV4, ARG_EV4HWMEM }, + { "hw_st/prq", EV4HWMEM(0x1F,0xB), EV4, ARG_EV4HWMEM }, + { "hw_st/pv", EV5HWMEM(0x1F,0x22), EV5, ARG_EV5HWMEM }, + { "hw_st/pvc", EV5HWMEM(0x1F,0x23), EV5, ARG_EV5HWMEM }, + { "hw_st/q", EV4HWMEM(0x1F,0x1), EV4, ARG_EV4HWMEM }, + { "hw_st/q", EV5HWMEM(0x1F,0x04), EV5, ARG_EV5HWMEM }, + { "hw_st/qc", EV5HWMEM(0x1F,0x05), EV5, ARG_EV5HWMEM }, + { "hw_st/qv", EV5HWMEM(0x1F,0x06), EV5, ARG_EV5HWMEM }, + { "hw_st/qvc", EV5HWMEM(0x1F,0x07), EV5, ARG_EV5HWMEM }, + { "hw_st/r", EV4HWMEM(0x1F,0x2), EV4, ARG_EV4HWMEM }, + { "hw_st/v", EV5HWMEM(0x1F,0x02), EV5, ARG_EV5HWMEM }, + { "hw_st/vc", EV5HWMEM(0x1F,0x03), EV5, ARG_EV5HWMEM }, + { "pal1f", PCD(0x1F), BASE, ARG_PCD }, + + { "ldf", MEM(0x20), BASE, ARG_FMEM }, + { "ldg", MEM(0x21), BASE, ARG_FMEM }, + { "lds", MEM(0x22), BASE, ARG_FMEM }, + { "ldt", MEM(0x23), BASE, ARG_FMEM }, + { "stf", MEM(0x24), BASE, ARG_FMEM }, + { "stg", MEM(0x25), BASE, ARG_FMEM }, + { "sts", MEM(0x26), BASE, ARG_FMEM }, + { "stt", MEM(0x27), BASE, ARG_FMEM }, + + { "ldl", MEM(0x28), BASE, ARG_MEM }, + { "ldq", MEM(0x29), BASE, ARG_MEM }, + { "ldl_l", MEM(0x2A), BASE, ARG_MEM }, + { "ldq_l", MEM(0x2B), BASE, ARG_MEM }, + { "stl", MEM(0x2C), BASE, ARG_MEM }, + { "stq", MEM(0x2D), BASE, ARG_MEM }, + { "stl_c", MEM(0x2E), BASE, ARG_MEM }, + { "stq_c", MEM(0x2F), BASE, ARG_MEM }, + + { "br", BRA(0x30), BASE, { ZA, BDISP } }, /* pseudo */ + { "br", BRA(0x30), BASE, ARG_BRA }, + { "fbeq", BRA(0x31), BASE, ARG_FBRA }, + { "fblt", BRA(0x32), BASE, ARG_FBRA }, + { "fble", BRA(0x33), BASE, ARG_FBRA }, + { "bsr", BRA(0x34), BASE, ARG_BRA }, + { "fbne", BRA(0x35), BASE, ARG_FBRA }, + { "fbge", BRA(0x36), BASE, ARG_FBRA }, + { "fbgt", BRA(0x37), BASE, ARG_FBRA }, + { "blbc", BRA(0x38), BASE, ARG_BRA }, + { "beq", BRA(0x39), BASE, ARG_BRA }, + { "blt", BRA(0x3A), BASE, ARG_BRA }, + { "ble", BRA(0x3B), BASE, ARG_BRA }, + { "blbs", BRA(0x3C), BASE, ARG_BRA }, + { "bne", BRA(0x3D), BASE, ARG_BRA }, + { "bge", BRA(0x3E), BASE, ARG_BRA }, + { "bgt", BRA(0x3F), BASE, ARG_BRA }, +}; + +const unsigned alpha_num_opcodes = sizeof(alpha_opcodes)/sizeof(*alpha_opcodes); + +/* OSF register names. */ + +static const char * const osf_regnames[64] = { + "v0", "t0", "t1", "t2", "t3", "t4", "t5", "t6", + "t7", "s0", "s1", "s2", "s3", "s4", "s5", "fp", + "a0", "a1", "a2", "a3", "a4", "a5", "t8", "t9", + "t10", "t11", "ra", "t12", "at", "gp", "sp", "zero", + "$f0", "$f1", "$f2", "$f3", "$f4", "$f5", "$f6", "$f7", + "$f8", "$f9", "$f10", "$f11", "$f12", "$f13", "$f14", "$f15", + "$f16", "$f17", "$f18", "$f19", "$f20", "$f21", "$f22", "$f23", + "$f24", "$f25", "$f26", "$f27", "$f28", "$f29", "$f30", "$f31" +}; + +/* VMS register names. */ + +static const char * const vms_regnames[64] = { + "R0", "R1", "R2", "R3", "R4", "R5", "R6", "R7", + "R8", "R9", "R10", "R11", "R12", "R13", "R14", "R15", + "R16", "R17", "R18", "R19", "R20", "R21", "R22", "R23", + "R24", "AI", "RA", "PV", "AT", "FP", "SP", "RZ", + "F0", "F1", "F2", "F3", "F4", "F5", "F6", "F7", + "F8", "F9", "F10", "F11", "F12", "F13", "F14", "F15", + "F16", "F17", "F18", "F19", "F20", "F21", "F22", "F23", + "F24", "F25", "F26", "F27", "F28", "F29", "F30", "FZ" +}; + +/* Disassemble Alpha instructions. */ + +int +print_insn_alpha (bfd_vma memaddr, struct disassemble_info *info) +{ + static const struct alpha_opcode *opcode_index[AXP_NOPS+1]; + const char * const * regnames; + const struct alpha_opcode *opcode, *opcode_end; + const unsigned char *opindex; + unsigned insn, op, isa_mask; + int need_comma; + + /* Initialize the majorop table the first time through */ + if (!opcode_index[0]) + { + opcode = alpha_opcodes; + opcode_end = opcode + alpha_num_opcodes; + + for (op = 0; op < AXP_NOPS; ++op) + { + opcode_index[op] = opcode; + while (opcode < opcode_end && op == AXP_OP (opcode->opcode)) + ++opcode; + } + opcode_index[op] = opcode; + } + + if (info->flavour == bfd_target_evax_flavour) + regnames = vms_regnames; + else + regnames = osf_regnames; + + isa_mask = AXP_OPCODE_NOPAL; + switch (info->mach) + { + case bfd_mach_alpha_ev4: + isa_mask |= AXP_OPCODE_EV4; + break; + case bfd_mach_alpha_ev5: + isa_mask |= AXP_OPCODE_EV5; + break; + case bfd_mach_alpha_ev6: + isa_mask |= AXP_OPCODE_EV6; + break; + } + + /* Read the insn into a host word */ + { + bfd_byte buffer[4]; + int status = (*info->read_memory_func) (memaddr, buffer, 4, info); + if (status != 0) + { + (*info->memory_error_func) (status, memaddr, info); + return -1; + } + insn = bfd_getl32 (buffer); + } + + /* Get the major opcode of the instruction. */ + op = AXP_OP (insn); + + /* Find the first match in the opcode table. */ + opcode_end = opcode_index[op + 1]; + for (opcode = opcode_index[op]; opcode < opcode_end; ++opcode) + { + if ((insn ^ opcode->opcode) & opcode->mask) + continue; + + if (!(opcode->flags & isa_mask)) + continue; + + /* Make two passes over the operands. First see if any of them + have extraction functions, and, if they do, make sure the + instruction is valid. */ + { + int invalid = 0; + for (opindex = opcode->operands; *opindex != 0; opindex++) + { + const struct alpha_operand *operand = alpha_operands + *opindex; + if (operand->extract) + (*operand->extract) (insn, &invalid); + } + if (invalid) + continue; + } + + /* The instruction is valid. */ + goto found; + } + + /* No instruction found */ + (*info->fprintf_func) (info->stream, ".long %#08x", insn); + + return 4; + +found: + (*info->fprintf_func) (info->stream, "%s", opcode->name); + if (opcode->operands[0] != 0) + (*info->fprintf_func) (info->stream, "\t"); + + /* Now extract and print the operands. */ + need_comma = 0; + for (opindex = opcode->operands; *opindex != 0; opindex++) + { + const struct alpha_operand *operand = alpha_operands + *opindex; + int value; + + /* Operands that are marked FAKE are simply ignored. We + already made sure that the extract function considered + the instruction to be valid. */ + if ((operand->flags & AXP_OPERAND_FAKE) != 0) + continue; + + /* Extract the value from the instruction. */ + if (operand->extract) + value = (*operand->extract) (insn, (int *) NULL); + else + { + value = (insn >> operand->shift) & ((1 << operand->bits) - 1); + if (operand->flags & AXP_OPERAND_SIGNED) + { + int signbit = 1 << (operand->bits - 1); + value = (value ^ signbit) - signbit; + } + } + + if (need_comma && + ((operand->flags & (AXP_OPERAND_PARENS | AXP_OPERAND_COMMA)) + != AXP_OPERAND_PARENS)) + { + (*info->fprintf_func) (info->stream, ","); + } + if (operand->flags & AXP_OPERAND_PARENS) + (*info->fprintf_func) (info->stream, "("); + + /* Print the operand as directed by the flags. */ + if (operand->flags & AXP_OPERAND_IR) + (*info->fprintf_func) (info->stream, "%s", regnames[value]); + else if (operand->flags & AXP_OPERAND_FPR) + (*info->fprintf_func) (info->stream, "%s", regnames[value + 32]); + else if (operand->flags & AXP_OPERAND_RELATIVE) + (*info->print_address_func) (memaddr + 4 + value, info); + else if (operand->flags & AXP_OPERAND_SIGNED) + (*info->fprintf_func) (info->stream, "%d", value); + else + (*info->fprintf_func) (info->stream, "%#x", value); + + if (operand->flags & AXP_OPERAND_PARENS) + (*info->fprintf_func) (info->stream, ")"); + need_comma = 1; + } + + return 4; +} diff --git a/alpha.ld b/alpha.ld new file mode 100644 index 0000000..906d76b --- /dev/null +++ b/alpha.ld @@ -0,0 +1,127 @@ +OUTPUT_FORMAT("elf64-alpha", "elf64-alpha", + "elf64-alpha") +OUTPUT_ARCH(alpha) +ENTRY(__start) +SECTIONS +{ + /* Read-only sections, merged into text segment: */ + . = 0x60000000 + SIZEOF_HEADERS; + .interp : { *(.interp) } + .hash : { *(.hash) } + .dynsym : { *(.dynsym) } + .dynstr : { *(.dynstr) } + .gnu.version : { *(.gnu.version) } + .gnu.version_d : { *(.gnu.version_d) } + .gnu.version_r : { *(.gnu.version_r) } + .rel.text : + { *(.rel.text) *(.rel.gnu.linkonce.t*) } + .rela.text : + { *(.rela.text) *(.rela.gnu.linkonce.t*) } + .rel.data : + { *(.rel.data) *(.rel.gnu.linkonce.d*) } + .rela.data : + { *(.rela.data) *(.rela.gnu.linkonce.d*) } + .rel.rodata : + { *(.rel.rodata) *(.rel.gnu.linkonce.r*) } + .rela.rodata : + { *(.rela.rodata) *(.rela.gnu.linkonce.r*) } + .rel.got : { *(.rel.got) } + .rela.got : { *(.rela.got) } + .rel.ctors : { *(.rel.ctors) } + .rela.ctors : { *(.rela.ctors) } + .rel.dtors : { *(.rel.dtors) } + .rela.dtors : { *(.rela.dtors) } + .rel.init : { *(.rel.init) } + .rela.init : { *(.rela.init) } + .rel.fini : { *(.rel.fini) } + .rela.fini : { *(.rela.fini) } + .rel.bss : { *(.rel.bss) } + .rela.bss : { *(.rela.bss) } + .rel.plt : { *(.rel.plt) } + .rela.plt : { *(.rela.plt) } + .init : { *(.init) } =0x47ff041f + .text : + { + *(.text) + /* .gnu.warning sections are handled specially by elf32.em. */ + *(.gnu.warning) + *(.gnu.linkonce.t*) + } =0x47ff041f + _etext = .; + PROVIDE (etext = .); + .fini : { *(.fini) } =0x47ff041f + .rodata : { *(.rodata) *(.gnu.linkonce.r*) } + .rodata1 : { *(.rodata1) } + .reginfo : { *(.reginfo) } + /* Adjust the address for the data segment. We want to adjust up to + the same address within the page on the next page up. */ + . = ALIGN(0x100000) + (. & (0x100000 - 1)); + .data : + { + *(.data) + *(.gnu.linkonce.d*) + CONSTRUCTORS + } + .data1 : { *(.data1) } + .ctors : + { + *(.ctors) + } + .dtors : + { + *(.dtors) + } + .plt : { *(.plt) } + .got : { *(.got.plt) *(.got) } + .dynamic : { *(.dynamic) } + /* We want the small data sections together, so single-instruction offsets + can access them all, and initialized data all before uninitialized, so + we can shorten the on-disk segment size. */ + .sdata : { *(.sdata) } + _edata = .; + PROVIDE (edata = .); + __bss_start = .; + .sbss : { *(.sbss) *(.scommon) } + .bss : + { + *(.dynbss) + *(.bss) + *(COMMON) + } + _end = . ; + PROVIDE (end = .); + /* Stabs debugging sections. */ + .stab 0 : { *(.stab) } + .stabstr 0 : { *(.stabstr) } + .stab.excl 0 : { *(.stab.excl) } + .stab.exclstr 0 : { *(.stab.exclstr) } + .stab.index 0 : { *(.stab.index) } + .stab.indexstr 0 : { *(.stab.indexstr) } + .comment 0 : { *(.comment) } + /* DWARF debug sections. + Symbols in the DWARF debugging sections are relative to the beginning + of the section so we begin them at 0. */ + /* DWARF 1 */ + .debug 0 : { *(.debug) } + .line 0 : { *(.line) } + /* GNU DWARF 1 extensions */ + .debug_srcinfo 0 : { *(.debug_srcinfo) } + .debug_sfnames 0 : { *(.debug_sfnames) } + /* DWARF 1.1 and DWARF 2 */ + .debug_aranges 0 : { *(.debug_aranges) } + .debug_pubnames 0 : { *(.debug_pubnames) } + /* DWARF 2 */ + .debug_info 0 : { *(.debug_info) } + .debug_abbrev 0 : { *(.debug_abbrev) } + .debug_line 0 : { *(.debug_line) } + .debug_frame 0 : { *(.debug_frame) } + .debug_str 0 : { *(.debug_str) } + .debug_loc 0 : { *(.debug_loc) } + .debug_macinfo 0 : { *(.debug_macinfo) } + /* SGI/MIPS DWARF 2 extensions */ + .debug_weaknames 0 : { *(.debug_weaknames) } + .debug_funcnames 0 : { *(.debug_funcnames) } + .debug_typenames 0 : { *(.debug_typenames) } + .debug_varnames 0 : { *(.debug_varnames) } + /* These must appear regardless of . */ +} diff --git a/arch_init.c b/arch_init.c new file mode 100644 index 0000000..cc56f0f --- /dev/null +++ b/arch_init.c @@ -0,0 +1,726 @@ +/* + * QEMU System Emulator + * + * Copyright (c) 2003-2008 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 +#include +#include +#ifndef _WIN32 +#include +#include +#endif +#include "config.h" +#include "monitor.h" +#include "sysemu.h" +#include "arch_init.h" +#include "audio/audio.h" +#include "hw/pc.h" +#include "hw/pci.h" +#include "hw/audiodev.h" +#include "kvm.h" +#include "migration.h" +#include "net.h" +#include "gdbstub.h" +#include "hw/smbios.h" + +#ifdef TARGET_SPARC +int graphic_width = 1024; +int graphic_height = 768; +int graphic_depth = 8; +#else +int graphic_width = 800; +int graphic_height = 600; +int graphic_depth = 15; +#endif + +const char arch_config_name[] = CONFIG_QEMU_CONFDIR "/target-" TARGET_ARCH ".conf"; + +#if defined(TARGET_ALPHA) +#define QEMU_ARCH QEMU_ARCH_ALPHA +#elif defined(TARGET_ARM) +#define QEMU_ARCH QEMU_ARCH_ARM +#elif defined(TARGET_CRIS) +#define QEMU_ARCH QEMU_ARCH_CRIS +#elif defined(TARGET_I386) +#define QEMU_ARCH QEMU_ARCH_I386 +#elif defined(TARGET_M68K) +#define QEMU_ARCH QEMU_ARCH_M68K +#elif defined(TARGET_MICROBLAZE) +#define QEMU_ARCH QEMU_ARCH_MICROBLAZE +#elif defined(TARGET_MIPS) +#define QEMU_ARCH QEMU_ARCH_MIPS +#elif defined(TARGET_PPC) +#define QEMU_ARCH QEMU_ARCH_PPC +#elif defined(TARGET_S390X) +#define QEMU_ARCH QEMU_ARCH_S390X +#elif defined(TARGET_SH4) +#define QEMU_ARCH QEMU_ARCH_SH4 +#elif defined(TARGET_SPARC) +#define QEMU_ARCH QEMU_ARCH_SPARC +#endif + +const uint32_t arch_type = QEMU_ARCH; + +/***********************************************************/ +/* ram save/restore */ + +#define RAM_SAVE_FLAG_FULL 0x01 /* Obsolete, not used anymore */ +#define RAM_SAVE_FLAG_COMPRESS 0x02 +#define RAM_SAVE_FLAG_MEM_SIZE 0x04 +#define RAM_SAVE_FLAG_PAGE 0x08 +#define RAM_SAVE_FLAG_EOS 0x10 +#define RAM_SAVE_FLAG_CONTINUE 0x20 + +static int is_dup_page(uint8_t *page, uint8_t ch) +{ + uint32_t val = ch << 24 | ch << 16 | ch << 8 | ch; + uint32_t *array = (uint32_t *)page; + int i; + + for (i = 0; i < (TARGET_PAGE_SIZE / 4); i++) { + if (array[i] != val) { + return 0; + } + } + + return 1; +} + +static RAMBlock *last_block; +static ram_addr_t last_offset; + +static int ram_save_block(QEMUFile *f) +{ + RAMBlock *block = last_block; + ram_addr_t offset = last_offset; + ram_addr_t current_addr; + int bytes_sent = 0; + + if (!block) + block = QLIST_FIRST(&ram_list.blocks); + + current_addr = block->offset + offset; + + do { + if (cpu_physical_memory_get_dirty(current_addr, MIGRATION_DIRTY_FLAG)) { + uint8_t *p; + int cont = (block == last_block) ? RAM_SAVE_FLAG_CONTINUE : 0; + + cpu_physical_memory_reset_dirty(current_addr, + current_addr + TARGET_PAGE_SIZE, + MIGRATION_DIRTY_FLAG); + + p = block->host + offset; + + if (is_dup_page(p, *p)) { + qemu_put_be64(f, offset | cont | RAM_SAVE_FLAG_COMPRESS); + if (!cont) { + qemu_put_byte(f, strlen(block->idstr)); + qemu_put_buffer(f, (uint8_t *)block->idstr, + strlen(block->idstr)); + } + qemu_put_byte(f, *p); + bytes_sent = 1; + } else { + qemu_put_be64(f, offset | cont | RAM_SAVE_FLAG_PAGE); + if (!cont) { + qemu_put_byte(f, strlen(block->idstr)); + qemu_put_buffer(f, (uint8_t *)block->idstr, + strlen(block->idstr)); + } + qemu_put_buffer(f, p, TARGET_PAGE_SIZE); + bytes_sent = TARGET_PAGE_SIZE; + } + + break; + } + + offset += TARGET_PAGE_SIZE; + if (offset >= block->length) { + offset = 0; + block = QLIST_NEXT(block, next); + if (!block) + block = QLIST_FIRST(&ram_list.blocks); + } + + current_addr = block->offset + offset; + + } while (current_addr != last_block->offset + last_offset); + + last_block = block; + last_offset = offset; + + return bytes_sent; +} + +static uint64_t bytes_transferred; + +static ram_addr_t ram_save_remaining(void) +{ + RAMBlock *block; + ram_addr_t count = 0; + + QLIST_FOREACH(block, &ram_list.blocks, next) { + ram_addr_t addr; + for (addr = block->offset; addr < block->offset + block->length; + addr += TARGET_PAGE_SIZE) { + if (cpu_physical_memory_get_dirty(addr, MIGRATION_DIRTY_FLAG)) { + count++; + } + } + } + + return count; +} + +uint64_t ram_bytes_remaining(void) +{ + return ram_save_remaining() * TARGET_PAGE_SIZE; +} + +uint64_t ram_bytes_transferred(void) +{ + return bytes_transferred; +} + +uint64_t ram_bytes_total(void) +{ + RAMBlock *block; + uint64_t total = 0; + + QLIST_FOREACH(block, &ram_list.blocks, next) + total += block->length; + + return total; +} + +static int block_compar(const void *a, const void *b) +{ + RAMBlock * const *ablock = a; + RAMBlock * const *bblock = b; + if ((*ablock)->offset < (*bblock)->offset) { + return -1; + } else if ((*ablock)->offset > (*bblock)->offset) { + return 1; + } + return 0; +} + +static void sort_ram_list(void) +{ + RAMBlock *block, *nblock, **blocks; + int n; + n = 0; + QLIST_FOREACH(block, &ram_list.blocks, next) { + ++n; + } + blocks = qemu_malloc(n * sizeof *blocks); + n = 0; + QLIST_FOREACH_SAFE(block, &ram_list.blocks, next, nblock) { + blocks[n++] = block; + QLIST_REMOVE(block, next); + } + qsort(blocks, n, sizeof *blocks, block_compar); + while (--n >= 0) { + QLIST_INSERT_HEAD(&ram_list.blocks, blocks[n], next); + } + qemu_free(blocks); +} + +int ram_save_live(Monitor *mon, QEMUFile *f, int stage, void *opaque) +{ + ram_addr_t addr; + uint64_t bytes_transferred_last; + double bwidth = 0; + uint64_t expected_time = 0; + + if (stage < 0) { + cpu_physical_memory_set_dirty_tracking(0); + return 0; + } + + if (cpu_physical_sync_dirty_bitmap(0, TARGET_PHYS_ADDR_MAX) != 0) { + qemu_file_set_error(f); + return 0; + } + + if (stage == 1) { + RAMBlock *block; + bytes_transferred = 0; + last_block = NULL; + last_offset = 0; + sort_ram_list(); + + /* Make sure all dirty bits are set */ + QLIST_FOREACH(block, &ram_list.blocks, next) { + for (addr = block->offset; addr < block->offset + block->length; + addr += TARGET_PAGE_SIZE) { + if (!cpu_physical_memory_get_dirty(addr, + MIGRATION_DIRTY_FLAG)) { + cpu_physical_memory_set_dirty(addr); + } + } + } + + /* Enable dirty memory tracking */ + cpu_physical_memory_set_dirty_tracking(1); + + qemu_put_be64(f, ram_bytes_total() | RAM_SAVE_FLAG_MEM_SIZE); + + QLIST_FOREACH(block, &ram_list.blocks, next) { + qemu_put_byte(f, strlen(block->idstr)); + qemu_put_buffer(f, (uint8_t *)block->idstr, strlen(block->idstr)); + qemu_put_be64(f, block->length); + } + } + + bytes_transferred_last = bytes_transferred; + bwidth = qemu_get_clock_ns(rt_clock); + + while (!qemu_file_rate_limit(f)) { + int bytes_sent; + + bytes_sent = ram_save_block(f); + bytes_transferred += bytes_sent; + if (bytes_sent == 0) { /* no more blocks */ + break; + } + } + + bwidth = qemu_get_clock_ns(rt_clock) - bwidth; + bwidth = (bytes_transferred - bytes_transferred_last) / bwidth; + + /* if we haven't transferred anything this round, force expected_time to a + * a very high value, but without crashing */ + if (bwidth == 0) { + bwidth = 0.000001; + } + + /* try transferring iterative blocks of memory */ + if (stage == 3) { + int bytes_sent; + + /* flush all remaining blocks regardless of rate limiting */ + while ((bytes_sent = ram_save_block(f)) != 0) { + bytes_transferred += bytes_sent; + } + cpu_physical_memory_set_dirty_tracking(0); + } + + qemu_put_be64(f, RAM_SAVE_FLAG_EOS); + + expected_time = ram_save_remaining() * TARGET_PAGE_SIZE / bwidth; + + return (stage == 2) && (expected_time <= migrate_max_downtime()); +} + +static inline void *host_from_stream_offset(QEMUFile *f, + ram_addr_t offset, + int flags) +{ + static RAMBlock *block = NULL; + char id[256]; + uint8_t len; + + if (flags & RAM_SAVE_FLAG_CONTINUE) { + if (!block) { + fprintf(stderr, "Ack, bad migration stream!\n"); + return NULL; + } + + return block->host + offset; + } + + len = qemu_get_byte(f); + qemu_get_buffer(f, (uint8_t *)id, len); + id[len] = 0; + + QLIST_FOREACH(block, &ram_list.blocks, next) { + if (!strncmp(id, block->idstr, sizeof(id))) + return block->host + offset; + } + + fprintf(stderr, "Can't find block %s!\n", id); + return NULL; +} + +int ram_load(QEMUFile *f, void *opaque, int version_id) +{ + ram_addr_t addr; + int flags; + + if (version_id < 3 || version_id > 4) { + return -EINVAL; + } + + do { + addr = qemu_get_be64(f); + + flags = addr & ~TARGET_PAGE_MASK; + addr &= TARGET_PAGE_MASK; + + if (flags & RAM_SAVE_FLAG_MEM_SIZE) { + if (version_id == 3) { + if (addr != ram_bytes_total()) { + return -EINVAL; + } + } else { + /* Synchronize RAM block list */ + char id[256]; + ram_addr_t length; + ram_addr_t total_ram_bytes = addr; + + while (total_ram_bytes) { + RAMBlock *block; + uint8_t len; + + len = qemu_get_byte(f); + qemu_get_buffer(f, (uint8_t *)id, len); + id[len] = 0; + length = qemu_get_be64(f); + + QLIST_FOREACH(block, &ram_list.blocks, next) { + if (!strncmp(id, block->idstr, sizeof(id))) { + if (block->length != length) + return -EINVAL; + break; + } + } + + if (!block) { + fprintf(stderr, "Unknown ramblock \"%s\", cannot " + "accept migration\n", id); + return -EINVAL; + } + + total_ram_bytes -= length; + } + } + } + + if (flags & RAM_SAVE_FLAG_COMPRESS) { + void *host; + uint8_t ch; + + if (version_id == 3) + host = qemu_get_ram_ptr(addr); + else + host = host_from_stream_offset(f, addr, flags); + if (!host) { + return -EINVAL; + } + + ch = qemu_get_byte(f); + memset(host, ch, TARGET_PAGE_SIZE); +#ifndef _WIN32 + if (ch == 0 && + (!kvm_enabled() || kvm_has_sync_mmu())) { + qemu_madvise(host, TARGET_PAGE_SIZE, QEMU_MADV_DONTNEED); + } +#endif + } else if (flags & RAM_SAVE_FLAG_PAGE) { + void *host; + + if (version_id == 3) + host = qemu_get_ram_ptr(addr); + else + host = host_from_stream_offset(f, addr, flags); + + qemu_get_buffer(f, host, TARGET_PAGE_SIZE); + } + if (qemu_file_has_error(f)) { + return -EIO; + } + } while (!(flags & RAM_SAVE_FLAG_EOS)); + + return 0; +} + +void qemu_service_io(void) +{ + qemu_notify_event(); +} + +#ifdef HAS_AUDIO +struct soundhw { + const char *name; + const char *descr; + int enabled; + int isa; + union { + int (*init_isa) (qemu_irq *pic); + int (*init_pci) (PCIBus *bus); + } init; +}; + +static struct soundhw soundhw[] = { +#ifdef HAS_AUDIO_CHOICE +#if defined(TARGET_I386) || defined(TARGET_MIPS) + { + "pcspk", + "PC speaker", + 0, + 1, + { .init_isa = pcspk_audio_init } + }, +#endif + +#ifdef CONFIG_SB16 + { + "sb16", + "Creative Sound Blaster 16", + 0, + 1, + { .init_isa = SB16_init } + }, +#endif + +#ifdef CONFIG_CS4231A + { + "cs4231a", + "CS4231A", + 0, + 1, + { .init_isa = cs4231a_init } + }, +#endif + +#ifdef CONFIG_ADLIB + { + "adlib", +#ifdef HAS_YMF262 + "Yamaha YMF262 (OPL3)", +#else + "Yamaha YM3812 (OPL2)", +#endif + 0, + 1, + { .init_isa = Adlib_init } + }, +#endif + +#ifdef CONFIG_GUS + { + "gus", + "Gravis Ultrasound GF1", + 0, + 1, + { .init_isa = GUS_init } + }, +#endif + +#ifdef CONFIG_AC97 + { + "ac97", + "Intel 82801AA AC97 Audio", + 0, + 0, + { .init_pci = ac97_init } + }, +#endif + +#ifdef CONFIG_ES1370 + { + "es1370", + "ENSONIQ AudioPCI ES1370", + 0, + 0, + { .init_pci = es1370_init } + }, +#endif + +#ifdef CONFIG_HDA + { + "hda", + "Intel HD Audio", + 0, + 0, + { .init_pci = intel_hda_and_codec_init } + }, +#endif + +#endif /* HAS_AUDIO_CHOICE */ + + { NULL, NULL, 0, 0, { NULL } } +}; + +void select_soundhw(const char *optarg) +{ + struct soundhw *c; + + if (*optarg == '?') { + show_valid_cards: + + printf("Valid sound card names (comma separated):\n"); + for (c = soundhw; c->name; ++c) { + printf ("%-11s %s\n", c->name, c->descr); + } + printf("\n-soundhw all will enable all of the above\n"); + exit(*optarg != '?'); + } + else { + size_t l; + const char *p; + char *e; + int bad_card = 0; + + if (!strcmp(optarg, "all")) { + for (c = soundhw; c->name; ++c) { + c->enabled = 1; + } + return; + } + + p = optarg; + while (*p) { + e = strchr(p, ','); + l = !e ? strlen(p) : (size_t) (e - p); + + for (c = soundhw; c->name; ++c) { + if (!strncmp(c->name, p, l) && !c->name[l]) { + c->enabled = 1; + break; + } + } + + if (!c->name) { + if (l > 80) { + fprintf(stderr, + "Unknown sound card name (too big to show)\n"); + } + else { + fprintf(stderr, "Unknown sound card name `%.*s'\n", + (int) l, p); + } + bad_card = 1; + } + p += l + (e != NULL); + } + + if (bad_card) { + goto show_valid_cards; + } + } +} + +void audio_init(qemu_irq *isa_pic, PCIBus *pci_bus) +{ + struct soundhw *c; + + for (c = soundhw; c->name; ++c) { + if (c->enabled) { + if (c->isa) { + if (isa_pic) { + c->init.init_isa(isa_pic); + } + } else { + if (pci_bus) { + c->init.init_pci(pci_bus); + } + } + } + } +} +#else +void select_soundhw(const char *optarg) +{ +} +void audio_init(qemu_irq *isa_pic, PCIBus *pci_bus) +{ +} +#endif + +int qemu_uuid_parse(const char *str, uint8_t *uuid) +{ + int ret; + + if (strlen(str) != 36) { + return -1; + } + + ret = sscanf(str, UUID_FMT, &uuid[0], &uuid[1], &uuid[2], &uuid[3], + &uuid[4], &uuid[5], &uuid[6], &uuid[7], &uuid[8], &uuid[9], + &uuid[10], &uuid[11], &uuid[12], &uuid[13], &uuid[14], + &uuid[15]); + + if (ret != 16) { + return -1; + } +#ifdef TARGET_I386 + smbios_add_field(1, offsetof(struct smbios_type_1, uuid), 16, uuid); +#endif + return 0; +} + +void do_acpitable_option(const char *optarg) +{ +#ifdef TARGET_I386 + if (acpi_table_add(optarg) < 0) { + fprintf(stderr, "Wrong acpi table provided\n"); + exit(1); + } +#endif +} + +void do_smbios_option(const char *optarg) +{ +#ifdef TARGET_I386 + if (smbios_entry_add(optarg) < 0) { + fprintf(stderr, "Wrong smbios provided\n"); + exit(1); + } +#endif +} + +void cpudef_init(void) +{ +#if defined(cpudef_setup) + cpudef_setup(); /* parse cpu definitions in target config file */ +#endif +} + +int audio_available(void) +{ +#ifdef HAS_AUDIO + return 1; +#else + return 0; +#endif +} + +int kvm_available(void) +{ +#ifdef CONFIG_KVM + return 1; +#else + return 0; +#endif +} + +int xen_available(void) +{ +#ifdef CONFIG_XEN + return 1; +#else + return 0; +#endif +} diff --git a/arch_init.h b/arch_init.h new file mode 100644 index 0000000..17c9164 --- /dev/null +++ b/arch_init.h @@ -0,0 +1,34 @@ +#ifndef QEMU_ARCH_INIT_H +#define QEMU_ARCH_INIT_H + +extern const char arch_config_name[]; + +enum { + QEMU_ARCH_ALL = -1, + QEMU_ARCH_ALPHA = 1, + QEMU_ARCH_ARM = 2, + QEMU_ARCH_CRIS = 4, + QEMU_ARCH_I386 = 8, + QEMU_ARCH_M68K = 16, + QEMU_ARCH_MICROBLAZE = 32, + QEMU_ARCH_MIPS = 64, + QEMU_ARCH_PPC = 128, + QEMU_ARCH_S390X = 256, + QEMU_ARCH_SH4 = 512, + QEMU_ARCH_SPARC = 1024, +}; + +extern const uint32_t arch_type; + +void select_soundhw(const char *optarg); +int ram_save_live(Monitor *mon, QEMUFile *f, int stage, void *opaque); +int ram_load(QEMUFile *f, void *opaque, int version_id); +void do_acpitable_option(const char *optarg); +void do_smbios_option(const char *optarg); +void cpudef_init(void); +int audio_available(void); +void audio_init(qemu_irq *isa_pic, PCIBus *pci_bus); +int kvm_available(void); +int xen_available(void); + +#endif diff --git a/arm-dis.c b/arm-dis.c new file mode 100644 index 0000000..3ece02c --- /dev/null +++ b/arm-dis.c @@ -0,0 +1,4136 @@ +/* Instruction printing code for the ARM + Copyright 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004 + 2007, Free Software Foundation, Inc. + Contributed by Richard Earnshaw (rwe@pegasus.esprit.ec.org) + Modification by James G. Smith (jsmith@cygnus.co.uk) + + This file is part of libopcodes. + + 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 . */ + +/* Start of qemu specific additions. Mostly this is stub definitions + for things we don't care about. */ + +#include "dis-asm.h" +#define ATTRIBUTE_UNUSED __attribute__((unused)) +#define ISSPACE(x) ((x) == ' ' || (x) == '\t' || (x) == '\n') + +#define ARM_EXT_V1 0 +#define ARM_EXT_V2 0 +#define ARM_EXT_V2S 0 +#define ARM_EXT_V3 0 +#define ARM_EXT_V3M 0 +#define ARM_EXT_V4 0 +#define ARM_EXT_V4T 0 +#define ARM_EXT_V5 0 +#define ARM_EXT_V5T 0 +#define ARM_EXT_V5ExP 0 +#define ARM_EXT_V5E 0 +#define ARM_EXT_V5J 0 +#define ARM_EXT_V6 0 +#define ARM_EXT_V6K 0 +#define ARM_EXT_V6Z 0 +#define ARM_EXT_V6T2 0 +#define ARM_EXT_V7 0 +#define ARM_EXT_DIV 0 + +/* Co-processor space extensions. */ +#define ARM_CEXT_XSCALE 0 +#define ARM_CEXT_MAVERICK 0 +#define ARM_CEXT_IWMMXT 0 + +#define FPU_FPA_EXT_V1 0 +#define FPU_FPA_EXT_V2 0 +#define FPU_VFP_EXT_NONE 0 +#define FPU_VFP_EXT_V1xD 0 +#define FPU_VFP_EXT_V1 0 +#define FPU_VFP_EXT_V2 0 +#define FPU_MAVERICK 0 +#define FPU_VFP_EXT_V3 0 +#define FPU_NEON_EXT_V1 0 + +/* Assume host uses ieee float. */ +static void floatformat_to_double (unsigned char *data, double *dest) +{ + union { + uint32_t i; + float f; + } u; + u.i = data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24); + *dest = u.f; +} + +/* End of qemu specific additions. */ + +/* FIXME: Belongs in global header. */ +#ifndef strneq +#define strneq(a,b,n) (strncmp ((a), (b), (n)) == 0) +#endif + +#ifndef NUM_ELEM +#define NUM_ELEM(a) (sizeof (a) / sizeof (a)[0]) +#endif + +struct opcode32 +{ + unsigned long arch; /* Architecture defining this insn. */ + unsigned long value, mask; /* Recognise insn if (op&mask)==value. */ + const char *assembler; /* How to disassemble this insn. */ +}; + +struct opcode16 +{ + unsigned long arch; /* Architecture defining this insn. */ + unsigned short value, mask; /* Recognise insn if (op&mask)==value. */ + const char *assembler; /* How to disassemble this insn. */ +}; + +/* print_insn_coprocessor recognizes the following format control codes: + + %% % + + %c print condition code (always bits 28-31 in ARM mode) + %q print shifter argument + %u print condition code (unconditional in ARM mode) + %A print address for ldc/stc/ldf/stf instruction + %B print vstm/vldm register list + %C print vstr/vldr address operand + %I print cirrus signed shift immediate: bits 0..3|4..6 + %F print the COUNT field of a LFM/SFM instruction. + %P print floating point precision in arithmetic insn + %Q print floating point precision in ldf/stf insn + %R print floating point rounding mode + + %r print as an ARM register + %d print the bitfield in decimal + %k print immediate for VFPv3 conversion instruction + %x print the bitfield in hex + %X print the bitfield as 1 hex digit without leading "0x" + %f print a floating point constant if >7 else a + floating point register + %w print as an iWMMXt width field - [bhwd]ss/us + %g print as an iWMMXt 64-bit register + %G print as an iWMMXt general purpose or control register + %D print as a NEON D register + %Q print as a NEON Q register + + %y print a single precision VFP reg. + Codes: 0=>Sm, 1=>Sd, 2=>Sn, 3=>multi-list, 4=>Sm pair + %z print a double precision VFP reg + Codes: 0=>Dm, 1=>Dd, 2=>Dn, 3=>multi-list + + %'c print specified char iff bitfield is all ones + %`c print specified char iff bitfield is all zeroes + %?ab... select from array of values in big endian order + + %L print as an iWMMXt N/M width field. + %Z print the Immediate of a WSHUFH instruction. + %l like 'A' except use byte offsets for 'B' & 'H' + versions. + %i print 5-bit immediate in bits 8,3..0 + (print "32" when 0) + %r print register offset address for wldt/wstr instruction +*/ + +/* Common coprocessor opcodes shared between Arm and Thumb-2. */ + +static const struct opcode32 coprocessor_opcodes[] = +{ + /* XScale instructions. */ + {ARM_CEXT_XSCALE, 0x0e200010, 0x0fff0ff0, "mia%c\tacc0, %0-3r, %12-15r"}, + {ARM_CEXT_XSCALE, 0x0e280010, 0x0fff0ff0, "miaph%c\tacc0, %0-3r, %12-15r"}, + {ARM_CEXT_XSCALE, 0x0e2c0010, 0x0ffc0ff0, "mia%17'T%17`B%16'T%16`B%c\tacc0, %0-3r, %12-15r"}, + {ARM_CEXT_XSCALE, 0x0c400000, 0x0ff00fff, "mar%c\tacc0, %12-15r, %16-19r"}, + {ARM_CEXT_XSCALE, 0x0c500000, 0x0ff00fff, "mra%c\t%12-15r, %16-19r, acc0"}, + + /* Intel Wireless MMX technology instructions. */ +#define FIRST_IWMMXT_INSN 0x0e130130 +#define IWMMXT_INSN_COUNT 73 + {ARM_CEXT_IWMMXT, 0x0e130130, 0x0f3f0fff, "tandc%22-23w%c\t%12-15r"}, + {ARM_CEXT_XSCALE, 0x0e400010, 0x0ff00f3f, "tbcst%6-7w%c\t%16-19g, %12-15r"}, + {ARM_CEXT_XSCALE, 0x0e130170, 0x0f3f0ff8, "textrc%22-23w%c\t%12-15r, #%0-2d"}, + {ARM_CEXT_XSCALE, 0x0e100070, 0x0f300ff0, "textrm%3?su%22-23w%c\t%12-15r, %16-19g, #%0-2d"}, + {ARM_CEXT_XSCALE, 0x0e600010, 0x0ff00f38, "tinsr%6-7w%c\t%16-19g, %12-15r, #%0-2d"}, + {ARM_CEXT_XSCALE, 0x0e000110, 0x0ff00fff, "tmcr%c\t%16-19G, %12-15r"}, + {ARM_CEXT_XSCALE, 0x0c400000, 0x0ff00ff0, "tmcrr%c\t%0-3g, %12-15r, %16-19r"}, + {ARM_CEXT_XSCALE, 0x0e2c0010, 0x0ffc0e10, "tmia%17?tb%16?tb%c\t%5-8g, %0-3r, %12-15r"}, + {ARM_CEXT_XSCALE, 0x0e200010, 0x0fff0e10, "tmia%c\t%5-8g, %0-3r, %12-15r"}, + {ARM_CEXT_XSCALE, 0x0e280010, 0x0fff0e10, "tmiaph%c\t%5-8g, %0-3r, %12-15r"}, + {ARM_CEXT_XSCALE, 0x0e100030, 0x0f300fff, "tmovmsk%22-23w%c\t%12-15r, %16-19g"}, + {ARM_CEXT_XSCALE, 0x0e100110, 0x0ff00ff0, "tmrc%c\t%12-15r, %16-19G"}, + {ARM_CEXT_XSCALE, 0x0c500000, 0x0ff00ff0, "tmrrc%c\t%12-15r, %16-19r, %0-3g"}, + {ARM_CEXT_XSCALE, 0x0e130150, 0x0f3f0fff, "torc%22-23w%c\t%12-15r"}, + {ARM_CEXT_XSCALE, 0x0e130190, 0x0f3f0fff, "torvsc%22-23w%c\t%12-15r"}, + {ARM_CEXT_XSCALE, 0x0e2001c0, 0x0f300fff, "wabs%22-23w%c\t%12-15g, %16-19g"}, + {ARM_CEXT_XSCALE, 0x0e0001c0, 0x0f300fff, "wacc%22-23w%c\t%12-15g, %16-19g"}, + {ARM_CEXT_XSCALE, 0x0e000180, 0x0f000ff0, "wadd%20-23w%c\t%12-15g, %16-19g, %0-3g"}, + {ARM_CEXT_XSCALE, 0x0e2001a0, 0x0f300ff0, "waddbhus%22?ml%c\t%12-15g, %16-19g, %0-3g"}, + {ARM_CEXT_XSCALE, 0x0ea001a0, 0x0ff00ff0, "waddsubhx%c\t%12-15g, %16-19g, %0-3g"}, + {ARM_CEXT_XSCALE, 0x0e000020, 0x0f800ff0, "waligni%c\t%12-15g, %16-19g, %0-3g, #%20-22d"}, + {ARM_CEXT_XSCALE, 0x0e800020, 0x0fc00ff0, "walignr%20-21d%c\t%12-15g, %16-19g, %0-3g"}, + {ARM_CEXT_XSCALE, 0x0e200000, 0x0fe00ff0, "wand%20'n%c\t%12-15g, %16-19g, %0-3g"}, + {ARM_CEXT_XSCALE, 0x0e800000, 0x0fa00ff0, "wavg2%22?hb%20'r%c\t%12-15g, %16-19g, %0-3g"}, + {ARM_CEXT_XSCALE, 0x0e400000, 0x0fe00ff0, "wavg4%20'r%c\t%12-15g, %16-19g, %0-3g"}, + {ARM_CEXT_XSCALE, 0x0e000060, 0x0f300ff0, "wcmpeq%22-23w%c\t%12-15g, %16-19g, %0-3g"}, + {ARM_CEXT_XSCALE, 0x0e100060, 0x0f100ff0, "wcmpgt%21?su%22-23w%c\t%12-15g, %16-19g, %0-3g"}, + {ARM_CEXT_XSCALE, 0xfc500100, 0xfe500f00, "wldrd\t%12-15g, %r"}, + {ARM_CEXT_XSCALE, 0xfc100100, 0xfe500f00, "wldrw\t%12-15G, %A"}, + {ARM_CEXT_XSCALE, 0x0c100000, 0x0e100e00, "wldr%L%c\t%12-15g, %l"}, + {ARM_CEXT_XSCALE, 0x0e400100, 0x0fc00ff0, "wmac%21?su%20'z%c\t%12-15g, %16-19g, %0-3g"}, + {ARM_CEXT_XSCALE, 0x0e800100, 0x0fc00ff0, "wmadd%21?su%20'x%c\t%12-15g, %16-19g, %0-3g"}, + {ARM_CEXT_XSCALE, 0x0ec00100, 0x0fd00ff0, "wmadd%21?sun%c\t%12-15g, %16-19g, %0-3g"}, + {ARM_CEXT_XSCALE, 0x0e000160, 0x0f100ff0, "wmax%21?su%22-23w%c\t%12-15g, %16-19g, %0-3g"}, + {ARM_CEXT_XSCALE, 0x0e000080, 0x0f100fe0, "wmerge%c\t%12-15g, %16-19g, %0-3g, #%21-23d"}, + {ARM_CEXT_XSCALE, 0x0e0000a0, 0x0f800ff0, "wmia%21?tb%20?tb%22'n%c\t%12-15g, %16-19g, %0-3g"}, + {ARM_CEXT_XSCALE, 0x0e800120, 0x0f800ff0, "wmiaw%21?tb%20?tb%22'n%c\t%12-15g, %16-19g, %0-3g"}, + {ARM_CEXT_XSCALE, 0x0e100160, 0x0f100ff0, "wmin%21?su%22-23w%c\t%12-15g, %16-19g, %0-3g"}, + {ARM_CEXT_XSCALE, 0x0e000100, 0x0fc00ff0, "wmul%21?su%20?ml%23'r%c\t%12-15g, %16-19g, %0-3g"}, + {ARM_CEXT_XSCALE, 0x0ed00100, 0x0fd00ff0, "wmul%21?sumr%c\t%12-15g, %16-19g, %0-3g"}, + {ARM_CEXT_XSCALE, 0x0ee000c0, 0x0fe00ff0, "wmulwsm%20`r%c\t%12-15g, %16-19g, %0-3g"}, + {ARM_CEXT_XSCALE, 0x0ec000c0, 0x0fe00ff0, "wmulwum%20`r%c\t%12-15g, %16-19g, %0-3g"}, + {ARM_CEXT_XSCALE, 0x0eb000c0, 0x0ff00ff0, "wmulwl%c\t%12-15g, %16-19g, %0-3g"}, + {ARM_CEXT_XSCALE, 0x0e8000a0, 0x0f800ff0, "wqmia%21?tb%20?tb%22'n%c\t%12-15g, %16-19g, %0-3g"}, + {ARM_CEXT_XSCALE, 0x0e100080, 0x0fd00ff0, "wqmulm%21'r%c\t%12-15g, %16-19g, %0-3g"}, + {ARM_CEXT_XSCALE, 0x0ec000e0, 0x0fd00ff0, "wqmulwm%21'r%c\t%12-15g, %16-19g, %0-3g"}, + {ARM_CEXT_XSCALE, 0x0e000000, 0x0ff00ff0, "wor%c\t%12-15g, %16-19g, %0-3g"}, + {ARM_CEXT_XSCALE, 0x0e000080, 0x0f000ff0, "wpack%20-23w%c\t%12-15g, %16-19g, %0-3g"}, + {ARM_CEXT_XSCALE, 0xfe300040, 0xff300ef0, "wror%22-23w\t%12-15g, %16-19g, #%i"}, + {ARM_CEXT_XSCALE, 0x0e300040, 0x0f300ff0, "wror%22-23w%c\t%12-15g, %16-19g, %0-3g"}, + {ARM_CEXT_XSCALE, 0x0e300140, 0x0f300ff0, "wror%22-23wg%c\t%12-15g, %16-19g, %0-3G"}, + {ARM_CEXT_XSCALE, 0x0e000120, 0x0fa00ff0, "wsad%22?hb%20'z%c\t%12-15g, %16-19g, %0-3g"}, + {ARM_CEXT_XSCALE, 0x0e0001e0, 0x0f000ff0, "wshufh%c\t%12-15g, %16-19g, #%Z"}, + {ARM_CEXT_XSCALE, 0xfe100040, 0xff300ef0, "wsll%22-23w\t%12-15g, %16-19g, #%i"}, + {ARM_CEXT_XSCALE, 0x0e100040, 0x0f300ff0, "wsll%22-23w%8'g%c\t%12-15g, %16-19g, %0-3g"}, + {ARM_CEXT_XSCALE, 0x0e100148, 0x0f300ffc, "wsll%22-23w%8'g%c\t%12-15g, %16-19g, %0-3G"}, + {ARM_CEXT_XSCALE, 0xfe000040, 0xff300ef0, "wsra%22-23w\t%12-15g, %16-19g, #%i"}, + {ARM_CEXT_XSCALE, 0x0e000040, 0x0f300ff0, "wsra%22-23w%8'g%c\t%12-15g, %16-19g, %0-3g"}, + {ARM_CEXT_XSCALE, 0x0e000148, 0x0f300ffc, "wsra%22-23w%8'g%c\t%12-15g, %16-19g, %0-3G"}, + {ARM_CEXT_XSCALE, 0xfe200040, 0xff300ef0, "wsrl%22-23w\t%12-15g, %16-19g, #%i"}, + {ARM_CEXT_XSCALE, 0x0e200040, 0x0f300ff0, "wsrl%22-23w%8'g%c\t%12-15g, %16-19g, %0-3g"}, + {ARM_CEXT_XSCALE, 0x0e200148, 0x0f300ffc, "wsrl%22-23w%8'g%c\t%12-15g, %16-19g, %0-3G"}, + {ARM_CEXT_XSCALE, 0xfc400100, 0xfe500f00, "wstrd\t%12-15g, %r"}, + {ARM_CEXT_XSCALE, 0xfc000100, 0xfe500f00, "wstrw\t%12-15G, %A"}, + {ARM_CEXT_XSCALE, 0x0c000000, 0x0e100e00, "wstr%L%c\t%12-15g, %l"}, + {ARM_CEXT_XSCALE, 0x0e0001a0, 0x0f000ff0, "wsub%20-23w%c\t%12-15g, %16-19g, %0-3g"}, + {ARM_CEXT_XSCALE, 0x0ed001c0, 0x0ff00ff0, "wsubaddhx%c\t%12-15g, %16-19g, %0-3g"}, + {ARM_CEXT_XSCALE, 0x0e1001c0, 0x0f300ff0, "wabsdiff%22-23w%c\t%12-15g, %16-19g, %0-3g"}, + {ARM_CEXT_XSCALE, 0x0e0000c0, 0x0fd00fff, "wunpckeh%21?sub%c\t%12-15g, %16-19g"}, + {ARM_CEXT_XSCALE, 0x0e4000c0, 0x0fd00fff, "wunpckeh%21?suh%c\t%12-15g, %16-19g"}, + {ARM_CEXT_XSCALE, 0x0e8000c0, 0x0fd00fff, "wunpckeh%21?suw%c\t%12-15g, %16-19g"}, + {ARM_CEXT_XSCALE, 0x0e0000e0, 0x0f100fff, "wunpckel%21?su%22-23w%c\t%12-15g, %16-19g"}, + {ARM_CEXT_XSCALE, 0x0e1000c0, 0x0f300ff0, "wunpckih%22-23w%c\t%12-15g, %16-19g, %0-3g"}, + {ARM_CEXT_XSCALE, 0x0e1000e0, 0x0f300ff0, "wunpckil%22-23w%c\t%12-15g, %16-19g, %0-3g"}, + {ARM_CEXT_XSCALE, 0x0e100000, 0x0ff00ff0, "wxor%c\t%12-15g, %16-19g, %0-3g"}, + + /* Floating point coprocessor (FPA) instructions */ + {FPU_FPA_EXT_V1, 0x0e000100, 0x0ff08f10, "adf%c%P%R\t%12-14f, %16-18f, %0-3f"}, + {FPU_FPA_EXT_V1, 0x0e100100, 0x0ff08f10, "muf%c%P%R\t%12-14f, %16-18f, %0-3f"}, + {FPU_FPA_EXT_V1, 0x0e200100, 0x0ff08f10, "suf%c%P%R\t%12-14f, %16-18f, %0-3f"}, + {FPU_FPA_EXT_V1, 0x0e300100, 0x0ff08f10, "rsf%c%P%R\t%12-14f, %16-18f, %0-3f"}, + {FPU_FPA_EXT_V1, 0x0e400100, 0x0ff08f10, "dvf%c%P%R\t%12-14f, %16-18f, %0-3f"}, + {FPU_FPA_EXT_V1, 0x0e500100, 0x0ff08f10, "rdf%c%P%R\t%12-14f, %16-18f, %0-3f"}, + {FPU_FPA_EXT_V1, 0x0e600100, 0x0ff08f10, "pow%c%P%R\t%12-14f, %16-18f, %0-3f"}, + {FPU_FPA_EXT_V1, 0x0e700100, 0x0ff08f10, "rpw%c%P%R\t%12-14f, %16-18f, %0-3f"}, + {FPU_FPA_EXT_V1, 0x0e800100, 0x0ff08f10, "rmf%c%P%R\t%12-14f, %16-18f, %0-3f"}, + {FPU_FPA_EXT_V1, 0x0e900100, 0x0ff08f10, "fml%c%P%R\t%12-14f, %16-18f, %0-3f"}, + {FPU_FPA_EXT_V1, 0x0ea00100, 0x0ff08f10, "fdv%c%P%R\t%12-14f, %16-18f, %0-3f"}, + {FPU_FPA_EXT_V1, 0x0eb00100, 0x0ff08f10, "frd%c%P%R\t%12-14f, %16-18f, %0-3f"}, + {FPU_FPA_EXT_V1, 0x0ec00100, 0x0ff08f10, "pol%c%P%R\t%12-14f, %16-18f, %0-3f"}, + {FPU_FPA_EXT_V1, 0x0e008100, 0x0ff08f10, "mvf%c%P%R\t%12-14f, %0-3f"}, + {FPU_FPA_EXT_V1, 0x0e108100, 0x0ff08f10, "mnf%c%P%R\t%12-14f, %0-3f"}, + {FPU_FPA_EXT_V1, 0x0e208100, 0x0ff08f10, "abs%c%P%R\t%12-14f, %0-3f"}, + {FPU_FPA_EXT_V1, 0x0e308100, 0x0ff08f10, "rnd%c%P%R\t%12-14f, %0-3f"}, + {FPU_FPA_EXT_V1, 0x0e408100, 0x0ff08f10, "sqt%c%P%R\t%12-14f, %0-3f"}, + {FPU_FPA_EXT_V1, 0x0e508100, 0x0ff08f10, "log%c%P%R\t%12-14f, %0-3f"}, + {FPU_FPA_EXT_V1, 0x0e608100, 0x0ff08f10, "lgn%c%P%R\t%12-14f, %0-3f"}, + {FPU_FPA_EXT_V1, 0x0e708100, 0x0ff08f10, "exp%c%P%R\t%12-14f, %0-3f"}, + {FPU_FPA_EXT_V1, 0x0e808100, 0x0ff08f10, "sin%c%P%R\t%12-14f, %0-3f"}, + {FPU_FPA_EXT_V1, 0x0e908100, 0x0ff08f10, "cos%c%P%R\t%12-14f, %0-3f"}, + {FPU_FPA_EXT_V1, 0x0ea08100, 0x0ff08f10, "tan%c%P%R\t%12-14f, %0-3f"}, + {FPU_FPA_EXT_V1, 0x0eb08100, 0x0ff08f10, "asn%c%P%R\t%12-14f, %0-3f"}, + {FPU_FPA_EXT_V1, 0x0ec08100, 0x0ff08f10, "acs%c%P%R\t%12-14f, %0-3f"}, + {FPU_FPA_EXT_V1, 0x0ed08100, 0x0ff08f10, "atn%c%P%R\t%12-14f, %0-3f"}, + {FPU_FPA_EXT_V1, 0x0ee08100, 0x0ff08f10, "urd%c%P%R\t%12-14f, %0-3f"}, + {FPU_FPA_EXT_V1, 0x0ef08100, 0x0ff08f10, "nrm%c%P%R\t%12-14f, %0-3f"}, + {FPU_FPA_EXT_V1, 0x0e000110, 0x0ff00f1f, "flt%c%P%R\t%16-18f, %12-15r"}, + {FPU_FPA_EXT_V1, 0x0e100110, 0x0fff0f98, "fix%c%R\t%12-15r, %0-2f"}, + {FPU_FPA_EXT_V1, 0x0e200110, 0x0fff0fff, "wfs%c\t%12-15r"}, + {FPU_FPA_EXT_V1, 0x0e300110, 0x0fff0fff, "rfs%c\t%12-15r"}, + {FPU_FPA_EXT_V1, 0x0e400110, 0x0fff0fff, "wfc%c\t%12-15r"}, + {FPU_FPA_EXT_V1, 0x0e500110, 0x0fff0fff, "rfc%c\t%12-15r"}, + {FPU_FPA_EXT_V1, 0x0e90f110, 0x0ff8fff0, "cmf%c\t%16-18f, %0-3f"}, + {FPU_FPA_EXT_V1, 0x0eb0f110, 0x0ff8fff0, "cnf%c\t%16-18f, %0-3f"}, + {FPU_FPA_EXT_V1, 0x0ed0f110, 0x0ff8fff0, "cmfe%c\t%16-18f, %0-3f"}, + {FPU_FPA_EXT_V1, 0x0ef0f110, 0x0ff8fff0, "cnfe%c\t%16-18f, %0-3f"}, + {FPU_FPA_EXT_V1, 0x0c000100, 0x0e100f00, "stf%c%Q\t%12-14f, %A"}, + {FPU_FPA_EXT_V1, 0x0c100100, 0x0e100f00, "ldf%c%Q\t%12-14f, %A"}, + {FPU_FPA_EXT_V2, 0x0c000200, 0x0e100f00, "sfm%c\t%12-14f, %F, %A"}, + {FPU_FPA_EXT_V2, 0x0c100200, 0x0e100f00, "lfm%c\t%12-14f, %F, %A"}, + + /* Register load/store */ + {FPU_NEON_EXT_V1, 0x0d200b00, 0x0fb00f01, "vstmdb%c\t%16-19r%21'!, %B"}, + {FPU_NEON_EXT_V1, 0x0d300b00, 0x0fb00f01, "vldmdb%c\t%16-19r%21'!, %B"}, + {FPU_NEON_EXT_V1, 0x0c800b00, 0x0f900f01, "vstmia%c\t%16-19r%21'!, %B"}, + {FPU_NEON_EXT_V1, 0x0c900b00, 0x0f900f01, "vldmia%c\t%16-19r%21'!, %B"}, + {FPU_NEON_EXT_V1, 0x0d000b00, 0x0f300f00, "vstr%c\t%12-15,22D, %C"}, + {FPU_NEON_EXT_V1, 0x0d100b00, 0x0f300f00, "vldr%c\t%12-15,22D, %C"}, + + /* Data transfer between ARM and NEON registers */ + {FPU_NEON_EXT_V1, 0x0e800b10, 0x0ff00f70, "vdup%c.32\t%16-19,7D, %12-15r"}, + {FPU_NEON_EXT_V1, 0x0e800b30, 0x0ff00f70, "vdup%c.16\t%16-19,7D, %12-15r"}, + {FPU_NEON_EXT_V1, 0x0ea00b10, 0x0ff00f70, "vdup%c.32\t%16-19,7Q, %12-15r"}, + {FPU_NEON_EXT_V1, 0x0ea00b30, 0x0ff00f70, "vdup%c.16\t%16-19,7Q, %12-15r"}, + {FPU_NEON_EXT_V1, 0x0ec00b10, 0x0ff00f70, "vdup%c.8\t%16-19,7D, %12-15r"}, + {FPU_NEON_EXT_V1, 0x0ee00b10, 0x0ff00f70, "vdup%c.8\t%16-19,7Q, %12-15r"}, + {FPU_NEON_EXT_V1, 0x0c400b10, 0x0ff00fd0, "vmov%c\t%0-3,5D, %12-15r, %16-19r"}, + {FPU_NEON_EXT_V1, 0x0c500b10, 0x0ff00fd0, "vmov%c\t%12-15r, %16-19r, %0-3,5D"}, + {FPU_NEON_EXT_V1, 0x0e000b10, 0x0fd00f70, "vmov%c.32\t%16-19,7D[%21d], %12-15r"}, + {FPU_NEON_EXT_V1, 0x0e100b10, 0x0f500f70, "vmov%c.32\t%12-15r, %16-19,7D[%21d]"}, + {FPU_NEON_EXT_V1, 0x0e000b30, 0x0fd00f30, "vmov%c.16\t%16-19,7D[%6,21d], %12-15r"}, + {FPU_NEON_EXT_V1, 0x0e100b30, 0x0f500f30, "vmov%c.%23?us16\t%12-15r, %16-19,7D[%6,21d]"}, + {FPU_NEON_EXT_V1, 0x0e400b10, 0x0fd00f10, "vmov%c.8\t%16-19,7D[%5,6,21d], %12-15r"}, + {FPU_NEON_EXT_V1, 0x0e500b10, 0x0f500f10, "vmov%c.%23?us8\t%12-15r, %16-19,7D[%5,6,21d]"}, + + /* Floating point coprocessor (VFP) instructions */ + {FPU_VFP_EXT_V1xD, 0x0ef1fa10, 0x0fffffff, "fmstat%c"}, + {FPU_VFP_EXT_V1xD, 0x0ee00a10, 0x0fff0fff, "fmxr%c\tfpsid, %12-15r"}, + {FPU_VFP_EXT_V1xD, 0x0ee10a10, 0x0fff0fff, "fmxr%c\tfpscr, %12-15r"}, + {FPU_VFP_EXT_V1xD, 0x0ee60a10, 0x0fff0fff, "fmxr%c\tmvfr1, %12-15r"}, + {FPU_VFP_EXT_V1xD, 0x0ee70a10, 0x0fff0fff, "fmxr%c\tmvfr0, %12-15r"}, + {FPU_VFP_EXT_V1xD, 0x0ee80a10, 0x0fff0fff, "fmxr%c\tfpexc, %12-15r"}, + {FPU_VFP_EXT_V1xD, 0x0ee90a10, 0x0fff0fff, "fmxr%c\tfpinst, %12-15r\t@ Impl def"}, + {FPU_VFP_EXT_V1xD, 0x0eea0a10, 0x0fff0fff, "fmxr%c\tfpinst2, %12-15r\t@ Impl def"}, + {FPU_VFP_EXT_V1xD, 0x0ef00a10, 0x0fff0fff, "fmrx%c\t%12-15r, fpsid"}, + {FPU_VFP_EXT_V1xD, 0x0ef10a10, 0x0fff0fff, "fmrx%c\t%12-15r, fpscr"}, + {FPU_VFP_EXT_V1xD, 0x0ef60a10, 0x0fff0fff, "fmrx%c\t%12-15r, mvfr1"}, + {FPU_VFP_EXT_V1xD, 0x0ef70a10, 0x0fff0fff, "fmrx%c\t%12-15r, mvfr0"}, + {FPU_VFP_EXT_V1xD, 0x0ef80a10, 0x0fff0fff, "fmrx%c\t%12-15r, fpexc"}, + {FPU_VFP_EXT_V1xD, 0x0ef90a10, 0x0fff0fff, "fmrx%c\t%12-15r, fpinst\t@ Impl def"}, + {FPU_VFP_EXT_V1xD, 0x0efa0a10, 0x0fff0fff, "fmrx%c\t%12-15r, fpinst2\t@ Impl def"}, + {FPU_VFP_EXT_V1, 0x0e000b10, 0x0ff00fff, "fmdlr%c\t%z2, %12-15r"}, + {FPU_VFP_EXT_V1, 0x0e100b10, 0x0ff00fff, "fmrdl%c\t%12-15r, %z2"}, + {FPU_VFP_EXT_V1, 0x0e200b10, 0x0ff00fff, "fmdhr%c\t%z2, %12-15r"}, + {FPU_VFP_EXT_V1, 0x0e300b10, 0x0ff00fff, "fmrdh%c\t%12-15r, %z2"}, + {FPU_VFP_EXT_V1xD, 0x0ee00a10, 0x0ff00fff, "fmxr%c\t, %12-15r"}, + {FPU_VFP_EXT_V1xD, 0x0ef00a10, 0x0ff00fff, "fmrx%c\t%12-15r, "}, + {FPU_VFP_EXT_V1xD, 0x0e000a10, 0x0ff00f7f, "fmsr%c\t%y2, %12-15r"}, + {FPU_VFP_EXT_V1xD, 0x0e100a10, 0x0ff00f7f, "fmrs%c\t%12-15r, %y2"}, + {FPU_VFP_EXT_V1xD, 0x0eb50a40, 0x0fbf0f70, "fcmp%7'ezs%c\t%y1"}, + {FPU_VFP_EXT_V1, 0x0eb50b40, 0x0fbf0f70, "fcmp%7'ezd%c\t%z1"}, + {FPU_VFP_EXT_V1xD, 0x0eb00a40, 0x0fbf0fd0, "fcpys%c\t%y1, %y0"}, + {FPU_VFP_EXT_V1xD, 0x0eb00ac0, 0x0fbf0fd0, "fabss%c\t%y1, %y0"}, + {FPU_VFP_EXT_V1, 0x0eb00b40, 0x0fbf0fd0, "fcpyd%c\t%z1, %z0"}, + {FPU_VFP_EXT_V1, 0x0eb00bc0, 0x0fbf0fd0, "fabsd%c\t%z1, %z0"}, + {FPU_VFP_EXT_V1xD, 0x0eb10a40, 0x0fbf0fd0, "fnegs%c\t%y1, %y0"}, + {FPU_VFP_EXT_V1xD, 0x0eb10ac0, 0x0fbf0fd0, "fsqrts%c\t%y1, %y0"}, + {FPU_VFP_EXT_V1, 0x0eb10b40, 0x0fbf0fd0, "fnegd%c\t%z1, %z0"}, + {FPU_VFP_EXT_V1, 0x0eb10bc0, 0x0fbf0fd0, "fsqrtd%c\t%z1, %z0"}, + {FPU_VFP_EXT_V1, 0x0eb70ac0, 0x0fbf0fd0, "fcvtds%c\t%z1, %y0"}, + {FPU_VFP_EXT_V1, 0x0eb70bc0, 0x0fbf0fd0, "fcvtsd%c\t%y1, %z0"}, + {FPU_VFP_EXT_V1xD, 0x0eb80a40, 0x0fbf0fd0, "fuitos%c\t%y1, %y0"}, + {FPU_VFP_EXT_V1xD, 0x0eb80ac0, 0x0fbf0fd0, "fsitos%c\t%y1, %y0"}, + {FPU_VFP_EXT_V1, 0x0eb80b40, 0x0fbf0fd0, "fuitod%c\t%z1, %y0"}, + {FPU_VFP_EXT_V1, 0x0eb80bc0, 0x0fbf0fd0, "fsitod%c\t%z1, %y0"}, + {FPU_VFP_EXT_V1xD, 0x0eb40a40, 0x0fbf0f50, "fcmp%7'es%c\t%y1, %y0"}, + {FPU_VFP_EXT_V1, 0x0eb40b40, 0x0fbf0f50, "fcmp%7'ed%c\t%z1, %z0"}, + {FPU_VFP_EXT_V3, 0x0eba0a40, 0x0fbe0f50, "f%16?us%7?lhtos%c\t%y1, #%5,0-3k"}, + {FPU_VFP_EXT_V3, 0x0eba0b40, 0x0fbe0f50, "f%16?us%7?lhtod%c\t%z1, #%5,0-3k"}, + {FPU_VFP_EXT_V1xD, 0x0ebc0a40, 0x0fbe0f50, "fto%16?sui%7'zs%c\t%y1, %y0"}, + {FPU_VFP_EXT_V1, 0x0ebc0b40, 0x0fbe0f50, "fto%16?sui%7'zd%c\t%y1, %z0"}, + {FPU_VFP_EXT_V3, 0x0ebe0a40, 0x0fbe0f50, "fto%16?us%7?lhs%c\t%y1, #%5,0-3k"}, + {FPU_VFP_EXT_V3, 0x0ebe0b40, 0x0fbe0f50, "fto%16?us%7?lhd%c\t%z1, #%5,0-3k"}, + {FPU_VFP_EXT_V1, 0x0c500b10, 0x0fb00ff0, "fmrrd%c\t%12-15r, %16-19r, %z0"}, + {FPU_VFP_EXT_V3, 0x0eb00a00, 0x0fb00ff0, "fconsts%c\t%y1, #%0-3,16-19d"}, + {FPU_VFP_EXT_V3, 0x0eb00b00, 0x0fb00ff0, "fconstd%c\t%z1, #%0-3,16-19d"}, + {FPU_VFP_EXT_V2, 0x0c400a10, 0x0ff00fd0, "fmsrr%c\t%y4, %12-15r, %16-19r"}, + {FPU_VFP_EXT_V2, 0x0c400b10, 0x0ff00fd0, "fmdrr%c\t%z0, %12-15r, %16-19r"}, + {FPU_VFP_EXT_V2, 0x0c500a10, 0x0ff00fd0, "fmrrs%c\t%12-15r, %16-19r, %y4"}, + {FPU_VFP_EXT_V1xD, 0x0e000a00, 0x0fb00f50, "fmacs%c\t%y1, %y2, %y0"}, + {FPU_VFP_EXT_V1xD, 0x0e000a40, 0x0fb00f50, "fnmacs%c\t%y1, %y2, %y0"}, + {FPU_VFP_EXT_V1, 0x0e000b00, 0x0fb00f50, "fmacd%c\t%z1, %z2, %z0"}, + {FPU_VFP_EXT_V1, 0x0e000b40, 0x0fb00f50, "fnmacd%c\t%z1, %z2, %z0"}, + {FPU_VFP_EXT_V1xD, 0x0e100a00, 0x0fb00f50, "fmscs%c\t%y1, %y2, %y0"}, + {FPU_VFP_EXT_V1xD, 0x0e100a40, 0x0fb00f50, "fnmscs%c\t%y1, %y2, %y0"}, + {FPU_VFP_EXT_V1, 0x0e100b00, 0x0fb00f50, "fmscd%c\t%z1, %z2, %z0"}, + {FPU_VFP_EXT_V1, 0x0e100b40, 0x0fb00f50, "fnmscd%c\t%z1, %z2, %z0"}, + {FPU_VFP_EXT_V1xD, 0x0e200a00, 0x0fb00f50, "fmuls%c\t%y1, %y2, %y0"}, + {FPU_VFP_EXT_V1xD, 0x0e200a40, 0x0fb00f50, "fnmuls%c\t%y1, %y2, %y0"}, + {FPU_VFP_EXT_V1, 0x0e200b00, 0x0fb00f50, "fmuld%c\t%z1, %z2, %z0"}, + {FPU_VFP_EXT_V1, 0x0e200b40, 0x0fb00f50, "fnmuld%c\t%z1, %z2, %z0"}, + {FPU_VFP_EXT_V1xD, 0x0e300a00, 0x0fb00f50, "fadds%c\t%y1, %y2, %y0"}, + {FPU_VFP_EXT_V1xD, 0x0e300a40, 0x0fb00f50, "fsubs%c\t%y1, %y2, %y0"}, + {FPU_VFP_EXT_V1, 0x0e300b00, 0x0fb00f50, "faddd%c\t%z1, %z2, %z0"}, + {FPU_VFP_EXT_V1, 0x0e300b40, 0x0fb00f50, "fsubd%c\t%z1, %z2, %z0"}, + {FPU_VFP_EXT_V1xD, 0x0e800a00, 0x0fb00f50, "fdivs%c\t%y1, %y2, %y0"}, + {FPU_VFP_EXT_V1, 0x0e800b00, 0x0fb00f50, "fdivd%c\t%z1, %z2, %z0"}, + {FPU_VFP_EXT_V1xD, 0x0d200a00, 0x0fb00f00, "fstmdbs%c\t%16-19r!, %y3"}, + {FPU_VFP_EXT_V1xD, 0x0d200b00, 0x0fb00f00, "fstmdb%0?xd%c\t%16-19r!, %z3"}, + {FPU_VFP_EXT_V1xD, 0x0d300a00, 0x0fb00f00, "fldmdbs%c\t%16-19r!, %y3"}, + {FPU_VFP_EXT_V1xD, 0x0d300b00, 0x0fb00f00, "fldmdb%0?xd%c\t%16-19r!, %z3"}, + {FPU_VFP_EXT_V1xD, 0x0d000a00, 0x0f300f00, "fsts%c\t%y1, %A"}, + {FPU_VFP_EXT_V1, 0x0d000b00, 0x0f300f00, "fstd%c\t%z1, %A"}, + {FPU_VFP_EXT_V1xD, 0x0d100a00, 0x0f300f00, "flds%c\t%y1, %A"}, + {FPU_VFP_EXT_V1, 0x0d100b00, 0x0f300f00, "fldd%c\t%z1, %A"}, + {FPU_VFP_EXT_V1xD, 0x0c800a00, 0x0f900f00, "fstmias%c\t%16-19r%21'!, %y3"}, + {FPU_VFP_EXT_V1xD, 0x0c800b00, 0x0f900f00, "fstmia%0?xd%c\t%16-19r%21'!, %z3"}, + {FPU_VFP_EXT_V1xD, 0x0c900a00, 0x0f900f00, "fldmias%c\t%16-19r%21'!, %y3"}, + {FPU_VFP_EXT_V1xD, 0x0c900b00, 0x0f900f00, "fldmia%0?xd%c\t%16-19r%21'!, %z3"}, + + /* Cirrus coprocessor instructions. */ + {ARM_CEXT_MAVERICK, 0x0d100400, 0x0f500f00, "cfldrs%c\tmvf%12-15d, %A"}, + {ARM_CEXT_MAVERICK, 0x0c100400, 0x0f500f00, "cfldrs%c\tmvf%12-15d, %A"}, + {ARM_CEXT_MAVERICK, 0x0d500400, 0x0f500f00, "cfldrd%c\tmvd%12-15d, %A"}, + {ARM_CEXT_MAVERICK, 0x0c500400, 0x0f500f00, "cfldrd%c\tmvd%12-15d, %A"}, + {ARM_CEXT_MAVERICK, 0x0d100500, 0x0f500f00, "cfldr32%c\tmvfx%12-15d, %A"}, + {ARM_CEXT_MAVERICK, 0x0c100500, 0x0f500f00, "cfldr32%c\tmvfx%12-15d, %A"}, + {ARM_CEXT_MAVERICK, 0x0d500500, 0x0f500f00, "cfldr64%c\tmvdx%12-15d, %A"}, + {ARM_CEXT_MAVERICK, 0x0c500500, 0x0f500f00, "cfldr64%c\tmvdx%12-15d, %A"}, + {ARM_CEXT_MAVERICK, 0x0d000400, 0x0f500f00, "cfstrs%c\tmvf%12-15d, %A"}, + {ARM_CEXT_MAVERICK, 0x0c000400, 0x0f500f00, "cfstrs%c\tmvf%12-15d, %A"}, + {ARM_CEXT_MAVERICK, 0x0d400400, 0x0f500f00, "cfstrd%c\tmvd%12-15d, %A"}, + {ARM_CEXT_MAVERICK, 0x0c400400, 0x0f500f00, "cfstrd%c\tmvd%12-15d, %A"}, + {ARM_CEXT_MAVERICK, 0x0d000500, 0x0f500f00, "cfstr32%c\tmvfx%12-15d, %A"}, + {ARM_CEXT_MAVERICK, 0x0c000500, 0x0f500f00, "cfstr32%c\tmvfx%12-15d, %A"}, + {ARM_CEXT_MAVERICK, 0x0d400500, 0x0f500f00, "cfstr64%c\tmvdx%12-15d, %A"}, + {ARM_CEXT_MAVERICK, 0x0c400500, 0x0f500f00, "cfstr64%c\tmvdx%12-15d, %A"}, + {ARM_CEXT_MAVERICK, 0x0e000450, 0x0ff00ff0, "cfmvsr%c\tmvf%16-19d, %12-15r"}, + {ARM_CEXT_MAVERICK, 0x0e100450, 0x0ff00ff0, "cfmvrs%c\t%12-15r, mvf%16-19d"}, + {ARM_CEXT_MAVERICK, 0x0e000410, 0x0ff00ff0, "cfmvdlr%c\tmvd%16-19d, %12-15r"}, + {ARM_CEXT_MAVERICK, 0x0e100410, 0x0ff00ff0, "cfmvrdl%c\t%12-15r, mvd%16-19d"}, + {ARM_CEXT_MAVERICK, 0x0e000430, 0x0ff00ff0, "cfmvdhr%c\tmvd%16-19d, %12-15r"}, + {ARM_CEXT_MAVERICK, 0x0e100430, 0x0ff00fff, "cfmvrdh%c\t%12-15r, mvd%16-19d"}, + {ARM_CEXT_MAVERICK, 0x0e000510, 0x0ff00fff, "cfmv64lr%c\tmvdx%16-19d, %12-15r"}, + {ARM_CEXT_MAVERICK, 0x0e100510, 0x0ff00fff, "cfmvr64l%c\t%12-15r, mvdx%16-19d"}, + {ARM_CEXT_MAVERICK, 0x0e000530, 0x0ff00fff, "cfmv64hr%c\tmvdx%16-19d, %12-15r"}, + {ARM_CEXT_MAVERICK, 0x0e100530, 0x0ff00fff, "cfmvr64h%c\t%12-15r, mvdx%16-19d"}, + {ARM_CEXT_MAVERICK, 0x0e200440, 0x0ff00fff, "cfmval32%c\tmvax%12-15d, mvfx%16-19d"}, + {ARM_CEXT_MAVERICK, 0x0e100440, 0x0ff00fff, "cfmv32al%c\tmvfx%12-15d, mvax%16-19d"}, + {ARM_CEXT_MAVERICK, 0x0e200460, 0x0ff00fff, "cfmvam32%c\tmvax%12-15d, mvfx%16-19d"}, + {ARM_CEXT_MAVERICK, 0x0e100460, 0x0ff00fff, "cfmv32am%c\tmvfx%12-15d, mvax%16-19d"}, + {ARM_CEXT_MAVERICK, 0x0e200480, 0x0ff00fff, "cfmvah32%c\tmvax%12-15d, mvfx%16-19d"}, + {ARM_CEXT_MAVERICK, 0x0e100480, 0x0ff00fff, "cfmv32ah%c\tmvfx%12-15d, mvax%16-19d"}, + {ARM_CEXT_MAVERICK, 0x0e2004a0, 0x0ff00fff, "cfmva32%c\tmvax%12-15d, mvfx%16-19d"}, + {ARM_CEXT_MAVERICK, 0x0e1004a0, 0x0ff00fff, "cfmv32a%c\tmvfx%12-15d, mvax%16-19d"}, + {ARM_CEXT_MAVERICK, 0x0e2004c0, 0x0ff00fff, "cfmva64%c\tmvax%12-15d, mvdx%16-19d"}, + {ARM_CEXT_MAVERICK, 0x0e1004c0, 0x0ff00fff, "cfmv64a%c\tmvdx%12-15d, mvax%16-19d"}, + {ARM_CEXT_MAVERICK, 0x0e2004e0, 0x0fff0fff, "cfmvsc32%c\tdspsc, mvdx%12-15d"}, + {ARM_CEXT_MAVERICK, 0x0e1004e0, 0x0fff0fff, "cfmv32sc%c\tmvdx%12-15d, dspsc"}, + {ARM_CEXT_MAVERICK, 0x0e000400, 0x0ff00fff, "cfcpys%c\tmvf%12-15d, mvf%16-19d"}, + {ARM_CEXT_MAVERICK, 0x0e000420, 0x0ff00fff, "cfcpyd%c\tmvd%12-15d, mvd%16-19d"}, + {ARM_CEXT_MAVERICK, 0x0e000460, 0x0ff00fff, "cfcvtsd%c\tmvd%12-15d, mvf%16-19d"}, + {ARM_CEXT_MAVERICK, 0x0e000440, 0x0ff00fff, "cfcvtds%c\tmvf%12-15d, mvd%16-19d"}, + {ARM_CEXT_MAVERICK, 0x0e000480, 0x0ff00fff, "cfcvt32s%c\tmvf%12-15d, mvfx%16-19d"}, + {ARM_CEXT_MAVERICK, 0x0e0004a0, 0x0ff00fff, "cfcvt32d%c\tmvd%12-15d, mvfx%16-19d"}, + {ARM_CEXT_MAVERICK, 0x0e0004c0, 0x0ff00fff, "cfcvt64s%c\tmvf%12-15d, mvdx%16-19d"}, + {ARM_CEXT_MAVERICK, 0x0e0004e0, 0x0ff00fff, "cfcvt64d%c\tmvd%12-15d, mvdx%16-19d"}, + {ARM_CEXT_MAVERICK, 0x0e100580, 0x0ff00fff, "cfcvts32%c\tmvfx%12-15d, mvf%16-19d"}, + {ARM_CEXT_MAVERICK, 0x0e1005a0, 0x0ff00fff, "cfcvtd32%c\tmvfx%12-15d, mvd%16-19d"}, + {ARM_CEXT_MAVERICK, 0x0e1005c0, 0x0ff00fff, "cftruncs32%c\tmvfx%12-15d, mvf%16-19d"}, + {ARM_CEXT_MAVERICK, 0x0e1005e0, 0x0ff00fff, "cftruncd32%c\tmvfx%12-15d, mvd%16-19d"}, + {ARM_CEXT_MAVERICK, 0x0e000550, 0x0ff00ff0, "cfrshl32%c\tmvfx%16-19d, mvfx%0-3d, %12-15r"}, + {ARM_CEXT_MAVERICK, 0x0e000570, 0x0ff00ff0, "cfrshl64%c\tmvdx%16-19d, mvdx%0-3d, %12-15r"}, + {ARM_CEXT_MAVERICK, 0x0e000500, 0x0ff00f10, "cfsh32%c\tmvfx%12-15d, mvfx%16-19d, #%I"}, + {ARM_CEXT_MAVERICK, 0x0e200500, 0x0ff00f10, "cfsh64%c\tmvdx%12-15d, mvdx%16-19d, #%I"}, + {ARM_CEXT_MAVERICK, 0x0e100490, 0x0ff00ff0, "cfcmps%c\t%12-15r, mvf%16-19d, mvf%0-3d"}, + {ARM_CEXT_MAVERICK, 0x0e1004b0, 0x0ff00ff0, "cfcmpd%c\t%12-15r, mvd%16-19d, mvd%0-3d"}, + {ARM_CEXT_MAVERICK, 0x0e100590, 0x0ff00ff0, "cfcmp32%c\t%12-15r, mvfx%16-19d, mvfx%0-3d"}, + {ARM_CEXT_MAVERICK, 0x0e1005b0, 0x0ff00ff0, "cfcmp64%c\t%12-15r, mvdx%16-19d, mvdx%0-3d"}, + {ARM_CEXT_MAVERICK, 0x0e300400, 0x0ff00fff, "cfabss%c\tmvf%12-15d, mvf%16-19d"}, + {ARM_CEXT_MAVERICK, 0x0e300420, 0x0ff00fff, "cfabsd%c\tmvd%12-15d, mvd%16-19d"}, + {ARM_CEXT_MAVERICK, 0x0e300440, 0x0ff00fff, "cfnegs%c\tmvf%12-15d, mvf%16-19d"}, + {ARM_CEXT_MAVERICK, 0x0e300460, 0x0ff00fff, "cfnegd%c\tmvd%12-15d, mvd%16-19d"}, + {ARM_CEXT_MAVERICK, 0x0e300480, 0x0ff00ff0, "cfadds%c\tmvf%12-15d, mvf%16-19d, mvf%0-3d"}, + {ARM_CEXT_MAVERICK, 0x0e3004a0, 0x0ff00ff0, "cfaddd%c\tmvd%12-15d, mvd%16-19d, mvd%0-3d"}, + {ARM_CEXT_MAVERICK, 0x0e3004c0, 0x0ff00ff0, "cfsubs%c\tmvf%12-15d, mvf%16-19d, mvf%0-3d"}, + {ARM_CEXT_MAVERICK, 0x0e3004e0, 0x0ff00ff0, "cfsubd%c\tmvd%12-15d, mvd%16-19d, mvd%0-3d"}, + {ARM_CEXT_MAVERICK, 0x0e100400, 0x0ff00ff0, "cfmuls%c\tmvf%12-15d, mvf%16-19d, mvf%0-3d"}, + {ARM_CEXT_MAVERICK, 0x0e100420, 0x0ff00ff0, "cfmuld%c\tmvd%12-15d, mvd%16-19d, mvd%0-3d"}, + {ARM_CEXT_MAVERICK, 0x0e300500, 0x0ff00fff, "cfabs32%c\tmvfx%12-15d, mvfx%16-19d"}, + {ARM_CEXT_MAVERICK, 0x0e300520, 0x0ff00fff, "cfabs64%c\tmvdx%12-15d, mvdx%16-19d"}, + {ARM_CEXT_MAVERICK, 0x0e300540, 0x0ff00fff, "cfneg32%c\tmvfx%12-15d, mvfx%16-19d"}, + {ARM_CEXT_MAVERICK, 0x0e300560, 0x0ff00fff, "cfneg64%c\tmvdx%12-15d, mvdx%16-19d"}, + {ARM_CEXT_MAVERICK, 0x0e300580, 0x0ff00ff0, "cfadd32%c\tmvfx%12-15d, mvfx%16-19d, mvfx%0-3d"}, + {ARM_CEXT_MAVERICK, 0x0e3005a0, 0x0ff00ff0, "cfadd64%c\tmvdx%12-15d, mvdx%16-19d, mvdx%0-3d"}, + {ARM_CEXT_MAVERICK, 0x0e3005c0, 0x0ff00ff0, "cfsub32%c\tmvfx%12-15d, mvfx%16-19d, mvfx%0-3d"}, + {ARM_CEXT_MAVERICK, 0x0e3005e0, 0x0ff00ff0, "cfsub64%c\tmvdx%12-15d, mvdx%16-19d, mvdx%0-3d"}, + {ARM_CEXT_MAVERICK, 0x0e100500, 0x0ff00ff0, "cfmul32%c\tmvfx%12-15d, mvfx%16-19d, mvfx%0-3d"}, + {ARM_CEXT_MAVERICK, 0x0e100520, 0x0ff00ff0, "cfmul64%c\tmvdx%12-15d, mvdx%16-19d, mvdx%0-3d"}, + {ARM_CEXT_MAVERICK, 0x0e100540, 0x0ff00ff0, "cfmac32%c\tmvfx%12-15d, mvfx%16-19d, mvfx%0-3d"}, + {ARM_CEXT_MAVERICK, 0x0e100560, 0x0ff00ff0, "cfmsc32%c\tmvfx%12-15d, mvfx%16-19d, mvfx%0-3d"}, + {ARM_CEXT_MAVERICK, 0x0e000600, 0x0ff00f10, "cfmadd32%c\tmvax%5-7d, mvfx%12-15d, mvfx%16-19d, mvfx%0-3d"}, + {ARM_CEXT_MAVERICK, 0x0e100600, 0x0ff00f10, "cfmsub32%c\tmvax%5-7d, mvfx%12-15d, mvfx%16-19d, mvfx%0-3d"}, + {ARM_CEXT_MAVERICK, 0x0e200600, 0x0ff00f10, "cfmadda32%c\tmvax%5-7d, mvax%12-15d, mvfx%16-19d, mvfx%0-3d"}, + {ARM_CEXT_MAVERICK, 0x0e300600, 0x0ff00f10, "cfmsuba32%c\tmvax%5-7d, mvax%12-15d, mvfx%16-19d, mvfx%0-3d"}, + + /* Generic coprocessor instructions */ + {ARM_EXT_V2, 0x0c400000, 0x0ff00000, "mcrr%c\t%8-11d, %4-7d, %12-15r, %16-19r, cr%0-3d"}, + {ARM_EXT_V2, 0x0c500000, 0x0ff00000, "mrrc%c\t%8-11d, %4-7d, %12-15r, %16-19r, cr%0-3d"}, + {ARM_EXT_V2, 0x0e000000, 0x0f000010, "cdp%c\t%8-11d, %20-23d, cr%12-15d, cr%16-19d, cr%0-3d, {%5-7d}"}, + {ARM_EXT_V2, 0x0e100010, 0x0f100010, "mrc%c\t%8-11d, %21-23d, %12-15r, cr%16-19d, cr%0-3d, {%5-7d}"}, + {ARM_EXT_V2, 0x0e000010, 0x0f100010, "mcr%c\t%8-11d, %21-23d, %12-15r, cr%16-19d, cr%0-3d, {%5-7d}"}, + {ARM_EXT_V2, 0x0c000000, 0x0e100000, "stc%22'l%c\t%8-11d, cr%12-15d, %A"}, + {ARM_EXT_V2, 0x0c100000, 0x0e100000, "ldc%22'l%c\t%8-11d, cr%12-15d, %A"}, + + /* V6 coprocessor instructions */ + {ARM_EXT_V6, 0xfc500000, 0xfff00000, "mrrc2%c\t%8-11d, %4-7d, %12-15r, %16-19r, cr%0-3d"}, + {ARM_EXT_V6, 0xfc400000, 0xfff00000, "mcrr2%c\t%8-11d, %4-7d, %12-15r, %16-19r, cr%0-3d"}, + + /* V5 coprocessor instructions */ + {ARM_EXT_V5, 0xfc100000, 0xfe100000, "ldc2%22'l%c\t%8-11d, cr%12-15d, %A"}, + {ARM_EXT_V5, 0xfc000000, 0xfe100000, "stc2%22'l%c\t%8-11d, cr%12-15d, %A"}, + {ARM_EXT_V5, 0xfe000000, 0xff000010, "cdp2%c\t%8-11d, %20-23d, cr%12-15d, cr%16-19d, cr%0-3d, {%5-7d}"}, + {ARM_EXT_V5, 0xfe000010, 0xff100010, "mcr2%c\t%8-11d, %21-23d, %12-15r, cr%16-19d, cr%0-3d, {%5-7d}"}, + {ARM_EXT_V5, 0xfe100010, 0xff100010, "mrc2%c\t%8-11d, %21-23d, %12-15r, cr%16-19d, cr%0-3d, {%5-7d}"}, + + {0, 0, 0, 0} +}; + +/* Neon opcode table: This does not encode the top byte -- that is + checked by the print_insn_neon routine, as it depends on whether we are + doing thumb32 or arm32 disassembly. */ + +/* print_insn_neon recognizes the following format control codes: + + %% % + + %c print condition code + %A print v{st,ld}[1234] operands + %B print v{st,ld}[1234] any one operands + %C print v{st,ld}[1234] single->all operands + %D print scalar + %E print vmov, vmvn, vorr, vbic encoded constant + %F print vtbl,vtbx register list + + %r print as an ARM register + %d print the bitfield in decimal + %e print the 2^N - bitfield in decimal + %D print as a NEON D register + %Q print as a NEON Q register + %R print as a NEON D or Q register + %Sn print byte scaled width limited by n + %Tn print short scaled width limited by n + %Un print long scaled width limited by n + + %'c print specified char iff bitfield is all ones + %`c print specified char iff bitfield is all zeroes + %?ab... select from array of values in big endian order */ + +static const struct opcode32 neon_opcodes[] = +{ + /* Extract */ + {FPU_NEON_EXT_V1, 0xf2b00840, 0xffb00850, "vext%c.8\t%12-15,22R, %16-19,7R, %0-3,5R, #%8-11d"}, + {FPU_NEON_EXT_V1, 0xf2b00000, 0xffb00810, "vext%c.8\t%12-15,22R, %16-19,7R, %0-3,5R, #%8-11d"}, + + /* Move data element to all lanes */ + {FPU_NEON_EXT_V1, 0xf3b40c00, 0xffb70f90, "vdup%c.32\t%12-15,22R, %0-3,5D[%19d]"}, + {FPU_NEON_EXT_V1, 0xf3b20c00, 0xffb30f90, "vdup%c.16\t%12-15,22R, %0-3,5D[%18-19d]"}, + {FPU_NEON_EXT_V1, 0xf3b10c00, 0xffb10f90, "vdup%c.8\t%12-15,22R, %0-3,5D[%17-19d]"}, + + /* Table lookup */ + {FPU_NEON_EXT_V1, 0xf3b00800, 0xffb00c50, "vtbl%c.8\t%12-15,22D, %F, %0-3,5D"}, + {FPU_NEON_EXT_V1, 0xf3b00840, 0xffb00c50, "vtbx%c.8\t%12-15,22D, %F, %0-3,5D"}, + + /* Two registers, miscellaneous */ + {FPU_NEON_EXT_V1, 0xf2880a10, 0xfebf0fd0, "vmovl%c.%24?us8\t%12-15,22Q, %0-3,5D"}, + {FPU_NEON_EXT_V1, 0xf2900a10, 0xfebf0fd0, "vmovl%c.%24?us16\t%12-15,22Q, %0-3,5D"}, + {FPU_NEON_EXT_V1, 0xf2a00a10, 0xfebf0fd0, "vmovl%c.%24?us32\t%12-15,22Q, %0-3,5D"}, + {FPU_NEON_EXT_V1, 0xf3b00500, 0xffbf0f90, "vcnt%c.8\t%12-15,22R, %0-3,5R"}, + {FPU_NEON_EXT_V1, 0xf3b00580, 0xffbf0f90, "vmvn%c\t%12-15,22R, %0-3,5R"}, + {FPU_NEON_EXT_V1, 0xf3b20000, 0xffbf0f90, "vswp%c\t%12-15,22R, %0-3,5R"}, + {FPU_NEON_EXT_V1, 0xf3b20200, 0xffb30fd0, "vmovn%c.i%18-19T2\t%12-15,22D, %0-3,5Q"}, + {FPU_NEON_EXT_V1, 0xf3b20240, 0xffb30fd0, "vqmovun%c.s%18-19T2\t%12-15,22D, %0-3,5Q"}, + {FPU_NEON_EXT_V1, 0xf3b20280, 0xffb30fd0, "vqmovn%c.s%18-19T2\t%12-15,22D, %0-3,5Q"}, + {FPU_NEON_EXT_V1, 0xf3b202c0, 0xffb30fd0, "vqmovn%c.u%18-19T2\t%12-15,22D, %0-3,5Q"}, + {FPU_NEON_EXT_V1, 0xf3b20300, 0xffb30fd0, "vshll%c.i%18-19S2\t%12-15,22Q, %0-3,5D, #%18-19S2"}, + {FPU_NEON_EXT_V1, 0xf3bb0400, 0xffbf0e90, "vrecpe%c.%8?fu%18-19S2\t%12-15,22R, %0-3,5R"}, + {FPU_NEON_EXT_V1, 0xf3bb0480, 0xffbf0e90, "vrsqrte%c.%8?fu%18-19S2\t%12-15,22R, %0-3,5R"}, + {FPU_NEON_EXT_V1, 0xf3b00000, 0xffb30f90, "vrev64%c.%18-19S2\t%12-15,22R, %0-3,5R"}, + {FPU_NEON_EXT_V1, 0xf3b00080, 0xffb30f90, "vrev32%c.%18-19S2\t%12-15,22R, %0-3,5R"}, + {FPU_NEON_EXT_V1, 0xf3b00100, 0xffb30f90, "vrev16%c.%18-19S2\t%12-15,22R, %0-3,5R"}, + {FPU_NEON_EXT_V1, 0xf3b00400, 0xffb30f90, "vcls%c.s%18-19S2\t%12-15,22R, %0-3,5R"}, + {FPU_NEON_EXT_V1, 0xf3b00480, 0xffb30f90, "vclz%c.i%18-19S2\t%12-15,22R, %0-3,5R"}, + {FPU_NEON_EXT_V1, 0xf3b00700, 0xffb30f90, "vqabs%c.s%18-19S2\t%12-15,22R, %0-3,5R"}, + {FPU_NEON_EXT_V1, 0xf3b00780, 0xffb30f90, "vqneg%c.s%18-19S2\t%12-15,22R, %0-3,5R"}, + {FPU_NEON_EXT_V1, 0xf3b20080, 0xffb30f90, "vtrn%c.%18-19S2\t%12-15,22R, %0-3,5R"}, + {FPU_NEON_EXT_V1, 0xf3b20100, 0xffb30f90, "vuzp%c.%18-19S2\t%12-15,22R, %0-3,5R"}, + {FPU_NEON_EXT_V1, 0xf3b20180, 0xffb30f90, "vzip%c.%18-19S2\t%12-15,22R, %0-3,5R"}, + {FPU_NEON_EXT_V1, 0xf3b10000, 0xffb30b90, "vcgt%c.%10?fs%18-19S2\t%12-15,22R, %0-3,5R, #0"}, + {FPU_NEON_EXT_V1, 0xf3b10080, 0xffb30b90, "vcge%c.%10?fs%18-19S2\t%12-15,22R, %0-3,5R, #0"}, + {FPU_NEON_EXT_V1, 0xf3b10100, 0xffb30b90, "vceq%c.%10?fi%18-19S2\t%12-15,22R, %0-3,5R, #0"}, + {FPU_NEON_EXT_V1, 0xf3b10180, 0xffb30b90, "vcle%c.%10?fs%18-19S2\t%12-15,22R, %0-3,5R, #0"}, + {FPU_NEON_EXT_V1, 0xf3b10200, 0xffb30b90, "vclt%c.%10?fs%18-19S2\t%12-15,22R, %0-3,5R, #0"}, + {FPU_NEON_EXT_V1, 0xf3b10300, 0xffb30b90, "vabs%c.%10?fs%18-19S2\t%12-15,22R, %0-3,5R"}, + {FPU_NEON_EXT_V1, 0xf3b10380, 0xffb30b90, "vneg%c.%10?fs%18-19S2\t%12-15,22R, %0-3,5R"}, + {FPU_NEON_EXT_V1, 0xf3b00200, 0xffb30f10, "vpaddl%c.%7?us%18-19S2\t%12-15,22R, %0-3,5R"}, + {FPU_NEON_EXT_V1, 0xf3b00600, 0xffb30f10, "vpadal%c.%7?us%18-19S2\t%12-15,22R, %0-3,5R"}, + {FPU_NEON_EXT_V1, 0xf3b30600, 0xffb30e10, "vcvt%c.%7-8?usff%18-19Sa.%7-8?ffus%18-19Sa\t%12-15,22R, %0-3,5R"}, + + /* Three registers of the same length */ + {FPU_NEON_EXT_V1, 0xf2000110, 0xffb00f10, "vand%c\t%12-15,22R, %16-19,7R, %0-3,5R"}, + {FPU_NEON_EXT_V1, 0xf2100110, 0xffb00f10, "vbic%c\t%12-15,22R, %16-19,7R, %0-3,5R"}, + {FPU_NEON_EXT_V1, 0xf2200110, 0xffb00f10, "vorr%c\t%12-15,22R, %16-19,7R, %0-3,5R"}, + {FPU_NEON_EXT_V1, 0xf2300110, 0xffb00f10, "vorn%c\t%12-15,22R, %16-19,7R, %0-3,5R"}, + {FPU_NEON_EXT_V1, 0xf3000110, 0xffb00f10, "veor%c\t%12-15,22R, %16-19,7R, %0-3,5R"}, + {FPU_NEON_EXT_V1, 0xf3100110, 0xffb00f10, "vbsl%c\t%12-15,22R, %16-19,7R, %0-3,5R"}, + {FPU_NEON_EXT_V1, 0xf3200110, 0xffb00f10, "vbit%c\t%12-15,22R, %16-19,7R, %0-3,5R"}, + {FPU_NEON_EXT_V1, 0xf3300110, 0xffb00f10, "vbif%c\t%12-15,22R, %16-19,7R, %0-3,5R"}, + {FPU_NEON_EXT_V1, 0xf2000d00, 0xffa00f10, "vadd%c.f%20U0\t%12-15,22R, %16-19,7R, %0-3,5R"}, + {FPU_NEON_EXT_V1, 0xf2000d10, 0xffa00f10, "vmla%c.f%20U0\t%12-15,22R, %16-19,7R, %0-3,5R"}, + {FPU_NEON_EXT_V1, 0xf2000e00, 0xffa00f10, "vceq%c.f%20U0\t%12-15,22R, %16-19,7R, %0-3,5R"}, + {FPU_NEON_EXT_V1, 0xf2000f00, 0xffa00f10, "vmax%c.f%20U0\t%12-15,22R, %16-19,7R, %0-3,5R"}, + {FPU_NEON_EXT_V1, 0xf2000f10, 0xffa00f10, "vrecps%c.f%20U0\t%12-15,22R, %16-19,7R, %0-3,5R"}, + {FPU_NEON_EXT_V1, 0xf2200d00, 0xffa00f10, "vsub%c.f%20U0\t%12-15,22R, %16-19,7R, %0-3,5R"}, + {FPU_NEON_EXT_V1, 0xf2200d10, 0xffa00f10, "vmls%c.f%20U0\t%12-15,22R, %16-19,7R, %0-3,5R"}, + {FPU_NEON_EXT_V1, 0xf2200f00, 0xffa00f10, "vmin%c.f%20U0\t%12-15,22R, %16-19,7R, %0-3,5R"}, + {FPU_NEON_EXT_V1, 0xf2200f10, 0xffa00f10, "vrsqrts%c.f%20U0\t%12-15,22R, %16-19,7R, %0-3,5R"}, + {FPU_NEON_EXT_V1, 0xf3000d00, 0xffa00f10, "vpadd%c.f%20U0\t%12-15,22R, %16-19,7R, %0-3,5R"}, + {FPU_NEON_EXT_V1, 0xf3000d10, 0xffa00f10, "vmul%c.f%20U0\t%12-15,22R, %16-19,7R, %0-3,5R"}, + {FPU_NEON_EXT_V1, 0xf3000e00, 0xffa00f10, "vcge%c.f%20U0\t%12-15,22R, %16-19,7R, %0-3,5R"}, + {FPU_NEON_EXT_V1, 0xf3000e10, 0xffa00f10, "vacge%c.f%20U0\t%12-15,22R, %16-19,7R, %0-3,5R"}, + {FPU_NEON_EXT_V1, 0xf3000f00, 0xffa00f10, "vpmax%c.f%20U0\t%12-15,22R, %16-19,7R, %0-3,5R"}, + {FPU_NEON_EXT_V1, 0xf3200d00, 0xffa00f10, "vabd%c.f%20U0\t%12-15,22R, %16-19,7R, %0-3,5R"}, + {FPU_NEON_EXT_V1, 0xf3200e00, 0xffa00f10, "vcgt%c.f%20U0\t%12-15,22R, %16-19,7R, %0-3,5R"}, + {FPU_NEON_EXT_V1, 0xf3200e10, 0xffa00f10, "vacgt%c.f%20U0\t%12-15,22R, %16-19,7R, %0-3,5R"}, + {FPU_NEON_EXT_V1, 0xf3200f00, 0xffa00f10, "vpmin%c.f%20U0\t%12-15,22R, %16-19,7R, %0-3,5R"}, + {FPU_NEON_EXT_V1, 0xf2000800, 0xff800f10, "vadd%c.i%20-21S3\t%12-15,22R, %16-19,7R, %0-3,5R"}, + {FPU_NEON_EXT_V1, 0xf2000810, 0xff800f10, "vtst%c.%20-21S2\t%12-15,22R, %16-19,7R, %0-3,5R"}, + {FPU_NEON_EXT_V1, 0xf2000900, 0xff800f10, "vmla%c.i%20-21S2\t%12-15,22R, %16-19,7R, %0-3,5R"}, + {FPU_NEON_EXT_V1, 0xf2000b00, 0xff800f10, "vqdmulh%c.s%20-21S6\t%12-15,22R, %16-19,7R, %0-3,5R"}, + {FPU_NEON_EXT_V1, 0xf2000b10, 0xff800f10, "vpadd%c.i%20-21S2\t%12-15,22R, %16-19,7R, %0-3,5R"}, + {FPU_NEON_EXT_V1, 0xf3000800, 0xff800f10, "vsub%c.i%20-21S3\t%12-15,22R, %16-19,7R, %0-3,5R"}, + {FPU_NEON_EXT_V1, 0xf3000810, 0xff800f10, "vceq%c.i%20-21S2\t%12-15,22R, %16-19,7R, %0-3,5R"}, + {FPU_NEON_EXT_V1, 0xf3000900, 0xff800f10, "vmls%c.i%20-21S2\t%12-15,22R, %16-19,7R, %0-3,5R"}, + {FPU_NEON_EXT_V1, 0xf3000b00, 0xff800f10, "vqrdmulh%c.s%20-21S6\t%12-15,22R, %16-19,7R, %0-3,5R"}, + {FPU_NEON_EXT_V1, 0xf2000000, 0xfe800f10, "vhadd%c.%24?us%20-21S2\t%12-15,22R, %16-19,7R, %0-3,5R"}, + {FPU_NEON_EXT_V1, 0xf2000010, 0xfe800f10, "vqadd%c.%24?us%20-21S3\t%12-15,22R, %16-19,7R, %0-3,5R"}, + {FPU_NEON_EXT_V1, 0xf2000100, 0xfe800f10, "vrhadd%c.%24?us%20-21S2\t%12-15,22R, %16-19,7R, %0-3,5R"}, + {FPU_NEON_EXT_V1, 0xf2000200, 0xfe800f10, "vhsub%c.%24?us%20-21S2\t%12-15,22R, %16-19,7R, %0-3,5R"}, + {FPU_NEON_EXT_V1, 0xf2000210, 0xfe800f10, "vqsub%c.%24?us%20-21S3\t%12-15,22R, %16-19,7R, %0-3,5R"}, + {FPU_NEON_EXT_V1, 0xf2000300, 0xfe800f10, "vcgt%c.%24?us%20-21S2\t%12-15,22R, %16-19,7R, %0-3,5R"}, + {FPU_NEON_EXT_V1, 0xf2000310, 0xfe800f10, "vcge%c.%24?us%20-21S2\t%12-15,22R, %16-19,7R, %0-3,5R"}, + {FPU_NEON_EXT_V1, 0xf2000400, 0xfe800f10, "vshl%c.%24?us%20-21S3\t%12-15,22R, %0-3,5R, %16-19,7R"}, + {FPU_NEON_EXT_V1, 0xf2000410, 0xfe800f10, "vqshl%c.%24?us%20-21S3\t%12-15,22R, %0-3,5R, %16-19,7R"}, + {FPU_NEON_EXT_V1, 0xf2000500, 0xfe800f10, "vrshl%c.%24?us%20-21S3\t%12-15,22R, %0-3,5R, %16-19,7R"}, + {FPU_NEON_EXT_V1, 0xf2000510, 0xfe800f10, "vqrshl%c.%24?us%20-21S3\t%12-15,22R, %0-3,5R, %16-19,7R"}, + {FPU_NEON_EXT_V1, 0xf2000600, 0xfe800f10, "vmax%c.%24?us%20-21S2\t%12-15,22R, %16-19,7R, %0-3,5R"}, + {FPU_NEON_EXT_V1, 0xf2000610, 0xfe800f10, "vmin%c.%24?us%20-21S2\t%12-15,22R, %16-19,7R, %0-3,5R"}, + {FPU_NEON_EXT_V1, 0xf2000700, 0xfe800f10, "vabd%c.%24?us%20-21S2\t%12-15,22R, %16-19,7R, %0-3,5R"}, + {FPU_NEON_EXT_V1, 0xf2000710, 0xfe800f10, "vaba%c.%24?us%20-21S2\t%12-15,22R, %16-19,7R, %0-3,5R"}, + {FPU_NEON_EXT_V1, 0xf2000910, 0xfe800f10, "vmul%c.%24?pi%20-21S2\t%12-15,22R, %16-19,7R, %0-3,5R"}, + {FPU_NEON_EXT_V1, 0xf2000a00, 0xfe800f10, "vpmax%c.%24?us%20-21S2\t%12-15,22R, %16-19,7R, %0-3,5R"}, + {FPU_NEON_EXT_V1, 0xf2000a10, 0xfe800f10, "vpmin%c.%24?us%20-21S2\t%12-15,22R, %16-19,7R, %0-3,5R"}, + + /* One register and an immediate value */ + {FPU_NEON_EXT_V1, 0xf2800e10, 0xfeb80fb0, "vmov%c.i8\t%12-15,22R, %E"}, + {FPU_NEON_EXT_V1, 0xf2800e30, 0xfeb80fb0, "vmov%c.i64\t%12-15,22R, %E"}, + {FPU_NEON_EXT_V1, 0xf2800f10, 0xfeb80fb0, "vmov%c.f32\t%12-15,22R, %E"}, + {FPU_NEON_EXT_V1, 0xf2800810, 0xfeb80db0, "vmov%c.i16\t%12-15,22R, %E"}, + {FPU_NEON_EXT_V1, 0xf2800830, 0xfeb80db0, "vmvn%c.i16\t%12-15,22R, %E"}, + {FPU_NEON_EXT_V1, 0xf2800910, 0xfeb80db0, "vorr%c.i16\t%12-15,22R, %E"}, + {FPU_NEON_EXT_V1, 0xf2800930, 0xfeb80db0, "vbic%c.i16\t%12-15,22R, %E"}, + {FPU_NEON_EXT_V1, 0xf2800c10, 0xfeb80eb0, "vmov%c.i32\t%12-15,22R, %E"}, + {FPU_NEON_EXT_V1, 0xf2800c30, 0xfeb80eb0, "vmvn%c.i32\t%12-15,22R, %E"}, + {FPU_NEON_EXT_V1, 0xf2800110, 0xfeb809b0, "vorr%c.i32\t%12-15,22R, %E"}, + {FPU_NEON_EXT_V1, 0xf2800130, 0xfeb809b0, "vbic%c.i32\t%12-15,22R, %E"}, + {FPU_NEON_EXT_V1, 0xf2800010, 0xfeb808b0, "vmov%c.i32\t%12-15,22R, %E"}, + {FPU_NEON_EXT_V1, 0xf2800030, 0xfeb808b0, "vmvn%c.i32\t%12-15,22R, %E"}, + + /* Two registers and a shift amount */ + {FPU_NEON_EXT_V1, 0xf2880810, 0xffb80fd0, "vshrn%c.i16\t%12-15,22D, %0-3,5Q, #%16-18e"}, + {FPU_NEON_EXT_V1, 0xf2880850, 0xffb80fd0, "vrshrn%c.i16\t%12-15,22D, %0-3,5Q, #%16-18e"}, + {FPU_NEON_EXT_V1, 0xf2880810, 0xfeb80fd0, "vqshrun%c.s16\t%12-15,22D, %0-3,5Q, #%16-18e"}, + {FPU_NEON_EXT_V1, 0xf2880850, 0xfeb80fd0, "vqrshrun%c.s16\t%12-15,22D, %0-3,5Q, #%16-18e"}, + {FPU_NEON_EXT_V1, 0xf2880910, 0xfeb80fd0, "vqshrn%c.%24?us16\t%12-15,22D, %0-3,5Q, #%16-18e"}, + {FPU_NEON_EXT_V1, 0xf2880950, 0xfeb80fd0, "vqrshrn%c.%24?us16\t%12-15,22D, %0-3,5Q, #%16-18e"}, + {FPU_NEON_EXT_V1, 0xf2880a10, 0xfeb80fd0, "vshll%c.%24?us8\t%12-15,22D, %0-3,5Q, #%16-18d"}, + {FPU_NEON_EXT_V1, 0xf2900810, 0xffb00fd0, "vshrn%c.i32\t%12-15,22D, %0-3,5Q, #%16-19e"}, + {FPU_NEON_EXT_V1, 0xf2900850, 0xffb00fd0, "vrshrn%c.i32\t%12-15,22D, %0-3,5Q, #%16-19e"}, + {FPU_NEON_EXT_V1, 0xf2880510, 0xffb80f90, "vshl%c.%24?us8\t%12-15,22R, %0-3,5R, #%16-18d"}, + {FPU_NEON_EXT_V1, 0xf3880410, 0xffb80f90, "vsri%c.8\t%12-15,22R, %0-3,5R, #%16-18e"}, + {FPU_NEON_EXT_V1, 0xf3880510, 0xffb80f90, "vsli%c.8\t%12-15,22R, %0-3,5R, #%16-18d"}, + {FPU_NEON_EXT_V1, 0xf3880610, 0xffb80f90, "vqshlu%c.s8\t%12-15,22R, %0-3,5R, #%16-18d"}, + {FPU_NEON_EXT_V1, 0xf2900810, 0xfeb00fd0, "vqshrun%c.s32\t%12-15,22D, %0-3,5Q, #%16-19e"}, + {FPU_NEON_EXT_V1, 0xf2900850, 0xfeb00fd0, "vqrshrun%c.s32\t%12-15,22D, %0-3,5Q, #%16-19e"}, + {FPU_NEON_EXT_V1, 0xf2900910, 0xfeb00fd0, "vqshrn%c.%24?us32\t%12-15,22D, %0-3,5Q, #%16-19e"}, + {FPU_NEON_EXT_V1, 0xf2900950, 0xfeb00fd0, "vqrshrn%c.%24?us32\t%12-15,22D, %0-3,5Q, #%16-19e"}, + {FPU_NEON_EXT_V1, 0xf2900a10, 0xfeb00fd0, "vshll%c.%24?us16\t%12-15,22D, %0-3,5Q, #%16-19d"}, + {FPU_NEON_EXT_V1, 0xf2880010, 0xfeb80f90, "vshr%c.%24?us8\t%12-15,22R, %0-3,5R, #%16-18e"}, + {FPU_NEON_EXT_V1, 0xf2880110, 0xfeb80f90, "vsra%c.%24?us8\t%12-15,22R, %0-3,5R, #%16-18e"}, + {FPU_NEON_EXT_V1, 0xf2880210, 0xfeb80f90, "vrshr%c.%24?us8\t%12-15,22R, %0-3,5R, #%16-18e"}, + {FPU_NEON_EXT_V1, 0xf2880310, 0xfeb80f90, "vrsra%c.%24?us8\t%12-15,22R, %0-3,5R, #%16-18e"}, + {FPU_NEON_EXT_V1, 0xf2880710, 0xfeb80f90, "vqshl%c.%24?us8\t%12-15,22R, %0-3,5R, #%16-18d"}, + {FPU_NEON_EXT_V1, 0xf2a00810, 0xffa00fd0, "vshrn%c.i64\t%12-15,22D, %0-3,5Q, #%16-20e"}, + {FPU_NEON_EXT_V1, 0xf2a00850, 0xffa00fd0, "vrshrn%c.i64\t%12-15,22D, %0-3,5Q, #%16-20e"}, + {FPU_NEON_EXT_V1, 0xf2900510, 0xffb00f90, "vshl%c.%24?us16\t%12-15,22R, %0-3,5R, #%16-19d"}, + {FPU_NEON_EXT_V1, 0xf3900410, 0xffb00f90, "vsri%c.16\t%12-15,22R, %0-3,5R, #%16-19e"}, + {FPU_NEON_EXT_V1, 0xf3900510, 0xffb00f90, "vsli%c.16\t%12-15,22R, %0-3,5R, #%16-19d"}, + {FPU_NEON_EXT_V1, 0xf3900610, 0xffb00f90, "vqshlu%c.s16\t%12-15,22R, %0-3,5R, #%16-19d"}, + {FPU_NEON_EXT_V1, 0xf2a00a10, 0xfea00fd0, "vshll%c.%24?us32\t%12-15,22D, %0-3,5Q, #%16-20d"}, + {FPU_NEON_EXT_V1, 0xf2900010, 0xfeb00f90, "vshr%c.%24?us16\t%12-15,22R, %0-3,5R, #%16-19e"}, + {FPU_NEON_EXT_V1, 0xf2900110, 0xfeb00f90, "vsra%c.%24?us16\t%12-15,22R, %0-3,5R, #%16-19e"}, + {FPU_NEON_EXT_V1, 0xf2900210, 0xfeb00f90, "vrshr%c.%24?us16\t%12-15,22R, %0-3,5R, #%16-19e"}, + {FPU_NEON_EXT_V1, 0xf2900310, 0xfeb00f90, "vrsra%c.%24?us16\t%12-15,22R, %0-3,5R, #%16-19e"}, + {FPU_NEON_EXT_V1, 0xf2900710, 0xfeb00f90, "vqshl%c.%24?us16\t%12-15,22R, %0-3,5R, #%16-19d"}, + {FPU_NEON_EXT_V1, 0xf2800810, 0xfec00fd0, "vqshrun%c.s64\t%12-15,22D, %0-3,5Q, #%16-20e"}, + {FPU_NEON_EXT_V1, 0xf2800850, 0xfec00fd0, "vqrshrun%c.s64\t%12-15,22D, %0-3,5Q, #%16-20e"}, + {FPU_NEON_EXT_V1, 0xf2800910, 0xfec00fd0, "vqshrn%c.%24?us64\t%12-15,22D, %0-3,5Q, #%16-20e"}, + {FPU_NEON_EXT_V1, 0xf2800950, 0xfec00fd0, "vqrshrn%c.%24?us64\t%12-15,22D, %0-3,5Q, #%16-20e"}, + {FPU_NEON_EXT_V1, 0xf2a00510, 0xffa00f90, "vshl%c.%24?us32\t%12-15,22R, %0-3,5R, #%16-20d"}, + {FPU_NEON_EXT_V1, 0xf3a00410, 0xffa00f90, "vsri%c.32\t%12-15,22R, %0-3,5R, #%16-20e"}, + {FPU_NEON_EXT_V1, 0xf3a00510, 0xffa00f90, "vsli%c.32\t%12-15,22R, %0-3,5R, #%16-20d"}, + {FPU_NEON_EXT_V1, 0xf3a00610, 0xffa00f90, "vqshlu%c.s32\t%12-15,22R, %0-3,5R, #%16-20d"}, + {FPU_NEON_EXT_V1, 0xf2a00010, 0xfea00f90, "vshr%c.%24?us32\t%12-15,22R, %0-3,5R, #%16-20e"}, + {FPU_NEON_EXT_V1, 0xf2a00110, 0xfea00f90, "vsra%c.%24?us32\t%12-15,22R, %0-3,5R, #%16-20e"}, + {FPU_NEON_EXT_V1, 0xf2a00210, 0xfea00f90, "vrshr%c.%24?us32\t%12-15,22R, %0-3,5R, #%16-20e"}, + {FPU_NEON_EXT_V1, 0xf2a00310, 0xfea00f90, "vrsra%c.%24?us32\t%12-15,22R, %0-3,5R, #%16-20e"}, + {FPU_NEON_EXT_V1, 0xf2a00710, 0xfea00f90, "vqshl%c.%24?us32\t%12-15,22R, %0-3,5R, #%16-20d"}, + {FPU_NEON_EXT_V1, 0xf2800590, 0xff800f90, "vshl%c.%24?us64\t%12-15,22R, %0-3,5R, #%16-21d"}, + {FPU_NEON_EXT_V1, 0xf3800490, 0xff800f90, "vsri%c.64\t%12-15,22R, %0-3,5R, #%16-21e"}, + {FPU_NEON_EXT_V1, 0xf3800590, 0xff800f90, "vsli%c.64\t%12-15,22R, %0-3,5R, #%16-21d"}, + {FPU_NEON_EXT_V1, 0xf3800690, 0xff800f90, "vqshlu%c.s64\t%12-15,22R, %0-3,5R, #%16-21d"}, + {FPU_NEON_EXT_V1, 0xf2800090, 0xfe800f90, "vshr%c.%24?us64\t%12-15,22R, %0-3,5R, #%16-21e"}, + {FPU_NEON_EXT_V1, 0xf2800190, 0xfe800f90, "vsra%c.%24?us64\t%12-15,22R, %0-3,5R, #%16-21e"}, + {FPU_NEON_EXT_V1, 0xf2800290, 0xfe800f90, "vrshr%c.%24?us64\t%12-15,22R, %0-3,5R, #%16-21e"}, + {FPU_NEON_EXT_V1, 0xf2800390, 0xfe800f90, "vrsra%c.%24?us64\t%12-15,22R, %0-3,5R, #%16-21e"}, + {FPU_NEON_EXT_V1, 0xf2800790, 0xfe800f90, "vqshl%c.%24?us64\t%12-15,22R, %0-3,5R, #%16-21d"}, + {FPU_NEON_EXT_V1, 0xf2a00e10, 0xfea00e90, "vcvt%c.%24,8?usff32.%24,8?ffus32\t%12-15,22R, %0-3,5R, #%16-20e"}, + + /* Three registers of different lengths */ + {FPU_NEON_EXT_V1, 0xf2800e00, 0xfea00f50, "vmull%c.p%20S0\t%12-15,22Q, %16-19,7D, %0-3,5D"}, + {FPU_NEON_EXT_V1, 0xf2800400, 0xff800f50, "vaddhn%c.i%20-21T2\t%12-15,22D, %16-19,7Q, %0-3,5Q"}, + {FPU_NEON_EXT_V1, 0xf2800600, 0xff800f50, "vsubhn%c.i%20-21T2\t%12-15,22D, %16-19,7Q, %0-3,5Q"}, + {FPU_NEON_EXT_V1, 0xf2800900, 0xff800f50, "vqdmlal%c.s%20-21S6\t%12-15,22Q, %16-19,7D, %0-3,5D"}, + {FPU_NEON_EXT_V1, 0xf2800b00, 0xff800f50, "vqdmlsl%c.s%20-21S6\t%12-15,22Q, %16-19,7D, %0-3,5D"}, + {FPU_NEON_EXT_V1, 0xf2800d00, 0xff800f50, "vqdmull%c.s%20-21S6\t%12-15,22Q, %16-19,7D, %0-3,5D"}, + {FPU_NEON_EXT_V1, 0xf3800400, 0xff800f50, "vraddhn%c.i%20-21T2\t%12-15,22D, %16-19,7Q, %0-3,5Q"}, + {FPU_NEON_EXT_V1, 0xf3800600, 0xff800f50, "vrsubhn%c.i%20-21T2\t%12-15,22D, %16-19,7Q, %0-3,5Q"}, + {FPU_NEON_EXT_V1, 0xf2800000, 0xfe800f50, "vaddl%c.%24?us%20-21S2\t%12-15,22Q, %16-19,7D, %0-3,5D"}, + {FPU_NEON_EXT_V1, 0xf2800100, 0xfe800f50, "vaddw%c.%24?us%20-21S2\t%12-15,22Q, %16-19,7Q, %0-3,5D"}, + {FPU_NEON_EXT_V1, 0xf2800200, 0xfe800f50, "vsubl%c.%24?us%20-21S2\t%12-15,22Q, %16-19,7D, %0-3,5D"}, + {FPU_NEON_EXT_V1, 0xf2800300, 0xfe800f50, "vsubw%c.%24?us%20-21S2\t%12-15,22Q, %16-19,7Q, %0-3,5D"}, + {FPU_NEON_EXT_V1, 0xf2800500, 0xfe800f50, "vabal%c.%24?us%20-21S2\t%12-15,22Q, %16-19,7D, %0-3,5D"}, + {FPU_NEON_EXT_V1, 0xf2800700, 0xfe800f50, "vabdl%c.%24?us%20-21S2\t%12-15,22Q, %16-19,7D, %0-3,5D"}, + {FPU_NEON_EXT_V1, 0xf2800800, 0xfe800f50, "vmlal%c.%24?us%20-21S2\t%12-15,22Q, %16-19,7D, %0-3,5D"}, + {FPU_NEON_EXT_V1, 0xf2800a00, 0xfe800f50, "vmlsl%c.%24?us%20-21S2\t%12-15,22Q, %16-19,7D, %0-3,5D"}, + {FPU_NEON_EXT_V1, 0xf2800c00, 0xfe800f50, "vmull%c.%24?us%20-21S2\t%12-15,22Q, %16-19,7D, %0-3,5D"}, + + /* Two registers and a scalar */ + {FPU_NEON_EXT_V1, 0xf2800040, 0xff800f50, "vmla%c.i%20-21S6\t%12-15,22D, %16-19,7D, %D"}, + {FPU_NEON_EXT_V1, 0xf2800140, 0xff800f50, "vmla%c.f%20-21Sa\t%12-15,22D, %16-19,7D, %D"}, + {FPU_NEON_EXT_V1, 0xf2800340, 0xff800f50, "vqdmlal%c.s%20-21S6\t%12-15,22Q, %16-19,7D, %D"}, + {FPU_NEON_EXT_V1, 0xf2800440, 0xff800f50, "vmls%c.i%20-21S6\t%12-15,22D, %16-19,7D, %D"}, + {FPU_NEON_EXT_V1, 0xf2800540, 0xff800f50, "vmls%c.f%20-21S6\t%12-15,22D, %16-19,7D, %D"}, + {FPU_NEON_EXT_V1, 0xf2800740, 0xff800f50, "vqdmlsl%c.s%20-21S6\t%12-15,22Q, %16-19,7D, %D"}, + {FPU_NEON_EXT_V1, 0xf2800840, 0xff800f50, "vmul%c.i%20-21S6\t%12-15,22D, %16-19,7D, %D"}, + {FPU_NEON_EXT_V1, 0xf2800940, 0xff800f50, "vmul%c.f%20-21Sa\t%12-15,22D, %16-19,7D, %D"}, + {FPU_NEON_EXT_V1, 0xf2800b40, 0xff800f50, "vqdmull%c.s%20-21S6\t%12-15,22Q, %16-19,7D, %D"}, + {FPU_NEON_EXT_V1, 0xf2800c40, 0xff800f50, "vqdmulh%c.s%20-21S6\t%12-15,22D, %16-19,7D, %D"}, + {FPU_NEON_EXT_V1, 0xf2800d40, 0xff800f50, "vqrdmulh%c.s%20-21S6\t%12-15,22D, %16-19,7D, %D"}, + {FPU_NEON_EXT_V1, 0xf3800040, 0xff800f50, "vmla%c.i%20-21S6\t%12-15,22Q, %16-19,7Q, %D"}, + {FPU_NEON_EXT_V1, 0xf3800140, 0xff800f50, "vmla%c.f%20-21Sa\t%12-15,22Q, %16-19,7Q, %D"}, + {FPU_NEON_EXT_V1, 0xf3800440, 0xff800f50, "vmls%c.i%20-21S6\t%12-15,22Q, %16-19,7Q, %D"}, + {FPU_NEON_EXT_V1, 0xf3800540, 0xff800f50, "vmls%c.f%20-21Sa\t%12-15,22Q, %16-19,7Q, %D"}, + {FPU_NEON_EXT_V1, 0xf3800840, 0xff800f50, "vmul%c.i%20-21S6\t%12-15,22Q, %16-19,7Q, %D"}, + {FPU_NEON_EXT_V1, 0xf3800940, 0xff800f50, "vmul%c.f%20-21Sa\t%12-15,22Q, %16-19,7Q, %D"}, + {FPU_NEON_EXT_V1, 0xf3800c40, 0xff800f50, "vqdmulh%c.s%20-21S6\t%12-15,22Q, %16-19,7Q, %D"}, + {FPU_NEON_EXT_V1, 0xf3800d40, 0xff800f50, "vqrdmulh%c.s%20-21S6\t%12-15,22Q, %16-19,7Q, %D"}, + {FPU_NEON_EXT_V1, 0xf2800240, 0xfe800f50, "vmlal%c.%24?us%20-21S6\t%12-15,22Q, %16-19,7D, %D"}, + {FPU_NEON_EXT_V1, 0xf2800640, 0xfe800f50, "vmlsl%c.%24?us%20-21S6\t%12-15,22Q, %16-19,7D, %D"}, + {FPU_NEON_EXT_V1, 0xf2800a40, 0xfe800f50, "vmull%c.%24?us%20-21S6\t%12-15,22Q, %16-19,7D, %D"}, + + /* Element and structure load/store */ + {FPU_NEON_EXT_V1, 0xf4a00fc0, 0xffb00fc0, "vld4%c.32\t%C"}, + {FPU_NEON_EXT_V1, 0xf4a00c00, 0xffb00f00, "vld1%c.%6-7S2\t%C"}, + {FPU_NEON_EXT_V1, 0xf4a00d00, 0xffb00f00, "vld2%c.%6-7S2\t%C"}, + {FPU_NEON_EXT_V1, 0xf4a00e00, 0xffb00f00, "vld3%c.%6-7S2\t%C"}, + {FPU_NEON_EXT_V1, 0xf4a00f00, 0xffb00f00, "vld4%c.%6-7S2\t%C"}, + {FPU_NEON_EXT_V1, 0xf4000200, 0xff900f00, "v%21?ls%21?dt1%c.%6-7S3\t%A"}, + {FPU_NEON_EXT_V1, 0xf4000300, 0xff900f00, "v%21?ls%21?dt2%c.%6-7S2\t%A"}, + {FPU_NEON_EXT_V1, 0xf4000400, 0xff900f00, "v%21?ls%21?dt3%c.%6-7S2\t%A"}, + {FPU_NEON_EXT_V1, 0xf4000500, 0xff900f00, "v%21?ls%21?dt3%c.%6-7S2\t%A"}, + {FPU_NEON_EXT_V1, 0xf4000600, 0xff900f00, "v%21?ls%21?dt1%c.%6-7S3\t%A"}, + {FPU_NEON_EXT_V1, 0xf4000700, 0xff900f00, "v%21?ls%21?dt1%c.%6-7S3\t%A"}, + {FPU_NEON_EXT_V1, 0xf4000800, 0xff900f00, "v%21?ls%21?dt2%c.%6-7S2\t%A"}, + {FPU_NEON_EXT_V1, 0xf4000900, 0xff900f00, "v%21?ls%21?dt2%c.%6-7S2\t%A"}, + {FPU_NEON_EXT_V1, 0xf4000a00, 0xff900f00, "v%21?ls%21?dt1%c.%6-7S3\t%A"}, + {FPU_NEON_EXT_V1, 0xf4000000, 0xff900e00, "v%21?ls%21?dt4%c.%6-7S2\t%A"}, + {FPU_NEON_EXT_V1, 0xf4800000, 0xff900300, "v%21?ls%21?dt1%c.%10-11S2\t%B"}, + {FPU_NEON_EXT_V1, 0xf4800100, 0xff900300, "v%21?ls%21?dt2%c.%10-11S2\t%B"}, + {FPU_NEON_EXT_V1, 0xf4800200, 0xff900300, "v%21?ls%21?dt3%c.%10-11S2\t%B"}, + {FPU_NEON_EXT_V1, 0xf4800300, 0xff900300, "v%21?ls%21?dt4%c.%10-11S2\t%B"}, + + {0,0 ,0, 0} +}; + +/* Opcode tables: ARM, 16-bit Thumb, 32-bit Thumb. All three are partially + ordered: they must be searched linearly from the top to obtain a correct + match. */ + +/* print_insn_arm recognizes the following format control codes: + + %% % + + %a print address for ldr/str instruction + %s print address for ldr/str halfword/signextend instruction + %b print branch destination + %c print condition code (always bits 28-31) + %m print register mask for ldm/stm instruction + %o print operand2 (immediate or register + shift) + %p print 'p' iff bits 12-15 are 15 + %t print 't' iff bit 21 set and bit 24 clear + %B print arm BLX(1) destination + %C print the PSR sub type. + %U print barrier type. + %P print address for pli instruction. + + %r print as an ARM register + %d print the bitfield in decimal + %W print the bitfield plus one in decimal + %x print the bitfield in hex + %X print the bitfield as 1 hex digit without leading "0x" + + %'c print specified char iff bitfield is all ones + %`c print specified char iff bitfield is all zeroes + %?ab... select from array of values in big endian order + + %e print arm SMI operand (bits 0..7,8..19). + %E print the LSB and WIDTH fields of a BFI or BFC instruction. + %V print the 16-bit immediate field of a MOVT or MOVW instruction. */ + +static const struct opcode32 arm_opcodes[] = +{ + /* ARM instructions. */ + {ARM_EXT_V1, 0xe1a00000, 0xffffffff, "nop\t\t\t(mov r0,r0)"}, + {ARM_EXT_V4T | ARM_EXT_V5, 0x012FFF10, 0x0ffffff0, "bx%c\t%0-3r"}, + {ARM_EXT_V2, 0x00000090, 0x0fe000f0, "mul%20's%c\t%16-19r, %0-3r, %8-11r"}, + {ARM_EXT_V2, 0x00200090, 0x0fe000f0, "mla%20's%c\t%16-19r, %0-3r, %8-11r, %12-15r"}, + {ARM_EXT_V2S, 0x01000090, 0x0fb00ff0, "swp%22'b%c\t%12-15r, %0-3r, [%16-19r]"}, + {ARM_EXT_V3M, 0x00800090, 0x0fa000f0, "%22?sumull%20's%c\t%12-15r, %16-19r, %0-3r, %8-11r"}, + {ARM_EXT_V3M, 0x00a00090, 0x0fa000f0, "%22?sumlal%20's%c\t%12-15r, %16-19r, %0-3r, %8-11r"}, + + /* V7 instructions. */ + {ARM_EXT_V7, 0xf450f000, 0xfd70f000, "pli\t%P"}, + {ARM_EXT_V7, 0x0320f0f0, 0x0ffffff0, "dbg%c\t#%0-3d"}, + {ARM_EXT_V7, 0xf57ff050, 0xfffffff0, "dmb\t%U"}, + {ARM_EXT_V7, 0xf57ff040, 0xfffffff0, "dsb\t%U"}, + {ARM_EXT_V7, 0xf57ff060, 0xfffffff0, "isb\t%U"}, + + /* ARM V6T2 instructions. */ + {ARM_EXT_V6T2, 0x07c0001f, 0x0fe0007f, "bfc%c\t%12-15r, %E"}, + {ARM_EXT_V6T2, 0x07c00010, 0x0fe00070, "bfi%c\t%12-15r, %0-3r, %E"}, + {ARM_EXT_V6T2, 0x00600090, 0x0ff000f0, "mls%c\t%16-19r, %0-3r, %8-11r, %12-15r"}, + {ARM_EXT_V6T2, 0x006000b0, 0x0f7000f0, "strht%c\t%12-15r, %s"}, + {ARM_EXT_V6T2, 0x00300090, 0x0f300090, "ldr%6's%5?hbt%c\t%12-15r, %s"}, + {ARM_EXT_V6T2, 0x03000000, 0x0ff00000, "movw%c\t%12-15r, %V"}, + {ARM_EXT_V6T2, 0x03400000, 0x0ff00000, "movt%c\t%12-15r, %V"}, + {ARM_EXT_V6T2, 0x06ff0f30, 0x0fff0ff0, "rbit%c\t%12-15r, %0-3r"}, + {ARM_EXT_V6T2, 0x07a00050, 0x0fa00070, "%22?usbfx%c\t%12-15r, %0-3r, #%7-11d, #%16-20W"}, + + /* ARM V6Z instructions. */ + {ARM_EXT_V6Z, 0x01600070, 0x0ff000f0, "smc%c\t%e"}, + + /* ARM V6K instructions. */ + {ARM_EXT_V6K, 0xf57ff01f, 0xffffffff, "clrex"}, + {ARM_EXT_V6K, 0x01d00f9f, 0x0ff00fff, "ldrexb%c\t%12-15r, [%16-19r]"}, + {ARM_EXT_V6K, 0x01b00f9f, 0x0ff00fff, "ldrexd%c\t%12-15r, [%16-19r]"}, + {ARM_EXT_V6K, 0x01f00f9f, 0x0ff00fff, "ldrexh%c\t%12-15r, [%16-19r]"}, + {ARM_EXT_V6K, 0x01c00f90, 0x0ff00ff0, "strexb%c\t%12-15r, %0-3r, [%16-19r]"}, + {ARM_EXT_V6K, 0x01a00f90, 0x0ff00ff0, "strexd%c\t%12-15r, %0-3r, [%16-19r]"}, + {ARM_EXT_V6K, 0x01e00f90, 0x0ff00ff0, "strexh%c\t%12-15r, %0-3r, [%16-19r]"}, + + /* ARM V6K NOP hints. */ + {ARM_EXT_V6K, 0x0320f001, 0x0fffffff, "yield%c"}, + {ARM_EXT_V6K, 0x0320f002, 0x0fffffff, "wfe%c"}, + {ARM_EXT_V6K, 0x0320f003, 0x0fffffff, "wfi%c"}, + {ARM_EXT_V6K, 0x0320f004, 0x0fffffff, "sev%c"}, + {ARM_EXT_V6K, 0x0320f000, 0x0fffff00, "nop%c\t{%0-7d}"}, + + /* ARM V6 instructions. */ + {ARM_EXT_V6, 0xf1080000, 0xfffffe3f, "cpsie\t%8'a%7'i%6'f"}, + {ARM_EXT_V6, 0xf10a0000, 0xfffffe20, "cpsie\t%8'a%7'i%6'f,#%0-4d"}, + {ARM_EXT_V6, 0xf10C0000, 0xfffffe3f, "cpsid\t%8'a%7'i%6'f"}, + {ARM_EXT_V6, 0xf10e0000, 0xfffffe20, "cpsid\t%8'a%7'i%6'f,#%0-4d"}, + {ARM_EXT_V6, 0xf1000000, 0xfff1fe20, "cps\t#%0-4d"}, + {ARM_EXT_V6, 0x06800010, 0x0ff00ff0, "pkhbt%c\t%12-15r, %16-19r, %0-3r"}, + {ARM_EXT_V6, 0x06800010, 0x0ff00070, "pkhbt%c\t%12-15r, %16-19r, %0-3r, lsl #%7-11d"}, + {ARM_EXT_V6, 0x06800050, 0x0ff00ff0, "pkhtb%c\t%12-15r, %16-19r, %0-3r, asr #32"}, + {ARM_EXT_V6, 0x06800050, 0x0ff00070, "pkhtb%c\t%12-15r, %16-19r, %0-3r, asr #%7-11d"}, + {ARM_EXT_V6, 0x01900f9f, 0x0ff00fff, "ldrex%c\tr%12-15d, [%16-19r]"}, + {ARM_EXT_V6, 0x06200f10, 0x0ff00ff0, "qadd16%c\t%12-15r, %16-19r, %0-3r"}, + {ARM_EXT_V6, 0x06200f90, 0x0ff00ff0, "qadd8%c\t%12-15r, %16-19r, %0-3r"}, + {ARM_EXT_V6, 0x06200f30, 0x0ff00ff0, "qaddsubx%c\t%12-15r, %16-19r, %0-3r"}, + {ARM_EXT_V6, 0x06200f70, 0x0ff00ff0, "qsub16%c\t%12-15r, %16-19r, %0-3r"}, + {ARM_EXT_V6, 0x06200ff0, 0x0ff00ff0, "qsub8%c\t%12-15r, %16-19r, %0-3r"}, + {ARM_EXT_V6, 0x06200f50, 0x0ff00ff0, "qsubaddx%c\t%12-15r, %16-19r, %0-3r"}, + {ARM_EXT_V6, 0x06100f10, 0x0ff00ff0, "sadd16%c\t%12-15r, %16-19r, %0-3r"}, + {ARM_EXT_V6, 0x06100f90, 0x0ff00ff0, "sadd8%c\t%12-15r, %16-19r, %0-3r"}, + {ARM_EXT_V6, 0x06100f30, 0x0ff00ff0, "saddaddx%c\t%12-15r, %16-19r, %0-3r"}, + {ARM_EXT_V6, 0x06300f10, 0x0ff00ff0, "shadd16%c\t%12-15r, %16-19r, %0-3r"}, + {ARM_EXT_V6, 0x06300f90, 0x0ff00ff0, "shadd8%c\t%12-15r, %16-19r, %0-3r"}, + {ARM_EXT_V6, 0x06300f30, 0x0ff00ff0, "shaddsubx%c\t%12-15r, %16-19r, %0-3r"}, + {ARM_EXT_V6, 0x06300f70, 0x0ff00ff0, "shsub16%c\t%12-15r, %16-19r, %0-3r"}, + {ARM_EXT_V6, 0x06300ff0, 0x0ff00ff0, "shsub8%c\t%12-15r, %16-19r, %0-3r"}, + {ARM_EXT_V6, 0x06300f50, 0x0ff00ff0, "shsubaddx%c\t%12-15r, %16-19r, %0-3r"}, + {ARM_EXT_V6, 0x06100f70, 0x0ff00ff0, "ssub16%c\t%12-15r, %16-19r, %0-3r"}, + {ARM_EXT_V6, 0x06100ff0, 0x0ff00ff0, "ssub8%c\t%12-15r, %16-19r, %0-3r"}, + {ARM_EXT_V6, 0x06100f50, 0x0ff00ff0, "ssubaddx%c\t%12-15r, %16-19r, %0-3r"}, + {ARM_EXT_V6, 0x06500f10, 0x0ff00ff0, "uadd16%c\t%12-15r, %16-19r, %0-3r"}, + {ARM_EXT_V6, 0x06500f90, 0x0ff00ff0, "uadd8%c\t%12-15r, %16-19r, %0-3r"}, + {ARM_EXT_V6, 0x06500f30, 0x0ff00ff0, "uaddsubx%c\t%12-15r, %16-19r, %0-3r"}, + {ARM_EXT_V6, 0x06700f10, 0x0ff00ff0, "uhadd16%c\t%12-15r, %16-19r, %0-3r"}, + {ARM_EXT_V6, 0x06700f90, 0x0ff00ff0, "uhadd8%c\t%12-15r, %16-19r, %0-3r"}, + {ARM_EXT_V6, 0x06700f30, 0x0ff00ff0, "uhaddsubx%c\t%12-15r, %16-19r, %0-3r"}, + {ARM_EXT_V6, 0x06700f70, 0x0ff00ff0, "uhsub16%c\t%12-15r, %16-19r, %0-3r"}, + {ARM_EXT_V6, 0x06700ff0, 0x0ff00ff0, "uhsub8%c\t%12-15r, %16-19r, %0-3r"}, + {ARM_EXT_V6, 0x06700f50, 0x0ff00ff0, "uhsubaddx%c\t%12-15r, %16-19r, %0-3r"}, + {ARM_EXT_V6, 0x06600f10, 0x0ff00ff0, "uqadd16%c\t%12-15r, %16-19r, %0-3r"}, + {ARM_EXT_V6, 0x06600f90, 0x0ff00ff0, "uqadd8%c\t%12-15r, %16-19r, %0-3r"}, + {ARM_EXT_V6, 0x06600f30, 0x0ff00ff0, "uqaddsubx%c\t%12-15r, %16-19r, %0-3r"}, + {ARM_EXT_V6, 0x06600f70, 0x0ff00ff0, "uqsub16%c\t%12-15r, %16-19r, %0-3r"}, + {ARM_EXT_V6, 0x06600ff0, 0x0ff00ff0, "uqsub8%c\t%12-15r, %16-19r, %0-3r"}, + {ARM_EXT_V6, 0x06600f50, 0x0ff00ff0, "uqsubaddx%c\t%12-15r, %16-19r, %0-3r"}, + {ARM_EXT_V6, 0x06500f70, 0x0ff00ff0, "usub16%c\t%12-15r, %16-19r, %0-3r"}, + {ARM_EXT_V6, 0x06500ff0, 0x0ff00ff0, "usub8%c\t%12-15r, %16-19r, %0-3r"}, + {ARM_EXT_V6, 0x06500f50, 0x0ff00ff0, "usubaddx%c\t%12-15r, %16-19r, %0-3r"}, + {ARM_EXT_V6, 0x06bf0f30, 0x0fff0ff0, "rev%c\t\%12-15r, %0-3r"}, + {ARM_EXT_V6, 0x06bf0fb0, 0x0fff0ff0, "rev16%c\t\%12-15r, %0-3r"}, + {ARM_EXT_V6, 0x06ff0fb0, 0x0fff0ff0, "revsh%c\t\%12-15r, %0-3r"}, + {ARM_EXT_V6, 0xf8100a00, 0xfe50ffff, "rfe%23?id%24?ba\t\%16-19r%21'!"}, + {ARM_EXT_V6, 0x06bf0070, 0x0fff0ff0, "sxth%c\t%12-15r, %0-3r"}, + {ARM_EXT_V6, 0x06bf0470, 0x0fff0ff0, "sxth%c\t%12-15r, %0-3r, ror #8"}, + {ARM_EXT_V6, 0x06bf0870, 0x0fff0ff0, "sxth%c\t%12-15r, %0-3r, ror #16"}, + {ARM_EXT_V6, 0x06bf0c70, 0x0fff0ff0, "sxth%c\t%12-15r, %0-3r, ror #24"}, + {ARM_EXT_V6, 0x068f0070, 0x0fff0ff0, "sxtb16%c\t%12-15r, %0-3r"}, + {ARM_EXT_V6, 0x068f0470, 0x0fff0ff0, "sxtb16%c\t%12-15r, %0-3r, ror #8"}, + {ARM_EXT_V6, 0x068f0870, 0x0fff0ff0, "sxtb16%c\t%12-15r, %0-3r, ror #16"}, + {ARM_EXT_V6, 0x068f0c70, 0x0fff0ff0, "sxtb16%c\t%12-15r, %0-3r, ror #24"}, + {ARM_EXT_V6, 0x06af0070, 0x0fff0ff0, "sxtb%c\t%12-15r, %0-3r"}, + {ARM_EXT_V6, 0x06af0470, 0x0fff0ff0, "sxtb%c\t%12-15r, %0-3r, ror #8"}, + {ARM_EXT_V6, 0x06af0870, 0x0fff0ff0, "sxtb%c\t%12-15r, %0-3r, ror #16"}, + {ARM_EXT_V6, 0x06af0c70, 0x0fff0ff0, "sxtb%c\t%12-15r, %0-3r, ror #24"}, + {ARM_EXT_V6, 0x06ff0070, 0x0fff0ff0, "uxth%c\t%12-15r, %0-3r"}, + {ARM_EXT_V6, 0x06ff0470, 0x0fff0ff0, "uxth%c\t%12-15r, %0-3r, ror #8"}, + {ARM_EXT_V6, 0x06ff0870, 0x0fff0ff0, "uxth%c\t%12-15r, %0-3r, ror #16"}, + {ARM_EXT_V6, 0x06ff0c70, 0x0fff0ff0, "uxth%c\t%12-15r, %0-3r, ror #24"}, + {ARM_EXT_V6, 0x06cf0070, 0x0fff0ff0, "uxtb16%c\t%12-15r, %0-3r"}, + {ARM_EXT_V6, 0x06cf0470, 0x0fff0ff0, "uxtb16%c\t%12-15r, %0-3r, ror #8"}, + {ARM_EXT_V6, 0x06cf0870, 0x0fff0ff0, "uxtb16%c\t%12-15r, %0-3r, ror #16"}, + {ARM_EXT_V6, 0x06cf0c70, 0x0fff0ff0, "uxtb16%c\t%12-15r, %0-3r, ror #24"}, + {ARM_EXT_V6, 0x06ef0070, 0x0fff0ff0, "uxtb%c\t%12-15r, %0-3r"}, + {ARM_EXT_V6, 0x06ef0470, 0x0fff0ff0, "uxtb%c\t%12-15r, %0-3r, ror #8"}, + {ARM_EXT_V6, 0x06ef0870, 0x0fff0ff0, "uxtb%c\t%12-15r, %0-3r, ror #16"}, + {ARM_EXT_V6, 0x06ef0c70, 0x0fff0ff0, "uxtb%c\t%12-15r, %0-3r, ror #24"}, + {ARM_EXT_V6, 0x06b00070, 0x0ff00ff0, "sxtah%c\t%12-15r, %16-19r, %0-3r"}, + {ARM_EXT_V6, 0x06b00470, 0x0ff00ff0, "sxtah%c\t%12-15r, %16-19r, %0-3r, ror #8"}, + {ARM_EXT_V6, 0x06b00870, 0x0ff00ff0, "sxtah%c\t%12-15r, %16-19r, %0-3r, ror #16"}, + {ARM_EXT_V6, 0x06b00c70, 0x0ff00ff0, "sxtah%c\t%12-15r, %16-19r, %0-3r, ror #24"}, + {ARM_EXT_V6, 0x06800070, 0x0ff00ff0, "sxtab16%c\t%12-15r, %16-19r, %0-3r"}, + {ARM_EXT_V6, 0x06800470, 0x0ff00ff0, "sxtab16%c\t%12-15r, %16-19r, %0-3r, ror #8"}, + {ARM_EXT_V6, 0x06800870, 0x0ff00ff0, "sxtab16%c\t%12-15r, %16-19r, %0-3r, ror #16"}, + {ARM_EXT_V6, 0x06800c70, 0x0ff00ff0, "sxtab16%c\t%12-15r, %16-19r, %0-3r, ror #24"}, + {ARM_EXT_V6, 0x06a00070, 0x0ff00ff0, "sxtab%c\t%12-15r, %16-19r, %0-3r"}, + {ARM_EXT_V6, 0x06a00470, 0x0ff00ff0, "sxtab%c\t%12-15r, %16-19r, %0-3r, ror #8"}, + {ARM_EXT_V6, 0x06a00870, 0x0ff00ff0, "sxtab%c\t%12-15r, %16-19r, %0-3r, ror #16"}, + {ARM_EXT_V6, 0x06a00c70, 0x0ff00ff0, "sxtab%c\t%12-15r, %16-19r, %0-3r, ror #24"}, + {ARM_EXT_V6, 0x06f00070, 0x0ff00ff0, "uxtah%c\t%12-15r, %16-19r, %0-3r"}, + {ARM_EXT_V6, 0x06f00470, 0x0ff00ff0, "uxtah%c\t%12-15r, %16-19r, %0-3r, ror #8"}, + {ARM_EXT_V6, 0x06f00870, 0x0ff00ff0, "uxtah%c\t%12-15r, %16-19r, %0-3r, ror #16"}, + {ARM_EXT_V6, 0x06f00c70, 0x0ff00ff0, "uxtah%c\t%12-15r, %16-19r, %0-3r, ror #24"}, + {ARM_EXT_V6, 0x06c00070, 0x0ff00ff0, "uxtab16%c\t%12-15r, %16-19r, %0-3r"}, + {ARM_EXT_V6, 0x06c00470, 0x0ff00ff0, "uxtab16%c\t%12-15r, %16-19r, %0-3r, ror #8"}, + {ARM_EXT_V6, 0x06c00870, 0x0ff00ff0, "uxtab16%c\t%12-15r, %16-19r, %0-3r, ror #16"}, + {ARM_EXT_V6, 0x06c00c70, 0x0ff00ff0, "uxtab16%c\t%12-15r, %16-19r, %0-3r, ROR #24"}, + {ARM_EXT_V6, 0x06e00070, 0x0ff00ff0, "uxtab%c\t%12-15r, %16-19r, %0-3r"}, + {ARM_EXT_V6, 0x06e00470, 0x0ff00ff0, "uxtab%c\t%12-15r, %16-19r, %0-3r, ror #8"}, + {ARM_EXT_V6, 0x06e00870, 0x0ff00ff0, "uxtab%c\t%12-15r, %16-19r, %0-3r, ror #16"}, + {ARM_EXT_V6, 0x06e00c70, 0x0ff00ff0, "uxtab%c\t%12-15r, %16-19r, %0-3r, ror #24"}, + {ARM_EXT_V6, 0x06800fb0, 0x0ff00ff0, "sel%c\t%12-15r, %16-19r, %0-3r"}, + {ARM_EXT_V6, 0xf1010000, 0xfffffc00, "setend\t%9?ble"}, + {ARM_EXT_V6, 0x0700f010, 0x0ff0f0d0, "smuad%5'x%c\t%16-19r, %0-3r, %8-11r"}, + {ARM_EXT_V6, 0x0700f050, 0x0ff0f0d0, "smusd%5'x%c\t%16-19r, %0-3r, %8-11r"}, + {ARM_EXT_V6, 0x07000010, 0x0ff000d0, "smlad%5'x%c\t%16-19r, %0-3r, %8-11r, %12-15r"}, + {ARM_EXT_V6, 0x07400010, 0x0ff000d0, "smlald%5'x%c\t%12-15r, %16-19r, %0-3r, %8-11r"}, + {ARM_EXT_V6, 0x07000050, 0x0ff000d0, "smlsd%5'x%c\t%16-19r, %0-3r, %8-11r, %12-15r"}, + {ARM_EXT_V6, 0x07400050, 0x0ff000d0, "smlsld%5'x%c\t%12-15r, %16-19r, %0-3r, %8-11r"}, + {ARM_EXT_V6, 0x0750f010, 0x0ff0f0d0, "smmul%5'r%c\t%16-19r, %0-3r, %8-11r"}, + {ARM_EXT_V6, 0x07500010, 0x0ff000d0, "smmla%5'r%c\t%16-19r, %0-3r, %8-11r, %12-15r"}, + {ARM_EXT_V6, 0x075000d0, 0x0ff000d0, "smmls%5'r%c\t%16-19r, %0-3r, %8-11r, %12-15r"}, + {ARM_EXT_V6, 0xf84d0500, 0xfe5fffe0, "srs%23?id%24?ba\t%16-19r%21'!, #%0-4d"}, + {ARM_EXT_V6, 0x06a00010, 0x0fe00ff0, "ssat%c\t%12-15r, #%16-20W, %0-3r"}, + {ARM_EXT_V6, 0x06a00010, 0x0fe00070, "ssat%c\t%12-15r, #%16-20W, %0-3r, lsl #%7-11d"}, + {ARM_EXT_V6, 0x06a00050, 0x0fe00070, "ssat%c\t%12-15r, #%16-20W, %0-3r, asr #%7-11d"}, + {ARM_EXT_V6, 0x06a00f30, 0x0ff00ff0, "ssat16%c\t%12-15r, #%16-19W, %0-3r"}, + {ARM_EXT_V6, 0x01800f90, 0x0ff00ff0, "strex%c\t%12-15r, %0-3r, [%16-19r]"}, + {ARM_EXT_V6, 0x00400090, 0x0ff000f0, "umaal%c\t%12-15r, %16-19r, %0-3r, %8-11r"}, + {ARM_EXT_V6, 0x0780f010, 0x0ff0f0f0, "usad8%c\t%16-19r, %0-3r, %8-11r"}, + {ARM_EXT_V6, 0x07800010, 0x0ff000f0, "usada8%c\t%16-19r, %0-3r, %8-11r, %12-15r"}, + {ARM_EXT_V6, 0x06e00010, 0x0fe00ff0, "usat%c\t%12-15r, #%16-20d, %0-3r"}, + {ARM_EXT_V6, 0x06e00010, 0x0fe00070, "usat%c\t%12-15r, #%16-20d, %0-3r, lsl #%7-11d"}, + {ARM_EXT_V6, 0x06e00050, 0x0fe00070, "usat%c\t%12-15r, #%16-20d, %0-3r, asr #%7-11d"}, + {ARM_EXT_V6, 0x06e00f30, 0x0ff00ff0, "usat16%c\t%12-15r, #%16-19d, %0-3r"}, + + /* V5J instruction. */ + {ARM_EXT_V5J, 0x012fff20, 0x0ffffff0, "bxj%c\t%0-3r"}, + + /* V5 Instructions. */ + {ARM_EXT_V5, 0xe1200070, 0xfff000f0, "bkpt\t0x%16-19X%12-15X%8-11X%0-3X"}, + {ARM_EXT_V5, 0xfa000000, 0xfe000000, "blx\t%B"}, + {ARM_EXT_V5, 0x012fff30, 0x0ffffff0, "blx%c\t%0-3r"}, + {ARM_EXT_V5, 0x016f0f10, 0x0fff0ff0, "clz%c\t%12-15r, %0-3r"}, + + /* V5E "El Segundo" Instructions. */ + {ARM_EXT_V5E, 0x000000d0, 0x0e1000f0, "ldrd%c\t%12-15r, %s"}, + {ARM_EXT_V5E, 0x000000f0, 0x0e1000f0, "strd%c\t%12-15r, %s"}, + {ARM_EXT_V5E, 0xf450f000, 0xfc70f000, "pld\t%a"}, + {ARM_EXT_V5ExP, 0x01000080, 0x0ff000f0, "smlabb%c\t%16-19r, %0-3r, %8-11r, %12-15r"}, + {ARM_EXT_V5ExP, 0x010000a0, 0x0ff000f0, "smlatb%c\t%16-19r, %0-3r, %8-11r, %12-15r"}, + {ARM_EXT_V5ExP, 0x010000c0, 0x0ff000f0, "smlabt%c\t%16-19r, %0-3r, %8-11r, %12-15r"}, + {ARM_EXT_V5ExP, 0x010000e0, 0x0ff000f0, "smlatt%c\t%16-19r, %0-3r, %8-11r, %12-15r"}, + + {ARM_EXT_V5ExP, 0x01200080, 0x0ff000f0, "smlawb%c\t%16-19r, %0-3r, %8-11r, %12-15r"}, + {ARM_EXT_V5ExP, 0x012000c0, 0x0ff000f0, "smlawt%c\t%16-19r, %0-3r, %8-11r, %12-15r"}, + + {ARM_EXT_V5ExP, 0x01400080, 0x0ff000f0, "smlalbb%c\t%12-15r, %16-19r, %0-3r, %8-11r"}, + {ARM_EXT_V5ExP, 0x014000a0, 0x0ff000f0, "smlaltb%c\t%12-15r, %16-19r, %0-3r, %8-11r"}, + {ARM_EXT_V5ExP, 0x014000c0, 0x0ff000f0, "smlalbt%c\t%12-15r, %16-19r, %0-3r, %8-11r"}, + {ARM_EXT_V5ExP, 0x014000e0, 0x0ff000f0, "smlaltt%c\t%12-15r, %16-19r, %0-3r, %8-11r"}, + + {ARM_EXT_V5ExP, 0x01600080, 0x0ff0f0f0, "smulbb%c\t%16-19r, %0-3r, %8-11r"}, + {ARM_EXT_V5ExP, 0x016000a0, 0x0ff0f0f0, "smultb%c\t%16-19r, %0-3r, %8-11r"}, + {ARM_EXT_V5ExP, 0x016000c0, 0x0ff0f0f0, "smulbt%c\t%16-19r, %0-3r, %8-11r"}, + {ARM_EXT_V5ExP, 0x016000e0, 0x0ff0f0f0, "smultt%c\t%16-19r, %0-3r, %8-11r"}, + + {ARM_EXT_V5ExP, 0x012000a0, 0x0ff0f0f0, "smulwb%c\t%16-19r, %0-3r, %8-11r"}, + {ARM_EXT_V5ExP, 0x012000e0, 0x0ff0f0f0, "smulwt%c\t%16-19r, %0-3r, %8-11r"}, + + {ARM_EXT_V5ExP, 0x01000050, 0x0ff00ff0, "qadd%c\t%12-15r, %0-3r, %16-19r"}, + {ARM_EXT_V5ExP, 0x01400050, 0x0ff00ff0, "qdadd%c\t%12-15r, %0-3r, %16-19r"}, + {ARM_EXT_V5ExP, 0x01200050, 0x0ff00ff0, "qsub%c\t%12-15r, %0-3r, %16-19r"}, + {ARM_EXT_V5ExP, 0x01600050, 0x0ff00ff0, "qdsub%c\t%12-15r, %0-3r, %16-19r"}, + + /* ARM Instructions. */ + {ARM_EXT_V1, 0x00000090, 0x0e100090, "str%6's%5?hb%c\t%12-15r, %s"}, + {ARM_EXT_V1, 0x00100090, 0x0e100090, "ldr%6's%5?hb%c\t%12-15r, %s"}, + {ARM_EXT_V1, 0x00000000, 0x0de00000, "and%20's%c\t%12-15r, %16-19r, %o"}, + {ARM_EXT_V1, 0x00200000, 0x0de00000, "eor%20's%c\t%12-15r, %16-19r, %o"}, + {ARM_EXT_V1, 0x00400000, 0x0de00000, "sub%20's%c\t%12-15r, %16-19r, %o"}, + {ARM_EXT_V1, 0x00600000, 0x0de00000, "rsb%20's%c\t%12-15r, %16-19r, %o"}, + {ARM_EXT_V1, 0x00800000, 0x0de00000, "add%20's%c\t%12-15r, %16-19r, %o"}, + {ARM_EXT_V1, 0x00a00000, 0x0de00000, "adc%20's%c\t%12-15r, %16-19r, %o"}, + {ARM_EXT_V1, 0x00c00000, 0x0de00000, "sbc%20's%c\t%12-15r, %16-19r, %o"}, + {ARM_EXT_V1, 0x00e00000, 0x0de00000, "rsc%20's%c\t%12-15r, %16-19r, %o"}, + {ARM_EXT_V3, 0x0120f000, 0x0db0f000, "msr%c\t%22?SCPSR%C, %o"}, + {ARM_EXT_V3, 0x010f0000, 0x0fbf0fff, "mrs%c\t%12-15r, %22?SCPSR"}, + {ARM_EXT_V1, 0x01000000, 0x0de00000, "tst%p%c\t%16-19r, %o"}, + {ARM_EXT_V1, 0x01200000, 0x0de00000, "teq%p%c\t%16-19r, %o"}, + {ARM_EXT_V1, 0x01400000, 0x0de00000, "cmp%p%c\t%16-19r, %o"}, + {ARM_EXT_V1, 0x01600000, 0x0de00000, "cmn%p%c\t%16-19r, %o"}, + {ARM_EXT_V1, 0x01800000, 0x0de00000, "orr%20's%c\t%12-15r, %16-19r, %o"}, + {ARM_EXT_V1, 0x03a00000, 0x0fef0000, "mov%20's%c\t%12-15r, %o"}, + {ARM_EXT_V1, 0x01a00000, 0x0def0ff0, "mov%20's%c\t%12-15r, %0-3r"}, + {ARM_EXT_V1, 0x01a00000, 0x0def0060, "lsl%20's%c\t%12-15r, %q"}, + {ARM_EXT_V1, 0x01a00020, 0x0def0060, "lsr%20's%c\t%12-15r, %q"}, + {ARM_EXT_V1, 0x01a00040, 0x0def0060, "asr%20's%c\t%12-15r, %q"}, + {ARM_EXT_V1, 0x01a00060, 0x0def0ff0, "rrx%20's%c\t%12-15r, %0-3r"}, + {ARM_EXT_V1, 0x01a00060, 0x0def0060, "ror%20's%c\t%12-15r, %q"}, + {ARM_EXT_V1, 0x01c00000, 0x0de00000, "bic%20's%c\t%12-15r, %16-19r, %o"}, + {ARM_EXT_V1, 0x01e00000, 0x0de00000, "mvn%20's%c\t%12-15r, %o"}, + {ARM_EXT_V1, 0x052d0004, 0x0fff0fff, "push%c\t{%12-15r}\t\t; (str%c %12-15r, %a)"}, + {ARM_EXT_V1, 0x04000000, 0x0e100000, "str%22'b%t%c\t%12-15r, %a"}, + {ARM_EXT_V1, 0x06000000, 0x0e100ff0, "str%22'b%t%c\t%12-15r, %a"}, + {ARM_EXT_V1, 0x04000000, 0x0c100010, "str%22'b%t%c\t%12-15r, %a"}, + {ARM_EXT_V1, 0x06000010, 0x0e000010, "undefined"}, + {ARM_EXT_V1, 0x049d0004, 0x0fff0fff, "pop%c\t{%12-15r}\t\t; (ldr%c %12-15r, %a)"}, + {ARM_EXT_V1, 0x04100000, 0x0c100000, "ldr%22'b%t%c\t%12-15r, %a"}, + {ARM_EXT_V1, 0x092d0000, 0x0fff0000, "push%c\t%m"}, + {ARM_EXT_V1, 0x08800000, 0x0ff00000, "stm%c\t%16-19r%21'!, %m%22'^"}, + {ARM_EXT_V1, 0x08000000, 0x0e100000, "stm%23?id%24?ba%c\t%16-19r%21'!, %m%22'^"}, + {ARM_EXT_V1, 0x08bd0000, 0x0fff0000, "pop%c\t%m"}, + {ARM_EXT_V1, 0x08900000, 0x0f900000, "ldm%c\t%16-19r%21'!, %m%22'^"}, + {ARM_EXT_V1, 0x08100000, 0x0e100000, "ldm%23?id%24?ba%c\t%16-19r%21'!, %m%22'^"}, + {ARM_EXT_V1, 0x0a000000, 0x0e000000, "b%24'l%c\t%b"}, + {ARM_EXT_V1, 0x0f000000, 0x0f000000, "svc%c\t%0-23x"}, + + /* The rest. */ + {ARM_EXT_V1, 0x00000000, 0x00000000, "undefined instruction %0-31x"}, + {0, 0x00000000, 0x00000000, 0} +}; + +/* print_insn_thumb16 recognizes the following format control codes: + + %S print Thumb register (bits 3..5 as high number if bit 6 set) + %D print Thumb register (bits 0..2 as high number if bit 7 set) + %I print bitfield as a signed decimal + (top bit of range being the sign bit) + %N print Thumb register mask (with LR) + %O print Thumb register mask (with PC) + %M print Thumb register mask + %b print CZB's 6-bit unsigned branch destination + %s print Thumb right-shift immediate (6..10; 0 == 32). + %c print the condition code + %C print the condition code, or "s" if not conditional + %x print warning if conditional an not at end of IT block" + %X print "\t; unpredictable " if conditional + %I print IT instruction suffix and operands + %r print bitfield as an ARM register + %d print bitfield as a decimal + %H print (bitfield * 2) as a decimal + %W print (bitfield * 4) as a decimal + %a print (bitfield * 4) as a pc-rel offset + decoded symbol + %B print Thumb branch destination (signed displacement) + %c print bitfield as a condition code + %'c print specified char iff bit is one + %?ab print a if bit is one else print b. */ + +static const struct opcode16 thumb_opcodes[] = +{ + /* Thumb instructions. */ + + /* ARM V6K no-argument instructions. */ + {ARM_EXT_V6K, 0xbf00, 0xffff, "nop%c"}, + {ARM_EXT_V6K, 0xbf10, 0xffff, "yield%c"}, + {ARM_EXT_V6K, 0xbf20, 0xffff, "wfe%c"}, + {ARM_EXT_V6K, 0xbf30, 0xffff, "wfi%c"}, + {ARM_EXT_V6K, 0xbf40, 0xffff, "sev%c"}, + {ARM_EXT_V6K, 0xbf00, 0xff0f, "nop%c\t{%4-7d}"}, + + /* ARM V6T2 instructions. */ + {ARM_EXT_V6T2, 0xb900, 0xfd00, "cbnz\t%0-2r, %b%X"}, + {ARM_EXT_V6T2, 0xb100, 0xfd00, "cbz\t%0-2r, %b%X"}, + {ARM_EXT_V6T2, 0xbf00, 0xff00, "it%I%X"}, + + /* ARM V6. */ + {ARM_EXT_V6, 0xb660, 0xfff8, "cpsie\t%2'a%1'i%0'f%X"}, + {ARM_EXT_V6, 0xb670, 0xfff8, "cpsid\t%2'a%1'i%0'f%X"}, + {ARM_EXT_V6, 0x4600, 0xffc0, "mov%c\t%0-2r, %3-5r"}, + {ARM_EXT_V6, 0xba00, 0xffc0, "rev%c\t%0-2r, %3-5r"}, + {ARM_EXT_V6, 0xba40, 0xffc0, "rev16%c\t%0-2r, %3-5r"}, + {ARM_EXT_V6, 0xbac0, 0xffc0, "revsh%c\t%0-2r, %3-5r"}, + {ARM_EXT_V6, 0xb650, 0xfff7, "setend\t%3?ble%X"}, + {ARM_EXT_V6, 0xb200, 0xffc0, "sxth%c\t%0-2r, %3-5r"}, + {ARM_EXT_V6, 0xb240, 0xffc0, "sxtb%c\t%0-2r, %3-5r"}, + {ARM_EXT_V6, 0xb280, 0xffc0, "uxth%c\t%0-2r, %3-5r"}, + {ARM_EXT_V6, 0xb2c0, 0xffc0, "uxtb%c\t%0-2r, %3-5r"}, + + /* ARM V5 ISA extends Thumb. */ + {ARM_EXT_V5T, 0xbe00, 0xff00, "bkpt\t%0-7x"}, /* Is always unconditional. */ + /* This is BLX(2). BLX(1) is a 32-bit instruction. */ + {ARM_EXT_V5T, 0x4780, 0xff87, "blx%c\t%3-6r%x"}, /* note: 4 bit register number. */ + /* ARM V4T ISA (Thumb v1). */ + {ARM_EXT_V4T, 0x46C0, 0xFFFF, "nop%c\t\t\t(mov r8, r8)"}, + /* Format 4. */ + {ARM_EXT_V4T, 0x4000, 0xFFC0, "and%C\t%0-2r, %3-5r"}, + {ARM_EXT_V4T, 0x4040, 0xFFC0, "eor%C\t%0-2r, %3-5r"}, + {ARM_EXT_V4T, 0x4080, 0xFFC0, "lsl%C\t%0-2r, %3-5r"}, + {ARM_EXT_V4T, 0x40C0, 0xFFC0, "lsr%C\t%0-2r, %3-5r"}, + {ARM_EXT_V4T, 0x4100, 0xFFC0, "asr%C\t%0-2r, %3-5r"}, + {ARM_EXT_V4T, 0x4140, 0xFFC0, "adc%C\t%0-2r, %3-5r"}, + {ARM_EXT_V4T, 0x4180, 0xFFC0, "sbc%C\t%0-2r, %3-5r"}, + {ARM_EXT_V4T, 0x41C0, 0xFFC0, "ror%C\t%0-2r, %3-5r"}, + {ARM_EXT_V4T, 0x4200, 0xFFC0, "tst%c\t%0-2r, %3-5r"}, + {ARM_EXT_V4T, 0x4240, 0xFFC0, "neg%C\t%0-2r, %3-5r"}, + {ARM_EXT_V4T, 0x4280, 0xFFC0, "cmp%c\t%0-2r, %3-5r"}, + {ARM_EXT_V4T, 0x42C0, 0xFFC0, "cmn%c\t%0-2r, %3-5r"}, + {ARM_EXT_V4T, 0x4300, 0xFFC0, "orr%C\t%0-2r, %3-5r"}, + {ARM_EXT_V4T, 0x4340, 0xFFC0, "mul%C\t%0-2r, %3-5r"}, + {ARM_EXT_V4T, 0x4380, 0xFFC0, "bic%C\t%0-2r, %3-5r"}, + {ARM_EXT_V4T, 0x43C0, 0xFFC0, "mvn%C\t%0-2r, %3-5r"}, + /* format 13 */ + {ARM_EXT_V4T, 0xB000, 0xFF80, "add%c\tsp, #%0-6W"}, + {ARM_EXT_V4T, 0xB080, 0xFF80, "sub%c\tsp, #%0-6W"}, + /* format 5 */ + {ARM_EXT_V4T, 0x4700, 0xFF80, "bx%c\t%S%x"}, + {ARM_EXT_V4T, 0x4400, 0xFF00, "add%c\t%D, %S"}, + {ARM_EXT_V4T, 0x4500, 0xFF00, "cmp%c\t%D, %S"}, + {ARM_EXT_V4T, 0x4600, 0xFF00, "mov%c\t%D, %S"}, + /* format 14 */ + {ARM_EXT_V4T, 0xB400, 0xFE00, "push%c\t%N"}, + {ARM_EXT_V4T, 0xBC00, 0xFE00, "pop%c\t%O"}, + /* format 2 */ + {ARM_EXT_V4T, 0x1800, 0xFE00, "add%C\t%0-2r, %3-5r, %6-8r"}, + {ARM_EXT_V4T, 0x1A00, 0xFE00, "sub%C\t%0-2r, %3-5r, %6-8r"}, + {ARM_EXT_V4T, 0x1C00, 0xFE00, "add%C\t%0-2r, %3-5r, #%6-8d"}, + {ARM_EXT_V4T, 0x1E00, 0xFE00, "sub%C\t%0-2r, %3-5r, #%6-8d"}, + /* format 8 */ + {ARM_EXT_V4T, 0x5200, 0xFE00, "strh%c\t%0-2r, [%3-5r, %6-8r]"}, + {ARM_EXT_V4T, 0x5A00, 0xFE00, "ldrh%c\t%0-2r, [%3-5r, %6-8r]"}, + {ARM_EXT_V4T, 0x5600, 0xF600, "ldrs%11?hb%c\t%0-2r, [%3-5r, %6-8r]"}, + /* format 7 */ + {ARM_EXT_V4T, 0x5000, 0xFA00, "str%10'b%c\t%0-2r, [%3-5r, %6-8r]"}, + {ARM_EXT_V4T, 0x5800, 0xFA00, "ldr%10'b%c\t%0-2r, [%3-5r, %6-8r]"}, + /* format 1 */ + {ARM_EXT_V4T, 0x0000, 0xF800, "lsl%C\t%0-2r, %3-5r, #%6-10d"}, + {ARM_EXT_V4T, 0x0800, 0xF800, "lsr%C\t%0-2r, %3-5r, %s"}, + {ARM_EXT_V4T, 0x1000, 0xF800, "asr%C\t%0-2r, %3-5r, %s"}, + /* format 3 */ + {ARM_EXT_V4T, 0x2000, 0xF800, "mov%C\t%8-10r, #%0-7d"}, + {ARM_EXT_V4T, 0x2800, 0xF800, "cmp%c\t%8-10r, #%0-7d"}, + {ARM_EXT_V4T, 0x3000, 0xF800, "add%C\t%8-10r, #%0-7d"}, + {ARM_EXT_V4T, 0x3800, 0xF800, "sub%C\t%8-10r, #%0-7d"}, + /* format 6 */ + {ARM_EXT_V4T, 0x4800, 0xF800, "ldr%c\t%8-10r, [pc, #%0-7W]\t(%0-7a)"}, /* TODO: Disassemble PC relative "LDR rD,=" */ + /* format 9 */ + {ARM_EXT_V4T, 0x6000, 0xF800, "str%c\t%0-2r, [%3-5r, #%6-10W]"}, + {ARM_EXT_V4T, 0x6800, 0xF800, "ldr%c\t%0-2r, [%3-5r, #%6-10W]"}, + {ARM_EXT_V4T, 0x7000, 0xF800, "strb%c\t%0-2r, [%3-5r, #%6-10d]"}, + {ARM_EXT_V4T, 0x7800, 0xF800, "ldrb%c\t%0-2r, [%3-5r, #%6-10d]"}, + /* format 10 */ + {ARM_EXT_V4T, 0x8000, 0xF800, "strh%c\t%0-2r, [%3-5r, #%6-10H]"}, + {ARM_EXT_V4T, 0x8800, 0xF800, "ldrh%c\t%0-2r, [%3-5r, #%6-10H]"}, + /* format 11 */ + {ARM_EXT_V4T, 0x9000, 0xF800, "str%c\t%8-10r, [sp, #%0-7W]"}, + {ARM_EXT_V4T, 0x9800, 0xF800, "ldr%c\t%8-10r, [sp, #%0-7W]"}, + /* format 12 */ + {ARM_EXT_V4T, 0xA000, 0xF800, "add%c\t%8-10r, pc, #%0-7W\t(adr %8-10r, %0-7a)"}, + {ARM_EXT_V4T, 0xA800, 0xF800, "add%c\t%8-10r, sp, #%0-7W"}, + /* format 15 */ + {ARM_EXT_V4T, 0xC000, 0xF800, "stmia%c\t%8-10r!, %M"}, + {ARM_EXT_V4T, 0xC800, 0xF800, "ldmia%c\t%8-10r!, %M"}, + /* format 17 */ + {ARM_EXT_V4T, 0xDF00, 0xFF00, "svc%c\t%0-7d"}, + /* format 16 */ + {ARM_EXT_V4T, 0xDE00, 0xFE00, "undefined"}, + {ARM_EXT_V4T, 0xD000, 0xF000, "b%8-11c.n\t%0-7B%X"}, + /* format 18 */ + {ARM_EXT_V4T, 0xE000, 0xF800, "b%c.n\t%0-10B%x"}, + + /* The E800 .. FFFF range is unconditionally redirected to the + 32-bit table, because even in pre-V6T2 ISAs, BL and BLX(1) pairs + are processed via that table. Thus, we can never encounter a + bare "second half of BL/BLX(1)" instruction here. */ + {ARM_EXT_V1, 0x0000, 0x0000, "undefined"}, + {0, 0, 0, 0} +}; + +/* Thumb32 opcodes use the same table structure as the ARM opcodes. + We adopt the convention that hw1 is the high 16 bits of .value and + .mask, hw2 the low 16 bits. + + print_insn_thumb32 recognizes the following format control codes: + + %% % + + %I print a 12-bit immediate from hw1[10],hw2[14:12,7:0] + %M print a modified 12-bit immediate (same location) + %J print a 16-bit immediate from hw1[3:0,10],hw2[14:12,7:0] + %K print a 16-bit immediate from hw2[3:0],hw1[3:0],hw2[11:4] + %S print a possibly-shifted Rm + + %a print the address of a plain load/store + %w print the width and signedness of a core load/store + %m print register mask for ldm/stm + + %E print the lsb and width fields of a bfc/bfi instruction + %F print the lsb and width fields of a sbfx/ubfx instruction + %b print a conditional branch offset + %B print an unconditional branch offset + %s print the shift field of an SSAT instruction + %R print the rotation field of an SXT instruction + %U print barrier type. + %P print address for pli instruction. + %c print the condition code + %x print warning if conditional an not at end of IT block" + %X print "\t; unpredictable " if conditional + + %d print bitfield in decimal + %W print bitfield*4 in decimal + %r print bitfield as an ARM register + %c print bitfield as a condition code + + %'c print specified char iff bitfield is all ones + %`c print specified char iff bitfield is all zeroes + %?ab... select from array of values in big endian order + + With one exception at the bottom (done because BL and BLX(1) need + to come dead last), this table was machine-sorted first in + decreasing order of number of bits set in the mask, then in + increasing numeric order of mask, then in increasing numeric order + of opcode. This order is not the clearest for a human reader, but + is guaranteed never to catch a special-case bit pattern with a more + general mask, which is important, because this instruction encoding + makes heavy use of special-case bit patterns. */ +static const struct opcode32 thumb32_opcodes[] = +{ + /* V7 instructions. */ + {ARM_EXT_V7, 0xf910f000, 0xff70f000, "pli%c\t%a"}, + {ARM_EXT_V7, 0xf3af80f0, 0xfffffff0, "dbg%c\t#%0-3d"}, + {ARM_EXT_V7, 0xf3bf8f50, 0xfffffff0, "dmb%c\t%U"}, + {ARM_EXT_V7, 0xf3bf8f40, 0xfffffff0, "dsb%c\t%U"}, + {ARM_EXT_V7, 0xf3bf8f60, 0xfffffff0, "isb%c\t%U"}, + {ARM_EXT_DIV, 0xfb90f0f0, 0xfff0f0f0, "sdiv%c\t%8-11r, %16-19r, %0-3r"}, + {ARM_EXT_DIV, 0xfbb0f0f0, 0xfff0f0f0, "udiv%c\t%8-11r, %16-19r, %0-3r"}, + + /* Instructions defined in the basic V6T2 set. */ + {ARM_EXT_V6T2, 0xf3af8000, 0xffffffff, "nop%c.w"}, + {ARM_EXT_V6T2, 0xf3af8001, 0xffffffff, "yield%c.w"}, + {ARM_EXT_V6T2, 0xf3af8002, 0xffffffff, "wfe%c.w"}, + {ARM_EXT_V6T2, 0xf3af8003, 0xffffffff, "wfi%c.w"}, + {ARM_EXT_V6T2, 0xf3af9004, 0xffffffff, "sev%c.w"}, + {ARM_EXT_V6T2, 0xf3af8000, 0xffffff00, "nop%c.w\t{%0-7d}"}, + + {ARM_EXT_V6T2, 0xf3bf8f2f, 0xffffffff, "clrex%c"}, + {ARM_EXT_V6T2, 0xf3af8400, 0xffffff1f, "cpsie.w\t%7'a%6'i%5'f%X"}, + {ARM_EXT_V6T2, 0xf3af8600, 0xffffff1f, "cpsid.w\t%7'a%6'i%5'f%X"}, + {ARM_EXT_V6T2, 0xf3c08f00, 0xfff0ffff, "bxj%c\t%16-19r%x"}, + {ARM_EXT_V6T2, 0xe810c000, 0xffd0ffff, "rfedb%c\t%16-19r%21'!"}, + {ARM_EXT_V6T2, 0xe990c000, 0xffd0ffff, "rfeia%c\t%16-19r%21'!"}, + {ARM_EXT_V6T2, 0xf3ef8000, 0xffeff000, "mrs%c\t%8-11r, %D"}, + {ARM_EXT_V6T2, 0xf3af8100, 0xffffffe0, "cps\t#%0-4d%X"}, + {ARM_EXT_V6T2, 0xe8d0f000, 0xfff0fff0, "tbb%c\t[%16-19r, %0-3r]%x"}, + {ARM_EXT_V6T2, 0xe8d0f010, 0xfff0fff0, "tbh%c\t[%16-19r, %0-3r, lsl #1]%x"}, + {ARM_EXT_V6T2, 0xf3af8500, 0xffffff00, "cpsie\t%7'a%6'i%5'f, #%0-4d%X"}, + {ARM_EXT_V6T2, 0xf3af8700, 0xffffff00, "cpsid\t%7'a%6'i%5'f, #%0-4d%X"}, + {ARM_EXT_V6T2, 0xf3de8f00, 0xffffff00, "subs%c\tpc, lr, #%0-7d"}, + {ARM_EXT_V6T2, 0xf3808000, 0xffe0f000, "msr%c\t%C, %16-19r"}, + {ARM_EXT_V6T2, 0xe8500f00, 0xfff00fff, "ldrex%c\t%12-15r, [%16-19r]"}, + {ARM_EXT_V6T2, 0xe8d00f4f, 0xfff00fef, "ldrex%4?hb%c\t%12-15r, [%16-19r]"}, + {ARM_EXT_V6T2, 0xe800c000, 0xffd0ffe0, "srsdb%c\t%16-19r%21'!, #%0-4d"}, + {ARM_EXT_V6T2, 0xe980c000, 0xffd0ffe0, "srsia%c\t%16-19r%21'!, #%0-4d"}, + {ARM_EXT_V6T2, 0xfa0ff080, 0xfffff0c0, "sxth%c.w\t%8-11r, %0-3r%R"}, + {ARM_EXT_V6T2, 0xfa1ff080, 0xfffff0c0, "uxth%c.w\t%8-11r, %0-3r%R"}, + {ARM_EXT_V6T2, 0xfa2ff080, 0xfffff0c0, "sxtb16%c\t%8-11r, %0-3r%R"}, + {ARM_EXT_V6T2, 0xfa3ff080, 0xfffff0c0, "uxtb16%c\t%8-11r, %0-3r%R"}, + {ARM_EXT_V6T2, 0xfa4ff080, 0xfffff0c0, "sxtb%c.w\t%8-11r, %0-3r%R"}, + {ARM_EXT_V6T2, 0xfa5ff080, 0xfffff0c0, "uxtb%c.w\t%8-11r, %0-3r%R"}, + {ARM_EXT_V6T2, 0xe8400000, 0xfff000ff, "strex%c\t%8-11r, %12-15r, [%16-19r]"}, + {ARM_EXT_V6T2, 0xe8d0007f, 0xfff000ff, "ldrexd%c\t%12-15r, %8-11r, [%16-19r]"}, + {ARM_EXT_V6T2, 0xfa80f000, 0xfff0f0f0, "sadd8%c\t%8-11r, %16-19r, %0-3r"}, + {ARM_EXT_V6T2, 0xfa80f010, 0xfff0f0f0, "qadd8%c\t%8-11r, %16-19r, %0-3r"}, + {ARM_EXT_V6T2, 0xfa80f020, 0xfff0f0f0, "shadd8%c\t%8-11r, %16-19r, %0-3r"}, + {ARM_EXT_V6T2, 0xfa80f040, 0xfff0f0f0, "uadd8%c\t%8-11r, %16-19r, %0-3r"}, + {ARM_EXT_V6T2, 0xfa80f050, 0xfff0f0f0, "uqadd8%c\t%8-11r, %16-19r, %0-3r"}, + {ARM_EXT_V6T2, 0xfa80f060, 0xfff0f0f0, "uhadd8%c\t%8-11r, %16-19r, %0-3r"}, + {ARM_EXT_V6T2, 0xfa80f080, 0xfff0f0f0, "qadd%c\t%8-11r, %0-3r, %16-19r"}, + {ARM_EXT_V6T2, 0xfa80f090, 0xfff0f0f0, "qdadd%c\t%8-11r, %0-3r, %16-19r"}, + {ARM_EXT_V6T2, 0xfa80f0a0, 0xfff0f0f0, "qsub%c\t%8-11r, %0-3r, %16-19r"}, + {ARM_EXT_V6T2, 0xfa80f0b0, 0xfff0f0f0, "qdsub%c\t%8-11r, %0-3r, %16-19r"}, + {ARM_EXT_V6T2, 0xfa90f000, 0xfff0f0f0, "sadd16%c\t%8-11r, %16-19r, %0-3r"}, + {ARM_EXT_V6T2, 0xfa90f010, 0xfff0f0f0, "qadd16%c\t%8-11r, %16-19r, %0-3r"}, + {ARM_EXT_V6T2, 0xfa90f020, 0xfff0f0f0, "shadd16%c\t%8-11r, %16-19r, %0-3r"}, + {ARM_EXT_V6T2, 0xfa90f040, 0xfff0f0f0, "uadd16%c\t%8-11r, %16-19r, %0-3r"}, + {ARM_EXT_V6T2, 0xfa90f050, 0xfff0f0f0, "uqadd16%c\t%8-11r, %16-19r, %0-3r"}, + {ARM_EXT_V6T2, 0xfa90f060, 0xfff0f0f0, "uhadd16%c\t%8-11r, %16-19r, %0-3r"}, + {ARM_EXT_V6T2, 0xfa90f080, 0xfff0f0f0, "rev%c.w\t%8-11r, %16-19r"}, + {ARM_EXT_V6T2, 0xfa90f090, 0xfff0f0f0, "rev16%c.w\t%8-11r, %16-19r"}, + {ARM_EXT_V6T2, 0xfa90f0a0, 0xfff0f0f0, "rbit%c\t%8-11r, %16-19r"}, + {ARM_EXT_V6T2, 0xfa90f0b0, 0xfff0f0f0, "revsh%c.w\t%8-11r, %16-19r"}, + {ARM_EXT_V6T2, 0xfaa0f000, 0xfff0f0f0, "saddsubx%c\t%8-11r, %16-19r, %0-3r"}, + {ARM_EXT_V6T2, 0xfaa0f010, 0xfff0f0f0, "qaddsubx%c\t%8-11r, %16-19r, %0-3r"}, + {ARM_EXT_V6T2, 0xfaa0f020, 0xfff0f0f0, "shaddsubx%c\t%8-11r, %16-19r, %0-3r"}, + {ARM_EXT_V6T2, 0xfaa0f040, 0xfff0f0f0, "uaddsubx%c\t%8-11r, %16-19r, %0-3r"}, + {ARM_EXT_V6T2, 0xfaa0f050, 0xfff0f0f0, "uqaddsubx%c\t%8-11r, %16-19r, %0-3r"}, + {ARM_EXT_V6T2, 0xfaa0f060, 0xfff0f0f0, "uhaddsubx%c\t%8-11r, %16-19r, %0-3r"}, + {ARM_EXT_V6T2, 0xfaa0f080, 0xfff0f0f0, "sel%c\t%8-11r, %16-19r, %0-3r"}, + {ARM_EXT_V6T2, 0xfab0f080, 0xfff0f0f0, "clz%c\t%8-11r, %16-19r"}, + {ARM_EXT_V6T2, 0xfac0f000, 0xfff0f0f0, "ssub8%c\t%8-11r, %16-19r, %0-3r"}, + {ARM_EXT_V6T2, 0xfac0f010, 0xfff0f0f0, "qsub8%c\t%8-11r, %16-19r, %0-3r"}, + {ARM_EXT_V6T2, 0xfac0f020, 0xfff0f0f0, "shsub8%c\t%8-11r, %16-19r, %0-3r"}, + {ARM_EXT_V6T2, 0xfac0f040, 0xfff0f0f0, "usub8%c\t%8-11r, %16-19r, %0-3r"}, + {ARM_EXT_V6T2, 0xfac0f050, 0xfff0f0f0, "uqsub8%c\t%8-11r, %16-19r, %0-3r"}, + {ARM_EXT_V6T2, 0xfac0f060, 0xfff0f0f0, "uhsub8%c\t%8-11r, %16-19r, %0-3r"}, + {ARM_EXT_V6T2, 0xfad0f000, 0xfff0f0f0, "ssub16%c\t%8-11r, %16-19r, %0-3r"}, + {ARM_EXT_V6T2, 0xfad0f010, 0xfff0f0f0, "qsub16%c\t%8-11r, %16-19r, %0-3r"}, + {ARM_EXT_V6T2, 0xfad0f020, 0xfff0f0f0, "shsub16%c\t%8-11r, %16-19r, %0-3r"}, + {ARM_EXT_V6T2, 0xfad0f040, 0xfff0f0f0, "usub16%c\t%8-11r, %16-19r, %0-3r"}, + {ARM_EXT_V6T2, 0xfad0f050, 0xfff0f0f0, "uqsub16%c\t%8-11r, %16-19r, %0-3r"}, + {ARM_EXT_V6T2, 0xfad0f060, 0xfff0f0f0, "uhsub16%c\t%8-11r, %16-19r, %0-3r"}, + {ARM_EXT_V6T2, 0xfae0f000, 0xfff0f0f0, "ssubaddx%c\t%8-11r, %16-19r, %0-3r"}, + {ARM_EXT_V6T2, 0xfae0f010, 0xfff0f0f0, "qsubaddx%c\t%8-11r, %16-19r, %0-3r"}, + {ARM_EXT_V6T2, 0xfae0f020, 0xfff0f0f0, "shsubaddx%c\t%8-11r, %16-19r, %0-3r"}, + {ARM_EXT_V6T2, 0xfae0f040, 0xfff0f0f0, "usubaddx%c\t%8-11r, %16-19r, %0-3r"}, + {ARM_EXT_V6T2, 0xfae0f050, 0xfff0f0f0, "uqsubaddx%c\t%8-11r, %16-19r, %0-3r"}, + {ARM_EXT_V6T2, 0xfae0f060, 0xfff0f0f0, "uhsubaddx%c\t%8-11r, %16-19r, %0-3r"}, + {ARM_EXT_V6T2, 0xfb00f000, 0xfff0f0f0, "mul%c.w\t%8-11r, %16-19r, %0-3r"}, + {ARM_EXT_V6T2, 0xfb70f000, 0xfff0f0f0, "usad8%c\t%8-11r, %16-19r, %0-3r"}, + {ARM_EXT_V6T2, 0xfa00f000, 0xffe0f0f0, "lsl%20's%c.w\t%8-11r, %16-19r, %0-3r"}, + {ARM_EXT_V6T2, 0xfa20f000, 0xffe0f0f0, "lsr%20's%c.w\t%8-11r, %16-19r, %0-3r"}, + {ARM_EXT_V6T2, 0xfa40f000, 0xffe0f0f0, "asr%20's%c.w\t%8-11r, %16-19r, %0-3r"}, + {ARM_EXT_V6T2, 0xfa60f000, 0xffe0f0f0, "ror%20's%c.w\t%8-11r, %16-19r, %0-3r"}, + {ARM_EXT_V6T2, 0xe8c00f40, 0xfff00fe0, "strex%4?hb%c\t%0-3r, %12-15r, [%16-19r]"}, + {ARM_EXT_V6T2, 0xf3200000, 0xfff0f0e0, "ssat16%c\t%8-11r, #%0-4d, %16-19r"}, + {ARM_EXT_V6T2, 0xf3a00000, 0xfff0f0e0, "usat16%c\t%8-11r, #%0-4d, %16-19r"}, + {ARM_EXT_V6T2, 0xfb20f000, 0xfff0f0e0, "smuad%4'x%c\t%8-11r, %16-19r, %0-3r"}, + {ARM_EXT_V6T2, 0xfb30f000, 0xfff0f0e0, "smulw%4?tb%c\t%8-11r, %16-19r, %0-3r"}, + {ARM_EXT_V6T2, 0xfb40f000, 0xfff0f0e0, "smusd%4'x%c\t%8-11r, %16-19r, %0-3r"}, + {ARM_EXT_V6T2, 0xfb50f000, 0xfff0f0e0, "smmul%4'r%c\t%8-11r, %16-19r, %0-3r"}, + {ARM_EXT_V6T2, 0xfa00f080, 0xfff0f0c0, "sxtah%c\t%8-11r, %16-19r, %0-3r%R"}, + {ARM_EXT_V6T2, 0xfa10f080, 0xfff0f0c0, "uxtah%c\t%8-11r, %16-19r, %0-3r%R"}, + {ARM_EXT_V6T2, 0xfa20f080, 0xfff0f0c0, "sxtab16%c\t%8-11r, %16-19r, %0-3r%R"}, + {ARM_EXT_V6T2, 0xfa30f080, 0xfff0f0c0, "uxtab16%c\t%8-11r, %16-19r, %0-3r%R"}, + {ARM_EXT_V6T2, 0xfa40f080, 0xfff0f0c0, "sxtab%c\t%8-11r, %16-19r, %0-3r%R"}, + {ARM_EXT_V6T2, 0xfa50f080, 0xfff0f0c0, "uxtab%c\t%8-11r, %16-19r, %0-3r%R"}, + {ARM_EXT_V6T2, 0xfb10f000, 0xfff0f0c0, "smul%5?tb%4?tb%c\t%8-11r, %16-19r, %0-3r"}, + {ARM_EXT_V6T2, 0xf36f0000, 0xffff8020, "bfc%c\t%8-11r, %E"}, + {ARM_EXT_V6T2, 0xea100f00, 0xfff08f00, "tst%c.w\t%16-19r, %S"}, + {ARM_EXT_V6T2, 0xea900f00, 0xfff08f00, "teq%c\t%16-19r, %S"}, + {ARM_EXT_V6T2, 0xeb100f00, 0xfff08f00, "cmn%c.w\t%16-19r, %S"}, + {ARM_EXT_V6T2, 0xebb00f00, 0xfff08f00, "cmp%c.w\t%16-19r, %S"}, + {ARM_EXT_V6T2, 0xf0100f00, 0xfbf08f00, "tst%c.w\t%16-19r, %M"}, + {ARM_EXT_V6T2, 0xf0900f00, 0xfbf08f00, "teq%c\t%16-19r, %M"}, + {ARM_EXT_V6T2, 0xf1100f00, 0xfbf08f00, "cmn%c.w\t%16-19r, %M"}, + {ARM_EXT_V6T2, 0xf1b00f00, 0xfbf08f00, "cmp%c.w\t%16-19r, %M"}, + {ARM_EXT_V6T2, 0xea4f0000, 0xffef8000, "mov%20's%c.w\t%8-11r, %S"}, + {ARM_EXT_V6T2, 0xea6f0000, 0xffef8000, "mvn%20's%c.w\t%8-11r, %S"}, + {ARM_EXT_V6T2, 0xe8c00070, 0xfff000f0, "strexd%c\t%0-3r, %12-15r, %8-11r, [%16-19r]"}, + {ARM_EXT_V6T2, 0xfb000000, 0xfff000f0, "mla%c\t%8-11r, %16-19r, %0-3r, %12-15r"}, + {ARM_EXT_V6T2, 0xfb000010, 0xfff000f0, "mls%c\t%8-11r, %16-19r, %0-3r, %12-15r"}, + {ARM_EXT_V6T2, 0xfb700000, 0xfff000f0, "usada8%c\t%8-11r, %16-19r, %0-3r, %12-15r"}, + {ARM_EXT_V6T2, 0xfb800000, 0xfff000f0, "smull%c\t%12-15r, %8-11r, %16-19r, %0-3r"}, + {ARM_EXT_V6T2, 0xfba00000, 0xfff000f0, "umull%c\t%12-15r, %8-11r, %16-19r, %0-3r"}, + {ARM_EXT_V6T2, 0xfbc00000, 0xfff000f0, "smlal%c\t%12-15r, %8-11r, %16-19r, %0-3r"}, + {ARM_EXT_V6T2, 0xfbe00000, 0xfff000f0, "umlal%c\t%12-15r, %8-11r, %16-19r, %0-3r"}, + {ARM_EXT_V6T2, 0xfbe00060, 0xfff000f0, "umaal%c\t%12-15r, %8-11r, %16-19r, %0-3r"}, + {ARM_EXT_V6T2, 0xe8500f00, 0xfff00f00, "ldrex%c\t%12-15r, [%16-19r, #%0-7W]"}, + {ARM_EXT_V6T2, 0xf7f08000, 0xfff0f000, "smc%c\t%K"}, + {ARM_EXT_V6T2, 0xf04f0000, 0xfbef8000, "mov%20's%c.w\t%8-11r, %M"}, + {ARM_EXT_V6T2, 0xf06f0000, 0xfbef8000, "mvn%20's%c.w\t%8-11r, %M"}, + {ARM_EXT_V6T2, 0xf810f000, 0xff70f000, "pld%c\t%a"}, + {ARM_EXT_V6T2, 0xfb200000, 0xfff000e0, "smlad%4'x%c\t%8-11r, %16-19r, %0-3r, %12-15r"}, + {ARM_EXT_V6T2, 0xfb300000, 0xfff000e0, "smlaw%4?tb%c\t%8-11r, %16-19r, %0-3r, %12-15r"}, + {ARM_EXT_V6T2, 0xfb400000, 0xfff000e0, "smlsd%4'x%c\t%8-11r, %16-19r, %0-3r, %12-15r"}, + {ARM_EXT_V6T2, 0xfb500000, 0xfff000e0, "smmla%4'r%c\t%8-11r, %16-19r, %0-3r, %12-15r"}, + {ARM_EXT_V6T2, 0xfb600000, 0xfff000e0, "smmls%4'r%c\t%8-11r, %16-19r, %0-3r, %12-15r"}, + {ARM_EXT_V6T2, 0xfbc000c0, 0xfff000e0, "smlald%4'x%c\t%12-15r, %8-11r, %16-19r, %0-3r"}, + {ARM_EXT_V6T2, 0xfbd000c0, 0xfff000e0, "smlsld%4'x%c\t%12-15r, %8-11r, %16-19r, %0-3r"}, + {ARM_EXT_V6T2, 0xeac00000, 0xfff08030, "pkhbt%c\t%8-11r, %16-19r, %S"}, + {ARM_EXT_V6T2, 0xeac00020, 0xfff08030, "pkhtb%c\t%8-11r, %16-19r, %S"}, + {ARM_EXT_V6T2, 0xf3400000, 0xfff08020, "sbfx%c\t%8-11r, %16-19r, %F"}, + {ARM_EXT_V6T2, 0xf3c00000, 0xfff08020, "ubfx%c\t%8-11r, %16-19r, %F"}, + {ARM_EXT_V6T2, 0xf8000e00, 0xff900f00, "str%wt%c\t%12-15r, %a"}, + {ARM_EXT_V6T2, 0xfb100000, 0xfff000c0, "smla%5?tb%4?tb%c\t%8-11r, %16-19r, %0-3r, %12-15r"}, + {ARM_EXT_V6T2, 0xfbc00080, 0xfff000c0, "smlal%5?tb%4?tb%c\t%12-15r, %8-11r, %16-19r, %0-3r"}, + {ARM_EXT_V6T2, 0xf3600000, 0xfff08020, "bfi%c\t%8-11r, %16-19r, %E"}, + {ARM_EXT_V6T2, 0xf8100e00, 0xfe900f00, "ldr%wt%c\t%12-15r, %a"}, + {ARM_EXT_V6T2, 0xf3000000, 0xffd08020, "ssat%c\t%8-11r, #%0-4d, %16-19r%s"}, + {ARM_EXT_V6T2, 0xf3800000, 0xffd08020, "usat%c\t%8-11r, #%0-4d, %16-19r%s"}, + {ARM_EXT_V6T2, 0xf2000000, 0xfbf08000, "addw%c\t%8-11r, %16-19r, %I"}, + {ARM_EXT_V6T2, 0xf2400000, 0xfbf08000, "movw%c\t%8-11r, %J"}, + {ARM_EXT_V6T2, 0xf2a00000, 0xfbf08000, "subw%c\t%8-11r, %16-19r, %I"}, + {ARM_EXT_V6T2, 0xf2c00000, 0xfbf08000, "movt%c\t%8-11r, %J"}, + {ARM_EXT_V6T2, 0xea000000, 0xffe08000, "and%20's%c.w\t%8-11r, %16-19r, %S"}, + {ARM_EXT_V6T2, 0xea200000, 0xffe08000, "bic%20's%c.w\t%8-11r, %16-19r, %S"}, + {ARM_EXT_V6T2, 0xea400000, 0xffe08000, "orr%20's%c.w\t%8-11r, %16-19r, %S"}, + {ARM_EXT_V6T2, 0xea600000, 0xffe08000, "orn%20's%c\t%8-11r, %16-19r, %S"}, + {ARM_EXT_V6T2, 0xea800000, 0xffe08000, "eor%20's%c.w\t%8-11r, %16-19r, %S"}, + {ARM_EXT_V6T2, 0xeb000000, 0xffe08000, "add%20's%c.w\t%8-11r, %16-19r, %S"}, + {ARM_EXT_V6T2, 0xeb400000, 0xffe08000, "adc%20's%c.w\t%8-11r, %16-19r, %S"}, + {ARM_EXT_V6T2, 0xeb600000, 0xffe08000, "sbc%20's%c.w\t%8-11r, %16-19r, %S"}, + {ARM_EXT_V6T2, 0xeba00000, 0xffe08000, "sub%20's%c.w\t%8-11r, %16-19r, %S"}, + {ARM_EXT_V6T2, 0xebc00000, 0xffe08000, "rsb%20's%c\t%8-11r, %16-19r, %S"}, + {ARM_EXT_V6T2, 0xe8400000, 0xfff00000, "strex%c\t%8-11r, %12-15r, [%16-19r, #%0-7W]"}, + {ARM_EXT_V6T2, 0xf0000000, 0xfbe08000, "and%20's%c.w\t%8-11r, %16-19r, %M"}, + {ARM_EXT_V6T2, 0xf0200000, 0xfbe08000, "bic%20's%c.w\t%8-11r, %16-19r, %M"}, + {ARM_EXT_V6T2, 0xf0400000, 0xfbe08000, "orr%20's%c.w\t%8-11r, %16-19r, %M"}, + {ARM_EXT_V6T2, 0xf0600000, 0xfbe08000, "orn%20's%c\t%8-11r, %16-19r, %M"}, + {ARM_EXT_V6T2, 0xf0800000, 0xfbe08000, "eor%20's%c.w\t%8-11r, %16-19r, %M"}, + {ARM_EXT_V6T2, 0xf1000000, 0xfbe08000, "add%20's%c.w\t%8-11r, %16-19r, %M"}, + {ARM_EXT_V6T2, 0xf1400000, 0xfbe08000, "adc%20's%c.w\t%8-11r, %16-19r, %M"}, + {ARM_EXT_V6T2, 0xf1600000, 0xfbe08000, "sbc%20's%c.w\t%8-11r, %16-19r, %M"}, + {ARM_EXT_V6T2, 0xf1a00000, 0xfbe08000, "sub%20's%c.w\t%8-11r, %16-19r, %M"}, + {ARM_EXT_V6T2, 0xf1c00000, 0xfbe08000, "rsb%20's%c\t%8-11r, %16-19r, %M"}, + {ARM_EXT_V6T2, 0xe8800000, 0xffd00000, "stmia%c.w\t%16-19r%21'!, %m"}, + {ARM_EXT_V6T2, 0xe8900000, 0xffd00000, "ldmia%c.w\t%16-19r%21'!, %m"}, + {ARM_EXT_V6T2, 0xe9000000, 0xffd00000, "stmdb%c\t%16-19r%21'!, %m"}, + {ARM_EXT_V6T2, 0xe9100000, 0xffd00000, "ldmdb%c\t%16-19r%21'!, %m"}, + {ARM_EXT_V6T2, 0xe9c00000, 0xffd000ff, "strd%c\t%12-15r, %8-11r, [%16-19r]"}, + {ARM_EXT_V6T2, 0xe9d00000, 0xffd000ff, "ldrd%c\t%12-15r, %8-11r, [%16-19r]"}, + {ARM_EXT_V6T2, 0xe9400000, 0xff500000, "strd%c\t%12-15r, %8-11r, [%16-19r, #%23`-%0-7W]%21'!"}, + {ARM_EXT_V6T2, 0xe9500000, 0xff500000, "ldrd%c\t%12-15r, %8-11r, [%16-19r, #%23`-%0-7W]%21'!"}, + {ARM_EXT_V6T2, 0xe8600000, 0xff700000, "strd%c\t%12-15r, %8-11r, [%16-19r], #%23`-%0-7W"}, + {ARM_EXT_V6T2, 0xe8700000, 0xff700000, "ldrd%c\t%12-15r, %8-11r, [%16-19r], #%23`-%0-7W"}, + {ARM_EXT_V6T2, 0xf8000000, 0xff100000, "str%w%c.w\t%12-15r, %a"}, + {ARM_EXT_V6T2, 0xf8100000, 0xfe100000, "ldr%w%c.w\t%12-15r, %a"}, + + /* Filter out Bcc with cond=E or F, which are used for other instructions. */ + {ARM_EXT_V6T2, 0xf3c08000, 0xfbc0d000, "undefined (bcc, cond=0xF)"}, + {ARM_EXT_V6T2, 0xf3808000, 0xfbc0d000, "undefined (bcc, cond=0xE)"}, + {ARM_EXT_V6T2, 0xf0008000, 0xf800d000, "b%22-25c.w\t%b%X"}, + {ARM_EXT_V6T2, 0xf0009000, 0xf800d000, "b%c.w\t%B%x"}, + + /* These have been 32-bit since the invention of Thumb. */ + {ARM_EXT_V4T, 0xf000c000, 0xf800d000, "blx%c\t%B%x"}, + {ARM_EXT_V4T, 0xf000d000, 0xf800d000, "bl%c\t%B%x"}, + + /* Fallback. */ + {ARM_EXT_V1, 0x00000000, 0x00000000, "undefined"}, + {0, 0, 0, 0} +}; + +static const char *const arm_conditional[] = +{"eq", "ne", "cs", "cc", "mi", "pl", "vs", "vc", + "hi", "ls", "ge", "lt", "gt", "le", "al", "", ""}; + +static const char *const arm_fp_const[] = +{"0.0", "1.0", "2.0", "3.0", "4.0", "5.0", "0.5", "10.0"}; + +static const char *const arm_shift[] = +{"lsl", "lsr", "asr", "ror"}; + +typedef struct +{ + const char *name; + const char *description; + const char *reg_names[16]; +} +arm_regname; + +static const arm_regname regnames[] = +{ + { "raw" , "Select raw register names", + { "r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15"}}, + { "gcc", "Select register names used by GCC", + { "r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7", "r8", "r9", "sl", "fp", "ip", "sp", "lr", "pc" }}, + { "std", "Select register names used in ARM's ISA documentation", + { "r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10", "r11", "r12", "sp", "lr", "pc" }}, + { "apcs", "Select register names used in the APCS", + { "a1", "a2", "a3", "a4", "v1", "v2", "v3", "v4", "v5", "v6", "sl", "fp", "ip", "sp", "lr", "pc" }}, + { "atpcs", "Select register names used in the ATPCS", + { "a1", "a2", "a3", "a4", "v1", "v2", "v3", "v4", "v5", "v6", "v7", "v8", "IP", "SP", "LR", "PC" }}, + { "special-atpcs", "Select special register names used in the ATPCS", + { "a1", "a2", "a3", "a4", "v1", "v2", "v3", "WR", "v5", "SB", "SL", "FP", "IP", "SP", "LR", "PC" }}, +}; + +static const char *const iwmmxt_wwnames[] = +{"b", "h", "w", "d"}; + +static const char *const iwmmxt_wwssnames[] = +{"b", "bus", "bc", "bss", + "h", "hus", "hc", "hss", + "w", "wus", "wc", "wss", + "d", "dus", "dc", "dss" +}; + +static const char *const iwmmxt_regnames[] = +{ "wr0", "wr1", "wr2", "wr3", "wr4", "wr5", "wr6", "wr7", + "wr8", "wr9", "wr10", "wr11", "wr12", "wr13", "wr14", "wr15" +}; + +static const char *const iwmmxt_cregnames[] = +{ "wcid", "wcon", "wcssf", "wcasf", "reserved", "reserved", "reserved", "reserved", + "wcgr0", "wcgr1", "wcgr2", "wcgr3", "reserved", "reserved", "reserved", "reserved" +}; + +/* Default to GCC register name set. */ +static unsigned int regname_selected = 1; + +#define NUM_ARM_REGNAMES NUM_ELEM (regnames) +#define arm_regnames regnames[regname_selected].reg_names + +static bfd_boolean force_thumb = false; + +/* Current IT instruction state. This contains the same state as the IT + bits in the CPSR. */ +static unsigned int ifthen_state; +/* IT state for the next instruction. */ +static unsigned int ifthen_next_state; +/* The address of the insn for which the IT state is valid. */ +static bfd_vma ifthen_address; +#define IFTHEN_COND ((ifthen_state >> 4) & 0xf) + +/* Cached mapping symbol state. */ +enum map_type { + MAP_ARM, + MAP_THUMB, + MAP_DATA +}; + +enum map_type last_type; +int last_mapping_sym = -1; +bfd_vma last_mapping_addr = 0; + +/* Decode a bitfield of the form matching regexp (N(-N)?,)*N(-N)?. + Returns pointer to following character of the format string and + fills in *VALUEP and *WIDTHP with the extracted value and number of + bits extracted. WIDTHP can be NULL. */ + +static const char * +arm_decode_bitfield (const char *ptr, unsigned long insn, + unsigned long *valuep, int *widthp) +{ + unsigned long value = 0; + int width = 0; + + do + { + int start, end; + int bits; + + for (start = 0; *ptr >= '0' && *ptr <= '9'; ptr++) + start = start * 10 + *ptr - '0'; + if (*ptr == '-') + for (end = 0, ptr++; *ptr >= '0' && *ptr <= '9'; ptr++) + end = end * 10 + *ptr - '0'; + else + end = start; + bits = end - start; + if (bits < 0) + abort (); + value |= ((insn >> start) & ((2ul << bits) - 1)) << width; + width += bits + 1; + } + while (*ptr++ == ','); + *valuep = value; + if (widthp) + *widthp = width; + return ptr - 1; +} + +static void +arm_decode_shift (long given, fprintf_function func, void *stream, + int print_shift) +{ + func (stream, "%s", arm_regnames[given & 0xf]); + + if ((given & 0xff0) != 0) + { + if ((given & 0x10) == 0) + { + int amount = (given & 0xf80) >> 7; + int shift = (given & 0x60) >> 5; + + if (amount == 0) + { + if (shift == 3) + { + func (stream, ", rrx"); + return; + } + + amount = 32; + } + + if (print_shift) + func (stream, ", %s #%d", arm_shift[shift], amount); + else + func (stream, ", #%d", amount); + } + else if (print_shift) + func (stream, ", %s %s", arm_shift[(given & 0x60) >> 5], + arm_regnames[(given & 0xf00) >> 8]); + else + func (stream, ", %s", arm_regnames[(given & 0xf00) >> 8]); + } +} + +/* Print one coprocessor instruction on INFO->STREAM. + Return true if the instuction matched, false if this is not a + recognised coprocessor instruction. */ + +static bfd_boolean +print_insn_coprocessor (bfd_vma pc, struct disassemble_info *info, long given, + bfd_boolean thumb) +{ + const struct opcode32 *insn; + void *stream = info->stream; + fprintf_function func = info->fprintf_func; + unsigned long mask; + unsigned long value; + int cond; + + for (insn = coprocessor_opcodes; insn->assembler; insn++) + { + if (insn->value == FIRST_IWMMXT_INSN + && info->mach != bfd_mach_arm_XScale + && info->mach != bfd_mach_arm_iWMMXt + && info->mach != bfd_mach_arm_iWMMXt2) + insn = insn + IWMMXT_INSN_COUNT; + + mask = insn->mask; + value = insn->value; + if (thumb) + { + /* The high 4 bits are 0xe for Arm conditional instructions, and + 0xe for arm unconditional instructions. The rest of the + encoding is the same. */ + mask |= 0xf0000000; + value |= 0xe0000000; + if (ifthen_state) + cond = IFTHEN_COND; + else + cond = 16; + } + else + { + /* Only match unconditional instuctions against unconditional + patterns. */ + if ((given & 0xf0000000) == 0xf0000000) + { + mask |= 0xf0000000; + cond = 16; + } + else + { + cond = (given >> 28) & 0xf; + if (cond == 0xe) + cond = 16; + } + } + if ((given & mask) == value) + { + const char *c; + + for (c = insn->assembler; *c; c++) + { + if (*c == '%') + { + switch (*++c) + { + case '%': + func (stream, "%%"); + break; + + case 'A': + func (stream, "[%s", arm_regnames [(given >> 16) & 0xf]); + + if ((given & (1 << 24)) != 0) + { + int offset = given & 0xff; + + if (offset) + func (stream, ", #%s%d]%s", + ((given & 0x00800000) == 0 ? "-" : ""), + offset * 4, + ((given & 0x00200000) != 0 ? "!" : "")); + else + func (stream, "]"); + } + else + { + int offset = given & 0xff; + + func (stream, "]"); + + if (given & (1 << 21)) + { + if (offset) + func (stream, ", #%s%d", + ((given & 0x00800000) == 0 ? "-" : ""), + offset * 4); + } + else + func (stream, ", {%d}", offset); + } + break; + + case 'B': + { + int regno = ((given >> 12) & 0xf) | ((given >> (22 - 4)) & 0x10); + int offset = (given >> 1) & 0x3f; + + if (offset == 1) + func (stream, "{d%d}", regno); + else if (regno + offset > 32) + func (stream, "{d%d-}", regno, regno + offset - 1); + else + func (stream, "{d%d-d%d}", regno, regno + offset - 1); + } + break; + + case 'C': + { + int rn = (given >> 16) & 0xf; + int offset = (given & 0xff) * 4; + int add = (given >> 23) & 1; + + func (stream, "[%s", arm_regnames[rn]); + + if (offset) + { + if (!add) + offset = -offset; + func (stream, ", #%d", offset); + } + func (stream, "]"); + if (rn == 15) + { + func (stream, "\t; "); + /* FIXME: Unsure if info->bytes_per_chunk is the + right thing to use here. */ + info->print_address_func (offset + pc + + info->bytes_per_chunk * 2, info); + } + } + break; + + case 'c': + func (stream, "%s", arm_conditional[cond]); + break; + + case 'I': + /* Print a Cirrus/DSP shift immediate. */ + /* Immediates are 7bit signed ints with bits 0..3 in + bits 0..3 of opcode and bits 4..6 in bits 5..7 + of opcode. */ + { + int imm; + + imm = (given & 0xf) | ((given & 0xe0) >> 1); + + /* Is ``imm'' a negative number? */ + if (imm & 0x40) + imm |= (-1 << 7); + + func (stream, "%d", imm); + } + + break; + + case 'F': + switch (given & 0x00408000) + { + case 0: + func (stream, "4"); + break; + case 0x8000: + func (stream, "1"); + break; + case 0x00400000: + func (stream, "2"); + break; + default: + func (stream, "3"); + } + break; + + case 'P': + switch (given & 0x00080080) + { + case 0: + func (stream, "s"); + break; + case 0x80: + func (stream, "d"); + break; + case 0x00080000: + func (stream, "e"); + break; + default: + func (stream, _("")); + break; + } + break; + case 'Q': + switch (given & 0x00408000) + { + case 0: + func (stream, "s"); + break; + case 0x8000: + func (stream, "d"); + break; + case 0x00400000: + func (stream, "e"); + break; + default: + func (stream, "p"); + break; + } + break; + case 'R': + switch (given & 0x60) + { + case 0: + break; + case 0x20: + func (stream, "p"); + break; + case 0x40: + func (stream, "m"); + break; + default: + func (stream, "z"); + break; + } + break; + + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + { + int width; + unsigned long value; + + c = arm_decode_bitfield (c, given, &value, &width); + + switch (*c) + { + case 'r': + func (stream, "%s", arm_regnames[value]); + break; + case 'D': + func (stream, "d%ld", value); + break; + case 'Q': + if (value & 1) + func (stream, "", value >> 1); + else + func (stream, "q%ld", value >> 1); + break; + case 'd': + func (stream, "%ld", value); + break; + case 'k': + { + int from = (given & (1 << 7)) ? 32 : 16; + func (stream, "%ld", from - value); + } + break; + + case 'f': + if (value > 7) + func (stream, "#%s", arm_fp_const[value & 7]); + else + func (stream, "f%ld", value); + break; + + case 'w': + if (width == 2) + func (stream, "%s", iwmmxt_wwnames[value]); + else + func (stream, "%s", iwmmxt_wwssnames[value]); + break; + + case 'g': + func (stream, "%s", iwmmxt_regnames[value]); + break; + case 'G': + func (stream, "%s", iwmmxt_cregnames[value]); + break; + + case 'x': + func (stream, "0x%lx", value); + break; + + case '`': + c++; + if (value == 0) + func (stream, "%c", *c); + break; + case '\'': + c++; + if (value == ((1ul << width) - 1)) + func (stream, "%c", *c); + break; + case '?': + func (stream, "%c", c[(1 << width) - (int)value]); + c += 1 << width; + break; + default: + abort (); + } + break; + + case 'y': + case 'z': + { + int single = *c++ == 'y'; + int regno; + + switch (*c) + { + case '4': /* Sm pair */ + func (stream, "{"); + /* Fall through. */ + case '0': /* Sm, Dm */ + regno = given & 0x0000000f; + if (single) + { + regno <<= 1; + regno += (given >> 5) & 1; + } + else + regno += ((given >> 5) & 1) << 4; + break; + + case '1': /* Sd, Dd */ + regno = (given >> 12) & 0x0000000f; + if (single) + { + regno <<= 1; + regno += (given >> 22) & 1; + } + else + regno += ((given >> 22) & 1) << 4; + break; + + case '2': /* Sn, Dn */ + regno = (given >> 16) & 0x0000000f; + if (single) + { + regno <<= 1; + regno += (given >> 7) & 1; + } + else + regno += ((given >> 7) & 1) << 4; + break; + + case '3': /* List */ + func (stream, "{"); + regno = (given >> 12) & 0x0000000f; + if (single) + { + regno <<= 1; + regno += (given >> 22) & 1; + } + else + regno += ((given >> 22) & 1) << 4; + break; + + default: + abort (); + } + + func (stream, "%c%d", single ? 's' : 'd', regno); + + if (*c == '3') + { + int count = given & 0xff; + + if (single == 0) + count >>= 1; + + if (--count) + { + func (stream, "-%c%d", + single ? 's' : 'd', + regno + count); + } + + func (stream, "}"); + } + else if (*c == '4') + func (stream, ", %c%d}", single ? 's' : 'd', + regno + 1); + } + break; + + case 'L': + switch (given & 0x00400100) + { + case 0x00000000: func (stream, "b"); break; + case 0x00400000: func (stream, "h"); break; + case 0x00000100: func (stream, "w"); break; + case 0x00400100: func (stream, "d"); break; + default: + break; + } + break; + + case 'Z': + { + int value; + /* given (20, 23) | given (0, 3) */ + value = ((given >> 16) & 0xf0) | (given & 0xf); + func (stream, "%d", value); + } + break; + + case 'l': + /* This is like the 'A' operator, except that if + the width field "M" is zero, then the offset is + *not* multiplied by four. */ + { + int offset = given & 0xff; + int multiplier = (given & 0x00000100) ? 4 : 1; + + func (stream, "[%s", arm_regnames [(given >> 16) & 0xf]); + + if (offset) + { + if ((given & 0x01000000) != 0) + func (stream, ", #%s%d]%s", + ((given & 0x00800000) == 0 ? "-" : ""), + offset * multiplier, + ((given & 0x00200000) != 0 ? "!" : "")); + else + func (stream, "], #%s%d", + ((given & 0x00800000) == 0 ? "-" : ""), + offset * multiplier); + } + else + func (stream, "]"); + } + break; + + case 'r': + { + int imm4 = (given >> 4) & 0xf; + int puw_bits = ((given >> 22) & 6) | ((given >> 21) & 1); + int ubit = (given >> 23) & 1; + const char *rm = arm_regnames [given & 0xf]; + const char *rn = arm_regnames [(given >> 16) & 0xf]; + + switch (puw_bits) + { + case 1: + /* fall through */ + case 3: + func (stream, "[%s], %c%s", rn, ubit ? '+' : '-', rm); + if (imm4) + func (stream, ", lsl #%d", imm4); + break; + + case 4: + /* fall through */ + case 5: + /* fall through */ + case 6: + /* fall through */ + case 7: + func (stream, "[%s, %c%s", rn, ubit ? '+' : '-', rm); + if (imm4 > 0) + func (stream, ", lsl #%d", imm4); + func (stream, "]"); + if (puw_bits == 5 || puw_bits == 7) + func (stream, "!"); + break; + + default: + func (stream, "INVALID"); + } + } + break; + + case 'i': + { + long imm5; + imm5 = ((given & 0x100) >> 4) | (given & 0xf); + func (stream, "%ld", (imm5 == 0) ? 32 : imm5); + } + break; + + default: + abort (); + } + } + } + else + func (stream, "%c", *c); + } + return true; + } + } + return false; +} + +static void +print_arm_address (bfd_vma pc, struct disassemble_info *info, long given) +{ + void *stream = info->stream; + fprintf_function func = info->fprintf_func; + + if (((given & 0x000f0000) == 0x000f0000) + && ((given & 0x02000000) == 0)) + { + int offset = given & 0xfff; + + func (stream, "[pc"); + + if (given & 0x01000000) + { + if ((given & 0x00800000) == 0) + offset = - offset; + + /* Pre-indexed. */ + func (stream, ", #%d]", offset); + + offset += pc + 8; + + /* Cope with the possibility of write-back + being used. Probably a very dangerous thing + for the programmer to do, but who are we to + argue ? */ + if (given & 0x00200000) + func (stream, "!"); + } + else + { + /* Post indexed. */ + func (stream, "], #%d", offset); + + /* ie ignore the offset. */ + offset = pc + 8; + } + + func (stream, "\t; "); + info->print_address_func (offset, info); + } + else + { + func (stream, "[%s", + arm_regnames[(given >> 16) & 0xf]); + if ((given & 0x01000000) != 0) + { + if ((given & 0x02000000) == 0) + { + int offset = given & 0xfff; + if (offset) + func (stream, ", #%s%d", + (((given & 0x00800000) == 0) + ? "-" : ""), offset); + } + else + { + func (stream, ", %s", + (((given & 0x00800000) == 0) + ? "-" : "")); + arm_decode_shift (given, func, stream, 1); + } + + func (stream, "]%s", + ((given & 0x00200000) != 0) ? "!" : ""); + } + else + { + if ((given & 0x02000000) == 0) + { + int offset = given & 0xfff; + if (offset) + func (stream, "], #%s%d", + (((given & 0x00800000) == 0) + ? "-" : ""), offset); + else + func (stream, "]"); + } + else + { + func (stream, "], %s", + (((given & 0x00800000) == 0) + ? "-" : "")); + arm_decode_shift (given, func, stream, 1); + } + } + } +} + +/* Print one neon instruction on INFO->STREAM. + Return true if the instuction matched, false if this is not a + recognised neon instruction. */ + +static bfd_boolean +print_insn_neon (struct disassemble_info *info, long given, bfd_boolean thumb) +{ + const struct opcode32 *insn; + void *stream = info->stream; + fprintf_function func = info->fprintf_func; + + if (thumb) + { + if ((given & 0xef000000) == 0xef000000) + { + /* move bit 28 to bit 24 to translate Thumb2 to ARM encoding. */ + unsigned long bit28 = given & (1 << 28); + + given &= 0x00ffffff; + if (bit28) + given |= 0xf3000000; + else + given |= 0xf2000000; + } + else if ((given & 0xff000000) == 0xf9000000) + given ^= 0xf9000000 ^ 0xf4000000; + else + return false; + } + + for (insn = neon_opcodes; insn->assembler; insn++) + { + if ((given & insn->mask) == insn->value) + { + const char *c; + + for (c = insn->assembler; *c; c++) + { + if (*c == '%') + { + switch (*++c) + { + case '%': + func (stream, "%%"); + break; + + case 'c': + if (thumb && ifthen_state) + func (stream, "%s", arm_conditional[IFTHEN_COND]); + break; + + case 'A': + { + static const unsigned char enc[16] = + { + 0x4, 0x14, /* st4 0,1 */ + 0x4, /* st1 2 */ + 0x4, /* st2 3 */ + 0x3, /* st3 4 */ + 0x13, /* st3 5 */ + 0x3, /* st1 6 */ + 0x1, /* st1 7 */ + 0x2, /* st2 8 */ + 0x12, /* st2 9 */ + 0x2, /* st1 10 */ + 0, 0, 0, 0, 0 + }; + int rd = ((given >> 12) & 0xf) | (((given >> 22) & 1) << 4); + int rn = ((given >> 16) & 0xf); + int rm = ((given >> 0) & 0xf); + int align = ((given >> 4) & 0x3); + int type = ((given >> 8) & 0xf); + int n = enc[type] & 0xf; + int stride = (enc[type] >> 4) + 1; + int ix; + + func (stream, "{"); + if (stride > 1) + for (ix = 0; ix != n; ix++) + func (stream, "%sd%d", ix ? "," : "", rd + ix * stride); + else if (n == 1) + func (stream, "d%d", rd); + else + func (stream, "d%d-d%d", rd, rd + n - 1); + func (stream, "}, [%s", arm_regnames[rn]); + if (align) + func (stream, ", :%d", 32 << align); + func (stream, "]"); + if (rm == 0xd) + func (stream, "!"); + else if (rm != 0xf) + func (stream, ", %s", arm_regnames[rm]); + } + break; + + case 'B': + { + int rd = ((given >> 12) & 0xf) | (((given >> 22) & 1) << 4); + int rn = ((given >> 16) & 0xf); + int rm = ((given >> 0) & 0xf); + int idx_align = ((given >> 4) & 0xf); + int align = 0; + int size = ((given >> 10) & 0x3); + int idx = idx_align >> (size + 1); + int length = ((given >> 8) & 3) + 1; + int stride = 1; + int i; + + if (length > 1 && size > 0) + stride = (idx_align & (1 << size)) ? 2 : 1; + + switch (length) + { + case 1: + { + int amask = (1 << size) - 1; + if ((idx_align & (1 << size)) != 0) + return false; + if (size > 0) + { + if ((idx_align & amask) == amask) + align = 8 << size; + else if ((idx_align & amask) != 0) + return false; + } + } + break; + + case 2: + if (size == 2 && (idx_align & 2) != 0) + return false; + align = (idx_align & 1) ? 16 << size : 0; + break; + + case 3: + if ((size == 2 && (idx_align & 3) != 0) + || (idx_align & 1) != 0) + return false; + break; + + case 4: + if (size == 2) + { + if ((idx_align & 3) == 3) + return false; + align = (idx_align & 3) * 64; + } + else + align = (idx_align & 1) ? 32 << size : 0; + break; + + default: + abort (); + } + + func (stream, "{"); + for (i = 0; i < length; i++) + func (stream, "%sd%d[%d]", (i == 0) ? "" : ",", + rd + i * stride, idx); + func (stream, "}, [%s", arm_regnames[rn]); + if (align) + func (stream, ", :%d", align); + func (stream, "]"); + if (rm == 0xd) + func (stream, "!"); + else if (rm != 0xf) + func (stream, ", %s", arm_regnames[rm]); + } + break; + + case 'C': + { + int rd = ((given >> 12) & 0xf) | (((given >> 22) & 1) << 4); + int rn = ((given >> 16) & 0xf); + int rm = ((given >> 0) & 0xf); + int align = ((given >> 4) & 0x1); + int size = ((given >> 6) & 0x3); + int type = ((given >> 8) & 0x3); + int n = type + 1; + int stride = ((given >> 5) & 0x1); + int ix; + + if (stride && (n == 1)) + n++; + else + stride++; + + func (stream, "{"); + if (stride > 1) + for (ix = 0; ix != n; ix++) + func (stream, "%sd%d[]", ix ? "," : "", rd + ix * stride); + else if (n == 1) + func (stream, "d%d[]", rd); + else + func (stream, "d%d[]-d%d[]", rd, rd + n - 1); + func (stream, "}, [%s", arm_regnames[rn]); + if (align) + { + int align = (8 * (type + 1)) << size; + if (type == 3) + align = (size > 1) ? align >> 1 : align; + if (type == 2 || (type == 0 && !size)) + func (stream, ", :", align); + else + func (stream, ", :%d", align); + } + func (stream, "]"); + if (rm == 0xd) + func (stream, "!"); + else if (rm != 0xf) + func (stream, ", %s", arm_regnames[rm]); + } + break; + + case 'D': + { + int raw_reg = (given & 0xf) | ((given >> 1) & 0x10); + int size = (given >> 20) & 3; + int reg = raw_reg & ((4 << size) - 1); + int ix = raw_reg >> size >> 2; + + func (stream, "d%d[%d]", reg, ix); + } + break; + + case 'E': + /* Neon encoded constant for mov, mvn, vorr, vbic */ + { + int bits = 0; + int cmode = (given >> 8) & 0xf; + int op = (given >> 5) & 0x1; + unsigned long value = 0, hival = 0; + unsigned shift; + int size = 0; + int isfloat = 0; + + bits |= ((given >> 24) & 1) << 7; + bits |= ((given >> 16) & 7) << 4; + bits |= ((given >> 0) & 15) << 0; + + if (cmode < 8) + { + shift = (cmode >> 1) & 3; + value = (unsigned long)bits << (8 * shift); + size = 32; + } + else if (cmode < 12) + { + shift = (cmode >> 1) & 1; + value = (unsigned long)bits << (8 * shift); + size = 16; + } + else if (cmode < 14) + { + shift = (cmode & 1) + 1; + value = (unsigned long)bits << (8 * shift); + value |= (1ul << (8 * shift)) - 1; + size = 32; + } + else if (cmode == 14) + { + if (op) + { + /* bit replication into bytes */ + int ix; + unsigned long mask; + + value = 0; + hival = 0; + for (ix = 7; ix >= 0; ix--) + { + mask = ((bits >> ix) & 1) ? 0xff : 0; + if (ix <= 3) + value = (value << 8) | mask; + else + hival = (hival << 8) | mask; + } + size = 64; + } + else + { + /* byte replication */ + value = (unsigned long)bits; + size = 8; + } + } + else if (!op) + { + /* floating point encoding */ + int tmp; + + value = (unsigned long)(bits & 0x7f) << 19; + value |= (unsigned long)(bits & 0x80) << 24; + tmp = bits & 0x40 ? 0x3c : 0x40; + value |= (unsigned long)tmp << 24; + size = 32; + isfloat = 1; + } + else + { + func (stream, "", + bits, cmode, op); + break; + } + switch (size) + { + case 8: + func (stream, "#%ld\t; 0x%.2lx", value, value); + break; + + case 16: + func (stream, "#%ld\t; 0x%.4lx", value, value); + break; + + case 32: + if (isfloat) + { + unsigned char valbytes[4]; + double fvalue; + + /* Do this a byte at a time so we don't have to + worry about the host's endianness. */ + valbytes[0] = value & 0xff; + valbytes[1] = (value >> 8) & 0xff; + valbytes[2] = (value >> 16) & 0xff; + valbytes[3] = (value >> 24) & 0xff; + + floatformat_to_double (valbytes, &fvalue); + + func (stream, "#%.7g\t; 0x%.8lx", fvalue, + value); + } + else + func (stream, "#%ld\t; 0x%.8lx", + (long) ((value & 0x80000000) + ? value | ~0xffffffffl : value), value); + break; + + case 64: + func (stream, "#0x%.8lx%.8lx", hival, value); + break; + + default: + abort (); + } + } + break; + + case 'F': + { + int regno = ((given >> 16) & 0xf) | ((given >> (7 - 4)) & 0x10); + int num = (given >> 8) & 0x3; + + if (!num) + func (stream, "{d%d}", regno); + else if (num + regno >= 32) + func (stream, "{d%d-= '0' && *c <= '9') + limit = *c - '0'; + else if (*c >= 'a' && *c <= 'f') + limit = *c - 'a' + 10; + else + abort (); + low = limit >> 2; + high = limit & 3; + + if (value < low || value > high) + func (stream, "", base << value); + else + func (stream, "%d", base << value); + } + break; + case 'R': + if (given & (1 << 6)) + goto Q; + /* FALLTHROUGH */ + case 'D': + func (stream, "d%ld", value); + break; + case 'Q': + Q: + if (value & 1) + func (stream, "", value >> 1); + else + func (stream, "q%ld", value >> 1); + break; + + case '`': + c++; + if (value == 0) + func (stream, "%c", *c); + break; + case '\'': + c++; + if (value == ((1ul << width) - 1)) + func (stream, "%c", *c); + break; + case '?': + func (stream, "%c", c[(1 << width) - (int)value]); + c += 1 << width; + break; + default: + abort (); + } + break; + + default: + abort (); + } + } + } + else + func (stream, "%c", *c); + } + return true; + } + } + return false; +} + +/* Print one ARM instruction from PC on INFO->STREAM. */ + +static void +print_insn_arm_internal (bfd_vma pc, struct disassemble_info *info, long given) +{ + const struct opcode32 *insn; + void *stream = info->stream; + fprintf_function func = info->fprintf_func; + + if (print_insn_coprocessor (pc, info, given, false)) + return; + + if (print_insn_neon (info, given, false)) + return; + + for (insn = arm_opcodes; insn->assembler; insn++) + { + if (insn->value == FIRST_IWMMXT_INSN + && info->mach != bfd_mach_arm_XScale + && info->mach != bfd_mach_arm_iWMMXt) + insn = insn + IWMMXT_INSN_COUNT; + + if ((given & insn->mask) == insn->value + /* Special case: an instruction with all bits set in the condition field + (0xFnnn_nnnn) is only matched if all those bits are set in insn->mask, + or by the catchall at the end of the table. */ + && ((given & 0xF0000000) != 0xF0000000 + || (insn->mask & 0xF0000000) == 0xF0000000 + || (insn->mask == 0 && insn->value == 0))) + { + const char *c; + + for (c = insn->assembler; *c; c++) + { + if (*c == '%') + { + switch (*++c) + { + case '%': + func (stream, "%%"); + break; + + case 'a': + print_arm_address (pc, info, given); + break; + + case 'P': + /* Set P address bit and use normal address + printing routine. */ + print_arm_address (pc, info, given | (1 << 24)); + break; + + case 's': + if ((given & 0x004f0000) == 0x004f0000) + { + /* PC relative with immediate offset. */ + int offset = ((given & 0xf00) >> 4) | (given & 0xf); + + if ((given & 0x00800000) == 0) + offset = -offset; + + func (stream, "[pc, #%d]\t; ", offset); + info->print_address_func (offset + pc + 8, info); + } + else + { + func (stream, "[%s", + arm_regnames[(given >> 16) & 0xf]); + if ((given & 0x01000000) != 0) + { + /* Pre-indexed. */ + if ((given & 0x00400000) == 0x00400000) + { + /* Immediate. */ + int offset = ((given & 0xf00) >> 4) | (given & 0xf); + if (offset) + func (stream, ", #%s%d", + (((given & 0x00800000) == 0) + ? "-" : ""), offset); + } + else + { + /* Register. */ + func (stream, ", %s%s", + (((given & 0x00800000) == 0) + ? "-" : ""), + arm_regnames[given & 0xf]); + } + + func (stream, "]%s", + ((given & 0x00200000) != 0) ? "!" : ""); + } + else + { + /* Post-indexed. */ + if ((given & 0x00400000) == 0x00400000) + { + /* Immediate. */ + int offset = ((given & 0xf00) >> 4) | (given & 0xf); + if (offset) + func (stream, "], #%s%d", + (((given & 0x00800000) == 0) + ? "-" : ""), offset); + else + func (stream, "]"); + } + else + { + /* Register. */ + func (stream, "], %s%s", + (((given & 0x00800000) == 0) + ? "-" : ""), + arm_regnames[given & 0xf]); + } + } + } + break; + + case 'b': + { + int disp = (((given & 0xffffff) ^ 0x800000) - 0x800000); + info->print_address_func (disp*4 + pc + 8, info); + } + break; + + case 'c': + if (((given >> 28) & 0xf) != 0xe) + func (stream, "%s", + arm_conditional [(given >> 28) & 0xf]); + break; + + case 'm': + { + int started = 0; + int reg; + + func (stream, "{"); + for (reg = 0; reg < 16; reg++) + if ((given & (1 << reg)) != 0) + { + if (started) + func (stream, ", "); + started = 1; + func (stream, "%s", arm_regnames[reg]); + } + func (stream, "}"); + } + break; + + case 'q': + arm_decode_shift (given, func, stream, 0); + break; + + case 'o': + if ((given & 0x02000000) != 0) + { + int rotate = (given & 0xf00) >> 7; + int immed = (given & 0xff); + immed = (((immed << (32 - rotate)) + | (immed >> rotate)) & 0xffffffff); + func (stream, "#%d\t; 0x%x", immed, immed); + } + else + arm_decode_shift (given, func, stream, 1); + break; + + case 'p': + if ((given & 0x0000f000) == 0x0000f000) + func (stream, "p"); + break; + + case 't': + if ((given & 0x01200000) == 0x00200000) + func (stream, "t"); + break; + + case 'A': + func (stream, "[%s", arm_regnames [(given >> 16) & 0xf]); + + if ((given & (1 << 24)) != 0) + { + int offset = given & 0xff; + + if (offset) + func (stream, ", #%s%d]%s", + ((given & 0x00800000) == 0 ? "-" : ""), + offset * 4, + ((given & 0x00200000) != 0 ? "!" : "")); + else + func (stream, "]"); + } + else + { + int offset = given & 0xff; + + func (stream, "]"); + + if (given & (1 << 21)) + { + if (offset) + func (stream, ", #%s%d", + ((given & 0x00800000) == 0 ? "-" : ""), + offset * 4); + } + else + func (stream, ", {%d}", offset); + } + break; + + case 'B': + /* Print ARM V5 BLX(1) address: pc+25 bits. */ + { + bfd_vma address; + bfd_vma offset = 0; + + if (given & 0x00800000) + /* Is signed, hi bits should be ones. */ + offset = (-1) ^ 0x00ffffff; + + /* Offset is (SignExtend(offset field)<<2). */ + offset += given & 0x00ffffff; + offset <<= 2; + address = offset + pc + 8; + + if (given & 0x01000000) + /* H bit allows addressing to 2-byte boundaries. */ + address += 2; + + info->print_address_func (address, info); + } + break; + + case 'C': + func (stream, "_"); + if (given & 0x80000) + func (stream, "f"); + if (given & 0x40000) + func (stream, "s"); + if (given & 0x20000) + func (stream, "x"); + if (given & 0x10000) + func (stream, "c"); + break; + + case 'U': + switch (given & 0xf) + { + case 0xf: func(stream, "sy"); break; + case 0x7: func(stream, "un"); break; + case 0xe: func(stream, "st"); break; + case 0x6: func(stream, "unst"); break; + default: + func(stream, "#%d", (int)given & 0xf); + break; + } + break; + + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + { + int width; + unsigned long value; + + c = arm_decode_bitfield (c, given, &value, &width); + + switch (*c) + { + case 'r': + func (stream, "%s", arm_regnames[value]); + break; + case 'd': + func (stream, "%ld", value); + break; + case 'b': + func (stream, "%ld", value * 8); + break; + case 'W': + func (stream, "%ld", value + 1); + break; + case 'x': + func (stream, "0x%08lx", value); + + /* Some SWI instructions have special + meanings. */ + if ((given & 0x0fffffff) == 0x0FF00000) + func (stream, "\t; IMB"); + else if ((given & 0x0fffffff) == 0x0FF00001) + func (stream, "\t; IMBRange"); + break; + case 'X': + func (stream, "%01lx", value & 0xf); + break; + case '`': + c++; + if (value == 0) + func (stream, "%c", *c); + break; + case '\'': + c++; + if (value == ((1ul << width) - 1)) + func (stream, "%c", *c); + break; + case '?': + func (stream, "%c", c[(1 << width) - (int)value]); + c += 1 << width; + break; + default: + abort (); + } + break; + + case 'e': + { + int imm; + + imm = (given & 0xf) | ((given & 0xfff00) >> 4); + func (stream, "%d", imm); + } + break; + + case 'E': + /* LSB and WIDTH fields of BFI or BFC. The machine- + language instruction encodes LSB and MSB. */ + { + long msb = (given & 0x001f0000) >> 16; + long lsb = (given & 0x00000f80) >> 7; + + long width = msb - lsb + 1; + if (width > 0) + func (stream, "#%lu, #%lu", lsb, width); + else + func (stream, "(invalid: %lu:%lu)", lsb, msb); + } + break; + + case 'V': + /* 16-bit unsigned immediate from a MOVT or MOVW + instruction, encoded in bits 0:11 and 15:19. */ + { + long hi = (given & 0x000f0000) >> 4; + long lo = (given & 0x00000fff); + long imm16 = hi | lo; + func (stream, "#%lu\t; 0x%lx", imm16, imm16); + } + break; + + default: + abort (); + } + } + } + else + func (stream, "%c", *c); + } + return; + } + } + abort (); +} + +/* Print one 16-bit Thumb instruction from PC on INFO->STREAM. */ + +static void +print_insn_thumb16 (bfd_vma pc, struct disassemble_info *info, long given) +{ + const struct opcode16 *insn; + void *stream = info->stream; + fprintf_function func = info->fprintf_func; + + for (insn = thumb_opcodes; insn->assembler; insn++) + if ((given & insn->mask) == insn->value) + { + const char *c = insn->assembler; + for (; *c; c++) + { + int domaskpc = 0; + int domasklr = 0; + + if (*c != '%') + { + func (stream, "%c", *c); + continue; + } + + switch (*++c) + { + case '%': + func (stream, "%%"); + break; + + case 'c': + if (ifthen_state) + func (stream, "%s", arm_conditional[IFTHEN_COND]); + break; + + case 'C': + if (ifthen_state) + func (stream, "%s", arm_conditional[IFTHEN_COND]); + else + func (stream, "s"); + break; + + case 'I': + { + unsigned int tmp; + + ifthen_next_state = given & 0xff; + for (tmp = given << 1; tmp & 0xf; tmp <<= 1) + func (stream, ((given ^ tmp) & 0x10) ? "e" : "t"); + func (stream, "\t%s", arm_conditional[(given >> 4) & 0xf]); + } + break; + + case 'x': + if (ifthen_next_state) + func (stream, "\t; unpredictable branch in IT block\n"); + break; + + case 'X': + if (ifthen_state) + func (stream, "\t; unpredictable ", + arm_conditional[IFTHEN_COND]); + break; + + case 'S': + { + long reg; + + reg = (given >> 3) & 0x7; + if (given & (1 << 6)) + reg += 8; + + func (stream, "%s", arm_regnames[reg]); + } + break; + + case 'D': + { + long reg; + + reg = given & 0x7; + if (given & (1 << 7)) + reg += 8; + + func (stream, "%s", arm_regnames[reg]); + } + break; + + case 'N': + if (given & (1 << 8)) + domasklr = 1; + /* Fall through. */ + case 'O': + if (*c == 'O' && (given & (1 << 8))) + domaskpc = 1; + /* Fall through. */ + case 'M': + { + int started = 0; + int reg; + + func (stream, "{"); + + /* It would be nice if we could spot + ranges, and generate the rS-rE format: */ + for (reg = 0; (reg < 8); reg++) + if ((given & (1 << reg)) != 0) + { + if (started) + func (stream, ", "); + started = 1; + func (stream, "%s", arm_regnames[reg]); + } + + if (domasklr) + { + if (started) + func (stream, ", "); + started = 1; + func (stream, "%s", arm_regnames[14] /* "lr" */); + } + + if (domaskpc) + { + if (started) + func (stream, ", "); + func (stream, "%s", arm_regnames[15] /* "pc" */); + } + + func (stream, "}"); + } + break; + + case 'b': + /* Print ARM V6T2 CZB address: pc+4+6 bits. */ + { + bfd_vma address = (pc + 4 + + ((given & 0x00f8) >> 2) + + ((given & 0x0200) >> 3)); + info->print_address_func (address, info); + } + break; + + case 's': + /* Right shift immediate -- bits 6..10; 1-31 print + as themselves, 0 prints as 32. */ + { + long imm = (given & 0x07c0) >> 6; + if (imm == 0) + imm = 32; + func (stream, "#%ld", imm); + } + break; + + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + { + int bitstart = *c++ - '0'; + int bitend = 0; + + while (*c >= '0' && *c <= '9') + bitstart = (bitstart * 10) + *c++ - '0'; + + switch (*c) + { + case '-': + { + long reg; + + c++; + while (*c >= '0' && *c <= '9') + bitend = (bitend * 10) + *c++ - '0'; + if (!bitend) + abort (); + reg = given >> bitstart; + reg &= (2 << (bitend - bitstart)) - 1; + switch (*c) + { + case 'r': + func (stream, "%s", arm_regnames[reg]); + break; + + case 'd': + func (stream, "%ld", reg); + break; + + case 'H': + func (stream, "%ld", reg << 1); + break; + + case 'W': + func (stream, "%ld", reg << 2); + break; + + case 'a': + /* PC-relative address -- the bottom two + bits of the address are dropped + before the calculation. */ + info->print_address_func + (((pc + 4) & ~3) + (reg << 2), info); + break; + + case 'x': + func (stream, "0x%04lx", reg); + break; + + case 'B': + reg = ((reg ^ (1 << bitend)) - (1 << bitend)); + info->print_address_func (reg * 2 + pc + 4, info); + break; + + case 'c': + func (stream, "%s", arm_conditional [reg]); + break; + + default: + abort (); + } + } + break; + + case '\'': + c++; + if ((given & (1 << bitstart)) != 0) + func (stream, "%c", *c); + break; + + case '?': + ++c; + if ((given & (1 << bitstart)) != 0) + func (stream, "%c", *c++); + else + func (stream, "%c", *++c); + break; + + default: + abort (); + } + } + break; + + default: + abort (); + } + } + return; + } + + /* No match. */ + abort (); +} + +/* Return the name of an V7M special register. */ +static const char * +psr_name (int regno) +{ + switch (regno) + { + case 0: return "APSR"; + case 1: return "IAPSR"; + case 2: return "EAPSR"; + case 3: return "PSR"; + case 5: return "IPSR"; + case 6: return "EPSR"; + case 7: return "IEPSR"; + case 8: return "MSP"; + case 9: return "PSP"; + case 16: return "PRIMASK"; + case 17: return "BASEPRI"; + case 18: return "BASEPRI_MASK"; + case 19: return "FAULTMASK"; + case 20: return "CONTROL"; + default: return ""; + } +} + +/* Print one 32-bit Thumb instruction from PC on INFO->STREAM. */ + +static void +print_insn_thumb32 (bfd_vma pc, struct disassemble_info *info, long given) +{ + const struct opcode32 *insn; + void *stream = info->stream; + fprintf_function func = info->fprintf_func; + + if (print_insn_coprocessor (pc, info, given, true)) + return; + + if (print_insn_neon (info, given, true)) + return; + + for (insn = thumb32_opcodes; insn->assembler; insn++) + if ((given & insn->mask) == insn->value) + { + const char *c = insn->assembler; + for (; *c; c++) + { + if (*c != '%') + { + func (stream, "%c", *c); + continue; + } + + switch (*++c) + { + case '%': + func (stream, "%%"); + break; + + case 'c': + if (ifthen_state) + func (stream, "%s", arm_conditional[IFTHEN_COND]); + break; + + case 'x': + if (ifthen_next_state) + func (stream, "\t; unpredictable branch in IT block\n"); + break; + + case 'X': + if (ifthen_state) + func (stream, "\t; unpredictable ", + arm_conditional[IFTHEN_COND]); + break; + + case 'I': + { + unsigned int imm12 = 0; + imm12 |= (given & 0x000000ffu); + imm12 |= (given & 0x00007000u) >> 4; + imm12 |= (given & 0x04000000u) >> 15; + func (stream, "#%u\t; 0x%x", imm12, imm12); + } + break; + + case 'M': + { + unsigned int bits = 0, imm, imm8, mod; + bits |= (given & 0x000000ffu); + bits |= (given & 0x00007000u) >> 4; + bits |= (given & 0x04000000u) >> 15; + imm8 = (bits & 0x0ff); + mod = (bits & 0xf00) >> 8; + switch (mod) + { + case 0: imm = imm8; break; + case 1: imm = ((imm8<<16) | imm8); break; + case 2: imm = ((imm8<<24) | (imm8 << 8)); break; + case 3: imm = ((imm8<<24) | (imm8 << 16) | (imm8 << 8) | imm8); break; + default: + mod = (bits & 0xf80) >> 7; + imm8 = (bits & 0x07f) | 0x80; + imm = (((imm8 << (32 - mod)) | (imm8 >> mod)) & 0xffffffff); + } + func (stream, "#%u\t; 0x%x", imm, imm); + } + break; + + case 'J': + { + unsigned int imm = 0; + imm |= (given & 0x000000ffu); + imm |= (given & 0x00007000u) >> 4; + imm |= (given & 0x04000000u) >> 15; + imm |= (given & 0x000f0000u) >> 4; + func (stream, "#%u\t; 0x%x", imm, imm); + } + break; + + case 'K': + { + unsigned int imm = 0; + imm |= (given & 0x000f0000u) >> 16; + imm |= (given & 0x00000ff0u) >> 0; + imm |= (given & 0x0000000fu) << 12; + func (stream, "#%u\t; 0x%x", imm, imm); + } + break; + + case 'S': + { + unsigned int reg = (given & 0x0000000fu); + unsigned int stp = (given & 0x00000030u) >> 4; + unsigned int imm = 0; + imm |= (given & 0x000000c0u) >> 6; + imm |= (given & 0x00007000u) >> 10; + + func (stream, "%s", arm_regnames[reg]); + switch (stp) + { + case 0: + if (imm > 0) + func (stream, ", lsl #%u", imm); + break; + + case 1: + if (imm == 0) + imm = 32; + func (stream, ", lsr #%u", imm); + break; + + case 2: + if (imm == 0) + imm = 32; + func (stream, ", asr #%u", imm); + break; + + case 3: + if (imm == 0) + func (stream, ", rrx"); + else + func (stream, ", ror #%u", imm); + } + } + break; + + case 'a': + { + unsigned int Rn = (given & 0x000f0000) >> 16; + unsigned int U = (given & 0x00800000) >> 23; + unsigned int op = (given & 0x00000f00) >> 8; + unsigned int i12 = (given & 0x00000fff); + unsigned int i8 = (given & 0x000000ff); + bfd_boolean writeback = false, postind = false; + int offset = 0; + + func (stream, "[%s", arm_regnames[Rn]); + if (U) /* 12-bit positive immediate offset */ + offset = i12; + else if (Rn == 15) /* 12-bit negative immediate offset */ + offset = -(int)i12; + else if (op == 0x0) /* shifted register offset */ + { + unsigned int Rm = (i8 & 0x0f); + unsigned int sh = (i8 & 0x30) >> 4; + func (stream, ", %s", arm_regnames[Rm]); + if (sh) + func (stream, ", lsl #%u", sh); + func (stream, "]"); + break; + } + else switch (op) + { + case 0xE: /* 8-bit positive immediate offset */ + offset = i8; + break; + + case 0xC: /* 8-bit negative immediate offset */ + offset = -i8; + break; + + case 0xF: /* 8-bit + preindex with wb */ + offset = i8; + writeback = true; + break; + + case 0xD: /* 8-bit - preindex with wb */ + offset = -i8; + writeback = true; + break; + + case 0xB: /* 8-bit + postindex */ + offset = i8; + postind = true; + break; + + case 0x9: /* 8-bit - postindex */ + offset = -i8; + postind = true; + break; + + default: + func (stream, ", ]"); + goto skip; + } + + if (postind) + func (stream, "], #%d", offset); + else + { + if (offset) + func (stream, ", #%d", offset); + func (stream, writeback ? "]!" : "]"); + } + + if (Rn == 15) + { + func (stream, "\t; "); + info->print_address_func (((pc + 4) & ~3) + offset, info); + } + } + skip: + break; + + case 'A': + { + unsigned int P = (given & 0x01000000) >> 24; + unsigned int U = (given & 0x00800000) >> 23; + unsigned int W = (given & 0x00400000) >> 21; + unsigned int Rn = (given & 0x000f0000) >> 16; + unsigned int off = (given & 0x000000ff); + + func (stream, "[%s", arm_regnames[Rn]); + if (P) + { + if (off || !U) + func (stream, ", #%c%u", U ? '+' : '-', off * 4); + func (stream, "]"); + if (W) + func (stream, "!"); + } + else + { + func (stream, "], "); + if (W) + func (stream, "#%c%u", U ? '+' : '-', off * 4); + else + func (stream, "{%u}", off); + } + } + break; + + case 'w': + { + unsigned int Sbit = (given & 0x01000000) >> 24; + unsigned int type = (given & 0x00600000) >> 21; + switch (type) + { + case 0: func (stream, Sbit ? "sb" : "b"); break; + case 1: func (stream, Sbit ? "sh" : "h"); break; + case 2: + if (Sbit) + func (stream, "??"); + break; + case 3: + func (stream, "??"); + break; + } + } + break; + + case 'm': + { + int started = 0; + int reg; + + func (stream, "{"); + for (reg = 0; reg < 16; reg++) + if ((given & (1 << reg)) != 0) + { + if (started) + func (stream, ", "); + started = 1; + func (stream, "%s", arm_regnames[reg]); + } + func (stream, "}"); + } + break; + + case 'E': + { + unsigned int msb = (given & 0x0000001f); + unsigned int lsb = 0; + lsb |= (given & 0x000000c0u) >> 6; + lsb |= (given & 0x00007000u) >> 10; + func (stream, "#%u, #%u", lsb, msb - lsb + 1); + } + break; + + case 'F': + { + unsigned int width = (given & 0x0000001f) + 1; + unsigned int lsb = 0; + lsb |= (given & 0x000000c0u) >> 6; + lsb |= (given & 0x00007000u) >> 10; + func (stream, "#%u, #%u", lsb, width); + } + break; + + case 'b': + { + unsigned int S = (given & 0x04000000u) >> 26; + unsigned int J1 = (given & 0x00002000u) >> 13; + unsigned int J2 = (given & 0x00000800u) >> 11; + int offset = 0; + + offset |= !S << 20; + offset |= J2 << 19; + offset |= J1 << 18; + offset |= (given & 0x003f0000) >> 4; + offset |= (given & 0x000007ff) << 1; + offset -= (1 << 20); + + info->print_address_func (pc + 4 + offset, info); + } + break; + + case 'B': + { + unsigned int S = (given & 0x04000000u) >> 26; + unsigned int I1 = (given & 0x00002000u) >> 13; + unsigned int I2 = (given & 0x00000800u) >> 11; + int offset = 0; + + offset |= !S << 24; + offset |= !(I1 ^ S) << 23; + offset |= !(I2 ^ S) << 22; + offset |= (given & 0x03ff0000u) >> 4; + offset |= (given & 0x000007ffu) << 1; + offset -= (1 << 24); + offset += pc + 4; + + /* BLX target addresses are always word aligned. */ + if ((given & 0x00001000u) == 0) + offset &= ~2u; + + info->print_address_func (offset, info); + } + break; + + case 's': + { + unsigned int shift = 0; + shift |= (given & 0x000000c0u) >> 6; + shift |= (given & 0x00007000u) >> 10; + if (given & 0x00200000u) + func (stream, ", asr #%u", shift); + else if (shift) + func (stream, ", lsl #%u", shift); + /* else print nothing - lsl #0 */ + } + break; + + case 'R': + { + unsigned int rot = (given & 0x00000030) >> 4; + if (rot) + func (stream, ", ror #%u", rot * 8); + } + break; + + case 'U': + switch (given & 0xf) + { + case 0xf: func(stream, "sy"); break; + case 0x7: func(stream, "un"); break; + case 0xe: func(stream, "st"); break; + case 0x6: func(stream, "unst"); break; + default: + func(stream, "#%d", (int)given & 0xf); + break; + } + break; + + case 'C': + if ((given & 0xff) == 0) + { + func (stream, "%cPSR_", (given & 0x100000) ? 'S' : 'C'); + if (given & 0x800) + func (stream, "f"); + if (given & 0x400) + func (stream, "s"); + if (given & 0x200) + func (stream, "x"); + if (given & 0x100) + func (stream, "c"); + } + else + { + func (stream, "%s", psr_name (given & 0xff)); + } + break; + + case 'D': + if ((given & 0xff) == 0) + func (stream, "%cPSR", (given & 0x100000) ? 'S' : 'C'); + else + func (stream, "%s", psr_name (given & 0xff)); + break; + + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + { + int width; + unsigned long val; + + c = arm_decode_bitfield (c, given, &val, &width); + + switch (*c) + { + case 'd': func (stream, "%lu", val); break; + case 'W': func (stream, "%lu", val * 4); break; + case 'r': func (stream, "%s", arm_regnames[val]); break; + + case 'c': + func (stream, "%s", arm_conditional[val]); + break; + + case '\'': + c++; + if (val == ((1ul << width) - 1)) + func (stream, "%c", *c); + break; + + case '`': + c++; + if (val == 0) + func (stream, "%c", *c); + break; + + case '?': + func (stream, "%c", c[(1 << width) - (int)val]); + c += 1 << width; + break; + + default: + abort (); + } + } + break; + + default: + abort (); + } + } + return; + } + + /* No match. */ + abort (); +} + +/* Print data bytes on INFO->STREAM. */ + +static void +print_insn_data (bfd_vma pc ATTRIBUTE_UNUSED, struct disassemble_info *info, + long given) +{ + switch (info->bytes_per_chunk) + { + case 1: + info->fprintf_func (info->stream, ".byte\t0x%02lx", given); + break; + case 2: + info->fprintf_func (info->stream, ".short\t0x%04lx", given); + break; + case 4: + info->fprintf_func (info->stream, ".word\t0x%08lx", given); + break; + default: + abort (); + } +} + +/* Search back through the insn stream to determine if this instruction is + conditionally executed. */ +static void +find_ifthen_state (bfd_vma pc, struct disassemble_info *info, + bfd_boolean little) +{ + unsigned char b[2]; + unsigned int insn; + int status; + /* COUNT is twice the number of instructions seen. It will be odd if we + just crossed an instruction boundary. */ + int count; + int it_count; + unsigned int seen_it; + bfd_vma addr; + + ifthen_address = pc; + ifthen_state = 0; + + addr = pc; + count = 1; + it_count = 0; + seen_it = 0; + /* Scan backwards looking for IT instructions, keeping track of where + instruction boundaries are. We don't know if something is actually an + IT instruction until we find a definite instruction boundary. */ + for (;;) + { + if (addr == 0 || info->symbol_at_address_func(addr, info)) + { + /* A symbol must be on an instruction boundary, and will not + be within an IT block. */ + if (seen_it && (count & 1)) + break; + + return; + } + addr -= 2; + status = info->read_memory_func (addr, (bfd_byte *)b, 2, info); + if (status) + return; + + if (little) + insn = (b[0]) | (b[1] << 8); + else + insn = (b[1]) | (b[0] << 8); + if (seen_it) + { + if ((insn & 0xf800) < 0xe800) + { + /* Addr + 2 is an instruction boundary. See if this matches + the expected boundary based on the position of the last + IT candidate. */ + if (count & 1) + break; + seen_it = 0; + } + } + if ((insn & 0xff00) == 0xbf00 && (insn & 0xf) != 0) + { + /* This could be an IT instruction. */ + seen_it = insn; + it_count = count >> 1; + } + if ((insn & 0xf800) >= 0xe800) + count++; + else + count = (count + 2) | 1; + /* IT blocks contain at most 4 instructions. */ + if (count >= 8 && !seen_it) + return; + } + /* We found an IT instruction. */ + ifthen_state = (seen_it & 0xe0) | ((seen_it << it_count) & 0x1f); + if ((ifthen_state & 0xf) == 0) + ifthen_state = 0; +} + +/* NOTE: There are no checks in these routines that + the relevant number of data bytes exist. */ + +int +print_insn_arm (bfd_vma pc, struct disassemble_info *info) +{ + unsigned char b[4]; + long given; + int status; + int is_thumb = false; + int is_data = false; + unsigned int size = 4; + void (*printer) (bfd_vma, struct disassemble_info *, long); +#if 0 + bfd_boolean found = false; + + if (info->disassembler_options) + { + parse_disassembler_options (info->disassembler_options); + + /* To avoid repeated parsing of these options, we remove them here. */ + info->disassembler_options = NULL; + } + + /* First check the full symtab for a mapping symbol, even if there + are no usable non-mapping symbols for this address. */ + if (info->symtab != NULL + && bfd_asymbol_flavour (*info->symtab) == bfd_target_elf_flavour) + { + bfd_vma addr; + int n; + int last_sym = -1; + enum map_type type = MAP_ARM; + + if (pc <= last_mapping_addr) + last_mapping_sym = -1; + is_thumb = (last_type == MAP_THUMB); + found = false; + /* Start scanning at the start of the function, or wherever + we finished last time. */ + n = info->symtab_pos + 1; + if (n < last_mapping_sym) + n = last_mapping_sym; + + /* Scan up to the location being disassembled. */ + for (; n < info->symtab_size; n++) + { + addr = bfd_asymbol_value (info->symtab[n]); + if (addr > pc) + break; + if ((info->section == NULL + || info->section == info->symtab[n]->section) + && get_sym_code_type (info, n, &type)) + { + last_sym = n; + found = true; + } + } + + if (!found) + { + n = info->symtab_pos; + if (n < last_mapping_sym - 1) + n = last_mapping_sym - 1; + + /* No mapping symbol found at this address. Look backwards + for a preceeding one. */ + for (; n >= 0; n--) + { + if (get_sym_code_type (info, n, &type)) + { + last_sym = n; + found = true; + break; + } + } + } + + last_mapping_sym = last_sym; + last_type = type; + is_thumb = (last_type == MAP_THUMB); + is_data = (last_type == MAP_DATA); + + /* Look a little bit ahead to see if we should print out + two or four bytes of data. If there's a symbol, + mapping or otherwise, after two bytes then don't + print more. */ + if (is_data) + { + size = 4 - (pc & 3); + for (n = last_sym + 1; n < info->symtab_size; n++) + { + addr = bfd_asymbol_value (info->symtab[n]); + if (addr > pc) + { + if (addr - pc < size) + size = addr - pc; + break; + } + } + /* If the next symbol is after three bytes, we need to + print only part of the data, so that we can use either + .byte or .short. */ + if (size == 3) + size = (pc & 1) ? 1 : 2; + } + } + + if (info->symbols != NULL) + { + if (bfd_asymbol_flavour (*info->symbols) == bfd_target_coff_flavour) + { + coff_symbol_type * cs; + + cs = coffsymbol (*info->symbols); + is_thumb = ( cs->native->u.syment.n_sclass == C_THUMBEXT + || cs->native->u.syment.n_sclass == C_THUMBSTAT + || cs->native->u.syment.n_sclass == C_THUMBLABEL + || cs->native->u.syment.n_sclass == C_THUMBEXTFUNC + || cs->native->u.syment.n_sclass == C_THUMBSTATFUNC); + } + else if (bfd_asymbol_flavour (*info->symbols) == bfd_target_elf_flavour + && !found) + { + /* If no mapping symbol has been found then fall back to the type + of the function symbol. */ + elf_symbol_type * es; + unsigned int type; + + es = *(elf_symbol_type **)(info->symbols); + type = ELF_ST_TYPE (es->internal_elf_sym.st_info); + + is_thumb = (type == STT_ARM_TFUNC) || (type == STT_ARM_16BIT); + } + } +#else + int little; + + little = (info->endian == BFD_ENDIAN_LITTLE); + is_thumb |= (pc & 1); + pc &= ~(bfd_vma)1; +#endif + + if (force_thumb) + is_thumb = true; + + info->bytes_per_line = 4; + + if (is_data) + { + int i; + + /* size was already set above. */ + info->bytes_per_chunk = size; + printer = print_insn_data; + + status = info->read_memory_func (pc, (bfd_byte *)b, size, info); + given = 0; + if (little) + for (i = size - 1; i >= 0; i--) + given = b[i] | (given << 8); + else + for (i = 0; i < (int) size; i++) + given = b[i] | (given << 8); + } + else if (!is_thumb) + { + /* In ARM mode endianness is a straightforward issue: the instruction + is four bytes long and is either ordered 0123 or 3210. */ + printer = print_insn_arm_internal; + info->bytes_per_chunk = 4; + size = 4; + + status = info->read_memory_func (pc, (bfd_byte *)b, 4, info); + if (little) + given = (b[0]) | (b[1] << 8) | (b[2] << 16) | (b[3] << 24); + else + given = (b[3]) | (b[2] << 8) | (b[1] << 16) | (b[0] << 24); + } + else + { + /* In Thumb mode we have the additional wrinkle of two + instruction lengths. Fortunately, the bits that determine + the length of the current instruction are always to be found + in the first two bytes. */ + printer = print_insn_thumb16; + info->bytes_per_chunk = 2; + size = 2; + + status = info->read_memory_func (pc, (bfd_byte *)b, 2, info); + if (little) + given = (b[0]) | (b[1] << 8); + else + given = (b[1]) | (b[0] << 8); + + if (!status) + { + /* These bit patterns signal a four-byte Thumb + instruction. */ + if ((given & 0xF800) == 0xF800 + || (given & 0xF800) == 0xF000 + || (given & 0xF800) == 0xE800) + { + status = info->read_memory_func (pc + 2, (bfd_byte *)b, 2, info); + if (little) + given = (b[0]) | (b[1] << 8) | (given << 16); + else + given = (b[1]) | (b[0] << 8) | (given << 16); + + printer = print_insn_thumb32; + size = 4; + } + } + + if (ifthen_address != pc) + find_ifthen_state(pc, info, little); + + if (ifthen_state) + { + if ((ifthen_state & 0xf) == 0x8) + ifthen_next_state = 0; + else + ifthen_next_state = (ifthen_state & 0xe0) + | ((ifthen_state & 0xf) << 1); + } + } + + if (status) + { + info->memory_error_func (status, pc, info); + return -1; + } + if (info->flags & INSN_HAS_RELOC) + /* If the instruction has a reloc associated with it, then + the offset field in the instruction will actually be the + addend for the reloc. (We are using REL type relocs). + In such cases, we can ignore the pc when computing + addresses, since the addend is not currently pc-relative. */ + pc = 0; + + /* We include the hexdump of the instruction. The format here + matches that used by objdump and the ARM ARM (in particular, + 32 bit Thumb instructions are displayed as pairs of halfwords, + not as a single word.) */ + if (is_thumb) + { + if (size == 2) + { + info->fprintf_func(info->stream, "%04lx ", + ((unsigned long)given) & 0xffff); + } + else + { + info->fprintf_func(info->stream, "%04lx %04lx ", + (((unsigned long)given) >> 16) & 0xffff, + ((unsigned long)given) & 0xffff); + } + } + else + { + info->fprintf_func(info->stream, "%08lx ", + ((unsigned long)given) & 0xffffffff); + } + + printer (pc, info, given); + + if (is_thumb) + { + ifthen_state = ifthen_next_state; + ifthen_address += size; + } + return size; +} diff --git a/arm-semi.c b/arm-semi.c new file mode 100644 index 0000000..1d5179b --- /dev/null +++ b/arm-semi.c @@ -0,0 +1,488 @@ +/* + * Arm "Angel" semihosting syscalls + * + * Copyright (c) 2005, 2007 CodeSourcery. + * 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. + * + * 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 . + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "cpu.h" +#ifdef CONFIG_USER_ONLY +#include "qemu.h" + +#define ARM_ANGEL_HEAP_SIZE (128 * 1024 * 1024) +#else +#include "qemu-common.h" +#include "sysemu.h" +#include "gdbstub.h" +#endif + +#define SYS_OPEN 0x01 +#define SYS_CLOSE 0x02 +#define SYS_WRITEC 0x03 +#define SYS_WRITE0 0x04 +#define SYS_WRITE 0x05 +#define SYS_READ 0x06 +#define SYS_READC 0x07 +#define SYS_ISTTY 0x09 +#define SYS_SEEK 0x0a +#define SYS_FLEN 0x0c +#define SYS_TMPNAM 0x0d +#define SYS_REMOVE 0x0e +#define SYS_RENAME 0x0f +#define SYS_CLOCK 0x10 +#define SYS_TIME 0x11 +#define SYS_SYSTEM 0x12 +#define SYS_ERRNO 0x13 +#define SYS_GET_CMDLINE 0x15 +#define SYS_HEAPINFO 0x16 +#define SYS_EXIT 0x18 + +#ifndef O_BINARY +#define O_BINARY 0 +#endif + +#define GDB_O_RDONLY 0x000 +#define GDB_O_WRONLY 0x001 +#define GDB_O_RDWR 0x002 +#define GDB_O_APPEND 0x008 +#define GDB_O_CREAT 0x200 +#define GDB_O_TRUNC 0x400 +#define GDB_O_BINARY 0 + +static int gdb_open_modeflags[12] = { + GDB_O_RDONLY, + GDB_O_RDONLY | GDB_O_BINARY, + GDB_O_RDWR, + GDB_O_RDWR | GDB_O_BINARY, + GDB_O_WRONLY | GDB_O_CREAT | GDB_O_TRUNC, + GDB_O_WRONLY | GDB_O_CREAT | GDB_O_TRUNC | GDB_O_BINARY, + GDB_O_RDWR | GDB_O_CREAT | GDB_O_TRUNC, + GDB_O_RDWR | GDB_O_CREAT | GDB_O_TRUNC | GDB_O_BINARY, + GDB_O_WRONLY | GDB_O_CREAT | GDB_O_APPEND, + GDB_O_WRONLY | GDB_O_CREAT | GDB_O_APPEND | GDB_O_BINARY, + GDB_O_RDWR | GDB_O_CREAT | GDB_O_APPEND, + GDB_O_RDWR | GDB_O_CREAT | GDB_O_APPEND | GDB_O_BINARY +}; + +static int open_modeflags[12] = { + O_RDONLY, + O_RDONLY | O_BINARY, + O_RDWR, + O_RDWR | O_BINARY, + O_WRONLY | O_CREAT | O_TRUNC, + O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, + O_RDWR | O_CREAT | O_TRUNC, + O_RDWR | O_CREAT | O_TRUNC | O_BINARY, + O_WRONLY | O_CREAT | O_APPEND, + O_WRONLY | O_CREAT | O_APPEND | O_BINARY, + O_RDWR | O_CREAT | O_APPEND, + O_RDWR | O_CREAT | O_APPEND | O_BINARY +}; + +#ifdef CONFIG_USER_ONLY +static inline uint32_t set_swi_errno(TaskState *ts, uint32_t code) +{ + if (code == (uint32_t)-1) + ts->swi_errno = errno; + return code; +} +#else +static inline uint32_t set_swi_errno(CPUState *env, uint32_t code) +{ + return code; +} + +#include "softmmu-semi.h" +#endif + +static target_ulong arm_semi_syscall_len; + +#if !defined(CONFIG_USER_ONLY) +static target_ulong syscall_err; +#endif + +static void arm_semi_cb(CPUState *env, target_ulong ret, target_ulong err) +{ +#ifdef CONFIG_USER_ONLY + TaskState *ts = env->opaque; +#endif + + if (ret == (target_ulong)-1) { +#ifdef CONFIG_USER_ONLY + ts->swi_errno = err; +#else + syscall_err = err; +#endif + env->regs[0] = ret; + } else { + /* Fixup syscalls that use nonstardard return conventions. */ + switch (env->regs[0]) { + case SYS_WRITE: + case SYS_READ: + env->regs[0] = arm_semi_syscall_len - ret; + break; + case SYS_SEEK: + env->regs[0] = 0; + break; + default: + env->regs[0] = ret; + break; + } + } +} + +static void arm_semi_flen_cb(CPUState *env, target_ulong ret, target_ulong err) +{ + /* The size is always stored in big-endian order, extract + the value. We assume the size always fit in 32 bits. */ + uint32_t size; + cpu_memory_rw_debug(env, env->regs[13]-64+32, (uint8_t *)&size, 4, 0); + env->regs[0] = be32_to_cpu(size); +#ifdef CONFIG_USER_ONLY + ((TaskState *)env->opaque)->swi_errno = err; +#else + syscall_err = err; +#endif +} + +#define ARG(n) \ +({ \ + target_ulong __arg; \ + /* FIXME - handle get_user() failure */ \ + get_user_ual(__arg, args + (n) * 4); \ + __arg; \ +}) +#define SET_ARG(n, val) put_user_ual(val, args + (n) * 4) +uint32_t do_arm_semihosting(CPUState *env) +{ + target_ulong args; + char * s; + int nr; + uint32_t ret; + uint32_t len; +#ifdef CONFIG_USER_ONLY + TaskState *ts = env->opaque; +#else + CPUState *ts = env; +#endif + + nr = env->regs[0]; + args = env->regs[1]; + switch (nr) { + case SYS_OPEN: + if (!(s = lock_user_string(ARG(0)))) + /* FIXME - should this error code be -TARGET_EFAULT ? */ + return (uint32_t)-1; + if (ARG(1) >= 12) + return (uint32_t)-1; + if (strcmp(s, ":tt") == 0) { + if (ARG(1) < 4) + return STDIN_FILENO; + else + return STDOUT_FILENO; + } + if (use_gdb_syscalls()) { + gdb_do_syscall(arm_semi_cb, "open,%s,%x,1a4", ARG(0), + (int)ARG(2)+1, gdb_open_modeflags[ARG(1)]); + return env->regs[0]; + } else { + ret = set_swi_errno(ts, open(s, open_modeflags[ARG(1)], 0644)); + } + unlock_user(s, ARG(0), 0); + return ret; + case SYS_CLOSE: + if (use_gdb_syscalls()) { + gdb_do_syscall(arm_semi_cb, "close,%x", ARG(0)); + return env->regs[0]; + } else { + return set_swi_errno(ts, close(ARG(0))); + } + case SYS_WRITEC: + { + char c; + + if (get_user_u8(c, args)) + /* FIXME - should this error code be -TARGET_EFAULT ? */ + return (uint32_t)-1; + /* Write to debug console. stderr is near enough. */ + if (use_gdb_syscalls()) { + gdb_do_syscall(arm_semi_cb, "write,2,%x,1", args); + return env->regs[0]; + } else { + return write(STDERR_FILENO, &c, 1); + } + } + case SYS_WRITE0: + if (!(s = lock_user_string(args))) + /* FIXME - should this error code be -TARGET_EFAULT ? */ + return (uint32_t)-1; + len = strlen(s); + if (use_gdb_syscalls()) { + gdb_do_syscall(arm_semi_cb, "write,2,%x,%x\n", args, len); + ret = env->regs[0]; + } else { + ret = write(STDERR_FILENO, s, len); + } + unlock_user(s, args, 0); + return ret; + case SYS_WRITE: + len = ARG(2); + if (use_gdb_syscalls()) { + arm_semi_syscall_len = len; + gdb_do_syscall(arm_semi_cb, "write,%x,%x,%x", ARG(0), ARG(1), len); + return env->regs[0]; + } else { + if (!(s = lock_user(VERIFY_READ, ARG(1), len, 1))) + /* FIXME - should this error code be -TARGET_EFAULT ? */ + return (uint32_t)-1; + ret = set_swi_errno(ts, write(ARG(0), s, len)); + unlock_user(s, ARG(1), 0); + if (ret == (uint32_t)-1) + return -1; + return len - ret; + } + case SYS_READ: + len = ARG(2); + if (use_gdb_syscalls()) { + arm_semi_syscall_len = len; + gdb_do_syscall(arm_semi_cb, "read,%x,%x,%x", ARG(0), ARG(1), len); + return env->regs[0]; + } else { + if (!(s = lock_user(VERIFY_WRITE, ARG(1), len, 0))) + /* FIXME - should this error code be -TARGET_EFAULT ? */ + return (uint32_t)-1; + do + ret = set_swi_errno(ts, read(ARG(0), s, len)); + while (ret == -1 && errno == EINTR); + unlock_user(s, ARG(1), len); + if (ret == (uint32_t)-1) + return -1; + return len - ret; + } + case SYS_READC: + /* XXX: Read from debug cosole. Not implemented. */ + return 0; + case SYS_ISTTY: + if (use_gdb_syscalls()) { + gdb_do_syscall(arm_semi_cb, "isatty,%x", ARG(0)); + return env->regs[0]; + } else { + return isatty(ARG(0)); + } + case SYS_SEEK: + if (use_gdb_syscalls()) { + gdb_do_syscall(arm_semi_cb, "lseek,%x,%x,0", ARG(0), ARG(1)); + return env->regs[0]; + } else { + ret = set_swi_errno(ts, lseek(ARG(0), ARG(1), SEEK_SET)); + if (ret == (uint32_t)-1) + return -1; + return 0; + } + case SYS_FLEN: + if (use_gdb_syscalls()) { + gdb_do_syscall(arm_semi_flen_cb, "fstat,%x,%x", + ARG(0), env->regs[13]-64); + return env->regs[0]; + } else { + struct stat buf; + ret = set_swi_errno(ts, fstat(ARG(0), &buf)); + if (ret == (uint32_t)-1) + return -1; + return buf.st_size; + } + case SYS_TMPNAM: + /* XXX: Not implemented. */ + return -1; + case SYS_REMOVE: + if (use_gdb_syscalls()) { + gdb_do_syscall(arm_semi_cb, "unlink,%s", ARG(0), (int)ARG(1)+1); + ret = env->regs[0]; + } else { + if (!(s = lock_user_string(ARG(0)))) + /* FIXME - should this error code be -TARGET_EFAULT ? */ + return (uint32_t)-1; + ret = set_swi_errno(ts, remove(s)); + unlock_user(s, ARG(0), 0); + } + return ret; + case SYS_RENAME: + if (use_gdb_syscalls()) { + gdb_do_syscall(arm_semi_cb, "rename,%s,%s", + ARG(0), (int)ARG(1)+1, ARG(2), (int)ARG(3)+1); + return env->regs[0]; + } else { + char *s2; + s = lock_user_string(ARG(0)); + s2 = lock_user_string(ARG(2)); + if (!s || !s2) + /* FIXME - should this error code be -TARGET_EFAULT ? */ + ret = (uint32_t)-1; + else + ret = set_swi_errno(ts, rename(s, s2)); + if (s2) + unlock_user(s2, ARG(2), 0); + if (s) + unlock_user(s, ARG(0), 0); + return ret; + } + case SYS_CLOCK: + return clock() / (CLOCKS_PER_SEC / 100); + case SYS_TIME: + return set_swi_errno(ts, time(NULL)); + case SYS_SYSTEM: + if (use_gdb_syscalls()) { + gdb_do_syscall(arm_semi_cb, "system,%s", ARG(0), (int)ARG(1)+1); + return env->regs[0]; + } else { + if (!(s = lock_user_string(ARG(0)))) + /* FIXME - should this error code be -TARGET_EFAULT ? */ + return (uint32_t)-1; + ret = set_swi_errno(ts, system(s)); + unlock_user(s, ARG(0), 0); + return ret; + } + case SYS_ERRNO: +#ifdef CONFIG_USER_ONLY + return ts->swi_errno; +#else + return syscall_err; +#endif + case SYS_GET_CMDLINE: +#ifdef CONFIG_USER_ONLY + /* Build a commandline from the original argv. */ + { + char *arm_cmdline_buffer; + const char *host_cmdline_buffer; + + unsigned int i; + unsigned int arm_cmdline_len = ARG(1); + unsigned int host_cmdline_len = + ts->info->arg_end-ts->info->arg_start; + + if (!arm_cmdline_len || host_cmdline_len > arm_cmdline_len) { + return -1; /* not enough space to store command line */ + } + + if (!host_cmdline_len) { + /* We special-case the "empty command line" case (argc==0). + Just provide the terminating 0. */ + arm_cmdline_buffer = lock_user(VERIFY_WRITE, ARG(0), 1, 0); + arm_cmdline_buffer[0] = 0; + unlock_user(arm_cmdline_buffer, ARG(0), 1); + + /* Adjust the commandline length argument. */ + SET_ARG(1, 0); + return 0; + } + + /* lock the buffers on the ARM side */ + arm_cmdline_buffer = + lock_user(VERIFY_WRITE, ARG(0), host_cmdline_len, 0); + host_cmdline_buffer = + lock_user(VERIFY_READ, ts->info->arg_start, + host_cmdline_len, 1); + + if (arm_cmdline_buffer && host_cmdline_buffer) + { + /* the last argument is zero-terminated; + no need for additional termination */ + memcpy(arm_cmdline_buffer, host_cmdline_buffer, + host_cmdline_len); + + /* separate arguments by white spaces */ + for (i = 0; i < host_cmdline_len-1; i++) { + if (arm_cmdline_buffer[i] == 0) { + arm_cmdline_buffer[i] = ' '; + } + } + + /* Adjust the commandline length argument. */ + SET_ARG(1, host_cmdline_len-1); + } + + /* Unlock the buffers on the ARM side. */ + unlock_user(arm_cmdline_buffer, ARG(0), host_cmdline_len); + unlock_user((void*)host_cmdline_buffer, ts->info->arg_start, 0); + + /* Return success if we could return a commandline. */ + return (arm_cmdline_buffer && host_cmdline_buffer) ? 0 : -1; + } +#else + return -1; +#endif + case SYS_HEAPINFO: + { + uint32_t *ptr; + uint32_t limit; + +#ifdef CONFIG_USER_ONLY + /* Some C libraries assume the heap immediately follows .bss, so + allocate it using sbrk. */ + if (!ts->heap_limit) { + long ret; + + ts->heap_base = do_brk(0); + limit = ts->heap_base + ARM_ANGEL_HEAP_SIZE; + /* Try a big heap, and reduce the size if that fails. */ + for (;;) { + ret = do_brk(limit); + if (ret != -1) + break; + limit = (ts->heap_base >> 1) + (limit >> 1); + } + ts->heap_limit = limit; + } + + if (!(ptr = lock_user(VERIFY_WRITE, ARG(0), 16, 0))) + /* FIXME - should this error code be -TARGET_EFAULT ? */ + return (uint32_t)-1; + ptr[0] = tswap32(ts->heap_base); + ptr[1] = tswap32(ts->heap_limit); + ptr[2] = tswap32(ts->stack_base); + ptr[3] = tswap32(0); /* Stack limit. */ + unlock_user(ptr, ARG(0), 16); +#else + limit = ram_size; + if (!(ptr = lock_user(VERIFY_WRITE, ARG(0), 16, 0))) + /* FIXME - should this error code be -TARGET_EFAULT ? */ + return (uint32_t)-1; + /* TODO: Make this use the limit of the loaded application. */ + ptr[0] = tswap32(limit / 2); + ptr[1] = tswap32(limit); + ptr[2] = tswap32(limit); /* Stack base */ + ptr[3] = tswap32(0); /* Stack limit. */ + unlock_user(ptr, ARG(0), 16); +#endif + return 0; + } + case SYS_EXIT: + gdb_exit(env, 0); + exit(0); + default: + fprintf(stderr, "qemu: Unsupported SemiHosting SWI 0x%02x\n", nr); + cpu_dump_state(env, stderr, fprintf, 0); + abort(); + } +} diff --git a/arm.ld b/arm.ld new file mode 100644 index 0000000..12b3edb --- /dev/null +++ b/arm.ld @@ -0,0 +1,153 @@ +OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", + "elf32-littlearm") +OUTPUT_ARCH(arm) +ENTRY(_start) +SECTIONS +{ + /* Read-only sections, merged into text segment: */ + . = 0x60000000 + SIZEOF_HEADERS; + .interp : { *(.interp) } + .hash : { *(.hash) } + .dynsym : { *(.dynsym) } + .dynstr : { *(.dynstr) } + .gnu.version : { *(.gnu.version) } + .gnu.version_d : { *(.gnu.version_d) } + .gnu.version_r : { *(.gnu.version_r) } + .rel.text : + { *(.rel.text) *(.rel.gnu.linkonce.t*) } + .rela.text : + { *(.rela.text) *(.rela.gnu.linkonce.t*) } + .rel.data : + { *(.rel.data) *(.rel.gnu.linkonce.d*) } + .rela.data : + { *(.rela.data) *(.rela.gnu.linkonce.d*) } + .rel.rodata : + { *(.rel.rodata) *(.rel.gnu.linkonce.r*) } + .rela.rodata : + { *(.rela.rodata) *(.rela.gnu.linkonce.r*) } + .rel.got : { *(.rel.got) } + .rela.got : { *(.rela.got) } + .rel.ctors : { *(.rel.ctors) } + .rela.ctors : { *(.rela.ctors) } + .rel.dtors : { *(.rel.dtors) } + .rela.dtors : { *(.rela.dtors) } + .rel.init : { *(.rel.init) } + .rela.init : { *(.rela.init) } + .rel.fini : { *(.rel.fini) } + .rela.fini : { *(.rela.fini) } + .rel.bss : { *(.rel.bss) } + .rela.bss : { *(.rela.bss) } + .rel.plt : { *(.rel.plt) } + .rela.plt : { *(.rela.plt) } + .init : { *(.init) } =0x47ff041f + .text : + { + *(.text) + /* .gnu.warning sections are handled specially by elf32.em. */ + *(.gnu.warning) + *(.gnu.linkonce.t*) + } =0x47ff041f + _etext = .; + PROVIDE (etext = .); + .fini : { *(.fini) } =0x47ff041f + .rodata : { *(.rodata) *(.gnu.linkonce.r*) } + .rodata1 : { *(.rodata1) } + .ARM.extab : { *(.ARM.extab* .gnu.linkonce.armextab.*) } + __exidx_start = .; + .ARM.exidx : { *(.ARM.exidx* .gnu.linkonce.armexidx.*) } + __exidx_end = .; + .reginfo : { *(.reginfo) } + /* Adjust the address for the data segment. We want to adjust up to + the same address within the page on the next page up. */ + . = ALIGN(0x100000) + (. & (0x100000 - 1)); + .data : + { + *(.gen_code) + *(.data) + *(.gnu.linkonce.d*) + CONSTRUCTORS + } + .tbss : { *(.tbss .tbss.* .gnu.linkonce.tb.*) *(.tcommon) } + .data1 : { *(.data1) } + .preinit_array : + { + PROVIDE_HIDDEN (__preinit_array_start = .); + KEEP (*(.preinit_array)) + PROVIDE_HIDDEN (__preinit_array_end = .); + } + .init_array : + { + PROVIDE_HIDDEN (__init_array_start = .); + KEEP (*(SORT(.init_array.*))) + KEEP (*(.init_array)) + PROVIDE_HIDDEN (__init_array_end = .); + } + .fini_array : + { + PROVIDE_HIDDEN (__fini_array_start = .); + KEEP (*(.fini_array)) + KEEP (*(SORT(.fini_array.*))) + PROVIDE_HIDDEN (__fini_array_end = .); + } + .ctors : + { + *(.ctors) + } + .dtors : + { + *(.dtors) + } + .plt : { *(.plt) } + .got : { *(.got.plt) *(.got) } + .dynamic : { *(.dynamic) } + /* We want the small data sections together, so single-instruction offsets + can access them all, and initialized data all before uninitialized, so + we can shorten the on-disk segment size. */ + .sdata : { *(.sdata) } + _edata = .; + PROVIDE (edata = .); + __bss_start = .; + .sbss : { *(.sbss) *(.scommon) } + .bss : + { + *(.dynbss) + *(.bss) + *(COMMON) + } + _end = . ; + PROVIDE (end = .); + /* Stabs debugging sections. */ + .stab 0 : { *(.stab) } + .stabstr 0 : { *(.stabstr) } + .stab.excl 0 : { *(.stab.excl) } + .stab.exclstr 0 : { *(.stab.exclstr) } + .stab.index 0 : { *(.stab.index) } + .stab.indexstr 0 : { *(.stab.indexstr) } + .comment 0 : { *(.comment) } + /* DWARF debug sections. + Symbols in the DWARF debugging sections are relative to the beginning + of the section so we begin them at 0. */ + /* DWARF 1 */ + .debug 0 : { *(.debug) } + .line 0 : { *(.line) } + /* GNU DWARF 1 extensions */ + .debug_srcinfo 0 : { *(.debug_srcinfo) } + .debug_sfnames 0 : { *(.debug_sfnames) } + /* DWARF 1.1 and DWARF 2 */ + .debug_aranges 0 : { *(.debug_aranges) } + .debug_pubnames 0 : { *(.debug_pubnames) } + /* DWARF 2 */ + .debug_info 0 : { *(.debug_info) } + .debug_abbrev 0 : { *(.debug_abbrev) } + .debug_line 0 : { *(.debug_line) } + .debug_frame 0 : { *(.debug_frame) } + .debug_str 0 : { *(.debug_str) } + .debug_loc 0 : { *(.debug_loc) } + .debug_macinfo 0 : { *(.debug_macinfo) } + /* SGI/MIPS DWARF 2 extensions */ + .debug_weaknames 0 : { *(.debug_weaknames) } + .debug_funcnames 0 : { *(.debug_funcnames) } + .debug_typenames 0 : { *(.debug_typenames) } + .debug_varnames 0 : { *(.debug_varnames) } + /* These must appear regardless of . */ +} diff --git a/async.c b/async.c new file mode 100644 index 0000000..57ac3a8 --- /dev/null +++ b/async.c @@ -0,0 +1,216 @@ +/* + * QEMU System Emulator + * + * Copyright (c) 2003-2008 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 "qemu-aio.h" + +/* + * An AsyncContext protects the callbacks of AIO requests and Bottom Halves + * against interfering with each other. A typical example is qcow2 that accepts + * asynchronous requests, but relies for manipulation of its metadata on + * synchronous bdrv_read/write that doesn't trigger any callbacks. + * + * However, these functions are often emulated using AIO which means that AIO + * callbacks must be run - but at the same time we must not run callbacks of + * other requests as they might start to modify metadata and corrupt the + * internal state of the caller of bdrv_read/write. + * + * To achieve the desired semantics we switch into a new AsyncContext. + * Callbacks must only be run if they belong to the current AsyncContext. + * Otherwise they need to be queued until their own context is active again. + * This is how you can make qemu_aio_wait() wait only for your own callbacks. + * + * The AsyncContexts form a stack. When you leave a AsyncContexts, you always + * return to the old ("parent") context. + */ +struct AsyncContext { + /* Consecutive number of the AsyncContext (position in the stack) */ + int id; + + /* Anchor of the list of Bottom Halves belonging to the context */ + struct QEMUBH *first_bh; + + /* Link to parent context */ + struct AsyncContext *parent; +}; + +/* The currently active AsyncContext */ +static struct AsyncContext *async_context = &(struct AsyncContext) { 0 }; + +/* + * Enter a new AsyncContext. Already scheduled Bottom Halves and AIO callbacks + * won't be called until this context is left again. + */ +void async_context_push(void) +{ + struct AsyncContext *new = qemu_mallocz(sizeof(*new)); + new->parent = async_context; + new->id = async_context->id + 1; + async_context = new; +} + +/* Run queued AIO completions and destroy Bottom Half */ +static void bh_run_aio_completions(void *opaque) +{ + QEMUBH **bh = opaque; + qemu_bh_delete(*bh); + qemu_free(bh); + qemu_aio_process_queue(); +} +/* + * Leave the currently active AsyncContext. All Bottom Halves belonging to the + * old context are executed before changing the context. + */ +void async_context_pop(void) +{ + struct AsyncContext *old = async_context; + QEMUBH **bh; + + /* Flush the bottom halves, we don't want to lose them */ + while (qemu_bh_poll()); + + /* Switch back to the parent context */ + async_context = async_context->parent; + qemu_free(old); + + if (async_context == NULL) { + abort(); + } + + /* Schedule BH to run any queued AIO completions as soon as possible */ + bh = qemu_malloc(sizeof(*bh)); + *bh = qemu_bh_new(bh_run_aio_completions, bh); + qemu_bh_schedule(*bh); +} + +/* + * Returns the ID of the currently active AsyncContext + */ +int get_async_context_id(void) +{ + return async_context->id; +} + +/***********************************************************/ +/* bottom halves (can be seen as timers which expire ASAP) */ + +struct QEMUBH { + QEMUBHFunc *cb; + void *opaque; + int scheduled; + int idle; + int deleted; + QEMUBH *next; +}; + +QEMUBH *qemu_bh_new(QEMUBHFunc *cb, void *opaque) +{ + QEMUBH *bh; + bh = qemu_mallocz(sizeof(QEMUBH)); + bh->cb = cb; + bh->opaque = opaque; + bh->next = async_context->first_bh; + async_context->first_bh = bh; + return bh; +} + +int qemu_bh_poll(void) +{ + QEMUBH *bh, **bhp; + int ret; + + ret = 0; + for (bh = async_context->first_bh; bh; bh = bh->next) { + if (!bh->deleted && bh->scheduled) { + bh->scheduled = 0; + if (!bh->idle) + ret = 1; + bh->idle = 0; + bh->cb(bh->opaque); + } + } + + /* remove deleted bhs */ + bhp = &async_context->first_bh; + while (*bhp) { + bh = *bhp; + if (bh->deleted) { + *bhp = bh->next; + qemu_free(bh); + } else + bhp = &bh->next; + } + + return ret; +} + +void qemu_bh_schedule_idle(QEMUBH *bh) +{ + if (bh->scheduled) + return; + bh->scheduled = 1; + bh->idle = 1; +} + +void qemu_bh_schedule(QEMUBH *bh) +{ + if (bh->scheduled) + return; + bh->scheduled = 1; + bh->idle = 0; + /* stop the currently executing CPU to execute the BH ASAP */ + qemu_notify_event(); +} + +void qemu_bh_cancel(QEMUBH *bh) +{ + bh->scheduled = 0; +} + +void qemu_bh_delete(QEMUBH *bh) +{ + bh->scheduled = 0; + bh->deleted = 1; +} + +void qemu_bh_update_timeout(int *timeout) +{ + QEMUBH *bh; + + for (bh = async_context->first_bh; bh; bh = bh->next) { + if (!bh->deleted && bh->scheduled) { + if (bh->idle) { + /* idle bottom halves will be polled at least + * every 10ms */ + *timeout = MIN(10, *timeout); + } else { + /* non-idle bottom halves will be executed + * immediately */ + *timeout = 0; + break; + } + } + } +} + diff --git a/audio/alsaaudio.c b/audio/alsaaudio.c new file mode 100644 index 0000000..4d72014 --- /dev/null +++ b/audio/alsaaudio.c @@ -0,0 +1,1260 @@ +/* + * QEMU ALSA audio driver + * + * 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. + */ +#include +#include "qemu-common.h" +#include "qemu-char.h" +#include "audio.h" + +#if QEMU_GNUC_PREREQ(4, 3) +#pragma GCC diagnostic ignored "-Waddress" +#endif + +#define AUDIO_CAP "alsa" +#include "audio_int.h" + +struct pollhlp { + snd_pcm_t *handle; + struct pollfd *pfds; + int count; + int mask; +}; + +typedef struct ALSAVoiceOut { + HWVoiceOut hw; + int wpos; + int pending; + void *pcm_buf; + snd_pcm_t *handle; + struct pollhlp pollhlp; +} ALSAVoiceOut; + +typedef struct ALSAVoiceIn { + HWVoiceIn hw; + snd_pcm_t *handle; + void *pcm_buf; + struct pollhlp pollhlp; +} ALSAVoiceIn; + +static struct { + int size_in_usec_in; + int size_in_usec_out; + const char *pcm_name_in; + const char *pcm_name_out; + unsigned int buffer_size_in; + unsigned int period_size_in; + unsigned int buffer_size_out; + unsigned int period_size_out; + unsigned int threshold; + + int buffer_size_in_overridden; + int period_size_in_overridden; + + int buffer_size_out_overridden; + int period_size_out_overridden; + int verbose; +} conf = { + .buffer_size_out = 4096, + .period_size_out = 1024, + .pcm_name_out = "default", + .pcm_name_in = "default", +}; + +struct alsa_params_req { + int freq; + snd_pcm_format_t fmt; + int nchannels; + int size_in_usec; + int override_mask; + unsigned int buffer_size; + unsigned int period_size; +}; + +struct alsa_params_obt { + int freq; + audfmt_e fmt; + int endianness; + int nchannels; + snd_pcm_uframes_t samples; +}; + +static void GCC_FMT_ATTR (2, 3) alsa_logerr (int err, const char *fmt, ...) +{ + va_list ap; + + va_start (ap, fmt); + AUD_vlog (AUDIO_CAP, fmt, ap); + va_end (ap); + + AUD_log (AUDIO_CAP, "Reason: %s\n", snd_strerror (err)); +} + +static void GCC_FMT_ATTR (3, 4) alsa_logerr2 ( + int err, + const char *typ, + const char *fmt, + ... + ) +{ + va_list ap; + + AUD_log (AUDIO_CAP, "Could not initialize %s\n", typ); + + va_start (ap, fmt); + AUD_vlog (AUDIO_CAP, fmt, ap); + va_end (ap); + + AUD_log (AUDIO_CAP, "Reason: %s\n", snd_strerror (err)); +} + +static void alsa_fini_poll (struct pollhlp *hlp) +{ + int i; + struct pollfd *pfds = hlp->pfds; + + if (pfds) { + for (i = 0; i < hlp->count; ++i) { + qemu_set_fd_handler (pfds[i].fd, NULL, NULL, NULL); + } + qemu_free (pfds); + } + hlp->pfds = NULL; + hlp->count = 0; + hlp->handle = NULL; +} + +static void alsa_anal_close1 (snd_pcm_t **handlep) +{ + int err = snd_pcm_close (*handlep); + if (err) { + alsa_logerr (err, "Failed to close PCM handle %p\n", *handlep); + } + *handlep = NULL; +} + +static void alsa_anal_close (snd_pcm_t **handlep, struct pollhlp *hlp) +{ + alsa_fini_poll (hlp); + alsa_anal_close1 (handlep); +} + +static int alsa_recover (snd_pcm_t *handle) +{ + int err = snd_pcm_prepare (handle); + if (err < 0) { + alsa_logerr (err, "Failed to prepare handle %p\n", handle); + return -1; + } + return 0; +} + +static int alsa_resume (snd_pcm_t *handle) +{ + int err = snd_pcm_resume (handle); + if (err < 0) { + alsa_logerr (err, "Failed to resume handle %p\n", handle); + return -1; + } + return 0; +} + +static void alsa_poll_handler (void *opaque) +{ + int err, count; + snd_pcm_state_t state; + struct pollhlp *hlp = opaque; + unsigned short revents; + + count = poll (hlp->pfds, hlp->count, 0); + if (count < 0) { + dolog ("alsa_poll_handler: poll %s\n", strerror (errno)); + return; + } + + if (!count) { + return; + } + + /* XXX: ALSA example uses initial count, not the one returned by + poll, correct? */ + err = snd_pcm_poll_descriptors_revents (hlp->handle, hlp->pfds, + hlp->count, &revents); + if (err < 0) { + alsa_logerr (err, "snd_pcm_poll_descriptors_revents"); + return; + } + + if (!(revents & hlp->mask)) { + if (conf.verbose) { + dolog ("revents = %d\n", revents); + } + return; + } + + state = snd_pcm_state (hlp->handle); + switch (state) { + case SND_PCM_STATE_SETUP: + alsa_recover (hlp->handle); + break; + + case SND_PCM_STATE_XRUN: + alsa_recover (hlp->handle); + break; + + case SND_PCM_STATE_SUSPENDED: + alsa_resume (hlp->handle); + break; + + case SND_PCM_STATE_PREPARED: + audio_run ("alsa run (prepared)"); + break; + + case SND_PCM_STATE_RUNNING: + audio_run ("alsa run (running)"); + break; + + default: + dolog ("Unexpected state %d\n", state); + } +} + +static int alsa_poll_helper (snd_pcm_t *handle, struct pollhlp *hlp, int mask) +{ + int i, count, err; + struct pollfd *pfds; + + count = snd_pcm_poll_descriptors_count (handle); + if (count <= 0) { + dolog ("Could not initialize poll mode\n" + "Invalid number of poll descriptors %d\n", count); + return -1; + } + + pfds = audio_calloc ("alsa_poll_helper", count, sizeof (*pfds)); + if (!pfds) { + dolog ("Could not initialize poll mode\n"); + return -1; + } + + err = snd_pcm_poll_descriptors (handle, pfds, count); + if (err < 0) { + alsa_logerr (err, "Could not initialize poll mode\n" + "Could not obtain poll descriptors\n"); + qemu_free (pfds); + return -1; + } + + for (i = 0; i < count; ++i) { + if (pfds[i].events & POLLIN) { + err = qemu_set_fd_handler (pfds[i].fd, alsa_poll_handler, + NULL, hlp); + } + if (pfds[i].events & POLLOUT) { + if (conf.verbose) { + dolog ("POLLOUT %d %d\n", i, pfds[i].fd); + } + err = qemu_set_fd_handler (pfds[i].fd, NULL, + alsa_poll_handler, hlp); + } + if (conf.verbose) { + dolog ("Set handler events=%#x index=%d fd=%d err=%d\n", + pfds[i].events, i, pfds[i].fd, err); + } + + if (err) { + dolog ("Failed to set handler events=%#x index=%d fd=%d err=%d\n", + pfds[i].events, i, pfds[i].fd, err); + + while (i--) { + qemu_set_fd_handler (pfds[i].fd, NULL, NULL, NULL); + } + qemu_free (pfds); + return -1; + } + } + hlp->pfds = pfds; + hlp->count = count; + hlp->handle = handle; + hlp->mask = mask; + return 0; +} + +static int alsa_poll_out (HWVoiceOut *hw) +{ + ALSAVoiceOut *alsa = (ALSAVoiceOut *) hw; + + return alsa_poll_helper (alsa->handle, &alsa->pollhlp, POLLOUT); +} + +static int alsa_poll_in (HWVoiceIn *hw) +{ + ALSAVoiceIn *alsa = (ALSAVoiceIn *) hw; + + return alsa_poll_helper (alsa->handle, &alsa->pollhlp, POLLIN); +} + +static int alsa_write (SWVoiceOut *sw, void *buf, int len) +{ + return audio_pcm_sw_write (sw, buf, len); +} + +static snd_pcm_format_t aud_to_alsafmt (audfmt_e fmt, int endianness) +{ + switch (fmt) { + case AUD_FMT_S8: + return SND_PCM_FORMAT_S8; + + case AUD_FMT_U8: + return SND_PCM_FORMAT_U8; + + case AUD_FMT_S16: + if (endianness) { + return SND_PCM_FORMAT_S16_BE; + } + else { + return SND_PCM_FORMAT_S16_LE; + } + + case AUD_FMT_U16: + if (endianness) { + return SND_PCM_FORMAT_U16_BE; + } + else { + return SND_PCM_FORMAT_U16_LE; + } + + case AUD_FMT_S32: + if (endianness) { + return SND_PCM_FORMAT_S32_BE; + } + else { + return SND_PCM_FORMAT_S32_LE; + } + + case AUD_FMT_U32: + if (endianness) { + return SND_PCM_FORMAT_U32_BE; + } + else { + return SND_PCM_FORMAT_U32_LE; + } + + default: + dolog ("Internal logic error: Bad audio format %d\n", fmt); +#ifdef DEBUG_AUDIO + abort (); +#endif + return SND_PCM_FORMAT_U8; + } +} + +static int alsa_to_audfmt (snd_pcm_format_t alsafmt, audfmt_e *fmt, + int *endianness) +{ + switch (alsafmt) { + case SND_PCM_FORMAT_S8: + *endianness = 0; + *fmt = AUD_FMT_S8; + break; + + case SND_PCM_FORMAT_U8: + *endianness = 0; + *fmt = AUD_FMT_U8; + break; + + case SND_PCM_FORMAT_S16_LE: + *endianness = 0; + *fmt = AUD_FMT_S16; + break; + + case SND_PCM_FORMAT_U16_LE: + *endianness = 0; + *fmt = AUD_FMT_U16; + break; + + case SND_PCM_FORMAT_S16_BE: + *endianness = 1; + *fmt = AUD_FMT_S16; + break; + + case SND_PCM_FORMAT_U16_BE: + *endianness = 1; + *fmt = AUD_FMT_U16; + break; + + case SND_PCM_FORMAT_S32_LE: + *endianness = 0; + *fmt = AUD_FMT_S32; + break; + + case SND_PCM_FORMAT_U32_LE: + *endianness = 0; + *fmt = AUD_FMT_U32; + break; + + case SND_PCM_FORMAT_S32_BE: + *endianness = 1; + *fmt = AUD_FMT_S32; + break; + + case SND_PCM_FORMAT_U32_BE: + *endianness = 1; + *fmt = AUD_FMT_U32; + break; + + default: + dolog ("Unrecognized audio format %d\n", alsafmt); + return -1; + } + + return 0; +} + +static void alsa_dump_info (struct alsa_params_req *req, + struct alsa_params_obt *obt, + snd_pcm_format_t obtfmt) +{ + dolog ("parameter | requested value | obtained value\n"); + dolog ("format | %10d | %10d\n", req->fmt, obtfmt); + dolog ("channels | %10d | %10d\n", + req->nchannels, obt->nchannels); + dolog ("frequency | %10d | %10d\n", req->freq, obt->freq); + dolog ("============================================\n"); + dolog ("requested: buffer size %d period size %d\n", + req->buffer_size, req->period_size); + dolog ("obtained: samples %ld\n", obt->samples); +} + +static void alsa_set_threshold (snd_pcm_t *handle, snd_pcm_uframes_t threshold) +{ + int err; + snd_pcm_sw_params_t *sw_params; + + snd_pcm_sw_params_alloca (&sw_params); + + err = snd_pcm_sw_params_current (handle, sw_params); + if (err < 0) { + dolog ("Could not fully initialize DAC\n"); + alsa_logerr (err, "Failed to get current software parameters\n"); + return; + } + + err = snd_pcm_sw_params_set_start_threshold (handle, sw_params, threshold); + if (err < 0) { + dolog ("Could not fully initialize DAC\n"); + alsa_logerr (err, "Failed to set software threshold to %ld\n", + threshold); + return; + } + + err = snd_pcm_sw_params (handle, sw_params); + if (err < 0) { + dolog ("Could not fully initialize DAC\n"); + alsa_logerr (err, "Failed to set software parameters\n"); + return; + } +} + +static int alsa_open (int in, struct alsa_params_req *req, + struct alsa_params_obt *obt, snd_pcm_t **handlep) +{ + snd_pcm_t *handle; + snd_pcm_hw_params_t *hw_params; + int err; + int size_in_usec; + unsigned int freq, nchannels; + const char *pcm_name = in ? conf.pcm_name_in : conf.pcm_name_out; + snd_pcm_uframes_t obt_buffer_size; + const char *typ = in ? "ADC" : "DAC"; + snd_pcm_format_t obtfmt; + + freq = req->freq; + nchannels = req->nchannels; + size_in_usec = req->size_in_usec; + + snd_pcm_hw_params_alloca (&hw_params); + + err = snd_pcm_open ( + &handle, + pcm_name, + in ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK, + SND_PCM_NONBLOCK + ); + if (err < 0) { + alsa_logerr2 (err, typ, "Failed to open `%s':\n", pcm_name); + return -1; + } + + err = snd_pcm_hw_params_any (handle, hw_params); + if (err < 0) { + alsa_logerr2 (err, typ, "Failed to initialize hardware parameters\n"); + goto err; + } + + err = snd_pcm_hw_params_set_access ( + handle, + hw_params, + SND_PCM_ACCESS_RW_INTERLEAVED + ); + if (err < 0) { + alsa_logerr2 (err, typ, "Failed to set access type\n"); + goto err; + } + + err = snd_pcm_hw_params_set_format (handle, hw_params, req->fmt); + if (err < 0 && conf.verbose) { + alsa_logerr2 (err, typ, "Failed to set format %d\n", req->fmt); + } + + err = snd_pcm_hw_params_set_rate_near (handle, hw_params, &freq, 0); + if (err < 0) { + alsa_logerr2 (err, typ, "Failed to set frequency %d\n", req->freq); + goto err; + } + + err = snd_pcm_hw_params_set_channels_near ( + handle, + hw_params, + &nchannels + ); + if (err < 0) { + alsa_logerr2 (err, typ, "Failed to set number of channels %d\n", + req->nchannels); + goto err; + } + + if (nchannels != 1 && nchannels != 2) { + alsa_logerr2 (err, typ, + "Can not handle obtained number of channels %d\n", + nchannels); + goto err; + } + + if (req->buffer_size) { + unsigned long obt; + + if (size_in_usec) { + int dir = 0; + unsigned int btime = req->buffer_size; + + err = snd_pcm_hw_params_set_buffer_time_near ( + handle, + hw_params, + &btime, + &dir + ); + obt = btime; + } + else { + snd_pcm_uframes_t bsize = req->buffer_size; + + err = snd_pcm_hw_params_set_buffer_size_near ( + handle, + hw_params, + &bsize + ); + obt = bsize; + } + if (err < 0) { + alsa_logerr2 (err, typ, "Failed to set buffer %s to %d\n", + size_in_usec ? "time" : "size", req->buffer_size); + goto err; + } + + if ((req->override_mask & 2) && (obt - req->buffer_size)) + dolog ("Requested buffer %s %u was rejected, using %lu\n", + size_in_usec ? "time" : "size", req->buffer_size, obt); + } + + if (req->period_size) { + unsigned long obt; + + if (size_in_usec) { + int dir = 0; + unsigned int ptime = req->period_size; + + err = snd_pcm_hw_params_set_period_time_near ( + handle, + hw_params, + &ptime, + &dir + ); + obt = ptime; + } + else { + int dir = 0; + snd_pcm_uframes_t psize = req->period_size; + + err = snd_pcm_hw_params_set_period_size_near ( + handle, + hw_params, + &psize, + &dir + ); + obt = psize; + } + + if (err < 0) { + alsa_logerr2 (err, typ, "Failed to set period %s to %d\n", + size_in_usec ? "time" : "size", req->period_size); + goto err; + } + + if (((req->override_mask & 1) && (obt - req->period_size))) + dolog ("Requested period %s %u was rejected, using %lu\n", + size_in_usec ? "time" : "size", req->period_size, obt); + } + + err = snd_pcm_hw_params (handle, hw_params); + if (err < 0) { + alsa_logerr2 (err, typ, "Failed to apply audio parameters\n"); + goto err; + } + + err = snd_pcm_hw_params_get_buffer_size (hw_params, &obt_buffer_size); + if (err < 0) { + alsa_logerr2 (err, typ, "Failed to get buffer size\n"); + goto err; + } + + err = snd_pcm_hw_params_get_format (hw_params, &obtfmt); + if (err < 0) { + alsa_logerr2 (err, typ, "Failed to get format\n"); + goto err; + } + + if (alsa_to_audfmt (obtfmt, &obt->fmt, &obt->endianness)) { + dolog ("Invalid format was returned %d\n", obtfmt); + goto err; + } + + err = snd_pcm_prepare (handle); + if (err < 0) { + alsa_logerr2 (err, typ, "Could not prepare handle %p\n", handle); + goto err; + } + + if (!in && conf.threshold) { + snd_pcm_uframes_t threshold; + int bytes_per_sec; + + bytes_per_sec = freq << (nchannels == 2); + + switch (obt->fmt) { + case AUD_FMT_S8: + case AUD_FMT_U8: + break; + + case AUD_FMT_S16: + case AUD_FMT_U16: + bytes_per_sec <<= 1; + break; + + case AUD_FMT_S32: + case AUD_FMT_U32: + bytes_per_sec <<= 2; + break; + } + + threshold = (conf.threshold * bytes_per_sec) / 1000; + alsa_set_threshold (handle, threshold); + } + + obt->nchannels = nchannels; + obt->freq = freq; + obt->samples = obt_buffer_size; + + *handlep = handle; + + if (conf.verbose && + (obtfmt != req->fmt || + obt->nchannels != req->nchannels || + obt->freq != req->freq)) { + dolog ("Audio parameters for %s\n", typ); + alsa_dump_info (req, obt, obtfmt); + } + +#ifdef DEBUG + alsa_dump_info (req, obt, obtfmt); +#endif + return 0; + + err: + alsa_anal_close1 (&handle); + return -1; +} + +static snd_pcm_sframes_t alsa_get_avail (snd_pcm_t *handle) +{ + snd_pcm_sframes_t avail; + + avail = snd_pcm_avail_update (handle); + if (avail < 0) { + if (avail == -EPIPE) { + if (!alsa_recover (handle)) { + avail = snd_pcm_avail_update (handle); + } + } + + if (avail < 0) { + alsa_logerr (avail, + "Could not obtain number of available frames\n"); + return -1; + } + } + + return avail; +} + +static void alsa_write_pending (ALSAVoiceOut *alsa) +{ + HWVoiceOut *hw = &alsa->hw; + + while (alsa->pending) { + int left_till_end_samples = hw->samples - alsa->wpos; + int len = audio_MIN (alsa->pending, left_till_end_samples); + char *src = advance (alsa->pcm_buf, alsa->wpos << hw->info.shift); + + while (len) { + snd_pcm_sframes_t written; + + written = snd_pcm_writei (alsa->handle, src, len); + + if (written <= 0) { + switch (written) { + case 0: + if (conf.verbose) { + dolog ("Failed to write %d frames (wrote zero)\n", len); + } + return; + + case -EPIPE: + if (alsa_recover (alsa->handle)) { + alsa_logerr (written, "Failed to write %d frames\n", + len); + return; + } + if (conf.verbose) { + dolog ("Recovering from playback xrun\n"); + } + continue; + + case -ESTRPIPE: + /* stream is suspended and waiting for an + application recovery */ + if (alsa_resume (alsa->handle)) { + alsa_logerr (written, "Failed to write %d frames\n", + len); + return; + } + if (conf.verbose) { + dolog ("Resuming suspended output stream\n"); + } + continue; + + case -EAGAIN: + return; + + default: + alsa_logerr (written, "Failed to write %d frames from %p\n", + len, src); + return; + } + } + + alsa->wpos = (alsa->wpos + written) % hw->samples; + alsa->pending -= written; + len -= written; + } + } +} + +static int alsa_run_out (HWVoiceOut *hw, int live) +{ + ALSAVoiceOut *alsa = (ALSAVoiceOut *) hw; + int decr; + snd_pcm_sframes_t avail; + + avail = alsa_get_avail (alsa->handle); + if (avail < 0) { + dolog ("Could not get number of available playback frames\n"); + return 0; + } + + decr = audio_MIN (live, avail); + decr = audio_pcm_hw_clip_out (hw, alsa->pcm_buf, decr, alsa->pending); + alsa->pending += decr; + alsa_write_pending (alsa); + return decr; +} + +static void alsa_fini_out (HWVoiceOut *hw) +{ + ALSAVoiceOut *alsa = (ALSAVoiceOut *) hw; + + ldebug ("alsa_fini\n"); + alsa_anal_close (&alsa->handle, &alsa->pollhlp); + + if (alsa->pcm_buf) { + qemu_free (alsa->pcm_buf); + alsa->pcm_buf = NULL; + } +} + +static int alsa_init_out (HWVoiceOut *hw, struct audsettings *as) +{ + ALSAVoiceOut *alsa = (ALSAVoiceOut *) hw; + struct alsa_params_req req; + struct alsa_params_obt obt; + snd_pcm_t *handle; + struct audsettings obt_as; + + req.fmt = aud_to_alsafmt (as->fmt, as->endianness); + req.freq = as->freq; + req.nchannels = as->nchannels; + req.period_size = conf.period_size_out; + req.buffer_size = conf.buffer_size_out; + req.size_in_usec = conf.size_in_usec_out; + req.override_mask = + (conf.period_size_out_overridden ? 1 : 0) | + (conf.buffer_size_out_overridden ? 2 : 0); + + if (alsa_open (0, &req, &obt, &handle)) { + return -1; + } + + obt_as.freq = obt.freq; + obt_as.nchannels = obt.nchannels; + obt_as.fmt = obt.fmt; + obt_as.endianness = obt.endianness; + + audio_pcm_init_info (&hw->info, &obt_as); + hw->samples = obt.samples; + + alsa->pcm_buf = audio_calloc (AUDIO_FUNC, obt.samples, 1 << hw->info.shift); + if (!alsa->pcm_buf) { + dolog ("Could not allocate DAC buffer (%d samples, each %d bytes)\n", + hw->samples, 1 << hw->info.shift); + alsa_anal_close1 (&handle); + return -1; + } + + alsa->handle = handle; + return 0; +} + +#define VOICE_CTL_PAUSE 0 +#define VOICE_CTL_PREPARE 1 +#define VOICE_CTL_START 2 + +static int alsa_voice_ctl (snd_pcm_t *handle, const char *typ, int ctl) +{ + int err; + + if (ctl == VOICE_CTL_PAUSE) { + err = snd_pcm_drop (handle); + if (err < 0) { + alsa_logerr (err, "Could not stop %s\n", typ); + return -1; + } + } + else { + err = snd_pcm_prepare (handle); + if (err < 0) { + alsa_logerr (err, "Could not prepare handle for %s\n", typ); + return -1; + } + if (ctl == VOICE_CTL_START) { + err = snd_pcm_start(handle); + if (err < 0) { + alsa_logerr (err, "Could not start handle for %s\n", typ); + return -1; + } + } + } + + return 0; +} + +static int alsa_ctl_out (HWVoiceOut *hw, int cmd, ...) +{ + ALSAVoiceOut *alsa = (ALSAVoiceOut *) hw; + + switch (cmd) { + case VOICE_ENABLE: + { + va_list ap; + int poll_mode; + + va_start (ap, cmd); + poll_mode = va_arg (ap, int); + va_end (ap); + + ldebug ("enabling voice\n"); + if (poll_mode && alsa_poll_out (hw)) { + poll_mode = 0; + } + hw->poll_mode = poll_mode; + return alsa_voice_ctl (alsa->handle, "playback", VOICE_CTL_PREPARE); + } + + case VOICE_DISABLE: + ldebug ("disabling voice\n"); + if (hw->poll_mode) { + hw->poll_mode = 0; + alsa_fini_poll (&alsa->pollhlp); + } + return alsa_voice_ctl (alsa->handle, "playback", VOICE_CTL_PAUSE); + } + + return -1; +} + +static int alsa_init_in (HWVoiceIn *hw, struct audsettings *as) +{ + ALSAVoiceIn *alsa = (ALSAVoiceIn *) hw; + struct alsa_params_req req; + struct alsa_params_obt obt; + snd_pcm_t *handle; + struct audsettings obt_as; + + req.fmt = aud_to_alsafmt (as->fmt, as->endianness); + req.freq = as->freq; + req.nchannels = as->nchannels; + req.period_size = conf.period_size_in; + req.buffer_size = conf.buffer_size_in; + req.size_in_usec = conf.size_in_usec_in; + req.override_mask = + (conf.period_size_in_overridden ? 1 : 0) | + (conf.buffer_size_in_overridden ? 2 : 0); + + if (alsa_open (1, &req, &obt, &handle)) { + return -1; + } + + obt_as.freq = obt.freq; + obt_as.nchannels = obt.nchannels; + obt_as.fmt = obt.fmt; + obt_as.endianness = obt.endianness; + + audio_pcm_init_info (&hw->info, &obt_as); + hw->samples = obt.samples; + + alsa->pcm_buf = audio_calloc (AUDIO_FUNC, hw->samples, 1 << hw->info.shift); + if (!alsa->pcm_buf) { + dolog ("Could not allocate ADC buffer (%d samples, each %d bytes)\n", + hw->samples, 1 << hw->info.shift); + alsa_anal_close1 (&handle); + return -1; + } + + alsa->handle = handle; + return 0; +} + +static void alsa_fini_in (HWVoiceIn *hw) +{ + ALSAVoiceIn *alsa = (ALSAVoiceIn *) hw; + + alsa_anal_close (&alsa->handle, &alsa->pollhlp); + + if (alsa->pcm_buf) { + qemu_free (alsa->pcm_buf); + alsa->pcm_buf = NULL; + } +} + +static int alsa_run_in (HWVoiceIn *hw) +{ + ALSAVoiceIn *alsa = (ALSAVoiceIn *) hw; + int hwshift = hw->info.shift; + int i; + int live = audio_pcm_hw_get_live_in (hw); + int dead = hw->samples - live; + int decr; + struct { + int add; + int len; + } bufs[2] = { + { .add = hw->wpos, .len = 0 }, + { .add = 0, .len = 0 } + }; + snd_pcm_sframes_t avail; + snd_pcm_uframes_t read_samples = 0; + + if (!dead) { + return 0; + } + + avail = alsa_get_avail (alsa->handle); + if (avail < 0) { + dolog ("Could not get number of captured frames\n"); + return 0; + } + + if (!avail) { + snd_pcm_state_t state; + + state = snd_pcm_state (alsa->handle); + switch (state) { + case SND_PCM_STATE_PREPARED: + avail = hw->samples; + break; + case SND_PCM_STATE_SUSPENDED: + /* stream is suspended and waiting for an application recovery */ + if (alsa_resume (alsa->handle)) { + dolog ("Failed to resume suspended input stream\n"); + return 0; + } + if (conf.verbose) { + dolog ("Resuming suspended input stream\n"); + } + break; + default: + if (conf.verbose) { + dolog ("No frames available and ALSA state is %d\n", state); + } + return 0; + } + } + + decr = audio_MIN (dead, avail); + if (!decr) { + return 0; + } + + if (hw->wpos + decr > hw->samples) { + bufs[0].len = (hw->samples - hw->wpos); + bufs[1].len = (decr - (hw->samples - hw->wpos)); + } + else { + bufs[0].len = decr; + } + + for (i = 0; i < 2; ++i) { + void *src; + struct st_sample *dst; + snd_pcm_sframes_t nread; + snd_pcm_uframes_t len; + + len = bufs[i].len; + + src = advance (alsa->pcm_buf, bufs[i].add << hwshift); + dst = hw->conv_buf + bufs[i].add; + + while (len) { + nread = snd_pcm_readi (alsa->handle, src, len); + + if (nread <= 0) { + switch (nread) { + case 0: + if (conf.verbose) { + dolog ("Failed to read %ld frames (read zero)\n", len); + } + goto exit; + + case -EPIPE: + if (alsa_recover (alsa->handle)) { + alsa_logerr (nread, "Failed to read %ld frames\n", len); + goto exit; + } + if (conf.verbose) { + dolog ("Recovering from capture xrun\n"); + } + continue; + + case -EAGAIN: + goto exit; + + default: + alsa_logerr ( + nread, + "Failed to read %ld frames from %p\n", + len, + src + ); + goto exit; + } + } + + hw->conv (dst, src, nread); + + src = advance (src, nread << hwshift); + dst += nread; + + read_samples += nread; + len -= nread; + } + } + + exit: + hw->wpos = (hw->wpos + read_samples) % hw->samples; + return read_samples; +} + +static int alsa_read (SWVoiceIn *sw, void *buf, int size) +{ + return audio_pcm_sw_read (sw, buf, size); +} + +static int alsa_ctl_in (HWVoiceIn *hw, int cmd, ...) +{ + ALSAVoiceIn *alsa = (ALSAVoiceIn *) hw; + + switch (cmd) { + case VOICE_ENABLE: + { + va_list ap; + int poll_mode; + + va_start (ap, cmd); + poll_mode = va_arg (ap, int); + va_end (ap); + + ldebug ("enabling voice\n"); + if (poll_mode && alsa_poll_in (hw)) { + poll_mode = 0; + } + hw->poll_mode = poll_mode; + + return alsa_voice_ctl (alsa->handle, "capture", VOICE_CTL_START); + } + + case VOICE_DISABLE: + ldebug ("disabling voice\n"); + if (hw->poll_mode) { + hw->poll_mode = 0; + alsa_fini_poll (&alsa->pollhlp); + } + return alsa_voice_ctl (alsa->handle, "capture", VOICE_CTL_PAUSE); + } + + return -1; +} + +static void *alsa_audio_init (void) +{ + return &conf; +} + +static void alsa_audio_fini (void *opaque) +{ + (void) opaque; +} + +static struct audio_option alsa_options[] = { + { + .name = "DAC_SIZE_IN_USEC", + .tag = AUD_OPT_BOOL, + .valp = &conf.size_in_usec_out, + .descr = "DAC period/buffer size in microseconds (otherwise in frames)" + }, + { + .name = "DAC_PERIOD_SIZE", + .tag = AUD_OPT_INT, + .valp = &conf.period_size_out, + .descr = "DAC period size (0 to go with system default)", + .overriddenp = &conf.period_size_out_overridden + }, + { + .name = "DAC_BUFFER_SIZE", + .tag = AUD_OPT_INT, + .valp = &conf.buffer_size_out, + .descr = "DAC buffer size (0 to go with system default)", + .overriddenp = &conf.buffer_size_out_overridden + }, + { + .name = "ADC_SIZE_IN_USEC", + .tag = AUD_OPT_BOOL, + .valp = &conf.size_in_usec_in, + .descr = + "ADC period/buffer size in microseconds (otherwise in frames)" + }, + { + .name = "ADC_PERIOD_SIZE", + .tag = AUD_OPT_INT, + .valp = &conf.period_size_in, + .descr = "ADC period size (0 to go with system default)", + .overriddenp = &conf.period_size_in_overridden + }, + { + .name = "ADC_BUFFER_SIZE", + .tag = AUD_OPT_INT, + .valp = &conf.buffer_size_in, + .descr = "ADC buffer size (0 to go with system default)", + .overriddenp = &conf.buffer_size_in_overridden + }, + { + .name = "THRESHOLD", + .tag = AUD_OPT_INT, + .valp = &conf.threshold, + .descr = "(undocumented)" + }, + { + .name = "DAC_DEV", + .tag = AUD_OPT_STR, + .valp = &conf.pcm_name_out, + .descr = "DAC device name (for instance dmix)" + }, + { + .name = "ADC_DEV", + .tag = AUD_OPT_STR, + .valp = &conf.pcm_name_in, + .descr = "ADC device name" + }, + { + .name = "VERBOSE", + .tag = AUD_OPT_BOOL, + .valp = &conf.verbose, + .descr = "Behave in a more verbose way" + }, + { /* End of list */ } +}; + +static struct audio_pcm_ops alsa_pcm_ops = { + .init_out = alsa_init_out, + .fini_out = alsa_fini_out, + .run_out = alsa_run_out, + .write = alsa_write, + .ctl_out = alsa_ctl_out, + + .init_in = alsa_init_in, + .fini_in = alsa_fini_in, + .run_in = alsa_run_in, + .read = alsa_read, + .ctl_in = alsa_ctl_in, +}; + +struct audio_driver alsa_audio_driver = { + .name = "alsa", + .descr = "ALSA http://www.alsa-project.org", + .options = alsa_options, + .init = alsa_audio_init, + .fini = alsa_audio_fini, + .pcm_ops = &alsa_pcm_ops, + .can_be_default = 1, + .max_voices_out = INT_MAX, + .max_voices_in = INT_MAX, + .voice_size_out = sizeof (ALSAVoiceOut), + .voice_size_in = sizeof (ALSAVoiceIn) +}; diff --git a/audio/audio.c b/audio/audio.c new file mode 100644 index 0000000..1729c0b --- /dev/null +++ b/audio/audio.c @@ -0,0 +1,2066 @@ +/* + * QEMU Audio subsystem + * + * 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 "audio.h" +#include "monitor.h" +#include "qemu-timer.h" +#include "sysemu.h" + +#define AUDIO_CAP "audio" +#include "audio_int.h" + +/* #define DEBUG_PLIVE */ +/* #define DEBUG_LIVE */ +/* #define DEBUG_OUT */ +/* #define DEBUG_CAPTURE */ +/* #define DEBUG_POLL */ + +#define SW_NAME(sw) (sw)->name ? (sw)->name : "unknown" + + +/* Order of CONFIG_AUDIO_DRIVERS is import. + The 1st one is the one used by default, that is the reason + that we generate the list. +*/ +static struct audio_driver *drvtab[] = { +#ifdef CONFIG_SPICE + &spice_audio_driver, +#endif + CONFIG_AUDIO_DRIVERS + &no_audio_driver, + &wav_audio_driver +}; + +struct fixed_settings { + int enabled; + int nb_voices; + int greedy; + struct audsettings settings; +}; + +static struct { + struct fixed_settings fixed_out; + struct fixed_settings fixed_in; + union { + int hertz; + int64_t ticks; + } period; + int plive; + int log_to_monitor; + int try_poll_in; + int try_poll_out; +} conf = { + .fixed_out = { /* DAC fixed settings */ + .enabled = 1, + .nb_voices = 1, + .greedy = 1, + .settings = { + .freq = 44100, + .nchannels = 2, + .fmt = AUD_FMT_S16, + .endianness = AUDIO_HOST_ENDIANNESS, + } + }, + + .fixed_in = { /* ADC fixed settings */ + .enabled = 1, + .nb_voices = 1, + .greedy = 1, + .settings = { + .freq = 44100, + .nchannels = 2, + .fmt = AUD_FMT_S16, + .endianness = AUDIO_HOST_ENDIANNESS, + } + }, + + .period = { .hertz = 250 }, + .plive = 0, + .log_to_monitor = 0, + .try_poll_in = 1, + .try_poll_out = 1, +}; + +static AudioState glob_audio_state; + +const struct mixeng_volume nominal_volume = { + .mute = 0, +#ifdef FLOAT_MIXENG + .r = 1.0, + .l = 1.0, +#else + .r = 1ULL << 32, + .l = 1ULL << 32, +#endif +}; + +#ifdef AUDIO_IS_FLAWLESS_AND_NO_CHECKS_ARE_REQURIED +#error No its not +#else +static void audio_print_options (const char *prefix, + struct audio_option *opt); + +int audio_bug (const char *funcname, int cond) +{ + if (cond) { + static int shown; + + AUD_log (NULL, "A bug was just triggered in %s\n", funcname); + if (!shown) { + struct audio_driver *d; + + shown = 1; + AUD_log (NULL, "Save all your work and restart without audio\n"); + AUD_log (NULL, "Please send bug report to av1474@comtv.ru\n"); + AUD_log (NULL, "I am sorry\n"); + d = glob_audio_state.drv; + if (d) { + audio_print_options (d->name, d->options); + } + } + AUD_log (NULL, "Context:\n"); + +#if defined AUDIO_BREAKPOINT_ON_BUG +# if defined HOST_I386 +# if defined __GNUC__ + __asm__ ("int3"); +# elif defined _MSC_VER + _asm _emit 0xcc; +# else + abort (); +# endif +# else + abort (); +# endif +#endif + } + + return cond; +} +#endif + +static inline int audio_bits_to_index (int bits) +{ + switch (bits) { + case 8: + return 0; + + case 16: + return 1; + + case 32: + return 2; + + default: + audio_bug ("bits_to_index", 1); + AUD_log (NULL, "invalid bits %d\n", bits); + return 0; + } +} + +void *audio_calloc (const char *funcname, int nmemb, size_t size) +{ + int cond; + size_t len; + + len = nmemb * size; + cond = !nmemb || !size; + cond |= nmemb < 0; + cond |= len < size; + + if (audio_bug ("audio_calloc", cond)) { + AUD_log (NULL, "%s passed invalid arguments to audio_calloc\n", + funcname); + AUD_log (NULL, "nmemb=%d size=%zu (len=%zu)\n", nmemb, size, len); + return NULL; + } + + return qemu_mallocz (len); +} + +static char *audio_alloc_prefix (const char *s) +{ + const char qemu_prefix[] = "QEMU_"; + size_t len, i; + char *r, *u; + + if (!s) { + return NULL; + } + + len = strlen (s); + r = qemu_malloc (len + sizeof (qemu_prefix)); + + u = r + sizeof (qemu_prefix) - 1; + + pstrcpy (r, len + sizeof (qemu_prefix), qemu_prefix); + pstrcat (r, len + sizeof (qemu_prefix), s); + + for (i = 0; i < len; ++i) { + u[i] = qemu_toupper(u[i]); + } + + return r; +} + +static const char *audio_audfmt_to_string (audfmt_e fmt) +{ + switch (fmt) { + case AUD_FMT_U8: + return "U8"; + + case AUD_FMT_U16: + return "U16"; + + case AUD_FMT_S8: + return "S8"; + + case AUD_FMT_S16: + return "S16"; + + case AUD_FMT_U32: + return "U32"; + + case AUD_FMT_S32: + return "S32"; + } + + dolog ("Bogus audfmt %d returning S16\n", fmt); + return "S16"; +} + +static audfmt_e audio_string_to_audfmt (const char *s, audfmt_e defval, + int *defaultp) +{ + if (!strcasecmp (s, "u8")) { + *defaultp = 0; + return AUD_FMT_U8; + } + else if (!strcasecmp (s, "u16")) { + *defaultp = 0; + return AUD_FMT_U16; + } + else if (!strcasecmp (s, "u32")) { + *defaultp = 0; + return AUD_FMT_U32; + } + else if (!strcasecmp (s, "s8")) { + *defaultp = 0; + return AUD_FMT_S8; + } + else if (!strcasecmp (s, "s16")) { + *defaultp = 0; + return AUD_FMT_S16; + } + else if (!strcasecmp (s, "s32")) { + *defaultp = 0; + return AUD_FMT_S32; + } + else { + dolog ("Bogus audio format `%s' using %s\n", + s, audio_audfmt_to_string (defval)); + *defaultp = 1; + return defval; + } +} + +static audfmt_e audio_get_conf_fmt (const char *envname, + audfmt_e defval, + int *defaultp) +{ + const char *var = getenv (envname); + if (!var) { + *defaultp = 1; + return defval; + } + return audio_string_to_audfmt (var, defval, defaultp); +} + +static int audio_get_conf_int (const char *key, int defval, int *defaultp) +{ + int val; + char *strval; + + strval = getenv (key); + if (strval) { + *defaultp = 0; + val = atoi (strval); + return val; + } + else { + *defaultp = 1; + return defval; + } +} + +static const char *audio_get_conf_str (const char *key, + const char *defval, + int *defaultp) +{ + const char *val = getenv (key); + if (!val) { + *defaultp = 1; + return defval; + } + else { + *defaultp = 0; + return val; + } +} + +void AUD_vlog (const char *cap, const char *fmt, va_list ap) +{ + if (conf.log_to_monitor) { + if (cap) { + monitor_printf(default_mon, "%s: ", cap); + } + + monitor_vprintf(default_mon, fmt, ap); + } + else { + if (cap) { + fprintf (stderr, "%s: ", cap); + } + + vfprintf (stderr, fmt, ap); + } +} + +void AUD_log (const char *cap, const char *fmt, ...) +{ + va_list ap; + + va_start (ap, fmt); + AUD_vlog (cap, fmt, ap); + va_end (ap); +} + +static void audio_print_options (const char *prefix, + struct audio_option *opt) +{ + char *uprefix; + + if (!prefix) { + dolog ("No prefix specified\n"); + return; + } + + if (!opt) { + dolog ("No options\n"); + return; + } + + uprefix = audio_alloc_prefix (prefix); + + for (; opt->name; opt++) { + const char *state = "default"; + printf (" %s_%s: ", uprefix, opt->name); + + if (opt->overriddenp && *opt->overriddenp) { + state = "current"; + } + + switch (opt->tag) { + case AUD_OPT_BOOL: + { + int *intp = opt->valp; + printf ("boolean, %s = %d\n", state, *intp ? 1 : 0); + } + break; + + case AUD_OPT_INT: + { + int *intp = opt->valp; + printf ("integer, %s = %d\n", state, *intp); + } + break; + + case AUD_OPT_FMT: + { + audfmt_e *fmtp = opt->valp; + printf ( + "format, %s = %s, (one of: U8 S8 U16 S16 U32 S32)\n", + state, + audio_audfmt_to_string (*fmtp) + ); + } + break; + + case AUD_OPT_STR: + { + const char **strp = opt->valp; + printf ("string, %s = %s\n", + state, + *strp ? *strp : "(not set)"); + } + break; + + default: + printf ("???\n"); + dolog ("Bad value tag for option %s_%s %d\n", + uprefix, opt->name, opt->tag); + break; + } + printf (" %s\n", opt->descr); + } + + qemu_free (uprefix); +} + +static void audio_process_options (const char *prefix, + struct audio_option *opt) +{ + char *optname; + const char qemu_prefix[] = "QEMU_"; + size_t preflen, optlen; + + if (audio_bug (AUDIO_FUNC, !prefix)) { + dolog ("prefix = NULL\n"); + return; + } + + if (audio_bug (AUDIO_FUNC, !opt)) { + dolog ("opt = NULL\n"); + return; + } + + preflen = strlen (prefix); + + for (; opt->name; opt++) { + size_t len, i; + int def; + + if (!opt->valp) { + dolog ("Option value pointer for `%s' is not set\n", + opt->name); + continue; + } + + len = strlen (opt->name); + /* len of opt->name + len of prefix + size of qemu_prefix + * (includes trailing zero) + zero + underscore (on behalf of + * sizeof) */ + optlen = len + preflen + sizeof (qemu_prefix) + 1; + optname = qemu_malloc (optlen); + + pstrcpy (optname, optlen, qemu_prefix); + + /* copy while upper-casing, including trailing zero */ + for (i = 0; i <= preflen; ++i) { + optname[i + sizeof (qemu_prefix) - 1] = qemu_toupper(prefix[i]); + } + pstrcat (optname, optlen, "_"); + pstrcat (optname, optlen, opt->name); + + def = 1; + switch (opt->tag) { + case AUD_OPT_BOOL: + case AUD_OPT_INT: + { + int *intp = opt->valp; + *intp = audio_get_conf_int (optname, *intp, &def); + } + break; + + case AUD_OPT_FMT: + { + audfmt_e *fmtp = opt->valp; + *fmtp = audio_get_conf_fmt (optname, *fmtp, &def); + } + break; + + case AUD_OPT_STR: + { + const char **strp = opt->valp; + *strp = audio_get_conf_str (optname, *strp, &def); + } + break; + + default: + dolog ("Bad value tag for option `%s' - %d\n", + optname, opt->tag); + break; + } + + if (!opt->overriddenp) { + opt->overriddenp = &opt->overridden; + } + *opt->overriddenp = !def; + qemu_free (optname); + } +} + +static void audio_print_settings (struct audsettings *as) +{ + dolog ("frequency=%d nchannels=%d fmt=", as->freq, as->nchannels); + + switch (as->fmt) { + case AUD_FMT_S8: + AUD_log (NULL, "S8"); + break; + case AUD_FMT_U8: + AUD_log (NULL, "U8"); + break; + case AUD_FMT_S16: + AUD_log (NULL, "S16"); + break; + case AUD_FMT_U16: + AUD_log (NULL, "U16"); + break; + case AUD_FMT_S32: + AUD_log (NULL, "S32"); + break; + case AUD_FMT_U32: + AUD_log (NULL, "U32"); + break; + default: + AUD_log (NULL, "invalid(%d)", as->fmt); + break; + } + + AUD_log (NULL, " endianness="); + switch (as->endianness) { + case 0: + AUD_log (NULL, "little"); + break; + case 1: + AUD_log (NULL, "big"); + break; + default: + AUD_log (NULL, "invalid"); + break; + } + AUD_log (NULL, "\n"); +} + +static int audio_validate_settings (struct audsettings *as) +{ + int invalid; + + invalid = as->nchannels != 1 && as->nchannels != 2; + invalid |= as->endianness != 0 && as->endianness != 1; + + switch (as->fmt) { + case AUD_FMT_S8: + case AUD_FMT_U8: + case AUD_FMT_S16: + case AUD_FMT_U16: + case AUD_FMT_S32: + case AUD_FMT_U32: + break; + default: + invalid = 1; + break; + } + + invalid |= as->freq <= 0; + return invalid ? -1 : 0; +} + +static int audio_pcm_info_eq (struct audio_pcm_info *info, struct audsettings *as) +{ + int bits = 8, sign = 0; + + switch (as->fmt) { + case AUD_FMT_S8: + sign = 1; + case AUD_FMT_U8: + break; + + case AUD_FMT_S16: + sign = 1; + case AUD_FMT_U16: + bits = 16; + break; + + case AUD_FMT_S32: + sign = 1; + case AUD_FMT_U32: + bits = 32; + break; + } + return info->freq == as->freq + && info->nchannels == as->nchannels + && info->sign == sign + && info->bits == bits + && info->swap_endianness == (as->endianness != AUDIO_HOST_ENDIANNESS); +} + +void audio_pcm_init_info (struct audio_pcm_info *info, struct audsettings *as) +{ + int bits = 8, sign = 0, shift = 0; + + switch (as->fmt) { + case AUD_FMT_S8: + sign = 1; + case AUD_FMT_U8: + break; + + case AUD_FMT_S16: + sign = 1; + case AUD_FMT_U16: + bits = 16; + shift = 1; + break; + + case AUD_FMT_S32: + sign = 1; + case AUD_FMT_U32: + bits = 32; + shift = 2; + break; + } + + info->freq = as->freq; + info->bits = bits; + info->sign = sign; + info->nchannels = as->nchannels; + info->shift = (as->nchannels == 2) + shift; + info->align = (1 << info->shift) - 1; + info->bytes_per_second = info->freq << info->shift; + info->swap_endianness = (as->endianness != AUDIO_HOST_ENDIANNESS); +} + +void audio_pcm_info_clear_buf (struct audio_pcm_info *info, void *buf, int len) +{ + if (!len) { + return; + } + + if (info->sign) { + memset (buf, 0x00, len << info->shift); + } + else { + switch (info->bits) { + case 8: + memset (buf, 0x80, len << info->shift); + break; + + case 16: + { + int i; + uint16_t *p = buf; + int shift = info->nchannels - 1; + short s = INT16_MAX; + + if (info->swap_endianness) { + s = bswap16 (s); + } + + for (i = 0; i < len << shift; i++) { + p[i] = s; + } + } + break; + + case 32: + { + int i; + uint32_t *p = buf; + int shift = info->nchannels - 1; + int32_t s = INT32_MAX; + + if (info->swap_endianness) { + s = bswap32 (s); + } + + for (i = 0; i < len << shift; i++) { + p[i] = s; + } + } + break; + + default: + AUD_log (NULL, "audio_pcm_info_clear_buf: invalid bits %d\n", + info->bits); + break; + } + } +} + +/* + * Capture + */ +static void noop_conv (struct st_sample *dst, const void *src, int samples) +{ + (void) src; + (void) dst; + (void) samples; +} + +static CaptureVoiceOut *audio_pcm_capture_find_specific ( + struct audsettings *as + ) +{ + CaptureVoiceOut *cap; + AudioState *s = &glob_audio_state; + + for (cap = s->cap_head.lh_first; cap; cap = cap->entries.le_next) { + if (audio_pcm_info_eq (&cap->hw.info, as)) { + return cap; + } + } + return NULL; +} + +static void audio_notify_capture (CaptureVoiceOut *cap, audcnotification_e cmd) +{ + struct capture_callback *cb; + +#ifdef DEBUG_CAPTURE + dolog ("notification %d sent\n", cmd); +#endif + for (cb = cap->cb_head.lh_first; cb; cb = cb->entries.le_next) { + cb->ops.notify (cb->opaque, cmd); + } +} + +static void audio_capture_maybe_changed (CaptureVoiceOut *cap, int enabled) +{ + if (cap->hw.enabled != enabled) { + audcnotification_e cmd; + cap->hw.enabled = enabled; + cmd = enabled ? AUD_CNOTIFY_ENABLE : AUD_CNOTIFY_DISABLE; + audio_notify_capture (cap, cmd); + } +} + +static void audio_recalc_and_notify_capture (CaptureVoiceOut *cap) +{ + HWVoiceOut *hw = &cap->hw; + SWVoiceOut *sw; + int enabled = 0; + + for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) { + if (sw->active) { + enabled = 1; + break; + } + } + audio_capture_maybe_changed (cap, enabled); +} + +static void audio_detach_capture (HWVoiceOut *hw) +{ + SWVoiceCap *sc = hw->cap_head.lh_first; + + while (sc) { + SWVoiceCap *sc1 = sc->entries.le_next; + SWVoiceOut *sw = &sc->sw; + CaptureVoiceOut *cap = sc->cap; + int was_active = sw->active; + + if (sw->rate) { + st_rate_stop (sw->rate); + sw->rate = NULL; + } + + QLIST_REMOVE (sw, entries); + QLIST_REMOVE (sc, entries); + qemu_free (sc); + if (was_active) { + /* We have removed soft voice from the capture: + this might have changed the overall status of the capture + since this might have been the only active voice */ + audio_recalc_and_notify_capture (cap); + } + sc = sc1; + } +} + +static int audio_attach_capture (HWVoiceOut *hw) +{ + AudioState *s = &glob_audio_state; + CaptureVoiceOut *cap; + + audio_detach_capture (hw); + for (cap = s->cap_head.lh_first; cap; cap = cap->entries.le_next) { + SWVoiceCap *sc; + SWVoiceOut *sw; + HWVoiceOut *hw_cap = &cap->hw; + + sc = audio_calloc (AUDIO_FUNC, 1, sizeof (*sc)); + if (!sc) { + dolog ("Could not allocate soft capture voice (%zu bytes)\n", + sizeof (*sc)); + return -1; + } + + sc->cap = cap; + sw = &sc->sw; + sw->hw = hw_cap; + sw->info = hw->info; + sw->empty = 1; + sw->active = hw->enabled; + sw->conv = noop_conv; + sw->ratio = ((int64_t) hw_cap->info.freq << 32) / sw->info.freq; + sw->rate = st_rate_start (sw->info.freq, hw_cap->info.freq); + if (!sw->rate) { + dolog ("Could not start rate conversion for `%s'\n", SW_NAME (sw)); + qemu_free (sw); + return -1; + } + QLIST_INSERT_HEAD (&hw_cap->sw_head, sw, entries); + QLIST_INSERT_HEAD (&hw->cap_head, sc, entries); +#ifdef DEBUG_CAPTURE + asprintf (&sw->name, "for %p %d,%d,%d", + hw, sw->info.freq, sw->info.bits, sw->info.nchannels); + dolog ("Added %s active = %d\n", sw->name, sw->active); +#endif + if (sw->active) { + audio_capture_maybe_changed (cap, 1); + } + } + return 0; +} + +/* + * Hard voice (capture) + */ +static int audio_pcm_hw_find_min_in (HWVoiceIn *hw) +{ + SWVoiceIn *sw; + int m = hw->total_samples_captured; + + for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) { + if (sw->active) { + m = audio_MIN (m, sw->total_hw_samples_acquired); + } + } + return m; +} + +int audio_pcm_hw_get_live_in (HWVoiceIn *hw) +{ + int live = hw->total_samples_captured - audio_pcm_hw_find_min_in (hw); + if (audio_bug (AUDIO_FUNC, live < 0 || live > hw->samples)) { + dolog ("live=%d hw->samples=%d\n", live, hw->samples); + return 0; + } + return live; +} + +int audio_pcm_hw_clip_out (HWVoiceOut *hw, void *pcm_buf, + int live, int pending) +{ + int left = hw->samples - pending; + int len = audio_MIN (left, live); + int clipped = 0; + + while (len) { + struct st_sample *src = hw->mix_buf + hw->rpos; + uint8_t *dst = advance (pcm_buf, hw->rpos << hw->info.shift); + int samples_till_end_of_buf = hw->samples - hw->rpos; + int samples_to_clip = audio_MIN (len, samples_till_end_of_buf); + + hw->clip (dst, src, samples_to_clip); + + hw->rpos = (hw->rpos + samples_to_clip) % hw->samples; + len -= samples_to_clip; + clipped += samples_to_clip; + } + return clipped; +} + +/* + * Soft voice (capture) + */ +static int audio_pcm_sw_get_rpos_in (SWVoiceIn *sw) +{ + HWVoiceIn *hw = sw->hw; + int live = hw->total_samples_captured - sw->total_hw_samples_acquired; + int rpos; + + if (audio_bug (AUDIO_FUNC, live < 0 || live > hw->samples)) { + dolog ("live=%d hw->samples=%d\n", live, hw->samples); + return 0; + } + + rpos = hw->wpos - live; + if (rpos >= 0) { + return rpos; + } + else { + return hw->samples + rpos; + } +} + +int audio_pcm_sw_read (SWVoiceIn *sw, void *buf, int size) +{ + HWVoiceIn *hw = sw->hw; + int samples, live, ret = 0, swlim, isamp, osamp, rpos, total = 0; + struct st_sample *src, *dst = sw->buf; + + rpos = audio_pcm_sw_get_rpos_in (sw) % hw->samples; + + live = hw->total_samples_captured - sw->total_hw_samples_acquired; + if (audio_bug (AUDIO_FUNC, live < 0 || live > hw->samples)) { + dolog ("live_in=%d hw->samples=%d\n", live, hw->samples); + return 0; + } + + samples = size >> sw->info.shift; + if (!live) { + return 0; + } + + swlim = (live * sw->ratio) >> 32; + swlim = audio_MIN (swlim, samples); + + while (swlim) { + src = hw->conv_buf + rpos; + isamp = hw->wpos - rpos; + /* XXX: <= ? */ + if (isamp <= 0) { + isamp = hw->samples - rpos; + } + + if (!isamp) { + break; + } + osamp = swlim; + + if (audio_bug (AUDIO_FUNC, osamp < 0)) { + dolog ("osamp=%d\n", osamp); + return 0; + } + + st_rate_flow (sw->rate, src, dst, &isamp, &osamp); + swlim -= osamp; + rpos = (rpos + isamp) % hw->samples; + dst += osamp; + ret += osamp; + total += isamp; + } + + mixeng_volume (sw->buf, ret, &sw->vol); + + sw->clip (buf, sw->buf, ret); + sw->total_hw_samples_acquired += total; + return ret << sw->info.shift; +} + +/* + * Hard voice (playback) + */ +static int audio_pcm_hw_find_min_out (HWVoiceOut *hw, int *nb_livep) +{ + SWVoiceOut *sw; + int m = INT_MAX; + int nb_live = 0; + + for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) { + if (sw->active || !sw->empty) { + m = audio_MIN (m, sw->total_hw_samples_mixed); + nb_live += 1; + } + } + + *nb_livep = nb_live; + return m; +} + +static int audio_pcm_hw_get_live_out (HWVoiceOut *hw, int *nb_live) +{ + int smin; + int nb_live1; + + smin = audio_pcm_hw_find_min_out (hw, &nb_live1); + if (nb_live) { + *nb_live = nb_live1; + } + + if (nb_live1) { + int live = smin; + + if (audio_bug (AUDIO_FUNC, live < 0 || live > hw->samples)) { + dolog ("live=%d hw->samples=%d\n", live, hw->samples); + return 0; + } + return live; + } + return 0; +} + +/* + * Soft voice (playback) + */ +int audio_pcm_sw_write (SWVoiceOut *sw, void *buf, int size) +{ + int hwsamples, samples, isamp, osamp, wpos, live, dead, left, swlim, blck; + int ret = 0, pos = 0, total = 0; + + if (!sw) { + return size; + } + + hwsamples = sw->hw->samples; + + live = sw->total_hw_samples_mixed; + if (audio_bug (AUDIO_FUNC, live < 0 || live > hwsamples)){ + dolog ("live=%d hw->samples=%d\n", live, hwsamples); + return 0; + } + + if (live == hwsamples) { +#ifdef DEBUG_OUT + dolog ("%s is full %d\n", sw->name, live); +#endif + return 0; + } + + wpos = (sw->hw->rpos + live) % hwsamples; + samples = size >> sw->info.shift; + + dead = hwsamples - live; + swlim = ((int64_t) dead << 32) / sw->ratio; + swlim = audio_MIN (swlim, samples); + if (swlim) { + sw->conv (sw->buf, buf, swlim); + mixeng_volume (sw->buf, swlim, &sw->vol); + } + + while (swlim) { + dead = hwsamples - live; + left = hwsamples - wpos; + blck = audio_MIN (dead, left); + if (!blck) { + break; + } + isamp = swlim; + osamp = blck; + st_rate_flow_mix ( + sw->rate, + sw->buf + pos, + sw->hw->mix_buf + wpos, + &isamp, + &osamp + ); + ret += isamp; + swlim -= isamp; + pos += isamp; + live += osamp; + wpos = (wpos + osamp) % hwsamples; + total += osamp; + } + + sw->total_hw_samples_mixed += total; + sw->empty = sw->total_hw_samples_mixed == 0; + +#ifdef DEBUG_OUT + dolog ( + "%s: write size %d ret %d total sw %d\n", + SW_NAME (sw), + size >> sw->info.shift, + ret, + sw->total_hw_samples_mixed + ); +#endif + + return ret << sw->info.shift; +} + +#ifdef DEBUG_AUDIO +static void audio_pcm_print_info (const char *cap, struct audio_pcm_info *info) +{ + dolog ("%s: bits %d, sign %d, freq %d, nchan %d\n", + cap, info->bits, info->sign, info->freq, info->nchannels); +} +#endif + +#define DAC +#include "audio_template.h" +#undef DAC +#include "audio_template.h" + +/* + * Timer + */ +static int audio_is_timer_needed (void) +{ + HWVoiceIn *hwi = NULL; + HWVoiceOut *hwo = NULL; + + while ((hwo = audio_pcm_hw_find_any_enabled_out (hwo))) { + if (!hwo->poll_mode) return 1; + } + while ((hwi = audio_pcm_hw_find_any_enabled_in (hwi))) { + if (!hwi->poll_mode) return 1; + } + return 0; +} + +static void audio_reset_timer (AudioState *s) +{ + if (audio_is_timer_needed ()) { + qemu_mod_timer (s->ts, qemu_get_clock (vm_clock) + 1); + } + else { + qemu_del_timer (s->ts); + } +} + +static void audio_timer (void *opaque) +{ + audio_run ("timer"); + audio_reset_timer (opaque); +} + +/* + * Public API + */ +int AUD_write (SWVoiceOut *sw, void *buf, int size) +{ + int bytes; + + if (!sw) { + /* XXX: Consider options */ + return size; + } + + if (!sw->hw->enabled) { + dolog ("Writing to disabled voice %s\n", SW_NAME (sw)); + return 0; + } + + bytes = sw->hw->pcm_ops->write (sw, buf, size); + return bytes; +} + +int AUD_read (SWVoiceIn *sw, void *buf, int size) +{ + int bytes; + + if (!sw) { + /* XXX: Consider options */ + return size; + } + + if (!sw->hw->enabled) { + dolog ("Reading from disabled voice %s\n", SW_NAME (sw)); + return 0; + } + + bytes = sw->hw->pcm_ops->read (sw, buf, size); + return bytes; +} + +int AUD_get_buffer_size_out (SWVoiceOut *sw) +{ + return sw->hw->samples << sw->hw->info.shift; +} + +void AUD_set_active_out (SWVoiceOut *sw, int on) +{ + HWVoiceOut *hw; + + if (!sw) { + return; + } + + hw = sw->hw; + if (sw->active != on) { + AudioState *s = &glob_audio_state; + SWVoiceOut *temp_sw; + SWVoiceCap *sc; + + if (on) { + hw->pending_disable = 0; + if (!hw->enabled) { + hw->enabled = 1; + if (s->vm_running) { + hw->pcm_ops->ctl_out (hw, VOICE_ENABLE, conf.try_poll_out); + audio_reset_timer (s); + } + } + } + else { + if (hw->enabled) { + int nb_active = 0; + + for (temp_sw = hw->sw_head.lh_first; temp_sw; + temp_sw = temp_sw->entries.le_next) { + nb_active += temp_sw->active != 0; + } + + hw->pending_disable = nb_active == 1; + } + } + + for (sc = hw->cap_head.lh_first; sc; sc = sc->entries.le_next) { + sc->sw.active = hw->enabled; + if (hw->enabled) { + audio_capture_maybe_changed (sc->cap, 1); + } + } + sw->active = on; + } +} + +void AUD_set_active_in (SWVoiceIn *sw, int on) +{ + HWVoiceIn *hw; + + if (!sw) { + return; + } + + hw = sw->hw; + if (sw->active != on) { + AudioState *s = &glob_audio_state; + SWVoiceIn *temp_sw; + + if (on) { + if (!hw->enabled) { + hw->enabled = 1; + if (s->vm_running) { + hw->pcm_ops->ctl_in (hw, VOICE_ENABLE, conf.try_poll_in); + audio_reset_timer (s); + } + } + sw->total_hw_samples_acquired = hw->total_samples_captured; + } + else { + if (hw->enabled) { + int nb_active = 0; + + for (temp_sw = hw->sw_head.lh_first; temp_sw; + temp_sw = temp_sw->entries.le_next) { + nb_active += temp_sw->active != 0; + } + + if (nb_active == 1) { + hw->enabled = 0; + hw->pcm_ops->ctl_in (hw, VOICE_DISABLE); + } + } + } + sw->active = on; + } +} + +static int audio_get_avail (SWVoiceIn *sw) +{ + int live; + + if (!sw) { + return 0; + } + + live = sw->hw->total_samples_captured - sw->total_hw_samples_acquired; + if (audio_bug (AUDIO_FUNC, live < 0 || live > sw->hw->samples)) { + dolog ("live=%d sw->hw->samples=%d\n", live, sw->hw->samples); + return 0; + } + + ldebug ( + "%s: get_avail live %d ret %" PRId64 "\n", + SW_NAME (sw), + live, (((int64_t) live << 32) / sw->ratio) << sw->info.shift + ); + + return (((int64_t) live << 32) / sw->ratio) << sw->info.shift; +} + +static int audio_get_free (SWVoiceOut *sw) +{ + int live, dead; + + if (!sw) { + return 0; + } + + live = sw->total_hw_samples_mixed; + + if (audio_bug (AUDIO_FUNC, live < 0 || live > sw->hw->samples)) { + dolog ("live=%d sw->hw->samples=%d\n", live, sw->hw->samples); + return 0; + } + + dead = sw->hw->samples - live; + +#ifdef DEBUG_OUT + dolog ("%s: get_free live %d dead %d ret %" PRId64 "\n", + SW_NAME (sw), + live, dead, (((int64_t) dead << 32) / sw->ratio) << sw->info.shift); +#endif + + return (((int64_t) dead << 32) / sw->ratio) << sw->info.shift; +} + +static void audio_capture_mix_and_clear (HWVoiceOut *hw, int rpos, int samples) +{ + int n; + + if (hw->enabled) { + SWVoiceCap *sc; + + for (sc = hw->cap_head.lh_first; sc; sc = sc->entries.le_next) { + SWVoiceOut *sw = &sc->sw; + int rpos2 = rpos; + + n = samples; + while (n) { + int till_end_of_hw = hw->samples - rpos2; + int to_write = audio_MIN (till_end_of_hw, n); + int bytes = to_write << hw->info.shift; + int written; + + sw->buf = hw->mix_buf + rpos2; + written = audio_pcm_sw_write (sw, NULL, bytes); + if (written - bytes) { + dolog ("Could not mix %d bytes into a capture " + "buffer, mixed %d\n", + bytes, written); + break; + } + n -= to_write; + rpos2 = (rpos2 + to_write) % hw->samples; + } + } + } + + n = audio_MIN (samples, hw->samples - rpos); + mixeng_clear (hw->mix_buf + rpos, n); + mixeng_clear (hw->mix_buf, samples - n); +} + +static void audio_run_out (AudioState *s) +{ + HWVoiceOut *hw = NULL; + SWVoiceOut *sw; + + while ((hw = audio_pcm_hw_find_any_enabled_out (hw))) { + int played; + int live, free, nb_live, cleanup_required, prev_rpos; + + live = audio_pcm_hw_get_live_out (hw, &nb_live); + if (!nb_live) { + live = 0; + } + + if (audio_bug (AUDIO_FUNC, live < 0 || live > hw->samples)) { + dolog ("live=%d hw->samples=%d\n", live, hw->samples); + continue; + } + + if (hw->pending_disable && !nb_live) { + SWVoiceCap *sc; +#ifdef DEBUG_OUT + dolog ("Disabling voice\n"); +#endif + hw->enabled = 0; + hw->pending_disable = 0; + hw->pcm_ops->ctl_out (hw, VOICE_DISABLE); + for (sc = hw->cap_head.lh_first; sc; sc = sc->entries.le_next) { + sc->sw.active = 0; + audio_recalc_and_notify_capture (sc->cap); + } + continue; + } + + if (!live) { + for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) { + if (sw->active) { + free = audio_get_free (sw); + if (free > 0) { + sw->callback.fn (sw->callback.opaque, free); + } + } + } + continue; + } + + prev_rpos = hw->rpos; + played = hw->pcm_ops->run_out (hw, live); + if (audio_bug (AUDIO_FUNC, hw->rpos >= hw->samples)) { + dolog ("hw->rpos=%d hw->samples=%d played=%d\n", + hw->rpos, hw->samples, played); + hw->rpos = 0; + } + +#ifdef DEBUG_OUT + dolog ("played=%d\n", played); +#endif + + if (played) { + hw->ts_helper += played; + audio_capture_mix_and_clear (hw, prev_rpos, played); + } + + cleanup_required = 0; + for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) { + if (!sw->active && sw->empty) { + continue; + } + + if (audio_bug (AUDIO_FUNC, played > sw->total_hw_samples_mixed)) { + dolog ("played=%d sw->total_hw_samples_mixed=%d\n", + played, sw->total_hw_samples_mixed); + played = sw->total_hw_samples_mixed; + } + + sw->total_hw_samples_mixed -= played; + + if (!sw->total_hw_samples_mixed) { + sw->empty = 1; + cleanup_required |= !sw->active && !sw->callback.fn; + } + + if (sw->active) { + free = audio_get_free (sw); + if (free > 0) { + sw->callback.fn (sw->callback.opaque, free); + } + } + } + + if (cleanup_required) { + SWVoiceOut *sw1; + + sw = hw->sw_head.lh_first; + while (sw) { + sw1 = sw->entries.le_next; + if (!sw->active && !sw->callback.fn) { +#ifdef DEBUG_PLIVE + dolog ("Finishing with old voice\n"); +#endif + audio_close_out (sw); + } + sw = sw1; + } + } + } +} + +static void audio_run_in (AudioState *s) +{ + HWVoiceIn *hw = NULL; + + while ((hw = audio_pcm_hw_find_any_enabled_in (hw))) { + SWVoiceIn *sw; + int captured, min; + + captured = hw->pcm_ops->run_in (hw); + + min = audio_pcm_hw_find_min_in (hw); + hw->total_samples_captured += captured - min; + hw->ts_helper += captured; + + for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) { + sw->total_hw_samples_acquired -= min; + + if (sw->active) { + int avail; + + avail = audio_get_avail (sw); + if (avail > 0) { + sw->callback.fn (sw->callback.opaque, avail); + } + } + } + } +} + +static void audio_run_capture (AudioState *s) +{ + CaptureVoiceOut *cap; + + for (cap = s->cap_head.lh_first; cap; cap = cap->entries.le_next) { + int live, rpos, captured; + HWVoiceOut *hw = &cap->hw; + SWVoiceOut *sw; + + captured = live = audio_pcm_hw_get_live_out (hw, NULL); + rpos = hw->rpos; + while (live) { + int left = hw->samples - rpos; + int to_capture = audio_MIN (live, left); + struct st_sample *src; + struct capture_callback *cb; + + src = hw->mix_buf + rpos; + hw->clip (cap->buf, src, to_capture); + mixeng_clear (src, to_capture); + + for (cb = cap->cb_head.lh_first; cb; cb = cb->entries.le_next) { + cb->ops.capture (cb->opaque, cap->buf, + to_capture << hw->info.shift); + } + rpos = (rpos + to_capture) % hw->samples; + live -= to_capture; + } + hw->rpos = rpos; + + for (sw = hw->sw_head.lh_first; sw; sw = sw->entries.le_next) { + if (!sw->active && sw->empty) { + continue; + } + + if (audio_bug (AUDIO_FUNC, captured > sw->total_hw_samples_mixed)) { + dolog ("captured=%d sw->total_hw_samples_mixed=%d\n", + captured, sw->total_hw_samples_mixed); + captured = sw->total_hw_samples_mixed; + } + + sw->total_hw_samples_mixed -= captured; + sw->empty = sw->total_hw_samples_mixed == 0; + } + } +} + +void audio_run (const char *msg) +{ + AudioState *s = &glob_audio_state; + + audio_run_out (s); + audio_run_in (s); + audio_run_capture (s); +#ifdef DEBUG_POLL + { + static double prevtime; + double currtime; + struct timeval tv; + + if (gettimeofday (&tv, NULL)) { + perror ("audio_run: gettimeofday"); + return; + } + + currtime = tv.tv_sec + tv.tv_usec * 1e-6; + dolog ("Elapsed since last %s: %f\n", msg, currtime - prevtime); + prevtime = currtime; + } +#endif +} + +static struct audio_option audio_options[] = { + /* DAC */ + { + .name = "DAC_FIXED_SETTINGS", + .tag = AUD_OPT_BOOL, + .valp = &conf.fixed_out.enabled, + .descr = "Use fixed settings for host DAC" + }, + { + .name = "DAC_FIXED_FREQ", + .tag = AUD_OPT_INT, + .valp = &conf.fixed_out.settings.freq, + .descr = "Frequency for fixed host DAC" + }, + { + .name = "DAC_FIXED_FMT", + .tag = AUD_OPT_FMT, + .valp = &conf.fixed_out.settings.fmt, + .descr = "Format for fixed host DAC" + }, + { + .name = "DAC_FIXED_CHANNELS", + .tag = AUD_OPT_INT, + .valp = &conf.fixed_out.settings.nchannels, + .descr = "Number of channels for fixed DAC (1 - mono, 2 - stereo)" + }, + { + .name = "DAC_VOICES", + .tag = AUD_OPT_INT, + .valp = &conf.fixed_out.nb_voices, + .descr = "Number of voices for DAC" + }, + { + .name = "DAC_TRY_POLL", + .tag = AUD_OPT_BOOL, + .valp = &conf.try_poll_out, + .descr = "Attempt using poll mode for DAC" + }, + /* ADC */ + { + .name = "ADC_FIXED_SETTINGS", + .tag = AUD_OPT_BOOL, + .valp = &conf.fixed_in.enabled, + .descr = "Use fixed settings for host ADC" + }, + { + .name = "ADC_FIXED_FREQ", + .tag = AUD_OPT_INT, + .valp = &conf.fixed_in.settings.freq, + .descr = "Frequency for fixed host ADC" + }, + { + .name = "ADC_FIXED_FMT", + .tag = AUD_OPT_FMT, + .valp = &conf.fixed_in.settings.fmt, + .descr = "Format for fixed host ADC" + }, + { + .name = "ADC_FIXED_CHANNELS", + .tag = AUD_OPT_INT, + .valp = &conf.fixed_in.settings.nchannels, + .descr = "Number of channels for fixed ADC (1 - mono, 2 - stereo)" + }, + { + .name = "ADC_VOICES", + .tag = AUD_OPT_INT, + .valp = &conf.fixed_in.nb_voices, + .descr = "Number of voices for ADC" + }, + { + .name = "ADC_TRY_POLL", + .tag = AUD_OPT_BOOL, + .valp = &conf.try_poll_in, + .descr = "Attempt using poll mode for ADC" + }, + /* Misc */ + { + .name = "TIMER_PERIOD", + .tag = AUD_OPT_INT, + .valp = &conf.period.hertz, + .descr = "Timer period in HZ (0 - use lowest possible)" + }, + { + .name = "PLIVE", + .tag = AUD_OPT_BOOL, + .valp = &conf.plive, + .descr = "(undocumented)" + }, + { + .name = "LOG_TO_MONITOR", + .tag = AUD_OPT_BOOL, + .valp = &conf.log_to_monitor, + .descr = "Print logging messages to monitor instead of stderr" + }, + { /* End of list */ } +}; + +static void audio_pp_nb_voices (const char *typ, int nb) +{ + switch (nb) { + case 0: + printf ("Does not support %s\n", typ); + break; + case 1: + printf ("One %s voice\n", typ); + break; + case INT_MAX: + printf ("Theoretically supports many %s voices\n", typ); + break; + default: + printf ("Theoretically supports upto %d %s voices\n", nb, typ); + break; + } + +} + +void AUD_help (void) +{ + size_t i; + + audio_process_options ("AUDIO", audio_options); + for (i = 0; i < ARRAY_SIZE (drvtab); i++) { + struct audio_driver *d = drvtab[i]; + if (d->options) { + audio_process_options (d->name, d->options); + } + } + + printf ("Audio options:\n"); + audio_print_options ("AUDIO", audio_options); + printf ("\n"); + + printf ("Available drivers:\n"); + + for (i = 0; i < ARRAY_SIZE (drvtab); i++) { + struct audio_driver *d = drvtab[i]; + + printf ("Name: %s\n", d->name); + printf ("Description: %s\n", d->descr); + + audio_pp_nb_voices ("playback", d->max_voices_out); + audio_pp_nb_voices ("capture", d->max_voices_in); + + if (d->options) { + printf ("Options:\n"); + audio_print_options (d->name, d->options); + } + else { + printf ("No options\n"); + } + printf ("\n"); + } + + printf ( + "Options are settable through environment variables.\n" + "Example:\n" +#ifdef _WIN32 + " set QEMU_AUDIO_DRV=wav\n" + " set QEMU_WAV_PATH=c:\\tune.wav\n" +#else + " export QEMU_AUDIO_DRV=wav\n" + " export QEMU_WAV_PATH=$HOME/tune.wav\n" + "(for csh replace export with setenv in the above)\n" +#endif + " qemu ...\n\n" + ); +} + +static int audio_driver_init (AudioState *s, struct audio_driver *drv) +{ + if (drv->options) { + audio_process_options (drv->name, drv->options); + } + s->drv_opaque = drv->init (); + + if (s->drv_opaque) { + audio_init_nb_voices_out (drv); + audio_init_nb_voices_in (drv); + s->drv = drv; + return 0; + } + else { + dolog ("Could not init `%s' audio driver\n", drv->name); + return -1; + } +} + +static void audio_vm_change_state_handler (void *opaque, int running, + int reason) +{ + AudioState *s = opaque; + HWVoiceOut *hwo = NULL; + HWVoiceIn *hwi = NULL; + int op = running ? VOICE_ENABLE : VOICE_DISABLE; + + s->vm_running = running; + while ((hwo = audio_pcm_hw_find_any_enabled_out (hwo))) { + hwo->pcm_ops->ctl_out (hwo, op, conf.try_poll_out); + } + + while ((hwi = audio_pcm_hw_find_any_enabled_in (hwi))) { + hwi->pcm_ops->ctl_in (hwi, op, conf.try_poll_in); + } + audio_reset_timer (s); +} + +static void audio_atexit (void) +{ + AudioState *s = &glob_audio_state; + HWVoiceOut *hwo = NULL; + HWVoiceIn *hwi = NULL; + + while ((hwo = audio_pcm_hw_find_any_enabled_out (hwo))) { + SWVoiceCap *sc; + + hwo->pcm_ops->ctl_out (hwo, VOICE_DISABLE); + hwo->pcm_ops->fini_out (hwo); + + for (sc = hwo->cap_head.lh_first; sc; sc = sc->entries.le_next) { + CaptureVoiceOut *cap = sc->cap; + struct capture_callback *cb; + + for (cb = cap->cb_head.lh_first; cb; cb = cb->entries.le_next) { + cb->ops.destroy (cb->opaque); + } + } + } + + while ((hwi = audio_pcm_hw_find_any_enabled_in (hwi))) { + hwi->pcm_ops->ctl_in (hwi, VOICE_DISABLE); + hwi->pcm_ops->fini_in (hwi); + } + + if (s->drv) { + s->drv->fini (s->drv_opaque); + } +} + +static const VMStateDescription vmstate_audio = { + .name = "audio", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField []) { + VMSTATE_END_OF_LIST() + } +}; + +static void audio_init (void) +{ + size_t i; + int done = 0; + const char *drvname; + VMChangeStateEntry *e; + AudioState *s = &glob_audio_state; + + if (s->drv) { + return; + } + + QLIST_INIT (&s->hw_head_out); + QLIST_INIT (&s->hw_head_in); + QLIST_INIT (&s->cap_head); + atexit (audio_atexit); + + s->ts = qemu_new_timer (vm_clock, audio_timer, s); + if (!s->ts) { + hw_error("Could not create audio timer\n"); + } + + audio_process_options ("AUDIO", audio_options); + + s->nb_hw_voices_out = conf.fixed_out.nb_voices; + s->nb_hw_voices_in = conf.fixed_in.nb_voices; + + if (s->nb_hw_voices_out <= 0) { + dolog ("Bogus number of playback voices %d, setting to 1\n", + s->nb_hw_voices_out); + s->nb_hw_voices_out = 1; + } + + if (s->nb_hw_voices_in <= 0) { + dolog ("Bogus number of capture voices %d, setting to 0\n", + s->nb_hw_voices_in); + s->nb_hw_voices_in = 0; + } + + { + int def; + drvname = audio_get_conf_str ("QEMU_AUDIO_DRV", NULL, &def); + } + + if (drvname) { + int found = 0; + + for (i = 0; i < ARRAY_SIZE (drvtab); i++) { + if (!strcmp (drvname, drvtab[i]->name)) { + done = !audio_driver_init (s, drvtab[i]); + found = 1; + break; + } + } + + if (!found) { + dolog ("Unknown audio driver `%s'\n", drvname); + dolog ("Run with -audio-help to list available drivers\n"); + } + } + + if (!done) { + for (i = 0; !done && i < ARRAY_SIZE (drvtab); i++) { + if (drvtab[i]->can_be_default) { + done = !audio_driver_init (s, drvtab[i]); + } + } + } + + if (!done) { + done = !audio_driver_init (s, &no_audio_driver); + if (!done) { + hw_error("Could not initialize audio subsystem\n"); + } + else { + dolog ("warning: Using timer based audio emulation\n"); + } + } + + if (conf.period.hertz <= 0) { + if (conf.period.hertz < 0) { + dolog ("warning: Timer period is negative - %d " + "treating as zero\n", + conf.period.hertz); + } + conf.period.ticks = 1; + } else { + conf.period.ticks = + muldiv64 (1, get_ticks_per_sec (), conf.period.hertz); + } + + e = qemu_add_vm_change_state_handler (audio_vm_change_state_handler, s); + if (!e) { + dolog ("warning: Could not register change state handler\n" + "(Audio can continue looping even after stopping the VM)\n"); + } + + QLIST_INIT (&s->card_head); + vmstate_register (NULL, 0, &vmstate_audio, s); +} + +void AUD_register_card (const char *name, QEMUSoundCard *card) +{ + audio_init (); + card->name = qemu_strdup (name); + memset (&card->entries, 0, sizeof (card->entries)); + QLIST_INSERT_HEAD (&glob_audio_state.card_head, card, entries); +} + +void AUD_remove_card (QEMUSoundCard *card) +{ + QLIST_REMOVE (card, entries); + qemu_free (card->name); +} + + +CaptureVoiceOut *AUD_add_capture ( + struct audsettings *as, + struct audio_capture_ops *ops, + void *cb_opaque + ) +{ + AudioState *s = &glob_audio_state; + CaptureVoiceOut *cap; + struct capture_callback *cb; + + if (audio_validate_settings (as)) { + dolog ("Invalid settings were passed when trying to add capture\n"); + audio_print_settings (as); + goto err0; + } + + cb = audio_calloc (AUDIO_FUNC, 1, sizeof (*cb)); + if (!cb) { + dolog ("Could not allocate capture callback information, size %zu\n", + sizeof (*cb)); + goto err0; + } + cb->ops = *ops; + cb->opaque = cb_opaque; + + cap = audio_pcm_capture_find_specific (as); + if (cap) { + QLIST_INSERT_HEAD (&cap->cb_head, cb, entries); + return cap; + } + else { + HWVoiceOut *hw; + CaptureVoiceOut *cap; + + cap = audio_calloc (AUDIO_FUNC, 1, sizeof (*cap)); + if (!cap) { + dolog ("Could not allocate capture voice, size %zu\n", + sizeof (*cap)); + goto err1; + } + + hw = &cap->hw; + QLIST_INIT (&hw->sw_head); + QLIST_INIT (&cap->cb_head); + + /* XXX find a more elegant way */ + hw->samples = 4096 * 4; + hw->mix_buf = audio_calloc (AUDIO_FUNC, hw->samples, + sizeof (struct st_sample)); + if (!hw->mix_buf) { + dolog ("Could not allocate capture mix buffer (%d samples)\n", + hw->samples); + goto err2; + } + + audio_pcm_init_info (&hw->info, as); + + cap->buf = audio_calloc (AUDIO_FUNC, hw->samples, 1 << hw->info.shift); + if (!cap->buf) { + dolog ("Could not allocate capture buffer " + "(%d samples, each %d bytes)\n", + hw->samples, 1 << hw->info.shift); + goto err3; + } + + hw->clip = mixeng_clip + [hw->info.nchannels == 2] + [hw->info.sign] + [hw->info.swap_endianness] + [audio_bits_to_index (hw->info.bits)]; + + QLIST_INSERT_HEAD (&s->cap_head, cap, entries); + QLIST_INSERT_HEAD (&cap->cb_head, cb, entries); + + hw = NULL; + while ((hw = audio_pcm_hw_find_any_out (hw))) { + audio_attach_capture (hw); + } + return cap; + + err3: + qemu_free (cap->hw.mix_buf); + err2: + qemu_free (cap); + err1: + qemu_free (cb); + err0: + return NULL; + } +} + +void AUD_del_capture (CaptureVoiceOut *cap, void *cb_opaque) +{ + struct capture_callback *cb; + + for (cb = cap->cb_head.lh_first; cb; cb = cb->entries.le_next) { + if (cb->opaque == cb_opaque) { + cb->ops.destroy (cb_opaque); + QLIST_REMOVE (cb, entries); + qemu_free (cb); + + if (!cap->cb_head.lh_first) { + SWVoiceOut *sw = cap->hw.sw_head.lh_first, *sw1; + + while (sw) { + SWVoiceCap *sc = (SWVoiceCap *) sw; +#ifdef DEBUG_CAPTURE + dolog ("freeing %s\n", sw->name); +#endif + + sw1 = sw->entries.le_next; + if (sw->rate) { + st_rate_stop (sw->rate); + sw->rate = NULL; + } + QLIST_REMOVE (sw, entries); + QLIST_REMOVE (sc, entries); + qemu_free (sc); + sw = sw1; + } + QLIST_REMOVE (cap, entries); + qemu_free (cap); + } + return; + } + } +} + +void AUD_set_volume_out (SWVoiceOut *sw, int mute, uint8_t lvol, uint8_t rvol) +{ + if (sw) { + sw->vol.mute = mute; + sw->vol.l = nominal_volume.l * lvol / 255; + sw->vol.r = nominal_volume.r * rvol / 255; + } +} + +void AUD_set_volume_in (SWVoiceIn *sw, int mute, uint8_t lvol, uint8_t rvol) +{ + if (sw) { + sw->vol.mute = mute; + sw->vol.l = nominal_volume.l * lvol / 255; + sw->vol.r = nominal_volume.r * rvol / 255; + } +} diff --git a/audio/audio.h b/audio/audio.h new file mode 100644 index 0000000..a70fda9 --- /dev/null +++ b/audio/audio.h @@ -0,0 +1,166 @@ +/* + * QEMU Audio subsystem header + * + * 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. + */ +#ifndef QEMU_AUDIO_H +#define QEMU_AUDIO_H + +#include "config-host.h" +#include "qemu-queue.h" + +typedef void (*audio_callback_fn) (void *opaque, int avail); + +typedef enum { + AUD_FMT_U8, + AUD_FMT_S8, + AUD_FMT_U16, + AUD_FMT_S16, + AUD_FMT_U32, + AUD_FMT_S32 +} audfmt_e; + +#ifdef HOST_WORDS_BIGENDIAN +#define AUDIO_HOST_ENDIANNESS 1 +#else +#define AUDIO_HOST_ENDIANNESS 0 +#endif + +struct audsettings { + int freq; + int nchannels; + audfmt_e fmt; + int endianness; +}; + +typedef enum { + AUD_CNOTIFY_ENABLE, + AUD_CNOTIFY_DISABLE +} audcnotification_e; + +struct audio_capture_ops { + void (*notify) (void *opaque, audcnotification_e cmd); + void (*capture) (void *opaque, void *buf, int size); + void (*destroy) (void *opaque); +}; + +struct capture_ops { + void (*info) (void *opaque); + void (*destroy) (void *opaque); +}; + +typedef struct CaptureState { + void *opaque; + struct capture_ops ops; + QLIST_ENTRY (CaptureState) entries; +} CaptureState; + +typedef struct SWVoiceOut SWVoiceOut; +typedef struct CaptureVoiceOut CaptureVoiceOut; +typedef struct SWVoiceIn SWVoiceIn; + +typedef struct QEMUSoundCard { + char *name; + QLIST_ENTRY (QEMUSoundCard) entries; +} QEMUSoundCard; + +typedef struct QEMUAudioTimeStamp { + uint64_t old_ts; +} QEMUAudioTimeStamp; + +void AUD_vlog (const char *cap, const char *fmt, va_list ap) GCC_FMT_ATTR(2, 0); +void AUD_log (const char *cap, const char *fmt, ...) GCC_FMT_ATTR(2, 3); + +void AUD_help (void); +void AUD_register_card (const char *name, QEMUSoundCard *card); +void AUD_remove_card (QEMUSoundCard *card); +CaptureVoiceOut *AUD_add_capture ( + struct audsettings *as, + struct audio_capture_ops *ops, + void *opaque + ); +void AUD_del_capture (CaptureVoiceOut *cap, void *cb_opaque); + +SWVoiceOut *AUD_open_out ( + QEMUSoundCard *card, + SWVoiceOut *sw, + const char *name, + void *callback_opaque, + audio_callback_fn callback_fn, + struct audsettings *settings + ); + +void AUD_close_out (QEMUSoundCard *card, SWVoiceOut *sw); +int AUD_write (SWVoiceOut *sw, void *pcm_buf, int size); +int AUD_get_buffer_size_out (SWVoiceOut *sw); +void AUD_set_active_out (SWVoiceOut *sw, int on); +int AUD_is_active_out (SWVoiceOut *sw); + +void AUD_init_time_stamp_out (SWVoiceOut *sw, QEMUAudioTimeStamp *ts); +uint64_t AUD_get_elapsed_usec_out (SWVoiceOut *sw, QEMUAudioTimeStamp *ts); + +void AUD_set_volume_out (SWVoiceOut *sw, int mute, uint8_t lvol, uint8_t rvol); +void AUD_set_volume_in (SWVoiceIn *sw, int mute, uint8_t lvol, uint8_t rvol); + +SWVoiceIn *AUD_open_in ( + QEMUSoundCard *card, + SWVoiceIn *sw, + const char *name, + void *callback_opaque, + audio_callback_fn callback_fn, + struct audsettings *settings + ); + +void AUD_close_in (QEMUSoundCard *card, SWVoiceIn *sw); +int AUD_read (SWVoiceIn *sw, void *pcm_buf, int size); +void AUD_set_active_in (SWVoiceIn *sw, int on); +int AUD_is_active_in (SWVoiceIn *sw); + +void AUD_init_time_stamp_in (SWVoiceIn *sw, QEMUAudioTimeStamp *ts); +uint64_t AUD_get_elapsed_usec_in (SWVoiceIn *sw, QEMUAudioTimeStamp *ts); + +static inline void *advance (void *p, int incr) +{ + uint8_t *d = p; + return (d + incr); +} + +#ifdef __GNUC__ +#define audio_MIN(a, b) ( __extension__ ({ \ + __typeof (a) ta = a; \ + __typeof (b) tb = b; \ + ((ta)>(tb)?(tb):(ta)); \ +})) + +#define audio_MAX(a, b) ( __extension__ ({ \ + __typeof (a) ta = a; \ + __typeof (b) tb = b; \ + ((ta)<(tb)?(tb):(ta)); \ +})) +#else +#define audio_MIN(a, b) ((a)>(b)?(b):(a)) +#define audio_MAX(a, b) ((a)<(b)?(b):(a)) +#endif + +int wav_start_capture (CaptureState *s, const char *path, int freq, + int bits, int nchannels); + +#endif /* audio.h */ diff --git a/audio/audio_int.h b/audio/audio_int.h new file mode 100644 index 0000000..2003f8b --- /dev/null +++ b/audio/audio_int.h @@ -0,0 +1,282 @@ +/* + * QEMU Audio subsystem header + * + * 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. + */ +#ifndef QEMU_AUDIO_INT_H +#define QEMU_AUDIO_INT_H + +#ifdef CONFIG_COREAUDIO +#define FLOAT_MIXENG +/* #define RECIPROCAL */ +#endif +#include "mixeng.h" + +struct audio_pcm_ops; + +typedef enum { + AUD_OPT_INT, + AUD_OPT_FMT, + AUD_OPT_STR, + AUD_OPT_BOOL +} audio_option_tag_e; + +struct audio_option { + const char *name; + audio_option_tag_e tag; + void *valp; + const char *descr; + int *overriddenp; + int overridden; +}; + +struct audio_callback { + void *opaque; + audio_callback_fn fn; +}; + +struct audio_pcm_info { + int bits; + int sign; + int freq; + int nchannels; + int align; + int shift; + int bytes_per_second; + int swap_endianness; +}; + +typedef struct SWVoiceCap SWVoiceCap; + +typedef struct HWVoiceOut { + int enabled; + int poll_mode; + int pending_disable; + struct audio_pcm_info info; + + f_sample *clip; + + int rpos; + uint64_t ts_helper; + + struct st_sample *mix_buf; + + int samples; + QLIST_HEAD (sw_out_listhead, SWVoiceOut) sw_head; + QLIST_HEAD (sw_cap_listhead, SWVoiceCap) cap_head; + struct audio_pcm_ops *pcm_ops; + QLIST_ENTRY (HWVoiceOut) entries; +} HWVoiceOut; + +typedef struct HWVoiceIn { + int enabled; + int poll_mode; + struct audio_pcm_info info; + + t_sample *conv; + + int wpos; + int total_samples_captured; + uint64_t ts_helper; + + struct st_sample *conv_buf; + + int samples; + QLIST_HEAD (sw_in_listhead, SWVoiceIn) sw_head; + struct audio_pcm_ops *pcm_ops; + QLIST_ENTRY (HWVoiceIn) entries; +} HWVoiceIn; + +struct SWVoiceOut { + QEMUSoundCard *card; + struct audio_pcm_info info; + t_sample *conv; + int64_t ratio; + struct st_sample *buf; + void *rate; + int total_hw_samples_mixed; + int active; + int empty; + HWVoiceOut *hw; + char *name; + struct mixeng_volume vol; + struct audio_callback callback; + QLIST_ENTRY (SWVoiceOut) entries; +}; + +struct SWVoiceIn { + QEMUSoundCard *card; + int active; + struct audio_pcm_info info; + int64_t ratio; + void *rate; + int total_hw_samples_acquired; + struct st_sample *buf; + f_sample *clip; + HWVoiceIn *hw; + char *name; + struct mixeng_volume vol; + struct audio_callback callback; + QLIST_ENTRY (SWVoiceIn) entries; +}; + +struct audio_driver { + const char *name; + const char *descr; + struct audio_option *options; + void *(*init) (void); + void (*fini) (void *); + struct audio_pcm_ops *pcm_ops; + int can_be_default; + int max_voices_out; + int max_voices_in; + int voice_size_out; + int voice_size_in; +}; + +struct audio_pcm_ops { + int (*init_out)(HWVoiceOut *hw, struct audsettings *as); + void (*fini_out)(HWVoiceOut *hw); + int (*run_out) (HWVoiceOut *hw, int live); + int (*write) (SWVoiceOut *sw, void *buf, int size); + int (*ctl_out) (HWVoiceOut *hw, int cmd, ...); + + int (*init_in) (HWVoiceIn *hw, struct audsettings *as); + void (*fini_in) (HWVoiceIn *hw); + int (*run_in) (HWVoiceIn *hw); + int (*read) (SWVoiceIn *sw, void *buf, int size); + int (*ctl_in) (HWVoiceIn *hw, int cmd, ...); +}; + +struct capture_callback { + struct audio_capture_ops ops; + void *opaque; + QLIST_ENTRY (capture_callback) entries; +}; + +struct CaptureVoiceOut { + HWVoiceOut hw; + void *buf; + QLIST_HEAD (cb_listhead, capture_callback) cb_head; + QLIST_ENTRY (CaptureVoiceOut) entries; +}; + +struct SWVoiceCap { + SWVoiceOut sw; + CaptureVoiceOut *cap; + QLIST_ENTRY (SWVoiceCap) entries; +}; + +struct AudioState { + struct audio_driver *drv; + void *drv_opaque; + + QEMUTimer *ts; + QLIST_HEAD (card_listhead, QEMUSoundCard) card_head; + QLIST_HEAD (hw_in_listhead, HWVoiceIn) hw_head_in; + QLIST_HEAD (hw_out_listhead, HWVoiceOut) hw_head_out; + QLIST_HEAD (cap_listhead, CaptureVoiceOut) cap_head; + int nb_hw_voices_out; + int nb_hw_voices_in; + int vm_running; +}; + +extern struct audio_driver no_audio_driver; +extern struct audio_driver oss_audio_driver; +extern struct audio_driver sdl_audio_driver; +extern struct audio_driver wav_audio_driver; +extern struct audio_driver fmod_audio_driver; +extern struct audio_driver alsa_audio_driver; +extern struct audio_driver coreaudio_audio_driver; +extern struct audio_driver dsound_audio_driver; +extern struct audio_driver esd_audio_driver; +extern struct audio_driver pa_audio_driver; +extern struct audio_driver spice_audio_driver; +extern struct audio_driver winwave_audio_driver; +extern const struct mixeng_volume nominal_volume; + +void audio_pcm_init_info (struct audio_pcm_info *info, struct audsettings *as); +void audio_pcm_info_clear_buf (struct audio_pcm_info *info, void *buf, int len); + +int audio_pcm_sw_write (SWVoiceOut *sw, void *buf, int len); +int audio_pcm_hw_get_live_in (HWVoiceIn *hw); + +int audio_pcm_sw_read (SWVoiceIn *sw, void *buf, int len); + +int audio_pcm_hw_clip_out (HWVoiceOut *hw, void *pcm_buf, + int live, int pending); + +int audio_bug (const char *funcname, int cond); +void *audio_calloc (const char *funcname, int nmemb, size_t size); + +void audio_run (const char *msg); + +#define VOICE_ENABLE 1 +#define VOICE_DISABLE 2 + +static inline int audio_ring_dist (int dst, int src, int len) +{ + return (dst >= src) ? (dst - src) : (len - src + dst); +} + +static void GCC_ATTR dolog (const char *fmt, ...) +{ + va_list ap; + + va_start (ap, fmt); + AUD_vlog (AUDIO_CAP, fmt, ap); + va_end (ap); +} + +#ifdef DEBUG +static void GCC_ATTR ldebug (const char *fmt, ...) +{ + va_list ap; + + va_start (ap, fmt); + AUD_vlog (AUDIO_CAP, fmt, ap); + va_end (ap); +} +#else +#if defined NDEBUG && defined __GNUC__ +#define ldebug(...) +#elif defined NDEBUG && defined _MSC_VER +#define ldebug __noop +#else +static void GCC_ATTR ldebug (const char *fmt, ...) +{ + (void) fmt; +} +#endif +#endif + +#undef GCC_ATTR + +#define AUDIO_STRINGIFY_(n) #n +#define AUDIO_STRINGIFY(n) AUDIO_STRINGIFY_(n) + +#if defined _MSC_VER || defined __GNUC__ +#define AUDIO_FUNC __FUNCTION__ +#else +#define AUDIO_FUNC __FILE__ ":" AUDIO_STRINGIFY (__LINE__) +#endif + +#endif /* audio_int.h */ diff --git a/audio/audio_pt_int.c b/audio/audio_pt_int.c new file mode 100644 index 0000000..908c569 --- /dev/null +++ b/audio/audio_pt_int.c @@ -0,0 +1,175 @@ +#include "qemu-common.h" +#include "audio.h" + +#define AUDIO_CAP "audio-pt" + +#include "audio_int.h" +#include "audio_pt_int.h" + +#include + +static void GCC_FMT_ATTR(3, 4) logerr (struct audio_pt *pt, int err, + const char *fmt, ...) +{ + va_list ap; + + va_start (ap, fmt); + AUD_vlog (pt->drv, fmt, ap); + va_end (ap); + + AUD_log (NULL, "\n"); + AUD_log (pt->drv, "Reason: %s\n", strerror (err)); +} + +int audio_pt_init (struct audio_pt *p, void *(*func) (void *), + void *opaque, const char *drv, const char *cap) +{ + int err, err2; + const char *efunc; + sigset_t set, old_set; + + p->drv = drv; + + err = sigfillset (&set); + if (err) { + logerr (p, errno, "%s(%s): sigfillset failed", cap, AUDIO_FUNC); + return -1; + } + + err = pthread_mutex_init (&p->mutex, NULL); + if (err) { + efunc = "pthread_mutex_init"; + goto err0; + } + + err = pthread_cond_init (&p->cond, NULL); + if (err) { + efunc = "pthread_cond_init"; + goto err1; + } + + err = pthread_sigmask (SIG_BLOCK, &set, &old_set); + if (err) { + efunc = "pthread_sigmask"; + goto err2; + } + + err = pthread_create (&p->thread, NULL, func, opaque); + + err2 = pthread_sigmask (SIG_SETMASK, &old_set, NULL); + if (err2) { + logerr (p, err2, "%s(%s): pthread_sigmask (restore) failed", + cap, AUDIO_FUNC); + /* We have failed to restore original signal mask, all bets are off, + so terminate the process */ + exit (EXIT_FAILURE); + } + + if (err) { + efunc = "pthread_create"; + goto err2; + } + + return 0; + + err2: + err2 = pthread_cond_destroy (&p->cond); + if (err2) { + logerr (p, err2, "%s(%s): pthread_cond_destroy failed", cap, AUDIO_FUNC); + } + + err1: + err2 = pthread_mutex_destroy (&p->mutex); + if (err2) { + logerr (p, err2, "%s(%s): pthread_mutex_destroy failed", cap, AUDIO_FUNC); + } + + err0: + logerr (p, err, "%s(%s): %s failed", cap, AUDIO_FUNC, efunc); + return -1; +} + +int audio_pt_fini (struct audio_pt *p, const char *cap) +{ + int err, ret = 0; + + err = pthread_cond_destroy (&p->cond); + if (err) { + logerr (p, err, "%s(%s): pthread_cond_destroy failed", cap, AUDIO_FUNC); + ret = -1; + } + + err = pthread_mutex_destroy (&p->mutex); + if (err) { + logerr (p, err, "%s(%s): pthread_mutex_destroy failed", cap, AUDIO_FUNC); + ret = -1; + } + return ret; +} + +int audio_pt_lock (struct audio_pt *p, const char *cap) +{ + int err; + + err = pthread_mutex_lock (&p->mutex); + if (err) { + logerr (p, err, "%s(%s): pthread_mutex_lock failed", cap, AUDIO_FUNC); + return -1; + } + return 0; +} + +int audio_pt_unlock (struct audio_pt *p, const char *cap) +{ + int err; + + err = pthread_mutex_unlock (&p->mutex); + if (err) { + logerr (p, err, "%s(%s): pthread_mutex_unlock failed", cap, AUDIO_FUNC); + return -1; + } + return 0; +} + +int audio_pt_wait (struct audio_pt *p, const char *cap) +{ + int err; + + err = pthread_cond_wait (&p->cond, &p->mutex); + if (err) { + logerr (p, err, "%s(%s): pthread_cond_wait failed", cap, AUDIO_FUNC); + return -1; + } + return 0; +} + +int audio_pt_unlock_and_signal (struct audio_pt *p, const char *cap) +{ + int err; + + err = pthread_mutex_unlock (&p->mutex); + if (err) { + logerr (p, err, "%s(%s): pthread_mutex_unlock failed", cap, AUDIO_FUNC); + return -1; + } + err = pthread_cond_signal (&p->cond); + if (err) { + logerr (p, err, "%s(%s): pthread_cond_signal failed", cap, AUDIO_FUNC); + return -1; + } + return 0; +} + +int audio_pt_join (struct audio_pt *p, void **arg, const char *cap) +{ + int err; + void *ret; + + err = pthread_join (p->thread, &ret); + if (err) { + logerr (p, err, "%s(%s): pthread_join failed", cap, AUDIO_FUNC); + return -1; + } + *arg = ret; + return 0; +} diff --git a/audio/audio_pt_int.h b/audio/audio_pt_int.h new file mode 100644 index 0000000..0dfff76 --- /dev/null +++ b/audio/audio_pt_int.h @@ -0,0 +1,22 @@ +#ifndef QEMU_AUDIO_PT_INT_H +#define QEMU_AUDIO_PT_INT_H + +#include + +struct audio_pt { + const char *drv; + pthread_t thread; + pthread_cond_t cond; + pthread_mutex_t mutex; +}; + +int audio_pt_init (struct audio_pt *, void *(*) (void *), void *, + const char *, const char *); +int audio_pt_fini (struct audio_pt *, const char *); +int audio_pt_lock (struct audio_pt *, const char *); +int audio_pt_unlock (struct audio_pt *, const char *); +int audio_pt_wait (struct audio_pt *, const char *); +int audio_pt_unlock_and_signal (struct audio_pt *, const char *); +int audio_pt_join (struct audio_pt *, void **, const char *); + +#endif /* audio_pt_int.h */ diff --git a/audio/audio_template.h b/audio/audio_template.h new file mode 100644 index 0000000..fd4469e --- /dev/null +++ b/audio/audio_template.h @@ -0,0 +1,560 @@ +/* + * QEMU Audio subsystem header + * + * 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. + */ + +#ifdef DAC +#define NAME "playback" +#define HWBUF hw->mix_buf +#define TYPE out +#define HW HWVoiceOut +#define SW SWVoiceOut +#else +#define NAME "capture" +#define TYPE in +#define HW HWVoiceIn +#define SW SWVoiceIn +#define HWBUF hw->conv_buf +#endif + +static void glue (audio_init_nb_voices_, TYPE) (struct audio_driver *drv) +{ + AudioState *s = &glob_audio_state; + int max_voices = glue (drv->max_voices_, TYPE); + int voice_size = glue (drv->voice_size_, TYPE); + + if (glue (s->nb_hw_voices_, TYPE) > max_voices) { + if (!max_voices) { +#ifdef DAC + dolog ("Driver `%s' does not support " NAME "\n", drv->name); +#endif + } + else { + dolog ("Driver `%s' does not support %d " NAME " voices, max %d\n", + drv->name, + glue (s->nb_hw_voices_, TYPE), + max_voices); + } + glue (s->nb_hw_voices_, TYPE) = max_voices; + } + + if (audio_bug (AUDIO_FUNC, !voice_size && max_voices)) { + dolog ("drv=`%s' voice_size=0 max_voices=%d\n", + drv->name, max_voices); + glue (s->nb_hw_voices_, TYPE) = 0; + } + + if (audio_bug (AUDIO_FUNC, voice_size && !max_voices)) { + dolog ("drv=`%s' voice_size=%d max_voices=0\n", + drv->name, voice_size); + } +} + +static void glue (audio_pcm_hw_free_resources_, TYPE) (HW *hw) +{ + if (HWBUF) { + qemu_free (HWBUF); + } + + HWBUF = NULL; +} + +static int glue (audio_pcm_hw_alloc_resources_, TYPE) (HW *hw) +{ + HWBUF = audio_calloc (AUDIO_FUNC, hw->samples, sizeof (struct st_sample)); + if (!HWBUF) { + dolog ("Could not allocate " NAME " buffer (%d samples)\n", + hw->samples); + return -1; + } + + return 0; +} + +static void glue (audio_pcm_sw_free_resources_, TYPE) (SW *sw) +{ + if (sw->buf) { + qemu_free (sw->buf); + } + + if (sw->rate) { + st_rate_stop (sw->rate); + } + + sw->buf = NULL; + sw->rate = NULL; +} + +static int glue (audio_pcm_sw_alloc_resources_, TYPE) (SW *sw) +{ + int samples; + + samples = ((int64_t) sw->hw->samples << 32) / sw->ratio; + + sw->buf = audio_calloc (AUDIO_FUNC, samples, sizeof (struct st_sample)); + if (!sw->buf) { + dolog ("Could not allocate buffer for `%s' (%d samples)\n", + SW_NAME (sw), samples); + return -1; + } + +#ifdef DAC + sw->rate = st_rate_start (sw->info.freq, sw->hw->info.freq); +#else + sw->rate = st_rate_start (sw->hw->info.freq, sw->info.freq); +#endif + if (!sw->rate) { + qemu_free (sw->buf); + sw->buf = NULL; + return -1; + } + return 0; +} + +static int glue (audio_pcm_sw_init_, TYPE) ( + SW *sw, + HW *hw, + const char *name, + struct audsettings *as + ) +{ + int err; + + audio_pcm_init_info (&sw->info, as); + sw->hw = hw; + sw->active = 0; +#ifdef DAC + sw->ratio = ((int64_t) sw->hw->info.freq << 32) / sw->info.freq; + sw->total_hw_samples_mixed = 0; + sw->empty = 1; +#else + sw->ratio = ((int64_t) sw->info.freq << 32) / sw->hw->info.freq; +#endif + +#ifdef DAC + sw->conv = mixeng_conv +#else + sw->clip = mixeng_clip +#endif + [sw->info.nchannels == 2] + [sw->info.sign] + [sw->info.swap_endianness] + [audio_bits_to_index (sw->info.bits)]; + + sw->name = qemu_strdup (name); + err = glue (audio_pcm_sw_alloc_resources_, TYPE) (sw); + if (err) { + qemu_free (sw->name); + sw->name = NULL; + } + return err; +} + +static void glue (audio_pcm_sw_fini_, TYPE) (SW *sw) +{ + glue (audio_pcm_sw_free_resources_, TYPE) (sw); + if (sw->name) { + qemu_free (sw->name); + sw->name = NULL; + } +} + +static void glue (audio_pcm_hw_add_sw_, TYPE) (HW *hw, SW *sw) +{ + QLIST_INSERT_HEAD (&hw->sw_head, sw, entries); +} + +static void glue (audio_pcm_hw_del_sw_, TYPE) (SW *sw) +{ + QLIST_REMOVE (sw, entries); +} + +static void glue (audio_pcm_hw_gc_, TYPE) (HW **hwp) +{ + AudioState *s = &glob_audio_state; + HW *hw = *hwp; + + if (!hw->sw_head.lh_first) { +#ifdef DAC + audio_detach_capture (hw); +#endif + QLIST_REMOVE (hw, entries); + glue (s->nb_hw_voices_, TYPE) += 1; + glue (audio_pcm_hw_free_resources_ ,TYPE) (hw); + glue (hw->pcm_ops->fini_, TYPE) (hw); + qemu_free (hw); + *hwp = NULL; + } +} + +static HW *glue (audio_pcm_hw_find_any_, TYPE) (HW *hw) +{ + AudioState *s = &glob_audio_state; + return hw ? hw->entries.le_next : glue (s->hw_head_, TYPE).lh_first; +} + +static HW *glue (audio_pcm_hw_find_any_enabled_, TYPE) (HW *hw) +{ + while ((hw = glue (audio_pcm_hw_find_any_, TYPE) (hw))) { + if (hw->enabled) { + return hw; + } + } + return NULL; +} + +static HW *glue (audio_pcm_hw_find_specific_, TYPE) ( + HW *hw, + struct audsettings *as + ) +{ + while ((hw = glue (audio_pcm_hw_find_any_, TYPE) (hw))) { + if (audio_pcm_info_eq (&hw->info, as)) { + return hw; + } + } + return NULL; +} + +static HW *glue (audio_pcm_hw_add_new_, TYPE) (struct audsettings *as) +{ + HW *hw; + AudioState *s = &glob_audio_state; + struct audio_driver *drv = s->drv; + + if (!glue (s->nb_hw_voices_, TYPE)) { + return NULL; + } + + if (audio_bug (AUDIO_FUNC, !drv)) { + dolog ("No host audio driver\n"); + return NULL; + } + + if (audio_bug (AUDIO_FUNC, !drv->pcm_ops)) { + dolog ("Host audio driver without pcm_ops\n"); + return NULL; + } + + hw = audio_calloc (AUDIO_FUNC, 1, glue (drv->voice_size_, TYPE)); + if (!hw) { + dolog ("Can not allocate voice `%s' size %d\n", + drv->name, glue (drv->voice_size_, TYPE)); + return NULL; + } + + hw->pcm_ops = drv->pcm_ops; + QLIST_INIT (&hw->sw_head); +#ifdef DAC + QLIST_INIT (&hw->cap_head); +#endif + if (glue (hw->pcm_ops->init_, TYPE) (hw, as)) { + goto err0; + } + + if (audio_bug (AUDIO_FUNC, hw->samples <= 0)) { + dolog ("hw->samples=%d\n", hw->samples); + goto err1; + } + +#ifdef DAC + hw->clip = mixeng_clip +#else + hw->conv = mixeng_conv +#endif + [hw->info.nchannels == 2] + [hw->info.sign] + [hw->info.swap_endianness] + [audio_bits_to_index (hw->info.bits)]; + + if (glue (audio_pcm_hw_alloc_resources_, TYPE) (hw)) { + goto err1; + } + + QLIST_INSERT_HEAD (&s->glue (hw_head_, TYPE), hw, entries); + glue (s->nb_hw_voices_, TYPE) -= 1; +#ifdef DAC + audio_attach_capture (hw); +#endif + return hw; + + err1: + glue (hw->pcm_ops->fini_, TYPE) (hw); + err0: + qemu_free (hw); + return NULL; +} + +static HW *glue (audio_pcm_hw_add_, TYPE) (struct audsettings *as) +{ + HW *hw; + + if (glue (conf.fixed_, TYPE).enabled && glue (conf.fixed_, TYPE).greedy) { + hw = glue (audio_pcm_hw_add_new_, TYPE) (as); + if (hw) { + return hw; + } + } + + hw = glue (audio_pcm_hw_find_specific_, TYPE) (NULL, as); + if (hw) { + return hw; + } + + hw = glue (audio_pcm_hw_add_new_, TYPE) (as); + if (hw) { + return hw; + } + + return glue (audio_pcm_hw_find_any_, TYPE) (NULL); +} + +static SW *glue (audio_pcm_create_voice_pair_, TYPE) ( + const char *sw_name, + struct audsettings *as + ) +{ + SW *sw; + HW *hw; + struct audsettings hw_as; + + if (glue (conf.fixed_, TYPE).enabled) { + hw_as = glue (conf.fixed_, TYPE).settings; + } + else { + hw_as = *as; + } + + sw = audio_calloc (AUDIO_FUNC, 1, sizeof (*sw)); + if (!sw) { + dolog ("Could not allocate soft voice `%s' (%zu bytes)\n", + sw_name ? sw_name : "unknown", sizeof (*sw)); + goto err1; + } + + hw = glue (audio_pcm_hw_add_, TYPE) (&hw_as); + if (!hw) { + goto err2; + } + + glue (audio_pcm_hw_add_sw_, TYPE) (hw, sw); + + if (glue (audio_pcm_sw_init_, TYPE) (sw, hw, sw_name, as)) { + goto err3; + } + + return sw; + +err3: + glue (audio_pcm_hw_del_sw_, TYPE) (sw); + glue (audio_pcm_hw_gc_, TYPE) (&hw); +err2: + qemu_free (sw); +err1: + return NULL; +} + +static void glue (audio_close_, TYPE) (SW *sw) +{ + glue (audio_pcm_sw_fini_, TYPE) (sw); + glue (audio_pcm_hw_del_sw_, TYPE) (sw); + glue (audio_pcm_hw_gc_, TYPE) (&sw->hw); + qemu_free (sw); +} + +void glue (AUD_close_, TYPE) (QEMUSoundCard *card, SW *sw) +{ + if (sw) { + if (audio_bug (AUDIO_FUNC, !card)) { + dolog ("card=%p\n", card); + return; + } + + glue (audio_close_, TYPE) (sw); + } +} + +SW *glue (AUD_open_, TYPE) ( + QEMUSoundCard *card, + SW *sw, + const char *name, + void *callback_opaque , + audio_callback_fn callback_fn, + struct audsettings *as + ) +{ + AudioState *s = &glob_audio_state; +#ifdef DAC + int live = 0; + SW *old_sw = NULL; +#endif + + ldebug ("open %s, freq %d, nchannels %d, fmt %d\n", + name, as->freq, as->nchannels, as->fmt); + + if (audio_bug (AUDIO_FUNC, !card || !name || !callback_fn || !as)) { + dolog ("card=%p name=%p callback_fn=%p as=%p\n", + card, name, callback_fn, as); + goto fail; + } + + if (audio_bug (AUDIO_FUNC, audio_validate_settings (as))) { + audio_print_settings (as); + goto fail; + } + + if (audio_bug (AUDIO_FUNC, !s->drv)) { + dolog ("Can not open `%s' (no host audio driver)\n", name); + goto fail; + } + + if (sw && audio_pcm_info_eq (&sw->info, as)) { + return sw; + } + +#ifdef DAC + if (conf.plive && sw && (!sw->active && !sw->empty)) { + live = sw->total_hw_samples_mixed; + +#ifdef DEBUG_PLIVE + dolog ("Replacing voice %s with %d live samples\n", SW_NAME (sw), live); + dolog ("Old %s freq %d, bits %d, channels %d\n", + SW_NAME (sw), sw->info.freq, sw->info.bits, sw->info.nchannels); + dolog ("New %s freq %d, bits %d, channels %d\n", + name, + as->freq, + (as->fmt == AUD_FMT_S16 || as->fmt == AUD_FMT_U16) ? 16 : 8, + as->nchannels); +#endif + + if (live) { + old_sw = sw; + old_sw->callback.fn = NULL; + sw = NULL; + } + } +#endif + + if (!glue (conf.fixed_, TYPE).enabled && sw) { + glue (AUD_close_, TYPE) (card, sw); + sw = NULL; + } + + if (sw) { + HW *hw = sw->hw; + + if (!hw) { + dolog ("Internal logic error voice `%s' has no hardware store\n", + SW_NAME (sw)); + goto fail; + } + + glue (audio_pcm_sw_fini_, TYPE) (sw); + if (glue (audio_pcm_sw_init_, TYPE) (sw, hw, name, as)) { + goto fail; + } + } + else { + sw = glue (audio_pcm_create_voice_pair_, TYPE) (name, as); + if (!sw) { + dolog ("Failed to create voice `%s'\n", name); + return NULL; + } + } + + sw->card = card; + sw->vol = nominal_volume; + sw->callback.fn = callback_fn; + sw->callback.opaque = callback_opaque; + +#ifdef DAC + if (live) { + int mixed = + (live << old_sw->info.shift) + * old_sw->info.bytes_per_second + / sw->info.bytes_per_second; + +#ifdef DEBUG_PLIVE + dolog ("Silence will be mixed %d\n", mixed); +#endif + sw->total_hw_samples_mixed += mixed; + } +#endif + +#ifdef DEBUG_AUDIO + dolog ("%s\n", name); + audio_pcm_print_info ("hw", &sw->hw->info); + audio_pcm_print_info ("sw", &sw->info); +#endif + + return sw; + + fail: + glue (AUD_close_, TYPE) (card, sw); + return NULL; +} + +int glue (AUD_is_active_, TYPE) (SW *sw) +{ + return sw ? sw->active : 0; +} + +void glue (AUD_init_time_stamp_, TYPE) (SW *sw, QEMUAudioTimeStamp *ts) +{ + if (!sw) { + return; + } + + ts->old_ts = sw->hw->ts_helper; +} + +uint64_t glue (AUD_get_elapsed_usec_, TYPE) (SW *sw, QEMUAudioTimeStamp *ts) +{ + uint64_t delta, cur_ts, old_ts; + + if (!sw) { + return 0; + } + + cur_ts = sw->hw->ts_helper; + old_ts = ts->old_ts; + /* dolog ("cur %" PRId64 " old %" PRId64 "\n", cur_ts, old_ts); */ + + if (cur_ts >= old_ts) { + delta = cur_ts - old_ts; + } + else { + delta = UINT64_MAX - old_ts + cur_ts; + } + + if (!delta) { + return 0; + } + + return muldiv64 (delta, sw->hw->info.freq, 1000000); +} + +#undef TYPE +#undef HW +#undef SW +#undef HWBUF +#undef NAME diff --git a/audio/audio_win_int.c b/audio/audio_win_int.c new file mode 100644 index 0000000..5869052 --- /dev/null +++ b/audio/audio_win_int.c @@ -0,0 +1,108 @@ +/* public domain */ + +#include "qemu-common.h" +#include "audio.h" + +#define AUDIO_CAP "win-int" +#include +#include + +#include "audio.h" +#include "audio_int.h" +#include "audio_win_int.h" + +int waveformat_from_audio_settings (WAVEFORMATEX *wfx, + struct audsettings *as) +{ + memset (wfx, 0, sizeof (*wfx)); + + wfx->wFormatTag = WAVE_FORMAT_PCM; + wfx->nChannels = as->nchannels; + wfx->nSamplesPerSec = as->freq; + wfx->nAvgBytesPerSec = as->freq << (as->nchannels == 2); + wfx->nBlockAlign = 1 << (as->nchannels == 2); + wfx->cbSize = 0; + + switch (as->fmt) { + case AUD_FMT_S8: + case AUD_FMT_U8: + wfx->wBitsPerSample = 8; + break; + + case AUD_FMT_S16: + case AUD_FMT_U16: + wfx->wBitsPerSample = 16; + wfx->nAvgBytesPerSec <<= 1; + wfx->nBlockAlign <<= 1; + break; + + case AUD_FMT_S32: + case AUD_FMT_U32: + wfx->wBitsPerSample = 32; + wfx->nAvgBytesPerSec <<= 2; + wfx->nBlockAlign <<= 2; + break; + + default: + dolog ("Internal logic error: Bad audio format %d\n", as->freq); + return -1; + } + + return 0; +} + +int waveformat_to_audio_settings (WAVEFORMATEX *wfx, + struct audsettings *as) +{ + if (wfx->wFormatTag != WAVE_FORMAT_PCM) { + dolog ("Invalid wave format, tag is not PCM, but %d\n", + wfx->wFormatTag); + return -1; + } + + if (!wfx->nSamplesPerSec) { + dolog ("Invalid wave format, frequency is zero\n"); + return -1; + } + as->freq = wfx->nSamplesPerSec; + + switch (wfx->nChannels) { + case 1: + as->nchannels = 1; + break; + + case 2: + as->nchannels = 2; + break; + + default: + dolog ( + "Invalid wave format, number of channels is not 1 or 2, but %d\n", + wfx->nChannels + ); + return -1; + } + + switch (wfx->wBitsPerSample) { + case 8: + as->fmt = AUD_FMT_U8; + break; + + case 16: + as->fmt = AUD_FMT_S16; + break; + + case 32: + as->fmt = AUD_FMT_S32; + break; + + default: + dolog ("Invalid wave format, bits per sample is not " + "8, 16 or 32, but %d\n", + wfx->wBitsPerSample); + return -1; + } + + return 0; +} + diff --git a/audio/audio_win_int.h b/audio/audio_win_int.h new file mode 100644 index 0000000..fa5b3cb --- /dev/null +++ b/audio/audio_win_int.h @@ -0,0 +1,10 @@ +#ifndef AUDIO_WIN_INT_H +#define AUDIO_WIN_INT_H + +int waveformat_from_audio_settings (WAVEFORMATEX *wfx, + struct audsettings *as); + +int waveformat_to_audio_settings (WAVEFORMATEX *wfx, + struct audsettings *as); + +#endif /* AUDIO_WIN_INT_H */ diff --git a/audio/coreaudio.c b/audio/coreaudio.c new file mode 100644 index 0000000..0a26413 --- /dev/null +++ b/audio/coreaudio.c @@ -0,0 +1,549 @@ +/* + * QEMU OS X CoreAudio audio driver + * + * Copyright (c) 2005 Mike Kronenberg + * + * 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 +#include /* strerror */ +#include /* pthread_X */ + +#include "qemu-common.h" +#include "audio.h" + +#define AUDIO_CAP "coreaudio" +#include "audio_int.h" + +struct { + int buffer_frames; + int nbuffers; + int isAtexit; +} conf = { + .buffer_frames = 512, + .nbuffers = 4, + .isAtexit = 0 +}; + +typedef struct coreaudioVoiceOut { + HWVoiceOut hw; + pthread_mutex_t mutex; + int isAtexit; + AudioDeviceID outputDeviceID; + UInt32 audioDevicePropertyBufferFrameSize; + AudioStreamBasicDescription outputStreamBasicDescription; + int live; + int decr; + int rpos; +} coreaudioVoiceOut; + +static void coreaudio_logstatus (OSStatus status) +{ + char *str = "BUG"; + + switch(status) { + case kAudioHardwareNoError: + str = "kAudioHardwareNoError"; + break; + + case kAudioHardwareNotRunningError: + str = "kAudioHardwareNotRunningError"; + break; + + case kAudioHardwareUnspecifiedError: + str = "kAudioHardwareUnspecifiedError"; + break; + + case kAudioHardwareUnknownPropertyError: + str = "kAudioHardwareUnknownPropertyError"; + break; + + case kAudioHardwareBadPropertySizeError: + str = "kAudioHardwareBadPropertySizeError"; + break; + + case kAudioHardwareIllegalOperationError: + str = "kAudioHardwareIllegalOperationError"; + break; + + case kAudioHardwareBadDeviceError: + str = "kAudioHardwareBadDeviceError"; + break; + + case kAudioHardwareBadStreamError: + str = "kAudioHardwareBadStreamError"; + break; + + case kAudioHardwareUnsupportedOperationError: + str = "kAudioHardwareUnsupportedOperationError"; + break; + + case kAudioDeviceUnsupportedFormatError: + str = "kAudioDeviceUnsupportedFormatError"; + break; + + case kAudioDevicePermissionsError: + str = "kAudioDevicePermissionsError"; + break; + + default: + AUD_log (AUDIO_CAP, "Reason: status code %ld\n", status); + return; + } + + AUD_log (AUDIO_CAP, "Reason: %s\n", str); +} + +static void GCC_FMT_ATTR (2, 3) coreaudio_logerr ( + OSStatus status, + const char *fmt, + ... + ) +{ + va_list ap; + + va_start (ap, fmt); + AUD_log (AUDIO_CAP, fmt, ap); + va_end (ap); + + coreaudio_logstatus (status); +} + +static void GCC_FMT_ATTR (3, 4) coreaudio_logerr2 ( + OSStatus status, + const char *typ, + const char *fmt, + ... + ) +{ + va_list ap; + + AUD_log (AUDIO_CAP, "Could not initialize %s\n", typ); + + va_start (ap, fmt); + AUD_vlog (AUDIO_CAP, fmt, ap); + va_end (ap); + + coreaudio_logstatus (status); +} + +static inline UInt32 isPlaying (AudioDeviceID outputDeviceID) +{ + OSStatus status; + UInt32 result = 0; + UInt32 propertySize = sizeof(outputDeviceID); + status = AudioDeviceGetProperty( + outputDeviceID, 0, 0, + kAudioDevicePropertyDeviceIsRunning, &propertySize, &result); + if (status != kAudioHardwareNoError) { + coreaudio_logerr(status, + "Could not determine whether Device is playing\n"); + } + return result; +} + +static void coreaudio_atexit (void) +{ + conf.isAtexit = 1; +} + +static int coreaudio_lock (coreaudioVoiceOut *core, const char *fn_name) +{ + int err; + + err = pthread_mutex_lock (&core->mutex); + if (err) { + dolog ("Could not lock voice for %s\nReason: %s\n", + fn_name, strerror (err)); + return -1; + } + return 0; +} + +static int coreaudio_unlock (coreaudioVoiceOut *core, const char *fn_name) +{ + int err; + + err = pthread_mutex_unlock (&core->mutex); + if (err) { + dolog ("Could not unlock voice for %s\nReason: %s\n", + fn_name, strerror (err)); + return -1; + } + return 0; +} + +static int coreaudio_run_out (HWVoiceOut *hw, int live) +{ + int decr; + coreaudioVoiceOut *core = (coreaudioVoiceOut *) hw; + + if (coreaudio_lock (core, "coreaudio_run_out")) { + return 0; + } + + if (core->decr > live) { + ldebug ("core->decr %d live %d core->live %d\n", + core->decr, + live, + core->live); + } + + decr = audio_MIN (core->decr, live); + core->decr -= decr; + + core->live = live - decr; + hw->rpos = core->rpos; + + coreaudio_unlock (core, "coreaudio_run_out"); + return decr; +} + +/* callback to feed audiooutput buffer */ +static OSStatus audioDeviceIOProc( + AudioDeviceID inDevice, + const AudioTimeStamp* inNow, + const AudioBufferList* inInputData, + const AudioTimeStamp* inInputTime, + AudioBufferList* outOutputData, + const AudioTimeStamp* inOutputTime, + void* hwptr) +{ + UInt32 frame, frameCount; + float *out = outOutputData->mBuffers[0].mData; + HWVoiceOut *hw = hwptr; + coreaudioVoiceOut *core = (coreaudioVoiceOut *) hwptr; + int rpos, live; + struct st_sample *src; +#ifndef FLOAT_MIXENG +#ifdef RECIPROCAL + const float scale = 1.f / UINT_MAX; +#else + const float scale = UINT_MAX; +#endif +#endif + + if (coreaudio_lock (core, "audioDeviceIOProc")) { + inInputTime = 0; + return 0; + } + + frameCount = core->audioDevicePropertyBufferFrameSize; + live = core->live; + + /* if there are not enough samples, set signal and return */ + if (live < frameCount) { + inInputTime = 0; + coreaudio_unlock (core, "audioDeviceIOProc(empty)"); + return 0; + } + + rpos = core->rpos; + src = hw->mix_buf + rpos; + + /* fill buffer */ + for (frame = 0; frame < frameCount; frame++) { +#ifdef FLOAT_MIXENG + *out++ = src[frame].l; /* left channel */ + *out++ = src[frame].r; /* right channel */ +#else +#ifdef RECIPROCAL + *out++ = src[frame].l * scale; /* left channel */ + *out++ = src[frame].r * scale; /* right channel */ +#else + *out++ = src[frame].l / scale; /* left channel */ + *out++ = src[frame].r / scale; /* right channel */ +#endif +#endif + } + + rpos = (rpos + frameCount) % hw->samples; + core->decr += frameCount; + core->rpos = rpos; + + coreaudio_unlock (core, "audioDeviceIOProc"); + return 0; +} + +static int coreaudio_write (SWVoiceOut *sw, void *buf, int len) +{ + return audio_pcm_sw_write (sw, buf, len); +} + +static int coreaudio_init_out (HWVoiceOut *hw, struct audsettings *as) +{ + OSStatus status; + coreaudioVoiceOut *core = (coreaudioVoiceOut *) hw; + UInt32 propertySize; + int err; + const char *typ = "playback"; + AudioValueRange frameRange; + + /* create mutex */ + err = pthread_mutex_init(&core->mutex, NULL); + if (err) { + dolog("Could not create mutex\nReason: %s\n", strerror (err)); + return -1; + } + + audio_pcm_init_info (&hw->info, as); + + /* open default output device */ + propertySize = sizeof(core->outputDeviceID); + status = AudioHardwareGetProperty( + kAudioHardwarePropertyDefaultOutputDevice, + &propertySize, + &core->outputDeviceID); + if (status != kAudioHardwareNoError) { + coreaudio_logerr2 (status, typ, + "Could not get default output Device\n"); + return -1; + } + if (core->outputDeviceID == kAudioDeviceUnknown) { + dolog ("Could not initialize %s - Unknown Audiodevice\n", typ); + return -1; + } + + /* get minimum and maximum buffer frame sizes */ + propertySize = sizeof(frameRange); + status = AudioDeviceGetProperty( + core->outputDeviceID, + 0, + 0, + kAudioDevicePropertyBufferFrameSizeRange, + &propertySize, + &frameRange); + if (status != kAudioHardwareNoError) { + coreaudio_logerr2 (status, typ, + "Could not get device buffer frame range\n"); + return -1; + } + + if (frameRange.mMinimum > conf.buffer_frames) { + core->audioDevicePropertyBufferFrameSize = (UInt32) frameRange.mMinimum; + dolog ("warning: Upsizing Buffer Frames to %f\n", frameRange.mMinimum); + } + else if (frameRange.mMaximum < conf.buffer_frames) { + core->audioDevicePropertyBufferFrameSize = (UInt32) frameRange.mMaximum; + dolog ("warning: Downsizing Buffer Frames to %f\n", frameRange.mMaximum); + } + else { + core->audioDevicePropertyBufferFrameSize = conf.buffer_frames; + } + + /* set Buffer Frame Size */ + propertySize = sizeof(core->audioDevicePropertyBufferFrameSize); + status = AudioDeviceSetProperty( + core->outputDeviceID, + NULL, + 0, + false, + kAudioDevicePropertyBufferFrameSize, + propertySize, + &core->audioDevicePropertyBufferFrameSize); + if (status != kAudioHardwareNoError) { + coreaudio_logerr2 (status, typ, + "Could not set device buffer frame size %ld\n", + core->audioDevicePropertyBufferFrameSize); + return -1; + } + + /* get Buffer Frame Size */ + propertySize = sizeof(core->audioDevicePropertyBufferFrameSize); + status = AudioDeviceGetProperty( + core->outputDeviceID, + 0, + false, + kAudioDevicePropertyBufferFrameSize, + &propertySize, + &core->audioDevicePropertyBufferFrameSize); + if (status != kAudioHardwareNoError) { + coreaudio_logerr2 (status, typ, + "Could not get device buffer frame size\n"); + return -1; + } + hw->samples = conf.nbuffers * core->audioDevicePropertyBufferFrameSize; + + /* get StreamFormat */ + propertySize = sizeof(core->outputStreamBasicDescription); + status = AudioDeviceGetProperty( + core->outputDeviceID, + 0, + false, + kAudioDevicePropertyStreamFormat, + &propertySize, + &core->outputStreamBasicDescription); + if (status != kAudioHardwareNoError) { + coreaudio_logerr2 (status, typ, + "Could not get Device Stream properties\n"); + core->outputDeviceID = kAudioDeviceUnknown; + return -1; + } + + /* set Samplerate */ + core->outputStreamBasicDescription.mSampleRate = (Float64) as->freq; + propertySize = sizeof(core->outputStreamBasicDescription); + status = AudioDeviceSetProperty( + core->outputDeviceID, + 0, + 0, + 0, + kAudioDevicePropertyStreamFormat, + propertySize, + &core->outputStreamBasicDescription); + if (status != kAudioHardwareNoError) { + coreaudio_logerr2 (status, typ, "Could not set samplerate %d\n", + as->freq); + core->outputDeviceID = kAudioDeviceUnknown; + return -1; + } + + /* set Callback */ + status = AudioDeviceAddIOProc(core->outputDeviceID, audioDeviceIOProc, hw); + if (status != kAudioHardwareNoError) { + coreaudio_logerr2 (status, typ, "Could not set IOProc\n"); + core->outputDeviceID = kAudioDeviceUnknown; + return -1; + } + + /* start Playback */ + if (!isPlaying(core->outputDeviceID)) { + status = AudioDeviceStart(core->outputDeviceID, audioDeviceIOProc); + if (status != kAudioHardwareNoError) { + coreaudio_logerr2 (status, typ, "Could not start playback\n"); + AudioDeviceRemoveIOProc(core->outputDeviceID, audioDeviceIOProc); + core->outputDeviceID = kAudioDeviceUnknown; + return -1; + } + } + + return 0; +} + +static void coreaudio_fini_out (HWVoiceOut *hw) +{ + OSStatus status; + int err; + coreaudioVoiceOut *core = (coreaudioVoiceOut *) hw; + + if (!conf.isAtexit) { + /* stop playback */ + if (isPlaying(core->outputDeviceID)) { + status = AudioDeviceStop(core->outputDeviceID, audioDeviceIOProc); + if (status != kAudioHardwareNoError) { + coreaudio_logerr (status, "Could not stop playback\n"); + } + } + + /* remove callback */ + status = AudioDeviceRemoveIOProc(core->outputDeviceID, + audioDeviceIOProc); + if (status != kAudioHardwareNoError) { + coreaudio_logerr (status, "Could not remove IOProc\n"); + } + } + core->outputDeviceID = kAudioDeviceUnknown; + + /* destroy mutex */ + err = pthread_mutex_destroy(&core->mutex); + if (err) { + dolog("Could not destroy mutex\nReason: %s\n", strerror (err)); + } +} + +static int coreaudio_ctl_out (HWVoiceOut *hw, int cmd, ...) +{ + OSStatus status; + coreaudioVoiceOut *core = (coreaudioVoiceOut *) hw; + + switch (cmd) { + case VOICE_ENABLE: + /* start playback */ + if (!isPlaying(core->outputDeviceID)) { + status = AudioDeviceStart(core->outputDeviceID, audioDeviceIOProc); + if (status != kAudioHardwareNoError) { + coreaudio_logerr (status, "Could not resume playback\n"); + } + } + break; + + case VOICE_DISABLE: + /* stop playback */ + if (!conf.isAtexit) { + if (isPlaying(core->outputDeviceID)) { + status = AudioDeviceStop(core->outputDeviceID, audioDeviceIOProc); + if (status != kAudioHardwareNoError) { + coreaudio_logerr (status, "Could not pause playback\n"); + } + } + } + break; + } + return 0; +} + +static void *coreaudio_audio_init (void) +{ + atexit(coreaudio_atexit); + return &coreaudio_audio_init; +} + +static void coreaudio_audio_fini (void *opaque) +{ + (void) opaque; +} + +static struct audio_option coreaudio_options[] = { + { + .name = "BUFFER_SIZE", + .tag = AUD_OPT_INT, + .valp = &conf.buffer_frames, + .descr = "Size of the buffer in frames" + }, + { + .name = "BUFFER_COUNT", + .tag = AUD_OPT_INT, + .valp = &conf.nbuffers, + .descr = "Number of buffers" + }, + { /* End of list */ } +}; + +static struct audio_pcm_ops coreaudio_pcm_ops = { + .init_out = coreaudio_init_out, + .fini_out = coreaudio_fini_out, + .run_out = coreaudio_run_out, + .write = coreaudio_write, + .ctl_out = coreaudio_ctl_out +}; + +struct audio_driver coreaudio_audio_driver = { + .name = "coreaudio", + .descr = "CoreAudio http://developer.apple.com/audio/coreaudio.html", + .options = coreaudio_options, + .init = coreaudio_audio_init, + .fini = coreaudio_audio_fini, + .pcm_ops = &coreaudio_pcm_ops, + .can_be_default = 1, + .max_voices_out = 1, + .max_voices_in = 0, + .voice_size_out = sizeof (coreaudioVoiceOut), + .voice_size_in = 0 +}; diff --git a/audio/dsound_template.h b/audio/dsound_template.h new file mode 100644 index 0000000..8b37d16 --- /dev/null +++ b/audio/dsound_template.h @@ -0,0 +1,293 @@ +/* + * QEMU DirectSound audio driver header + * + * 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. + */ +#ifdef DSBTYPE_IN +#define NAME "capture buffer" +#define NAME2 "DirectSoundCapture" +#define TYPE in +#define IFACE IDirectSoundCaptureBuffer +#define BUFPTR LPDIRECTSOUNDCAPTUREBUFFER +#define FIELD dsound_capture_buffer +#define FIELD2 dsound_capture +#else +#define NAME "playback buffer" +#define NAME2 "DirectSound" +#define TYPE out +#define IFACE IDirectSoundBuffer +#define BUFPTR LPDIRECTSOUNDBUFFER +#define FIELD dsound_buffer +#define FIELD2 dsound +#endif + +static int glue (dsound_unlock_, TYPE) ( + BUFPTR buf, + LPVOID p1, + LPVOID p2, + DWORD blen1, + DWORD blen2 + ) +{ + HRESULT hr; + + hr = glue (IFACE, _Unlock) (buf, p1, blen1, p2, blen2); + if (FAILED (hr)) { + dsound_logerr (hr, "Could not unlock " NAME "\n"); + return -1; + } + + return 0; +} + +static int glue (dsound_lock_, TYPE) ( + BUFPTR buf, + struct audio_pcm_info *info, + DWORD pos, + DWORD len, + LPVOID *p1p, + LPVOID *p2p, + DWORD *blen1p, + DWORD *blen2p, + int entire + ) +{ + HRESULT hr; + int i; + LPVOID p1 = NULL, p2 = NULL; + DWORD blen1 = 0, blen2 = 0; + DWORD flag; + +#ifdef DSBTYPE_IN + flag = entire ? DSCBLOCK_ENTIREBUFFER : 0; +#else + flag = entire ? DSBLOCK_ENTIREBUFFER : 0; +#endif + for (i = 0; i < conf.lock_retries; ++i) { + hr = glue (IFACE, _Lock) ( + buf, + pos, + len, + &p1, + &blen1, + &p2, + &blen2, + flag + ); + + if (FAILED (hr)) { +#ifndef DSBTYPE_IN + if (hr == DSERR_BUFFERLOST) { + if (glue (dsound_restore_, TYPE) (buf)) { + dsound_logerr (hr, "Could not lock " NAME "\n"); + goto fail; + } + continue; + } +#endif + dsound_logerr (hr, "Could not lock " NAME "\n"); + goto fail; + } + + break; + } + + if (i == conf.lock_retries) { + dolog ("%d attempts to lock " NAME " failed\n", i); + goto fail; + } + + if ((p1 && (blen1 & info->align)) || (p2 && (blen2 & info->align))) { + dolog ("DirectSound returned misaligned buffer %ld %ld\n", + blen1, blen2); + glue (dsound_unlock_, TYPE) (buf, p1, p2, blen1, blen2); + goto fail; + } + + if (!p1 && blen1) { + dolog ("warning: !p1 && blen1=%ld\n", blen1); + blen1 = 0; + } + + if (!p2 && blen2) { + dolog ("warning: !p2 && blen2=%ld\n", blen2); + blen2 = 0; + } + + *p1p = p1; + *p2p = p2; + *blen1p = blen1; + *blen2p = blen2; + return 0; + + fail: + *p1p = NULL - 1; + *p2p = NULL - 1; + *blen1p = -1; + *blen2p = -1; + return -1; +} + +#ifdef DSBTYPE_IN +static void dsound_fini_in (HWVoiceIn *hw) +#else +static void dsound_fini_out (HWVoiceOut *hw) +#endif +{ + HRESULT hr; +#ifdef DSBTYPE_IN + DSoundVoiceIn *ds = (DSoundVoiceIn *) hw; +#else + DSoundVoiceOut *ds = (DSoundVoiceOut *) hw; +#endif + + if (ds->FIELD) { + hr = glue (IFACE, _Stop) (ds->FIELD); + if (FAILED (hr)) { + dsound_logerr (hr, "Could not stop " NAME "\n"); + } + + hr = glue (IFACE, _Release) (ds->FIELD); + if (FAILED (hr)) { + dsound_logerr (hr, "Could not release " NAME "\n"); + } + ds->FIELD = NULL; + } +} + +#ifdef DSBTYPE_IN +static int dsound_init_in (HWVoiceIn *hw, struct audsettings *as) +#else +static int dsound_init_out (HWVoiceOut *hw, struct audsettings *as) +#endif +{ + int err; + HRESULT hr; + dsound *s = &glob_dsound; + WAVEFORMATEX wfx; + struct audsettings obt_as; +#ifdef DSBTYPE_IN + const char *typ = "ADC"; + DSoundVoiceIn *ds = (DSoundVoiceIn *) hw; + DSCBUFFERDESC bd; + DSCBCAPS bc; +#else + const char *typ = "DAC"; + DSoundVoiceOut *ds = (DSoundVoiceOut *) hw; + DSBUFFERDESC bd; + DSBCAPS bc; +#endif + + if (!s->FIELD2) { + dolog ("Attempt to initialize voice without " NAME2 " object\n"); + return -1; + } + + err = waveformat_from_audio_settings (&wfx, as); + if (err) { + return -1; + } + + memset (&bd, 0, sizeof (bd)); + bd.dwSize = sizeof (bd); + bd.lpwfxFormat = &wfx; +#ifdef DSBTYPE_IN + bd.dwBufferBytes = conf.bufsize_in; + hr = IDirectSoundCapture_CreateCaptureBuffer ( + s->dsound_capture, + &bd, + &ds->dsound_capture_buffer, + NULL + ); +#else + bd.dwFlags = DSBCAPS_STICKYFOCUS | DSBCAPS_GETCURRENTPOSITION2; + bd.dwBufferBytes = conf.bufsize_out; + hr = IDirectSound_CreateSoundBuffer ( + s->dsound, + &bd, + &ds->dsound_buffer, + NULL + ); +#endif + + if (FAILED (hr)) { + dsound_logerr2 (hr, typ, "Could not create " NAME "\n"); + return -1; + } + + hr = glue (IFACE, _GetFormat) (ds->FIELD, &wfx, sizeof (wfx), NULL); + if (FAILED (hr)) { + dsound_logerr2 (hr, typ, "Could not get " NAME " format\n"); + goto fail0; + } + +#ifdef DEBUG_DSOUND + dolog (NAME "\n"); + print_wave_format (&wfx); +#endif + + memset (&bc, 0, sizeof (bc)); + bc.dwSize = sizeof (bc); + + hr = glue (IFACE, _GetCaps) (ds->FIELD, &bc); + if (FAILED (hr)) { + dsound_logerr2 (hr, typ, "Could not get " NAME " format\n"); + goto fail0; + } + + err = waveformat_to_audio_settings (&wfx, &obt_as); + if (err) { + goto fail0; + } + + ds->first_time = 1; + obt_as.endianness = 0; + audio_pcm_init_info (&hw->info, &obt_as); + + if (bc.dwBufferBytes & hw->info.align) { + dolog ( + "GetCaps returned misaligned buffer size %ld, alignment %d\n", + bc.dwBufferBytes, hw->info.align + 1 + ); + } + hw->samples = bc.dwBufferBytes >> hw->info.shift; + +#ifdef DEBUG_DSOUND + dolog ("caps %ld, desc %ld\n", + bc.dwBufferBytes, bd.dwBufferBytes); + + dolog ("bufsize %d, freq %d, chan %d, fmt %d\n", + hw->bufsize, settings.freq, settings.nchannels, settings.fmt); +#endif + return 0; + + fail0: + glue (dsound_fini_, TYPE) (hw); + return -1; +} + +#undef NAME +#undef NAME2 +#undef TYPE +#undef IFACE +#undef BUFPTR +#undef FIELD +#undef FIELD2 diff --git a/audio/dsoundaudio.c b/audio/dsoundaudio.c new file mode 100644 index 0000000..e2d89fd --- /dev/null +++ b/audio/dsoundaudio.c @@ -0,0 +1,1030 @@ +/* + * QEMU DirectSound audio driver + * + * 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. + */ + +/* + * SEAL 1.07 by Carlos 'pel' Hasan was used as documentation + */ + +#include "qemu-common.h" +#include "audio.h" + +#define AUDIO_CAP "dsound" +#include "audio_int.h" + +#include +#include +#include +#include + +#include "audio_win_int.h" + +/* #define DEBUG_DSOUND */ + +static struct { + int lock_retries; + int restore_retries; + int getstatus_retries; + int set_primary; + int bufsize_in; + int bufsize_out; + struct audsettings settings; + int latency_millis; +} conf = { + .lock_retries = 1, + .restore_retries = 1, + .getstatus_retries = 1, + .set_primary = 0, + .bufsize_in = 16384, + .bufsize_out = 16384, + .settings.freq = 44100, + .settings.nchannels = 2, + .settings.fmt = AUD_FMT_S16, + .latency_millis = 10 +}; + +typedef struct { + LPDIRECTSOUND dsound; + LPDIRECTSOUNDCAPTURE dsound_capture; + LPDIRECTSOUNDBUFFER dsound_primary_buffer; + struct audsettings settings; +} dsound; + +static dsound glob_dsound; + +typedef struct { + HWVoiceOut hw; + LPDIRECTSOUNDBUFFER dsound_buffer; + DWORD old_pos; + int first_time; +#ifdef DEBUG_DSOUND + DWORD old_ppos; + DWORD played; + DWORD mixed; +#endif +} DSoundVoiceOut; + +typedef struct { + HWVoiceIn hw; + int first_time; + LPDIRECTSOUNDCAPTUREBUFFER dsound_capture_buffer; +} DSoundVoiceIn; + +static void dsound_log_hresult (HRESULT hr) +{ + const char *str = "BUG"; + + switch (hr) { + case DS_OK: + str = "The method succeeded"; + break; +#ifdef DS_NO_VIRTUALIZATION + case DS_NO_VIRTUALIZATION: + str = "The buffer was created, but another 3D algorithm was substituted"; + break; +#endif +#ifdef DS_INCOMPLETE + case DS_INCOMPLETE: + str = "The method succeeded, but not all the optional effects were obtained"; + break; +#endif +#ifdef DSERR_ACCESSDENIED + case DSERR_ACCESSDENIED: + str = "The request failed because access was denied"; + break; +#endif +#ifdef DSERR_ALLOCATED + case DSERR_ALLOCATED: + str = "The request failed because resources, such as a priority level, were already in use by another caller"; + break; +#endif +#ifdef DSERR_ALREADYINITIALIZED + case DSERR_ALREADYINITIALIZED: + str = "The object is already initialized"; + break; +#endif +#ifdef DSERR_BADFORMAT + case DSERR_BADFORMAT: + str = "The specified wave format is not supported"; + break; +#endif +#ifdef DSERR_BADSENDBUFFERGUID + case DSERR_BADSENDBUFFERGUID: + str = "The GUID specified in an audiopath file does not match a valid mix-in buffer"; + break; +#endif +#ifdef DSERR_BUFFERLOST + case DSERR_BUFFERLOST: + str = "The buffer memory has been lost and must be restored"; + break; +#endif +#ifdef DSERR_BUFFERTOOSMALL + case DSERR_BUFFERTOOSMALL: + str = "The buffer size is not great enough to enable effects processing"; + break; +#endif +#ifdef DSERR_CONTROLUNAVAIL + case DSERR_CONTROLUNAVAIL: + str = "The buffer control (volume, pan, and so on) requested by the caller is not available. Controls must be specified when the buffer is created, using the dwFlags member of DSBUFFERDESC"; + break; +#endif +#ifdef DSERR_DS8_REQUIRED + case DSERR_DS8_REQUIRED: + str = "A DirectSound object of class CLSID_DirectSound8 or later is required for the requested functionality. For more information, see IDirectSound8 Interface"; + break; +#endif +#ifdef DSERR_FXUNAVAILABLE + case DSERR_FXUNAVAILABLE: + str = "The effects requested could not be found on the system, or they are in the wrong order or in the wrong location; for example, an effect expected in hardware was found in software"; + break; +#endif +#ifdef DSERR_GENERIC + case DSERR_GENERIC : + str = "An undetermined error occurred inside the DirectSound subsystem"; + break; +#endif +#ifdef DSERR_INVALIDCALL + case DSERR_INVALIDCALL: + str = "This function is not valid for the current state of this object"; + break; +#endif +#ifdef DSERR_INVALIDPARAM + case DSERR_INVALIDPARAM: + str = "An invalid parameter was passed to the returning function"; + break; +#endif +#ifdef DSERR_NOAGGREGATION + case DSERR_NOAGGREGATION: + str = "The object does not support aggregation"; + break; +#endif +#ifdef DSERR_NODRIVER + case DSERR_NODRIVER: + str = "No sound driver is available for use, or the given GUID is not a valid DirectSound device ID"; + break; +#endif +#ifdef DSERR_NOINTERFACE + case DSERR_NOINTERFACE: + str = "The requested COM interface is not available"; + break; +#endif +#ifdef DSERR_OBJECTNOTFOUND + case DSERR_OBJECTNOTFOUND: + str = "The requested object was not found"; + break; +#endif +#ifdef DSERR_OTHERAPPHASPRIO + case DSERR_OTHERAPPHASPRIO: + str = "Another application has a higher priority level, preventing this call from succeeding"; + break; +#endif +#ifdef DSERR_OUTOFMEMORY + case DSERR_OUTOFMEMORY: + str = "The DirectSound subsystem could not allocate sufficient memory to complete the caller's request"; + break; +#endif +#ifdef DSERR_PRIOLEVELNEEDED + case DSERR_PRIOLEVELNEEDED: + str = "A cooperative level of DSSCL_PRIORITY or higher is required"; + break; +#endif +#ifdef DSERR_SENDLOOP + case DSERR_SENDLOOP: + str = "A circular loop of send effects was detected"; + break; +#endif +#ifdef DSERR_UNINITIALIZED + case DSERR_UNINITIALIZED: + str = "The Initialize method has not been called or has not been called successfully before other methods were called"; + break; +#endif +#ifdef DSERR_UNSUPPORTED + case DSERR_UNSUPPORTED: + str = "The function called is not supported at this time"; + break; +#endif + default: + AUD_log (AUDIO_CAP, "Reason: Unknown (HRESULT %#lx)\n", hr); + return; + } + + AUD_log (AUDIO_CAP, "Reason: %s\n", str); +} + +static void GCC_FMT_ATTR (2, 3) dsound_logerr ( + HRESULT hr, + const char *fmt, + ... + ) +{ + va_list ap; + + va_start (ap, fmt); + AUD_vlog (AUDIO_CAP, fmt, ap); + va_end (ap); + + dsound_log_hresult (hr); +} + +static void GCC_FMT_ATTR (3, 4) dsound_logerr2 ( + HRESULT hr, + const char *typ, + const char *fmt, + ... + ) +{ + va_list ap; + + AUD_log (AUDIO_CAP, "Could not initialize %s\n", typ); + va_start (ap, fmt); + AUD_vlog (AUDIO_CAP, fmt, ap); + va_end (ap); + + dsound_log_hresult (hr); +} + +static DWORD millis_to_bytes (struct audio_pcm_info *info, DWORD millis) +{ + return (millis * info->bytes_per_second) / 1000; +} + +#ifdef DEBUG_DSOUND +static void print_wave_format (WAVEFORMATEX *wfx) +{ + dolog ("tag = %d\n", wfx->wFormatTag); + dolog ("nChannels = %d\n", wfx->nChannels); + dolog ("nSamplesPerSec = %ld\n", wfx->nSamplesPerSec); + dolog ("nAvgBytesPerSec = %ld\n", wfx->nAvgBytesPerSec); + dolog ("nBlockAlign = %d\n", wfx->nBlockAlign); + dolog ("wBitsPerSample = %d\n", wfx->wBitsPerSample); + dolog ("cbSize = %d\n", wfx->cbSize); +} +#endif + +static int dsound_restore_out (LPDIRECTSOUNDBUFFER dsb) +{ + HRESULT hr; + int i; + + for (i = 0; i < conf.restore_retries; ++i) { + hr = IDirectSoundBuffer_Restore (dsb); + + switch (hr) { + case DS_OK: + return 0; + + case DSERR_BUFFERLOST: + continue; + + default: + dsound_logerr (hr, "Could not restore playback buffer\n"); + return -1; + } + } + + dolog ("%d attempts to restore playback buffer failed\n", i); + return -1; +} + +#include "dsound_template.h" +#define DSBTYPE_IN +#include "dsound_template.h" +#undef DSBTYPE_IN + +static int dsound_get_status_out (LPDIRECTSOUNDBUFFER dsb, DWORD *statusp) +{ + HRESULT hr; + int i; + + for (i = 0; i < conf.getstatus_retries; ++i) { + hr = IDirectSoundBuffer_GetStatus (dsb, statusp); + if (FAILED (hr)) { + dsound_logerr (hr, "Could not get playback buffer status\n"); + return -1; + } + + if (*statusp & DSERR_BUFFERLOST) { + if (dsound_restore_out (dsb)) { + return -1; + } + continue; + } + break; + } + + return 0; +} + +static int dsound_get_status_in (LPDIRECTSOUNDCAPTUREBUFFER dscb, + DWORD *statusp) +{ + HRESULT hr; + + hr = IDirectSoundCaptureBuffer_GetStatus (dscb, statusp); + if (FAILED (hr)) { + dsound_logerr (hr, "Could not get capture buffer status\n"); + return -1; + } + + return 0; +} + +static void dsound_write_sample (HWVoiceOut *hw, uint8_t *dst, int dst_len) +{ + int src_len1 = dst_len; + int src_len2 = 0; + int pos = hw->rpos + dst_len; + struct st_sample *src1 = hw->mix_buf + hw->rpos; + struct st_sample *src2 = NULL; + + if (pos > hw->samples) { + src_len1 = hw->samples - hw->rpos; + src2 = hw->mix_buf; + src_len2 = dst_len - src_len1; + pos = src_len2; + } + + if (src_len1) { + hw->clip (dst, src1, src_len1); + } + + if (src_len2) { + dst = advance (dst, src_len1 << hw->info.shift); + hw->clip (dst, src2, src_len2); + } + + hw->rpos = pos % hw->samples; +} + +static void dsound_clear_sample (HWVoiceOut *hw, LPDIRECTSOUNDBUFFER dsb) +{ + int err; + LPVOID p1, p2; + DWORD blen1, blen2, len1, len2; + + err = dsound_lock_out ( + dsb, + &hw->info, + 0, + hw->samples << hw->info.shift, + &p1, &p2, + &blen1, &blen2, + 1 + ); + if (err) { + return; + } + + len1 = blen1 >> hw->info.shift; + len2 = blen2 >> hw->info.shift; + +#ifdef DEBUG_DSOUND + dolog ("clear %p,%ld,%ld %p,%ld,%ld\n", + p1, blen1, len1, + p2, blen2, len2); +#endif + + if (p1 && len1) { + audio_pcm_info_clear_buf (&hw->info, p1, len1); + } + + if (p2 && len2) { + audio_pcm_info_clear_buf (&hw->info, p2, len2); + } + + dsound_unlock_out (dsb, p1, p2, blen1, blen2); +} + +static void dsound_close (dsound *s) +{ + HRESULT hr; + + if (s->dsound_primary_buffer) { + hr = IDirectSoundBuffer_Release (s->dsound_primary_buffer); + if (FAILED (hr)) { + dsound_logerr (hr, "Could not release primary buffer\n"); + } + s->dsound_primary_buffer = NULL; + } +} + +static int dsound_open (dsound *s) +{ + int err; + HRESULT hr; + WAVEFORMATEX wfx; + DSBUFFERDESC dsbd; + HWND hwnd; + + hwnd = GetForegroundWindow (); + hr = IDirectSound_SetCooperativeLevel ( + s->dsound, + hwnd, + DSSCL_PRIORITY + ); + + if (FAILED (hr)) { + dsound_logerr (hr, "Could not set cooperative level for window %p\n", + hwnd); + return -1; + } + + if (!conf.set_primary) { + return 0; + } + + err = waveformat_from_audio_settings (&wfx, &conf.settings); + if (err) { + return -1; + } + + memset (&dsbd, 0, sizeof (dsbd)); + dsbd.dwSize = sizeof (dsbd); + dsbd.dwFlags = DSBCAPS_PRIMARYBUFFER; + dsbd.dwBufferBytes = 0; + dsbd.lpwfxFormat = NULL; + + hr = IDirectSound_CreateSoundBuffer ( + s->dsound, + &dsbd, + &s->dsound_primary_buffer, + NULL + ); + if (FAILED (hr)) { + dsound_logerr (hr, "Could not create primary playback buffer\n"); + return -1; + } + + hr = IDirectSoundBuffer_SetFormat (s->dsound_primary_buffer, &wfx); + if (FAILED (hr)) { + dsound_logerr (hr, "Could not set primary playback buffer format\n"); + } + + hr = IDirectSoundBuffer_GetFormat ( + s->dsound_primary_buffer, + &wfx, + sizeof (wfx), + NULL + ); + if (FAILED (hr)) { + dsound_logerr (hr, "Could not get primary playback buffer format\n"); + goto fail0; + } + +#ifdef DEBUG_DSOUND + dolog ("Primary\n"); + print_wave_format (&wfx); +#endif + + err = waveformat_to_audio_settings (&wfx, &s->settings); + if (err) { + goto fail0; + } + + return 0; + + fail0: + dsound_close (s); + return -1; +} + +static int dsound_ctl_out (HWVoiceOut *hw, int cmd, ...) +{ + HRESULT hr; + DWORD status; + DSoundVoiceOut *ds = (DSoundVoiceOut *) hw; + LPDIRECTSOUNDBUFFER dsb = ds->dsound_buffer; + + if (!dsb) { + dolog ("Attempt to control voice without a buffer\n"); + return 0; + } + + switch (cmd) { + case VOICE_ENABLE: + if (dsound_get_status_out (dsb, &status)) { + return -1; + } + + if (status & DSBSTATUS_PLAYING) { + dolog ("warning: Voice is already playing\n"); + return 0; + } + + dsound_clear_sample (hw, dsb); + + hr = IDirectSoundBuffer_Play (dsb, 0, 0, DSBPLAY_LOOPING); + if (FAILED (hr)) { + dsound_logerr (hr, "Could not start playing buffer\n"); + return -1; + } + break; + + case VOICE_DISABLE: + if (dsound_get_status_out (dsb, &status)) { + return -1; + } + + if (status & DSBSTATUS_PLAYING) { + hr = IDirectSoundBuffer_Stop (dsb); + if (FAILED (hr)) { + dsound_logerr (hr, "Could not stop playing buffer\n"); + return -1; + } + } + else { + dolog ("warning: Voice is not playing\n"); + } + break; + } + return 0; +} + +static int dsound_write (SWVoiceOut *sw, void *buf, int len) +{ + return audio_pcm_sw_write (sw, buf, len); +} + +static int dsound_run_out (HWVoiceOut *hw, int live) +{ + int err; + HRESULT hr; + DSoundVoiceOut *ds = (DSoundVoiceOut *) hw; + LPDIRECTSOUNDBUFFER dsb = ds->dsound_buffer; + int len, hwshift; + DWORD blen1, blen2; + DWORD len1, len2; + DWORD decr; + DWORD wpos, ppos, old_pos; + LPVOID p1, p2; + int bufsize; + + if (!dsb) { + dolog ("Attempt to run empty with playback buffer\n"); + return 0; + } + + hwshift = hw->info.shift; + bufsize = hw->samples << hwshift; + + hr = IDirectSoundBuffer_GetCurrentPosition ( + dsb, + &ppos, + ds->first_time ? &wpos : NULL + ); + if (FAILED (hr)) { + dsound_logerr (hr, "Could not get playback buffer position\n"); + return 0; + } + + len = live << hwshift; + + if (ds->first_time) { + if (conf.latency_millis) { + DWORD cur_blat; + + cur_blat = audio_ring_dist (wpos, ppos, bufsize); + ds->first_time = 0; + old_pos = wpos; + old_pos += + millis_to_bytes (&hw->info, conf.latency_millis) - cur_blat; + old_pos %= bufsize; + old_pos &= ~hw->info.align; + } + else { + old_pos = wpos; + } +#ifdef DEBUG_DSOUND + ds->played = 0; + ds->mixed = 0; +#endif + } + else { + if (ds->old_pos == ppos) { +#ifdef DEBUG_DSOUND + dolog ("old_pos == ppos\n"); +#endif + return 0; + } + +#ifdef DEBUG_DSOUND + ds->played += audio_ring_dist (ds->old_pos, ppos, hw->bufsize); +#endif + old_pos = ds->old_pos; + } + + if ((old_pos < ppos) && ((old_pos + len) > ppos)) { + len = ppos - old_pos; + } + else { + if ((old_pos > ppos) && ((old_pos + len) > (ppos + bufsize))) { + len = bufsize - old_pos + ppos; + } + } + + if (audio_bug (AUDIO_FUNC, len < 0 || len > bufsize)) { + dolog ("len=%d bufsize=%d old_pos=%ld ppos=%ld\n", + len, bufsize, old_pos, ppos); + return 0; + } + + len &= ~hw->info.align; + if (!len) { + return 0; + } + +#ifdef DEBUG_DSOUND + ds->old_ppos = ppos; +#endif + err = dsound_lock_out ( + dsb, + &hw->info, + old_pos, + len, + &p1, &p2, + &blen1, &blen2, + 0 + ); + if (err) { + return 0; + } + + len1 = blen1 >> hwshift; + len2 = blen2 >> hwshift; + decr = len1 + len2; + + if (p1 && len1) { + dsound_write_sample (hw, p1, len1); + } + + if (p2 && len2) { + dsound_write_sample (hw, p2, len2); + } + + dsound_unlock_out (dsb, p1, p2, blen1, blen2); + ds->old_pos = (old_pos + (decr << hwshift)) % bufsize; + +#ifdef DEBUG_DSOUND + ds->mixed += decr << hwshift; + + dolog ("played %lu mixed %lu diff %ld sec %f\n", + ds->played, + ds->mixed, + ds->mixed - ds->played, + abs (ds->mixed - ds->played) / (double) hw->info.bytes_per_second); +#endif + return decr; +} + +static int dsound_ctl_in (HWVoiceIn *hw, int cmd, ...) +{ + HRESULT hr; + DWORD status; + DSoundVoiceIn *ds = (DSoundVoiceIn *) hw; + LPDIRECTSOUNDCAPTUREBUFFER dscb = ds->dsound_capture_buffer; + + if (!dscb) { + dolog ("Attempt to control capture voice without a buffer\n"); + return -1; + } + + switch (cmd) { + case VOICE_ENABLE: + if (dsound_get_status_in (dscb, &status)) { + return -1; + } + + if (status & DSCBSTATUS_CAPTURING) { + dolog ("warning: Voice is already capturing\n"); + return 0; + } + + /* clear ?? */ + + hr = IDirectSoundCaptureBuffer_Start (dscb, DSCBSTART_LOOPING); + if (FAILED (hr)) { + dsound_logerr (hr, "Could not start capturing\n"); + return -1; + } + break; + + case VOICE_DISABLE: + if (dsound_get_status_in (dscb, &status)) { + return -1; + } + + if (status & DSCBSTATUS_CAPTURING) { + hr = IDirectSoundCaptureBuffer_Stop (dscb); + if (FAILED (hr)) { + dsound_logerr (hr, "Could not stop capturing\n"); + return -1; + } + } + else { + dolog ("warning: Voice is not capturing\n"); + } + break; + } + return 0; +} + +static int dsound_read (SWVoiceIn *sw, void *buf, int len) +{ + return audio_pcm_sw_read (sw, buf, len); +} + +static int dsound_run_in (HWVoiceIn *hw) +{ + int err; + HRESULT hr; + DSoundVoiceIn *ds = (DSoundVoiceIn *) hw; + LPDIRECTSOUNDCAPTUREBUFFER dscb = ds->dsound_capture_buffer; + int live, len, dead; + DWORD blen1, blen2; + DWORD len1, len2; + DWORD decr; + DWORD cpos, rpos; + LPVOID p1, p2; + int hwshift; + + if (!dscb) { + dolog ("Attempt to run without capture buffer\n"); + return 0; + } + + hwshift = hw->info.shift; + + live = audio_pcm_hw_get_live_in (hw); + dead = hw->samples - live; + if (!dead) { + return 0; + } + + hr = IDirectSoundCaptureBuffer_GetCurrentPosition ( + dscb, + &cpos, + ds->first_time ? &rpos : NULL + ); + if (FAILED (hr)) { + dsound_logerr (hr, "Could not get capture buffer position\n"); + return 0; + } + + if (ds->first_time) { + ds->first_time = 0; + if (rpos & hw->info.align) { + ldebug ("warning: Misaligned capture read position %ld(%d)\n", + rpos, hw->info.align); + } + hw->wpos = rpos >> hwshift; + } + + if (cpos & hw->info.align) { + ldebug ("warning: Misaligned capture position %ld(%d)\n", + cpos, hw->info.align); + } + cpos >>= hwshift; + + len = audio_ring_dist (cpos, hw->wpos, hw->samples); + if (!len) { + return 0; + } + len = audio_MIN (len, dead); + + err = dsound_lock_in ( + dscb, + &hw->info, + hw->wpos << hwshift, + len << hwshift, + &p1, + &p2, + &blen1, + &blen2, + 0 + ); + if (err) { + return 0; + } + + len1 = blen1 >> hwshift; + len2 = blen2 >> hwshift; + decr = len1 + len2; + + if (p1 && len1) { + hw->conv (hw->conv_buf + hw->wpos, p1, len1); + } + + if (p2 && len2) { + hw->conv (hw->conv_buf, p2, len2); + } + + dsound_unlock_in (dscb, p1, p2, blen1, blen2); + hw->wpos = (hw->wpos + decr) % hw->samples; + return decr; +} + +static void dsound_audio_fini (void *opaque) +{ + HRESULT hr; + dsound *s = opaque; + + if (!s->dsound) { + return; + } + + hr = IDirectSound_Release (s->dsound); + if (FAILED (hr)) { + dsound_logerr (hr, "Could not release DirectSound\n"); + } + s->dsound = NULL; + + if (!s->dsound_capture) { + return; + } + + hr = IDirectSoundCapture_Release (s->dsound_capture); + if (FAILED (hr)) { + dsound_logerr (hr, "Could not release DirectSoundCapture\n"); + } + s->dsound_capture = NULL; +} + +static void *dsound_audio_init (void) +{ + int err; + HRESULT hr; + dsound *s = &glob_dsound; + + hr = CoInitialize (NULL); + if (FAILED (hr)) { + dsound_logerr (hr, "Could not initialize COM\n"); + return NULL; + } + + hr = CoCreateInstance ( + &CLSID_DirectSound, + NULL, + CLSCTX_ALL, + &IID_IDirectSound, + (void **) &s->dsound + ); + if (FAILED (hr)) { + dsound_logerr (hr, "Could not create DirectSound instance\n"); + return NULL; + } + + hr = IDirectSound_Initialize (s->dsound, NULL); + if (FAILED (hr)) { + dsound_logerr (hr, "Could not initialize DirectSound\n"); + + hr = IDirectSound_Release (s->dsound); + if (FAILED (hr)) { + dsound_logerr (hr, "Could not release DirectSound\n"); + } + s->dsound = NULL; + return NULL; + } + + hr = CoCreateInstance ( + &CLSID_DirectSoundCapture, + NULL, + CLSCTX_ALL, + &IID_IDirectSoundCapture, + (void **) &s->dsound_capture + ); + if (FAILED (hr)) { + dsound_logerr (hr, "Could not create DirectSoundCapture instance\n"); + } + else { + hr = IDirectSoundCapture_Initialize (s->dsound_capture, NULL); + if (FAILED (hr)) { + dsound_logerr (hr, "Could not initialize DirectSoundCapture\n"); + + hr = IDirectSoundCapture_Release (s->dsound_capture); + if (FAILED (hr)) { + dsound_logerr (hr, "Could not release DirectSoundCapture\n"); + } + s->dsound_capture = NULL; + } + } + + err = dsound_open (s); + if (err) { + dsound_audio_fini (s); + return NULL; + } + + return s; +} + +static struct audio_option dsound_options[] = { + { + .name = "LOCK_RETRIES", + .tag = AUD_OPT_INT, + .valp = &conf.lock_retries, + .descr = "Number of times to attempt locking the buffer" + }, + { + .name = "RESTOURE_RETRIES", + .tag = AUD_OPT_INT, + .valp = &conf.restore_retries, + .descr = "Number of times to attempt restoring the buffer" + }, + { + .name = "GETSTATUS_RETRIES", + .tag = AUD_OPT_INT, + .valp = &conf.getstatus_retries, + .descr = "Number of times to attempt getting status of the buffer" + }, + { + .name = "SET_PRIMARY", + .tag = AUD_OPT_BOOL, + .valp = &conf.set_primary, + .descr = "Set the parameters of primary buffer" + }, + { + .name = "LATENCY_MILLIS", + .tag = AUD_OPT_INT, + .valp = &conf.latency_millis, + .descr = "(undocumented)" + }, + { + .name = "PRIMARY_FREQ", + .tag = AUD_OPT_INT, + .valp = &conf.settings.freq, + .descr = "Primary buffer frequency" + }, + { + .name = "PRIMARY_CHANNELS", + .tag = AUD_OPT_INT, + .valp = &conf.settings.nchannels, + .descr = "Primary buffer number of channels (1 - mono, 2 - stereo)" + }, + { + .name = "PRIMARY_FMT", + .tag = AUD_OPT_FMT, + .valp = &conf.settings.fmt, + .descr = "Primary buffer format" + }, + { + .name = "BUFSIZE_OUT", + .tag = AUD_OPT_INT, + .valp = &conf.bufsize_out, + .descr = "(undocumented)" + }, + { + .name = "BUFSIZE_IN", + .tag = AUD_OPT_INT, + .valp = &conf.bufsize_in, + .descr = "(undocumented)" + }, + { /* End of list */ } +}; + +static struct audio_pcm_ops dsound_pcm_ops = { + .init_out = dsound_init_out, + .fini_out = dsound_fini_out, + .run_out = dsound_run_out, + .write = dsound_write, + .ctl_out = dsound_ctl_out, + + .init_in = dsound_init_in, + .fini_in = dsound_fini_in, + .run_in = dsound_run_in, + .read = dsound_read, + .ctl_in = dsound_ctl_in +}; + +struct audio_driver dsound_audio_driver = { + .name = "dsound", + .descr = "DirectSound http://wikipedia.org/wiki/DirectSound", + .options = dsound_options, + .init = dsound_audio_init, + .fini = dsound_audio_fini, + .pcm_ops = &dsound_pcm_ops, + .can_be_default = 1, + .max_voices_out = INT_MAX, + .max_voices_in = 1, + .voice_size_out = sizeof (DSoundVoiceOut), + .voice_size_in = sizeof (DSoundVoiceIn) +}; diff --git a/audio/esdaudio.c b/audio/esdaudio.c new file mode 100644 index 0000000..ff97b39 --- /dev/null +++ b/audio/esdaudio.c @@ -0,0 +1,557 @@ +/* + * QEMU ESD audio driver + * + * Copyright (c) 2006 Frederick Reeve (brushed up by 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 +#include "qemu-common.h" +#include "audio.h" + +#define AUDIO_CAP "esd" +#include "audio_int.h" +#include "audio_pt_int.h" + +typedef struct { + HWVoiceOut hw; + int done; + int live; + int decr; + int rpos; + void *pcm_buf; + int fd; + struct audio_pt pt; +} ESDVoiceOut; + +typedef struct { + HWVoiceIn hw; + int done; + int dead; + int incr; + int wpos; + void *pcm_buf; + int fd; + struct audio_pt pt; +} ESDVoiceIn; + +static struct { + int samples; + int divisor; + char *dac_host; + char *adc_host; +} conf = { + .samples = 1024, + .divisor = 2, +}; + +static void GCC_FMT_ATTR (2, 3) qesd_logerr (int err, const char *fmt, ...) +{ + va_list ap; + + va_start (ap, fmt); + AUD_vlog (AUDIO_CAP, fmt, ap); + va_end (ap); + + AUD_log (AUDIO_CAP, "Reason: %s\n", strerror (err)); +} + +/* playback */ +static void *qesd_thread_out (void *arg) +{ + ESDVoiceOut *esd = arg; + HWVoiceOut *hw = &esd->hw; + int threshold; + + threshold = conf.divisor ? hw->samples / conf.divisor : 0; + + if (audio_pt_lock (&esd->pt, AUDIO_FUNC)) { + return NULL; + } + + for (;;) { + int decr, to_mix, rpos; + + for (;;) { + if (esd->done) { + goto exit; + } + + if (esd->live > threshold) { + break; + } + + if (audio_pt_wait (&esd->pt, AUDIO_FUNC)) { + goto exit; + } + } + + decr = to_mix = esd->live; + rpos = hw->rpos; + + if (audio_pt_unlock (&esd->pt, AUDIO_FUNC)) { + return NULL; + } + + while (to_mix) { + ssize_t written; + int chunk = audio_MIN (to_mix, hw->samples - rpos); + struct st_sample *src = hw->mix_buf + rpos; + + hw->clip (esd->pcm_buf, src, chunk); + + again: + written = write (esd->fd, esd->pcm_buf, chunk << hw->info.shift); + if (written == -1) { + if (errno == EINTR || errno == EAGAIN) { + goto again; + } + qesd_logerr (errno, "write failed\n"); + return NULL; + } + + if (written != chunk << hw->info.shift) { + int wsamples = written >> hw->info.shift; + int wbytes = wsamples << hw->info.shift; + if (wbytes != written) { + dolog ("warning: Misaligned write %d (requested %zd), " + "alignment %d\n", + wbytes, written, hw->info.align + 1); + } + to_mix -= wsamples; + rpos = (rpos + wsamples) % hw->samples; + break; + } + + rpos = (rpos + chunk) % hw->samples; + to_mix -= chunk; + } + + if (audio_pt_lock (&esd->pt, AUDIO_FUNC)) { + return NULL; + } + + esd->rpos = rpos; + esd->live -= decr; + esd->decr += decr; + } + + exit: + audio_pt_unlock (&esd->pt, AUDIO_FUNC); + return NULL; +} + +static int qesd_run_out (HWVoiceOut *hw, int live) +{ + int decr; + ESDVoiceOut *esd = (ESDVoiceOut *) hw; + + if (audio_pt_lock (&esd->pt, AUDIO_FUNC)) { + return 0; + } + + decr = audio_MIN (live, esd->decr); + esd->decr -= decr; + esd->live = live - decr; + hw->rpos = esd->rpos; + if (esd->live > 0) { + audio_pt_unlock_and_signal (&esd->pt, AUDIO_FUNC); + } + else { + audio_pt_unlock (&esd->pt, AUDIO_FUNC); + } + return decr; +} + +static int qesd_write (SWVoiceOut *sw, void *buf, int len) +{ + return audio_pcm_sw_write (sw, buf, len); +} + +static int qesd_init_out (HWVoiceOut *hw, struct audsettings *as) +{ + ESDVoiceOut *esd = (ESDVoiceOut *) hw; + struct audsettings obt_as = *as; + int esdfmt = ESD_STREAM | ESD_PLAY; + + esdfmt |= (as->nchannels == 2) ? ESD_STEREO : ESD_MONO; + switch (as->fmt) { + case AUD_FMT_S8: + case AUD_FMT_U8: + esdfmt |= ESD_BITS8; + obt_as.fmt = AUD_FMT_U8; + break; + + case AUD_FMT_S32: + case AUD_FMT_U32: + dolog ("Will use 16 instead of 32 bit samples\n"); + + case AUD_FMT_S16: + case AUD_FMT_U16: + deffmt: + esdfmt |= ESD_BITS16; + obt_as.fmt = AUD_FMT_S16; + break; + + default: + dolog ("Internal logic error: Bad audio format %d\n", as->fmt); + goto deffmt; + + } + obt_as.endianness = AUDIO_HOST_ENDIANNESS; + + audio_pcm_init_info (&hw->info, &obt_as); + + hw->samples = conf.samples; + esd->pcm_buf = audio_calloc (AUDIO_FUNC, hw->samples, 1 << hw->info.shift); + if (!esd->pcm_buf) { + dolog ("Could not allocate buffer (%d bytes)\n", + hw->samples << hw->info.shift); + return -1; + } + + esd->fd = esd_play_stream (esdfmt, as->freq, conf.dac_host, NULL); + if (esd->fd < 0) { + qesd_logerr (errno, "esd_play_stream failed\n"); + goto fail1; + } + + if (audio_pt_init (&esd->pt, qesd_thread_out, esd, AUDIO_CAP, AUDIO_FUNC)) { + goto fail2; + } + + return 0; + + fail2: + if (close (esd->fd)) { + qesd_logerr (errno, "%s: close on esd socket(%d) failed\n", + AUDIO_FUNC, esd->fd); + } + esd->fd = -1; + + fail1: + qemu_free (esd->pcm_buf); + esd->pcm_buf = NULL; + return -1; +} + +static void qesd_fini_out (HWVoiceOut *hw) +{ + void *ret; + ESDVoiceOut *esd = (ESDVoiceOut *) hw; + + audio_pt_lock (&esd->pt, AUDIO_FUNC); + esd->done = 1; + audio_pt_unlock_and_signal (&esd->pt, AUDIO_FUNC); + audio_pt_join (&esd->pt, &ret, AUDIO_FUNC); + + if (esd->fd >= 0) { + if (close (esd->fd)) { + qesd_logerr (errno, "failed to close esd socket\n"); + } + esd->fd = -1; + } + + audio_pt_fini (&esd->pt, AUDIO_FUNC); + + qemu_free (esd->pcm_buf); + esd->pcm_buf = NULL; +} + +static int qesd_ctl_out (HWVoiceOut *hw, int cmd, ...) +{ + (void) hw; + (void) cmd; + return 0; +} + +/* capture */ +static void *qesd_thread_in (void *arg) +{ + ESDVoiceIn *esd = arg; + HWVoiceIn *hw = &esd->hw; + int threshold; + + threshold = conf.divisor ? hw->samples / conf.divisor : 0; + + if (audio_pt_lock (&esd->pt, AUDIO_FUNC)) { + return NULL; + } + + for (;;) { + int incr, to_grab, wpos; + + for (;;) { + if (esd->done) { + goto exit; + } + + if (esd->dead > threshold) { + break; + } + + if (audio_pt_wait (&esd->pt, AUDIO_FUNC)) { + goto exit; + } + } + + incr = to_grab = esd->dead; + wpos = hw->wpos; + + if (audio_pt_unlock (&esd->pt, AUDIO_FUNC)) { + return NULL; + } + + while (to_grab) { + ssize_t nread; + int chunk = audio_MIN (to_grab, hw->samples - wpos); + void *buf = advance (esd->pcm_buf, wpos); + + again: + nread = read (esd->fd, buf, chunk << hw->info.shift); + if (nread == -1) { + if (errno == EINTR || errno == EAGAIN) { + goto again; + } + qesd_logerr (errno, "read failed\n"); + return NULL; + } + + if (nread != chunk << hw->info.shift) { + int rsamples = nread >> hw->info.shift; + int rbytes = rsamples << hw->info.shift; + if (rbytes != nread) { + dolog ("warning: Misaligned write %d (requested %zd), " + "alignment %d\n", + rbytes, nread, hw->info.align + 1); + } + to_grab -= rsamples; + wpos = (wpos + rsamples) % hw->samples; + break; + } + + hw->conv (hw->conv_buf + wpos, buf, nread >> hw->info.shift); + wpos = (wpos + chunk) % hw->samples; + to_grab -= chunk; + } + + if (audio_pt_lock (&esd->pt, AUDIO_FUNC)) { + return NULL; + } + + esd->wpos = wpos; + esd->dead -= incr; + esd->incr += incr; + } + + exit: + audio_pt_unlock (&esd->pt, AUDIO_FUNC); + return NULL; +} + +static int qesd_run_in (HWVoiceIn *hw) +{ + int live, incr, dead; + ESDVoiceIn *esd = (ESDVoiceIn *) hw; + + if (audio_pt_lock (&esd->pt, AUDIO_FUNC)) { + return 0; + } + + live = audio_pcm_hw_get_live_in (hw); + dead = hw->samples - live; + incr = audio_MIN (dead, esd->incr); + esd->incr -= incr; + esd->dead = dead - incr; + hw->wpos = esd->wpos; + if (esd->dead > 0) { + audio_pt_unlock_and_signal (&esd->pt, AUDIO_FUNC); + } + else { + audio_pt_unlock (&esd->pt, AUDIO_FUNC); + } + return incr; +} + +static int qesd_read (SWVoiceIn *sw, void *buf, int len) +{ + return audio_pcm_sw_read (sw, buf, len); +} + +static int qesd_init_in (HWVoiceIn *hw, struct audsettings *as) +{ + ESDVoiceIn *esd = (ESDVoiceIn *) hw; + struct audsettings obt_as = *as; + int esdfmt = ESD_STREAM | ESD_RECORD; + + esdfmt |= (as->nchannels == 2) ? ESD_STEREO : ESD_MONO; + switch (as->fmt) { + case AUD_FMT_S8: + case AUD_FMT_U8: + esdfmt |= ESD_BITS8; + obt_as.fmt = AUD_FMT_U8; + break; + + case AUD_FMT_S16: + case AUD_FMT_U16: + esdfmt |= ESD_BITS16; + obt_as.fmt = AUD_FMT_S16; + break; + + case AUD_FMT_S32: + case AUD_FMT_U32: + dolog ("Will use 16 instead of 32 bit samples\n"); + esdfmt |= ESD_BITS16; + obt_as.fmt = AUD_FMT_S16; + break; + } + obt_as.endianness = AUDIO_HOST_ENDIANNESS; + + audio_pcm_init_info (&hw->info, &obt_as); + + hw->samples = conf.samples; + esd->pcm_buf = audio_calloc (AUDIO_FUNC, hw->samples, 1 << hw->info.shift); + if (!esd->pcm_buf) { + dolog ("Could not allocate buffer (%d bytes)\n", + hw->samples << hw->info.shift); + return -1; + } + + esd->fd = esd_record_stream (esdfmt, as->freq, conf.adc_host, NULL); + if (esd->fd < 0) { + qesd_logerr (errno, "esd_record_stream failed\n"); + goto fail1; + } + + if (audio_pt_init (&esd->pt, qesd_thread_in, esd, AUDIO_CAP, AUDIO_FUNC)) { + goto fail2; + } + + return 0; + + fail2: + if (close (esd->fd)) { + qesd_logerr (errno, "%s: close on esd socket(%d) failed\n", + AUDIO_FUNC, esd->fd); + } + esd->fd = -1; + + fail1: + qemu_free (esd->pcm_buf); + esd->pcm_buf = NULL; + return -1; +} + +static void qesd_fini_in (HWVoiceIn *hw) +{ + void *ret; + ESDVoiceIn *esd = (ESDVoiceIn *) hw; + + audio_pt_lock (&esd->pt, AUDIO_FUNC); + esd->done = 1; + audio_pt_unlock_and_signal (&esd->pt, AUDIO_FUNC); + audio_pt_join (&esd->pt, &ret, AUDIO_FUNC); + + if (esd->fd >= 0) { + if (close (esd->fd)) { + qesd_logerr (errno, "failed to close esd socket\n"); + } + esd->fd = -1; + } + + audio_pt_fini (&esd->pt, AUDIO_FUNC); + + qemu_free (esd->pcm_buf); + esd->pcm_buf = NULL; +} + +static int qesd_ctl_in (HWVoiceIn *hw, int cmd, ...) +{ + (void) hw; + (void) cmd; + return 0; +} + +/* common */ +static void *qesd_audio_init (void) +{ + return &conf; +} + +static void qesd_audio_fini (void *opaque) +{ + (void) opaque; + ldebug ("esd_fini"); +} + +struct audio_option qesd_options[] = { + { + .name = "SAMPLES", + .tag = AUD_OPT_INT, + .valp = &conf.samples, + .descr = "buffer size in samples" + }, + { + .name = "DIVISOR", + .tag = AUD_OPT_INT, + .valp = &conf.divisor, + .descr = "threshold divisor" + }, + { + .name = "DAC_HOST", + .tag = AUD_OPT_STR, + .valp = &conf.dac_host, + .descr = "playback host" + }, + { + .name = "ADC_HOST", + .tag = AUD_OPT_STR, + .valp = &conf.adc_host, + .descr = "capture host" + }, + { /* End of list */ } +}; + +static struct audio_pcm_ops qesd_pcm_ops = { + .init_out = qesd_init_out, + .fini_out = qesd_fini_out, + .run_out = qesd_run_out, + .write = qesd_write, + .ctl_out = qesd_ctl_out, + + .init_in = qesd_init_in, + .fini_in = qesd_fini_in, + .run_in = qesd_run_in, + .read = qesd_read, + .ctl_in = qesd_ctl_in, +}; + +struct audio_driver esd_audio_driver = { + .name = "esd", + .descr = "http://en.wikipedia.org/wiki/Esound", + .options = qesd_options, + .init = qesd_audio_init, + .fini = qesd_audio_fini, + .pcm_ops = &qesd_pcm_ops, + .can_be_default = 0, + .max_voices_out = INT_MAX, + .max_voices_in = INT_MAX, + .voice_size_out = sizeof (ESDVoiceOut), + .voice_size_in = sizeof (ESDVoiceIn) +}; diff --git a/audio/fmodaudio.c b/audio/fmodaudio.c new file mode 100644 index 0000000..c34cf53 --- /dev/null +++ b/audio/fmodaudio.c @@ -0,0 +1,687 @@ +/* + * QEMU FMOD audio driver + * + * 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 +#include +#include "qemu-common.h" +#include "audio.h" + +#define AUDIO_CAP "fmod" +#include "audio_int.h" + +typedef struct FMODVoiceOut { + HWVoiceOut hw; + unsigned int old_pos; + FSOUND_SAMPLE *fmod_sample; + int channel; +} FMODVoiceOut; + +typedef struct FMODVoiceIn { + HWVoiceIn hw; + FSOUND_SAMPLE *fmod_sample; +} FMODVoiceIn; + +static struct { + const char *drvname; + int nb_samples; + int freq; + int nb_channels; + int bufsize; + int broken_adc; +} conf = { + .nb_samples = 2048 * 2, + .freq = 44100, + .nb_channels = 2, +}; + +static void GCC_FMT_ATTR (1, 2) fmod_logerr (const char *fmt, ...) +{ + va_list ap; + + va_start (ap, fmt); + AUD_vlog (AUDIO_CAP, fmt, ap); + va_end (ap); + + AUD_log (AUDIO_CAP, "Reason: %s\n", + FMOD_ErrorString (FSOUND_GetError ())); +} + +static void GCC_FMT_ATTR (2, 3) fmod_logerr2 ( + const char *typ, + const char *fmt, + ... + ) +{ + va_list ap; + + AUD_log (AUDIO_CAP, "Could not initialize %s\n", typ); + + va_start (ap, fmt); + AUD_vlog (AUDIO_CAP, fmt, ap); + va_end (ap); + + AUD_log (AUDIO_CAP, "Reason: %s\n", + FMOD_ErrorString (FSOUND_GetError ())); +} + +static int fmod_write (SWVoiceOut *sw, void *buf, int len) +{ + return audio_pcm_sw_write (sw, buf, len); +} + +static void fmod_clear_sample (FMODVoiceOut *fmd) +{ + HWVoiceOut *hw = &fmd->hw; + int status; + void *p1 = 0, *p2 = 0; + unsigned int len1 = 0, len2 = 0; + + status = FSOUND_Sample_Lock ( + fmd->fmod_sample, + 0, + hw->samples << hw->info.shift, + &p1, + &p2, + &len1, + &len2 + ); + + if (!status) { + fmod_logerr ("Failed to lock sample\n"); + return; + } + + if ((len1 & hw->info.align) || (len2 & hw->info.align)) { + dolog ("Lock returned misaligned length %d, %d, alignment %d\n", + len1, len2, hw->info.align + 1); + goto fail; + } + + if ((len1 + len2) - (hw->samples << hw->info.shift)) { + dolog ("Lock returned incomplete length %d, %d\n", + len1 + len2, hw->samples << hw->info.shift); + goto fail; + } + + audio_pcm_info_clear_buf (&hw->info, p1, hw->samples); + + fail: + status = FSOUND_Sample_Unlock (fmd->fmod_sample, p1, p2, len1, len2); + if (!status) { + fmod_logerr ("Failed to unlock sample\n"); + } +} + +static void fmod_write_sample (HWVoiceOut *hw, uint8_t *dst, int dst_len) +{ + int src_len1 = dst_len; + int src_len2 = 0; + int pos = hw->rpos + dst_len; + struct st_sample *src1 = hw->mix_buf + hw->rpos; + struct st_sample *src2 = NULL; + + if (pos > hw->samples) { + src_len1 = hw->samples - hw->rpos; + src2 = hw->mix_buf; + src_len2 = dst_len - src_len1; + pos = src_len2; + } + + if (src_len1) { + hw->clip (dst, src1, src_len1); + } + + if (src_len2) { + dst = advance (dst, src_len1 << hw->info.shift); + hw->clip (dst, src2, src_len2); + } + + hw->rpos = pos % hw->samples; +} + +static int fmod_unlock_sample (FSOUND_SAMPLE *sample, void *p1, void *p2, + unsigned int blen1, unsigned int blen2) +{ + int status = FSOUND_Sample_Unlock (sample, p1, p2, blen1, blen2); + if (!status) { + fmod_logerr ("Failed to unlock sample\n"); + return -1; + } + return 0; +} + +static int fmod_lock_sample ( + FSOUND_SAMPLE *sample, + struct audio_pcm_info *info, + int pos, + int len, + void **p1, + void **p2, + unsigned int *blen1, + unsigned int *blen2 + ) +{ + int status; + + status = FSOUND_Sample_Lock ( + sample, + pos << info->shift, + len << info->shift, + p1, + p2, + blen1, + blen2 + ); + + if (!status) { + fmod_logerr ("Failed to lock sample\n"); + return -1; + } + + if ((*blen1 & info->align) || (*blen2 & info->align)) { + dolog ("Lock returned misaligned length %d, %d, alignment %d\n", + *blen1, *blen2, info->align + 1); + + fmod_unlock_sample (sample, *p1, *p2, *blen1, *blen2); + + *p1 = NULL - 1; + *p2 = NULL - 1; + *blen1 = ~0U; + *blen2 = ~0U; + return -1; + } + + if (!*p1 && *blen1) { + dolog ("warning: !p1 && blen1=%d\n", *blen1); + *blen1 = 0; + } + + if (!p2 && *blen2) { + dolog ("warning: !p2 && blen2=%d\n", *blen2); + *blen2 = 0; + } + + return 0; +} + +static int fmod_run_out (HWVoiceOut *hw, int live) +{ + FMODVoiceOut *fmd = (FMODVoiceOut *) hw; + int decr; + void *p1 = 0, *p2 = 0; + unsigned int blen1 = 0, blen2 = 0; + unsigned int len1 = 0, len2 = 0; + + if (!hw->pending_disable) { + return 0; + } + + decr = live; + + if (fmd->channel >= 0) { + int len = decr; + int old_pos = fmd->old_pos; + int ppos = FSOUND_GetCurrentPosition (fmd->channel); + + if (ppos == old_pos || !ppos) { + return 0; + } + + if ((old_pos < ppos) && ((old_pos + len) > ppos)) { + len = ppos - old_pos; + } + else { + if ((old_pos > ppos) && ((old_pos + len) > (ppos + hw->samples))) { + len = hw->samples - old_pos + ppos; + } + } + decr = len; + + if (audio_bug (AUDIO_FUNC, decr < 0)) { + dolog ("decr=%d live=%d ppos=%d old_pos=%d len=%d\n", + decr, live, ppos, old_pos, len); + return 0; + } + } + + + if (!decr) { + return 0; + } + + if (fmod_lock_sample (fmd->fmod_sample, &fmd->hw.info, + fmd->old_pos, decr, + &p1, &p2, + &blen1, &blen2)) { + return 0; + } + + len1 = blen1 >> hw->info.shift; + len2 = blen2 >> hw->info.shift; + ldebug ("%p %p %d %d %d %d\n", p1, p2, len1, len2, blen1, blen2); + decr = len1 + len2; + + if (p1 && len1) { + fmod_write_sample (hw, p1, len1); + } + + if (p2 && len2) { + fmod_write_sample (hw, p2, len2); + } + + fmod_unlock_sample (fmd->fmod_sample, p1, p2, blen1, blen2); + + fmd->old_pos = (fmd->old_pos + decr) % hw->samples; + return decr; +} + +static int aud_to_fmodfmt (audfmt_e fmt, int stereo) +{ + int mode = FSOUND_LOOP_NORMAL; + + switch (fmt) { + case AUD_FMT_S8: + mode |= FSOUND_SIGNED | FSOUND_8BITS; + break; + + case AUD_FMT_U8: + mode |= FSOUND_UNSIGNED | FSOUND_8BITS; + break; + + case AUD_FMT_S16: + mode |= FSOUND_SIGNED | FSOUND_16BITS; + break; + + case AUD_FMT_U16: + mode |= FSOUND_UNSIGNED | FSOUND_16BITS; + break; + + default: + dolog ("Internal logic error: Bad audio format %d\n", fmt); +#ifdef DEBUG_FMOD + abort (); +#endif + mode |= FSOUND_8BITS; + } + mode |= stereo ? FSOUND_STEREO : FSOUND_MONO; + return mode; +} + +static void fmod_fini_out (HWVoiceOut *hw) +{ + FMODVoiceOut *fmd = (FMODVoiceOut *) hw; + + if (fmd->fmod_sample) { + FSOUND_Sample_Free (fmd->fmod_sample); + fmd->fmod_sample = 0; + + if (fmd->channel >= 0) { + FSOUND_StopSound (fmd->channel); + } + } +} + +static int fmod_init_out (HWVoiceOut *hw, struct audsettings *as) +{ + int bits16, mode, channel; + FMODVoiceOut *fmd = (FMODVoiceOut *) hw; + struct audsettings obt_as = *as; + + mode = aud_to_fmodfmt (as->fmt, as->nchannels == 2 ? 1 : 0); + fmd->fmod_sample = FSOUND_Sample_Alloc ( + FSOUND_FREE, /* index */ + conf.nb_samples, /* length */ + mode, /* mode */ + as->freq, /* freq */ + 255, /* volume */ + 128, /* pan */ + 255 /* priority */ + ); + + if (!fmd->fmod_sample) { + fmod_logerr2 ("DAC", "Failed to allocate FMOD sample\n"); + return -1; + } + + channel = FSOUND_PlaySoundEx (FSOUND_FREE, fmd->fmod_sample, 0, 1); + if (channel < 0) { + fmod_logerr2 ("DAC", "Failed to start playing sound\n"); + FSOUND_Sample_Free (fmd->fmod_sample); + return -1; + } + fmd->channel = channel; + + /* FMOD always operates on little endian frames? */ + obt_as.endianness = 0; + audio_pcm_init_info (&hw->info, &obt_as); + bits16 = (mode & FSOUND_16BITS) != 0; + hw->samples = conf.nb_samples; + return 0; +} + +static int fmod_ctl_out (HWVoiceOut *hw, int cmd, ...) +{ + int status; + FMODVoiceOut *fmd = (FMODVoiceOut *) hw; + + switch (cmd) { + case VOICE_ENABLE: + fmod_clear_sample (fmd); + status = FSOUND_SetPaused (fmd->channel, 0); + if (!status) { + fmod_logerr ("Failed to resume channel %d\n", fmd->channel); + } + break; + + case VOICE_DISABLE: + status = FSOUND_SetPaused (fmd->channel, 1); + if (!status) { + fmod_logerr ("Failed to pause channel %d\n", fmd->channel); + } + break; + } + return 0; +} + +static int fmod_init_in (HWVoiceIn *hw, struct audsettings *as) +{ + int bits16, mode; + FMODVoiceIn *fmd = (FMODVoiceIn *) hw; + struct audsettings obt_as = *as; + + if (conf.broken_adc) { + return -1; + } + + mode = aud_to_fmodfmt (as->fmt, as->nchannels == 2 ? 1 : 0); + fmd->fmod_sample = FSOUND_Sample_Alloc ( + FSOUND_FREE, /* index */ + conf.nb_samples, /* length */ + mode, /* mode */ + as->freq, /* freq */ + 255, /* volume */ + 128, /* pan */ + 255 /* priority */ + ); + + if (!fmd->fmod_sample) { + fmod_logerr2 ("ADC", "Failed to allocate FMOD sample\n"); + return -1; + } + + /* FMOD always operates on little endian frames? */ + obt_as.endianness = 0; + audio_pcm_init_info (&hw->info, &obt_as); + bits16 = (mode & FSOUND_16BITS) != 0; + hw->samples = conf.nb_samples; + return 0; +} + +static void fmod_fini_in (HWVoiceIn *hw) +{ + FMODVoiceIn *fmd = (FMODVoiceIn *) hw; + + if (fmd->fmod_sample) { + FSOUND_Record_Stop (); + FSOUND_Sample_Free (fmd->fmod_sample); + fmd->fmod_sample = 0; + } +} + +static int fmod_run_in (HWVoiceIn *hw) +{ + FMODVoiceIn *fmd = (FMODVoiceIn *) hw; + int hwshift = hw->info.shift; + int live, dead, new_pos, len; + unsigned int blen1 = 0, blen2 = 0; + unsigned int len1, len2; + unsigned int decr; + void *p1, *p2; + + live = audio_pcm_hw_get_live_in (hw); + dead = hw->samples - live; + if (!dead) { + return 0; + } + + new_pos = FSOUND_Record_GetPosition (); + if (new_pos < 0) { + fmod_logerr ("Could not get recording position\n"); + return 0; + } + + len = audio_ring_dist (new_pos, hw->wpos, hw->samples); + if (!len) { + return 0; + } + len = audio_MIN (len, dead); + + if (fmod_lock_sample (fmd->fmod_sample, &fmd->hw.info, + hw->wpos, len, + &p1, &p2, + &blen1, &blen2)) { + return 0; + } + + len1 = blen1 >> hwshift; + len2 = blen2 >> hwshift; + decr = len1 + len2; + + if (p1 && blen1) { + hw->conv (hw->conv_buf + hw->wpos, p1, len1); + } + if (p2 && len2) { + hw->conv (hw->conv_buf, p2, len2); + } + + fmod_unlock_sample (fmd->fmod_sample, p1, p2, blen1, blen2); + hw->wpos = (hw->wpos + decr) % hw->samples; + return decr; +} + +static struct { + const char *name; + int type; +} drvtab[] = { + { .name = "none", .type = FSOUND_OUTPUT_NOSOUND }, +#ifdef _WIN32 + { .name = "winmm", .type = FSOUND_OUTPUT_WINMM }, + { .name = "dsound", .type = FSOUND_OUTPUT_DSOUND }, + { .name = "a3d", .type = FSOUND_OUTPUT_A3D }, + { .name = "asio", .type = FSOUND_OUTPUT_ASIO }, +#endif +#ifdef __linux__ + { .name = "oss", .type = FSOUND_OUTPUT_OSS }, + { .name = "alsa", .type = FSOUND_OUTPUT_ALSA }, + { .name = "esd", .type = FSOUND_OUTPUT_ESD }, +#endif +#ifdef __APPLE__ + { .name = "mac", .type = FSOUND_OUTPUT_MAC }, +#endif +#if 0 + { .name = "xbox", .type = FSOUND_OUTPUT_XBOX }, + { .name = "ps2", .type = FSOUND_OUTPUT_PS2 }, + { .name = "gcube", .type = FSOUND_OUTPUT_GC }, +#endif + { .name = "none-realtime", .type = FSOUND_OUTPUT_NOSOUND_NONREALTIME } +}; + +static void *fmod_audio_init (void) +{ + size_t i; + double ver; + int status; + int output_type = -1; + const char *drv = conf.drvname; + + ver = FSOUND_GetVersion (); + if (ver < FMOD_VERSION) { + dolog ("Wrong FMOD version %f, need at least %f\n", ver, FMOD_VERSION); + return NULL; + } + +#ifdef __linux__ + if (ver < 3.75) { + dolog ("FMOD before 3.75 has bug preventing ADC from working\n" + "ADC will be disabled.\n"); + conf.broken_adc = 1; + } +#endif + + if (drv) { + int found = 0; + for (i = 0; i < ARRAY_SIZE (drvtab); i++) { + if (!strcmp (drv, drvtab[i].name)) { + output_type = drvtab[i].type; + found = 1; + break; + } + } + if (!found) { + dolog ("Unknown FMOD driver `%s'\n", drv); + dolog ("Valid drivers:\n"); + for (i = 0; i < ARRAY_SIZE (drvtab); i++) { + dolog (" %s\n", drvtab[i].name); + } + } + } + + if (output_type != -1) { + status = FSOUND_SetOutput (output_type); + if (!status) { + fmod_logerr ("FSOUND_SetOutput(%d) failed\n", output_type); + return NULL; + } + } + + if (conf.bufsize) { + status = FSOUND_SetBufferSize (conf.bufsize); + if (!status) { + fmod_logerr ("FSOUND_SetBufferSize (%d) failed\n", conf.bufsize); + } + } + + status = FSOUND_Init (conf.freq, conf.nb_channels, 0); + if (!status) { + fmod_logerr ("FSOUND_Init failed\n"); + return NULL; + } + + return &conf; +} + +static int fmod_read (SWVoiceIn *sw, void *buf, int size) +{ + return audio_pcm_sw_read (sw, buf, size); +} + +static int fmod_ctl_in (HWVoiceIn *hw, int cmd, ...) +{ + int status; + FMODVoiceIn *fmd = (FMODVoiceIn *) hw; + + switch (cmd) { + case VOICE_ENABLE: + status = FSOUND_Record_StartSample (fmd->fmod_sample, 1); + if (!status) { + fmod_logerr ("Failed to start recording\n"); + } + break; + + case VOICE_DISABLE: + status = FSOUND_Record_Stop (); + if (!status) { + fmod_logerr ("Failed to stop recording\n"); + } + break; + } + return 0; +} + +static void fmod_audio_fini (void *opaque) +{ + (void) opaque; + FSOUND_Close (); +} + +static struct audio_option fmod_options[] = { + { + .name = "DRV", + .tag = AUD_OPT_STR, + .valp = &conf.drvname, + .descr = "FMOD driver" + }, + { + .name = "FREQ", + .tag = AUD_OPT_INT, + .valp = &conf.freq, + .descr = "Default frequency" + }, + { + .name = "SAMPLES", + .tag = AUD_OPT_INT, + .valp = &conf.nb_samples, + .descr = "Buffer size in samples" + }, + { + .name = "CHANNELS", + .tag = AUD_OPT_INT, + .valp = &conf.nb_channels, + .descr = "Number of default channels (1 - mono, 2 - stereo)" + }, + { + .name = "BUFSIZE", + .tag = AUD_OPT_INT, + .valp = &conf.bufsize, + .descr = "(undocumented)" + }, + { /* End of list */ } +}; + +static struct audio_pcm_ops fmod_pcm_ops = { + .init_out = fmod_init_out, + .fini_out = fmod_fini_out, + .run_out = fmod_run_out, + .write = fmod_write, + .ctl_out = fmod_ctl_out, + + .init_in = fmod_init_in, + .fini_in = fmod_fini_in, + .run_in = fmod_run_in, + .read = fmod_read, + .ctl_in = fmod_ctl_in +}; + +struct audio_driver fmod_audio_driver = { + .name = "fmod", + .descr = "FMOD 3.xx http://www.fmod.org", + .options = fmod_options, + .init = fmod_audio_init, + .fini = fmod_audio_fini, + .pcm_ops = &fmod_pcm_ops, + .can_be_default = 1, + .max_voices_out = INT_MAX, + .max_voices_in = INT_MAX, + .voice_size_out = sizeof (FMODVoiceOut), + .voice_size_in = sizeof (FMODVoiceIn) +}; diff --git a/audio/mixeng.c b/audio/mixeng.c new file mode 100644 index 0000000..4a9e8eb --- /dev/null +++ b/audio/mixeng.c @@ -0,0 +1,360 @@ +/* + * QEMU Mixing engine + * + * Copyright (c) 2004-2005 Vassili Karpov (malc) + * Copyright (c) 1998 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 "audio.h" + +#define AUDIO_CAP "mixeng" +#include "audio_int.h" + +/* 8 bit */ +#define ENDIAN_CONVERSION natural +#define ENDIAN_CONVERT(v) (v) + +/* Signed 8 bit */ +#define IN_T int8_t +#define IN_MIN SCHAR_MIN +#define IN_MAX SCHAR_MAX +#define SIGNED +#define SHIFT 8 +#include "mixeng_template.h" +#undef SIGNED +#undef IN_MAX +#undef IN_MIN +#undef IN_T +#undef SHIFT + +/* Unsigned 8 bit */ +#define IN_T uint8_t +#define IN_MIN 0 +#define IN_MAX UCHAR_MAX +#define SHIFT 8 +#include "mixeng_template.h" +#undef IN_MAX +#undef IN_MIN +#undef IN_T +#undef SHIFT + +#undef ENDIAN_CONVERT +#undef ENDIAN_CONVERSION + +/* Signed 16 bit */ +#define IN_T int16_t +#define IN_MIN SHRT_MIN +#define IN_MAX SHRT_MAX +#define SIGNED +#define SHIFT 16 +#define ENDIAN_CONVERSION natural +#define ENDIAN_CONVERT(v) (v) +#include "mixeng_template.h" +#undef ENDIAN_CONVERT +#undef ENDIAN_CONVERSION +#define ENDIAN_CONVERSION swap +#define ENDIAN_CONVERT(v) bswap16 (v) +#include "mixeng_template.h" +#undef ENDIAN_CONVERT +#undef ENDIAN_CONVERSION +#undef SIGNED +#undef IN_MAX +#undef IN_MIN +#undef IN_T +#undef SHIFT + +/* Unsigned 16 bit */ +#define IN_T uint16_t +#define IN_MIN 0 +#define IN_MAX USHRT_MAX +#define SHIFT 16 +#define ENDIAN_CONVERSION natural +#define ENDIAN_CONVERT(v) (v) +#include "mixeng_template.h" +#undef ENDIAN_CONVERT +#undef ENDIAN_CONVERSION +#define ENDIAN_CONVERSION swap +#define ENDIAN_CONVERT(v) bswap16 (v) +#include "mixeng_template.h" +#undef ENDIAN_CONVERT +#undef ENDIAN_CONVERSION +#undef IN_MAX +#undef IN_MIN +#undef IN_T +#undef SHIFT + +/* Signed 32 bit */ +#define IN_T int32_t +#define IN_MIN INT32_MIN +#define IN_MAX INT32_MAX +#define SIGNED +#define SHIFT 32 +#define ENDIAN_CONVERSION natural +#define ENDIAN_CONVERT(v) (v) +#include "mixeng_template.h" +#undef ENDIAN_CONVERT +#undef ENDIAN_CONVERSION +#define ENDIAN_CONVERSION swap +#define ENDIAN_CONVERT(v) bswap32 (v) +#include "mixeng_template.h" +#undef ENDIAN_CONVERT +#undef ENDIAN_CONVERSION +#undef SIGNED +#undef IN_MAX +#undef IN_MIN +#undef IN_T +#undef SHIFT + +/* Unsigned 32 bit */ +#define IN_T uint32_t +#define IN_MIN 0 +#define IN_MAX UINT32_MAX +#define SHIFT 32 +#define ENDIAN_CONVERSION natural +#define ENDIAN_CONVERT(v) (v) +#include "mixeng_template.h" +#undef ENDIAN_CONVERT +#undef ENDIAN_CONVERSION +#define ENDIAN_CONVERSION swap +#define ENDIAN_CONVERT(v) bswap32 (v) +#include "mixeng_template.h" +#undef ENDIAN_CONVERT +#undef ENDIAN_CONVERSION +#undef IN_MAX +#undef IN_MIN +#undef IN_T +#undef SHIFT + +t_sample *mixeng_conv[2][2][2][3] = { + { + { + { + conv_natural_uint8_t_to_mono, + conv_natural_uint16_t_to_mono, + conv_natural_uint32_t_to_mono + }, + { + conv_natural_uint8_t_to_mono, + conv_swap_uint16_t_to_mono, + conv_swap_uint32_t_to_mono, + } + }, + { + { + conv_natural_int8_t_to_mono, + conv_natural_int16_t_to_mono, + conv_natural_int32_t_to_mono + }, + { + conv_natural_int8_t_to_mono, + conv_swap_int16_t_to_mono, + conv_swap_int32_t_to_mono + } + } + }, + { + { + { + conv_natural_uint8_t_to_stereo, + conv_natural_uint16_t_to_stereo, + conv_natural_uint32_t_to_stereo + }, + { + conv_natural_uint8_t_to_stereo, + conv_swap_uint16_t_to_stereo, + conv_swap_uint32_t_to_stereo + } + }, + { + { + conv_natural_int8_t_to_stereo, + conv_natural_int16_t_to_stereo, + conv_natural_int32_t_to_stereo + }, + { + conv_natural_int8_t_to_stereo, + conv_swap_int16_t_to_stereo, + conv_swap_int32_t_to_stereo, + } + } + } +}; + +f_sample *mixeng_clip[2][2][2][3] = { + { + { + { + clip_natural_uint8_t_from_mono, + clip_natural_uint16_t_from_mono, + clip_natural_uint32_t_from_mono + }, + { + clip_natural_uint8_t_from_mono, + clip_swap_uint16_t_from_mono, + clip_swap_uint32_t_from_mono + } + }, + { + { + clip_natural_int8_t_from_mono, + clip_natural_int16_t_from_mono, + clip_natural_int32_t_from_mono + }, + { + clip_natural_int8_t_from_mono, + clip_swap_int16_t_from_mono, + clip_swap_int32_t_from_mono + } + } + }, + { + { + { + clip_natural_uint8_t_from_stereo, + clip_natural_uint16_t_from_stereo, + clip_natural_uint32_t_from_stereo + }, + { + clip_natural_uint8_t_from_stereo, + clip_swap_uint16_t_from_stereo, + clip_swap_uint32_t_from_stereo + } + }, + { + { + clip_natural_int8_t_from_stereo, + clip_natural_int16_t_from_stereo, + clip_natural_int32_t_from_stereo + }, + { + clip_natural_int8_t_from_stereo, + clip_swap_int16_t_from_stereo, + clip_swap_int32_t_from_stereo + } + } + } +}; + +/* + * August 21, 1998 + * Copyright 1998 Fabrice Bellard. + * + * [Rewrote completly the code of Lance Norskog And Sundry + * Contributors with a more efficient algorithm.] + * + * This source code is freely redistributable and may be used for + * any purpose. This copyright notice must be maintained. + * Lance Norskog And Sundry Contributors are not responsible for + * the consequences of using this software. + */ + +/* + * Sound Tools rate change effect file. + */ +/* + * Linear Interpolation. + * + * The use of fractional increment allows us to use no buffer. It + * avoid the problems at the end of the buffer we had with the old + * method which stored a possibly big buffer of size + * lcm(in_rate,out_rate). + * + * Limited to 16 bit samples and sampling frequency <= 65535 Hz. If + * the input & output frequencies are equal, a delay of one sample is + * introduced. Limited to processing 32-bit count worth of samples. + * + * 1 << FRAC_BITS evaluating to zero in several places. Changed with + * an (unsigned long) cast to make it safe. MarkMLl 2/1/99 + */ + +/* Private data */ +struct rate { + uint64_t opos; + uint64_t opos_inc; + uint32_t ipos; /* position in the input stream (integer) */ + struct st_sample ilast; /* last sample in the input stream */ +}; + +/* + * Prepare processing. + */ +void *st_rate_start (int inrate, int outrate) +{ + struct rate *rate = audio_calloc (AUDIO_FUNC, 1, sizeof (*rate)); + + if (!rate) { + dolog ("Could not allocate resampler (%zu bytes)\n", sizeof (*rate)); + return NULL; + } + + rate->opos = 0; + + /* increment */ + rate->opos_inc = ((uint64_t) inrate << 32) / outrate; + + rate->ipos = 0; + rate->ilast.l = 0; + rate->ilast.r = 0; + return rate; +} + +#define NAME st_rate_flow_mix +#define OP(a, b) a += b +#include "rate_template.h" + +#define NAME st_rate_flow +#define OP(a, b) a = b +#include "rate_template.h" + +void st_rate_stop (void *opaque) +{ + qemu_free (opaque); +} + +void mixeng_clear (struct st_sample *buf, int len) +{ + memset (buf, 0, len * sizeof (struct st_sample)); +} + +void mixeng_volume (struct st_sample *buf, int len, struct mixeng_volume *vol) +{ +#ifdef CONFIG_MIXEMU + if (vol->mute) { + mixeng_clear (buf, len); + return; + } + + while (len--) { +#ifdef FLOAT_MIXENG + buf->l = buf->l * vol->l; + buf->r = buf->r * vol->r; +#else + buf->l = (buf->l * vol->l) >> 32; + buf->r = (buf->r * vol->r) >> 32; +#endif + buf += 1; + } +#else + (void) buf; + (void) len; + (void) vol; +#endif +} diff --git a/audio/mixeng.h b/audio/mixeng.h new file mode 100644 index 0000000..9de443b --- /dev/null +++ b/audio/mixeng.h @@ -0,0 +1,51 @@ +/* + * QEMU Mixing engine header + * + * 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. + */ +#ifndef QEMU_MIXENG_H +#define QEMU_MIXENG_H + +#ifdef FLOAT_MIXENG +typedef float mixeng_real; +struct mixeng_volume { int mute; mixeng_real r; mixeng_real l; }; +struct st_sample { mixeng_real l; mixeng_real r; }; +#else +struct mixeng_volume { int mute; int64_t r; int64_t l; }; +struct st_sample { int64_t l; int64_t r; }; +#endif + +typedef void (t_sample) (struct st_sample *dst, const void *src, int samples); +typedef void (f_sample) (void *dst, const struct st_sample *src, int samples); + +extern t_sample *mixeng_conv[2][2][2][3]; +extern f_sample *mixeng_clip[2][2][2][3]; + +void *st_rate_start (int inrate, int outrate); +void st_rate_flow (void *opaque, struct st_sample *ibuf, struct st_sample *obuf, + int *isamp, int *osamp); +void st_rate_flow_mix (void *opaque, struct st_sample *ibuf, struct st_sample *obuf, + int *isamp, int *osamp); +void st_rate_stop (void *opaque); +void mixeng_clear (struct st_sample *buf, int len); +void mixeng_volume (struct st_sample *buf, int len, struct mixeng_volume *vol); + +#endif /* mixeng.h */ diff --git a/audio/mixeng_template.h b/audio/mixeng_template.h new file mode 100644 index 0000000..a2d0ef8 --- /dev/null +++ b/audio/mixeng_template.h @@ -0,0 +1,152 @@ +/* + * QEMU Mixing engine + * + * 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. + */ + +/* + * Tusen tack till Mike Nordell + * dec++'ified by Dscho + */ + +#ifndef SIGNED +#define HALF (IN_MAX >> 1) +#endif + +#define ET glue (ENDIAN_CONVERSION, glue (_, IN_T)) + +#ifdef FLOAT_MIXENG +static mixeng_real inline glue (conv_, ET) (IN_T v) +{ + IN_T nv = ENDIAN_CONVERT (v); + +#ifdef RECIPROCAL +#ifdef SIGNED + return nv * (1.f / (mixeng_real) (IN_MAX - IN_MIN)); +#else + return (nv - HALF) * (1.f / (mixeng_real) IN_MAX); +#endif +#else /* !RECIPROCAL */ +#ifdef SIGNED + return nv / (mixeng_real) (IN_MAX - IN_MIN); +#else + return (nv - HALF) / (mixeng_real) IN_MAX; +#endif +#endif +} + +static IN_T inline glue (clip_, ET) (mixeng_real v) +{ + if (v >= 0.5) { + return IN_MAX; + } + else if (v < -0.5) { + return IN_MIN; + } + +#ifdef SIGNED + return ENDIAN_CONVERT ((IN_T) (v * (IN_MAX - IN_MIN))); +#else + return ENDIAN_CONVERT ((IN_T) ((v * IN_MAX) + HALF)); +#endif +} + +#else /* !FLOAT_MIXENG */ + +static inline int64_t glue (conv_, ET) (IN_T v) +{ + IN_T nv = ENDIAN_CONVERT (v); +#ifdef SIGNED + return ((int64_t) nv) << (32 - SHIFT); +#else + return ((int64_t) nv - HALF) << (32 - SHIFT); +#endif +} + +static inline IN_T glue (clip_, ET) (int64_t v) +{ + if (v >= 0x7f000000) { + return IN_MAX; + } + else if (v < -2147483648LL) { + return IN_MIN; + } + +#ifdef SIGNED + return ENDIAN_CONVERT ((IN_T) (v >> (32 - SHIFT))); +#else + return ENDIAN_CONVERT ((IN_T) ((v >> (32 - SHIFT)) + HALF)); +#endif +} +#endif + +static void glue (glue (conv_, ET), _to_stereo) + (struct st_sample *dst, const void *src, int samples) +{ + struct st_sample *out = dst; + IN_T *in = (IN_T *) src; + + while (samples--) { + out->l = glue (conv_, ET) (*in++); + out->r = glue (conv_, ET) (*in++); + out += 1; + } +} + +static void glue (glue (conv_, ET), _to_mono) + (struct st_sample *dst, const void *src, int samples) +{ + struct st_sample *out = dst; + IN_T *in = (IN_T *) src; + + while (samples--) { + out->l = glue (conv_, ET) (in[0]); + out->r = out->l; + out += 1; + in += 1; + } +} + +static void glue (glue (clip_, ET), _from_stereo) + (void *dst, const struct st_sample *src, int samples) +{ + const struct st_sample *in = src; + IN_T *out = (IN_T *) dst; + while (samples--) { + *out++ = glue (clip_, ET) (in->l); + *out++ = glue (clip_, ET) (in->r); + in += 1; + } +} + +static void glue (glue (clip_, ET), _from_mono) + (void *dst, const struct st_sample *src, int samples) +{ + const struct st_sample *in = src; + IN_T *out = (IN_T *) dst; + while (samples--) { + *out++ = glue (clip_, ET) (in->l + in->r); + in += 1; + } +} + +#undef ET +#undef HALF diff --git a/audio/noaudio.c b/audio/noaudio.c new file mode 100644 index 0000000..0304094 --- /dev/null +++ b/audio/noaudio.c @@ -0,0 +1,173 @@ +/* + * QEMU Timer based audio emulation + * + * 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 "qemu-common.h" +#include "audio.h" +#include "qemu-timer.h" + +#define AUDIO_CAP "noaudio" +#include "audio_int.h" + +typedef struct NoVoiceOut { + HWVoiceOut hw; + int64_t old_ticks; +} NoVoiceOut; + +typedef struct NoVoiceIn { + HWVoiceIn hw; + int64_t old_ticks; +} NoVoiceIn; + +static int no_run_out (HWVoiceOut *hw, int live) +{ + NoVoiceOut *no = (NoVoiceOut *) hw; + int decr, samples; + int64_t now; + int64_t ticks; + int64_t bytes; + + now = qemu_get_clock (vm_clock); + ticks = now - no->old_ticks; + bytes = muldiv64 (ticks, hw->info.bytes_per_second, get_ticks_per_sec ()); + bytes = audio_MIN (bytes, INT_MAX); + samples = bytes >> hw->info.shift; + + no->old_ticks = now; + decr = audio_MIN (live, samples); + hw->rpos = (hw->rpos + decr) % hw->samples; + return decr; +} + +static int no_write (SWVoiceOut *sw, void *buf, int len) +{ + return audio_pcm_sw_write (sw, buf, len); +} + +static int no_init_out (HWVoiceOut *hw, struct audsettings *as) +{ + audio_pcm_init_info (&hw->info, as); + hw->samples = 1024; + return 0; +} + +static void no_fini_out (HWVoiceOut *hw) +{ + (void) hw; +} + +static int no_ctl_out (HWVoiceOut *hw, int cmd, ...) +{ + (void) hw; + (void) cmd; + return 0; +} + +static int no_init_in (HWVoiceIn *hw, struct audsettings *as) +{ + audio_pcm_init_info (&hw->info, as); + hw->samples = 1024; + return 0; +} + +static void no_fini_in (HWVoiceIn *hw) +{ + (void) hw; +} + +static int no_run_in (HWVoiceIn *hw) +{ + NoVoiceIn *no = (NoVoiceIn *) hw; + int live = audio_pcm_hw_get_live_in (hw); + int dead = hw->samples - live; + int samples = 0; + + if (dead) { + int64_t now = qemu_get_clock (vm_clock); + int64_t ticks = now - no->old_ticks; + int64_t bytes = + muldiv64 (ticks, hw->info.bytes_per_second, get_ticks_per_sec ()); + + no->old_ticks = now; + bytes = audio_MIN (bytes, INT_MAX); + samples = bytes >> hw->info.shift; + samples = audio_MIN (samples, dead); + } + return samples; +} + +static int no_read (SWVoiceIn *sw, void *buf, int size) +{ + /* use custom code here instead of audio_pcm_sw_read() to avoid + * useless resampling/mixing */ + int samples = size >> sw->info.shift; + int total = sw->hw->total_samples_captured - sw->total_hw_samples_acquired; + int to_clear = audio_MIN (samples, total); + sw->total_hw_samples_acquired += total; + audio_pcm_info_clear_buf (&sw->info, buf, to_clear); + return to_clear << sw->info.shift; +} + +static int no_ctl_in (HWVoiceIn *hw, int cmd, ...) +{ + (void) hw; + (void) cmd; + return 0; +} + +static void *no_audio_init (void) +{ + return &no_audio_init; +} + +static void no_audio_fini (void *opaque) +{ + (void) opaque; +} + +static struct audio_pcm_ops no_pcm_ops = { + .init_out = no_init_out, + .fini_out = no_fini_out, + .run_out = no_run_out, + .write = no_write, + .ctl_out = no_ctl_out, + + .init_in = no_init_in, + .fini_in = no_fini_in, + .run_in = no_run_in, + .read = no_read, + .ctl_in = no_ctl_in +}; + +struct audio_driver no_audio_driver = { + .name = "none", + .descr = "Timer based audio emulation", + .options = NULL, + .init = no_audio_init, + .fini = no_audio_fini, + .pcm_ops = &no_pcm_ops, + .can_be_default = 1, + .max_voices_out = INT_MAX, + .max_voices_in = INT_MAX, + .voice_size_out = sizeof (NoVoiceOut), + .voice_size_in = sizeof (NoVoiceIn) +}; diff --git a/audio/ossaudio.c b/audio/ossaudio.c new file mode 100644 index 0000000..b49e102 --- /dev/null +++ b/audio/ossaudio.c @@ -0,0 +1,944 @@ +/* + * QEMU OSS audio driver + * + * 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 +#include +#include +#include +#ifdef __OpenBSD__ +#include +#else +#include +#endif +#include "qemu-common.h" +#include "host-utils.h" +#include "qemu-char.h" +#include "audio.h" + +#define AUDIO_CAP "oss" +#include "audio_int.h" + +#if defined OSS_GETVERSION && defined SNDCTL_DSP_POLICY +#define USE_DSP_POLICY +#endif + +typedef struct OSSVoiceOut { + HWVoiceOut hw; + void *pcm_buf; + int fd; + int wpos; + int nfrags; + int fragsize; + int mmapped; + int pending; +} OSSVoiceOut; + +typedef struct OSSVoiceIn { + HWVoiceIn hw; + void *pcm_buf; + int fd; + int nfrags; + int fragsize; +} OSSVoiceIn; + +static struct { + int try_mmap; + int nfrags; + int fragsize; + const char *devpath_out; + const char *devpath_in; + int debug; + int exclusive; + int policy; +} conf = { + .try_mmap = 0, + .nfrags = 4, + .fragsize = 4096, + .devpath_out = "/dev/dsp", + .devpath_in = "/dev/dsp", + .debug = 0, + .exclusive = 0, + .policy = 5 +}; + +struct oss_params { + int freq; + audfmt_e fmt; + int nchannels; + int nfrags; + int fragsize; +}; + +static void GCC_FMT_ATTR (2, 3) oss_logerr (int err, const char *fmt, ...) +{ + va_list ap; + + va_start (ap, fmt); + AUD_vlog (AUDIO_CAP, fmt, ap); + va_end (ap); + + AUD_log (AUDIO_CAP, "Reason: %s\n", strerror (err)); +} + +static void GCC_FMT_ATTR (3, 4) oss_logerr2 ( + int err, + const char *typ, + const char *fmt, + ... + ) +{ + va_list ap; + + AUD_log (AUDIO_CAP, "Could not initialize %s\n", typ); + + va_start (ap, fmt); + AUD_vlog (AUDIO_CAP, fmt, ap); + va_end (ap); + + AUD_log (AUDIO_CAP, "Reason: %s\n", strerror (err)); +} + +static void oss_anal_close (int *fdp) +{ + int err; + + qemu_set_fd_handler (*fdp, NULL, NULL, NULL); + err = close (*fdp); + if (err) { + oss_logerr (errno, "Failed to close file(fd=%d)\n", *fdp); + } + *fdp = -1; +} + +static void oss_helper_poll_out (void *opaque) +{ + (void) opaque; + audio_run ("oss_poll_out"); +} + +static void oss_helper_poll_in (void *opaque) +{ + (void) opaque; + audio_run ("oss_poll_in"); +} + +static int oss_poll_out (HWVoiceOut *hw) +{ + OSSVoiceOut *oss = (OSSVoiceOut *) hw; + + return qemu_set_fd_handler (oss->fd, NULL, oss_helper_poll_out, NULL); +} + +static int oss_poll_in (HWVoiceIn *hw) +{ + OSSVoiceIn *oss = (OSSVoiceIn *) hw; + + return qemu_set_fd_handler (oss->fd, oss_helper_poll_in, NULL, NULL); +} + +static int oss_write (SWVoiceOut *sw, void *buf, int len) +{ + return audio_pcm_sw_write (sw, buf, len); +} + +static int aud_to_ossfmt (audfmt_e fmt, int endianness) +{ + switch (fmt) { + case AUD_FMT_S8: + return AFMT_S8; + + case AUD_FMT_U8: + return AFMT_U8; + + case AUD_FMT_S16: + if (endianness) { + return AFMT_S16_BE; + } + else { + return AFMT_S16_LE; + } + + case AUD_FMT_U16: + if (endianness) { + return AFMT_U16_BE; + } + else { + return AFMT_U16_LE; + } + + default: + dolog ("Internal logic error: Bad audio format %d\n", fmt); +#ifdef DEBUG_AUDIO + abort (); +#endif + return AFMT_U8; + } +} + +static int oss_to_audfmt (int ossfmt, audfmt_e *fmt, int *endianness) +{ + switch (ossfmt) { + case AFMT_S8: + *endianness = 0; + *fmt = AUD_FMT_S8; + break; + + case AFMT_U8: + *endianness = 0; + *fmt = AUD_FMT_U8; + break; + + case AFMT_S16_LE: + *endianness = 0; + *fmt = AUD_FMT_S16; + break; + + case AFMT_U16_LE: + *endianness = 0; + *fmt = AUD_FMT_U16; + break; + + case AFMT_S16_BE: + *endianness = 1; + *fmt = AUD_FMT_S16; + break; + + case AFMT_U16_BE: + *endianness = 1; + *fmt = AUD_FMT_U16; + break; + + default: + dolog ("Unrecognized audio format %d\n", ossfmt); + return -1; + } + + return 0; +} + +#if defined DEBUG_MISMATCHES || defined DEBUG +static void oss_dump_info (struct oss_params *req, struct oss_params *obt) +{ + dolog ("parameter | requested value | obtained value\n"); + dolog ("format | %10d | %10d\n", req->fmt, obt->fmt); + dolog ("channels | %10d | %10d\n", + req->nchannels, obt->nchannels); + dolog ("frequency | %10d | %10d\n", req->freq, obt->freq); + dolog ("nfrags | %10d | %10d\n", req->nfrags, obt->nfrags); + dolog ("fragsize | %10d | %10d\n", + req->fragsize, obt->fragsize); +} +#endif + +#ifdef USE_DSP_POLICY +static int oss_get_version (int fd, int *version, const char *typ) +{ + if (ioctl (fd, OSS_GETVERSION, &version)) { +#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) + /* + * Looks like atm (20100109) FreeBSD knows OSS_GETVERSION + * since 7.x, but currently only on the mixer device (or in + * the Linuxolator), and in the native version that part of + * the code is in fact never reached so the ioctl fails anyway. + * Until this is fixed, just check the errno and if its what + * FreeBSD's sound drivers return atm assume they are new enough. + */ + if (errno == EINVAL) { + *version = 0x040000; + return 0; + } +#endif + oss_logerr2 (errno, typ, "Failed to get OSS version\n"); + return -1; + } + return 0; +} +#endif + +static int oss_open (int in, struct oss_params *req, + struct oss_params *obt, int *pfd) +{ + int fd; + int oflags = conf.exclusive ? O_EXCL : 0; + audio_buf_info abinfo; + int fmt, freq, nchannels; + int setfragment = 1; + const char *dspname = in ? conf.devpath_in : conf.devpath_out; + const char *typ = in ? "ADC" : "DAC"; + + /* Kludge needed to have working mmap on Linux */ + oflags |= conf.try_mmap ? O_RDWR : (in ? O_RDONLY : O_WRONLY); + + fd = open (dspname, oflags | O_NONBLOCK); + if (-1 == fd) { + oss_logerr2 (errno, typ, "Failed to open `%s'\n", dspname); + return -1; + } + + freq = req->freq; + nchannels = req->nchannels; + fmt = req->fmt; + + if (ioctl (fd, SNDCTL_DSP_SAMPLESIZE, &fmt)) { + oss_logerr2 (errno, typ, "Failed to set sample size %d\n", req->fmt); + goto err; + } + + if (ioctl (fd, SNDCTL_DSP_CHANNELS, &nchannels)) { + oss_logerr2 (errno, typ, "Failed to set number of channels %d\n", + req->nchannels); + goto err; + } + + if (ioctl (fd, SNDCTL_DSP_SPEED, &freq)) { + oss_logerr2 (errno, typ, "Failed to set frequency %d\n", req->freq); + goto err; + } + + if (ioctl (fd, SNDCTL_DSP_NONBLOCK, NULL)) { + oss_logerr2 (errno, typ, "Failed to set non-blocking mode\n"); + goto err; + } + +#ifdef USE_DSP_POLICY + if (conf.policy >= 0) { + int version; + + if (!oss_get_version (fd, &version, typ)) { + if (conf.debug) { + dolog ("OSS version = %#x\n", version); + } + + if (version >= 0x040000) { + int policy = conf.policy; + if (ioctl (fd, SNDCTL_DSP_POLICY, &policy)) { + oss_logerr2 (errno, typ, + "Failed to set timing policy to %d\n", + conf.policy); + goto err; + } + setfragment = 0; + } + } + } +#endif + + if (setfragment) { + int mmmmssss = (req->nfrags << 16) | ctz32 (req->fragsize); + if (ioctl (fd, SNDCTL_DSP_SETFRAGMENT, &mmmmssss)) { + oss_logerr2 (errno, typ, "Failed to set buffer length (%d, %d)\n", + req->nfrags, req->fragsize); + goto err; + } + } + + if (ioctl (fd, in ? SNDCTL_DSP_GETISPACE : SNDCTL_DSP_GETOSPACE, &abinfo)) { + oss_logerr2 (errno, typ, "Failed to get buffer length\n"); + goto err; + } + + if (!abinfo.fragstotal || !abinfo.fragsize) { + AUD_log (AUDIO_CAP, "Returned bogus buffer information(%d, %d) for %s\n", + abinfo.fragstotal, abinfo.fragsize, typ); + goto err; + } + + obt->fmt = fmt; + obt->nchannels = nchannels; + obt->freq = freq; + obt->nfrags = abinfo.fragstotal; + obt->fragsize = abinfo.fragsize; + *pfd = fd; + +#ifdef DEBUG_MISMATCHES + if ((req->fmt != obt->fmt) || + (req->nchannels != obt->nchannels) || + (req->freq != obt->freq) || + (req->fragsize != obt->fragsize) || + (req->nfrags != obt->nfrags)) { + dolog ("Audio parameters mismatch\n"); + oss_dump_info (req, obt); + } +#endif + +#ifdef DEBUG + oss_dump_info (req, obt); +#endif + return 0; + + err: + oss_anal_close (&fd); + return -1; +} + +static void oss_write_pending (OSSVoiceOut *oss) +{ + HWVoiceOut *hw = &oss->hw; + + if (oss->mmapped) { + return; + } + + while (oss->pending) { + int samples_written; + ssize_t bytes_written; + int samples_till_end = hw->samples - oss->wpos; + int samples_to_write = audio_MIN (oss->pending, samples_till_end); + int bytes_to_write = samples_to_write << hw->info.shift; + void *pcm = advance (oss->pcm_buf, oss->wpos << hw->info.shift); + + bytes_written = write (oss->fd, pcm, bytes_to_write); + if (bytes_written < 0) { + if (errno != EAGAIN) { + oss_logerr (errno, "failed to write %d bytes\n", + bytes_to_write); + } + break; + } + + if (bytes_written & hw->info.align) { + dolog ("misaligned write asked for %d, but got %zd\n", + bytes_to_write, bytes_written); + return; + } + + samples_written = bytes_written >> hw->info.shift; + oss->pending -= samples_written; + oss->wpos = (oss->wpos + samples_written) % hw->samples; + if (bytes_written - bytes_to_write) { + break; + } + } +} + +static int oss_run_out (HWVoiceOut *hw, int live) +{ + OSSVoiceOut *oss = (OSSVoiceOut *) hw; + int err, decr; + struct audio_buf_info abinfo; + struct count_info cntinfo; + int bufsize; + + bufsize = hw->samples << hw->info.shift; + + if (oss->mmapped) { + int bytes, pos; + + err = ioctl (oss->fd, SNDCTL_DSP_GETOPTR, &cntinfo); + if (err < 0) { + oss_logerr (errno, "SNDCTL_DSP_GETOPTR failed\n"); + return 0; + } + + pos = hw->rpos << hw->info.shift; + bytes = audio_ring_dist (cntinfo.ptr, pos, bufsize); + decr = audio_MIN (bytes >> hw->info.shift, live); + } + else { + err = ioctl (oss->fd, SNDCTL_DSP_GETOSPACE, &abinfo); + if (err < 0) { + oss_logerr (errno, "SNDCTL_DSP_GETOPTR failed\n"); + return 0; + } + + if (abinfo.bytes > bufsize) { + if (conf.debug) { + dolog ("warning: Invalid available size, size=%d bufsize=%d\n" + "please report your OS/audio hw to av1474@comtv.ru\n", + abinfo.bytes, bufsize); + } + abinfo.bytes = bufsize; + } + + if (abinfo.bytes < 0) { + if (conf.debug) { + dolog ("warning: Invalid available size, size=%d bufsize=%d\n", + abinfo.bytes, bufsize); + } + return 0; + } + + decr = audio_MIN (abinfo.bytes >> hw->info.shift, live); + if (!decr) { + return 0; + } + } + + decr = audio_pcm_hw_clip_out (hw, oss->pcm_buf, decr, oss->pending); + oss->pending += decr; + oss_write_pending (oss); + + return decr; +} + +static void oss_fini_out (HWVoiceOut *hw) +{ + int err; + OSSVoiceOut *oss = (OSSVoiceOut *) hw; + + ldebug ("oss_fini\n"); + oss_anal_close (&oss->fd); + + if (oss->pcm_buf) { + if (oss->mmapped) { + err = munmap (oss->pcm_buf, hw->samples << hw->info.shift); + if (err) { + oss_logerr (errno, "Failed to unmap buffer %p, size %d\n", + oss->pcm_buf, hw->samples << hw->info.shift); + } + } + else { + qemu_free (oss->pcm_buf); + } + oss->pcm_buf = NULL; + } +} + +static int oss_init_out (HWVoiceOut *hw, struct audsettings *as) +{ + OSSVoiceOut *oss = (OSSVoiceOut *) hw; + struct oss_params req, obt; + int endianness; + int err; + int fd; + audfmt_e effective_fmt; + struct audsettings obt_as; + + oss->fd = -1; + + req.fmt = aud_to_ossfmt (as->fmt, as->endianness); + req.freq = as->freq; + req.nchannels = as->nchannels; + req.fragsize = conf.fragsize; + req.nfrags = conf.nfrags; + + if (oss_open (0, &req, &obt, &fd)) { + return -1; + } + + err = oss_to_audfmt (obt.fmt, &effective_fmt, &endianness); + if (err) { + oss_anal_close (&fd); + return -1; + } + + obt_as.freq = obt.freq; + obt_as.nchannels = obt.nchannels; + obt_as.fmt = effective_fmt; + obt_as.endianness = endianness; + + audio_pcm_init_info (&hw->info, &obt_as); + oss->nfrags = obt.nfrags; + oss->fragsize = obt.fragsize; + + if (obt.nfrags * obt.fragsize & hw->info.align) { + dolog ("warning: Misaligned DAC buffer, size %d, alignment %d\n", + obt.nfrags * obt.fragsize, hw->info.align + 1); + } + + hw->samples = (obt.nfrags * obt.fragsize) >> hw->info.shift; + + oss->mmapped = 0; + if (conf.try_mmap) { + oss->pcm_buf = mmap ( + NULL, + hw->samples << hw->info.shift, + PROT_READ | PROT_WRITE, + MAP_SHARED, + fd, + 0 + ); + if (oss->pcm_buf == MAP_FAILED) { + oss_logerr (errno, "Failed to map %d bytes of DAC\n", + hw->samples << hw->info.shift); + } + else { + int err; + int trig = 0; + if (ioctl (fd, SNDCTL_DSP_SETTRIGGER, &trig) < 0) { + oss_logerr (errno, "SNDCTL_DSP_SETTRIGGER 0 failed\n"); + } + else { + trig = PCM_ENABLE_OUTPUT; + if (ioctl (fd, SNDCTL_DSP_SETTRIGGER, &trig) < 0) { + oss_logerr ( + errno, + "SNDCTL_DSP_SETTRIGGER PCM_ENABLE_OUTPUT failed\n" + ); + } + else { + oss->mmapped = 1; + } + } + + if (!oss->mmapped) { + err = munmap (oss->pcm_buf, hw->samples << hw->info.shift); + if (err) { + oss_logerr (errno, "Failed to unmap buffer %p size %d\n", + oss->pcm_buf, hw->samples << hw->info.shift); + } + } + } + } + + if (!oss->mmapped) { + oss->pcm_buf = audio_calloc ( + AUDIO_FUNC, + hw->samples, + 1 << hw->info.shift + ); + if (!oss->pcm_buf) { + dolog ( + "Could not allocate DAC buffer (%d samples, each %d bytes)\n", + hw->samples, + 1 << hw->info.shift + ); + oss_anal_close (&fd); + return -1; + } + } + + oss->fd = fd; + return 0; +} + +static int oss_ctl_out (HWVoiceOut *hw, int cmd, ...) +{ + int trig; + OSSVoiceOut *oss = (OSSVoiceOut *) hw; + + switch (cmd) { + case VOICE_ENABLE: + { + va_list ap; + int poll_mode; + + va_start (ap, cmd); + poll_mode = va_arg (ap, int); + va_end (ap); + + ldebug ("enabling voice\n"); + if (poll_mode && oss_poll_out (hw)) { + poll_mode = 0; + } + hw->poll_mode = poll_mode; + + if (!oss->mmapped) { + return 0; + } + + audio_pcm_info_clear_buf (&hw->info, oss->pcm_buf, hw->samples); + trig = PCM_ENABLE_OUTPUT; + if (ioctl (oss->fd, SNDCTL_DSP_SETTRIGGER, &trig) < 0) { + oss_logerr ( + errno, + "SNDCTL_DSP_SETTRIGGER PCM_ENABLE_OUTPUT failed\n" + ); + return -1; + } + } + break; + + case VOICE_DISABLE: + if (hw->poll_mode) { + qemu_set_fd_handler (oss->fd, NULL, NULL, NULL); + hw->poll_mode = 0; + } + + if (!oss->mmapped) { + return 0; + } + + ldebug ("disabling voice\n"); + trig = 0; + if (ioctl (oss->fd, SNDCTL_DSP_SETTRIGGER, &trig) < 0) { + oss_logerr (errno, "SNDCTL_DSP_SETTRIGGER 0 failed\n"); + return -1; + } + break; + } + return 0; +} + +static int oss_init_in (HWVoiceIn *hw, struct audsettings *as) +{ + OSSVoiceIn *oss = (OSSVoiceIn *) hw; + struct oss_params req, obt; + int endianness; + int err; + int fd; + audfmt_e effective_fmt; + struct audsettings obt_as; + + oss->fd = -1; + + req.fmt = aud_to_ossfmt (as->fmt, as->endianness); + req.freq = as->freq; + req.nchannels = as->nchannels; + req.fragsize = conf.fragsize; + req.nfrags = conf.nfrags; + if (oss_open (1, &req, &obt, &fd)) { + return -1; + } + + err = oss_to_audfmt (obt.fmt, &effective_fmt, &endianness); + if (err) { + oss_anal_close (&fd); + return -1; + } + + obt_as.freq = obt.freq; + obt_as.nchannels = obt.nchannels; + obt_as.fmt = effective_fmt; + obt_as.endianness = endianness; + + audio_pcm_init_info (&hw->info, &obt_as); + oss->nfrags = obt.nfrags; + oss->fragsize = obt.fragsize; + + if (obt.nfrags * obt.fragsize & hw->info.align) { + dolog ("warning: Misaligned ADC buffer, size %d, alignment %d\n", + obt.nfrags * obt.fragsize, hw->info.align + 1); + } + + hw->samples = (obt.nfrags * obt.fragsize) >> hw->info.shift; + oss->pcm_buf = audio_calloc (AUDIO_FUNC, hw->samples, 1 << hw->info.shift); + if (!oss->pcm_buf) { + dolog ("Could not allocate ADC buffer (%d samples, each %d bytes)\n", + hw->samples, 1 << hw->info.shift); + oss_anal_close (&fd); + return -1; + } + + oss->fd = fd; + return 0; +} + +static void oss_fini_in (HWVoiceIn *hw) +{ + OSSVoiceIn *oss = (OSSVoiceIn *) hw; + + oss_anal_close (&oss->fd); + + if (oss->pcm_buf) { + qemu_free (oss->pcm_buf); + oss->pcm_buf = NULL; + } +} + +static int oss_run_in (HWVoiceIn *hw) +{ + OSSVoiceIn *oss = (OSSVoiceIn *) hw; + int hwshift = hw->info.shift; + int i; + int live = audio_pcm_hw_get_live_in (hw); + int dead = hw->samples - live; + size_t read_samples = 0; + struct { + int add; + int len; + } bufs[2] = { + { .add = hw->wpos, .len = 0 }, + { .add = 0, .len = 0 } + }; + + if (!dead) { + return 0; + } + + if (hw->wpos + dead > hw->samples) { + bufs[0].len = (hw->samples - hw->wpos) << hwshift; + bufs[1].len = (dead - (hw->samples - hw->wpos)) << hwshift; + } + else { + bufs[0].len = dead << hwshift; + } + + for (i = 0; i < 2; ++i) { + ssize_t nread; + + if (bufs[i].len) { + void *p = advance (oss->pcm_buf, bufs[i].add << hwshift); + nread = read (oss->fd, p, bufs[i].len); + + if (nread > 0) { + if (nread & hw->info.align) { + dolog ("warning: Misaligned read %zd (requested %d), " + "alignment %d\n", nread, bufs[i].add << hwshift, + hw->info.align + 1); + } + read_samples += nread >> hwshift; + hw->conv (hw->conv_buf + bufs[i].add, p, nread >> hwshift); + } + + if (bufs[i].len - nread) { + if (nread == -1) { + switch (errno) { + case EINTR: + case EAGAIN: + break; + default: + oss_logerr ( + errno, + "Failed to read %d bytes of audio (to %p)\n", + bufs[i].len, p + ); + break; + } + } + break; + } + } + } + + hw->wpos = (hw->wpos + read_samples) % hw->samples; + return read_samples; +} + +static int oss_read (SWVoiceIn *sw, void *buf, int size) +{ + return audio_pcm_sw_read (sw, buf, size); +} + +static int oss_ctl_in (HWVoiceIn *hw, int cmd, ...) +{ + OSSVoiceIn *oss = (OSSVoiceIn *) hw; + + switch (cmd) { + case VOICE_ENABLE: + { + va_list ap; + int poll_mode; + + va_start (ap, cmd); + poll_mode = va_arg (ap, int); + va_end (ap); + + if (poll_mode && oss_poll_in (hw)) { + poll_mode = 0; + } + hw->poll_mode = poll_mode; + } + break; + + case VOICE_DISABLE: + if (hw->poll_mode) { + hw->poll_mode = 0; + qemu_set_fd_handler (oss->fd, NULL, NULL, NULL); + } + break; + } + return 0; +} + +static void *oss_audio_init (void) +{ + return &conf; +} + +static void oss_audio_fini (void *opaque) +{ + (void) opaque; +} + +static struct audio_option oss_options[] = { + { + .name = "FRAGSIZE", + .tag = AUD_OPT_INT, + .valp = &conf.fragsize, + .descr = "Fragment size in bytes" + }, + { + .name = "NFRAGS", + .tag = AUD_OPT_INT, + .valp = &conf.nfrags, + .descr = "Number of fragments" + }, + { + .name = "MMAP", + .tag = AUD_OPT_BOOL, + .valp = &conf.try_mmap, + .descr = "Try using memory mapped access" + }, + { + .name = "DAC_DEV", + .tag = AUD_OPT_STR, + .valp = &conf.devpath_out, + .descr = "Path to DAC device" + }, + { + .name = "ADC_DEV", + .tag = AUD_OPT_STR, + .valp = &conf.devpath_in, + .descr = "Path to ADC device" + }, + { + .name = "EXCLUSIVE", + .tag = AUD_OPT_BOOL, + .valp = &conf.exclusive, + .descr = "Open device in exclusive mode (vmix wont work)" + }, +#ifdef USE_DSP_POLICY + { + .name = "POLICY", + .tag = AUD_OPT_INT, + .valp = &conf.policy, + .descr = "Set the timing policy of the device, -1 to use fragment mode", + }, +#endif + { + .name = "DEBUG", + .tag = AUD_OPT_BOOL, + .valp = &conf.debug, + .descr = "Turn on some debugging messages" + }, + { /* End of list */ } +}; + +static struct audio_pcm_ops oss_pcm_ops = { + .init_out = oss_init_out, + .fini_out = oss_fini_out, + .run_out = oss_run_out, + .write = oss_write, + .ctl_out = oss_ctl_out, + + .init_in = oss_init_in, + .fini_in = oss_fini_in, + .run_in = oss_run_in, + .read = oss_read, + .ctl_in = oss_ctl_in +}; + +struct audio_driver oss_audio_driver = { + .name = "oss", + .descr = "OSS http://www.opensound.com", + .options = oss_options, + .init = oss_audio_init, + .fini = oss_audio_fini, + .pcm_ops = &oss_pcm_ops, + .can_be_default = 1, + .max_voices_out = INT_MAX, + .max_voices_in = INT_MAX, + .voice_size_out = sizeof (OSSVoiceOut), + .voice_size_in = sizeof (OSSVoiceIn) +}; diff --git a/audio/paaudio.c b/audio/paaudio.c new file mode 100644 index 0000000..fb4510e --- /dev/null +++ b/audio/paaudio.c @@ -0,0 +1,525 @@ +/* public domain */ +#include "qemu-common.h" +#include "audio.h" + +#include +#include + +#define AUDIO_CAP "pulseaudio" +#include "audio_int.h" +#include "audio_pt_int.h" + +typedef struct { + HWVoiceOut hw; + int done; + int live; + int decr; + int rpos; + pa_simple *s; + void *pcm_buf; + struct audio_pt pt; +} PAVoiceOut; + +typedef struct { + HWVoiceIn hw; + int done; + int dead; + int incr; + int wpos; + pa_simple *s; + void *pcm_buf; + struct audio_pt pt; +} PAVoiceIn; + +static struct { + int samples; + char *server; + char *sink; + char *source; +} conf = { + .samples = 4096, +}; + +static void GCC_FMT_ATTR (2, 3) qpa_logerr (int err, const char *fmt, ...) +{ + va_list ap; + + va_start (ap, fmt); + AUD_vlog (AUDIO_CAP, fmt, ap); + va_end (ap); + + AUD_log (AUDIO_CAP, "Reason: %s\n", pa_strerror (err)); +} + +static void *qpa_thread_out (void *arg) +{ + PAVoiceOut *pa = arg; + HWVoiceOut *hw = &pa->hw; + + if (audio_pt_lock (&pa->pt, AUDIO_FUNC)) { + return NULL; + } + + for (;;) { + int decr, to_mix, rpos; + + for (;;) { + if (pa->done) { + goto exit; + } + + if (pa->live > 0) { + break; + } + + if (audio_pt_wait (&pa->pt, AUDIO_FUNC)) { + goto exit; + } + } + + decr = to_mix = audio_MIN (pa->live, conf.samples >> 2); + rpos = pa->rpos; + + if (audio_pt_unlock (&pa->pt, AUDIO_FUNC)) { + return NULL; + } + + while (to_mix) { + int error; + int chunk = audio_MIN (to_mix, hw->samples - rpos); + struct st_sample *src = hw->mix_buf + rpos; + + hw->clip (pa->pcm_buf, src, chunk); + + if (pa_simple_write (pa->s, pa->pcm_buf, + chunk << hw->info.shift, &error) < 0) { + qpa_logerr (error, "pa_simple_write failed\n"); + return NULL; + } + + rpos = (rpos + chunk) % hw->samples; + to_mix -= chunk; + } + + if (audio_pt_lock (&pa->pt, AUDIO_FUNC)) { + return NULL; + } + + pa->rpos = rpos; + pa->live -= decr; + pa->decr += decr; + } + + exit: + audio_pt_unlock (&pa->pt, AUDIO_FUNC); + return NULL; +} + +static int qpa_run_out (HWVoiceOut *hw, int live) +{ + int decr; + PAVoiceOut *pa = (PAVoiceOut *) hw; + + if (audio_pt_lock (&pa->pt, AUDIO_FUNC)) { + return 0; + } + + decr = audio_MIN (live, pa->decr); + pa->decr -= decr; + pa->live = live - decr; + hw->rpos = pa->rpos; + if (pa->live > 0) { + audio_pt_unlock_and_signal (&pa->pt, AUDIO_FUNC); + } + else { + audio_pt_unlock (&pa->pt, AUDIO_FUNC); + } + return decr; +} + +static int qpa_write (SWVoiceOut *sw, void *buf, int len) +{ + return audio_pcm_sw_write (sw, buf, len); +} + +/* capture */ +static void *qpa_thread_in (void *arg) +{ + PAVoiceIn *pa = arg; + HWVoiceIn *hw = &pa->hw; + + if (audio_pt_lock (&pa->pt, AUDIO_FUNC)) { + return NULL; + } + + for (;;) { + int incr, to_grab, wpos; + + for (;;) { + if (pa->done) { + goto exit; + } + + if (pa->dead > 0) { + break; + } + + if (audio_pt_wait (&pa->pt, AUDIO_FUNC)) { + goto exit; + } + } + + incr = to_grab = audio_MIN (pa->dead, conf.samples >> 2); + wpos = pa->wpos; + + if (audio_pt_unlock (&pa->pt, AUDIO_FUNC)) { + return NULL; + } + + while (to_grab) { + int error; + int chunk = audio_MIN (to_grab, hw->samples - wpos); + void *buf = advance (pa->pcm_buf, wpos); + + if (pa_simple_read (pa->s, buf, + chunk << hw->info.shift, &error) < 0) { + qpa_logerr (error, "pa_simple_read failed\n"); + return NULL; + } + + hw->conv (hw->conv_buf + wpos, buf, chunk); + wpos = (wpos + chunk) % hw->samples; + to_grab -= chunk; + } + + if (audio_pt_lock (&pa->pt, AUDIO_FUNC)) { + return NULL; + } + + pa->wpos = wpos; + pa->dead -= incr; + pa->incr += incr; + } + + exit: + audio_pt_unlock (&pa->pt, AUDIO_FUNC); + return NULL; +} + +static int qpa_run_in (HWVoiceIn *hw) +{ + int live, incr, dead; + PAVoiceIn *pa = (PAVoiceIn *) hw; + + if (audio_pt_lock (&pa->pt, AUDIO_FUNC)) { + return 0; + } + + live = audio_pcm_hw_get_live_in (hw); + dead = hw->samples - live; + incr = audio_MIN (dead, pa->incr); + pa->incr -= incr; + pa->dead = dead - incr; + hw->wpos = pa->wpos; + if (pa->dead > 0) { + audio_pt_unlock_and_signal (&pa->pt, AUDIO_FUNC); + } + else { + audio_pt_unlock (&pa->pt, AUDIO_FUNC); + } + return incr; +} + +static int qpa_read (SWVoiceIn *sw, void *buf, int len) +{ + return audio_pcm_sw_read (sw, buf, len); +} + +static pa_sample_format_t audfmt_to_pa (audfmt_e afmt, int endianness) +{ + int format; + + switch (afmt) { + case AUD_FMT_S8: + case AUD_FMT_U8: + format = PA_SAMPLE_U8; + break; + case AUD_FMT_S16: + case AUD_FMT_U16: + format = endianness ? PA_SAMPLE_S16BE : PA_SAMPLE_S16LE; + break; + case AUD_FMT_S32: + case AUD_FMT_U32: + format = endianness ? PA_SAMPLE_S32BE : PA_SAMPLE_S32LE; + break; + default: + dolog ("Internal logic error: Bad audio format %d\n", afmt); + format = PA_SAMPLE_U8; + break; + } + return format; +} + +static audfmt_e pa_to_audfmt (pa_sample_format_t fmt, int *endianness) +{ + switch (fmt) { + case PA_SAMPLE_U8: + return AUD_FMT_U8; + case PA_SAMPLE_S16BE: + *endianness = 1; + return AUD_FMT_S16; + case PA_SAMPLE_S16LE: + *endianness = 0; + return AUD_FMT_S16; + case PA_SAMPLE_S32BE: + *endianness = 1; + return AUD_FMT_S32; + case PA_SAMPLE_S32LE: + *endianness = 0; + return AUD_FMT_S32; + default: + dolog ("Internal logic error: Bad pa_sample_format %d\n", fmt); + return AUD_FMT_U8; + } +} + +static int qpa_init_out (HWVoiceOut *hw, struct audsettings *as) +{ + int error; + static pa_sample_spec ss; + static pa_buffer_attr ba; + struct audsettings obt_as = *as; + PAVoiceOut *pa = (PAVoiceOut *) hw; + + ss.format = audfmt_to_pa (as->fmt, as->endianness); + ss.channels = as->nchannels; + ss.rate = as->freq; + + /* + * qemu audio tick runs at 250 Hz (by default), so processing + * data chunks worth 4 ms of sound should be a good fit. + */ + ba.tlength = pa_usec_to_bytes (4 * 1000, &ss); + ba.minreq = pa_usec_to_bytes (2 * 1000, &ss); + ba.maxlength = -1; + ba.prebuf = -1; + + obt_as.fmt = pa_to_audfmt (ss.format, &obt_as.endianness); + + pa->s = pa_simple_new ( + conf.server, + "qemu", + PA_STREAM_PLAYBACK, + conf.sink, + "pcm.playback", + &ss, + NULL, /* channel map */ + &ba, /* buffering attributes */ + &error + ); + if (!pa->s) { + qpa_logerr (error, "pa_simple_new for playback failed\n"); + goto fail1; + } + + audio_pcm_init_info (&hw->info, &obt_as); + hw->samples = conf.samples; + pa->pcm_buf = audio_calloc (AUDIO_FUNC, hw->samples, 1 << hw->info.shift); + pa->rpos = hw->rpos; + if (!pa->pcm_buf) { + dolog ("Could not allocate buffer (%d bytes)\n", + hw->samples << hw->info.shift); + goto fail2; + } + + if (audio_pt_init (&pa->pt, qpa_thread_out, hw, AUDIO_CAP, AUDIO_FUNC)) { + goto fail3; + } + + return 0; + + fail3: + qemu_free (pa->pcm_buf); + pa->pcm_buf = NULL; + fail2: + pa_simple_free (pa->s); + pa->s = NULL; + fail1: + return -1; +} + +static int qpa_init_in (HWVoiceIn *hw, struct audsettings *as) +{ + int error; + static pa_sample_spec ss; + struct audsettings obt_as = *as; + PAVoiceIn *pa = (PAVoiceIn *) hw; + + ss.format = audfmt_to_pa (as->fmt, as->endianness); + ss.channels = as->nchannels; + ss.rate = as->freq; + + obt_as.fmt = pa_to_audfmt (ss.format, &obt_as.endianness); + + pa->s = pa_simple_new ( + conf.server, + "qemu", + PA_STREAM_RECORD, + conf.source, + "pcm.capture", + &ss, + NULL, /* channel map */ + NULL, /* buffering attributes */ + &error + ); + if (!pa->s) { + qpa_logerr (error, "pa_simple_new for capture failed\n"); + goto fail1; + } + + audio_pcm_init_info (&hw->info, &obt_as); + hw->samples = conf.samples; + pa->pcm_buf = audio_calloc (AUDIO_FUNC, hw->samples, 1 << hw->info.shift); + pa->wpos = hw->wpos; + if (!pa->pcm_buf) { + dolog ("Could not allocate buffer (%d bytes)\n", + hw->samples << hw->info.shift); + goto fail2; + } + + if (audio_pt_init (&pa->pt, qpa_thread_in, hw, AUDIO_CAP, AUDIO_FUNC)) { + goto fail3; + } + + return 0; + + fail3: + qemu_free (pa->pcm_buf); + pa->pcm_buf = NULL; + fail2: + pa_simple_free (pa->s); + pa->s = NULL; + fail1: + return -1; +} + +static void qpa_fini_out (HWVoiceOut *hw) +{ + void *ret; + PAVoiceOut *pa = (PAVoiceOut *) hw; + + audio_pt_lock (&pa->pt, AUDIO_FUNC); + pa->done = 1; + audio_pt_unlock_and_signal (&pa->pt, AUDIO_FUNC); + audio_pt_join (&pa->pt, &ret, AUDIO_FUNC); + + if (pa->s) { + pa_simple_free (pa->s); + pa->s = NULL; + } + + audio_pt_fini (&pa->pt, AUDIO_FUNC); + qemu_free (pa->pcm_buf); + pa->pcm_buf = NULL; +} + +static void qpa_fini_in (HWVoiceIn *hw) +{ + void *ret; + PAVoiceIn *pa = (PAVoiceIn *) hw; + + audio_pt_lock (&pa->pt, AUDIO_FUNC); + pa->done = 1; + audio_pt_unlock_and_signal (&pa->pt, AUDIO_FUNC); + audio_pt_join (&pa->pt, &ret, AUDIO_FUNC); + + if (pa->s) { + pa_simple_free (pa->s); + pa->s = NULL; + } + + audio_pt_fini (&pa->pt, AUDIO_FUNC); + qemu_free (pa->pcm_buf); + pa->pcm_buf = NULL; +} + +static int qpa_ctl_out (HWVoiceOut *hw, int cmd, ...) +{ + (void) hw; + (void) cmd; + return 0; +} + +static int qpa_ctl_in (HWVoiceIn *hw, int cmd, ...) +{ + (void) hw; + (void) cmd; + return 0; +} + +/* common */ +static void *qpa_audio_init (void) +{ + return &conf; +} + +static void qpa_audio_fini (void *opaque) +{ + (void) opaque; +} + +struct audio_option qpa_options[] = { + { + .name = "SAMPLES", + .tag = AUD_OPT_INT, + .valp = &conf.samples, + .descr = "buffer size in samples" + }, + { + .name = "SERVER", + .tag = AUD_OPT_STR, + .valp = &conf.server, + .descr = "server address" + }, + { + .name = "SINK", + .tag = AUD_OPT_STR, + .valp = &conf.sink, + .descr = "sink device name" + }, + { + .name = "SOURCE", + .tag = AUD_OPT_STR, + .valp = &conf.source, + .descr = "source device name" + }, + { /* End of list */ } +}; + +static struct audio_pcm_ops qpa_pcm_ops = { + .init_out = qpa_init_out, + .fini_out = qpa_fini_out, + .run_out = qpa_run_out, + .write = qpa_write, + .ctl_out = qpa_ctl_out, + + .init_in = qpa_init_in, + .fini_in = qpa_fini_in, + .run_in = qpa_run_in, + .read = qpa_read, + .ctl_in = qpa_ctl_in +}; + +struct audio_driver pa_audio_driver = { + .name = "pa", + .descr = "http://www.pulseaudio.org/", + .options = qpa_options, + .init = qpa_audio_init, + .fini = qpa_audio_fini, + .pcm_ops = &qpa_pcm_ops, + .can_be_default = 1, + .max_voices_out = INT_MAX, + .max_voices_in = INT_MAX, + .voice_size_out = sizeof (PAVoiceOut), + .voice_size_in = sizeof (PAVoiceIn) +}; diff --git a/audio/rate_template.h b/audio/rate_template.h new file mode 100644 index 0000000..bd4b1c7 --- /dev/null +++ b/audio/rate_template.h @@ -0,0 +1,111 @@ +/* + * QEMU Mixing engine + * + * Copyright (c) 2004-2005 Vassili Karpov (malc) + * Copyright (c) 1998 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. + */ + +/* + * Processed signed long samples from ibuf to obuf. + * Return number of samples processed. + */ +void NAME (void *opaque, struct st_sample *ibuf, struct st_sample *obuf, + int *isamp, int *osamp) +{ + struct rate *rate = opaque; + struct st_sample *istart, *iend; + struct st_sample *ostart, *oend; + struct st_sample ilast, icur, out; +#ifdef FLOAT_MIXENG + mixeng_real t; +#else + int64_t t; +#endif + + ilast = rate->ilast; + + istart = ibuf; + iend = ibuf + *isamp; + + ostart = obuf; + oend = obuf + *osamp; + + if (rate->opos_inc == (1ULL + UINT_MAX)) { + int i, n = *isamp > *osamp ? *osamp : *isamp; + for (i = 0; i < n; i++) { + OP (obuf[i].l, ibuf[i].l); + OP (obuf[i].r, ibuf[i].r); + } + *isamp = n; + *osamp = n; + return; + } + + while (obuf < oend) { + + /* Safety catch to make sure we have input samples. */ + if (ibuf >= iend) { + break; + } + + /* read as many input samples so that ipos > opos */ + + while (rate->ipos <= (rate->opos >> 32)) { + ilast = *ibuf++; + rate->ipos++; + /* See if we finished the input buffer yet */ + if (ibuf >= iend) { + goto the_end; + } + } + + icur = *ibuf; + + /* interpolate */ +#ifdef FLOAT_MIXENG +#ifdef RECIPROCAL + t = (rate->opos & UINT_MAX) * (1.f / UINT_MAX); +#else + t = (rate->opos & UINT_MAX) / (mixeng_real) UINT_MAX; +#endif + out.l = (ilast.l * (1.0 - t)) + icur.l * t; + out.r = (ilast.r * (1.0 - t)) + icur.r * t; +#else + t = rate->opos & 0xffffffff; + out.l = (ilast.l * ((int64_t) UINT_MAX - t) + icur.l * t) >> 32; + out.r = (ilast.r * ((int64_t) UINT_MAX - t) + icur.r * t) >> 32; +#endif + + /* output sample & increment position */ + OP (obuf->l, out.l); + OP (obuf->r, out.r); + obuf += 1; + rate->opos += rate->opos_inc; + } + +the_end: + *isamp = ibuf - istart; + *osamp = obuf - ostart; + rate->ilast = ilast; +} + +#undef NAME +#undef OP diff --git a/audio/sdlaudio.c b/audio/sdlaudio.c new file mode 100644 index 0000000..b74dcfa --- /dev/null +++ b/audio/sdlaudio.c @@ -0,0 +1,459 @@ +/* + * QEMU SDL audio driver + * + * 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 +#include +#include "qemu-common.h" +#include "audio.h" + +#ifndef _WIN32 +#ifdef __sun__ +#define _POSIX_PTHREAD_SEMANTICS 1 +#elif defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__DragonFly__) +#include +#endif +#include +#endif + +#define AUDIO_CAP "sdl" +#include "audio_int.h" + +typedef struct SDLVoiceOut { + HWVoiceOut hw; + int live; + int rpos; + int decr; +} SDLVoiceOut; + +static struct { + int nb_samples; +} conf = { + .nb_samples = 1024 +}; + +static struct SDLAudioState { + int exit; + SDL_mutex *mutex; + SDL_sem *sem; + int initialized; +} glob_sdl; +typedef struct SDLAudioState SDLAudioState; + +static void GCC_FMT_ATTR (1, 2) sdl_logerr (const char *fmt, ...) +{ + va_list ap; + + va_start (ap, fmt); + AUD_vlog (AUDIO_CAP, fmt, ap); + va_end (ap); + + AUD_log (AUDIO_CAP, "Reason: %s\n", SDL_GetError ()); +} + +static int sdl_lock (SDLAudioState *s, const char *forfn) +{ + if (SDL_LockMutex (s->mutex)) { + sdl_logerr ("SDL_LockMutex for %s failed\n", forfn); + return -1; + } + return 0; +} + +static int sdl_unlock (SDLAudioState *s, const char *forfn) +{ + if (SDL_UnlockMutex (s->mutex)) { + sdl_logerr ("SDL_UnlockMutex for %s failed\n", forfn); + return -1; + } + return 0; +} + +static int sdl_post (SDLAudioState *s, const char *forfn) +{ + if (SDL_SemPost (s->sem)) { + sdl_logerr ("SDL_SemPost for %s failed\n", forfn); + return -1; + } + return 0; +} + +static int sdl_wait (SDLAudioState *s, const char *forfn) +{ + if (SDL_SemWait (s->sem)) { + sdl_logerr ("SDL_SemWait for %s failed\n", forfn); + return -1; + } + return 0; +} + +static int sdl_unlock_and_post (SDLAudioState *s, const char *forfn) +{ + if (sdl_unlock (s, forfn)) { + return -1; + } + + return sdl_post (s, forfn); +} + +static int aud_to_sdlfmt (audfmt_e fmt) +{ + switch (fmt) { + case AUD_FMT_S8: + return AUDIO_S8; + + case AUD_FMT_U8: + return AUDIO_U8; + + case AUD_FMT_S16: + return AUDIO_S16LSB; + + case AUD_FMT_U16: + return AUDIO_U16LSB; + + default: + dolog ("Internal logic error: Bad audio format %d\n", fmt); +#ifdef DEBUG_AUDIO + abort (); +#endif + return AUDIO_U8; + } +} + +static int sdl_to_audfmt (int sdlfmt, audfmt_e *fmt, int *endianess) +{ + switch (sdlfmt) { + case AUDIO_S8: + *endianess = 0; + *fmt = AUD_FMT_S8; + break; + + case AUDIO_U8: + *endianess = 0; + *fmt = AUD_FMT_U8; + break; + + case AUDIO_S16LSB: + *endianess = 0; + *fmt = AUD_FMT_S16; + break; + + case AUDIO_U16LSB: + *endianess = 0; + *fmt = AUD_FMT_U16; + break; + + case AUDIO_S16MSB: + *endianess = 1; + *fmt = AUD_FMT_S16; + break; + + case AUDIO_U16MSB: + *endianess = 1; + *fmt = AUD_FMT_U16; + break; + + default: + dolog ("Unrecognized SDL audio format %d\n", sdlfmt); + return -1; + } + + return 0; +} + +static int sdl_open (SDL_AudioSpec *req, SDL_AudioSpec *obt) +{ + int status; +#ifndef _WIN32 + int err; + sigset_t new, old; + + /* Make sure potential threads created by SDL don't hog signals. */ + err = sigfillset (&new); + if (err) { + dolog ("sdl_open: sigfillset failed: %s\n", strerror (errno)); + return -1; + } + err = pthread_sigmask (SIG_BLOCK, &new, &old); + if (err) { + dolog ("sdl_open: pthread_sigmask failed: %s\n", strerror (err)); + return -1; + } +#endif + + status = SDL_OpenAudio (req, obt); + if (status) { + sdl_logerr ("SDL_OpenAudio failed\n"); + } + +#ifndef _WIN32 + err = pthread_sigmask (SIG_SETMASK, &old, NULL); + if (err) { + dolog ("sdl_open: pthread_sigmask (restore) failed: %s\n", + strerror (errno)); + /* We have failed to restore original signal mask, all bets are off, + so exit the process */ + exit (EXIT_FAILURE); + } +#endif + return status; +} + +static void sdl_close (SDLAudioState *s) +{ + if (s->initialized) { + sdl_lock (s, "sdl_close"); + s->exit = 1; + sdl_unlock_and_post (s, "sdl_close"); + SDL_PauseAudio (1); + SDL_CloseAudio (); + s->initialized = 0; + } +} + +static void sdl_callback (void *opaque, Uint8 *buf, int len) +{ + SDLVoiceOut *sdl = opaque; + SDLAudioState *s = &glob_sdl; + HWVoiceOut *hw = &sdl->hw; + int samples = len >> hw->info.shift; + + if (s->exit) { + return; + } + + while (samples) { + int to_mix, decr; + + /* dolog ("in callback samples=%d\n", samples); */ + sdl_wait (s, "sdl_callback"); + if (s->exit) { + return; + } + + if (sdl_lock (s, "sdl_callback")) { + return; + } + + if (audio_bug (AUDIO_FUNC, sdl->live < 0 || sdl->live > hw->samples)) { + dolog ("sdl->live=%d hw->samples=%d\n", + sdl->live, hw->samples); + return; + } + + if (!sdl->live) { + goto again; + } + + /* dolog ("in callback live=%d\n", live); */ + to_mix = audio_MIN (samples, sdl->live); + decr = to_mix; + while (to_mix) { + int chunk = audio_MIN (to_mix, hw->samples - hw->rpos); + struct st_sample *src = hw->mix_buf + hw->rpos; + + /* dolog ("in callback to_mix %d, chunk %d\n", to_mix, chunk); */ + hw->clip (buf, src, chunk); + sdl->rpos = (sdl->rpos + chunk) % hw->samples; + to_mix -= chunk; + buf += chunk << hw->info.shift; + } + samples -= decr; + sdl->live -= decr; + sdl->decr += decr; + + again: + if (sdl_unlock (s, "sdl_callback")) { + return; + } + } + /* dolog ("done len=%d\n", len); */ +} + +static int sdl_write_out (SWVoiceOut *sw, void *buf, int len) +{ + return audio_pcm_sw_write (sw, buf, len); +} + +static int sdl_run_out (HWVoiceOut *hw, int live) +{ + int decr; + SDLVoiceOut *sdl = (SDLVoiceOut *) hw; + SDLAudioState *s = &glob_sdl; + + if (sdl_lock (s, "sdl_run_out")) { + return 0; + } + + if (sdl->decr > live) { + ldebug ("sdl->decr %d live %d sdl->live %d\n", + sdl->decr, + live, + sdl->live); + } + + decr = audio_MIN (sdl->decr, live); + sdl->decr -= decr; + + sdl->live = live - decr; + hw->rpos = sdl->rpos; + + if (sdl->live > 0) { + sdl_unlock_and_post (s, "sdl_run_out"); + } + else { + sdl_unlock (s, "sdl_run_out"); + } + return decr; +} + +static void sdl_fini_out (HWVoiceOut *hw) +{ + (void) hw; + + sdl_close (&glob_sdl); +} + +static int sdl_init_out (HWVoiceOut *hw, struct audsettings *as) +{ + SDLVoiceOut *sdl = (SDLVoiceOut *) hw; + SDLAudioState *s = &glob_sdl; + SDL_AudioSpec req, obt; + int endianess; + int err; + audfmt_e effective_fmt; + struct audsettings obt_as; + + req.freq = as->freq; + req.format = aud_to_sdlfmt (as->fmt); + req.channels = as->nchannels; + req.samples = conf.nb_samples; + req.callback = sdl_callback; + req.userdata = sdl; + + if (sdl_open (&req, &obt)) { + return -1; + } + + err = sdl_to_audfmt (obt.format, &effective_fmt, &endianess); + if (err) { + sdl_close (s); + return -1; + } + + obt_as.freq = obt.freq; + obt_as.nchannels = obt.channels; + obt_as.fmt = effective_fmt; + obt_as.endianness = endianess; + + audio_pcm_init_info (&hw->info, &obt_as); + hw->samples = obt.samples; + + s->initialized = 1; + s->exit = 0; + SDL_PauseAudio (0); + return 0; +} + +static int sdl_ctl_out (HWVoiceOut *hw, int cmd, ...) +{ + (void) hw; + + switch (cmd) { + case VOICE_ENABLE: + SDL_PauseAudio (0); + break; + + case VOICE_DISABLE: + SDL_PauseAudio (1); + break; + } + return 0; +} + +static void *sdl_audio_init (void) +{ + SDLAudioState *s = &glob_sdl; + + if (SDL_InitSubSystem (SDL_INIT_AUDIO)) { + sdl_logerr ("SDL failed to initialize audio subsystem\n"); + return NULL; + } + + s->mutex = SDL_CreateMutex (); + if (!s->mutex) { + sdl_logerr ("Failed to create SDL mutex\n"); + SDL_QuitSubSystem (SDL_INIT_AUDIO); + return NULL; + } + + s->sem = SDL_CreateSemaphore (0); + if (!s->sem) { + sdl_logerr ("Failed to create SDL semaphore\n"); + SDL_DestroyMutex (s->mutex); + SDL_QuitSubSystem (SDL_INIT_AUDIO); + return NULL; + } + + return s; +} + +static void sdl_audio_fini (void *opaque) +{ + SDLAudioState *s = opaque; + sdl_close (s); + SDL_DestroySemaphore (s->sem); + SDL_DestroyMutex (s->mutex); + SDL_QuitSubSystem (SDL_INIT_AUDIO); +} + +static struct audio_option sdl_options[] = { + { + .name = "SAMPLES", + .tag = AUD_OPT_INT, + .valp = &conf.nb_samples, + .descr = "Size of SDL buffer in samples" + }, + { /* End of list */ } +}; + +static struct audio_pcm_ops sdl_pcm_ops = { + .init_out = sdl_init_out, + .fini_out = sdl_fini_out, + .run_out = sdl_run_out, + .write = sdl_write_out, + .ctl_out = sdl_ctl_out, +}; + +struct audio_driver sdl_audio_driver = { + .name = "sdl", + .descr = "SDL http://www.libsdl.org", + .options = sdl_options, + .init = sdl_audio_init, + .fini = sdl_audio_fini, + .pcm_ops = &sdl_pcm_ops, + .can_be_default = 1, + .max_voices_out = 1, + .max_voices_in = 0, + .voice_size_out = sizeof (SDLVoiceOut), + .voice_size_in = 0 +}; diff --git a/audio/spiceaudio.c b/audio/spiceaudio.c new file mode 100644 index 0000000..a5c0d6b --- /dev/null +++ b/audio/spiceaudio.c @@ -0,0 +1,345 @@ +/* + * Copyright (C) 2010 Red Hat, Inc. + * + * maintained by Gerd Hoffmann + * + * 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 . + */ + +#include "hw/hw.h" +#include "qemu-timer.h" +#include "ui/qemu-spice.h" + +#define AUDIO_CAP "spice" +#include "audio.h" +#include "audio_int.h" + +#define LINE_IN_SAMPLES 1024 +#define LINE_OUT_SAMPLES 1024 + +typedef struct SpiceRateCtl { + int64_t start_ticks; + int64_t bytes_sent; +} SpiceRateCtl; + +typedef struct SpiceVoiceOut { + HWVoiceOut hw; + SpicePlaybackInstance sin; + SpiceRateCtl rate; + int active; + uint32_t *frame; + uint32_t *fpos; + uint32_t fsize; +} SpiceVoiceOut; + +typedef struct SpiceVoiceIn { + HWVoiceIn hw; + SpiceRecordInstance sin; + SpiceRateCtl rate; + int active; + uint32_t samples[LINE_IN_SAMPLES]; +} SpiceVoiceIn; + +static const SpicePlaybackInterface playback_sif = { + .base.type = SPICE_INTERFACE_PLAYBACK, + .base.description = "playback", + .base.major_version = SPICE_INTERFACE_PLAYBACK_MAJOR, + .base.minor_version = SPICE_INTERFACE_PLAYBACK_MINOR, +}; + +static const SpiceRecordInterface record_sif = { + .base.type = SPICE_INTERFACE_RECORD, + .base.description = "record", + .base.major_version = SPICE_INTERFACE_RECORD_MAJOR, + .base.minor_version = SPICE_INTERFACE_RECORD_MINOR, +}; + +static void *spice_audio_init (void) +{ + if (!using_spice) { + return NULL; + } + return &spice_audio_init; +} + +static void spice_audio_fini (void *opaque) +{ + /* nothing */ +} + +static void rate_start (SpiceRateCtl *rate) +{ + memset (rate, 0, sizeof (*rate)); + rate->start_ticks = qemu_get_clock (vm_clock); +} + +static int rate_get_samples (struct audio_pcm_info *info, SpiceRateCtl *rate) +{ + int64_t now; + int64_t ticks; + int64_t bytes; + int64_t samples; + + now = qemu_get_clock (vm_clock); + ticks = now - rate->start_ticks; + bytes = muldiv64 (ticks, info->bytes_per_second, get_ticks_per_sec ()); + samples = (bytes - rate->bytes_sent) >> info->shift; + if (samples < 0 || samples > 65536) { + fprintf (stderr, "Resetting rate control (%" PRId64 " samples)\n", samples); + rate_start (rate); + samples = 0; + } + rate->bytes_sent += samples << info->shift; + return samples; +} + +/* playback */ + +static int line_out_init (HWVoiceOut *hw, struct audsettings *as) +{ + SpiceVoiceOut *out = container_of (hw, SpiceVoiceOut, hw); + struct audsettings settings; + + settings.freq = SPICE_INTERFACE_PLAYBACK_FREQ; + settings.nchannels = SPICE_INTERFACE_PLAYBACK_CHAN; + settings.fmt = AUD_FMT_S16; + settings.endianness = AUDIO_HOST_ENDIANNESS; + + audio_pcm_init_info (&hw->info, &settings); + hw->samples = LINE_OUT_SAMPLES; + out->active = 0; + + out->sin.base.sif = &playback_sif.base; + qemu_spice_add_interface (&out->sin.base); + return 0; +} + +static void line_out_fini (HWVoiceOut *hw) +{ + SpiceVoiceOut *out = container_of (hw, SpiceVoiceOut, hw); + + spice_server_remove_interface (&out->sin.base); +} + +static int line_out_run (HWVoiceOut *hw, int live) +{ + SpiceVoiceOut *out = container_of (hw, SpiceVoiceOut, hw); + int rpos, decr; + int samples; + + if (!live) { + return 0; + } + + decr = rate_get_samples (&hw->info, &out->rate); + decr = audio_MIN (live, decr); + + samples = decr; + rpos = hw->rpos; + while (samples) { + int left_till_end_samples = hw->samples - rpos; + int len = audio_MIN (samples, left_till_end_samples); + + if (!out->frame) { + spice_server_playback_get_buffer (&out->sin, &out->frame, &out->fsize); + out->fpos = out->frame; + } + if (out->frame) { + len = audio_MIN (len, out->fsize); + hw->clip (out->fpos, hw->mix_buf + rpos, len); + out->fsize -= len; + out->fpos += len; + if (out->fsize == 0) { + spice_server_playback_put_samples (&out->sin, out->frame); + out->frame = out->fpos = NULL; + } + } + rpos = (rpos + len) % hw->samples; + samples -= len; + } + hw->rpos = rpos; + return decr; +} + +static int line_out_write (SWVoiceOut *sw, void *buf, int len) +{ + return audio_pcm_sw_write (sw, buf, len); +} + +static int line_out_ctl (HWVoiceOut *hw, int cmd, ...) +{ + SpiceVoiceOut *out = container_of (hw, SpiceVoiceOut, hw); + + switch (cmd) { + case VOICE_ENABLE: + if (out->active) { + break; + } + out->active = 1; + rate_start (&out->rate); + spice_server_playback_start (&out->sin); + break; + case VOICE_DISABLE: + if (!out->active) { + break; + } + out->active = 0; + if (out->frame) { + memset (out->fpos, 0, out->fsize << 2); + spice_server_playback_put_samples (&out->sin, out->frame); + out->frame = out->fpos = NULL; + } + spice_server_playback_stop (&out->sin); + break; + } + return 0; +} + +/* record */ + +static int line_in_init (HWVoiceIn *hw, struct audsettings *as) +{ + SpiceVoiceIn *in = container_of (hw, SpiceVoiceIn, hw); + struct audsettings settings; + + settings.freq = SPICE_INTERFACE_RECORD_FREQ; + settings.nchannels = SPICE_INTERFACE_RECORD_CHAN; + settings.fmt = AUD_FMT_S16; + settings.endianness = AUDIO_HOST_ENDIANNESS; + + audio_pcm_init_info (&hw->info, &settings); + hw->samples = LINE_IN_SAMPLES; + in->active = 0; + + in->sin.base.sif = &record_sif.base; + qemu_spice_add_interface (&in->sin.base); + return 0; +} + +static void line_in_fini (HWVoiceIn *hw) +{ + SpiceVoiceIn *in = container_of (hw, SpiceVoiceIn, hw); + + spice_server_remove_interface (&in->sin.base); +} + +static int line_in_run (HWVoiceIn *hw) +{ + SpiceVoiceIn *in = container_of (hw, SpiceVoiceIn, hw); + int num_samples; + int ready; + int len[2]; + uint64_t delta_samp; + const uint32_t *samples; + + if (!(num_samples = hw->samples - audio_pcm_hw_get_live_in (hw))) { + return 0; + } + + delta_samp = rate_get_samples (&hw->info, &in->rate); + num_samples = audio_MIN (num_samples, delta_samp); + + ready = spice_server_record_get_samples (&in->sin, in->samples, num_samples); + samples = in->samples; + if (ready == 0) { + static const uint32_t silence[LINE_IN_SAMPLES]; + samples = silence; + ready = LINE_IN_SAMPLES; + } + + num_samples = audio_MIN (ready, num_samples); + + if (hw->wpos + num_samples > hw->samples) { + len[0] = hw->samples - hw->wpos; + len[1] = num_samples - len[0]; + } else { + len[0] = num_samples; + len[1] = 0; + } + + hw->conv (hw->conv_buf + hw->wpos, samples, len[0]); + + if (len[1]) { + hw->conv (hw->conv_buf, samples + len[0], len[1]); + } + + hw->wpos = (hw->wpos + num_samples) % hw->samples; + + return num_samples; +} + +static int line_in_read (SWVoiceIn *sw, void *buf, int size) +{ + return audio_pcm_sw_read (sw, buf, size); +} + +static int line_in_ctl (HWVoiceIn *hw, int cmd, ...) +{ + SpiceVoiceIn *in = container_of (hw, SpiceVoiceIn, hw); + + switch (cmd) { + case VOICE_ENABLE: + if (in->active) { + break; + } + in->active = 1; + rate_start (&in->rate); + spice_server_record_start (&in->sin); + break; + case VOICE_DISABLE: + if (!in->active) { + break; + } + in->active = 0; + spice_server_record_stop (&in->sin); + break; + } + return 0; +} + +static struct audio_option audio_options[] = { + { /* end of list */ }, +}; + +static struct audio_pcm_ops audio_callbacks = { + .init_out = line_out_init, + .fini_out = line_out_fini, + .run_out = line_out_run, + .write = line_out_write, + .ctl_out = line_out_ctl, + + .init_in = line_in_init, + .fini_in = line_in_fini, + .run_in = line_in_run, + .read = line_in_read, + .ctl_in = line_in_ctl, +}; + +struct audio_driver spice_audio_driver = { + .name = "spice", + .descr = "spice audio driver", + .options = audio_options, + .init = spice_audio_init, + .fini = spice_audio_fini, + .pcm_ops = &audio_callbacks, + .max_voices_out = 1, + .max_voices_in = 1, + .voice_size_out = sizeof (SpiceVoiceOut), + .voice_size_in = sizeof (SpiceVoiceIn), +}; + +void qemu_spice_audio_init (void) +{ + spice_audio_driver.can_be_default = 1; +} diff --git a/audio/wavaudio.c b/audio/wavaudio.c new file mode 100644 index 0000000..c522be4 --- /dev/null +++ b/audio/wavaudio.c @@ -0,0 +1,262 @@ +/* + * QEMU WAV audio driver + * + * 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 "qemu-timer.h" +#include "audio.h" + +#define AUDIO_CAP "wav" +#include "audio_int.h" + +typedef struct WAVVoiceOut { + HWVoiceOut hw; + QEMUFile *f; + int64_t old_ticks; + void *pcm_buf; + int total_samples; +} WAVVoiceOut; + +static struct { + struct audsettings settings; + const char *wav_path; +} conf = { + .settings.freq = 44100, + .settings.nchannels = 2, + .settings.fmt = AUD_FMT_S16, + .wav_path = "qemu.wav" +}; + +static int wav_run_out (HWVoiceOut *hw, int live) +{ + WAVVoiceOut *wav = (WAVVoiceOut *) hw; + int rpos, decr, samples; + uint8_t *dst; + struct st_sample *src; + int64_t now = qemu_get_clock (vm_clock); + int64_t ticks = now - wav->old_ticks; + int64_t bytes = + muldiv64 (ticks, hw->info.bytes_per_second, get_ticks_per_sec ()); + + if (bytes > INT_MAX) { + samples = INT_MAX >> hw->info.shift; + } + else { + samples = bytes >> hw->info.shift; + } + + wav->old_ticks = now; + decr = audio_MIN (live, samples); + samples = decr; + rpos = hw->rpos; + while (samples) { + int left_till_end_samples = hw->samples - rpos; + int convert_samples = audio_MIN (samples, left_till_end_samples); + + src = hw->mix_buf + rpos; + dst = advance (wav->pcm_buf, rpos << hw->info.shift); + + hw->clip (dst, src, convert_samples); + qemu_put_buffer (wav->f, dst, convert_samples << hw->info.shift); + + rpos = (rpos + convert_samples) % hw->samples; + samples -= convert_samples; + wav->total_samples += convert_samples; + } + + hw->rpos = rpos; + return decr; +} + +static int wav_write_out (SWVoiceOut *sw, void *buf, int len) +{ + return audio_pcm_sw_write (sw, buf, len); +} + +/* VICE code: Store number as little endian. */ +static void le_store (uint8_t *buf, uint32_t val, int len) +{ + int i; + for (i = 0; i < len; i++) { + buf[i] = (uint8_t) (val & 0xff); + val >>= 8; + } +} + +static int wav_init_out (HWVoiceOut *hw, struct audsettings *as) +{ + WAVVoiceOut *wav = (WAVVoiceOut *) hw; + int bits16 = 0, stereo = 0; + uint8_t hdr[] = { + 0x52, 0x49, 0x46, 0x46, 0x00, 0x00, 0x00, 0x00, 0x57, 0x41, 0x56, + 0x45, 0x66, 0x6d, 0x74, 0x20, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x02, 0x00, 0x44, 0xac, 0x00, 0x00, 0x10, 0xb1, 0x02, 0x00, 0x04, + 0x00, 0x10, 0x00, 0x64, 0x61, 0x74, 0x61, 0x00, 0x00, 0x00, 0x00 + }; + struct audsettings wav_as = conf.settings; + + (void) as; + + stereo = wav_as.nchannels == 2; + switch (wav_as.fmt) { + case AUD_FMT_S8: + case AUD_FMT_U8: + bits16 = 0; + break; + + case AUD_FMT_S16: + case AUD_FMT_U16: + bits16 = 1; + break; + + case AUD_FMT_S32: + case AUD_FMT_U32: + dolog ("WAVE files can not handle 32bit formats\n"); + return -1; + } + + hdr[34] = bits16 ? 0x10 : 0x08; + + wav_as.endianness = 0; + audio_pcm_init_info (&hw->info, &wav_as); + + hw->samples = 1024; + wav->pcm_buf = audio_calloc (AUDIO_FUNC, hw->samples, 1 << hw->info.shift); + if (!wav->pcm_buf) { + dolog ("Could not allocate buffer (%d bytes)\n", + hw->samples << hw->info.shift); + return -1; + } + + le_store (hdr + 22, hw->info.nchannels, 2); + le_store (hdr + 24, hw->info.freq, 4); + le_store (hdr + 28, hw->info.freq << (bits16 + stereo), 4); + le_store (hdr + 32, 1 << (bits16 + stereo), 2); + + wav->f = qemu_fopen (conf.wav_path, "wb"); + if (!wav->f) { + dolog ("Failed to open wave file `%s'\nReason: %s\n", + conf.wav_path, strerror (errno)); + qemu_free (wav->pcm_buf); + wav->pcm_buf = NULL; + return -1; + } + + qemu_put_buffer (wav->f, hdr, sizeof (hdr)); + return 0; +} + +static void wav_fini_out (HWVoiceOut *hw) +{ + WAVVoiceOut *wav = (WAVVoiceOut *) hw; + uint8_t rlen[4]; + uint8_t dlen[4]; + uint32_t datalen = wav->total_samples << hw->info.shift; + uint32_t rifflen = datalen + 36; + + if (!wav->f) { + return; + } + + le_store (rlen, rifflen, 4); + le_store (dlen, datalen, 4); + + qemu_fseek (wav->f, 4, SEEK_SET); + qemu_put_buffer (wav->f, rlen, 4); + + qemu_fseek (wav->f, 32, SEEK_CUR); + qemu_put_buffer (wav->f, dlen, 4); + + qemu_fclose (wav->f); + wav->f = NULL; + + qemu_free (wav->pcm_buf); + wav->pcm_buf = NULL; +} + +static int wav_ctl_out (HWVoiceOut *hw, int cmd, ...) +{ + (void) hw; + (void) cmd; + return 0; +} + +static void *wav_audio_init (void) +{ + return &conf; +} + +static void wav_audio_fini (void *opaque) +{ + (void) opaque; + ldebug ("wav_fini"); +} + +static struct audio_option wav_options[] = { + { + .name = "FREQUENCY", + .tag = AUD_OPT_INT, + .valp = &conf.settings.freq, + .descr = "Frequency" + }, + { + .name = "FORMAT", + .tag = AUD_OPT_FMT, + .valp = &conf.settings.fmt, + .descr = "Format" + }, + { + .name = "DAC_FIXED_CHANNELS", + .tag = AUD_OPT_INT, + .valp = &conf.settings.nchannels, + .descr = "Number of channels (1 - mono, 2 - stereo)" + }, + { + .name = "PATH", + .tag = AUD_OPT_STR, + .valp = &conf.wav_path, + .descr = "Path to wave file" + }, + { /* End of list */ } +}; + +static struct audio_pcm_ops wav_pcm_ops = { + .init_out = wav_init_out, + .fini_out = wav_fini_out, + .run_out = wav_run_out, + .write = wav_write_out, + .ctl_out = wav_ctl_out, +}; + +struct audio_driver wav_audio_driver = { + .name = "wav", + .descr = "WAV renderer http://wikipedia.org/wiki/WAV", + .options = wav_options, + .init = wav_audio_init, + .fini = wav_audio_fini, + .pcm_ops = &wav_pcm_ops, + .can_be_default = 0, + .max_voices_out = 1, + .max_voices_in = 0, + .voice_size_out = sizeof (WAVVoiceOut), + .voice_size_in = 0 +}; diff --git a/audio/wavcapture.c b/audio/wavcapture.c new file mode 100644 index 0000000..1f49cd1 --- /dev/null +++ b/audio/wavcapture.c @@ -0,0 +1,161 @@ +#include "hw/hw.h" +#include "monitor.h" +#include "audio.h" + +typedef struct { + QEMUFile *f; + int bytes; + char *path; + int freq; + int bits; + int nchannels; + CaptureVoiceOut *cap; +} WAVState; + +/* VICE code: Store number as little endian. */ +static void le_store (uint8_t *buf, uint32_t val, int len) +{ + int i; + for (i = 0; i < len; i++) { + buf[i] = (uint8_t) (val & 0xff); + val >>= 8; + } +} + +static void wav_notify (void *opaque, audcnotification_e cmd) +{ + (void) opaque; + (void) cmd; +} + +static void wav_destroy (void *opaque) +{ + WAVState *wav = opaque; + uint8_t rlen[4]; + uint8_t dlen[4]; + uint32_t datalen = wav->bytes; + uint32_t rifflen = datalen + 36; + + if (wav->f) { + le_store (rlen, rifflen, 4); + le_store (dlen, datalen, 4); + + qemu_fseek (wav->f, 4, SEEK_SET); + qemu_put_buffer (wav->f, rlen, 4); + + qemu_fseek (wav->f, 32, SEEK_CUR); + qemu_put_buffer (wav->f, dlen, 4); + qemu_fclose (wav->f); + } + + qemu_free (wav->path); +} + +static void wav_capture (void *opaque, void *buf, int size) +{ + WAVState *wav = opaque; + + qemu_put_buffer (wav->f, buf, size); + wav->bytes += size; +} + +static void wav_capture_destroy (void *opaque) +{ + WAVState *wav = opaque; + + AUD_del_capture (wav->cap, wav); +} + +static void wav_capture_info (void *opaque) +{ + WAVState *wav = opaque; + char *path = wav->path; + + monitor_printf(cur_mon, "Capturing audio(%d,%d,%d) to %s: %d bytes\n", + wav->freq, wav->bits, wav->nchannels, + path ? path : "", wav->bytes); +} + +static struct capture_ops wav_capture_ops = { + .destroy = wav_capture_destroy, + .info = wav_capture_info +}; + +int wav_start_capture (CaptureState *s, const char *path, int freq, + int bits, int nchannels) +{ + Monitor *mon = cur_mon; + WAVState *wav; + uint8_t hdr[] = { + 0x52, 0x49, 0x46, 0x46, 0x00, 0x00, 0x00, 0x00, 0x57, 0x41, 0x56, + 0x45, 0x66, 0x6d, 0x74, 0x20, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x02, 0x00, 0x44, 0xac, 0x00, 0x00, 0x10, 0xb1, 0x02, 0x00, 0x04, + 0x00, 0x10, 0x00, 0x64, 0x61, 0x74, 0x61, 0x00, 0x00, 0x00, 0x00 + }; + struct audsettings as; + struct audio_capture_ops ops; + int stereo, bits16, shift; + CaptureVoiceOut *cap; + + if (bits != 8 && bits != 16) { + monitor_printf(mon, "incorrect bit count %d, must be 8 or 16\n", bits); + return -1; + } + + if (nchannels != 1 && nchannels != 2) { + monitor_printf(mon, "incorrect channel count %d, must be 1 or 2\n", + nchannels); + return -1; + } + + stereo = nchannels == 2; + bits16 = bits == 16; + + as.freq = freq; + as.nchannels = 1 << stereo; + as.fmt = bits16 ? AUD_FMT_S16 : AUD_FMT_U8; + as.endianness = 0; + + ops.notify = wav_notify; + ops.capture = wav_capture; + ops.destroy = wav_destroy; + + wav = qemu_mallocz (sizeof (*wav)); + + shift = bits16 + stereo; + hdr[34] = bits16 ? 0x10 : 0x08; + + le_store (hdr + 22, as.nchannels, 2); + le_store (hdr + 24, freq, 4); + le_store (hdr + 28, freq << shift, 4); + le_store (hdr + 32, 1 << shift, 2); + + wav->f = qemu_fopen (path, "wb"); + if (!wav->f) { + monitor_printf(mon, "Failed to open wave file `%s'\nReason: %s\n", + path, strerror (errno)); + qemu_free (wav); + return -1; + } + + wav->path = qemu_strdup (path); + wav->bits = bits; + wav->nchannels = nchannels; + wav->freq = freq; + + qemu_put_buffer (wav->f, hdr, sizeof (hdr)); + + cap = AUD_add_capture (&as, &ops, wav); + if (!cap) { + monitor_printf(mon, "Failed to add audio capture\n"); + qemu_free (wav->path); + qemu_fclose (wav->f); + qemu_free (wav); + return -1; + } + + wav->cap = cap; + s->opaque = wav; + s->ops = wav_capture_ops; + return 0; +} diff --git a/audio/winwaveaudio.c b/audio/winwaveaudio.c new file mode 100644 index 0000000..e5ad3c6 --- /dev/null +++ b/audio/winwaveaudio.c @@ -0,0 +1,723 @@ +/* public domain */ + +#include "qemu-common.h" +#include "sysemu.h" +#include "audio.h" + +#define AUDIO_CAP "winwave" +#include "audio_int.h" + +#include +#include + +#include "audio_win_int.h" + +static struct { + int dac_headers; + int dac_samples; + int adc_headers; + int adc_samples; +} conf = { + .dac_headers = 4, + .dac_samples = 1024, + .adc_headers = 4, + .adc_samples = 1024 +}; + +typedef struct { + HWVoiceOut hw; + HWAVEOUT hwo; + WAVEHDR *hdrs; + HANDLE event; + void *pcm_buf; + int avail; + int pending; + int curhdr; + int paused; + CRITICAL_SECTION crit_sect; +} WaveVoiceOut; + +typedef struct { + HWVoiceIn hw; + HWAVEIN hwi; + WAVEHDR *hdrs; + HANDLE event; + void *pcm_buf; + int curhdr; + int paused; + int rpos; + int avail; + CRITICAL_SECTION crit_sect; +} WaveVoiceIn; + +static void winwave_log_mmresult (MMRESULT mr) +{ + const char *str = "BUG"; + + switch (mr) { + case MMSYSERR_NOERROR: + str = "Success"; + break; + + case MMSYSERR_INVALHANDLE: + str = "Specified device handle is invalid"; + break; + + case MMSYSERR_BADDEVICEID: + str = "Specified device id is out of range"; + break; + + case MMSYSERR_NODRIVER: + str = "No device driver is present"; + break; + + case MMSYSERR_NOMEM: + str = "Unable to allocate or locl memory"; + break; + + case WAVERR_SYNC: + str = "Device is synchronous but waveOutOpen was called " + "without using the WINWAVE_ALLOWSYNC flag"; + break; + + case WAVERR_UNPREPARED: + str = "The data block pointed to by the pwh parameter " + "hasn't been prepared"; + break; + + case WAVERR_STILLPLAYING: + str = "There are still buffers in the queue"; + break; + + default: + dolog ("Reason: Unknown (MMRESULT %#x)\n", mr); + return; + } + + dolog ("Reason: %s\n", str); +} + +static void GCC_FMT_ATTR (2, 3) winwave_logerr ( + MMRESULT mr, + const char *fmt, + ... + ) +{ + va_list ap; + + va_start (ap, fmt); + AUD_vlog (AUDIO_CAP, fmt, ap); + va_end (ap); + + AUD_log (NULL, " failed\n"); + winwave_log_mmresult (mr); +} + +static void winwave_anal_close_out (WaveVoiceOut *wave) +{ + MMRESULT mr; + + mr = waveOutClose (wave->hwo); + if (mr != MMSYSERR_NOERROR) { + winwave_logerr (mr, "waveOutClose"); + } + wave->hwo = NULL; +} + +static void CALLBACK winwave_callback_out ( + HWAVEOUT hwo, + UINT msg, + DWORD_PTR dwInstance, + DWORD_PTR dwParam1, + DWORD_PTR dwParam2 + ) +{ + WaveVoiceOut *wave = (WaveVoiceOut *) dwInstance; + + switch (msg) { + case WOM_DONE: + { + WAVEHDR *h = (WAVEHDR *) dwParam1; + if (!h->dwUser) { + h->dwUser = 1; + EnterCriticalSection (&wave->crit_sect); + { + wave->avail += conf.dac_samples; + } + LeaveCriticalSection (&wave->crit_sect); + if (wave->hw.poll_mode) { + if (!SetEvent (wave->event)) { + dolog ("DAC SetEvent failed %lx\n", GetLastError ()); + } + } + } + } + break; + + case WOM_CLOSE: + case WOM_OPEN: + break; + + default: + dolog ("unknown wave out callback msg %x\n", msg); + } +} + +static int winwave_init_out (HWVoiceOut *hw, struct audsettings *as) +{ + int i; + int err; + MMRESULT mr; + WAVEFORMATEX wfx; + WaveVoiceOut *wave; + + wave = (WaveVoiceOut *) hw; + + InitializeCriticalSection (&wave->crit_sect); + + err = waveformat_from_audio_settings (&wfx, as); + if (err) { + goto err0; + } + + mr = waveOutOpen (&wave->hwo, WAVE_MAPPER, &wfx, + (DWORD_PTR) winwave_callback_out, + (DWORD_PTR) wave, CALLBACK_FUNCTION); + if (mr != MMSYSERR_NOERROR) { + winwave_logerr (mr, "waveOutOpen"); + goto err1; + } + + wave->hdrs = audio_calloc (AUDIO_FUNC, conf.dac_headers, + sizeof (*wave->hdrs)); + if (!wave->hdrs) { + goto err2; + } + + audio_pcm_init_info (&hw->info, as); + hw->samples = conf.dac_samples * conf.dac_headers; + wave->avail = hw->samples; + + wave->pcm_buf = audio_calloc (AUDIO_FUNC, conf.dac_samples, + conf.dac_headers << hw->info.shift); + if (!wave->pcm_buf) { + goto err3; + } + + for (i = 0; i < conf.dac_headers; ++i) { + WAVEHDR *h = &wave->hdrs[i]; + + h->dwUser = 0; + h->dwBufferLength = conf.dac_samples << hw->info.shift; + h->lpData = advance (wave->pcm_buf, i * h->dwBufferLength); + h->dwFlags = 0; + + mr = waveOutPrepareHeader (wave->hwo, h, sizeof (*h)); + if (mr != MMSYSERR_NOERROR) { + winwave_logerr (mr, "waveOutPrepareHeader(%d)", i); + goto err4; + } + } + + return 0; + + err4: + qemu_free (wave->pcm_buf); + err3: + qemu_free (wave->hdrs); + err2: + winwave_anal_close_out (wave); + err1: + err0: + return -1; +} + +static int winwave_write (SWVoiceOut *sw, void *buf, int len) +{ + return audio_pcm_sw_write (sw, buf, len); +} + +static int winwave_run_out (HWVoiceOut *hw, int live) +{ + WaveVoiceOut *wave = (WaveVoiceOut *) hw; + int decr; + int doreset; + + EnterCriticalSection (&wave->crit_sect); + { + decr = audio_MIN (live, wave->avail); + decr = audio_pcm_hw_clip_out (hw, wave->pcm_buf, decr, wave->pending); + wave->pending += decr; + wave->avail -= decr; + } + LeaveCriticalSection (&wave->crit_sect); + + doreset = hw->poll_mode && (wave->pending >= conf.dac_samples); + if (doreset && !ResetEvent (wave->event)) { + dolog ("DAC ResetEvent failed %lx\n", GetLastError ()); + } + + while (wave->pending >= conf.dac_samples) { + MMRESULT mr; + WAVEHDR *h = &wave->hdrs[wave->curhdr]; + + h->dwUser = 0; + mr = waveOutWrite (wave->hwo, h, sizeof (*h)); + if (mr != MMSYSERR_NOERROR) { + winwave_logerr (mr, "waveOutWrite(%d)", wave->curhdr); + break; + } + + wave->pending -= conf.dac_samples; + wave->curhdr = (wave->curhdr + 1) % conf.dac_headers; + } + + return decr; +} + +static void winwave_poll (void *opaque) +{ + (void) opaque; + audio_run ("winwave_poll"); +} + +static void winwave_fini_out (HWVoiceOut *hw) +{ + int i; + MMRESULT mr; + WaveVoiceOut *wave = (WaveVoiceOut *) hw; + + mr = waveOutReset (wave->hwo); + if (mr != MMSYSERR_NOERROR) { + winwave_logerr (mr, "waveOutReset"); + } + + for (i = 0; i < conf.dac_headers; ++i) { + mr = waveOutUnprepareHeader (wave->hwo, &wave->hdrs[i], + sizeof (wave->hdrs[i])); + if (mr != MMSYSERR_NOERROR) { + winwave_logerr (mr, "waveOutUnprepareHeader(%d)", i); + } + } + + winwave_anal_close_out (wave); + + if (wave->event) { + qemu_del_wait_object (wave->event, winwave_poll, wave); + if (!CloseHandle (wave->event)) { + dolog ("DAC CloseHandle failed %lx\n", GetLastError ()); + } + wave->event = NULL; + } + + qemu_free (wave->pcm_buf); + wave->pcm_buf = NULL; + + qemu_free (wave->hdrs); + wave->hdrs = NULL; +} + +static int winwave_ctl_out (HWVoiceOut *hw, int cmd, ...) +{ + MMRESULT mr; + WaveVoiceOut *wave = (WaveVoiceOut *) hw; + + switch (cmd) { + case VOICE_ENABLE: + { + va_list ap; + int poll_mode; + + va_start (ap, cmd); + poll_mode = va_arg (ap, int); + va_end (ap); + + if (poll_mode && !wave->event) { + wave->event = CreateEvent (NULL, TRUE, TRUE, NULL); + if (!wave->event) { + dolog ("DAC CreateEvent: %lx, poll mode will be disabled\n", + GetLastError ()); + } + } + + if (wave->event) { + int ret; + + ret = qemu_add_wait_object (wave->event, winwave_poll, wave); + hw->poll_mode = (ret == 0); + } + else { + hw->poll_mode = 0; + } + if (wave->paused) { + mr = waveOutRestart (wave->hwo); + if (mr != MMSYSERR_NOERROR) { + winwave_logerr (mr, "waveOutRestart"); + } + wave->paused = 0; + } + } + return 0; + + case VOICE_DISABLE: + if (!wave->paused) { + mr = waveOutPause (wave->hwo); + if (mr != MMSYSERR_NOERROR) { + winwave_logerr (mr, "waveOutPause"); + } + else { + wave->paused = 1; + } + } + if (wave->event) { + qemu_del_wait_object (wave->event, winwave_poll, wave); + } + return 0; + } + return -1; +} + +static void winwave_anal_close_in (WaveVoiceIn *wave) +{ + MMRESULT mr; + + mr = waveInClose (wave->hwi); + if (mr != MMSYSERR_NOERROR) { + winwave_logerr (mr, "waveInClose"); + } + wave->hwi = NULL; +} + +static void CALLBACK winwave_callback_in ( + HWAVEIN *hwi, + UINT msg, + DWORD_PTR dwInstance, + DWORD_PTR dwParam1, + DWORD_PTR dwParam2 + ) +{ + WaveVoiceIn *wave = (WaveVoiceIn *) dwInstance; + + switch (msg) { + case WIM_DATA: + { + WAVEHDR *h = (WAVEHDR *) dwParam1; + if (!h->dwUser) { + h->dwUser = 1; + EnterCriticalSection (&wave->crit_sect); + { + wave->avail += conf.adc_samples; + } + LeaveCriticalSection (&wave->crit_sect); + if (wave->hw.poll_mode) { + if (!SetEvent (wave->event)) { + dolog ("ADC SetEvent failed %lx\n", GetLastError ()); + } + } + } + } + break; + + case WIM_CLOSE: + case WIM_OPEN: + break; + + default: + dolog ("unknown wave in callback msg %x\n", msg); + } +} + +static void winwave_add_buffers (WaveVoiceIn *wave, int samples) +{ + int doreset; + + doreset = wave->hw.poll_mode && (samples >= conf.adc_samples); + if (doreset && !ResetEvent (wave->event)) { + dolog ("ADC ResetEvent failed %lx\n", GetLastError ()); + } + + while (samples >= conf.adc_samples) { + MMRESULT mr; + WAVEHDR *h = &wave->hdrs[wave->curhdr]; + + h->dwUser = 0; + mr = waveInAddBuffer (wave->hwi, h, sizeof (*h)); + if (mr != MMSYSERR_NOERROR) { + winwave_logerr (mr, "waveInAddBuffer(%d)", wave->curhdr); + } + wave->curhdr = (wave->curhdr + 1) % conf.adc_headers; + samples -= conf.adc_samples; + } +} + +static int winwave_init_in (HWVoiceIn *hw, struct audsettings *as) +{ + int i; + int err; + MMRESULT mr; + WAVEFORMATEX wfx; + WaveVoiceIn *wave; + + wave = (WaveVoiceIn *) hw; + + InitializeCriticalSection (&wave->crit_sect); + + err = waveformat_from_audio_settings (&wfx, as); + if (err) { + goto err0; + } + + mr = waveInOpen (&wave->hwi, WAVE_MAPPER, &wfx, + (DWORD_PTR) winwave_callback_in, + (DWORD_PTR) wave, CALLBACK_FUNCTION); + if (mr != MMSYSERR_NOERROR) { + winwave_logerr (mr, "waveInOpen"); + goto err1; + } + + wave->hdrs = audio_calloc (AUDIO_FUNC, conf.dac_headers, + sizeof (*wave->hdrs)); + if (!wave->hdrs) { + goto err2; + } + + audio_pcm_init_info (&hw->info, as); + hw->samples = conf.adc_samples * conf.adc_headers; + wave->avail = 0; + + wave->pcm_buf = audio_calloc (AUDIO_FUNC, conf.adc_samples, + conf.adc_headers << hw->info.shift); + if (!wave->pcm_buf) { + goto err3; + } + + for (i = 0; i < conf.adc_headers; ++i) { + WAVEHDR *h = &wave->hdrs[i]; + + h->dwUser = 0; + h->dwBufferLength = conf.adc_samples << hw->info.shift; + h->lpData = advance (wave->pcm_buf, i * h->dwBufferLength); + h->dwFlags = 0; + + mr = waveInPrepareHeader (wave->hwi, h, sizeof (*h)); + if (mr != MMSYSERR_NOERROR) { + winwave_logerr (mr, "waveInPrepareHeader(%d)", i); + goto err4; + } + } + + wave->paused = 1; + winwave_add_buffers (wave, hw->samples); + return 0; + + err4: + qemu_free (wave->pcm_buf); + err3: + qemu_free (wave->hdrs); + err2: + winwave_anal_close_in (wave); + err1: + err0: + return -1; +} + +static void winwave_fini_in (HWVoiceIn *hw) +{ + int i; + MMRESULT mr; + WaveVoiceIn *wave = (WaveVoiceIn *) hw; + + mr = waveInReset (wave->hwi); + if (mr != MMSYSERR_NOERROR) { + winwave_logerr (mr, "waveInReset"); + } + + for (i = 0; i < conf.adc_headers; ++i) { + mr = waveInUnprepareHeader (wave->hwi, &wave->hdrs[i], + sizeof (wave->hdrs[i])); + if (mr != MMSYSERR_NOERROR) { + winwave_logerr (mr, "waveInUnprepareHeader(%d)", i); + } + } + + winwave_anal_close_in (wave); + + if (wave->event) { + qemu_del_wait_object (wave->event, winwave_poll, wave); + if (!CloseHandle (wave->event)) { + dolog ("ADC CloseHandle failed %lx\n", GetLastError ()); + } + wave->event = NULL; + } + + qemu_free (wave->pcm_buf); + wave->pcm_buf = NULL; + + qemu_free (wave->hdrs); + wave->hdrs = NULL; +} + +static int winwave_run_in (HWVoiceIn *hw) +{ + WaveVoiceIn *wave = (WaveVoiceIn *) hw; + int live = audio_pcm_hw_get_live_in (hw); + int dead = hw->samples - live; + int decr, ret; + + if (!dead) { + return 0; + } + + EnterCriticalSection (&wave->crit_sect); + { + decr = audio_MIN (dead, wave->avail); + wave->avail -= decr; + } + LeaveCriticalSection (&wave->crit_sect); + + ret = decr; + while (decr) { + int left = hw->samples - hw->wpos; + int conv = audio_MIN (left, decr); + hw->conv (hw->conv_buf + hw->wpos, + advance (wave->pcm_buf, wave->rpos << hw->info.shift), + conv); + + wave->rpos = (wave->rpos + conv) % hw->samples; + hw->wpos = (hw->wpos + conv) % hw->samples; + decr -= conv; + } + + winwave_add_buffers (wave, ret); + return ret; +} + +static int winwave_read (SWVoiceIn *sw, void *buf, int size) +{ + return audio_pcm_sw_read (sw, buf, size); +} + +static int winwave_ctl_in (HWVoiceIn *hw, int cmd, ...) +{ + MMRESULT mr; + WaveVoiceIn *wave = (WaveVoiceIn *) hw; + + switch (cmd) { + case VOICE_ENABLE: + { + va_list ap; + int poll_mode; + + va_start (ap, cmd); + poll_mode = va_arg (ap, int); + va_end (ap); + + if (poll_mode && !wave->event) { + wave->event = CreateEvent (NULL, TRUE, TRUE, NULL); + if (!wave->event) { + dolog ("ADC CreateEvent: %lx, poll mode will be disabled\n", + GetLastError ()); + } + } + + if (wave->event) { + int ret; + + ret = qemu_add_wait_object (wave->event, winwave_poll, wave); + hw->poll_mode = (ret == 0); + } + else { + hw->poll_mode = 0; + } + if (wave->paused) { + mr = waveInStart (wave->hwi); + if (mr != MMSYSERR_NOERROR) { + winwave_logerr (mr, "waveInStart"); + } + wave->paused = 0; + } + } + return 0; + + case VOICE_DISABLE: + if (!wave->paused) { + mr = waveInStop (wave->hwi); + if (mr != MMSYSERR_NOERROR) { + winwave_logerr (mr, "waveInStop"); + } + else { + wave->paused = 1; + } + } + if (wave->event) { + qemu_del_wait_object (wave->event, winwave_poll, wave); + } + return 0; + } + return 0; +} + +static void *winwave_audio_init (void) +{ + return &conf; +} + +static void winwave_audio_fini (void *opaque) +{ + (void) opaque; +} + +static struct audio_option winwave_options[] = { + { + .name = "DAC_HEADERS", + .tag = AUD_OPT_INT, + .valp = &conf.dac_headers, + .descr = "DAC number of headers", + }, + { + .name = "DAC_SAMPLES", + .tag = AUD_OPT_INT, + .valp = &conf.dac_samples, + .descr = "DAC number of samples per header", + }, + { + .name = "ADC_HEADERS", + .tag = AUD_OPT_INT, + .valp = &conf.adc_headers, + .descr = "ADC number of headers", + }, + { + .name = "ADC_SAMPLES", + .tag = AUD_OPT_INT, + .valp = &conf.adc_samples, + .descr = "ADC number of samples per header", + }, + { /* End of list */ } +}; + +static struct audio_pcm_ops winwave_pcm_ops = { + .init_out = winwave_init_out, + .fini_out = winwave_fini_out, + .run_out = winwave_run_out, + .write = winwave_write, + .ctl_out = winwave_ctl_out, + .init_in = winwave_init_in, + .fini_in = winwave_fini_in, + .run_in = winwave_run_in, + .read = winwave_read, + .ctl_in = winwave_ctl_in +}; + +struct audio_driver winwave_audio_driver = { + .name = "winwave", + .descr = "Windows Waveform Audio http://msdn.microsoft.com", + .options = winwave_options, + .init = winwave_audio_init, + .fini = winwave_audio_fini, + .pcm_ops = &winwave_pcm_ops, + .can_be_default = 1, + .max_voices_out = INT_MAX, + .max_voices_in = INT_MAX, + .voice_size_out = sizeof (WaveVoiceOut), + .voice_size_in = sizeof (WaveVoiceIn) +}; diff --git a/balloon.c b/balloon.c new file mode 100644 index 0000000..0021fef --- /dev/null +++ b/balloon.c @@ -0,0 +1,148 @@ +/* + * QEMU System Emulator + * + * Copyright (c) 2003-2008 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.h" +#include "monitor.h" +#include "qjson.h" +#include "qint.h" +#include "cpu-common.h" +#include "kvm.h" +#include "balloon.h" +#include "trace.h" + + +static QEMUBalloonEvent *qemu_balloon_event; +void *qemu_balloon_event_opaque; + +void qemu_add_balloon_handler(QEMUBalloonEvent *func, void *opaque) +{ + qemu_balloon_event = func; + qemu_balloon_event_opaque = opaque; +} + +int qemu_balloon(ram_addr_t target, MonitorCompletion cb, void *opaque) +{ + if (qemu_balloon_event) { + trace_balloon_event(qemu_balloon_event_opaque, target); + qemu_balloon_event(qemu_balloon_event_opaque, target, cb, opaque); + return 1; + } else { + return 0; + } +} + +int qemu_balloon_status(MonitorCompletion cb, void *opaque) +{ + if (qemu_balloon_event) { + qemu_balloon_event(qemu_balloon_event_opaque, 0, cb, opaque); + return 1; + } else { + return 0; + } +} + +static void print_balloon_stat(const char *key, QObject *obj, void *opaque) +{ + Monitor *mon = opaque; + + if (strcmp(key, "actual")) + monitor_printf(mon, ",%s=%" PRId64, key, + qint_get_int(qobject_to_qint(obj))); +} + +void monitor_print_balloon(Monitor *mon, const QObject *data) +{ + QDict *qdict; + + qdict = qobject_to_qdict(data); + if (!qdict_haskey(qdict, "actual")) + return; + + monitor_printf(mon, "balloon: actual=%" PRId64, + qdict_get_int(qdict, "actual") >> 20); + qdict_iter(qdict, print_balloon_stat, mon); + monitor_printf(mon, "\n"); +} + +/** + * do_info_balloon(): Balloon information + * + * Make an asynchronous request for balloon info. When the request completes + * a QDict will be returned according to the following specification: + * + * - "actual": current balloon value in bytes + * The following fields may or may not be present: + * - "mem_swapped_in": Amount of memory swapped in (bytes) + * - "mem_swapped_out": Amount of memory swapped out (bytes) + * - "major_page_faults": Number of major faults + * - "minor_page_faults": Number of minor faults + * - "free_mem": Total amount of free and unused memory (bytes) + * - "total_mem": Total amount of available memory (bytes) + * + * Example: + * + * { "actual": 1073741824, "mem_swapped_in": 0, "mem_swapped_out": 0, + * "major_page_faults": 142, "minor_page_faults": 239245, + * "free_mem": 1014185984, "total_mem": 1044668416 } + */ +int do_info_balloon(Monitor *mon, MonitorCompletion cb, void *opaque) +{ + int ret; + + if (kvm_enabled() && !kvm_has_sync_mmu()) { + qerror_report(QERR_KVM_MISSING_CAP, "synchronous MMU", "balloon"); + return -1; + } + + ret = qemu_balloon_status(cb, opaque); + if (!ret) { + qerror_report(QERR_DEVICE_NOT_ACTIVE, "balloon"); + return -1; + } + + return 0; +} + +/** + * do_balloon(): Request VM to change its memory allocation + */ +int do_balloon(Monitor *mon, const QDict *params, + MonitorCompletion cb, void *opaque) +{ + int ret; + + if (kvm_enabled() && !kvm_has_sync_mmu()) { + qerror_report(QERR_KVM_MISSING_CAP, "synchronous MMU", "balloon"); + return -1; + } + + ret = qemu_balloon(qdict_get_int(params, "value"), cb, opaque); + if (ret == 0) { + qerror_report(QERR_DEVICE_NOT_ACTIVE, "balloon"); + return -1; + } + + cb(opaque, NULL); + return 0; +} diff --git a/balloon.h b/balloon.h new file mode 100644 index 0000000..d478e28 --- /dev/null +++ b/balloon.h @@ -0,0 +1,33 @@ +/* + * Balloon + * + * Copyright IBM, Corp. 2008 + * + * Authors: + * Anthony Liguori + * + * This work is licensed under the terms of the GNU GPL, version 2. See + * the COPYING file in the top-level directory. + * + */ + +#ifndef _QEMU_BALLOON_H +#define _QEMU_BALLOON_H + +#include "monitor.h" + +typedef void (QEMUBalloonEvent)(void *opaque, ram_addr_t target, + MonitorCompletion cb, void *cb_data); + +void qemu_add_balloon_handler(QEMUBalloonEvent *func, void *opaque); + +int qemu_balloon(ram_addr_t target, MonitorCompletion cb, void *opaque); + +int qemu_balloon_status(MonitorCompletion cb, void *opaque); + +void monitor_print_balloon(Monitor *mon, const QObject *data); +int do_info_balloon(Monitor *mon, MonitorCompletion cb, void *opaque); +int do_balloon(Monitor *mon, const QDict *params, + MonitorCompletion cb, void *opaque); + +#endif diff --git a/block-migration.c b/block-migration.c new file mode 100644 index 0000000..8218bac --- /dev/null +++ b/block-migration.c @@ -0,0 +1,732 @@ +/* + * QEMU live block migration + * + * Copyright IBM, Corp. 2009 + * + * Authors: + * Liran Schour + * + * 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 "block_int.h" +#include "hw/hw.h" +#include "qemu-queue.h" +#include "qemu-timer.h" +#include "monitor.h" +#include "block-migration.h" +#include "migration.h" +#include "blockdev.h" +#include + +#define BLOCK_SIZE (BDRV_SECTORS_PER_DIRTY_CHUNK << BDRV_SECTOR_BITS) + +#define BLK_MIG_FLAG_DEVICE_BLOCK 0x01 +#define BLK_MIG_FLAG_EOS 0x02 +#define BLK_MIG_FLAG_PROGRESS 0x04 + +#define MAX_IS_ALLOCATED_SEARCH 65536 + +//#define DEBUG_BLK_MIGRATION + +#ifdef DEBUG_BLK_MIGRATION +#define DPRINTF(fmt, ...) \ + do { printf("blk_migration: " fmt, ## __VA_ARGS__); } while (0) +#else +#define DPRINTF(fmt, ...) \ + do { } while (0) +#endif + +typedef struct BlkMigDevState { + BlockDriverState *bs; + int bulk_completed; + int shared_base; + int64_t cur_sector; + int64_t cur_dirty; + int64_t completed_sectors; + int64_t total_sectors; + int64_t dirty; + QSIMPLEQ_ENTRY(BlkMigDevState) entry; + unsigned long *aio_bitmap; +} BlkMigDevState; + +typedef struct BlkMigBlock { + uint8_t *buf; + BlkMigDevState *bmds; + int64_t sector; + int nr_sectors; + struct iovec iov; + QEMUIOVector qiov; + BlockDriverAIOCB *aiocb; + int ret; + int64_t time; + QSIMPLEQ_ENTRY(BlkMigBlock) entry; +} BlkMigBlock; + +typedef struct BlkMigState { + int blk_enable; + int shared_base; + QSIMPLEQ_HEAD(bmds_list, BlkMigDevState) bmds_list; + QSIMPLEQ_HEAD(blk_list, BlkMigBlock) blk_list; + int submitted; + int read_done; + int transferred; + int64_t total_sector_sum; + int prev_progress; + int bulk_completed; + long double total_time; + int reads; +} BlkMigState; + +static BlkMigState block_mig_state; + +static void blk_send(QEMUFile *f, BlkMigBlock * blk) +{ + int len; + + /* sector number and flags */ + qemu_put_be64(f, (blk->sector << BDRV_SECTOR_BITS) + | BLK_MIG_FLAG_DEVICE_BLOCK); + + /* device name */ + len = strlen(blk->bmds->bs->device_name); + qemu_put_byte(f, len); + qemu_put_buffer(f, (uint8_t *)blk->bmds->bs->device_name, len); + + qemu_put_buffer(f, blk->buf, BLOCK_SIZE); +} + +int blk_mig_active(void) +{ + return !QSIMPLEQ_EMPTY(&block_mig_state.bmds_list); +} + +uint64_t blk_mig_bytes_transferred(void) +{ + BlkMigDevState *bmds; + uint64_t sum = 0; + + QSIMPLEQ_FOREACH(bmds, &block_mig_state.bmds_list, entry) { + sum += bmds->completed_sectors; + } + return sum << BDRV_SECTOR_BITS; +} + +uint64_t blk_mig_bytes_remaining(void) +{ + return blk_mig_bytes_total() - blk_mig_bytes_transferred(); +} + +uint64_t blk_mig_bytes_total(void) +{ + BlkMigDevState *bmds; + uint64_t sum = 0; + + QSIMPLEQ_FOREACH(bmds, &block_mig_state.bmds_list, entry) { + sum += bmds->total_sectors; + } + return sum << BDRV_SECTOR_BITS; +} + +static inline void add_avg_read_time(int64_t time) +{ + block_mig_state.reads++; + block_mig_state.total_time += time; +} + +static inline long double compute_read_bwidth(void) +{ + assert(block_mig_state.total_time != 0); + return (block_mig_state.reads * BLOCK_SIZE)/ block_mig_state.total_time; +} + +static int bmds_aio_inflight(BlkMigDevState *bmds, int64_t sector) +{ + int64_t chunk = sector / (int64_t)BDRV_SECTORS_PER_DIRTY_CHUNK; + + if ((sector << BDRV_SECTOR_BITS) < bdrv_getlength(bmds->bs)) { + return !!(bmds->aio_bitmap[chunk / (sizeof(unsigned long) * 8)] & + (1UL << (chunk % (sizeof(unsigned long) * 8)))); + } else { + return 0; + } +} + +static void bmds_set_aio_inflight(BlkMigDevState *bmds, int64_t sector_num, + int nb_sectors, int set) +{ + int64_t start, end; + unsigned long val, idx, bit; + + start = sector_num / BDRV_SECTORS_PER_DIRTY_CHUNK; + end = (sector_num + nb_sectors - 1) / BDRV_SECTORS_PER_DIRTY_CHUNK; + + for (; start <= end; start++) { + idx = start / (sizeof(unsigned long) * 8); + bit = start % (sizeof(unsigned long) * 8); + val = bmds->aio_bitmap[idx]; + if (set) { + val |= 1UL << bit; + } else { + val &= ~(1UL << bit); + } + bmds->aio_bitmap[idx] = val; + } +} + +static void alloc_aio_bitmap(BlkMigDevState *bmds) +{ + BlockDriverState *bs = bmds->bs; + int64_t bitmap_size; + + bitmap_size = (bdrv_getlength(bs) >> BDRV_SECTOR_BITS) + + BDRV_SECTORS_PER_DIRTY_CHUNK * 8 - 1; + bitmap_size /= BDRV_SECTORS_PER_DIRTY_CHUNK * 8; + + bmds->aio_bitmap = qemu_mallocz(bitmap_size); +} + +static void blk_mig_read_cb(void *opaque, int ret) +{ + BlkMigBlock *blk = opaque; + + blk->ret = ret; + + blk->time = qemu_get_clock_ns(rt_clock) - blk->time; + + add_avg_read_time(blk->time); + + QSIMPLEQ_INSERT_TAIL(&block_mig_state.blk_list, blk, entry); + bmds_set_aio_inflight(blk->bmds, blk->sector, blk->nr_sectors, 0); + + block_mig_state.submitted--; + block_mig_state.read_done++; + assert(block_mig_state.submitted >= 0); +} + +static int mig_save_device_bulk(Monitor *mon, QEMUFile *f, + BlkMigDevState *bmds) +{ + int64_t total_sectors = bmds->total_sectors; + int64_t cur_sector = bmds->cur_sector; + BlockDriverState *bs = bmds->bs; + BlkMigBlock *blk; + int nr_sectors; + + if (bmds->shared_base) { + while (cur_sector < total_sectors && + !bdrv_is_allocated(bs, cur_sector, MAX_IS_ALLOCATED_SEARCH, + &nr_sectors)) { + cur_sector += nr_sectors; + } + } + + if (cur_sector >= total_sectors) { + bmds->cur_sector = bmds->completed_sectors = total_sectors; + return 1; + } + + bmds->completed_sectors = cur_sector; + + cur_sector &= ~((int64_t)BDRV_SECTORS_PER_DIRTY_CHUNK - 1); + + /* we are going to transfer a full block even if it is not allocated */ + nr_sectors = BDRV_SECTORS_PER_DIRTY_CHUNK; + + if (total_sectors - cur_sector < BDRV_SECTORS_PER_DIRTY_CHUNK) { + nr_sectors = total_sectors - cur_sector; + } + + blk = qemu_malloc(sizeof(BlkMigBlock)); + blk->buf = qemu_malloc(BLOCK_SIZE); + blk->bmds = bmds; + blk->sector = cur_sector; + blk->nr_sectors = nr_sectors; + + blk->iov.iov_base = blk->buf; + blk->iov.iov_len = nr_sectors * BDRV_SECTOR_SIZE; + qemu_iovec_init_external(&blk->qiov, &blk->iov, 1); + + blk->time = qemu_get_clock_ns(rt_clock); + + blk->aiocb = bdrv_aio_readv(bs, cur_sector, &blk->qiov, + nr_sectors, blk_mig_read_cb, blk); + if (!blk->aiocb) { + goto error; + } + block_mig_state.submitted++; + + bdrv_reset_dirty(bs, cur_sector, nr_sectors); + bmds->cur_sector = cur_sector + nr_sectors; + + return (bmds->cur_sector >= total_sectors); + +error: + monitor_printf(mon, "Error reading sector %" PRId64 "\n", cur_sector); + qemu_file_set_error(f); + qemu_free(blk->buf); + qemu_free(blk); + return 0; +} + +static void set_dirty_tracking(int enable) +{ + BlkMigDevState *bmds; + + QSIMPLEQ_FOREACH(bmds, &block_mig_state.bmds_list, entry) { + bdrv_set_dirty_tracking(bmds->bs, enable); + } +} + +static void init_blk_migration_it(void *opaque, BlockDriverState *bs) +{ + Monitor *mon = opaque; + BlkMigDevState *bmds; + int64_t sectors; + + if (!bdrv_is_read_only(bs)) { + sectors = bdrv_getlength(bs) >> BDRV_SECTOR_BITS; + if (sectors <= 0) { + return; + } + + bmds = qemu_mallocz(sizeof(BlkMigDevState)); + bmds->bs = bs; + bmds->bulk_completed = 0; + bmds->total_sectors = sectors; + bmds->completed_sectors = 0; + bmds->shared_base = block_mig_state.shared_base; + alloc_aio_bitmap(bmds); + drive_get_ref(drive_get_by_blockdev(bs)); + bdrv_set_in_use(bs, 1); + + block_mig_state.total_sector_sum += sectors; + + if (bmds->shared_base) { + monitor_printf(mon, "Start migration for %s with shared base " + "image\n", + bs->device_name); + } else { + monitor_printf(mon, "Start full migration for %s\n", + bs->device_name); + } + + QSIMPLEQ_INSERT_TAIL(&block_mig_state.bmds_list, bmds, entry); + } +} + +static void init_blk_migration(Monitor *mon, QEMUFile *f) +{ + block_mig_state.submitted = 0; + block_mig_state.read_done = 0; + block_mig_state.transferred = 0; + block_mig_state.total_sector_sum = 0; + block_mig_state.prev_progress = -1; + block_mig_state.bulk_completed = 0; + block_mig_state.total_time = 0; + block_mig_state.reads = 0; + + bdrv_iterate(init_blk_migration_it, mon); +} + +static int blk_mig_save_bulked_block(Monitor *mon, QEMUFile *f) +{ + int64_t completed_sector_sum = 0; + BlkMigDevState *bmds; + int progress; + int ret = 0; + + QSIMPLEQ_FOREACH(bmds, &block_mig_state.bmds_list, entry) { + if (bmds->bulk_completed == 0) { + if (mig_save_device_bulk(mon, f, bmds) == 1) { + /* completed bulk section for this device */ + bmds->bulk_completed = 1; + } + completed_sector_sum += bmds->completed_sectors; + ret = 1; + break; + } else { + completed_sector_sum += bmds->completed_sectors; + } + } + + if (block_mig_state.total_sector_sum != 0) { + progress = completed_sector_sum * 100 / + block_mig_state.total_sector_sum; + } else { + progress = 100; + } + if (progress != block_mig_state.prev_progress) { + block_mig_state.prev_progress = progress; + qemu_put_be64(f, (progress << BDRV_SECTOR_BITS) + | BLK_MIG_FLAG_PROGRESS); + monitor_printf(mon, "Completed %d %%\r", progress); + monitor_flush(mon); + } + + return ret; +} + +static void blk_mig_reset_dirty_cursor(void) +{ + BlkMigDevState *bmds; + + QSIMPLEQ_FOREACH(bmds, &block_mig_state.bmds_list, entry) { + bmds->cur_dirty = 0; + } +} + +static int mig_save_device_dirty(Monitor *mon, QEMUFile *f, + BlkMigDevState *bmds, int is_async) +{ + BlkMigBlock *blk; + int64_t total_sectors = bmds->total_sectors; + int64_t sector; + int nr_sectors; + + for (sector = bmds->cur_dirty; sector < bmds->total_sectors;) { + if (bmds_aio_inflight(bmds, sector)) { + qemu_aio_flush(); + } + if (bdrv_get_dirty(bmds->bs, sector)) { + + if (total_sectors - sector < BDRV_SECTORS_PER_DIRTY_CHUNK) { + nr_sectors = total_sectors - sector; + } else { + nr_sectors = BDRV_SECTORS_PER_DIRTY_CHUNK; + } + blk = qemu_malloc(sizeof(BlkMigBlock)); + blk->buf = qemu_malloc(BLOCK_SIZE); + blk->bmds = bmds; + blk->sector = sector; + blk->nr_sectors = nr_sectors; + + if (is_async) { + blk->iov.iov_base = blk->buf; + blk->iov.iov_len = nr_sectors * BDRV_SECTOR_SIZE; + qemu_iovec_init_external(&blk->qiov, &blk->iov, 1); + + blk->time = qemu_get_clock_ns(rt_clock); + + blk->aiocb = bdrv_aio_readv(bmds->bs, sector, &blk->qiov, + nr_sectors, blk_mig_read_cb, blk); + if (!blk->aiocb) { + goto error; + } + block_mig_state.submitted++; + bmds_set_aio_inflight(bmds, sector, nr_sectors, 1); + } else { + if (bdrv_read(bmds->bs, sector, blk->buf, + nr_sectors) < 0) { + goto error; + } + blk_send(f, blk); + + qemu_free(blk->buf); + qemu_free(blk); + } + + bdrv_reset_dirty(bmds->bs, sector, nr_sectors); + break; + } + sector += BDRV_SECTORS_PER_DIRTY_CHUNK; + bmds->cur_dirty = sector; + } + + return (bmds->cur_dirty >= bmds->total_sectors); + +error: + monitor_printf(mon, "Error reading sector %" PRId64 "\n", sector); + qemu_file_set_error(f); + qemu_free(blk->buf); + qemu_free(blk); + return 0; +} + +static int blk_mig_save_dirty_block(Monitor *mon, QEMUFile *f, int is_async) +{ + BlkMigDevState *bmds; + int ret = 0; + + QSIMPLEQ_FOREACH(bmds, &block_mig_state.bmds_list, entry) { + if (mig_save_device_dirty(mon, f, bmds, is_async) == 0) { + ret = 1; + break; + } + } + + return ret; +} + +static void flush_blks(QEMUFile* f) +{ + BlkMigBlock *blk; + + DPRINTF("%s Enter submitted %d read_done %d transferred %d\n", + __FUNCTION__, block_mig_state.submitted, block_mig_state.read_done, + block_mig_state.transferred); + + while ((blk = QSIMPLEQ_FIRST(&block_mig_state.blk_list)) != NULL) { + if (qemu_file_rate_limit(f)) { + break; + } + if (blk->ret < 0) { + qemu_file_set_error(f); + break; + } + blk_send(f, blk); + + QSIMPLEQ_REMOVE_HEAD(&block_mig_state.blk_list, entry); + qemu_free(blk->buf); + qemu_free(blk); + + block_mig_state.read_done--; + block_mig_state.transferred++; + assert(block_mig_state.read_done >= 0); + } + + DPRINTF("%s Exit submitted %d read_done %d transferred %d\n", __FUNCTION__, + block_mig_state.submitted, block_mig_state.read_done, + block_mig_state.transferred); +} + +static int64_t get_remaining_dirty(void) +{ + BlkMigDevState *bmds; + int64_t dirty = 0; + + QSIMPLEQ_FOREACH(bmds, &block_mig_state.bmds_list, entry) { + dirty += bdrv_get_dirty_count(bmds->bs); + } + + return dirty * BLOCK_SIZE; +} + +static int is_stage2_completed(void) +{ + int64_t remaining_dirty; + long double bwidth; + + if (block_mig_state.bulk_completed == 1) { + + remaining_dirty = get_remaining_dirty(); + if (remaining_dirty == 0) { + return 1; + } + + bwidth = compute_read_bwidth(); + + if ((remaining_dirty / bwidth) <= + migrate_max_downtime()) { + /* finish stage2 because we think that we can finish remaing work + below max_downtime */ + + return 1; + } + } + + return 0; +} + +static void blk_mig_cleanup(Monitor *mon) +{ + BlkMigDevState *bmds; + BlkMigBlock *blk; + + set_dirty_tracking(0); + + while ((bmds = QSIMPLEQ_FIRST(&block_mig_state.bmds_list)) != NULL) { + QSIMPLEQ_REMOVE_HEAD(&block_mig_state.bmds_list, entry); + bdrv_set_in_use(bmds->bs, 0); + drive_put_ref(drive_get_by_blockdev(bmds->bs)); + qemu_free(bmds->aio_bitmap); + qemu_free(bmds); + } + + while ((blk = QSIMPLEQ_FIRST(&block_mig_state.blk_list)) != NULL) { + QSIMPLEQ_REMOVE_HEAD(&block_mig_state.blk_list, entry); + qemu_free(blk->buf); + qemu_free(blk); + } + + monitor_printf(mon, "\n"); +} + +static int block_save_live(Monitor *mon, QEMUFile *f, int stage, void *opaque) +{ + DPRINTF("Enter save live stage %d submitted %d transferred %d\n", + stage, block_mig_state.submitted, block_mig_state.transferred); + + if (stage < 0) { + blk_mig_cleanup(mon); + return 0; + } + + if (block_mig_state.blk_enable != 1) { + /* no need to migrate storage */ + qemu_put_be64(f, BLK_MIG_FLAG_EOS); + return 1; + } + + if (stage == 1) { + init_blk_migration(mon, f); + + /* start track dirty blocks */ + set_dirty_tracking(1); + } + + flush_blks(f); + + if (qemu_file_has_error(f)) { + blk_mig_cleanup(mon); + return 0; + } + + blk_mig_reset_dirty_cursor(); + + if (stage == 2) { + /* control the rate of transfer */ + while ((block_mig_state.submitted + + block_mig_state.read_done) * BLOCK_SIZE < + qemu_file_get_rate_limit(f)) { + if (block_mig_state.bulk_completed == 0) { + /* first finish the bulk phase */ + if (blk_mig_save_bulked_block(mon, f) == 0) { + /* finished saving bulk on all devices */ + block_mig_state.bulk_completed = 1; + } + } else { + if (blk_mig_save_dirty_block(mon, f, 1) == 0) { + /* no more dirty blocks */ + break; + } + } + } + + flush_blks(f); + + if (qemu_file_has_error(f)) { + blk_mig_cleanup(mon); + return 0; + } + } + + if (stage == 3) { + /* we know for sure that save bulk is completed and + all async read completed */ + assert(block_mig_state.submitted == 0); + + while (blk_mig_save_dirty_block(mon, f, 0) != 0); + blk_mig_cleanup(mon); + + /* report completion */ + qemu_put_be64(f, (100 << BDRV_SECTOR_BITS) | BLK_MIG_FLAG_PROGRESS); + + if (qemu_file_has_error(f)) { + return 0; + } + + monitor_printf(mon, "Block migration completed\n"); + } + + qemu_put_be64(f, BLK_MIG_FLAG_EOS); + + return ((stage == 2) && is_stage2_completed()); +} + +static int block_load(QEMUFile *f, void *opaque, int version_id) +{ + static int banner_printed; + int len, flags; + char device_name[256]; + int64_t addr; + BlockDriverState *bs, *bs_prev = NULL; + uint8_t *buf; + int64_t total_sectors = 0; + int nr_sectors; + + do { + addr = qemu_get_be64(f); + + flags = addr & ~BDRV_SECTOR_MASK; + addr >>= BDRV_SECTOR_BITS; + + if (flags & BLK_MIG_FLAG_DEVICE_BLOCK) { + int ret; + /* get device name */ + len = qemu_get_byte(f); + qemu_get_buffer(f, (uint8_t *)device_name, len); + device_name[len] = '\0'; + + bs = bdrv_find(device_name); + if (!bs) { + fprintf(stderr, "Error unknown block device %s\n", + device_name); + return -EINVAL; + } + + if (bs != bs_prev) { + bs_prev = bs; + total_sectors = bdrv_getlength(bs) >> BDRV_SECTOR_BITS; + if (total_sectors <= 0) { + error_report("Error getting length of block device %s\n", + device_name); + return -EINVAL; + } + } + + if (total_sectors - addr < BDRV_SECTORS_PER_DIRTY_CHUNK) { + nr_sectors = total_sectors - addr; + } else { + nr_sectors = BDRV_SECTORS_PER_DIRTY_CHUNK; + } + + buf = qemu_malloc(BLOCK_SIZE); + + qemu_get_buffer(f, buf, BLOCK_SIZE); + ret = bdrv_write(bs, addr, buf, nr_sectors); + + qemu_free(buf); + if (ret < 0) { + return ret; + } + } else if (flags & BLK_MIG_FLAG_PROGRESS) { + if (!banner_printed) { + printf("Receiving block device images\n"); + banner_printed = 1; + } + printf("Completed %d %%%c", (int)addr, + (addr == 100) ? '\n' : '\r'); + fflush(stdout); + } else if (!(flags & BLK_MIG_FLAG_EOS)) { + fprintf(stderr, "Unknown flags\n"); + return -EINVAL; + } + if (qemu_file_has_error(f)) { + return -EIO; + } + } while (!(flags & BLK_MIG_FLAG_EOS)); + + return 0; +} + +static void block_set_params(int blk_enable, int shared_base, void *opaque) +{ + block_mig_state.blk_enable = blk_enable; + block_mig_state.shared_base = shared_base; + + /* shared base means that blk_enable = 1 */ + block_mig_state.blk_enable |= shared_base; +} + +void blk_mig_init(void) +{ + QSIMPLEQ_INIT(&block_mig_state.bmds_list); + QSIMPLEQ_INIT(&block_mig_state.blk_list); + + register_savevm_live(NULL, "block", 0, 1, block_set_params, + block_save_live, NULL, block_load, &block_mig_state); +} diff --git a/block-migration.h b/block-migration.h new file mode 100644 index 0000000..ffa8ac0 --- /dev/null +++ b/block-migration.h @@ -0,0 +1,23 @@ +/* + * QEMU live block migration + * + * Copyright IBM, Corp. 2009 + * + * Authors: + * Liran Schour + * + * This work is licensed under the terms of the GNU GPL, version 2. See + * the COPYING file in the top-level directory. + * + */ + +#ifndef BLOCK_MIGRATION_H +#define BLOCK_MIGRATION_H + +void blk_mig_init(void); +int blk_mig_active(void); +uint64_t blk_mig_bytes_transferred(void); +uint64_t blk_mig_bytes_remaining(void); +uint64_t blk_mig_bytes_total(void); + +#endif /* BLOCK_MIGRATION_H */ diff --git a/block.c b/block.c new file mode 100644 index 0000000..b203875 --- /dev/null +++ b/block.c @@ -0,0 +1,2946 @@ +/* + * QEMU System Emulator block driver + * + * 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 "config-host.h" +#include "qemu-common.h" +#include "trace.h" +#include "monitor.h" +#include "block_int.h" +#include "module.h" +#include "qemu-objects.h" + +#ifdef CONFIG_BSD +#include +#include +#include +#include +#ifndef __DragonFly__ +#include +#endif +#endif + +#ifdef _WIN32 +#include +#endif + +static BlockDriverAIOCB *bdrv_aio_readv_em(BlockDriverState *bs, + int64_t sector_num, QEMUIOVector *qiov, int nb_sectors, + BlockDriverCompletionFunc *cb, void *opaque); +static BlockDriverAIOCB *bdrv_aio_writev_em(BlockDriverState *bs, + int64_t sector_num, QEMUIOVector *qiov, int nb_sectors, + BlockDriverCompletionFunc *cb, void *opaque); +static BlockDriverAIOCB *bdrv_aio_flush_em(BlockDriverState *bs, + BlockDriverCompletionFunc *cb, void *opaque); +static BlockDriverAIOCB *bdrv_aio_noop_em(BlockDriverState *bs, + BlockDriverCompletionFunc *cb, void *opaque); +static int bdrv_read_em(BlockDriverState *bs, int64_t sector_num, + uint8_t *buf, int nb_sectors); +static int bdrv_write_em(BlockDriverState *bs, int64_t sector_num, + const uint8_t *buf, int nb_sectors); + +static QTAILQ_HEAD(, BlockDriverState) bdrv_states = + QTAILQ_HEAD_INITIALIZER(bdrv_states); + +static QLIST_HEAD(, BlockDriver) bdrv_drivers = + QLIST_HEAD_INITIALIZER(bdrv_drivers); + +/* The device to use for VM snapshots */ +static BlockDriverState *bs_snapshots; + +/* If non-zero, use only whitelisted block drivers */ +static int use_bdrv_whitelist; + +#ifdef _WIN32 +static int is_windows_drive_prefix(const char *filename) +{ + return (((filename[0] >= 'a' && filename[0] <= 'z') || + (filename[0] >= 'A' && filename[0] <= 'Z')) && + filename[1] == ':'); +} + +int is_windows_drive(const char *filename) +{ + if (is_windows_drive_prefix(filename) && + filename[2] == '\0') + return 1; + if (strstart(filename, "\\\\.\\", NULL) || + strstart(filename, "//./", NULL)) + return 1; + return 0; +} +#endif + +/* check if the path starts with ":" */ +static int path_has_protocol(const char *path) +{ +#ifdef _WIN32 + if (is_windows_drive(path) || + is_windows_drive_prefix(path)) { + return 0; + } +#endif + + return strchr(path, ':') != NULL; +} + +int path_is_absolute(const char *path) +{ + const char *p; +#ifdef _WIN32 + /* specific case for names like: "\\.\d:" */ + if (*path == '/' || *path == '\\') + return 1; +#endif + p = strchr(path, ':'); + if (p) + p++; + else + p = path; +#ifdef _WIN32 + return (*p == '/' || *p == '\\'); +#else + return (*p == '/'); +#endif +} + +/* if filename is absolute, just copy it to dest. Otherwise, build a + path to it by considering it is relative to base_path. URL are + supported. */ +void path_combine(char *dest, int dest_size, + const char *base_path, + const char *filename) +{ + const char *p, *p1; + int len; + + if (dest_size <= 0) + return; + if (path_is_absolute(filename)) { + pstrcpy(dest, dest_size, filename); + } else { + p = strchr(base_path, ':'); + if (p) + p++; + else + p = base_path; + p1 = strrchr(base_path, '/'); +#ifdef _WIN32 + { + const char *p2; + p2 = strrchr(base_path, '\\'); + if (!p1 || p2 > p1) + p1 = p2; + } +#endif + if (p1) + p1++; + else + p1 = base_path; + if (p1 > p) + p = p1; + len = p - base_path; + if (len > dest_size - 1) + len = dest_size - 1; + memcpy(dest, base_path, len); + dest[len] = '\0'; + pstrcat(dest, dest_size, filename); + } +} + +void bdrv_register(BlockDriver *bdrv) +{ + if (!bdrv->bdrv_aio_readv) { + /* add AIO emulation layer */ + bdrv->bdrv_aio_readv = bdrv_aio_readv_em; + bdrv->bdrv_aio_writev = bdrv_aio_writev_em; + } else if (!bdrv->bdrv_read) { + /* add synchronous IO emulation layer */ + bdrv->bdrv_read = bdrv_read_em; + bdrv->bdrv_write = bdrv_write_em; + } + + if (!bdrv->bdrv_aio_flush) + bdrv->bdrv_aio_flush = bdrv_aio_flush_em; + + QLIST_INSERT_HEAD(&bdrv_drivers, bdrv, list); +} + +/* create a new block device (by default it is empty) */ +BlockDriverState *bdrv_new(const char *device_name) +{ + BlockDriverState *bs; + + bs = qemu_mallocz(sizeof(BlockDriverState)); + pstrcpy(bs->device_name, sizeof(bs->device_name), device_name); + if (device_name[0] != '\0') { + QTAILQ_INSERT_TAIL(&bdrv_states, bs, list); + } + return bs; +} + +BlockDriver *bdrv_find_format(const char *format_name) +{ + BlockDriver *drv1; + QLIST_FOREACH(drv1, &bdrv_drivers, list) { + if (!strcmp(drv1->format_name, format_name)) { + return drv1; + } + } + return NULL; +} + +static int bdrv_is_whitelisted(BlockDriver *drv) +{ + static const char *whitelist[] = { + CONFIG_BDRV_WHITELIST + }; + const char **p; + + if (!whitelist[0]) + return 1; /* no whitelist, anything goes */ + + for (p = whitelist; *p; p++) { + if (!strcmp(drv->format_name, *p)) { + return 1; + } + } + return 0; +} + +BlockDriver *bdrv_find_whitelisted_format(const char *format_name) +{ + BlockDriver *drv = bdrv_find_format(format_name); + return drv && bdrv_is_whitelisted(drv) ? drv : NULL; +} + +int bdrv_create(BlockDriver *drv, const char* filename, + QEMUOptionParameter *options) +{ + if (!drv->bdrv_create) + return -ENOTSUP; + + return drv->bdrv_create(filename, options); +} + +int bdrv_create_file(const char* filename, QEMUOptionParameter *options) +{ + BlockDriver *drv; + + drv = bdrv_find_protocol(filename); + if (drv == NULL) { + return -ENOENT; + } + + return bdrv_create(drv, filename, options); +} + +#ifdef _WIN32 +void get_tmp_filename(char *filename, int size) +{ + char temp_dir[MAX_PATH]; + + GetTempPath(MAX_PATH, temp_dir); + GetTempFileName(temp_dir, "qem", 0, filename); +} +#else +void get_tmp_filename(char *filename, int size) +{ + int fd; + const char *tmpdir; + /* XXX: race condition possible */ + tmpdir = getenv("TMPDIR"); + if (!tmpdir) + tmpdir = "/tmp"; + snprintf(filename, size, "%s/vl.XXXXXX", tmpdir); + fd = mkstemp(filename); + close(fd); +} +#endif + +/* + * Detect host devices. By convention, /dev/cdrom[N] is always + * recognized as a host CDROM. + */ +static BlockDriver *find_hdev_driver(const char *filename) +{ + int score_max = 0, score; + BlockDriver *drv = NULL, *d; + + QLIST_FOREACH(d, &bdrv_drivers, list) { + if (d->bdrv_probe_device) { + score = d->bdrv_probe_device(filename); + if (score > score_max) { + score_max = score; + drv = d; + } + } + } + + return drv; +} + +BlockDriver *bdrv_find_protocol(const char *filename) +{ + BlockDriver *drv1; + char protocol[128]; + int len; + const char *p; + + /* TODO Drivers without bdrv_file_open must be specified explicitly */ + + /* + * XXX(hch): we really should not let host device detection + * override an explicit protocol specification, but moving this + * later breaks access to device names with colons in them. + * Thanks to the brain-dead persistent naming schemes on udev- + * based Linux systems those actually are quite common. + */ + drv1 = find_hdev_driver(filename); + if (drv1) { + return drv1; + } + + if (!path_has_protocol(filename)) { + return bdrv_find_format("file"); + } + p = strchr(filename, ':'); + assert(p != NULL); + len = p - filename; + if (len > sizeof(protocol) - 1) + len = sizeof(protocol) - 1; + memcpy(protocol, filename, len); + protocol[len] = '\0'; + QLIST_FOREACH(drv1, &bdrv_drivers, list) { + if (drv1->protocol_name && + !strcmp(drv1->protocol_name, protocol)) { + return drv1; + } + } + return NULL; +} + +static int find_image_format(const char *filename, BlockDriver **pdrv) +{ + int ret, score, score_max; + BlockDriver *drv1, *drv; + uint8_t buf[2048]; + BlockDriverState *bs; + + ret = bdrv_file_open(&bs, filename, 0); + if (ret < 0) { + *pdrv = NULL; + return ret; + } + + /* Return the raw BlockDriver * to scsi-generic devices or empty drives */ + if (bs->sg || !bdrv_is_inserted(bs)) { + bdrv_delete(bs); + drv = bdrv_find_format("raw"); + if (!drv) { + ret = -ENOENT; + } + *pdrv = drv; + return ret; + } + + ret = bdrv_pread(bs, 0, buf, sizeof(buf)); + bdrv_delete(bs); + if (ret < 0) { + *pdrv = NULL; + return ret; + } + + score_max = 0; + drv = NULL; + QLIST_FOREACH(drv1, &bdrv_drivers, list) { + if (drv1->bdrv_probe) { + score = drv1->bdrv_probe(buf, ret, filename); + if (score > score_max) { + score_max = score; + drv = drv1; + } + } + } + if (!drv) { + ret = -ENOENT; + } + *pdrv = drv; + return ret; +} + +/** + * Set the current 'total_sectors' value + */ +static int refresh_total_sectors(BlockDriverState *bs, int64_t hint) +{ + BlockDriver *drv = bs->drv; + + /* Do not attempt drv->bdrv_getlength() on scsi-generic devices */ + if (bs->sg) + return 0; + + /* query actual device if possible, otherwise just trust the hint */ + if (drv->bdrv_getlength) { + int64_t length = drv->bdrv_getlength(bs); + if (length < 0) { + return length; + } + hint = length >> BDRV_SECTOR_BITS; + } + + bs->total_sectors = hint; + return 0; +} + +/* + * Common part for opening disk images and files + */ +static int bdrv_open_common(BlockDriverState *bs, const char *filename, + int flags, BlockDriver *drv) +{ + int ret, open_flags; + + assert(drv != NULL); + + bs->file = NULL; + bs->total_sectors = 0; + bs->encrypted = 0; + bs->valid_key = 0; + bs->open_flags = flags; + /* buffer_alignment defaulted to 512, drivers can change this value */ + bs->buffer_alignment = 512; + + pstrcpy(bs->filename, sizeof(bs->filename), filename); + + if (use_bdrv_whitelist && !bdrv_is_whitelisted(drv)) { + return -ENOTSUP; + } + + bs->drv = drv; + bs->opaque = qemu_mallocz(drv->instance_size); + + /* + * Yes, BDRV_O_NOCACHE aka O_DIRECT means we have to present a + * write cache to the guest. We do need the fdatasync to flush + * out transactions for block allocations, and we maybe have a + * volatile write cache in our backing device to deal with. + */ + if (flags & (BDRV_O_CACHE_WB|BDRV_O_NOCACHE)) + bs->enable_write_cache = 1; + + /* + * Clear flags that are internal to the block layer before opening the + * image. + */ + open_flags = flags & ~(BDRV_O_SNAPSHOT | BDRV_O_NO_BACKING); + + /* + * Snapshots should be writeable. + */ + if (bs->is_temporary) { + open_flags |= BDRV_O_RDWR; + } + + /* Open the image, either directly or using a protocol */ + if (drv->bdrv_file_open) { + ret = drv->bdrv_file_open(bs, filename, open_flags); + } else { + ret = bdrv_file_open(&bs->file, filename, open_flags); + if (ret >= 0) { + ret = drv->bdrv_open(bs, open_flags); + } + } + + if (ret < 0) { + goto free_and_fail; + } + + bs->keep_read_only = bs->read_only = !(open_flags & BDRV_O_RDWR); + + ret = refresh_total_sectors(bs, bs->total_sectors); + if (ret < 0) { + goto free_and_fail; + } + +#ifndef _WIN32 + if (bs->is_temporary) { + unlink(filename); + } +#endif + return 0; + +free_and_fail: + if (bs->file) { + bdrv_delete(bs->file); + bs->file = NULL; + } + qemu_free(bs->opaque); + bs->opaque = NULL; + bs->drv = NULL; + return ret; +} + +/* + * Opens a file using a protocol (file, host_device, nbd, ...) + */ +int bdrv_file_open(BlockDriverState **pbs, const char *filename, int flags) +{ + BlockDriverState *bs; + BlockDriver *drv; + int ret; + + drv = bdrv_find_protocol(filename); + if (!drv) { + return -ENOENT; + } + + bs = bdrv_new(""); + ret = bdrv_open_common(bs, filename, flags, drv); + if (ret < 0) { + bdrv_delete(bs); + return ret; + } + bs->growable = 1; + *pbs = bs; + return 0; +} + +/* + * Opens a disk image (raw, qcow2, vmdk, ...) + */ +int bdrv_open(BlockDriverState *bs, const char *filename, int flags, + BlockDriver *drv) +{ + int ret; + + if (flags & BDRV_O_SNAPSHOT) { + BlockDriverState *bs1; + int64_t total_size; + int is_protocol = 0; + BlockDriver *bdrv_qcow2; + QEMUOptionParameter *options; + char tmp_filename[PATH_MAX]; + char backing_filename[PATH_MAX]; + + /* if snapshot, we create a temporary backing file and open it + instead of opening 'filename' directly */ + + /* if there is a backing file, use it */ + bs1 = bdrv_new(""); + ret = bdrv_open(bs1, filename, 0, drv); + if (ret < 0) { + bdrv_delete(bs1); + return ret; + } + total_size = bdrv_getlength(bs1) & BDRV_SECTOR_MASK; + + if (bs1->drv && bs1->drv->protocol_name) + is_protocol = 1; + + bdrv_delete(bs1); + + get_tmp_filename(tmp_filename, sizeof(tmp_filename)); + + /* Real path is meaningless for protocols */ + if (is_protocol) + snprintf(backing_filename, sizeof(backing_filename), + "%s", filename); + else if (!realpath(filename, backing_filename)) + return -errno; + + bdrv_qcow2 = bdrv_find_format("qcow2"); + options = parse_option_parameters("", bdrv_qcow2->create_options, NULL); + + set_option_parameter_int(options, BLOCK_OPT_SIZE, total_size); + set_option_parameter(options, BLOCK_OPT_BACKING_FILE, backing_filename); + if (drv) { + set_option_parameter(options, BLOCK_OPT_BACKING_FMT, + drv->format_name); + } + + ret = bdrv_create(bdrv_qcow2, tmp_filename, options); + free_option_parameters(options); + if (ret < 0) { + return ret; + } + + filename = tmp_filename; + drv = bdrv_qcow2; + bs->is_temporary = 1; + } + + /* Find the right image format driver */ + if (!drv) { + ret = find_image_format(filename, &drv); + } + + if (!drv) { + goto unlink_and_fail; + } + + /* Open the image */ + ret = bdrv_open_common(bs, filename, flags, drv); + if (ret < 0) { + goto unlink_and_fail; + } + + /* If there is a backing file, use it */ + if ((flags & BDRV_O_NO_BACKING) == 0 && bs->backing_file[0] != '\0') { + char backing_filename[PATH_MAX]; + int back_flags; + BlockDriver *back_drv = NULL; + + bs->backing_hd = bdrv_new(""); + + if (path_has_protocol(bs->backing_file)) { + pstrcpy(backing_filename, sizeof(backing_filename), + bs->backing_file); + } else { + path_combine(backing_filename, sizeof(backing_filename), + filename, bs->backing_file); + } + + if (bs->backing_format[0] != '\0') { + back_drv = bdrv_find_format(bs->backing_format); + } + + /* backing files always opened read-only */ + back_flags = + flags & ~(BDRV_O_RDWR | BDRV_O_SNAPSHOT | BDRV_O_NO_BACKING); + + ret = bdrv_open(bs->backing_hd, backing_filename, back_flags, back_drv); + if (ret < 0) { + bdrv_close(bs); + return ret; + } + if (bs->is_temporary) { + bs->backing_hd->keep_read_only = !(flags & BDRV_O_RDWR); + } else { + /* base image inherits from "parent" */ + bs->backing_hd->keep_read_only = bs->keep_read_only; + } + } + + if (!bdrv_key_required(bs)) { + /* call the change callback */ + bs->media_changed = 1; + if (bs->change_cb) + bs->change_cb(bs->change_opaque, CHANGE_MEDIA); + } + + return 0; + +unlink_and_fail: + if (bs->is_temporary) { + unlink(filename); + } + return ret; +} + +void bdrv_close(BlockDriverState *bs) +{ + if (bs->drv) { + if (bs == bs_snapshots) { + bs_snapshots = NULL; + } + if (bs->backing_hd) { + bdrv_delete(bs->backing_hd); + bs->backing_hd = NULL; + } + bs->drv->bdrv_close(bs); + qemu_free(bs->opaque); +#ifdef _WIN32 + if (bs->is_temporary) { + unlink(bs->filename); + } +#endif + bs->opaque = NULL; + bs->drv = NULL; + + if (bs->file != NULL) { + bdrv_close(bs->file); + } + + /* call the change callback */ + bs->media_changed = 1; + if (bs->change_cb) + bs->change_cb(bs->change_opaque, CHANGE_MEDIA); + } +} + +void bdrv_close_all(void) +{ + BlockDriverState *bs; + + QTAILQ_FOREACH(bs, &bdrv_states, list) { + bdrv_close(bs); + } +} + +/* make a BlockDriverState anonymous by removing from bdrv_state list. + Also, NULL terminate the device_name to prevent double remove */ +void bdrv_make_anon(BlockDriverState *bs) +{ + if (bs->device_name[0] != '\0') { + QTAILQ_REMOVE(&bdrv_states, bs, list); + } + bs->device_name[0] = '\0'; +} + +void bdrv_delete(BlockDriverState *bs) +{ + assert(!bs->peer); + + /* remove from list, if necessary */ + bdrv_make_anon(bs); + + bdrv_close(bs); + if (bs->file != NULL) { + bdrv_delete(bs->file); + } + + assert(bs != bs_snapshots); + qemu_free(bs); +} + +int bdrv_attach(BlockDriverState *bs, DeviceState *qdev) +{ + if (bs->peer) { + return -EBUSY; + } + bs->peer = qdev; + return 0; +} + +void bdrv_detach(BlockDriverState *bs, DeviceState *qdev) +{ + assert(bs->peer == qdev); + bs->peer = NULL; +} + +DeviceState *bdrv_get_attached(BlockDriverState *bs) +{ + return bs->peer; +} + +/* + * Run consistency checks on an image + * + * Returns 0 if the check could be completed (it doesn't mean that the image is + * free of errors) or -errno when an internal error occured. The results of the + * check are stored in res. + */ +int bdrv_check(BlockDriverState *bs, BdrvCheckResult *res) +{ + if (bs->drv->bdrv_check == NULL) { + return -ENOTSUP; + } + + memset(res, 0, sizeof(*res)); + return bs->drv->bdrv_check(bs, res); +} + +#define COMMIT_BUF_SECTORS 2048 + +/* commit COW file into the raw image */ +int bdrv_commit(BlockDriverState *bs) +{ + BlockDriver *drv = bs->drv; + BlockDriver *backing_drv; + int64_t sector, total_sectors; + int n, ro, open_flags; + int ret = 0, rw_ret = 0; + uint8_t *buf; + char filename[1024]; + BlockDriverState *bs_rw, *bs_ro; + + if (!drv) + return -ENOMEDIUM; + + if (!bs->backing_hd) { + return -ENOTSUP; + } + + if (bs->backing_hd->keep_read_only) { + return -EACCES; + } + + backing_drv = bs->backing_hd->drv; + ro = bs->backing_hd->read_only; + strncpy(filename, bs->backing_hd->filename, sizeof(filename)); + open_flags = bs->backing_hd->open_flags; + + if (ro) { + /* re-open as RW */ + bdrv_delete(bs->backing_hd); + bs->backing_hd = NULL; + bs_rw = bdrv_new(""); + rw_ret = bdrv_open(bs_rw, filename, open_flags | BDRV_O_RDWR, + backing_drv); + if (rw_ret < 0) { + bdrv_delete(bs_rw); + /* try to re-open read-only */ + bs_ro = bdrv_new(""); + ret = bdrv_open(bs_ro, filename, open_flags & ~BDRV_O_RDWR, + backing_drv); + if (ret < 0) { + bdrv_delete(bs_ro); + /* drive not functional anymore */ + bs->drv = NULL; + return ret; + } + bs->backing_hd = bs_ro; + return rw_ret; + } + bs->backing_hd = bs_rw; + } + + total_sectors = bdrv_getlength(bs) >> BDRV_SECTOR_BITS; + buf = qemu_malloc(COMMIT_BUF_SECTORS * BDRV_SECTOR_SIZE); + + for (sector = 0; sector < total_sectors; sector += n) { + if (drv->bdrv_is_allocated(bs, sector, COMMIT_BUF_SECTORS, &n)) { + + if (bdrv_read(bs, sector, buf, n) != 0) { + ret = -EIO; + goto ro_cleanup; + } + + if (bdrv_write(bs->backing_hd, sector, buf, n) != 0) { + ret = -EIO; + goto ro_cleanup; + } + } + } + + if (drv->bdrv_make_empty) { + ret = drv->bdrv_make_empty(bs); + bdrv_flush(bs); + } + + /* + * Make sure all data we wrote to the backing device is actually + * stable on disk. + */ + if (bs->backing_hd) + bdrv_flush(bs->backing_hd); + +ro_cleanup: + qemu_free(buf); + + if (ro) { + /* re-open as RO */ + bdrv_delete(bs->backing_hd); + bs->backing_hd = NULL; + bs_ro = bdrv_new(""); + ret = bdrv_open(bs_ro, filename, open_flags & ~BDRV_O_RDWR, + backing_drv); + if (ret < 0) { + bdrv_delete(bs_ro); + /* drive not functional anymore */ + bs->drv = NULL; + return ret; + } + bs->backing_hd = bs_ro; + bs->backing_hd->keep_read_only = 0; + } + + return ret; +} + +void bdrv_commit_all(void) +{ + BlockDriverState *bs; + + QTAILQ_FOREACH(bs, &bdrv_states, list) { + bdrv_commit(bs); + } +} + +/* + * Return values: + * 0 - success + * -EINVAL - backing format specified, but no file + * -ENOSPC - can't update the backing file because no space is left in the + * image file header + * -ENOTSUP - format driver doesn't support changing the backing file + */ +int bdrv_change_backing_file(BlockDriverState *bs, + const char *backing_file, const char *backing_fmt) +{ + BlockDriver *drv = bs->drv; + + if (drv->bdrv_change_backing_file != NULL) { + return drv->bdrv_change_backing_file(bs, backing_file, backing_fmt); + } else { + return -ENOTSUP; + } +} + +static int bdrv_check_byte_request(BlockDriverState *bs, int64_t offset, + size_t size) +{ + int64_t len; + + if (!bdrv_is_inserted(bs)) + return -ENOMEDIUM; + + if (bs->growable) + return 0; + + len = bdrv_getlength(bs); + + if (offset < 0) + return -EIO; + + if ((offset > len) || (len - offset < size)) + return -EIO; + + return 0; +} + +static int bdrv_check_request(BlockDriverState *bs, int64_t sector_num, + int nb_sectors) +{ + return bdrv_check_byte_request(bs, sector_num * BDRV_SECTOR_SIZE, + nb_sectors * BDRV_SECTOR_SIZE); +} + +/* return < 0 if error. See bdrv_write() for the return codes */ +int bdrv_read(BlockDriverState *bs, int64_t sector_num, + uint8_t *buf, int nb_sectors) +{ + BlockDriver *drv = bs->drv; + + if (!drv) + return -ENOMEDIUM; + if (bdrv_check_request(bs, sector_num, nb_sectors)) + return -EIO; + + return drv->bdrv_read(bs, sector_num, buf, nb_sectors); +} + +static void set_dirty_bitmap(BlockDriverState *bs, int64_t sector_num, + int nb_sectors, int dirty) +{ + int64_t start, end; + unsigned long val, idx, bit; + + start = sector_num / BDRV_SECTORS_PER_DIRTY_CHUNK; + end = (sector_num + nb_sectors - 1) / BDRV_SECTORS_PER_DIRTY_CHUNK; + + for (; start <= end; start++) { + idx = start / (sizeof(unsigned long) * 8); + bit = start % (sizeof(unsigned long) * 8); + val = bs->dirty_bitmap[idx]; + if (dirty) { + if (!(val & (1UL << bit))) { + bs->dirty_count++; + val |= 1UL << bit; + } + } else { + if (val & (1UL << bit)) { + bs->dirty_count--; + val &= ~(1UL << bit); + } + } + bs->dirty_bitmap[idx] = val; + } +} + +/* Return < 0 if error. Important errors are: + -EIO generic I/O error (may happen for all errors) + -ENOMEDIUM No media inserted. + -EINVAL Invalid sector number or nb_sectors + -EACCES Trying to write a read-only device +*/ +int bdrv_write(BlockDriverState *bs, int64_t sector_num, + const uint8_t *buf, int nb_sectors) +{ + BlockDriver *drv = bs->drv; + if (!bs->drv) + return -ENOMEDIUM; + if (bs->read_only) + return -EACCES; + if (bdrv_check_request(bs, sector_num, nb_sectors)) + return -EIO; + + if (bs->dirty_bitmap) { + set_dirty_bitmap(bs, sector_num, nb_sectors, 1); + } + + if (bs->wr_highest_sector < sector_num + nb_sectors - 1) { + bs->wr_highest_sector = sector_num + nb_sectors - 1; + } + + return drv->bdrv_write(bs, sector_num, buf, nb_sectors); +} + +int bdrv_pread(BlockDriverState *bs, int64_t offset, + void *buf, int count1) +{ + uint8_t tmp_buf[BDRV_SECTOR_SIZE]; + int len, nb_sectors, count; + int64_t sector_num; + int ret; + + count = count1; + /* first read to align to sector start */ + len = (BDRV_SECTOR_SIZE - offset) & (BDRV_SECTOR_SIZE - 1); + if (len > count) + len = count; + sector_num = offset >> BDRV_SECTOR_BITS; + if (len > 0) { + if ((ret = bdrv_read(bs, sector_num, tmp_buf, 1)) < 0) + return ret; + memcpy(buf, tmp_buf + (offset & (BDRV_SECTOR_SIZE - 1)), len); + count -= len; + if (count == 0) + return count1; + sector_num++; + buf += len; + } + + /* read the sectors "in place" */ + nb_sectors = count >> BDRV_SECTOR_BITS; + if (nb_sectors > 0) { + if ((ret = bdrv_read(bs, sector_num, buf, nb_sectors)) < 0) + return ret; + sector_num += nb_sectors; + len = nb_sectors << BDRV_SECTOR_BITS; + buf += len; + count -= len; + } + + /* add data from the last sector */ + if (count > 0) { + if ((ret = bdrv_read(bs, sector_num, tmp_buf, 1)) < 0) + return ret; + memcpy(buf, tmp_buf, count); + } + return count1; +} + +int bdrv_pwrite(BlockDriverState *bs, int64_t offset, + const void *buf, int count1) +{ + uint8_t tmp_buf[BDRV_SECTOR_SIZE]; + int len, nb_sectors, count; + int64_t sector_num; + int ret; + + count = count1; + /* first write to align to sector start */ + len = (BDRV_SECTOR_SIZE - offset) & (BDRV_SECTOR_SIZE - 1); + if (len > count) + len = count; + sector_num = offset >> BDRV_SECTOR_BITS; + if (len > 0) { + if ((ret = bdrv_read(bs, sector_num, tmp_buf, 1)) < 0) + return ret; + memcpy(tmp_buf + (offset & (BDRV_SECTOR_SIZE - 1)), buf, len); + if ((ret = bdrv_write(bs, sector_num, tmp_buf, 1)) < 0) + return ret; + count -= len; + if (count == 0) + return count1; + sector_num++; + buf += len; + } + + /* write the sectors "in place" */ + nb_sectors = count >> BDRV_SECTOR_BITS; + if (nb_sectors > 0) { + if ((ret = bdrv_write(bs, sector_num, buf, nb_sectors)) < 0) + return ret; + sector_num += nb_sectors; + len = nb_sectors << BDRV_SECTOR_BITS; + buf += len; + count -= len; + } + + /* add data from the last sector */ + if (count > 0) { + if ((ret = bdrv_read(bs, sector_num, tmp_buf, 1)) < 0) + return ret; + memcpy(tmp_buf, buf, count); + if ((ret = bdrv_write(bs, sector_num, tmp_buf, 1)) < 0) + return ret; + } + return count1; +} + +/* + * Writes to the file and ensures that no writes are reordered across this + * request (acts as a barrier) + * + * Returns 0 on success, -errno in error cases. + */ +int bdrv_pwrite_sync(BlockDriverState *bs, int64_t offset, + const void *buf, int count) +{ + int ret; + + ret = bdrv_pwrite(bs, offset, buf, count); + if (ret < 0) { + return ret; + } + + /* No flush needed for cache=writethrough, it uses O_DSYNC */ + if ((bs->open_flags & BDRV_O_CACHE_MASK) != 0) { + bdrv_flush(bs); + } + + return 0; +} + +/* + * Writes to the file and ensures that no writes are reordered across this + * request (acts as a barrier) + * + * Returns 0 on success, -errno in error cases. + */ +int bdrv_write_sync(BlockDriverState *bs, int64_t sector_num, + const uint8_t *buf, int nb_sectors) +{ + return bdrv_pwrite_sync(bs, BDRV_SECTOR_SIZE * sector_num, + buf, BDRV_SECTOR_SIZE * nb_sectors); +} + +/** + * Truncate file to 'offset' bytes (needed only for file protocols) + */ +int bdrv_truncate(BlockDriverState *bs, int64_t offset) +{ + BlockDriver *drv = bs->drv; + int ret; + if (!drv) + return -ENOMEDIUM; + if (!drv->bdrv_truncate) + return -ENOTSUP; + if (bs->read_only) + return -EACCES; + if (bdrv_in_use(bs)) + return -EBUSY; + ret = drv->bdrv_truncate(bs, offset); + if (ret == 0) { + ret = refresh_total_sectors(bs, offset >> BDRV_SECTOR_BITS); + if (bs->change_cb) { + bs->change_cb(bs->change_opaque, CHANGE_SIZE); + } + } + return ret; +} + +/** + * Length of a file in bytes. Return < 0 if error or unknown. + */ +int64_t bdrv_getlength(BlockDriverState *bs) +{ + BlockDriver *drv = bs->drv; + if (!drv) + return -ENOMEDIUM; + + /* Fixed size devices use the total_sectors value for speed instead of + issuing a length query (like lseek) on each call. Also, legacy block + drivers don't provide a bdrv_getlength function and must use + total_sectors. */ + if (!bs->growable || !drv->bdrv_getlength) { + return bs->total_sectors * BDRV_SECTOR_SIZE; + } + return drv->bdrv_getlength(bs); +} + +/* return 0 as number of sectors if no device present or error */ +void bdrv_get_geometry(BlockDriverState *bs, uint64_t *nb_sectors_ptr) +{ + int64_t length; + length = bdrv_getlength(bs); + if (length < 0) + length = 0; + else + length = length >> BDRV_SECTOR_BITS; + *nb_sectors_ptr = length; +} + +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 */ +} __attribute__((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(BlockDriverState *bs, + int *pcylinders, int *pheads, int *psectors) +{ + uint8_t buf[BDRV_SECTOR_SIZE]; + int ret, i, heads, sectors, cylinders; + struct partition *p; + uint32_t nr_sects; + uint64_t nb_sectors; + + bdrv_get_geometry(bs, &nb_sectors); + + ret = bdrv_read(bs, 0, buf, 1); + if (ret < 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; +#if 0 + printf("guessed geometry: LCHS=%d %d %d\n", + cylinders, heads, sectors); +#endif + return 0; + } + } + return -1; +} + +void bdrv_guess_geometry(BlockDriverState *bs, int *pcyls, int *pheads, int *psecs) +{ + int translation, lba_detected = 0; + int cylinders, heads, secs; + uint64_t nb_sectors; + + /* if a geometry hint is available, use it */ + bdrv_get_geometry(bs, &nb_sectors); + bdrv_get_geometry_hint(bs, &cylinders, &heads, &secs); + translation = bdrv_get_translation_hint(bs); + if (cylinders != 0) { + *pcyls = cylinders; + *pheads = heads; + *psecs = secs; + } else { + if (guess_disk_lchs(bs, &cylinders, &heads, &secs) == 0) { + if (heads > 16) { + /* if heads > 16, it means that a BIOS LBA + translation was active, so the default + hardware geometry is OK */ + lba_detected = 1; + goto default_geometry; + } else { + *pcyls = cylinders; + *pheads = heads; + *psecs = secs; + /* disable any translation to be in sync with + the logical geometry */ + if (translation == BIOS_ATA_TRANSLATION_AUTO) { + bdrv_set_translation_hint(bs, + BIOS_ATA_TRANSLATION_NONE); + } + } + } else { + default_geometry: + /* if no geometry, use a standard physical disk geometry */ + cylinders = nb_sectors / (16 * 63); + + if (cylinders > 16383) + cylinders = 16383; + else if (cylinders < 2) + cylinders = 2; + *pcyls = cylinders; + *pheads = 16; + *psecs = 63; + if ((lba_detected == 1) && (translation == BIOS_ATA_TRANSLATION_AUTO)) { + if ((*pcyls * *pheads) <= 131072) { + bdrv_set_translation_hint(bs, + BIOS_ATA_TRANSLATION_LARGE); + } else { + bdrv_set_translation_hint(bs, + BIOS_ATA_TRANSLATION_LBA); + } + } + } + bdrv_set_geometry_hint(bs, *pcyls, *pheads, *psecs); + } +} + +void bdrv_set_geometry_hint(BlockDriverState *bs, + int cyls, int heads, int secs) +{ + bs->cyls = cyls; + bs->heads = heads; + bs->secs = secs; +} + +void bdrv_set_type_hint(BlockDriverState *bs, int type) +{ + bs->type = type; + bs->removable = ((type == BDRV_TYPE_CDROM || + type == BDRV_TYPE_FLOPPY)); +} + +void bdrv_set_translation_hint(BlockDriverState *bs, int translation) +{ + bs->translation = translation; +} + +void bdrv_get_geometry_hint(BlockDriverState *bs, + int *pcyls, int *pheads, int *psecs) +{ + *pcyls = bs->cyls; + *pheads = bs->heads; + *psecs = bs->secs; +} + +int bdrv_get_type_hint(BlockDriverState *bs) +{ + return bs->type; +} + +int bdrv_get_translation_hint(BlockDriverState *bs) +{ + return bs->translation; +} + +void bdrv_set_on_error(BlockDriverState *bs, BlockErrorAction on_read_error, + BlockErrorAction on_write_error) +{ + bs->on_read_error = on_read_error; + bs->on_write_error = on_write_error; +} + +BlockErrorAction bdrv_get_on_error(BlockDriverState *bs, int is_read) +{ + return is_read ? bs->on_read_error : bs->on_write_error; +} + +void bdrv_set_removable(BlockDriverState *bs, int removable) +{ + bs->removable = removable; + if (removable && bs == bs_snapshots) { + bs_snapshots = NULL; + } +} + +int bdrv_is_removable(BlockDriverState *bs) +{ + return bs->removable; +} + +int bdrv_is_read_only(BlockDriverState *bs) +{ + return bs->read_only; +} + +int bdrv_is_sg(BlockDriverState *bs) +{ + return bs->sg; +} + +int bdrv_enable_write_cache(BlockDriverState *bs) +{ + return bs->enable_write_cache; +} + +/* XXX: no longer used */ +void bdrv_set_change_cb(BlockDriverState *bs, + void (*change_cb)(void *opaque, int reason), + void *opaque) +{ + bs->change_cb = change_cb; + bs->change_opaque = opaque; +} + +int bdrv_is_encrypted(BlockDriverState *bs) +{ + if (bs->backing_hd && bs->backing_hd->encrypted) + return 1; + return bs->encrypted; +} + +int bdrv_key_required(BlockDriverState *bs) +{ + BlockDriverState *backing_hd = bs->backing_hd; + + if (backing_hd && backing_hd->encrypted && !backing_hd->valid_key) + return 1; + return (bs->encrypted && !bs->valid_key); +} + +int bdrv_set_key(BlockDriverState *bs, const char *key) +{ + int ret; + if (bs->backing_hd && bs->backing_hd->encrypted) { + ret = bdrv_set_key(bs->backing_hd, key); + if (ret < 0) + return ret; + if (!bs->encrypted) + return 0; + } + if (!bs->encrypted) { + return -EINVAL; + } else if (!bs->drv || !bs->drv->bdrv_set_key) { + return -ENOMEDIUM; + } + ret = bs->drv->bdrv_set_key(bs, key); + if (ret < 0) { + bs->valid_key = 0; + } else if (!bs->valid_key) { + bs->valid_key = 1; + /* call the change callback now, we skipped it on open */ + bs->media_changed = 1; + if (bs->change_cb) + bs->change_cb(bs->change_opaque, CHANGE_MEDIA); + } + return ret; +} + +void bdrv_get_format(BlockDriverState *bs, char *buf, int buf_size) +{ + if (!bs->drv) { + buf[0] = '\0'; + } else { + pstrcpy(buf, buf_size, bs->drv->format_name); + } +} + +void bdrv_iterate_format(void (*it)(void *opaque, const char *name), + void *opaque) +{ + BlockDriver *drv; + + QLIST_FOREACH(drv, &bdrv_drivers, list) { + it(opaque, drv->format_name); + } +} + +BlockDriverState *bdrv_find(const char *name) +{ + BlockDriverState *bs; + + QTAILQ_FOREACH(bs, &bdrv_states, list) { + if (!strcmp(name, bs->device_name)) { + return bs; + } + } + return NULL; +} + +BlockDriverState *bdrv_next(BlockDriverState *bs) +{ + if (!bs) { + return QTAILQ_FIRST(&bdrv_states); + } + return QTAILQ_NEXT(bs, list); +} + +void bdrv_iterate(void (*it)(void *opaque, BlockDriverState *bs), void *opaque) +{ + BlockDriverState *bs; + + QTAILQ_FOREACH(bs, &bdrv_states, list) { + it(opaque, bs); + } +} + +const char *bdrv_get_device_name(BlockDriverState *bs) +{ + return bs->device_name; +} + +int bdrv_flush(BlockDriverState *bs) +{ + if (bs->open_flags & BDRV_O_NO_FLUSH) { + return 0; + } + + if (bs->drv && bs->drv->bdrv_flush) { + return bs->drv->bdrv_flush(bs); + } + + /* + * Some block drivers always operate in either writethrough or unsafe mode + * and don't support bdrv_flush therefore. Usually qemu doesn't know how + * the server works (because the behaviour is hardcoded or depends on + * server-side configuration), so we can't ensure that everything is safe + * on disk. Returning an error doesn't work because that would break guests + * even if the server operates in writethrough mode. + * + * Let's hope the user knows what he's doing. + */ + return 0; +} + +void bdrv_flush_all(void) +{ + BlockDriverState *bs; + + QTAILQ_FOREACH(bs, &bdrv_states, list) { + if (bs->drv && !bdrv_is_read_only(bs) && + (!bdrv_is_removable(bs) || bdrv_is_inserted(bs))) { + bdrv_flush(bs); + } + } +} + +int bdrv_has_zero_init(BlockDriverState *bs) +{ + assert(bs->drv); + + if (bs->drv->bdrv_has_zero_init) { + return bs->drv->bdrv_has_zero_init(bs); + } + + return 1; +} + +int bdrv_discard(BlockDriverState *bs, int64_t sector_num, int nb_sectors) +{ + if (!bs->drv) { + return -ENOMEDIUM; + } + if (!bs->drv->bdrv_discard) { + return 0; + } + return bs->drv->bdrv_discard(bs, sector_num, nb_sectors); +} + +/* + * Returns true iff the specified sector is present in the disk image. Drivers + * not implementing the functionality are assumed to not support backing files, + * hence all their sectors are reported as allocated. + * + * 'pnum' is set to the number of sectors (including and immediately following + * the specified sector) that are known to be in the same + * allocated/unallocated state. + * + * 'nb_sectors' is the max value 'pnum' should be set to. + */ +int bdrv_is_allocated(BlockDriverState *bs, int64_t sector_num, int nb_sectors, + int *pnum) +{ + int64_t n; + if (!bs->drv->bdrv_is_allocated) { + if (sector_num >= bs->total_sectors) { + *pnum = 0; + return 0; + } + n = bs->total_sectors - sector_num; + *pnum = (n < nb_sectors) ? (n) : (nb_sectors); + return 1; + } + return bs->drv->bdrv_is_allocated(bs, sector_num, nb_sectors, pnum); +} + +void bdrv_mon_event(const BlockDriverState *bdrv, + BlockMonEventAction action, int is_read) +{ + QObject *data; + const char *action_str; + + switch (action) { + case BDRV_ACTION_REPORT: + action_str = "report"; + break; + case BDRV_ACTION_IGNORE: + action_str = "ignore"; + break; + case BDRV_ACTION_STOP: + action_str = "stop"; + break; + default: + abort(); + } + + data = qobject_from_jsonf("{ 'device': %s, 'action': %s, 'operation': %s }", + bdrv->device_name, + action_str, + is_read ? "read" : "write"); + monitor_protocol_event(QEVENT_BLOCK_IO_ERROR, data); + + qobject_decref(data); +} + +static void bdrv_print_dict(QObject *obj, void *opaque) +{ + QDict *bs_dict; + Monitor *mon = opaque; + + bs_dict = qobject_to_qdict(obj); + + monitor_printf(mon, "%s: type=%s removable=%d", + qdict_get_str(bs_dict, "device"), + qdict_get_str(bs_dict, "type"), + qdict_get_bool(bs_dict, "removable")); + + if (qdict_get_bool(bs_dict, "removable")) { + monitor_printf(mon, " locked=%d", qdict_get_bool(bs_dict, "locked")); + } + + if (qdict_haskey(bs_dict, "inserted")) { + QDict *qdict = qobject_to_qdict(qdict_get(bs_dict, "inserted")); + + monitor_printf(mon, " file="); + monitor_print_filename(mon, qdict_get_str(qdict, "file")); + if (qdict_haskey(qdict, "backing_file")) { + monitor_printf(mon, " backing_file="); + monitor_print_filename(mon, qdict_get_str(qdict, "backing_file")); + } + monitor_printf(mon, " ro=%d drv=%s encrypted=%d", + qdict_get_bool(qdict, "ro"), + qdict_get_str(qdict, "drv"), + qdict_get_bool(qdict, "encrypted")); + } else { + monitor_printf(mon, " [not inserted]"); + } + + monitor_printf(mon, "\n"); +} + +void bdrv_info_print(Monitor *mon, const QObject *data) +{ + qlist_iter(qobject_to_qlist(data), bdrv_print_dict, mon); +} + +void bdrv_info(Monitor *mon, QObject **ret_data) +{ + QList *bs_list; + BlockDriverState *bs; + + bs_list = qlist_new(); + + QTAILQ_FOREACH(bs, &bdrv_states, list) { + QObject *bs_obj; + const char *type = "unknown"; + + switch(bs->type) { + case BDRV_TYPE_HD: + type = "hd"; + break; + case BDRV_TYPE_CDROM: + type = "cdrom"; + break; + case BDRV_TYPE_FLOPPY: + type = "floppy"; + break; + } + + bs_obj = qobject_from_jsonf("{ 'device': %s, 'type': %s, " + "'removable': %i, 'locked': %i }", + bs->device_name, type, bs->removable, + bs->locked); + + if (bs->drv) { + QObject *obj; + QDict *bs_dict = qobject_to_qdict(bs_obj); + + obj = qobject_from_jsonf("{ 'file': %s, 'ro': %i, 'drv': %s, " + "'encrypted': %i }", + bs->filename, bs->read_only, + bs->drv->format_name, + bdrv_is_encrypted(bs)); + if (bs->backing_file[0] != '\0') { + QDict *qdict = qobject_to_qdict(obj); + qdict_put(qdict, "backing_file", + qstring_from_str(bs->backing_file)); + } + + qdict_put_obj(bs_dict, "inserted", obj); + } + qlist_append_obj(bs_list, bs_obj); + } + + *ret_data = QOBJECT(bs_list); +} + +static void bdrv_stats_iter(QObject *data, void *opaque) +{ + QDict *qdict; + Monitor *mon = opaque; + + qdict = qobject_to_qdict(data); + monitor_printf(mon, "%s:", qdict_get_str(qdict, "device")); + + qdict = qobject_to_qdict(qdict_get(qdict, "stats")); + monitor_printf(mon, " rd_bytes=%" PRId64 + " wr_bytes=%" PRId64 + " rd_operations=%" PRId64 + " wr_operations=%" PRId64 + "\n", + qdict_get_int(qdict, "rd_bytes"), + qdict_get_int(qdict, "wr_bytes"), + qdict_get_int(qdict, "rd_operations"), + qdict_get_int(qdict, "wr_operations")); +} + +void bdrv_stats_print(Monitor *mon, const QObject *data) +{ + qlist_iter(qobject_to_qlist(data), bdrv_stats_iter, mon); +} + +static QObject* bdrv_info_stats_bs(BlockDriverState *bs) +{ + QObject *res; + QDict *dict; + + res = qobject_from_jsonf("{ 'stats': {" + "'rd_bytes': %" PRId64 "," + "'wr_bytes': %" PRId64 "," + "'rd_operations': %" PRId64 "," + "'wr_operations': %" PRId64 "," + "'wr_highest_offset': %" PRId64 + "} }", + bs->rd_bytes, bs->wr_bytes, + bs->rd_ops, bs->wr_ops, + bs->wr_highest_sector * + (uint64_t)BDRV_SECTOR_SIZE); + dict = qobject_to_qdict(res); + + if (*bs->device_name) { + qdict_put(dict, "device", qstring_from_str(bs->device_name)); + } + + if (bs->file) { + QObject *parent = bdrv_info_stats_bs(bs->file); + qdict_put_obj(dict, "parent", parent); + } + + return res; +} + +void bdrv_info_stats(Monitor *mon, QObject **ret_data) +{ + QObject *obj; + QList *devices; + BlockDriverState *bs; + + devices = qlist_new(); + + QTAILQ_FOREACH(bs, &bdrv_states, list) { + obj = bdrv_info_stats_bs(bs); + qlist_append_obj(devices, obj); + } + + *ret_data = QOBJECT(devices); +} + +const char *bdrv_get_encrypted_filename(BlockDriverState *bs) +{ + if (bs->backing_hd && bs->backing_hd->encrypted) + return bs->backing_file; + else if (bs->encrypted) + return bs->filename; + else + return NULL; +} + +void bdrv_get_backing_filename(BlockDriverState *bs, + char *filename, int filename_size) +{ + if (!bs->backing_file) { + pstrcpy(filename, filename_size, ""); + } else { + pstrcpy(filename, filename_size, bs->backing_file); + } +} + +int bdrv_write_compressed(BlockDriverState *bs, int64_t sector_num, + const uint8_t *buf, int nb_sectors) +{ + BlockDriver *drv = bs->drv; + if (!drv) + return -ENOMEDIUM; + if (!drv->bdrv_write_compressed) + return -ENOTSUP; + if (bdrv_check_request(bs, sector_num, nb_sectors)) + return -EIO; + + if (bs->dirty_bitmap) { + set_dirty_bitmap(bs, sector_num, nb_sectors, 1); + } + + return drv->bdrv_write_compressed(bs, sector_num, buf, nb_sectors); +} + +int bdrv_get_info(BlockDriverState *bs, BlockDriverInfo *bdi) +{ + BlockDriver *drv = bs->drv; + if (!drv) + return -ENOMEDIUM; + if (!drv->bdrv_get_info) + return -ENOTSUP; + memset(bdi, 0, sizeof(*bdi)); + return drv->bdrv_get_info(bs, bdi); +} + +int bdrv_save_vmstate(BlockDriverState *bs, const uint8_t *buf, + int64_t pos, int size) +{ + BlockDriver *drv = bs->drv; + if (!drv) + return -ENOMEDIUM; + if (drv->bdrv_save_vmstate) + return drv->bdrv_save_vmstate(bs, buf, pos, size); + if (bs->file) + return bdrv_save_vmstate(bs->file, buf, pos, size); + return -ENOTSUP; +} + +int bdrv_load_vmstate(BlockDriverState *bs, uint8_t *buf, + int64_t pos, int size) +{ + BlockDriver *drv = bs->drv; + if (!drv) + return -ENOMEDIUM; + if (drv->bdrv_load_vmstate) + return drv->bdrv_load_vmstate(bs, buf, pos, size); + if (bs->file) + return bdrv_load_vmstate(bs->file, buf, pos, size); + return -ENOTSUP; +} + +void bdrv_debug_event(BlockDriverState *bs, BlkDebugEvent event) +{ + BlockDriver *drv = bs->drv; + + if (!drv || !drv->bdrv_debug_event) { + return; + } + + return drv->bdrv_debug_event(bs, event); + +} + +/**************************************************************/ +/* handling of snapshots */ + +int bdrv_can_snapshot(BlockDriverState *bs) +{ + BlockDriver *drv = bs->drv; + if (!drv || bdrv_is_removable(bs) || bdrv_is_read_only(bs)) { + return 0; + } + + if (!drv->bdrv_snapshot_create) { + if (bs->file != NULL) { + return bdrv_can_snapshot(bs->file); + } + return 0; + } + + return 1; +} + +int bdrv_is_snapshot(BlockDriverState *bs) +{ + return !!(bs->open_flags & BDRV_O_SNAPSHOT); +} + +BlockDriverState *bdrv_snapshots(void) +{ + BlockDriverState *bs; + + if (bs_snapshots) { + return bs_snapshots; + } + + bs = NULL; + while ((bs = bdrv_next(bs))) { + if (bdrv_can_snapshot(bs)) { + bs_snapshots = bs; + return bs; + } + } + return NULL; +} + +int bdrv_snapshot_create(BlockDriverState *bs, + QEMUSnapshotInfo *sn_info) +{ + BlockDriver *drv = bs->drv; + if (!drv) + return -ENOMEDIUM; + if (drv->bdrv_snapshot_create) + return drv->bdrv_snapshot_create(bs, sn_info); + if (bs->file) + return bdrv_snapshot_create(bs->file, sn_info); + return -ENOTSUP; +} + +int bdrv_snapshot_goto(BlockDriverState *bs, + const char *snapshot_id) +{ + BlockDriver *drv = bs->drv; + int ret, open_ret; + + if (!drv) + return -ENOMEDIUM; + if (drv->bdrv_snapshot_goto) + return drv->bdrv_snapshot_goto(bs, snapshot_id); + + if (bs->file) { + drv->bdrv_close(bs); + ret = bdrv_snapshot_goto(bs->file, snapshot_id); + open_ret = drv->bdrv_open(bs, bs->open_flags); + if (open_ret < 0) { + bdrv_delete(bs->file); + bs->drv = NULL; + return open_ret; + } + return ret; + } + + return -ENOTSUP; +} + +int bdrv_snapshot_delete(BlockDriverState *bs, const char *snapshot_id) +{ + BlockDriver *drv = bs->drv; + if (!drv) + return -ENOMEDIUM; + if (drv->bdrv_snapshot_delete) + return drv->bdrv_snapshot_delete(bs, snapshot_id); + if (bs->file) + return bdrv_snapshot_delete(bs->file, snapshot_id); + return -ENOTSUP; +} + +int bdrv_snapshot_list(BlockDriverState *bs, + QEMUSnapshotInfo **psn_info) +{ + BlockDriver *drv = bs->drv; + if (!drv) + return -ENOMEDIUM; + if (drv->bdrv_snapshot_list) + return drv->bdrv_snapshot_list(bs, psn_info); + if (bs->file) + return bdrv_snapshot_list(bs->file, psn_info); + return -ENOTSUP; +} + +int bdrv_snapshot_load_tmp(BlockDriverState *bs, + const char *snapshot_name) +{ + BlockDriver *drv = bs->drv; + if (!drv) { + return -ENOMEDIUM; + } + if (!bs->read_only) { + return -EINVAL; + } + if (drv->bdrv_snapshot_load_tmp) { + return drv->bdrv_snapshot_load_tmp(bs, snapshot_name); + } + return -ENOTSUP; +} + +#define NB_SUFFIXES 4 + +char *get_human_readable_size(char *buf, int buf_size, int64_t size) +{ + static const char suffixes[NB_SUFFIXES] = "KMGT"; + int64_t base; + int i; + + if (size <= 999) { + snprintf(buf, buf_size, "%" PRId64, size); + } else { + base = 1024; + for(i = 0; i < NB_SUFFIXES; i++) { + if (size < (10 * base)) { + snprintf(buf, buf_size, "%0.1f%c", + (double)size / base, + suffixes[i]); + break; + } else if (size < (1000 * base) || i == (NB_SUFFIXES - 1)) { + snprintf(buf, buf_size, "%" PRId64 "%c", + ((size + (base >> 1)) / base), + suffixes[i]); + break; + } + base = base * 1024; + } + } + return buf; +} + +char *bdrv_snapshot_dump(char *buf, int buf_size, QEMUSnapshotInfo *sn) +{ + char buf1[128], date_buf[128], clock_buf[128]; +#ifdef _WIN32 + struct tm *ptm; +#else + struct tm tm; +#endif + time_t ti; + int64_t secs; + + if (!sn) { + snprintf(buf, buf_size, + "%-10s%-20s%7s%20s%15s", + "ID", "TAG", "VM SIZE", "DATE", "VM CLOCK"); + } else { + ti = sn->date_sec; +#ifdef _WIN32 + ptm = localtime(&ti); + strftime(date_buf, sizeof(date_buf), + "%Y-%m-%d %H:%M:%S", ptm); +#else + localtime_r(&ti, &tm); + strftime(date_buf, sizeof(date_buf), + "%Y-%m-%d %H:%M:%S", &tm); +#endif + secs = sn->vm_clock_nsec / 1000000000; + snprintf(clock_buf, sizeof(clock_buf), + "%02d:%02d:%02d.%03d", + (int)(secs / 3600), + (int)((secs / 60) % 60), + (int)(secs % 60), + (int)((sn->vm_clock_nsec / 1000000) % 1000)); + snprintf(buf, buf_size, + "%-10s%-20s%7s%20s%15s", + sn->id_str, sn->name, + get_human_readable_size(buf1, sizeof(buf1), sn->vm_state_size), + date_buf, + clock_buf); + } + return buf; +} + + +/**************************************************************/ +/* async I/Os */ + +BlockDriverAIOCB *bdrv_aio_readv(BlockDriverState *bs, int64_t sector_num, + QEMUIOVector *qiov, int nb_sectors, + BlockDriverCompletionFunc *cb, void *opaque) +{ + BlockDriver *drv = bs->drv; + BlockDriverAIOCB *ret; + + trace_bdrv_aio_readv(bs, sector_num, nb_sectors, opaque); + + if (!drv) + return NULL; + if (bdrv_check_request(bs, sector_num, nb_sectors)) + return NULL; + + ret = drv->bdrv_aio_readv(bs, sector_num, qiov, nb_sectors, + cb, opaque); + + if (ret) { + /* Update stats even though technically transfer has not happened. */ + bs->rd_bytes += (unsigned) nb_sectors * BDRV_SECTOR_SIZE; + bs->rd_ops ++; + } + + return ret; +} + +typedef struct BlockCompleteData { + BlockDriverCompletionFunc *cb; + void *opaque; + BlockDriverState *bs; + int64_t sector_num; + int nb_sectors; +} BlockCompleteData; + +static void block_complete_cb(void *opaque, int ret) +{ + BlockCompleteData *b = opaque; + + if (b->bs->dirty_bitmap) { + set_dirty_bitmap(b->bs, b->sector_num, b->nb_sectors, 1); + } + b->cb(b->opaque, ret); + qemu_free(b); +} + +static BlockCompleteData *blk_dirty_cb_alloc(BlockDriverState *bs, + int64_t sector_num, + int nb_sectors, + BlockDriverCompletionFunc *cb, + void *opaque) +{ + BlockCompleteData *blkdata = qemu_mallocz(sizeof(BlockCompleteData)); + + blkdata->bs = bs; + blkdata->cb = cb; + blkdata->opaque = opaque; + blkdata->sector_num = sector_num; + blkdata->nb_sectors = nb_sectors; + + return blkdata; +} + +BlockDriverAIOCB *bdrv_aio_writev(BlockDriverState *bs, int64_t sector_num, + QEMUIOVector *qiov, int nb_sectors, + BlockDriverCompletionFunc *cb, void *opaque) +{ + BlockDriver *drv = bs->drv; + BlockDriverAIOCB *ret; + BlockCompleteData *blk_cb_data; + + trace_bdrv_aio_writev(bs, sector_num, nb_sectors, opaque); + + if (!drv) + return NULL; + if (bs->read_only) + return NULL; + if (bdrv_check_request(bs, sector_num, nb_sectors)) + return NULL; + + if (bs->dirty_bitmap) { + blk_cb_data = blk_dirty_cb_alloc(bs, sector_num, nb_sectors, cb, + opaque); + cb = &block_complete_cb; + opaque = blk_cb_data; + } + + ret = drv->bdrv_aio_writev(bs, sector_num, qiov, nb_sectors, + cb, opaque); + + if (ret) { + /* Update stats even though technically transfer has not happened. */ + bs->wr_bytes += (unsigned) nb_sectors * BDRV_SECTOR_SIZE; + bs->wr_ops ++; + if (bs->wr_highest_sector < sector_num + nb_sectors - 1) { + bs->wr_highest_sector = sector_num + nb_sectors - 1; + } + } + + return ret; +} + + +typedef struct MultiwriteCB { + int error; + int num_requests; + int num_callbacks; + struct { + BlockDriverCompletionFunc *cb; + void *opaque; + QEMUIOVector *free_qiov; + void *free_buf; + } callbacks[]; +} MultiwriteCB; + +static void multiwrite_user_cb(MultiwriteCB *mcb) +{ + int i; + + for (i = 0; i < mcb->num_callbacks; i++) { + mcb->callbacks[i].cb(mcb->callbacks[i].opaque, mcb->error); + if (mcb->callbacks[i].free_qiov) { + qemu_iovec_destroy(mcb->callbacks[i].free_qiov); + } + qemu_free(mcb->callbacks[i].free_qiov); + qemu_vfree(mcb->callbacks[i].free_buf); + } +} + +static void multiwrite_cb(void *opaque, int ret) +{ + MultiwriteCB *mcb = opaque; + + trace_multiwrite_cb(mcb, ret); + + if (ret < 0 && !mcb->error) { + mcb->error = ret; + } + + mcb->num_requests--; + if (mcb->num_requests == 0) { + multiwrite_user_cb(mcb); + qemu_free(mcb); + } +} + +static int multiwrite_req_compare(const void *a, const void *b) +{ + const BlockRequest *req1 = a, *req2 = b; + + /* + * Note that we can't simply subtract req2->sector from req1->sector + * here as that could overflow the return value. + */ + if (req1->sector > req2->sector) { + return 1; + } else if (req1->sector < req2->sector) { + return -1; + } else { + return 0; + } +} + +/* + * Takes a bunch of requests and tries to merge them. Returns the number of + * requests that remain after merging. + */ +static int multiwrite_merge(BlockDriverState *bs, BlockRequest *reqs, + int num_reqs, MultiwriteCB *mcb) +{ + int i, outidx; + + // Sort requests by start sector + qsort(reqs, num_reqs, sizeof(*reqs), &multiwrite_req_compare); + + // Check if adjacent requests touch the same clusters. If so, combine them, + // filling up gaps with zero sectors. + outidx = 0; + for (i = 1; i < num_reqs; i++) { + int merge = 0; + int64_t oldreq_last = reqs[outidx].sector + reqs[outidx].nb_sectors; + + // This handles the cases that are valid for all block drivers, namely + // exactly sequential writes and overlapping writes. + if (reqs[i].sector <= oldreq_last) { + merge = 1; + } + + // The block driver may decide that it makes sense to combine requests + // even if there is a gap of some sectors between them. In this case, + // the gap is filled with zeros (therefore only applicable for yet + // unused space in format like qcow2). + if (!merge && bs->drv->bdrv_merge_requests) { + merge = bs->drv->bdrv_merge_requests(bs, &reqs[outidx], &reqs[i]); + } + + if (reqs[outidx].qiov->niov + reqs[i].qiov->niov + 1 > IOV_MAX) { + merge = 0; + } + + if (merge) { + size_t size; + QEMUIOVector *qiov = qemu_mallocz(sizeof(*qiov)); + qemu_iovec_init(qiov, + reqs[outidx].qiov->niov + reqs[i].qiov->niov + 1); + + // Add the first request to the merged one. If the requests are + // overlapping, drop the last sectors of the first request. + size = (reqs[i].sector - reqs[outidx].sector) << 9; + qemu_iovec_concat(qiov, reqs[outidx].qiov, size); + + // We might need to add some zeros between the two requests + if (reqs[i].sector > oldreq_last) { + size_t zero_bytes = (reqs[i].sector - oldreq_last) << 9; + uint8_t *buf = qemu_blockalign(bs, zero_bytes); + memset(buf, 0, zero_bytes); + qemu_iovec_add(qiov, buf, zero_bytes); + mcb->callbacks[i].free_buf = buf; + } + + // Add the second request + qemu_iovec_concat(qiov, reqs[i].qiov, reqs[i].qiov->size); + + reqs[outidx].nb_sectors = qiov->size >> 9; + reqs[outidx].qiov = qiov; + + mcb->callbacks[i].free_qiov = reqs[outidx].qiov; + } else { + outidx++; + reqs[outidx].sector = reqs[i].sector; + reqs[outidx].nb_sectors = reqs[i].nb_sectors; + reqs[outidx].qiov = reqs[i].qiov; + } + } + + return outidx + 1; +} + +/* + * Submit multiple AIO write requests at once. + * + * On success, the function returns 0 and all requests in the reqs array have + * been submitted. In error case this function returns -1, and any of the + * requests may or may not be submitted yet. In particular, this means that the + * callback will be called for some of the requests, for others it won't. The + * caller must check the error field of the BlockRequest to wait for the right + * callbacks (if error != 0, no callback will be called). + * + * The implementation may modify the contents of the reqs array, e.g. to merge + * requests. However, the fields opaque and error are left unmodified as they + * are used to signal failure for a single request to the caller. + */ +int bdrv_aio_multiwrite(BlockDriverState *bs, BlockRequest *reqs, int num_reqs) +{ + BlockDriverAIOCB *acb; + MultiwriteCB *mcb; + int i; + + /* don't submit writes if we don't have a medium */ + if (bs->drv == NULL) { + for (i = 0; i < num_reqs; i++) { + reqs[i].error = -ENOMEDIUM; + } + return -1; + } + + if (num_reqs == 0) { + return 0; + } + + // Create MultiwriteCB structure + mcb = qemu_mallocz(sizeof(*mcb) + num_reqs * sizeof(*mcb->callbacks)); + mcb->num_requests = 0; + mcb->num_callbacks = num_reqs; + + for (i = 0; i < num_reqs; i++) { + mcb->callbacks[i].cb = reqs[i].cb; + mcb->callbacks[i].opaque = reqs[i].opaque; + } + + // Check for mergable requests + num_reqs = multiwrite_merge(bs, reqs, num_reqs, mcb); + + trace_bdrv_aio_multiwrite(mcb, mcb->num_callbacks, num_reqs); + + /* + * Run the aio requests. As soon as one request can't be submitted + * successfully, fail all requests that are not yet submitted (we must + * return failure for all requests anyway) + * + * num_requests cannot be set to the right value immediately: If + * bdrv_aio_writev fails for some request, num_requests would be too high + * and therefore multiwrite_cb() would never recognize the multiwrite + * request as completed. We also cannot use the loop variable i to set it + * when the first request fails because the callback may already have been + * called for previously submitted requests. Thus, num_requests must be + * incremented for each request that is submitted. + * + * The problem that callbacks may be called early also means that we need + * to take care that num_requests doesn't become 0 before all requests are + * submitted - multiwrite_cb() would consider the multiwrite request + * completed. A dummy request that is "completed" by a manual call to + * multiwrite_cb() takes care of this. + */ + mcb->num_requests = 1; + + // Run the aio requests + for (i = 0; i < num_reqs; i++) { + mcb->num_requests++; + acb = bdrv_aio_writev(bs, reqs[i].sector, reqs[i].qiov, + reqs[i].nb_sectors, multiwrite_cb, mcb); + + if (acb == NULL) { + // We can only fail the whole thing if no request has been + // submitted yet. Otherwise we'll wait for the submitted AIOs to + // complete and report the error in the callback. + if (i == 0) { + trace_bdrv_aio_multiwrite_earlyfail(mcb); + goto fail; + } else { + trace_bdrv_aio_multiwrite_latefail(mcb, i); + multiwrite_cb(mcb, -EIO); + break; + } + } + } + + /* Complete the dummy request */ + multiwrite_cb(mcb, 0); + + return 0; + +fail: + for (i = 0; i < mcb->num_callbacks; i++) { + reqs[i].error = -EIO; + } + qemu_free(mcb); + return -1; +} + +BlockDriverAIOCB *bdrv_aio_flush(BlockDriverState *bs, + BlockDriverCompletionFunc *cb, void *opaque) +{ + BlockDriver *drv = bs->drv; + + if (bs->open_flags & BDRV_O_NO_FLUSH) { + return bdrv_aio_noop_em(bs, cb, opaque); + } + + if (!drv) + return NULL; + return drv->bdrv_aio_flush(bs, cb, opaque); +} + +void bdrv_aio_cancel(BlockDriverAIOCB *acb) +{ + acb->pool->cancel(acb); +} + + +/**************************************************************/ +/* async block device emulation */ + +typedef struct BlockDriverAIOCBSync { + BlockDriverAIOCB common; + QEMUBH *bh; + int ret; + /* vector translation state */ + QEMUIOVector *qiov; + uint8_t *bounce; + int is_write; +} BlockDriverAIOCBSync; + +static void bdrv_aio_cancel_em(BlockDriverAIOCB *blockacb) +{ + BlockDriverAIOCBSync *acb = + container_of(blockacb, BlockDriverAIOCBSync, common); + qemu_bh_delete(acb->bh); + acb->bh = NULL; + qemu_aio_release(acb); +} + +static AIOPool bdrv_em_aio_pool = { + .aiocb_size = sizeof(BlockDriverAIOCBSync), + .cancel = bdrv_aio_cancel_em, +}; + +static void bdrv_aio_bh_cb(void *opaque) +{ + BlockDriverAIOCBSync *acb = opaque; + + if (!acb->is_write) + qemu_iovec_from_buffer(acb->qiov, acb->bounce, acb->qiov->size); + qemu_vfree(acb->bounce); + acb->common.cb(acb->common.opaque, acb->ret); + qemu_bh_delete(acb->bh); + acb->bh = NULL; + qemu_aio_release(acb); +} + +static BlockDriverAIOCB *bdrv_aio_rw_vector(BlockDriverState *bs, + int64_t sector_num, + QEMUIOVector *qiov, + int nb_sectors, + BlockDriverCompletionFunc *cb, + void *opaque, + int is_write) + +{ + BlockDriverAIOCBSync *acb; + + acb = qemu_aio_get(&bdrv_em_aio_pool, bs, cb, opaque); + acb->is_write = is_write; + acb->qiov = qiov; + acb->bounce = qemu_blockalign(bs, qiov->size); + + if (!acb->bh) + acb->bh = qemu_bh_new(bdrv_aio_bh_cb, acb); + + if (is_write) { + qemu_iovec_to_buffer(acb->qiov, acb->bounce); + acb->ret = bdrv_write(bs, sector_num, acb->bounce, nb_sectors); + } else { + acb->ret = bdrv_read(bs, sector_num, acb->bounce, nb_sectors); + } + + qemu_bh_schedule(acb->bh); + + return &acb->common; +} + +static BlockDriverAIOCB *bdrv_aio_readv_em(BlockDriverState *bs, + int64_t sector_num, QEMUIOVector *qiov, int nb_sectors, + BlockDriverCompletionFunc *cb, void *opaque) +{ + return bdrv_aio_rw_vector(bs, sector_num, qiov, nb_sectors, cb, opaque, 0); +} + +static BlockDriverAIOCB *bdrv_aio_writev_em(BlockDriverState *bs, + int64_t sector_num, QEMUIOVector *qiov, int nb_sectors, + BlockDriverCompletionFunc *cb, void *opaque) +{ + return bdrv_aio_rw_vector(bs, sector_num, qiov, nb_sectors, cb, opaque, 1); +} + +static BlockDriverAIOCB *bdrv_aio_flush_em(BlockDriverState *bs, + BlockDriverCompletionFunc *cb, void *opaque) +{ + BlockDriverAIOCBSync *acb; + + acb = qemu_aio_get(&bdrv_em_aio_pool, bs, cb, opaque); + acb->is_write = 1; /* don't bounce in the completion hadler */ + acb->qiov = NULL; + acb->bounce = NULL; + acb->ret = 0; + + if (!acb->bh) + acb->bh = qemu_bh_new(bdrv_aio_bh_cb, acb); + + bdrv_flush(bs); + qemu_bh_schedule(acb->bh); + return &acb->common; +} + +static BlockDriverAIOCB *bdrv_aio_noop_em(BlockDriverState *bs, + BlockDriverCompletionFunc *cb, void *opaque) +{ + BlockDriverAIOCBSync *acb; + + acb = qemu_aio_get(&bdrv_em_aio_pool, bs, cb, opaque); + acb->is_write = 1; /* don't bounce in the completion handler */ + acb->qiov = NULL; + acb->bounce = NULL; + acb->ret = 0; + + if (!acb->bh) { + acb->bh = qemu_bh_new(bdrv_aio_bh_cb, acb); + } + + qemu_bh_schedule(acb->bh); + return &acb->common; +} + +/**************************************************************/ +/* sync block device emulation */ + +static void bdrv_rw_em_cb(void *opaque, int ret) +{ + *(int *)opaque = ret; +} + +#define NOT_DONE 0x7fffffff + +static int bdrv_read_em(BlockDriverState *bs, int64_t sector_num, + uint8_t *buf, int nb_sectors) +{ + int async_ret; + BlockDriverAIOCB *acb; + struct iovec iov; + QEMUIOVector qiov; + + async_context_push(); + + async_ret = NOT_DONE; + iov.iov_base = (void *)buf; + iov.iov_len = nb_sectors * BDRV_SECTOR_SIZE; + qemu_iovec_init_external(&qiov, &iov, 1); + acb = bdrv_aio_readv(bs, sector_num, &qiov, nb_sectors, + bdrv_rw_em_cb, &async_ret); + if (acb == NULL) { + async_ret = -1; + goto fail; + } + + while (async_ret == NOT_DONE) { + qemu_aio_wait(); + } + + +fail: + async_context_pop(); + return async_ret; +} + +static int bdrv_write_em(BlockDriverState *bs, int64_t sector_num, + const uint8_t *buf, int nb_sectors) +{ + int async_ret; + BlockDriverAIOCB *acb; + struct iovec iov; + QEMUIOVector qiov; + + async_context_push(); + + async_ret = NOT_DONE; + iov.iov_base = (void *)buf; + iov.iov_len = nb_sectors * BDRV_SECTOR_SIZE; + qemu_iovec_init_external(&qiov, &iov, 1); + acb = bdrv_aio_writev(bs, sector_num, &qiov, nb_sectors, + bdrv_rw_em_cb, &async_ret); + if (acb == NULL) { + async_ret = -1; + goto fail; + } + while (async_ret == NOT_DONE) { + qemu_aio_wait(); + } + +fail: + async_context_pop(); + return async_ret; +} + +void bdrv_init(void) +{ + module_call_init(MODULE_INIT_BLOCK); +} + +void bdrv_init_with_whitelist(void) +{ + use_bdrv_whitelist = 1; + bdrv_init(); +} + +void *qemu_aio_get(AIOPool *pool, BlockDriverState *bs, + BlockDriverCompletionFunc *cb, void *opaque) +{ + BlockDriverAIOCB *acb; + + if (pool->free_aiocb) { + acb = pool->free_aiocb; + pool->free_aiocb = acb->next; + } else { + acb = qemu_mallocz(pool->aiocb_size); + acb->pool = pool; + } + acb->bs = bs; + acb->cb = cb; + acb->opaque = opaque; + return acb; +} + +void qemu_aio_release(void *p) +{ + BlockDriverAIOCB *acb = (BlockDriverAIOCB *)p; + AIOPool *pool = acb->pool; + acb->next = pool->free_aiocb; + pool->free_aiocb = acb; +} + +/**************************************************************/ +/* removable device support */ + +/** + * Return TRUE if the media is present + */ +int bdrv_is_inserted(BlockDriverState *bs) +{ + BlockDriver *drv = bs->drv; + int ret; + if (!drv) + return 0; + if (!drv->bdrv_is_inserted) + return !bs->tray_open; + ret = drv->bdrv_is_inserted(bs); + return ret; +} + +/** + * Return TRUE if the media changed since the last call to this + * function. It is currently only used for floppy disks + */ +int bdrv_media_changed(BlockDriverState *bs) +{ + BlockDriver *drv = bs->drv; + int ret; + + if (!drv || !drv->bdrv_media_changed) + ret = -ENOTSUP; + else + ret = drv->bdrv_media_changed(bs); + if (ret == -ENOTSUP) + ret = bs->media_changed; + bs->media_changed = 0; + return ret; +} + +/** + * If eject_flag is TRUE, eject the media. Otherwise, close the tray + */ +int bdrv_eject(BlockDriverState *bs, int eject_flag) +{ + BlockDriver *drv = bs->drv; + int ret; + + if (bs->locked) { + return -EBUSY; + } + + if (!drv || !drv->bdrv_eject) { + ret = -ENOTSUP; + } else { + ret = drv->bdrv_eject(bs, eject_flag); + } + if (ret == -ENOTSUP) { + ret = 0; + } + if (ret >= 0) { + bs->tray_open = eject_flag; + } + + return ret; +} + +int bdrv_is_locked(BlockDriverState *bs) +{ + return bs->locked; +} + +/** + * Lock or unlock the media (if it is locked, the user won't be able + * to eject it manually). + */ +void bdrv_set_locked(BlockDriverState *bs, int locked) +{ + BlockDriver *drv = bs->drv; + + bs->locked = locked; + if (drv && drv->bdrv_set_locked) { + drv->bdrv_set_locked(bs, locked); + } +} + +/* needed for generic scsi interface */ + +int bdrv_ioctl(BlockDriverState *bs, unsigned long int req, void *buf) +{ + BlockDriver *drv = bs->drv; + + if (drv && drv->bdrv_ioctl) + return drv->bdrv_ioctl(bs, req, buf); + return -ENOTSUP; +} + +BlockDriverAIOCB *bdrv_aio_ioctl(BlockDriverState *bs, + unsigned long int req, void *buf, + BlockDriverCompletionFunc *cb, void *opaque) +{ + BlockDriver *drv = bs->drv; + + if (drv && drv->bdrv_aio_ioctl) + return drv->bdrv_aio_ioctl(bs, req, buf, cb, opaque); + return NULL; +} + + + +void *qemu_blockalign(BlockDriverState *bs, size_t size) +{ + return qemu_memalign((bs && bs->buffer_alignment) ? bs->buffer_alignment : 512, size); +} + +void bdrv_set_dirty_tracking(BlockDriverState *bs, int enable) +{ + int64_t bitmap_size; + + bs->dirty_count = 0; + if (enable) { + if (!bs->dirty_bitmap) { + bitmap_size = (bdrv_getlength(bs) >> BDRV_SECTOR_BITS) + + BDRV_SECTORS_PER_DIRTY_CHUNK * 8 - 1; + bitmap_size /= BDRV_SECTORS_PER_DIRTY_CHUNK * 8; + + bs->dirty_bitmap = qemu_mallocz(bitmap_size); + } + } else { + if (bs->dirty_bitmap) { + qemu_free(bs->dirty_bitmap); + bs->dirty_bitmap = NULL; + } + } +} + +int bdrv_get_dirty(BlockDriverState *bs, int64_t sector) +{ + int64_t chunk = sector / (int64_t)BDRV_SECTORS_PER_DIRTY_CHUNK; + + if (bs->dirty_bitmap && + (sector << BDRV_SECTOR_BITS) < bdrv_getlength(bs)) { + return !!(bs->dirty_bitmap[chunk / (sizeof(unsigned long) * 8)] & + (1UL << (chunk % (sizeof(unsigned long) * 8)))); + } else { + return 0; + } +} + +void bdrv_reset_dirty(BlockDriverState *bs, int64_t cur_sector, + int nr_sectors) +{ + set_dirty_bitmap(bs, cur_sector, nr_sectors, 0); +} + +int64_t bdrv_get_dirty_count(BlockDriverState *bs) +{ + return bs->dirty_count; +} + +void bdrv_set_in_use(BlockDriverState *bs, int in_use) +{ + assert(bs->in_use != in_use); + bs->in_use = in_use; +} + +int bdrv_in_use(BlockDriverState *bs) +{ + return bs->in_use; +} + +int bdrv_img_create(const char *filename, const char *fmt, + const char *base_filename, const char *base_fmt, + char *options, uint64_t img_size, int flags) +{ + QEMUOptionParameter *param = NULL, *create_options = NULL; + QEMUOptionParameter *backing_fmt, *backing_file; + BlockDriverState *bs = NULL; + BlockDriver *drv, *proto_drv; + BlockDriver *backing_drv = NULL; + int ret = 0; + + /* Find driver and parse its options */ + drv = bdrv_find_format(fmt); + if (!drv) { + error_report("Unknown file format '%s'", fmt); + ret = -EINVAL; + goto out; + } + + proto_drv = bdrv_find_protocol(filename); + if (!proto_drv) { + error_report("Unknown protocol '%s'", filename); + ret = -EINVAL; + goto out; + } + + create_options = append_option_parameters(create_options, + drv->create_options); + create_options = append_option_parameters(create_options, + proto_drv->create_options); + + /* Create parameter list with default values */ + param = parse_option_parameters("", create_options, param); + + set_option_parameter_int(param, BLOCK_OPT_SIZE, img_size); + + /* Parse -o options */ + if (options) { + param = parse_option_parameters(options, create_options, param); + if (param == NULL) { + error_report("Invalid options for file format '%s'.", fmt); + ret = -EINVAL; + goto out; + } + } + + if (base_filename) { + if (set_option_parameter(param, BLOCK_OPT_BACKING_FILE, + base_filename)) { + error_report("Backing file not supported for file format '%s'", + fmt); + ret = -EINVAL; + goto out; + } + } + + if (base_fmt) { + if (set_option_parameter(param, BLOCK_OPT_BACKING_FMT, base_fmt)) { + error_report("Backing file format not supported for file " + "format '%s'", fmt); + ret = -EINVAL; + goto out; + } + } + + backing_file = get_option_parameter(param, BLOCK_OPT_BACKING_FILE); + if (backing_file && backing_file->value.s) { + if (!strcmp(filename, backing_file->value.s)) { + error_report("Error: Trying to create an image with the " + "same filename as the backing file"); + ret = -EINVAL; + goto out; + } + } + + backing_fmt = get_option_parameter(param, BLOCK_OPT_BACKING_FMT); + if (backing_fmt && backing_fmt->value.s) { + backing_drv = bdrv_find_format(backing_fmt->value.s); + if (!backing_drv) { + error_report("Unknown backing file format '%s'", + backing_fmt->value.s); + ret = -EINVAL; + goto out; + } + } + + // The size for the image must always be specified, with one exception: + // If we are using a backing file, we can obtain the size from there + if (get_option_parameter(param, BLOCK_OPT_SIZE)->value.n == -1) { + if (backing_file && backing_file->value.s) { + uint64_t size; + char buf[32]; + + bs = bdrv_new(""); + + ret = bdrv_open(bs, backing_file->value.s, flags, backing_drv); + if (ret < 0) { + error_report("Could not open '%s'", backing_file->value.s); + goto out; + } + bdrv_get_geometry(bs, &size); + size *= 512; + + snprintf(buf, sizeof(buf), "%" PRId64, size); + set_option_parameter(param, BLOCK_OPT_SIZE, buf); + } else { + error_report("Image creation needs a size parameter"); + ret = -EINVAL; + goto out; + } + } + + printf("Formatting '%s', fmt=%s ", filename, fmt); + print_option_parameters(param); + puts(""); + + ret = bdrv_create(drv, filename, param); + + if (ret < 0) { + if (ret == -ENOTSUP) { + error_report("Formatting or formatting option not supported for " + "file format '%s'", fmt); + } else if (ret == -EFBIG) { + error_report("The image size is too large for file format '%s'", + fmt); + } else { + error_report("%s: error while creating %s: %s", filename, fmt, + strerror(-ret)); + } + } + +out: + free_option_parameters(create_options); + free_option_parameters(param); + + if (bs) { + bdrv_delete(bs); + } + + return ret; +} diff --git a/block.h b/block.h new file mode 100644 index 0000000..097b789 --- /dev/null +++ b/block.h @@ -0,0 +1,299 @@ +#ifndef BLOCK_H +#define BLOCK_H + +#include "qemu-aio.h" +#include "qemu-common.h" +#include "qemu-option.h" +#include "qobject.h" + +/* block.c */ +typedef struct BlockDriver BlockDriver; + +typedef struct BlockDriverInfo { + /* in bytes, 0 if irrelevant */ + int cluster_size; + /* offset at which the VM state can be saved (0 if not possible) */ + int64_t vm_state_offset; +} BlockDriverInfo; + +typedef struct QEMUSnapshotInfo { + char id_str[128]; /* unique snapshot id */ + /* the following fields are informative. They are not needed for + the consistency of the snapshot */ + char name[256]; /* user choosen name */ + uint32_t vm_state_size; /* VM state info size */ + uint32_t date_sec; /* UTC date of the snapshot */ + uint32_t date_nsec; + uint64_t vm_clock_nsec; /* VM clock relative to boot */ +} QEMUSnapshotInfo; + +#define BDRV_O_RDWR 0x0002 +#define BDRV_O_SNAPSHOT 0x0008 /* open the file read only and save writes in a snapshot */ +#define BDRV_O_NOCACHE 0x0020 /* do not use the host page cache */ +#define BDRV_O_CACHE_WB 0x0040 /* use write-back caching */ +#define BDRV_O_NATIVE_AIO 0x0080 /* use native AIO instead of the thread pool */ +#define BDRV_O_NO_BACKING 0x0100 /* don't open the backing file */ +#define BDRV_O_NO_FLUSH 0x0200 /* disable flushing on this disk */ + +#define BDRV_O_CACHE_MASK (BDRV_O_NOCACHE | BDRV_O_CACHE_WB | BDRV_O_NO_FLUSH) + +#define BDRV_SECTOR_BITS 9 +#define BDRV_SECTOR_SIZE (1ULL << BDRV_SECTOR_BITS) +#define BDRV_SECTOR_MASK ~(BDRV_SECTOR_SIZE - 1) + +typedef enum { + BLOCK_ERR_REPORT, BLOCK_ERR_IGNORE, BLOCK_ERR_STOP_ENOSPC, + BLOCK_ERR_STOP_ANY +} BlockErrorAction; + +typedef enum { + BDRV_ACTION_REPORT, BDRV_ACTION_IGNORE, BDRV_ACTION_STOP +} BlockMonEventAction; + +void bdrv_mon_event(const BlockDriverState *bdrv, + BlockMonEventAction action, int is_read); +void bdrv_info_print(Monitor *mon, const QObject *data); +void bdrv_info(Monitor *mon, QObject **ret_data); +void bdrv_stats_print(Monitor *mon, const QObject *data); +void bdrv_info_stats(Monitor *mon, QObject **ret_data); + +void bdrv_init(void); +void bdrv_init_with_whitelist(void); +BlockDriver *bdrv_find_protocol(const char *filename); +BlockDriver *bdrv_find_format(const char *format_name); +BlockDriver *bdrv_find_whitelisted_format(const char *format_name); +int bdrv_create(BlockDriver *drv, const char* filename, + QEMUOptionParameter *options); +int bdrv_create_file(const char* filename, QEMUOptionParameter *options); +BlockDriverState *bdrv_new(const char *device_name); +void bdrv_make_anon(BlockDriverState *bs); +void bdrv_delete(BlockDriverState *bs); +int bdrv_file_open(BlockDriverState **pbs, const char *filename, int flags); +int bdrv_open(BlockDriverState *bs, const char *filename, int flags, + BlockDriver *drv); +void bdrv_close(BlockDriverState *bs); +int bdrv_attach(BlockDriverState *bs, DeviceState *qdev); +void bdrv_detach(BlockDriverState *bs, DeviceState *qdev); +DeviceState *bdrv_get_attached(BlockDriverState *bs); +int bdrv_read(BlockDriverState *bs, int64_t sector_num, + uint8_t *buf, int nb_sectors); +int bdrv_write(BlockDriverState *bs, int64_t sector_num, + const uint8_t *buf, int nb_sectors); +int bdrv_pread(BlockDriverState *bs, int64_t offset, + void *buf, int count); +int bdrv_pwrite(BlockDriverState *bs, int64_t offset, + const void *buf, int count); +int bdrv_pwrite_sync(BlockDriverState *bs, int64_t offset, + const void *buf, int count); +int bdrv_write_sync(BlockDriverState *bs, int64_t sector_num, + const uint8_t *buf, int nb_sectors); +int bdrv_truncate(BlockDriverState *bs, int64_t offset); +int64_t bdrv_getlength(BlockDriverState *bs); +void bdrv_get_geometry(BlockDriverState *bs, uint64_t *nb_sectors_ptr); +void bdrv_guess_geometry(BlockDriverState *bs, int *pcyls, int *pheads, int *psecs); +int bdrv_commit(BlockDriverState *bs); +void bdrv_commit_all(void); +int bdrv_change_backing_file(BlockDriverState *bs, + const char *backing_file, const char *backing_fmt); +void bdrv_register(BlockDriver *bdrv); + + +typedef struct BdrvCheckResult { + int corruptions; + int leaks; + int check_errors; +} BdrvCheckResult; + +int bdrv_check(BlockDriverState *bs, BdrvCheckResult *res); + +/* async block I/O */ +typedef struct BlockDriverAIOCB BlockDriverAIOCB; +typedef void BlockDriverCompletionFunc(void *opaque, int ret); +typedef void BlockDriverDirtyHandler(BlockDriverState *bs, int64_t sector, + int sector_num); +BlockDriverAIOCB *bdrv_aio_readv(BlockDriverState *bs, int64_t sector_num, + QEMUIOVector *iov, int nb_sectors, + BlockDriverCompletionFunc *cb, void *opaque); +BlockDriverAIOCB *bdrv_aio_writev(BlockDriverState *bs, int64_t sector_num, + QEMUIOVector *iov, int nb_sectors, + BlockDriverCompletionFunc *cb, void *opaque); +BlockDriverAIOCB *bdrv_aio_flush(BlockDriverState *bs, + BlockDriverCompletionFunc *cb, void *opaque); +void bdrv_aio_cancel(BlockDriverAIOCB *acb); + +typedef struct BlockRequest { + /* Fields to be filled by multiwrite caller */ + int64_t sector; + int nb_sectors; + QEMUIOVector *qiov; + BlockDriverCompletionFunc *cb; + void *opaque; + + /* Filled by multiwrite implementation */ + int error; +} BlockRequest; + +int bdrv_aio_multiwrite(BlockDriverState *bs, BlockRequest *reqs, + int num_reqs); + +/* sg packet commands */ +int bdrv_ioctl(BlockDriverState *bs, unsigned long int req, void *buf); +BlockDriverAIOCB *bdrv_aio_ioctl(BlockDriverState *bs, + unsigned long int req, void *buf, + BlockDriverCompletionFunc *cb, void *opaque); + +/* Ensure contents are flushed to disk. */ +int bdrv_flush(BlockDriverState *bs); +void bdrv_flush_all(void); +void bdrv_close_all(void); + +int bdrv_discard(BlockDriverState *bs, int64_t sector_num, int nb_sectors); +int bdrv_has_zero_init(BlockDriverState *bs); +int bdrv_is_allocated(BlockDriverState *bs, int64_t sector_num, int nb_sectors, + int *pnum); + +#define BDRV_TYPE_HD 0 +#define BDRV_TYPE_CDROM 1 +#define BDRV_TYPE_FLOPPY 2 +#define BIOS_ATA_TRANSLATION_AUTO 0 +#define BIOS_ATA_TRANSLATION_NONE 1 +#define BIOS_ATA_TRANSLATION_LBA 2 +#define BIOS_ATA_TRANSLATION_LARGE 3 +#define BIOS_ATA_TRANSLATION_RECHS 4 + +void bdrv_set_geometry_hint(BlockDriverState *bs, + int cyls, int heads, int secs); +void bdrv_set_type_hint(BlockDriverState *bs, int type); +void bdrv_set_translation_hint(BlockDriverState *bs, int translation); +void bdrv_get_geometry_hint(BlockDriverState *bs, + int *pcyls, int *pheads, int *psecs); +int bdrv_get_type_hint(BlockDriverState *bs); +int bdrv_get_translation_hint(BlockDriverState *bs); +void bdrv_set_on_error(BlockDriverState *bs, BlockErrorAction on_read_error, + BlockErrorAction on_write_error); +BlockErrorAction bdrv_get_on_error(BlockDriverState *bs, int is_read); +void bdrv_set_removable(BlockDriverState *bs, int removable); +int bdrv_is_removable(BlockDriverState *bs); +int bdrv_is_read_only(BlockDriverState *bs); +int bdrv_is_sg(BlockDriverState *bs); +int bdrv_enable_write_cache(BlockDriverState *bs); +int bdrv_is_inserted(BlockDriverState *bs); +int bdrv_media_changed(BlockDriverState *bs); +int bdrv_is_locked(BlockDriverState *bs); +void bdrv_set_locked(BlockDriverState *bs, int locked); +int bdrv_eject(BlockDriverState *bs, int eject_flag); +void bdrv_set_change_cb(BlockDriverState *bs, + void (*change_cb)(void *opaque, int reason), + void *opaque); +void bdrv_get_format(BlockDriverState *bs, char *buf, int buf_size); +BlockDriverState *bdrv_find(const char *name); +BlockDriverState *bdrv_next(BlockDriverState *bs); +void bdrv_iterate(void (*it)(void *opaque, BlockDriverState *bs), + void *opaque); +int bdrv_is_encrypted(BlockDriverState *bs); +int bdrv_key_required(BlockDriverState *bs); +int bdrv_set_key(BlockDriverState *bs, const char *key); +int bdrv_query_missing_keys(void); +void bdrv_iterate_format(void (*it)(void *opaque, const char *name), + void *opaque); +const char *bdrv_get_device_name(BlockDriverState *bs); +int bdrv_write_compressed(BlockDriverState *bs, int64_t sector_num, + const uint8_t *buf, int nb_sectors); +int bdrv_get_info(BlockDriverState *bs, BlockDriverInfo *bdi); + +const char *bdrv_get_encrypted_filename(BlockDriverState *bs); +void bdrv_get_backing_filename(BlockDriverState *bs, + char *filename, int filename_size); +int bdrv_can_snapshot(BlockDriverState *bs); +int bdrv_is_snapshot(BlockDriverState *bs); +BlockDriverState *bdrv_snapshots(void); +int bdrv_snapshot_create(BlockDriverState *bs, + QEMUSnapshotInfo *sn_info); +int bdrv_snapshot_goto(BlockDriverState *bs, + const char *snapshot_id); +int bdrv_snapshot_delete(BlockDriverState *bs, const char *snapshot_id); +int bdrv_snapshot_list(BlockDriverState *bs, + QEMUSnapshotInfo **psn_info); +int bdrv_snapshot_load_tmp(BlockDriverState *bs, + const char *snapshot_name); +char *bdrv_snapshot_dump(char *buf, int buf_size, QEMUSnapshotInfo *sn); + +char *get_human_readable_size(char *buf, int buf_size, int64_t size); +int path_is_absolute(const char *path); +void path_combine(char *dest, int dest_size, + const char *base_path, + const char *filename); + +int bdrv_save_vmstate(BlockDriverState *bs, const uint8_t *buf, + int64_t pos, int size); + +int bdrv_load_vmstate(BlockDriverState *bs, uint8_t *buf, + int64_t pos, int size); + +int bdrv_img_create(const char *filename, const char *fmt, + const char *base_filename, const char *base_fmt, + char *options, uint64_t img_size, int flags); + +#define BDRV_SECTORS_PER_DIRTY_CHUNK 2048 + +void bdrv_set_dirty_tracking(BlockDriverState *bs, int enable); +int bdrv_get_dirty(BlockDriverState *bs, int64_t sector); +void bdrv_reset_dirty(BlockDriverState *bs, int64_t cur_sector, + int nr_sectors); +int64_t bdrv_get_dirty_count(BlockDriverState *bs); + +void bdrv_set_in_use(BlockDriverState *bs, int in_use); +int bdrv_in_use(BlockDriverState *bs); + +typedef enum { + BLKDBG_L1_UPDATE, + + BLKDBG_L1_GROW_ALLOC_TABLE, + BLKDBG_L1_GROW_WRITE_TABLE, + BLKDBG_L1_GROW_ACTIVATE_TABLE, + + BLKDBG_L2_LOAD, + BLKDBG_L2_UPDATE, + BLKDBG_L2_UPDATE_COMPRESSED, + BLKDBG_L2_ALLOC_COW_READ, + BLKDBG_L2_ALLOC_WRITE, + + BLKDBG_READ, + BLKDBG_READ_AIO, + BLKDBG_READ_BACKING, + BLKDBG_READ_BACKING_AIO, + BLKDBG_READ_COMPRESSED, + + BLKDBG_WRITE_AIO, + BLKDBG_WRITE_COMPRESSED, + + BLKDBG_VMSTATE_LOAD, + BLKDBG_VMSTATE_SAVE, + + BLKDBG_COW_READ, + BLKDBG_COW_WRITE, + + BLKDBG_REFTABLE_LOAD, + BLKDBG_REFTABLE_GROW, + + BLKDBG_REFBLOCK_LOAD, + BLKDBG_REFBLOCK_UPDATE, + BLKDBG_REFBLOCK_UPDATE_PART, + BLKDBG_REFBLOCK_ALLOC, + BLKDBG_REFBLOCK_ALLOC_HOOKUP, + BLKDBG_REFBLOCK_ALLOC_WRITE, + BLKDBG_REFBLOCK_ALLOC_WRITE_BLOCKS, + BLKDBG_REFBLOCK_ALLOC_WRITE_TABLE, + BLKDBG_REFBLOCK_ALLOC_SWITCH_TABLE, + + BLKDBG_CLUSTER_ALLOC, + BLKDBG_CLUSTER_ALLOC_BYTES, + BLKDBG_CLUSTER_FREE, + + BLKDBG_EVENT_MAX, +} BlkDebugEvent; + +#define BLKDBG_EVENT(bs, evt) bdrv_debug_event(bs, evt) +void bdrv_debug_event(BlockDriverState *bs, BlkDebugEvent event); + +#endif diff --git a/block/blkdebug.c b/block/blkdebug.c new file mode 100644 index 0000000..cd9eb80 --- /dev/null +++ b/block/blkdebug.c @@ -0,0 +1,471 @@ +/* + * Block protocol for I/O error injection + * + * Copyright (c) 2010 Kevin Wolf + * + * 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 "block_int.h" +#include "module.h" + +typedef struct BlkdebugVars { + int state; + + /* If inject_errno != 0, an error is injected for requests */ + int inject_errno; + + /* Decides if all future requests fail (false) or only the next one and + * after the next request inject_errno is reset to 0 (true) */ + bool inject_once; + + /* Decides if aio_readv/writev fails right away (true) or returns an error + * return value only in the callback (false) */ + bool inject_immediately; +} BlkdebugVars; + +typedef struct BDRVBlkdebugState { + BlkdebugVars vars; + QLIST_HEAD(list, BlkdebugRule) rules[BLKDBG_EVENT_MAX]; +} BDRVBlkdebugState; + +typedef struct BlkdebugAIOCB { + BlockDriverAIOCB common; + QEMUBH *bh; + int ret; +} BlkdebugAIOCB; + +static void blkdebug_aio_cancel(BlockDriverAIOCB *blockacb); + +static AIOPool blkdebug_aio_pool = { + .aiocb_size = sizeof(BlkdebugAIOCB), + .cancel = blkdebug_aio_cancel, +}; + +enum { + ACTION_INJECT_ERROR, + ACTION_SET_STATE, +}; + +typedef struct BlkdebugRule { + BlkDebugEvent event; + int action; + int state; + union { + struct { + int error; + int immediately; + int once; + } inject; + struct { + int new_state; + } set_state; + } options; + QLIST_ENTRY(BlkdebugRule) next; +} BlkdebugRule; + +static QemuOptsList inject_error_opts = { + .name = "inject-error", + .head = QTAILQ_HEAD_INITIALIZER(inject_error_opts.head), + .desc = { + { + .name = "event", + .type = QEMU_OPT_STRING, + }, + { + .name = "state", + .type = QEMU_OPT_NUMBER, + }, + { + .name = "errno", + .type = QEMU_OPT_NUMBER, + }, + { + .name = "once", + .type = QEMU_OPT_BOOL, + }, + { + .name = "immediately", + .type = QEMU_OPT_BOOL, + }, + { /* end of list */ } + }, +}; + +static QemuOptsList set_state_opts = { + .name = "set-state", + .head = QTAILQ_HEAD_INITIALIZER(set_state_opts.head), + .desc = { + { + .name = "event", + .type = QEMU_OPT_STRING, + }, + { + .name = "state", + .type = QEMU_OPT_NUMBER, + }, + { + .name = "new_state", + .type = QEMU_OPT_NUMBER, + }, + { /* end of list */ } + }, +}; + +static QemuOptsList *config_groups[] = { + &inject_error_opts, + &set_state_opts, + NULL +}; + +static const char *event_names[BLKDBG_EVENT_MAX] = { + [BLKDBG_L1_UPDATE] = "l1_update", + [BLKDBG_L1_GROW_ALLOC_TABLE] = "l1_grow.alloc_table", + [BLKDBG_L1_GROW_WRITE_TABLE] = "l1_grow.write_table", + [BLKDBG_L1_GROW_ACTIVATE_TABLE] = "l1_grow.activate_table", + + [BLKDBG_L2_LOAD] = "l2_load", + [BLKDBG_L2_UPDATE] = "l2_update", + [BLKDBG_L2_UPDATE_COMPRESSED] = "l2_update_compressed", + [BLKDBG_L2_ALLOC_COW_READ] = "l2_alloc.cow_read", + [BLKDBG_L2_ALLOC_WRITE] = "l2_alloc.write", + + [BLKDBG_READ] = "read", + [BLKDBG_READ_AIO] = "read_aio", + [BLKDBG_READ_BACKING] = "read_backing", + [BLKDBG_READ_BACKING_AIO] = "read_backing_aio", + [BLKDBG_READ_COMPRESSED] = "read_compressed", + + [BLKDBG_WRITE_AIO] = "write_aio", + [BLKDBG_WRITE_COMPRESSED] = "write_compressed", + + [BLKDBG_VMSTATE_LOAD] = "vmstate_load", + [BLKDBG_VMSTATE_SAVE] = "vmstate_save", + + [BLKDBG_COW_READ] = "cow_read", + [BLKDBG_COW_WRITE] = "cow_write", + + [BLKDBG_REFTABLE_LOAD] = "reftable_load", + [BLKDBG_REFTABLE_GROW] = "reftable_grow", + + [BLKDBG_REFBLOCK_LOAD] = "refblock_load", + [BLKDBG_REFBLOCK_UPDATE] = "refblock_update", + [BLKDBG_REFBLOCK_UPDATE_PART] = "refblock_update_part", + [BLKDBG_REFBLOCK_ALLOC] = "refblock_alloc", + [BLKDBG_REFBLOCK_ALLOC_HOOKUP] = "refblock_alloc.hookup", + [BLKDBG_REFBLOCK_ALLOC_WRITE] = "refblock_alloc.write", + [BLKDBG_REFBLOCK_ALLOC_WRITE_BLOCKS] = "refblock_alloc.write_blocks", + [BLKDBG_REFBLOCK_ALLOC_WRITE_TABLE] = "refblock_alloc.write_table", + [BLKDBG_REFBLOCK_ALLOC_SWITCH_TABLE] = "refblock_alloc.switch_table", + + [BLKDBG_CLUSTER_ALLOC] = "cluster_alloc", + [BLKDBG_CLUSTER_ALLOC_BYTES] = "cluster_alloc_bytes", + [BLKDBG_CLUSTER_FREE] = "cluster_free", +}; + +static int get_event_by_name(const char *name, BlkDebugEvent *event) +{ + int i; + + for (i = 0; i < BLKDBG_EVENT_MAX; i++) { + if (!strcmp(event_names[i], name)) { + *event = i; + return 0; + } + } + + return -1; +} + +struct add_rule_data { + BDRVBlkdebugState *s; + int action; +}; + +static int add_rule(QemuOpts *opts, void *opaque) +{ + struct add_rule_data *d = opaque; + BDRVBlkdebugState *s = d->s; + const char* event_name; + BlkDebugEvent event; + struct BlkdebugRule *rule; + + /* Find the right event for the rule */ + event_name = qemu_opt_get(opts, "event"); + if (!event_name || get_event_by_name(event_name, &event) < 0) { + return -1; + } + + /* Set attributes common for all actions */ + rule = qemu_mallocz(sizeof(*rule)); + *rule = (struct BlkdebugRule) { + .event = event, + .action = d->action, + .state = qemu_opt_get_number(opts, "state", 0), + }; + + /* Parse action-specific options */ + switch (d->action) { + case ACTION_INJECT_ERROR: + rule->options.inject.error = qemu_opt_get_number(opts, "errno", EIO); + rule->options.inject.once = qemu_opt_get_bool(opts, "once", 0); + rule->options.inject.immediately = + qemu_opt_get_bool(opts, "immediately", 0); + break; + + case ACTION_SET_STATE: + rule->options.set_state.new_state = + qemu_opt_get_number(opts, "new_state", 0); + break; + }; + + /* Add the rule */ + QLIST_INSERT_HEAD(&s->rules[event], rule, next); + + return 0; +} + +static int read_config(BDRVBlkdebugState *s, const char *filename) +{ + FILE *f; + int ret; + struct add_rule_data d; + + f = fopen(filename, "r"); + if (f == NULL) { + return -errno; + } + + ret = qemu_config_parse(f, config_groups, filename); + if (ret < 0) { + goto fail; + } + + d.s = s; + d.action = ACTION_INJECT_ERROR; + qemu_opts_foreach(&inject_error_opts, add_rule, &d, 0); + + d.action = ACTION_SET_STATE; + qemu_opts_foreach(&set_state_opts, add_rule, &d, 0); + + ret = 0; +fail: + qemu_opts_reset(&inject_error_opts); + qemu_opts_reset(&set_state_opts); + fclose(f); + return ret; +} + +/* Valid blkdebug filenames look like blkdebug:path/to/config:path/to/image */ +static int blkdebug_open(BlockDriverState *bs, const char *filename, int flags) +{ + BDRVBlkdebugState *s = bs->opaque; + int ret; + char *config, *c; + + /* Parse the blkdebug: prefix */ + if (strncmp(filename, "blkdebug:", strlen("blkdebug:"))) { + return -EINVAL; + } + filename += strlen("blkdebug:"); + + /* Read rules from config file */ + c = strchr(filename, ':'); + if (c == NULL) { + return -EINVAL; + } + + config = strdup(filename); + config[c - filename] = '\0'; + ret = read_config(s, config); + free(config); + if (ret < 0) { + return ret; + } + filename = c + 1; + + /* Set initial state */ + s->vars.state = 1; + + /* Open the backing file */ + ret = bdrv_file_open(&bs->file, filename, flags); + if (ret < 0) { + return ret; + } + + return 0; +} + +static void error_callback_bh(void *opaque) +{ + struct BlkdebugAIOCB *acb = opaque; + qemu_bh_delete(acb->bh); + acb->common.cb(acb->common.opaque, acb->ret); + qemu_aio_release(acb); +} + +static void blkdebug_aio_cancel(BlockDriverAIOCB *blockacb) +{ + BlkdebugAIOCB *acb = container_of(blockacb, BlkdebugAIOCB, common); + qemu_aio_release(acb); +} + +static BlockDriverAIOCB *inject_error(BlockDriverState *bs, + BlockDriverCompletionFunc *cb, void *opaque) +{ + BDRVBlkdebugState *s = bs->opaque; + int error = s->vars.inject_errno; + struct BlkdebugAIOCB *acb; + QEMUBH *bh; + + if (s->vars.inject_once) { + s->vars.inject_errno = 0; + } + + if (s->vars.inject_immediately) { + return NULL; + } + + acb = qemu_aio_get(&blkdebug_aio_pool, bs, cb, opaque); + acb->ret = -error; + + bh = qemu_bh_new(error_callback_bh, acb); + acb->bh = bh; + qemu_bh_schedule(bh); + + return &acb->common; +} + +static BlockDriverAIOCB *blkdebug_aio_readv(BlockDriverState *bs, + int64_t sector_num, QEMUIOVector *qiov, int nb_sectors, + BlockDriverCompletionFunc *cb, void *opaque) +{ + BDRVBlkdebugState *s = bs->opaque; + + if (s->vars.inject_errno) { + return inject_error(bs, cb, opaque); + } + + BlockDriverAIOCB *acb = + bdrv_aio_readv(bs->file, sector_num, qiov, nb_sectors, cb, opaque); + return acb; +} + +static BlockDriverAIOCB *blkdebug_aio_writev(BlockDriverState *bs, + int64_t sector_num, QEMUIOVector *qiov, int nb_sectors, + BlockDriverCompletionFunc *cb, void *opaque) +{ + BDRVBlkdebugState *s = bs->opaque; + + if (s->vars.inject_errno) { + return inject_error(bs, cb, opaque); + } + + BlockDriverAIOCB *acb = + bdrv_aio_writev(bs->file, sector_num, qiov, nb_sectors, cb, opaque); + return acb; +} + +static void blkdebug_close(BlockDriverState *bs) +{ + BDRVBlkdebugState *s = bs->opaque; + BlkdebugRule *rule, *next; + int i; + + for (i = 0; i < BLKDBG_EVENT_MAX; i++) { + QLIST_FOREACH_SAFE(rule, &s->rules[i], next, next) { + QLIST_REMOVE(rule, next); + qemu_free(rule); + } + } +} + +static int blkdebug_flush(BlockDriverState *bs) +{ + return bdrv_flush(bs->file); +} + +static BlockDriverAIOCB *blkdebug_aio_flush(BlockDriverState *bs, + BlockDriverCompletionFunc *cb, void *opaque) +{ + return bdrv_aio_flush(bs->file, cb, opaque); +} + +static void process_rule(BlockDriverState *bs, struct BlkdebugRule *rule, + BlkdebugVars *old_vars) +{ + BDRVBlkdebugState *s = bs->opaque; + BlkdebugVars *vars = &s->vars; + + /* Only process rules for the current state */ + if (rule->state && rule->state != old_vars->state) { + return; + } + + /* Take the action */ + switch (rule->action) { + case ACTION_INJECT_ERROR: + vars->inject_errno = rule->options.inject.error; + vars->inject_once = rule->options.inject.once; + vars->inject_immediately = rule->options.inject.immediately; + break; + + case ACTION_SET_STATE: + vars->state = rule->options.set_state.new_state; + break; + } +} + +static void blkdebug_debug_event(BlockDriverState *bs, BlkDebugEvent event) +{ + BDRVBlkdebugState *s = bs->opaque; + struct BlkdebugRule *rule; + BlkdebugVars old_vars = s->vars; + + assert((int)event >= 0 && event < BLKDBG_EVENT_MAX); + + QLIST_FOREACH(rule, &s->rules[event], next) { + process_rule(bs, rule, &old_vars); + } +} + +static BlockDriver bdrv_blkdebug = { + .format_name = "blkdebug", + .protocol_name = "blkdebug", + + .instance_size = sizeof(BDRVBlkdebugState), + + .bdrv_file_open = blkdebug_open, + .bdrv_close = blkdebug_close, + .bdrv_flush = blkdebug_flush, + + .bdrv_aio_readv = blkdebug_aio_readv, + .bdrv_aio_writev = blkdebug_aio_writev, + .bdrv_aio_flush = blkdebug_aio_flush, + + .bdrv_debug_event = blkdebug_debug_event, +}; + +static void bdrv_blkdebug_init(void) +{ + bdrv_register(&bdrv_blkdebug); +} + +block_init(bdrv_blkdebug_init); diff --git a/block/blkverify.c b/block/blkverify.c new file mode 100644 index 0000000..c7522b4 --- /dev/null +++ b/block/blkverify.c @@ -0,0 +1,383 @@ +/* + * Block protocol for block driver correctness testing + * + * Copyright (C) 2010 IBM, Corp. + * + * 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 +#include "qemu_socket.h" /* for EINPROGRESS on Windows */ +#include "block_int.h" + +typedef struct { + BlockDriverState *test_file; +} BDRVBlkverifyState; + +typedef struct BlkverifyAIOCB BlkverifyAIOCB; +struct BlkverifyAIOCB { + BlockDriverAIOCB common; + QEMUBH *bh; + + /* Request metadata */ + bool is_write; + int64_t sector_num; + int nb_sectors; + + int ret; /* first completed request's result */ + unsigned int done; /* completion counter */ + bool *finished; /* completion signal for cancel */ + + QEMUIOVector *qiov; /* user I/O vector */ + QEMUIOVector raw_qiov; /* cloned I/O vector for raw file */ + void *buf; /* buffer for raw file I/O */ + + void (*verify)(BlkverifyAIOCB *acb); +}; + +static void blkverify_aio_cancel(BlockDriverAIOCB *blockacb) +{ + BlkverifyAIOCB *acb = (BlkverifyAIOCB *)blockacb; + bool finished = false; + + /* Wait until request completes, invokes its callback, and frees itself */ + acb->finished = &finished; + while (!finished) { + qemu_aio_wait(); + } +} + +static AIOPool blkverify_aio_pool = { + .aiocb_size = sizeof(BlkverifyAIOCB), + .cancel = blkverify_aio_cancel, +}; + +static void GCC_FMT_ATTR(2, 3) blkverify_err(BlkverifyAIOCB *acb, + const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + fprintf(stderr, "blkverify: %s sector_num=%" PRId64 " nb_sectors=%d ", + acb->is_write ? "write" : "read", acb->sector_num, + acb->nb_sectors); + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + va_end(ap); + exit(1); +} + +/* Valid blkverify filenames look like blkverify:path/to/raw_image:path/to/image */ +static int blkverify_open(BlockDriverState *bs, const char *filename, int flags) +{ + BDRVBlkverifyState *s = bs->opaque; + int ret; + char *raw, *c; + + /* Parse the blkverify: prefix */ + if (strncmp(filename, "blkverify:", strlen("blkverify:"))) { + return -EINVAL; + } + filename += strlen("blkverify:"); + + /* Parse the raw image filename */ + c = strchr(filename, ':'); + if (c == NULL) { + return -EINVAL; + } + + raw = strdup(filename); + raw[c - filename] = '\0'; + ret = bdrv_file_open(&bs->file, raw, flags); + free(raw); + if (ret < 0) { + return ret; + } + filename = c + 1; + + /* Open the test file */ + s->test_file = bdrv_new(""); + ret = bdrv_open(s->test_file, filename, flags, NULL); + if (ret < 0) { + bdrv_delete(s->test_file); + s->test_file = NULL; + return ret; + } + + return 0; +} + +static void blkverify_close(BlockDriverState *bs) +{ + BDRVBlkverifyState *s = bs->opaque; + + bdrv_delete(s->test_file); + s->test_file = NULL; +} + +static int blkverify_flush(BlockDriverState *bs) +{ + BDRVBlkverifyState *s = bs->opaque; + + /* Only flush test file, the raw file is not important */ + return bdrv_flush(s->test_file); +} + +static int64_t blkverify_getlength(BlockDriverState *bs) +{ + BDRVBlkverifyState *s = bs->opaque; + + return bdrv_getlength(s->test_file); +} + +/** + * Check that I/O vector contents are identical + * + * @a: I/O vector + * @b: I/O vector + * @ret: Offset to first mismatching byte or -1 if match + */ +static ssize_t blkverify_iovec_compare(QEMUIOVector *a, QEMUIOVector *b) +{ + int i; + ssize_t offset = 0; + + assert(a->niov == b->niov); + for (i = 0; i < a->niov; i++) { + size_t len = 0; + uint8_t *p = (uint8_t *)a->iov[i].iov_base; + uint8_t *q = (uint8_t *)b->iov[i].iov_base; + + assert(a->iov[i].iov_len == b->iov[i].iov_len); + while (len < a->iov[i].iov_len && *p++ == *q++) { + len++; + } + + offset += len; + + if (len != a->iov[i].iov_len) { + return offset; + } + } + return -1; +} + +typedef struct { + int src_index; + struct iovec *src_iov; + void *dest_base; +} IOVectorSortElem; + +static int sortelem_cmp_src_base(const void *a, const void *b) +{ + const IOVectorSortElem *elem_a = a; + const IOVectorSortElem *elem_b = b; + + /* Don't overflow */ + if (elem_a->src_iov->iov_base < elem_b->src_iov->iov_base) { + return -1; + } else if (elem_a->src_iov->iov_base > elem_b->src_iov->iov_base) { + return 1; + } else { + return 0; + } +} + +static int sortelem_cmp_src_index(const void *a, const void *b) +{ + const IOVectorSortElem *elem_a = a; + const IOVectorSortElem *elem_b = b; + + return elem_a->src_index - elem_b->src_index; +} + +/** + * Copy contents of I/O vector + * + * The relative relationships of overlapping iovecs are preserved. This is + * necessary to ensure identical semantics in the cloned I/O vector. + */ +static void blkverify_iovec_clone(QEMUIOVector *dest, const QEMUIOVector *src, + void *buf) +{ + IOVectorSortElem sortelems[src->niov]; + void *last_end; + int i; + + /* Sort by source iovecs by base address */ + for (i = 0; i < src->niov; i++) { + sortelems[i].src_index = i; + sortelems[i].src_iov = &src->iov[i]; + } + qsort(sortelems, src->niov, sizeof(sortelems[0]), sortelem_cmp_src_base); + + /* Allocate buffer space taking into account overlapping iovecs */ + last_end = NULL; + for (i = 0; i < src->niov; i++) { + struct iovec *cur = sortelems[i].src_iov; + ptrdiff_t rewind = 0; + + /* Detect overlap */ + if (last_end && last_end > cur->iov_base) { + rewind = last_end - cur->iov_base; + } + + sortelems[i].dest_base = buf - rewind; + buf += cur->iov_len - MIN(rewind, cur->iov_len); + last_end = MAX(cur->iov_base + cur->iov_len, last_end); + } + + /* Sort by source iovec index and build destination iovec */ + qsort(sortelems, src->niov, sizeof(sortelems[0]), sortelem_cmp_src_index); + for (i = 0; i < src->niov; i++) { + qemu_iovec_add(dest, sortelems[i].dest_base, src->iov[i].iov_len); + } +} + +static BlkverifyAIOCB *blkverify_aio_get(BlockDriverState *bs, bool is_write, + int64_t sector_num, QEMUIOVector *qiov, + int nb_sectors, + BlockDriverCompletionFunc *cb, + void *opaque) +{ + BlkverifyAIOCB *acb = qemu_aio_get(&blkverify_aio_pool, bs, cb, opaque); + + acb->bh = NULL; + acb->is_write = is_write; + acb->sector_num = sector_num; + acb->nb_sectors = nb_sectors; + acb->ret = -EINPROGRESS; + acb->done = 0; + acb->qiov = qiov; + acb->buf = NULL; + acb->verify = NULL; + acb->finished = NULL; + return acb; +} + +static void blkverify_aio_bh(void *opaque) +{ + BlkverifyAIOCB *acb = opaque; + + qemu_bh_delete(acb->bh); + if (acb->buf) { + qemu_iovec_destroy(&acb->raw_qiov); + qemu_vfree(acb->buf); + } + acb->common.cb(acb->common.opaque, acb->ret); + if (acb->finished) { + *acb->finished = true; + } + qemu_aio_release(acb); +} + +static void blkverify_aio_cb(void *opaque, int ret) +{ + BlkverifyAIOCB *acb = opaque; + + switch (++acb->done) { + case 1: + acb->ret = ret; + break; + + case 2: + if (acb->ret != ret) { + blkverify_err(acb, "return value mismatch %d != %d", acb->ret, ret); + } + + if (acb->verify) { + acb->verify(acb); + } + + acb->bh = qemu_bh_new(blkverify_aio_bh, acb); + qemu_bh_schedule(acb->bh); + break; + } +} + +static void blkverify_verify_readv(BlkverifyAIOCB *acb) +{ + ssize_t offset = blkverify_iovec_compare(acb->qiov, &acb->raw_qiov); + if (offset != -1) { + blkverify_err(acb, "contents mismatch in sector %" PRId64, + acb->sector_num + (int64_t)(offset / BDRV_SECTOR_SIZE)); + } +} + +static BlockDriverAIOCB *blkverify_aio_readv(BlockDriverState *bs, + int64_t sector_num, QEMUIOVector *qiov, int nb_sectors, + BlockDriverCompletionFunc *cb, void *opaque) +{ + BDRVBlkverifyState *s = bs->opaque; + BlkverifyAIOCB *acb = blkverify_aio_get(bs, false, sector_num, qiov, + nb_sectors, cb, opaque); + + acb->verify = blkverify_verify_readv; + acb->buf = qemu_blockalign(bs->file, qiov->size); + qemu_iovec_init(&acb->raw_qiov, acb->qiov->niov); + blkverify_iovec_clone(&acb->raw_qiov, qiov, acb->buf); + + if (!bdrv_aio_readv(s->test_file, sector_num, qiov, nb_sectors, + blkverify_aio_cb, acb)) { + blkverify_aio_cb(acb, -EIO); + } + if (!bdrv_aio_readv(bs->file, sector_num, &acb->raw_qiov, nb_sectors, + blkverify_aio_cb, acb)) { + blkverify_aio_cb(acb, -EIO); + } + return &acb->common; +} + +static BlockDriverAIOCB *blkverify_aio_writev(BlockDriverState *bs, + int64_t sector_num, QEMUIOVector *qiov, int nb_sectors, + BlockDriverCompletionFunc *cb, void *opaque) +{ + BDRVBlkverifyState *s = bs->opaque; + BlkverifyAIOCB *acb = blkverify_aio_get(bs, true, sector_num, qiov, + nb_sectors, cb, opaque); + + if (!bdrv_aio_writev(s->test_file, sector_num, qiov, nb_sectors, + blkverify_aio_cb, acb)) { + blkverify_aio_cb(acb, -EIO); + } + if (!bdrv_aio_writev(bs->file, sector_num, qiov, nb_sectors, + blkverify_aio_cb, acb)) { + blkverify_aio_cb(acb, -EIO); + } + return &acb->common; +} + +static BlockDriverAIOCB *blkverify_aio_flush(BlockDriverState *bs, + BlockDriverCompletionFunc *cb, + void *opaque) +{ + BDRVBlkverifyState *s = bs->opaque; + + /* Only flush test file, the raw file is not important */ + return bdrv_aio_flush(s->test_file, cb, opaque); +} + +static BlockDriver bdrv_blkverify = { + .format_name = "blkverify", + .protocol_name = "blkverify", + + .instance_size = sizeof(BDRVBlkverifyState), + + .bdrv_getlength = blkverify_getlength, + + .bdrv_file_open = blkverify_open, + .bdrv_close = blkverify_close, + .bdrv_flush = blkverify_flush, + + .bdrv_aio_readv = blkverify_aio_readv, + .bdrv_aio_writev = blkverify_aio_writev, + .bdrv_aio_flush = blkverify_aio_flush, +}; + +static void bdrv_blkverify_init(void) +{ + bdrv_register(&bdrv_blkverify); +} + +block_init(bdrv_blkverify_init); diff --git a/block/bochs.c b/block/bochs.c new file mode 100644 index 0000000..5fe2fa3 --- /dev/null +++ b/block/bochs.c @@ -0,0 +1,230 @@ +/* + * Block driver for the various disk image formats used by Bochs + * Currently only for "growing" type in read-only mode + * + * Copyright (c) 2005 Alex Beregszaszi + * + * 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 "block_int.h" +#include "module.h" + +/**************************************************************/ + +#define HEADER_MAGIC "Bochs Virtual HD Image" +#define HEADER_VERSION 0x00020000 +#define HEADER_V1 0x00010000 +#define HEADER_SIZE 512 + +#define REDOLOG_TYPE "Redolog" +#define GROWING_TYPE "Growing" + +// not allocated: 0xffffffff + +// always little-endian +struct bochs_header_v1 { + char magic[32]; // "Bochs Virtual HD Image" + char type[16]; // "Redolog" + char subtype[16]; // "Undoable" / "Volatile" / "Growing" + uint32_t version; + uint32_t header; // size of header + + union { + struct { + uint32_t catalog; // num of entries + uint32_t bitmap; // bitmap size + uint32_t extent; // extent size + uint64_t disk; // disk size + char padding[HEADER_SIZE - 64 - 8 - 20]; + } redolog; + char padding[HEADER_SIZE - 64 - 8]; + } extra; +}; + +// always little-endian +struct bochs_header { + char magic[32]; // "Bochs Virtual HD Image" + char type[16]; // "Redolog" + char subtype[16]; // "Undoable" / "Volatile" / "Growing" + uint32_t version; + uint32_t header; // size of header + + union { + struct { + uint32_t catalog; // num of entries + uint32_t bitmap; // bitmap size + uint32_t extent; // extent size + uint32_t reserved; // for ??? + uint64_t disk; // disk size + char padding[HEADER_SIZE - 64 - 8 - 24]; + } redolog; + char padding[HEADER_SIZE - 64 - 8]; + } extra; +}; + +typedef struct BDRVBochsState { + uint32_t *catalog_bitmap; + int catalog_size; + + int data_offset; + + int bitmap_blocks; + int extent_blocks; + int extent_size; +} BDRVBochsState; + +static int bochs_probe(const uint8_t *buf, int buf_size, const char *filename) +{ + const struct bochs_header *bochs = (const void *)buf; + + if (buf_size < HEADER_SIZE) + return 0; + + if (!strcmp(bochs->magic, HEADER_MAGIC) && + !strcmp(bochs->type, REDOLOG_TYPE) && + !strcmp(bochs->subtype, GROWING_TYPE) && + ((le32_to_cpu(bochs->version) == HEADER_VERSION) || + (le32_to_cpu(bochs->version) == HEADER_V1))) + return 100; + + return 0; +} + +static int bochs_open(BlockDriverState *bs, int flags) +{ + BDRVBochsState *s = bs->opaque; + int i; + struct bochs_header bochs; + struct bochs_header_v1 header_v1; + + bs->read_only = 1; // no write support yet + + if (bdrv_pread(bs->file, 0, &bochs, sizeof(bochs)) != sizeof(bochs)) { + goto fail; + } + + if (strcmp(bochs.magic, HEADER_MAGIC) || + strcmp(bochs.type, REDOLOG_TYPE) || + strcmp(bochs.subtype, GROWING_TYPE) || + ((le32_to_cpu(bochs.version) != HEADER_VERSION) && + (le32_to_cpu(bochs.version) != HEADER_V1))) { + goto fail; + } + + if (le32_to_cpu(bochs.version) == HEADER_V1) { + memcpy(&header_v1, &bochs, sizeof(bochs)); + bs->total_sectors = le64_to_cpu(header_v1.extra.redolog.disk) / 512; + } else { + bs->total_sectors = le64_to_cpu(bochs.extra.redolog.disk) / 512; + } + + s->catalog_size = le32_to_cpu(bochs.extra.redolog.catalog); + s->catalog_bitmap = qemu_malloc(s->catalog_size * 4); + if (bdrv_pread(bs->file, le32_to_cpu(bochs.header), s->catalog_bitmap, + s->catalog_size * 4) != s->catalog_size * 4) + goto fail; + for (i = 0; i < s->catalog_size; i++) + le32_to_cpus(&s->catalog_bitmap[i]); + + s->data_offset = le32_to_cpu(bochs.header) + (s->catalog_size * 4); + + s->bitmap_blocks = 1 + (le32_to_cpu(bochs.extra.redolog.bitmap) - 1) / 512; + s->extent_blocks = 1 + (le32_to_cpu(bochs.extra.redolog.extent) - 1) / 512; + + s->extent_size = le32_to_cpu(bochs.extra.redolog.extent); + + return 0; + fail: + return -1; +} + +static int64_t seek_to_sector(BlockDriverState *bs, int64_t sector_num) +{ + BDRVBochsState *s = bs->opaque; + int64_t offset = sector_num * 512; + int64_t extent_index, extent_offset, bitmap_offset; + char bitmap_entry; + + // seek to sector + extent_index = offset / s->extent_size; + extent_offset = (offset % s->extent_size) / 512; + + if (s->catalog_bitmap[extent_index] == 0xffffffff) { + return -1; /* not allocated */ + } + + bitmap_offset = s->data_offset + (512 * s->catalog_bitmap[extent_index] * + (s->extent_blocks + s->bitmap_blocks)); + + /* read in bitmap for current extent */ + if (bdrv_pread(bs->file, bitmap_offset + (extent_offset / 8), + &bitmap_entry, 1) != 1) { + return -1; + } + + if (!((bitmap_entry >> (extent_offset % 8)) & 1)) { + return -1; /* not allocated */ + } + + return bitmap_offset + (512 * (s->bitmap_blocks + extent_offset)); +} + +static int bochs_read(BlockDriverState *bs, int64_t sector_num, + uint8_t *buf, int nb_sectors) +{ + int ret; + + while (nb_sectors > 0) { + int64_t block_offset = seek_to_sector(bs, sector_num); + if (block_offset >= 0) { + ret = bdrv_pread(bs->file, block_offset, buf, 512); + if (ret != 512) { + return -1; + } + } else + memset(buf, 0, 512); + nb_sectors--; + sector_num++; + buf += 512; + } + return 0; +} + +static void bochs_close(BlockDriverState *bs) +{ + BDRVBochsState *s = bs->opaque; + qemu_free(s->catalog_bitmap); +} + +static BlockDriver bdrv_bochs = { + .format_name = "bochs", + .instance_size = sizeof(BDRVBochsState), + .bdrv_probe = bochs_probe, + .bdrv_open = bochs_open, + .bdrv_read = bochs_read, + .bdrv_close = bochs_close, +}; + +static void bdrv_bochs_init(void) +{ + bdrv_register(&bdrv_bochs); +} + +block_init(bdrv_bochs_init); diff --git a/block/cloop.c b/block/cloop.c new file mode 100644 index 0000000..fe015c4 --- /dev/null +++ b/block/cloop.c @@ -0,0 +1,171 @@ +/* + * QEMU Block driver for CLOOP images + * + * Copyright (c) 2004 Johannes E. Schindelin + * + * 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 "block_int.h" +#include "module.h" +#include + +typedef struct BDRVCloopState { + uint32_t block_size; + uint32_t n_blocks; + uint64_t* offsets; + uint32_t sectors_per_block; + uint32_t current_block; + uint8_t *compressed_block; + uint8_t *uncompressed_block; + z_stream zstream; +} BDRVCloopState; + +static int cloop_probe(const uint8_t *buf, int buf_size, const char *filename) +{ + const char* magic_version_2_0="#!/bin/sh\n" + "#V2.0 Format\n" + "modprobe cloop file=$0 && mount -r -t iso9660 /dev/cloop $1\n"; + int length=strlen(magic_version_2_0); + if(length>buf_size) + length=buf_size; + if(!memcmp(magic_version_2_0,buf,length)) + return 2; + return 0; +} + +static int cloop_open(BlockDriverState *bs, int flags) +{ + BDRVCloopState *s = bs->opaque; + uint32_t offsets_size,max_compressed_block_size=1,i; + + bs->read_only = 1; + + /* read header */ + if (bdrv_pread(bs->file, 128, &s->block_size, 4) < 4) { + goto cloop_close; + } + s->block_size = be32_to_cpu(s->block_size); + + if (bdrv_pread(bs->file, 128 + 4, &s->n_blocks, 4) < 4) { + goto cloop_close; + } + s->n_blocks = be32_to_cpu(s->n_blocks); + + /* read offsets */ + offsets_size = s->n_blocks * sizeof(uint64_t); + s->offsets = qemu_malloc(offsets_size); + if (bdrv_pread(bs->file, 128 + 4 + 4, s->offsets, offsets_size) < + offsets_size) { + goto cloop_close; + } + for(i=0;in_blocks;i++) { + s->offsets[i]=be64_to_cpu(s->offsets[i]); + if(i>0) { + uint32_t size=s->offsets[i]-s->offsets[i-1]; + if(size>max_compressed_block_size) + max_compressed_block_size=size; + } + } + + /* initialize zlib engine */ + s->compressed_block = qemu_malloc(max_compressed_block_size+1); + s->uncompressed_block = qemu_malloc(s->block_size); + if(inflateInit(&s->zstream) != Z_OK) + goto cloop_close; + s->current_block=s->n_blocks; + + s->sectors_per_block = s->block_size/512; + bs->total_sectors = s->n_blocks*s->sectors_per_block; + return 0; + +cloop_close: + return -1; +} + +static inline int cloop_read_block(BlockDriverState *bs, int block_num) +{ + BDRVCloopState *s = bs->opaque; + + if(s->current_block != block_num) { + int ret; + uint32_t bytes = s->offsets[block_num+1]-s->offsets[block_num]; + + ret = bdrv_pread(bs->file, s->offsets[block_num], s->compressed_block, + bytes); + if (ret != bytes) + return -1; + + s->zstream.next_in = s->compressed_block; + s->zstream.avail_in = bytes; + s->zstream.next_out = s->uncompressed_block; + s->zstream.avail_out = s->block_size; + ret = inflateReset(&s->zstream); + if(ret != Z_OK) + return -1; + ret = inflate(&s->zstream, Z_FINISH); + if(ret != Z_STREAM_END || s->zstream.total_out != s->block_size) + return -1; + + s->current_block = block_num; + } + return 0; +} + +static int cloop_read(BlockDriverState *bs, int64_t sector_num, + uint8_t *buf, int nb_sectors) +{ + BDRVCloopState *s = bs->opaque; + int i; + + for(i=0;isectors_per_block), + block_num=(sector_num+i)/s->sectors_per_block; + if(cloop_read_block(bs, block_num) != 0) + return -1; + memcpy(buf+i*512,s->uncompressed_block+sector_offset_in_block*512,512); + } + return 0; +} + +static void cloop_close(BlockDriverState *bs) +{ + BDRVCloopState *s = bs->opaque; + if(s->n_blocks>0) + free(s->offsets); + free(s->compressed_block); + free(s->uncompressed_block); + inflateEnd(&s->zstream); +} + +static BlockDriver bdrv_cloop = { + .format_name = "cloop", + .instance_size = sizeof(BDRVCloopState), + .bdrv_probe = cloop_probe, + .bdrv_open = cloop_open, + .bdrv_read = cloop_read, + .bdrv_close = cloop_close, +}; + +static void bdrv_cloop_init(void) +{ + bdrv_register(&bdrv_cloop); +} + +block_init(bdrv_cloop_init); diff --git a/block/cow.c b/block/cow.c new file mode 100644 index 0000000..4cf543c --- /dev/null +++ b/block/cow.c @@ -0,0 +1,324 @@ +/* + * Block driver for the COW format + * + * 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 "qemu-common.h" +#include "block_int.h" +#include "module.h" + +/**************************************************************/ +/* COW block driver using file system holes */ + +/* user mode linux compatible COW file */ +#define COW_MAGIC 0x4f4f4f4d /* MOOO */ +#define COW_VERSION 2 + +struct cow_header_v2 { + uint32_t magic; + uint32_t version; + char backing_file[1024]; + int32_t mtime; + uint64_t size; + uint32_t sectorsize; +}; + +typedef struct BDRVCowState { + int64_t cow_sectors_offset; +} BDRVCowState; + +static int cow_probe(const uint8_t *buf, int buf_size, const char *filename) +{ + const struct cow_header_v2 *cow_header = (const void *)buf; + + if (buf_size >= sizeof(struct cow_header_v2) && + be32_to_cpu(cow_header->magic) == COW_MAGIC && + be32_to_cpu(cow_header->version) == COW_VERSION) + return 100; + else + return 0; +} + +static int cow_open(BlockDriverState *bs, int flags) +{ + BDRVCowState *s = bs->opaque; + struct cow_header_v2 cow_header; + int bitmap_size; + int64_t size; + + /* see if it is a cow image */ + if (bdrv_pread(bs->file, 0, &cow_header, sizeof(cow_header)) != + sizeof(cow_header)) { + goto fail; + } + + if (be32_to_cpu(cow_header.magic) != COW_MAGIC || + be32_to_cpu(cow_header.version) != COW_VERSION) { + goto fail; + } + + /* cow image found */ + size = be64_to_cpu(cow_header.size); + bs->total_sectors = size / 512; + + pstrcpy(bs->backing_file, sizeof(bs->backing_file), + cow_header.backing_file); + + bitmap_size = ((bs->total_sectors + 7) >> 3) + sizeof(cow_header); + s->cow_sectors_offset = (bitmap_size + 511) & ~511; + return 0; + fail: + return -1; +} + +/* + * XXX(hch): right now these functions are extremly ineffcient. + * We should just read the whole bitmap we'll need in one go instead. + */ +static inline int cow_set_bit(BlockDriverState *bs, int64_t bitnum) +{ + uint64_t offset = sizeof(struct cow_header_v2) + bitnum / 8; + uint8_t bitmap; + int ret; + + ret = bdrv_pread(bs->file, offset, &bitmap, sizeof(bitmap)); + if (ret < 0) { + return ret; + } + + bitmap |= (1 << (bitnum % 8)); + + ret = bdrv_pwrite_sync(bs->file, offset, &bitmap, sizeof(bitmap)); + if (ret < 0) { + return ret; + } + return 0; +} + +static inline int is_bit_set(BlockDriverState *bs, int64_t bitnum) +{ + uint64_t offset = sizeof(struct cow_header_v2) + bitnum / 8; + uint8_t bitmap; + int ret; + + ret = bdrv_pread(bs->file, offset, &bitmap, sizeof(bitmap)); + if (ret < 0) { + return ret; + } + + return !!(bitmap & (1 << (bitnum % 8))); +} + +/* Return true if first block has been changed (ie. current version is + * in COW file). Set the number of continuous blocks for which that + * is true. */ +static int cow_is_allocated(BlockDriverState *bs, int64_t sector_num, + int nb_sectors, int *num_same) +{ + int changed; + + if (nb_sectors == 0) { + *num_same = nb_sectors; + return 0; + } + + changed = is_bit_set(bs, sector_num); + if (changed < 0) { + return 0; /* XXX: how to return I/O errors? */ + } + + for (*num_same = 1; *num_same < nb_sectors; (*num_same)++) { + if (is_bit_set(bs, sector_num + *num_same) != changed) + break; + } + + return changed; +} + +static int cow_update_bitmap(BlockDriverState *bs, int64_t sector_num, + int nb_sectors) +{ + int error = 0; + int i; + + for (i = 0; i < nb_sectors; i++) { + error = cow_set_bit(bs, sector_num + i); + if (error) { + break; + } + } + + return error; +} + +static int cow_read(BlockDriverState *bs, int64_t sector_num, + uint8_t *buf, int nb_sectors) +{ + BDRVCowState *s = bs->opaque; + int ret, n; + + while (nb_sectors > 0) { + if (cow_is_allocated(bs, sector_num, nb_sectors, &n)) { + ret = bdrv_pread(bs->file, + s->cow_sectors_offset + sector_num * 512, + buf, n * 512); + if (ret != n * 512) + return -1; + } else { + if (bs->backing_hd) { + /* read from the base image */ + ret = bdrv_read(bs->backing_hd, sector_num, buf, n); + if (ret < 0) + return -1; + } else { + memset(buf, 0, n * 512); + } + } + nb_sectors -= n; + sector_num += n; + buf += n * 512; + } + return 0; +} + +static int cow_write(BlockDriverState *bs, int64_t sector_num, + const uint8_t *buf, int nb_sectors) +{ + BDRVCowState *s = bs->opaque; + int ret; + + ret = bdrv_pwrite(bs->file, s->cow_sectors_offset + sector_num * 512, + buf, nb_sectors * 512); + if (ret != nb_sectors * 512) + return -1; + + return cow_update_bitmap(bs, sector_num, nb_sectors); +} + +static void cow_close(BlockDriverState *bs) +{ +} + +static int cow_create(const char *filename, QEMUOptionParameter *options) +{ + int fd, cow_fd; + struct cow_header_v2 cow_header; + struct stat st; + int64_t image_sectors = 0; + const char *image_filename = NULL; + int ret; + + /* Read out options */ + while (options && options->name) { + if (!strcmp(options->name, BLOCK_OPT_SIZE)) { + image_sectors = options->value.n / 512; + } else if (!strcmp(options->name, BLOCK_OPT_BACKING_FILE)) { + image_filename = options->value.s; + } + options++; + } + + cow_fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, + 0644); + if (cow_fd < 0) + return -errno; + memset(&cow_header, 0, sizeof(cow_header)); + cow_header.magic = cpu_to_be32(COW_MAGIC); + cow_header.version = cpu_to_be32(COW_VERSION); + if (image_filename) { + /* Note: if no file, we put a dummy mtime */ + cow_header.mtime = cpu_to_be32(0); + + fd = open(image_filename, O_RDONLY | O_BINARY); + if (fd < 0) { + close(cow_fd); + goto mtime_fail; + } + if (fstat(fd, &st) != 0) { + close(fd); + goto mtime_fail; + } + close(fd); + cow_header.mtime = cpu_to_be32(st.st_mtime); + mtime_fail: + pstrcpy(cow_header.backing_file, sizeof(cow_header.backing_file), + image_filename); + } + cow_header.sectorsize = cpu_to_be32(512); + cow_header.size = cpu_to_be64(image_sectors * 512); + ret = qemu_write_full(cow_fd, &cow_header, sizeof(cow_header)); + if (ret != sizeof(cow_header)) { + ret = -errno; + goto exit; + } + + /* resize to include at least all the bitmap */ + ret = ftruncate(cow_fd, sizeof(cow_header) + ((image_sectors + 7) >> 3)); + if (ret) { + ret = -errno; + goto exit; + } + +exit: + close(cow_fd); + return ret; +} + +static int cow_flush(BlockDriverState *bs) +{ + return bdrv_flush(bs->file); +} + +static QEMUOptionParameter cow_create_options[] = { + { + .name = BLOCK_OPT_SIZE, + .type = OPT_SIZE, + .help = "Virtual disk size" + }, + { + .name = BLOCK_OPT_BACKING_FILE, + .type = OPT_STRING, + .help = "File name of a base image" + }, + { NULL } +}; + +static BlockDriver bdrv_cow = { + .format_name = "cow", + .instance_size = sizeof(BDRVCowState), + .bdrv_probe = cow_probe, + .bdrv_open = cow_open, + .bdrv_read = cow_read, + .bdrv_write = cow_write, + .bdrv_close = cow_close, + .bdrv_create = cow_create, + .bdrv_flush = cow_flush, + .bdrv_is_allocated = cow_is_allocated, + + .create_options = cow_create_options, +}; + +static void bdrv_cow_init(void) +{ + bdrv_register(&bdrv_cow); +} + +block_init(bdrv_cow_init); diff --git a/block/curl.c b/block/curl.c new file mode 100644 index 0000000..407f095 --- /dev/null +++ b/block/curl.c @@ -0,0 +1,564 @@ +/* + * QEMU Block driver for CURL images + * + * Copyright (c) 2009 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. + */ +#include "qemu-common.h" +#include "block_int.h" +#include + +// #define DEBUG +// #define DEBUG_VERBOSE + +#ifdef DEBUG_CURL +#define DPRINTF(fmt, ...) do { printf(fmt, ## __VA_ARGS__); } while (0) +#else +#define DPRINTF(fmt, ...) do { } while (0) +#endif + +#define CURL_NUM_STATES 8 +#define CURL_NUM_ACB 8 +#define SECTOR_SIZE 512 +#define READ_AHEAD_SIZE (256 * 1024) + +#define FIND_RET_NONE 0 +#define FIND_RET_OK 1 +#define FIND_RET_WAIT 2 + +struct BDRVCURLState; + +typedef struct CURLAIOCB { + BlockDriverAIOCB common; + QEMUIOVector *qiov; + size_t start; + size_t end; +} CURLAIOCB; + +typedef struct CURLState +{ + struct BDRVCURLState *s; + CURLAIOCB *acb[CURL_NUM_ACB]; + CURL *curl; + char *orig_buf; + size_t buf_start; + size_t buf_off; + size_t buf_len; + char range[128]; + char errmsg[CURL_ERROR_SIZE]; + char in_use; +} CURLState; + +typedef struct BDRVCURLState { + CURLM *multi; + size_t len; + CURLState states[CURL_NUM_STATES]; + char *url; + size_t readahead_size; +} BDRVCURLState; + +static void curl_clean_state(CURLState *s); +static void curl_multi_do(void *arg); + +static int curl_sock_cb(CURL *curl, curl_socket_t fd, int action, + void *s, void *sp) +{ + DPRINTF("CURL (AIO): Sock action %d on fd %d\n", action, fd); + switch (action) { + case CURL_POLL_IN: + qemu_aio_set_fd_handler(fd, curl_multi_do, NULL, NULL, NULL, s); + break; + case CURL_POLL_OUT: + qemu_aio_set_fd_handler(fd, NULL, curl_multi_do, NULL, NULL, s); + break; + case CURL_POLL_INOUT: + qemu_aio_set_fd_handler(fd, curl_multi_do, + curl_multi_do, NULL, NULL, s); + break; + case CURL_POLL_REMOVE: + qemu_aio_set_fd_handler(fd, NULL, NULL, NULL, NULL, NULL); + break; + } + + return 0; +} + +static size_t curl_size_cb(void *ptr, size_t size, size_t nmemb, void *opaque) +{ + CURLState *s = ((CURLState*)opaque); + size_t realsize = size * nmemb; + size_t fsize; + + if(sscanf(ptr, "Content-Length: %zd", &fsize) == 1) { + s->s->len = fsize; + } + + return realsize; +} + +static size_t curl_read_cb(void *ptr, size_t size, size_t nmemb, void *opaque) +{ + CURLState *s = ((CURLState*)opaque); + size_t realsize = size * nmemb; + int i; + + DPRINTF("CURL: Just reading %zd bytes\n", realsize); + + if (!s || !s->orig_buf) + goto read_end; + + memcpy(s->orig_buf + s->buf_off, ptr, realsize); + s->buf_off += realsize; + + for(i=0; iacb[i]; + + if (!acb) + continue; + + if ((s->buf_off >= acb->end)) { + qemu_iovec_from_buffer(acb->qiov, s->orig_buf + acb->start, + acb->end - acb->start); + acb->common.cb(acb->common.opaque, 0); + qemu_aio_release(acb); + s->acb[i] = NULL; + } + } + +read_end: + return realsize; +} + +static int curl_find_buf(BDRVCURLState *s, size_t start, size_t len, + CURLAIOCB *acb) +{ + int i; + size_t end = start + len; + + for (i=0; istates[i]; + size_t buf_end = (state->buf_start + state->buf_off); + size_t buf_fend = (state->buf_start + state->buf_len); + + if (!state->orig_buf) + continue; + if (!state->buf_off) + continue; + + // Does the existing buffer cover our section? + if ((start >= state->buf_start) && + (start <= buf_end) && + (end >= state->buf_start) && + (end <= buf_end)) + { + char *buf = state->orig_buf + (start - state->buf_start); + + qemu_iovec_from_buffer(acb->qiov, buf, len); + acb->common.cb(acb->common.opaque, 0); + + return FIND_RET_OK; + } + + // Wait for unfinished chunks + if ((start >= state->buf_start) && + (start <= buf_fend) && + (end >= state->buf_start) && + (end <= buf_fend)) + { + int j; + + acb->start = start - state->buf_start; + acb->end = acb->start + len; + + for (j=0; jacb[j]) { + state->acb[j] = acb; + return FIND_RET_WAIT; + } + } + } + } + + return FIND_RET_NONE; +} + +static void curl_multi_do(void *arg) +{ + BDRVCURLState *s = (BDRVCURLState *)arg; + int running; + int r; + int msgs_in_queue; + + if (!s->multi) + return; + + do { + r = curl_multi_socket_all(s->multi, &running); + } while(r == CURLM_CALL_MULTI_PERFORM); + + /* Try to find done transfers, so we can free the easy + * handle again. */ + do { + CURLMsg *msg; + msg = curl_multi_info_read(s->multi, &msgs_in_queue); + + if (!msg) + break; + if (msg->msg == CURLMSG_NONE) + break; + + switch (msg->msg) { + case CURLMSG_DONE: + { + CURLState *state = NULL; + curl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, (char**)&state); + curl_clean_state(state); + break; + } + default: + msgs_in_queue = 0; + break; + } + } while(msgs_in_queue); +} + +static CURLState *curl_init_state(BDRVCURLState *s) +{ + CURLState *state = NULL; + int i, j; + + do { + for (i=0; istates[i].acb[j]) + continue; + if (s->states[i].in_use) + continue; + + state = &s->states[i]; + state->in_use = 1; + break; + } + if (!state) { + usleep(100); + curl_multi_do(s); + } + } while(!state); + + if (state->curl) + goto has_curl; + + state->curl = curl_easy_init(); + if (!state->curl) + return NULL; + curl_easy_setopt(state->curl, CURLOPT_URL, s->url); + curl_easy_setopt(state->curl, CURLOPT_TIMEOUT, 5); + curl_easy_setopt(state->curl, CURLOPT_WRITEFUNCTION, (void *)curl_read_cb); + curl_easy_setopt(state->curl, CURLOPT_WRITEDATA, (void *)state); + curl_easy_setopt(state->curl, CURLOPT_PRIVATE, (void *)state); + curl_easy_setopt(state->curl, CURLOPT_AUTOREFERER, 1); + curl_easy_setopt(state->curl, CURLOPT_FOLLOWLOCATION, 1); + curl_easy_setopt(state->curl, CURLOPT_NOSIGNAL, 1); + curl_easy_setopt(state->curl, CURLOPT_ERRORBUFFER, state->errmsg); + +#ifdef DEBUG_VERBOSE + curl_easy_setopt(state->curl, CURLOPT_VERBOSE, 1); +#endif + +has_curl: + + state->s = s; + + return state; +} + +static void curl_clean_state(CURLState *s) +{ + if (s->s->multi) + curl_multi_remove_handle(s->s->multi, s->curl); + s->in_use = 0; +} + +static int curl_open(BlockDriverState *bs, const char *filename, int flags) +{ + BDRVCURLState *s = bs->opaque; + CURLState *state = NULL; + double d; + + #define RA_OPTSTR ":readahead=" + char *file; + char *ra; + const char *ra_val; + int parse_state = 0; + + static int inited = 0; + + file = qemu_strdup(filename); + s->readahead_size = READ_AHEAD_SIZE; + + /* Parse a trailing ":readahead=#:" param, if present. */ + ra = file + strlen(file) - 1; + while (ra >= file) { + if (parse_state == 0) { + if (*ra == ':') + parse_state++; + else + break; + } else if (parse_state == 1) { + if (*ra > '9' || *ra < '0') { + char *opt_start = ra - strlen(RA_OPTSTR) + 1; + if (opt_start > file && + strncmp(opt_start, RA_OPTSTR, strlen(RA_OPTSTR)) == 0) { + ra_val = ra + 1; + ra -= strlen(RA_OPTSTR) - 1; + *ra = '\0'; + s->readahead_size = atoi(ra_val); + break; + } else { + break; + } + } + } + ra--; + } + + if ((s->readahead_size & 0x1ff) != 0) { + fprintf(stderr, "HTTP_READAHEAD_SIZE %zd is not a multiple of 512\n", + s->readahead_size); + goto out_noclean; + } + + if (!inited) { + curl_global_init(CURL_GLOBAL_ALL); + inited = 1; + } + + DPRINTF("CURL: Opening %s\n", file); + s->url = file; + state = curl_init_state(s); + if (!state) + goto out_noclean; + + // Get file size + + curl_easy_setopt(state->curl, CURLOPT_NOBODY, 1); + curl_easy_setopt(state->curl, CURLOPT_WRITEFUNCTION, (void *)curl_size_cb); + if (curl_easy_perform(state->curl)) + goto out; + curl_easy_getinfo(state->curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &d); + curl_easy_setopt(state->curl, CURLOPT_WRITEFUNCTION, (void *)curl_read_cb); + curl_easy_setopt(state->curl, CURLOPT_NOBODY, 0); + if (d) + s->len = (size_t)d; + else if(!s->len) + goto out; + DPRINTF("CURL: Size = %zd\n", s->len); + + curl_clean_state(state); + curl_easy_cleanup(state->curl); + state->curl = NULL; + + // Now we know the file exists and its size, so let's + // initialize the multi interface! + + s->multi = curl_multi_init(); + curl_multi_setopt( s->multi, CURLMOPT_SOCKETDATA, s); + curl_multi_setopt( s->multi, CURLMOPT_SOCKETFUNCTION, curl_sock_cb ); + curl_multi_do(s); + + return 0; + +out: + fprintf(stderr, "CURL: Error opening file: %s\n", state->errmsg); + curl_easy_cleanup(state->curl); + state->curl = NULL; +out_noclean: + qemu_free(file); + return -EINVAL; +} + +static void curl_aio_cancel(BlockDriverAIOCB *blockacb) +{ + // Do we have to implement canceling? Seems to work without... +} + +static AIOPool curl_aio_pool = { + .aiocb_size = sizeof(CURLAIOCB), + .cancel = curl_aio_cancel, +}; + +static BlockDriverAIOCB *curl_aio_readv(BlockDriverState *bs, + int64_t sector_num, QEMUIOVector *qiov, int nb_sectors, + BlockDriverCompletionFunc *cb, void *opaque) +{ + BDRVCURLState *s = bs->opaque; + CURLAIOCB *acb; + size_t start = sector_num * SECTOR_SIZE; + size_t end; + CURLState *state; + + acb = qemu_aio_get(&curl_aio_pool, bs, cb, opaque); + if (!acb) + return NULL; + + acb->qiov = qiov; + + // In case we have the requested data already (e.g. read-ahead), + // we can just call the callback and be done. + + switch (curl_find_buf(s, start, nb_sectors * SECTOR_SIZE, acb)) { + case FIND_RET_OK: + qemu_aio_release(acb); + // fall through + case FIND_RET_WAIT: + return &acb->common; + default: + break; + } + + // No cache found, so let's start a new request + + state = curl_init_state(s); + if (!state) + return NULL; + + acb->start = 0; + acb->end = (nb_sectors * SECTOR_SIZE); + + state->buf_off = 0; + if (state->orig_buf) + qemu_free(state->orig_buf); + state->buf_start = start; + state->buf_len = acb->end + s->readahead_size; + end = MIN(start + state->buf_len, s->len) - 1; + state->orig_buf = qemu_malloc(state->buf_len); + state->acb[0] = acb; + + snprintf(state->range, 127, "%zd-%zd", start, end); + DPRINTF("CURL (AIO): Reading %d at %zd (%s)\n", + (nb_sectors * SECTOR_SIZE), start, state->range); + curl_easy_setopt(state->curl, CURLOPT_RANGE, state->range); + + curl_multi_add_handle(s->multi, state->curl); + curl_multi_do(s); + + return &acb->common; +} + +static void curl_close(BlockDriverState *bs) +{ + BDRVCURLState *s = bs->opaque; + int i; + + DPRINTF("CURL: Close\n"); + for (i=0; istates[i].in_use) + curl_clean_state(&s->states[i]); + if (s->states[i].curl) { + curl_easy_cleanup(s->states[i].curl); + s->states[i].curl = NULL; + } + if (s->states[i].orig_buf) { + qemu_free(s->states[i].orig_buf); + s->states[i].orig_buf = NULL; + } + } + if (s->multi) + curl_multi_cleanup(s->multi); + if (s->url) + free(s->url); +} + +static int64_t curl_getlength(BlockDriverState *bs) +{ + BDRVCURLState *s = bs->opaque; + return s->len; +} + +static BlockDriver bdrv_http = { + .format_name = "http", + .protocol_name = "http", + + .instance_size = sizeof(BDRVCURLState), + .bdrv_file_open = curl_open, + .bdrv_close = curl_close, + .bdrv_getlength = curl_getlength, + + .bdrv_aio_readv = curl_aio_readv, +}; + +static BlockDriver bdrv_https = { + .format_name = "https", + .protocol_name = "https", + + .instance_size = sizeof(BDRVCURLState), + .bdrv_file_open = curl_open, + .bdrv_close = curl_close, + .bdrv_getlength = curl_getlength, + + .bdrv_aio_readv = curl_aio_readv, +}; + +static BlockDriver bdrv_ftp = { + .format_name = "ftp", + .protocol_name = "ftp", + + .instance_size = sizeof(BDRVCURLState), + .bdrv_file_open = curl_open, + .bdrv_close = curl_close, + .bdrv_getlength = curl_getlength, + + .bdrv_aio_readv = curl_aio_readv, +}; + +static BlockDriver bdrv_ftps = { + .format_name = "ftps", + .protocol_name = "ftps", + + .instance_size = sizeof(BDRVCURLState), + .bdrv_file_open = curl_open, + .bdrv_close = curl_close, + .bdrv_getlength = curl_getlength, + + .bdrv_aio_readv = curl_aio_readv, +}; + +static BlockDriver bdrv_tftp = { + .format_name = "tftp", + .protocol_name = "tftp", + + .instance_size = sizeof(BDRVCURLState), + .bdrv_file_open = curl_open, + .bdrv_close = curl_close, + .bdrv_getlength = curl_getlength, + + .bdrv_aio_readv = curl_aio_readv, +}; + +static void curl_block_init(void) +{ + bdrv_register(&bdrv_http); + bdrv_register(&bdrv_https); + bdrv_register(&bdrv_ftp); + bdrv_register(&bdrv_ftps); + bdrv_register(&bdrv_tftp); +} + +block_init(curl_block_init); diff --git a/block/dmg.c b/block/dmg.c new file mode 100644 index 0000000..a3c815b --- /dev/null +++ b/block/dmg.c @@ -0,0 +1,312 @@ +/* + * QEMU Block driver for DMG images + * + * Copyright (c) 2004 Johannes E. Schindelin + * + * 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 "block_int.h" +#include "bswap.h" +#include "module.h" +#include + +typedef struct BDRVDMGState { + /* each chunk contains a certain number of sectors, + * offsets[i] is the offset in the .dmg file, + * lengths[i] is the length of the compressed chunk, + * sectors[i] is the sector beginning at offsets[i], + * sectorcounts[i] is the number of sectors in that chunk, + * the sectors array is ordered + * 0<=i4 && !strcmp(filename+len-4,".dmg")) + return 2; + return 0; +} + +static off_t read_off(BlockDriverState *bs, int64_t offset) +{ + uint64_t buffer; + if (bdrv_pread(bs->file, offset, &buffer, 8) < 8) + return 0; + return be64_to_cpu(buffer); +} + +static off_t read_uint32(BlockDriverState *bs, int64_t offset) +{ + uint32_t buffer; + if (bdrv_pread(bs->file, offset, &buffer, 4) < 4) + return 0; + return be32_to_cpu(buffer); +} + +static int dmg_open(BlockDriverState *bs, int flags) +{ + BDRVDMGState *s = bs->opaque; + off_t info_begin,info_end,last_in_offset,last_out_offset; + uint32_t count; + uint32_t max_compressed_size=1,max_sectors_per_chunk=1,i; + int64_t offset; + + bs->read_only = 1; + s->n_chunks = 0; + s->offsets = s->lengths = s->sectors = s->sectorcounts = NULL; + + /* read offset of info blocks */ + offset = bdrv_getlength(bs->file); + if (offset < 0) { + goto fail; + } + offset -= 0x1d8; + + info_begin = read_off(bs, offset); + if (info_begin == 0) { + goto fail; + } + + if (read_uint32(bs, info_begin) != 0x100) { + goto fail; + } + + count = read_uint32(bs, info_begin + 4); + if (count == 0) { + goto fail; + } + info_end = info_begin + count; + + offset = info_begin + 0x100; + + /* read offsets */ + last_in_offset = last_out_offset = 0; + while (offset < info_end) { + uint32_t type; + + count = read_uint32(bs, offset); + if(count==0) + goto fail; + offset += 4; + + type = read_uint32(bs, offset); + if (type == 0x6d697368 && count >= 244) { + int new_size, chunk_count; + + offset += 4; + offset += 200; + + chunk_count = (count-204)/40; + new_size = sizeof(uint64_t) * (s->n_chunks + chunk_count); + s->types = qemu_realloc(s->types, new_size/2); + s->offsets = qemu_realloc(s->offsets, new_size); + s->lengths = qemu_realloc(s->lengths, new_size); + s->sectors = qemu_realloc(s->sectors, new_size); + s->sectorcounts = qemu_realloc(s->sectorcounts, new_size); + + for(i=s->n_chunks;in_chunks+chunk_count;i++) { + s->types[i] = read_uint32(bs, offset); + offset += 4; + if(s->types[i]!=0x80000005 && s->types[i]!=1 && s->types[i]!=2) { + if(s->types[i]==0xffffffff) { + last_in_offset = s->offsets[i-1]+s->lengths[i-1]; + last_out_offset = s->sectors[i-1]+s->sectorcounts[i-1]; + } + chunk_count--; + i--; + offset += 36; + continue; + } + offset += 4; + + s->sectors[i] = last_out_offset+read_off(bs, offset); + offset += 8; + + s->sectorcounts[i] = read_off(bs, offset); + offset += 8; + + s->offsets[i] = last_in_offset+read_off(bs, offset); + offset += 8; + + s->lengths[i] = read_off(bs, offset); + offset += 8; + + if(s->lengths[i]>max_compressed_size) + max_compressed_size = s->lengths[i]; + if(s->sectorcounts[i]>max_sectors_per_chunk) + max_sectors_per_chunk = s->sectorcounts[i]; + } + s->n_chunks+=chunk_count; + } + } + + /* initialize zlib engine */ + s->compressed_chunk = qemu_malloc(max_compressed_size+1); + s->uncompressed_chunk = qemu_malloc(512*max_sectors_per_chunk); + if(inflateInit(&s->zstream) != Z_OK) + goto fail; + + s->current_chunk = s->n_chunks; + + return 0; +fail: + return -1; +} + +static inline int is_sector_in_chunk(BDRVDMGState* s, + uint32_t chunk_num,int sector_num) +{ + if(chunk_num>=s->n_chunks || s->sectors[chunk_num]>sector_num || + s->sectors[chunk_num]+s->sectorcounts[chunk_num]<=sector_num) + return 0; + else + return -1; +} + +static inline uint32_t search_chunk(BDRVDMGState* s,int sector_num) +{ + /* binary search */ + uint32_t chunk1=0,chunk2=s->n_chunks,chunk3; + while(chunk1!=chunk2) { + chunk3 = (chunk1+chunk2)/2; + if(s->sectors[chunk3]>sector_num) + chunk2 = chunk3; + else if(s->sectors[chunk3]+s->sectorcounts[chunk3]>sector_num) + return chunk3; + else + chunk1 = chunk3; + } + return s->n_chunks; /* error */ +} + +static inline int dmg_read_chunk(BlockDriverState *bs, int sector_num) +{ + BDRVDMGState *s = bs->opaque; + + if(!is_sector_in_chunk(s,s->current_chunk,sector_num)) { + int ret; + uint32_t chunk = search_chunk(s,sector_num); + + if(chunk>=s->n_chunks) + return -1; + + s->current_chunk = s->n_chunks; + switch(s->types[chunk]) { + case 0x80000005: { /* zlib compressed */ + int i; + + /* we need to buffer, because only the chunk as whole can be + * inflated. */ + i=0; + do { + ret = bdrv_pread(bs->file, s->offsets[chunk] + i, + s->compressed_chunk+i, s->lengths[chunk]-i); + if(ret<0 && errno==EINTR) + ret=0; + i+=ret; + } while(ret>=0 && ret+ilengths[chunk]); + + if (ret != s->lengths[chunk]) + return -1; + + s->zstream.next_in = s->compressed_chunk; + s->zstream.avail_in = s->lengths[chunk]; + s->zstream.next_out = s->uncompressed_chunk; + s->zstream.avail_out = 512*s->sectorcounts[chunk]; + ret = inflateReset(&s->zstream); + if(ret != Z_OK) + return -1; + ret = inflate(&s->zstream, Z_FINISH); + if(ret != Z_STREAM_END || s->zstream.total_out != 512*s->sectorcounts[chunk]) + return -1; + break; } + case 1: /* copy */ + ret = bdrv_pread(bs->file, s->offsets[chunk], + s->uncompressed_chunk, s->lengths[chunk]); + if (ret != s->lengths[chunk]) + return -1; + break; + case 2: /* zero */ + memset(s->uncompressed_chunk, 0, 512*s->sectorcounts[chunk]); + break; + } + s->current_chunk = chunk; + } + return 0; +} + +static int dmg_read(BlockDriverState *bs, int64_t sector_num, + uint8_t *buf, int nb_sectors) +{ + BDRVDMGState *s = bs->opaque; + int i; + + for(i=0;isectors[s->current_chunk]; + memcpy(buf+i*512,s->uncompressed_chunk+sector_offset_in_chunk*512,512); + } + return 0; +} + +static void dmg_close(BlockDriverState *bs) +{ + BDRVDMGState *s = bs->opaque; + if(s->n_chunks>0) { + free(s->types); + free(s->offsets); + free(s->lengths); + free(s->sectors); + free(s->sectorcounts); + } + free(s->compressed_chunk); + free(s->uncompressed_chunk); + inflateEnd(&s->zstream); +} + +static BlockDriver bdrv_dmg = { + .format_name = "dmg", + .instance_size = sizeof(BDRVDMGState), + .bdrv_probe = dmg_probe, + .bdrv_open = dmg_open, + .bdrv_read = dmg_read, + .bdrv_close = dmg_close, +}; + +static void bdrv_dmg_init(void) +{ + bdrv_register(&bdrv_dmg); +} + +block_init(bdrv_dmg_init); diff --git a/block/nbd.c b/block/nbd.c new file mode 100644 index 0000000..c8dc763 --- /dev/null +++ b/block/nbd.c @@ -0,0 +1,221 @@ +/* + * QEMU Block driver for NBD + * + * Copyright (C) 2008 Bull S.A.S. + * Author: Laurent Vivier + * + * Some parts: + * Copyright (C) 2007 Anthony Liguori + * + * 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 "nbd.h" +#include "module.h" + +#include +#include + +#define EN_OPTSTR ":exportname=" + +typedef struct BDRVNBDState { + int sock; + off_t size; + size_t blocksize; +} BDRVNBDState; + +static int nbd_open(BlockDriverState *bs, const char* filename, int flags) +{ + BDRVNBDState *s = bs->opaque; + uint32_t nbdflags; + + char *file; + char *name; + const char *host; + const char *unixpath; + int sock; + off_t size; + size_t blocksize; + int ret; + int err = -EINVAL; + + file = qemu_strdup(filename); + + name = strstr(file, EN_OPTSTR); + if (name) { + if (name[strlen(EN_OPTSTR)] == 0) { + goto out; + } + name[0] = 0; + name += strlen(EN_OPTSTR); + } + + if (!strstart(file, "nbd:", &host)) { + goto out; + } + + if (strstart(host, "unix:", &unixpath)) { + + if (unixpath[0] != '/') { + goto out; + } + + sock = unix_socket_outgoing(unixpath); + + } else { + uint16_t port = NBD_DEFAULT_PORT; + char *p, *r; + char hostname[128]; + + pstrcpy(hostname, 128, host); + + p = strchr(hostname, ':'); + if (p != NULL) { + *p = '\0'; + p++; + + port = strtol(p, &r, 0); + if (r == p) { + goto out; + } + } + + sock = tcp_socket_outgoing(hostname, port); + } + + if (sock == -1) { + err = -errno; + goto out; + } + + ret = nbd_receive_negotiate(sock, name, &nbdflags, &size, &blocksize); + if (ret == -1) { + err = -errno; + goto out; + } + + s->sock = sock; + s->size = size; + s->blocksize = blocksize; + err = 0; + +out: + qemu_free(file); + return err; +} + +static int nbd_read(BlockDriverState *bs, int64_t sector_num, + uint8_t *buf, int nb_sectors) +{ + BDRVNBDState *s = bs->opaque; + struct nbd_request request; + struct nbd_reply reply; + + request.type = NBD_CMD_READ; + request.handle = (uint64_t)(intptr_t)bs; + request.from = sector_num * 512;; + request.len = nb_sectors * 512; + + if (nbd_send_request(s->sock, &request) == -1) + return -errno; + + if (nbd_receive_reply(s->sock, &reply) == -1) + return -errno; + + if (reply.error !=0) + return -reply.error; + + if (reply.handle != request.handle) + return -EIO; + + if (nbd_wr_sync(s->sock, buf, request.len, 1) != request.len) + return -EIO; + + return 0; +} + +static int nbd_write(BlockDriverState *bs, int64_t sector_num, + const uint8_t *buf, int nb_sectors) +{ + BDRVNBDState *s = bs->opaque; + struct nbd_request request; + struct nbd_reply reply; + + request.type = NBD_CMD_WRITE; + request.handle = (uint64_t)(intptr_t)bs; + request.from = sector_num * 512;; + request.len = nb_sectors * 512; + + if (nbd_send_request(s->sock, &request) == -1) + return -errno; + + if (nbd_wr_sync(s->sock, (uint8_t*)buf, request.len, 0) != request.len) + return -EIO; + + if (nbd_receive_reply(s->sock, &reply) == -1) + return -errno; + + if (reply.error !=0) + return -reply.error; + + if (reply.handle != request.handle) + return -EIO; + + return 0; +} + +static void nbd_close(BlockDriverState *bs) +{ + BDRVNBDState *s = bs->opaque; + struct nbd_request request; + + request.type = NBD_CMD_DISC; + request.handle = (uint64_t)(intptr_t)bs; + request.from = 0; + request.len = 0; + nbd_send_request(s->sock, &request); + + close(s->sock); +} + +static int64_t nbd_getlength(BlockDriverState *bs) +{ + BDRVNBDState *s = bs->opaque; + + return s->size; +} + +static BlockDriver bdrv_nbd = { + .format_name = "nbd", + .instance_size = sizeof(BDRVNBDState), + .bdrv_file_open = nbd_open, + .bdrv_read = nbd_read, + .bdrv_write = nbd_write, + .bdrv_close = nbd_close, + .bdrv_getlength = nbd_getlength, + .protocol_name = "nbd", +}; + +static void bdrv_nbd_init(void) +{ + bdrv_register(&bdrv_nbd); +} + +block_init(bdrv_nbd_init); diff --git a/block/parallels.c b/block/parallels.c new file mode 100644 index 0000000..35a14aa --- /dev/null +++ b/block/parallels.c @@ -0,0 +1,157 @@ +/* + * Block driver for Parallels disk image format + * + * Copyright (c) 2007 Alex Beregszaszi + * + * This code is based on comparing different disk images created by Parallels. + * + * 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 "block_int.h" +#include "module.h" + +/**************************************************************/ + +#define HEADER_MAGIC "WithoutFreeSpace" +#define HEADER_VERSION 2 +#define HEADER_SIZE 64 + +// always little-endian +struct parallels_header { + char magic[16]; // "WithoutFreeSpace" + uint32_t version; + uint32_t heads; + uint32_t cylinders; + uint32_t tracks; + uint32_t catalog_entries; + uint32_t nb_sectors; + char padding[24]; +} __attribute__((packed)); + +typedef struct BDRVParallelsState { + + uint32_t *catalog_bitmap; + int catalog_size; + + int tracks; +} BDRVParallelsState; + +static int parallels_probe(const uint8_t *buf, int buf_size, const char *filename) +{ + const struct parallels_header *ph = (const void *)buf; + + if (buf_size < HEADER_SIZE) + return 0; + + if (!memcmp(ph->magic, HEADER_MAGIC, 16) && + (le32_to_cpu(ph->version) == HEADER_VERSION)) + return 100; + + return 0; +} + +static int parallels_open(BlockDriverState *bs, int flags) +{ + BDRVParallelsState *s = bs->opaque; + int i; + struct parallels_header ph; + + bs->read_only = 1; // no write support yet + + if (bdrv_pread(bs->file, 0, &ph, sizeof(ph)) != sizeof(ph)) + goto fail; + + if (memcmp(ph.magic, HEADER_MAGIC, 16) || + (le32_to_cpu(ph.version) != HEADER_VERSION)) { + goto fail; + } + + bs->total_sectors = le32_to_cpu(ph.nb_sectors); + + s->tracks = le32_to_cpu(ph.tracks); + + s->catalog_size = le32_to_cpu(ph.catalog_entries); + s->catalog_bitmap = qemu_malloc(s->catalog_size * 4); + if (bdrv_pread(bs->file, 64, s->catalog_bitmap, s->catalog_size * 4) != + s->catalog_size * 4) + goto fail; + for (i = 0; i < s->catalog_size; i++) + le32_to_cpus(&s->catalog_bitmap[i]); + + return 0; +fail: + if (s->catalog_bitmap) + qemu_free(s->catalog_bitmap); + return -1; +} + +static int64_t seek_to_sector(BlockDriverState *bs, int64_t sector_num) +{ + BDRVParallelsState *s = bs->opaque; + uint32_t index, offset; + + index = sector_num / s->tracks; + offset = sector_num % s->tracks; + + /* not allocated */ + if ((index > s->catalog_size) || (s->catalog_bitmap[index] == 0)) + return -1; + return (uint64_t)(s->catalog_bitmap[index] + offset) * 512; +} + +static int parallels_read(BlockDriverState *bs, int64_t sector_num, + uint8_t *buf, int nb_sectors) +{ + while (nb_sectors > 0) { + int64_t position = seek_to_sector(bs, sector_num); + if (position >= 0) { + if (bdrv_pread(bs->file, position, buf, 512) != 512) + return -1; + } else { + memset(buf, 0, 512); + } + nb_sectors--; + sector_num++; + buf += 512; + } + return 0; +} + +static void parallels_close(BlockDriverState *bs) +{ + BDRVParallelsState *s = bs->opaque; + qemu_free(s->catalog_bitmap); +} + +static BlockDriver bdrv_parallels = { + .format_name = "parallels", + .instance_size = sizeof(BDRVParallelsState), + .bdrv_probe = parallels_probe, + .bdrv_open = parallels_open, + .bdrv_read = parallels_read, + .bdrv_close = parallels_close, +}; + +static void bdrv_parallels_init(void) +{ + bdrv_register(&bdrv_parallels); +} + +block_init(bdrv_parallels_init); diff --git a/block/qcow.c b/block/qcow.c new file mode 100644 index 0000000..f67d3d3 --- /dev/null +++ b/block/qcow.c @@ -0,0 +1,975 @@ +/* + * Block driver for the QCOW format + * + * Copyright (c) 2004-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 "qemu-common.h" +#include "block_int.h" +#include "module.h" +#include +#include "aes.h" + +/**************************************************************/ +/* QEMU COW block driver with compression and encryption support */ + +#define QCOW_MAGIC (('Q' << 24) | ('F' << 16) | ('I' << 8) | 0xfb) +#define QCOW_VERSION 1 + +#define QCOW_CRYPT_NONE 0 +#define QCOW_CRYPT_AES 1 + +#define QCOW_OFLAG_COMPRESSED (1LL << 63) + +typedef struct QCowHeader { + uint32_t magic; + uint32_t version; + uint64_t backing_file_offset; + uint32_t backing_file_size; + uint32_t mtime; + uint64_t size; /* in bytes */ + uint8_t cluster_bits; + uint8_t l2_bits; + uint32_t crypt_method; + uint64_t l1_table_offset; +} QCowHeader; + +#define L2_CACHE_SIZE 16 + +typedef struct BDRVQcowState { + int cluster_bits; + int cluster_size; + int cluster_sectors; + int l2_bits; + int l2_size; + int l1_size; + uint64_t cluster_offset_mask; + uint64_t l1_table_offset; + uint64_t *l1_table; + uint64_t *l2_cache; + uint64_t l2_cache_offsets[L2_CACHE_SIZE]; + uint32_t l2_cache_counts[L2_CACHE_SIZE]; + uint8_t *cluster_cache; + uint8_t *cluster_data; + uint64_t cluster_cache_offset; + uint32_t crypt_method; /* current crypt method, 0 if no key yet */ + uint32_t crypt_method_header; + AES_KEY aes_encrypt_key; + AES_KEY aes_decrypt_key; +} BDRVQcowState; + +static int decompress_cluster(BlockDriverState *bs, uint64_t cluster_offset); + +static int qcow_probe(const uint8_t *buf, int buf_size, const char *filename) +{ + const QCowHeader *cow_header = (const void *)buf; + + if (buf_size >= sizeof(QCowHeader) && + be32_to_cpu(cow_header->magic) == QCOW_MAGIC && + be32_to_cpu(cow_header->version) == QCOW_VERSION) + return 100; + else + return 0; +} + +static int qcow_open(BlockDriverState *bs, int flags) +{ + BDRVQcowState *s = bs->opaque; + int len, i, shift; + QCowHeader header; + + if (bdrv_pread(bs->file, 0, &header, sizeof(header)) != sizeof(header)) + goto fail; + be32_to_cpus(&header.magic); + be32_to_cpus(&header.version); + be64_to_cpus(&header.backing_file_offset); + be32_to_cpus(&header.backing_file_size); + be32_to_cpus(&header.mtime); + be64_to_cpus(&header.size); + be32_to_cpus(&header.crypt_method); + be64_to_cpus(&header.l1_table_offset); + + if (header.magic != QCOW_MAGIC || header.version != QCOW_VERSION) + goto fail; + if (header.size <= 1 || header.cluster_bits < 9) + goto fail; + if (header.crypt_method > QCOW_CRYPT_AES) + goto fail; + s->crypt_method_header = header.crypt_method; + if (s->crypt_method_header) + bs->encrypted = 1; + s->cluster_bits = header.cluster_bits; + s->cluster_size = 1 << s->cluster_bits; + s->cluster_sectors = 1 << (s->cluster_bits - 9); + s->l2_bits = header.l2_bits; + s->l2_size = 1 << s->l2_bits; + bs->total_sectors = header.size / 512; + s->cluster_offset_mask = (1LL << (63 - s->cluster_bits)) - 1; + + /* read the level 1 table */ + shift = s->cluster_bits + s->l2_bits; + s->l1_size = (header.size + (1LL << shift) - 1) >> shift; + + s->l1_table_offset = header.l1_table_offset; + s->l1_table = qemu_malloc(s->l1_size * sizeof(uint64_t)); + if (!s->l1_table) + goto fail; + if (bdrv_pread(bs->file, s->l1_table_offset, s->l1_table, s->l1_size * sizeof(uint64_t)) != + s->l1_size * sizeof(uint64_t)) + goto fail; + for(i = 0;i < s->l1_size; i++) { + be64_to_cpus(&s->l1_table[i]); + } + /* alloc L2 cache */ + s->l2_cache = qemu_malloc(s->l2_size * L2_CACHE_SIZE * sizeof(uint64_t)); + if (!s->l2_cache) + goto fail; + s->cluster_cache = qemu_malloc(s->cluster_size); + if (!s->cluster_cache) + goto fail; + s->cluster_data = qemu_malloc(s->cluster_size); + if (!s->cluster_data) + goto fail; + s->cluster_cache_offset = -1; + + /* read the backing file name */ + if (header.backing_file_offset != 0) { + len = header.backing_file_size; + if (len > 1023) + len = 1023; + if (bdrv_pread(bs->file, header.backing_file_offset, bs->backing_file, len) != len) + goto fail; + bs->backing_file[len] = '\0'; + } + return 0; + + fail: + qemu_free(s->l1_table); + qemu_free(s->l2_cache); + qemu_free(s->cluster_cache); + qemu_free(s->cluster_data); + return -1; +} + +static int qcow_set_key(BlockDriverState *bs, const char *key) +{ + BDRVQcowState *s = bs->opaque; + uint8_t keybuf[16]; + int len, i; + + memset(keybuf, 0, 16); + len = strlen(key); + if (len > 16) + len = 16; + /* XXX: we could compress the chars to 7 bits to increase + entropy */ + for(i = 0;i < len;i++) { + keybuf[i] = key[i]; + } + s->crypt_method = s->crypt_method_header; + + if (AES_set_encrypt_key(keybuf, 128, &s->aes_encrypt_key) != 0) + return -1; + if (AES_set_decrypt_key(keybuf, 128, &s->aes_decrypt_key) != 0) + return -1; +#if 0 + /* test */ + { + uint8_t in[16]; + uint8_t out[16]; + uint8_t tmp[16]; + for(i=0;i<16;i++) + in[i] = i; + AES_encrypt(in, tmp, &s->aes_encrypt_key); + AES_decrypt(tmp, out, &s->aes_decrypt_key); + for(i = 0; i < 16; i++) + printf(" %02x", tmp[i]); + printf("\n"); + for(i = 0; i < 16; i++) + printf(" %02x", out[i]); + printf("\n"); + } +#endif + return 0; +} + +/* The crypt function is compatible with the linux cryptoloop + algorithm for < 4 GB images. NOTE: out_buf == in_buf is + supported */ +static void encrypt_sectors(BDRVQcowState *s, int64_t sector_num, + uint8_t *out_buf, const uint8_t *in_buf, + int nb_sectors, int enc, + const AES_KEY *key) +{ + union { + uint64_t ll[2]; + uint8_t b[16]; + } ivec; + int i; + + for(i = 0; i < nb_sectors; i++) { + ivec.ll[0] = cpu_to_le64(sector_num); + ivec.ll[1] = 0; + AES_cbc_encrypt(in_buf, out_buf, 512, key, + ivec.b, enc); + sector_num++; + in_buf += 512; + out_buf += 512; + } +} + +/* 'allocate' is: + * + * 0 to not allocate. + * + * 1 to allocate a normal cluster (for sector indexes 'n_start' to + * 'n_end') + * + * 2 to allocate a compressed cluster of size + * 'compressed_size'. 'compressed_size' must be > 0 and < + * cluster_size + * + * return 0 if not allocated. + */ +static uint64_t get_cluster_offset(BlockDriverState *bs, + uint64_t offset, int allocate, + int compressed_size, + int n_start, int n_end) +{ + BDRVQcowState *s = bs->opaque; + int min_index, i, j, l1_index, l2_index; + uint64_t l2_offset, *l2_table, cluster_offset, tmp; + uint32_t min_count; + int new_l2_table; + + l1_index = offset >> (s->l2_bits + s->cluster_bits); + l2_offset = s->l1_table[l1_index]; + new_l2_table = 0; + if (!l2_offset) { + if (!allocate) + return 0; + /* allocate a new l2 entry */ + l2_offset = bdrv_getlength(bs->file); + /* round to cluster size */ + l2_offset = (l2_offset + s->cluster_size - 1) & ~(s->cluster_size - 1); + /* update the L1 entry */ + s->l1_table[l1_index] = l2_offset; + tmp = cpu_to_be64(l2_offset); + if (bdrv_pwrite_sync(bs->file, + s->l1_table_offset + l1_index * sizeof(tmp), + &tmp, sizeof(tmp)) < 0) + return 0; + new_l2_table = 1; + } + for(i = 0; i < L2_CACHE_SIZE; i++) { + if (l2_offset == s->l2_cache_offsets[i]) { + /* increment the hit count */ + if (++s->l2_cache_counts[i] == 0xffffffff) { + for(j = 0; j < L2_CACHE_SIZE; j++) { + s->l2_cache_counts[j] >>= 1; + } + } + l2_table = s->l2_cache + (i << s->l2_bits); + goto found; + } + } + /* not found: load a new entry in the least used one */ + min_index = 0; + min_count = 0xffffffff; + for(i = 0; i < L2_CACHE_SIZE; i++) { + if (s->l2_cache_counts[i] < min_count) { + min_count = s->l2_cache_counts[i]; + min_index = i; + } + } + l2_table = s->l2_cache + (min_index << s->l2_bits); + if (new_l2_table) { + memset(l2_table, 0, s->l2_size * sizeof(uint64_t)); + if (bdrv_pwrite_sync(bs->file, l2_offset, l2_table, + s->l2_size * sizeof(uint64_t)) < 0) + return 0; + } else { + if (bdrv_pread(bs->file, l2_offset, l2_table, s->l2_size * sizeof(uint64_t)) != + s->l2_size * sizeof(uint64_t)) + return 0; + } + s->l2_cache_offsets[min_index] = l2_offset; + s->l2_cache_counts[min_index] = 1; + found: + l2_index = (offset >> s->cluster_bits) & (s->l2_size - 1); + cluster_offset = be64_to_cpu(l2_table[l2_index]); + if (!cluster_offset || + ((cluster_offset & QCOW_OFLAG_COMPRESSED) && allocate == 1)) { + if (!allocate) + return 0; + /* allocate a new cluster */ + if ((cluster_offset & QCOW_OFLAG_COMPRESSED) && + (n_end - n_start) < s->cluster_sectors) { + /* if the cluster is already compressed, we must + decompress it in the case it is not completely + overwritten */ + if (decompress_cluster(bs, cluster_offset) < 0) + return 0; + cluster_offset = bdrv_getlength(bs->file); + cluster_offset = (cluster_offset + s->cluster_size - 1) & + ~(s->cluster_size - 1); + /* write the cluster content */ + if (bdrv_pwrite(bs->file, cluster_offset, s->cluster_cache, s->cluster_size) != + s->cluster_size) + return -1; + } else { + cluster_offset = bdrv_getlength(bs->file); + if (allocate == 1) { + /* round to cluster size */ + cluster_offset = (cluster_offset + s->cluster_size - 1) & + ~(s->cluster_size - 1); + bdrv_truncate(bs->file, cluster_offset + s->cluster_size); + /* if encrypted, we must initialize the cluster + content which won't be written */ + if (s->crypt_method && + (n_end - n_start) < s->cluster_sectors) { + uint64_t start_sect; + start_sect = (offset & ~(s->cluster_size - 1)) >> 9; + memset(s->cluster_data + 512, 0x00, 512); + for(i = 0; i < s->cluster_sectors; i++) { + if (i < n_start || i >= n_end) { + encrypt_sectors(s, start_sect + i, + s->cluster_data, + s->cluster_data + 512, 1, 1, + &s->aes_encrypt_key); + if (bdrv_pwrite(bs->file, cluster_offset + i * 512, + s->cluster_data, 512) != 512) + return -1; + } + } + } + } else if (allocate == 2) { + cluster_offset |= QCOW_OFLAG_COMPRESSED | + (uint64_t)compressed_size << (63 - s->cluster_bits); + } + } + /* update L2 table */ + tmp = cpu_to_be64(cluster_offset); + l2_table[l2_index] = tmp; + if (bdrv_pwrite_sync(bs->file, l2_offset + l2_index * sizeof(tmp), + &tmp, sizeof(tmp)) < 0) + return 0; + } + return cluster_offset; +} + +static int qcow_is_allocated(BlockDriverState *bs, int64_t sector_num, + int nb_sectors, int *pnum) +{ + BDRVQcowState *s = bs->opaque; + int index_in_cluster, n; + uint64_t cluster_offset; + + cluster_offset = get_cluster_offset(bs, sector_num << 9, 0, 0, 0, 0); + index_in_cluster = sector_num & (s->cluster_sectors - 1); + n = s->cluster_sectors - index_in_cluster; + if (n > nb_sectors) + n = nb_sectors; + *pnum = n; + return (cluster_offset != 0); +} + +static int decompress_buffer(uint8_t *out_buf, int out_buf_size, + const uint8_t *buf, int buf_size) +{ + z_stream strm1, *strm = &strm1; + int ret, out_len; + + memset(strm, 0, sizeof(*strm)); + + strm->next_in = (uint8_t *)buf; + strm->avail_in = buf_size; + strm->next_out = out_buf; + strm->avail_out = out_buf_size; + + ret = inflateInit2(strm, -12); + if (ret != Z_OK) + return -1; + ret = inflate(strm, Z_FINISH); + out_len = strm->next_out - out_buf; + if ((ret != Z_STREAM_END && ret != Z_BUF_ERROR) || + out_len != out_buf_size) { + inflateEnd(strm); + return -1; + } + inflateEnd(strm); + return 0; +} + +static int decompress_cluster(BlockDriverState *bs, uint64_t cluster_offset) +{ + BDRVQcowState *s = bs->opaque; + int ret, csize; + uint64_t coffset; + + coffset = cluster_offset & s->cluster_offset_mask; + if (s->cluster_cache_offset != coffset) { + csize = cluster_offset >> (63 - s->cluster_bits); + csize &= (s->cluster_size - 1); + ret = bdrv_pread(bs->file, coffset, s->cluster_data, csize); + if (ret != csize) + return -1; + if (decompress_buffer(s->cluster_cache, s->cluster_size, + s->cluster_data, csize) < 0) { + return -1; + } + s->cluster_cache_offset = coffset; + } + return 0; +} + +#if 0 + +static int qcow_read(BlockDriverState *bs, int64_t sector_num, + uint8_t *buf, int nb_sectors) +{ + BDRVQcowState *s = bs->opaque; + int ret, index_in_cluster, n; + uint64_t cluster_offset; + + while (nb_sectors > 0) { + cluster_offset = get_cluster_offset(bs, sector_num << 9, 0, 0, 0, 0); + index_in_cluster = sector_num & (s->cluster_sectors - 1); + n = s->cluster_sectors - index_in_cluster; + if (n > nb_sectors) + n = nb_sectors; + if (!cluster_offset) { + if (bs->backing_hd) { + /* read from the base image */ + ret = bdrv_read(bs->backing_hd, sector_num, buf, n); + if (ret < 0) + return -1; + } else { + memset(buf, 0, 512 * n); + } + } else if (cluster_offset & QCOW_OFLAG_COMPRESSED) { + if (decompress_cluster(bs, cluster_offset) < 0) + return -1; + memcpy(buf, s->cluster_cache + index_in_cluster * 512, 512 * n); + } else { + ret = bdrv_pread(bs->file, cluster_offset + index_in_cluster * 512, buf, n * 512); + if (ret != n * 512) + return -1; + if (s->crypt_method) { + encrypt_sectors(s, sector_num, buf, buf, n, 0, + &s->aes_decrypt_key); + } + } + nb_sectors -= n; + sector_num += n; + buf += n * 512; + } + return 0; +} +#endif + +typedef struct QCowAIOCB { + BlockDriverAIOCB common; + int64_t sector_num; + QEMUIOVector *qiov; + uint8_t *buf; + void *orig_buf; + int nb_sectors; + int n; + uint64_t cluster_offset; + uint8_t *cluster_data; + struct iovec hd_iov; + QEMUIOVector hd_qiov; + BlockDriverAIOCB *hd_aiocb; +} QCowAIOCB; + +static void qcow_aio_cancel(BlockDriverAIOCB *blockacb) +{ + QCowAIOCB *acb = container_of(blockacb, QCowAIOCB, common); + if (acb->hd_aiocb) + bdrv_aio_cancel(acb->hd_aiocb); + qemu_aio_release(acb); +} + +static AIOPool qcow_aio_pool = { + .aiocb_size = sizeof(QCowAIOCB), + .cancel = qcow_aio_cancel, +}; + +static QCowAIOCB *qcow_aio_setup(BlockDriverState *bs, + int64_t sector_num, QEMUIOVector *qiov, int nb_sectors, + BlockDriverCompletionFunc *cb, void *opaque, int is_write) +{ + QCowAIOCB *acb; + + acb = qemu_aio_get(&qcow_aio_pool, bs, cb, opaque); + if (!acb) + return NULL; + acb->hd_aiocb = NULL; + acb->sector_num = sector_num; + acb->qiov = qiov; + if (qiov->niov > 1) { + acb->buf = acb->orig_buf = qemu_blockalign(bs, qiov->size); + if (is_write) + qemu_iovec_to_buffer(qiov, acb->buf); + } else { + acb->buf = (uint8_t *)qiov->iov->iov_base; + } + acb->nb_sectors = nb_sectors; + acb->n = 0; + acb->cluster_offset = 0; + return acb; +} + +static void qcow_aio_read_cb(void *opaque, int ret) +{ + QCowAIOCB *acb = opaque; + BlockDriverState *bs = acb->common.bs; + BDRVQcowState *s = bs->opaque; + int index_in_cluster; + + acb->hd_aiocb = NULL; + if (ret < 0) + goto done; + + redo: + /* post process the read buffer */ + if (!acb->cluster_offset) { + /* nothing to do */ + } else if (acb->cluster_offset & QCOW_OFLAG_COMPRESSED) { + /* nothing to do */ + } else { + if (s->crypt_method) { + encrypt_sectors(s, acb->sector_num, acb->buf, acb->buf, + acb->n, 0, + &s->aes_decrypt_key); + } + } + + acb->nb_sectors -= acb->n; + acb->sector_num += acb->n; + acb->buf += acb->n * 512; + + if (acb->nb_sectors == 0) { + /* request completed */ + ret = 0; + goto done; + } + + /* prepare next AIO request */ + acb->cluster_offset = get_cluster_offset(bs, acb->sector_num << 9, + 0, 0, 0, 0); + index_in_cluster = acb->sector_num & (s->cluster_sectors - 1); + acb->n = s->cluster_sectors - index_in_cluster; + if (acb->n > acb->nb_sectors) + acb->n = acb->nb_sectors; + + if (!acb->cluster_offset) { + if (bs->backing_hd) { + /* read from the base image */ + acb->hd_iov.iov_base = (void *)acb->buf; + acb->hd_iov.iov_len = acb->n * 512; + qemu_iovec_init_external(&acb->hd_qiov, &acb->hd_iov, 1); + acb->hd_aiocb = bdrv_aio_readv(bs->backing_hd, acb->sector_num, + &acb->hd_qiov, acb->n, qcow_aio_read_cb, acb); + if (acb->hd_aiocb == NULL) + goto done; + } else { + /* Note: in this case, no need to wait */ + memset(acb->buf, 0, 512 * acb->n); + goto redo; + } + } else if (acb->cluster_offset & QCOW_OFLAG_COMPRESSED) { + /* add AIO support for compressed blocks ? */ + if (decompress_cluster(bs, acb->cluster_offset) < 0) + goto done; + memcpy(acb->buf, + s->cluster_cache + index_in_cluster * 512, 512 * acb->n); + goto redo; + } else { + if ((acb->cluster_offset & 511) != 0) { + ret = -EIO; + goto done; + } + acb->hd_iov.iov_base = (void *)acb->buf; + acb->hd_iov.iov_len = acb->n * 512; + qemu_iovec_init_external(&acb->hd_qiov, &acb->hd_iov, 1); + acb->hd_aiocb = bdrv_aio_readv(bs->file, + (acb->cluster_offset >> 9) + index_in_cluster, + &acb->hd_qiov, acb->n, qcow_aio_read_cb, acb); + if (acb->hd_aiocb == NULL) + goto done; + } + + return; + +done: + if (acb->qiov->niov > 1) { + qemu_iovec_from_buffer(acb->qiov, acb->orig_buf, acb->qiov->size); + qemu_vfree(acb->orig_buf); + } + acb->common.cb(acb->common.opaque, ret); + qemu_aio_release(acb); +} + +static BlockDriverAIOCB *qcow_aio_readv(BlockDriverState *bs, + int64_t sector_num, QEMUIOVector *qiov, int nb_sectors, + BlockDriverCompletionFunc *cb, void *opaque) +{ + QCowAIOCB *acb; + + acb = qcow_aio_setup(bs, sector_num, qiov, nb_sectors, cb, opaque, 0); + if (!acb) + return NULL; + + qcow_aio_read_cb(acb, 0); + return &acb->common; +} + +static void qcow_aio_write_cb(void *opaque, int ret) +{ + QCowAIOCB *acb = opaque; + BlockDriverState *bs = acb->common.bs; + BDRVQcowState *s = bs->opaque; + int index_in_cluster; + uint64_t cluster_offset; + const uint8_t *src_buf; + + acb->hd_aiocb = NULL; + + if (ret < 0) + goto done; + + acb->nb_sectors -= acb->n; + acb->sector_num += acb->n; + acb->buf += acb->n * 512; + + if (acb->nb_sectors == 0) { + /* request completed */ + ret = 0; + goto done; + } + + index_in_cluster = acb->sector_num & (s->cluster_sectors - 1); + acb->n = s->cluster_sectors - index_in_cluster; + if (acb->n > acb->nb_sectors) + acb->n = acb->nb_sectors; + cluster_offset = get_cluster_offset(bs, acb->sector_num << 9, 1, 0, + index_in_cluster, + index_in_cluster + acb->n); + if (!cluster_offset || (cluster_offset & 511) != 0) { + ret = -EIO; + goto done; + } + if (s->crypt_method) { + if (!acb->cluster_data) { + acb->cluster_data = qemu_mallocz(s->cluster_size); + if (!acb->cluster_data) { + ret = -ENOMEM; + goto done; + } + } + encrypt_sectors(s, acb->sector_num, acb->cluster_data, acb->buf, + acb->n, 1, &s->aes_encrypt_key); + src_buf = acb->cluster_data; + } else { + src_buf = acb->buf; + } + + acb->hd_iov.iov_base = (void *)src_buf; + acb->hd_iov.iov_len = acb->n * 512; + qemu_iovec_init_external(&acb->hd_qiov, &acb->hd_iov, 1); + acb->hd_aiocb = bdrv_aio_writev(bs->file, + (cluster_offset >> 9) + index_in_cluster, + &acb->hd_qiov, acb->n, + qcow_aio_write_cb, acb); + if (acb->hd_aiocb == NULL) + goto done; + return; + +done: + if (acb->qiov->niov > 1) + qemu_vfree(acb->orig_buf); + acb->common.cb(acb->common.opaque, ret); + qemu_aio_release(acb); +} + +static BlockDriverAIOCB *qcow_aio_writev(BlockDriverState *bs, + int64_t sector_num, QEMUIOVector *qiov, int nb_sectors, + BlockDriverCompletionFunc *cb, void *opaque) +{ + BDRVQcowState *s = bs->opaque; + QCowAIOCB *acb; + + s->cluster_cache_offset = -1; /* disable compressed cache */ + + acb = qcow_aio_setup(bs, sector_num, qiov, nb_sectors, cb, opaque, 1); + if (!acb) + return NULL; + + + qcow_aio_write_cb(acb, 0); + return &acb->common; +} + +static void qcow_close(BlockDriverState *bs) +{ + BDRVQcowState *s = bs->opaque; + qemu_free(s->l1_table); + qemu_free(s->l2_cache); + qemu_free(s->cluster_cache); + qemu_free(s->cluster_data); +} + +static int qcow_create(const char *filename, QEMUOptionParameter *options) +{ + int fd, header_size, backing_filename_len, l1_size, i, shift; + QCowHeader header; + uint64_t tmp; + int64_t total_size = 0; + const char *backing_file = NULL; + int flags = 0; + int ret; + + /* Read out options */ + while (options && options->name) { + if (!strcmp(options->name, BLOCK_OPT_SIZE)) { + total_size = options->value.n / 512; + } else if (!strcmp(options->name, BLOCK_OPT_BACKING_FILE)) { + backing_file = options->value.s; + } else if (!strcmp(options->name, BLOCK_OPT_ENCRYPT)) { + flags |= options->value.n ? BLOCK_FLAG_ENCRYPT : 0; + } + options++; + } + + fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0644); + if (fd < 0) + return -errno; + memset(&header, 0, sizeof(header)); + header.magic = cpu_to_be32(QCOW_MAGIC); + header.version = cpu_to_be32(QCOW_VERSION); + header.size = cpu_to_be64(total_size * 512); + header_size = sizeof(header); + backing_filename_len = 0; + if (backing_file) { + if (strcmp(backing_file, "fat:")) { + header.backing_file_offset = cpu_to_be64(header_size); + backing_filename_len = strlen(backing_file); + header.backing_file_size = cpu_to_be32(backing_filename_len); + header_size += backing_filename_len; + } else { + /* special backing file for vvfat */ + backing_file = NULL; + } + header.cluster_bits = 9; /* 512 byte cluster to avoid copying + unmodifyed sectors */ + header.l2_bits = 12; /* 32 KB L2 tables */ + } else { + header.cluster_bits = 12; /* 4 KB clusters */ + header.l2_bits = 9; /* 4 KB L2 tables */ + } + header_size = (header_size + 7) & ~7; + shift = header.cluster_bits + header.l2_bits; + l1_size = ((total_size * 512) + (1LL << shift) - 1) >> shift; + + header.l1_table_offset = cpu_to_be64(header_size); + if (flags & BLOCK_FLAG_ENCRYPT) { + header.crypt_method = cpu_to_be32(QCOW_CRYPT_AES); + } else { + header.crypt_method = cpu_to_be32(QCOW_CRYPT_NONE); + } + + /* write all the data */ + ret = qemu_write_full(fd, &header, sizeof(header)); + if (ret != sizeof(header)) { + ret = -errno; + goto exit; + } + + if (backing_file) { + ret = qemu_write_full(fd, backing_file, backing_filename_len); + if (ret != backing_filename_len) { + ret = -errno; + goto exit; + } + + } + lseek(fd, header_size, SEEK_SET); + tmp = 0; + for(i = 0;i < l1_size; i++) { + ret = qemu_write_full(fd, &tmp, sizeof(tmp)); + if (ret != sizeof(tmp)) { + ret = -errno; + goto exit; + } + } + + ret = 0; +exit: + close(fd); + return ret; +} + +static int qcow_make_empty(BlockDriverState *bs) +{ + BDRVQcowState *s = bs->opaque; + uint32_t l1_length = s->l1_size * sizeof(uint64_t); + int ret; + + memset(s->l1_table, 0, l1_length); + if (bdrv_pwrite_sync(bs->file, s->l1_table_offset, s->l1_table, + l1_length) < 0) + return -1; + ret = bdrv_truncate(bs->file, s->l1_table_offset + l1_length); + if (ret < 0) + return ret; + + memset(s->l2_cache, 0, s->l2_size * L2_CACHE_SIZE * sizeof(uint64_t)); + memset(s->l2_cache_offsets, 0, L2_CACHE_SIZE * sizeof(uint64_t)); + memset(s->l2_cache_counts, 0, L2_CACHE_SIZE * sizeof(uint32_t)); + + return 0; +} + +/* XXX: put compressed sectors first, then all the cluster aligned + tables to avoid losing bytes in alignment */ +static int qcow_write_compressed(BlockDriverState *bs, int64_t sector_num, + const uint8_t *buf, int nb_sectors) +{ + BDRVQcowState *s = bs->opaque; + z_stream strm; + int ret, out_len; + uint8_t *out_buf; + uint64_t cluster_offset; + + if (nb_sectors != s->cluster_sectors) + return -EINVAL; + + out_buf = qemu_malloc(s->cluster_size + (s->cluster_size / 1000) + 128); + if (!out_buf) + return -1; + + /* best compression, small window, no zlib header */ + memset(&strm, 0, sizeof(strm)); + ret = deflateInit2(&strm, Z_DEFAULT_COMPRESSION, + Z_DEFLATED, -12, + 9, Z_DEFAULT_STRATEGY); + if (ret != 0) { + qemu_free(out_buf); + return -1; + } + + strm.avail_in = s->cluster_size; + strm.next_in = (uint8_t *)buf; + strm.avail_out = s->cluster_size; + strm.next_out = out_buf; + + ret = deflate(&strm, Z_FINISH); + if (ret != Z_STREAM_END && ret != Z_OK) { + qemu_free(out_buf); + deflateEnd(&strm); + return -1; + } + out_len = strm.next_out - out_buf; + + deflateEnd(&strm); + + if (ret != Z_STREAM_END || out_len >= s->cluster_size) { + /* could not compress: write normal cluster */ + bdrv_write(bs, sector_num, buf, s->cluster_sectors); + } else { + cluster_offset = get_cluster_offset(bs, sector_num << 9, 2, + out_len, 0, 0); + cluster_offset &= s->cluster_offset_mask; + if (bdrv_pwrite(bs->file, cluster_offset, out_buf, out_len) != out_len) { + qemu_free(out_buf); + return -1; + } + } + + qemu_free(out_buf); + return 0; +} + +static int qcow_flush(BlockDriverState *bs) +{ + return bdrv_flush(bs->file); +} + +static BlockDriverAIOCB *qcow_aio_flush(BlockDriverState *bs, + BlockDriverCompletionFunc *cb, void *opaque) +{ + return bdrv_aio_flush(bs->file, cb, opaque); +} + +static int qcow_get_info(BlockDriverState *bs, BlockDriverInfo *bdi) +{ + BDRVQcowState *s = bs->opaque; + bdi->cluster_size = s->cluster_size; + return 0; +} + + +static QEMUOptionParameter qcow_create_options[] = { + { + .name = BLOCK_OPT_SIZE, + .type = OPT_SIZE, + .help = "Virtual disk size" + }, + { + .name = BLOCK_OPT_BACKING_FILE, + .type = OPT_STRING, + .help = "File name of a base image" + }, + { + .name = BLOCK_OPT_ENCRYPT, + .type = OPT_FLAG, + .help = "Encrypt the image" + }, + { NULL } +}; + +static BlockDriver bdrv_qcow = { + .format_name = "qcow", + .instance_size = sizeof(BDRVQcowState), + .bdrv_probe = qcow_probe, + .bdrv_open = qcow_open, + .bdrv_close = qcow_close, + .bdrv_create = qcow_create, + .bdrv_flush = qcow_flush, + .bdrv_is_allocated = qcow_is_allocated, + .bdrv_set_key = qcow_set_key, + .bdrv_make_empty = qcow_make_empty, + .bdrv_aio_readv = qcow_aio_readv, + .bdrv_aio_writev = qcow_aio_writev, + .bdrv_aio_flush = qcow_aio_flush, + .bdrv_write_compressed = qcow_write_compressed, + .bdrv_get_info = qcow_get_info, + + .create_options = qcow_create_options, +}; + +static void bdrv_qcow_init(void) +{ + bdrv_register(&bdrv_qcow); +} + +block_init(bdrv_qcow_init); diff --git a/block/qcow2-cache.c b/block/qcow2-cache.c new file mode 100644 index 0000000..3824739 --- /dev/null +++ b/block/qcow2-cache.c @@ -0,0 +1,314 @@ +/* + * L2/refcount table cache for the QCOW2 format + * + * Copyright (c) 2010 Kevin Wolf + * + * 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 "block_int.h" +#include "qemu-common.h" +#include "qcow2.h" + +typedef struct Qcow2CachedTable { + void* table; + int64_t offset; + bool dirty; + int cache_hits; + int ref; +} Qcow2CachedTable; + +struct Qcow2Cache { + Qcow2CachedTable* entries; + struct Qcow2Cache* depends; + int size; + bool depends_on_flush; + bool writethrough; +}; + +Qcow2Cache *qcow2_cache_create(BlockDriverState *bs, int num_tables, + bool writethrough) +{ + BDRVQcowState *s = bs->opaque; + Qcow2Cache *c; + int i; + + c = qemu_mallocz(sizeof(*c)); + c->size = num_tables; + c->entries = qemu_mallocz(sizeof(*c->entries) * num_tables); + c->writethrough = writethrough; + + for (i = 0; i < c->size; i++) { + c->entries[i].table = qemu_blockalign(bs, s->cluster_size); + } + + return c; +} + +int qcow2_cache_destroy(BlockDriverState* bs, Qcow2Cache *c) +{ + int i; + + for (i = 0; i < c->size; i++) { + assert(c->entries[i].ref == 0); + qemu_vfree(c->entries[i].table); + } + + qemu_free(c->entries); + qemu_free(c); + + return 0; +} + +static int qcow2_cache_flush_dependency(BlockDriverState *bs, Qcow2Cache *c) +{ + int ret; + + ret = qcow2_cache_flush(bs, c->depends); + if (ret < 0) { + return ret; + } + + c->depends = NULL; + c->depends_on_flush = false; + + return 0; +} + +static int qcow2_cache_entry_flush(BlockDriverState *bs, Qcow2Cache *c, int i) +{ + BDRVQcowState *s = bs->opaque; + int ret = 0; + + if (!c->entries[i].dirty || !c->entries[i].offset) { + return 0; + } + + if (c->depends) { + ret = qcow2_cache_flush_dependency(bs, c); + } else if (c->depends_on_flush) { + ret = bdrv_flush(bs->file); + if (ret >= 0) { + c->depends_on_flush = false; + } + } + + if (ret < 0) { + return ret; + } + + if (c == s->refcount_block_cache) { + BLKDBG_EVENT(bs->file, BLKDBG_REFBLOCK_UPDATE_PART); + } else if (c == s->l2_table_cache) { + BLKDBG_EVENT(bs->file, BLKDBG_L2_UPDATE); + } + + ret = bdrv_pwrite(bs->file, c->entries[i].offset, c->entries[i].table, + s->cluster_size); + if (ret < 0) { + return ret; + } + + c->entries[i].dirty = false; + + return 0; +} + +int qcow2_cache_flush(BlockDriverState *bs, Qcow2Cache *c) +{ + int result = 0; + int ret; + int i; + + for (i = 0; i < c->size; i++) { + ret = qcow2_cache_entry_flush(bs, c, i); + if (ret < 0 && result != -ENOSPC) { + result = ret; + } + } + + if (result == 0) { + ret = bdrv_flush(bs->file); + if (ret < 0) { + result = ret; + } + } + + return result; +} + +int qcow2_cache_set_dependency(BlockDriverState *bs, Qcow2Cache *c, + Qcow2Cache *dependency) +{ + int ret; + + if (dependency->depends) { + ret = qcow2_cache_flush_dependency(bs, dependency); + if (ret < 0) { + return ret; + } + } + + if (c->depends && (c->depends != dependency)) { + ret = qcow2_cache_flush_dependency(bs, c); + if (ret < 0) { + return ret; + } + } + + c->depends = dependency; + return 0; +} + +void qcow2_cache_depends_on_flush(Qcow2Cache *c) +{ + c->depends_on_flush = true; +} + +static int qcow2_cache_find_entry_to_replace(Qcow2Cache *c) +{ + int i; + int min_count = INT_MAX; + int min_index = -1; + + + for (i = 0; i < c->size; i++) { + if (c->entries[i].ref) { + continue; + } + + if (c->entries[i].cache_hits < min_count) { + min_index = i; + min_count = c->entries[i].cache_hits; + } + + /* Give newer hits priority */ + /* TODO Check how to optimize the replacement strategy */ + c->entries[i].cache_hits /= 2; + } + + if (min_index == -1) { + /* This can't happen in current synchronous code, but leave the check + * here as a reminder for whoever starts using AIO with the cache */ + abort(); + } + return min_index; +} + +static int qcow2_cache_do_get(BlockDriverState *bs, Qcow2Cache *c, + uint64_t offset, void **table, bool read_from_disk) +{ + BDRVQcowState *s = bs->opaque; + int i; + int ret; + + /* Check if the table is already cached */ + for (i = 0; i < c->size; i++) { + if (c->entries[i].offset == offset) { + goto found; + } + } + + /* If not, write a table back and replace it */ + i = qcow2_cache_find_entry_to_replace(c); + if (i < 0) { + return i; + } + + ret = qcow2_cache_entry_flush(bs, c, i); + if (ret < 0) { + return ret; + } + + c->entries[i].offset = 0; + if (read_from_disk) { + if (c == s->l2_table_cache) { + BLKDBG_EVENT(bs->file, BLKDBG_L2_LOAD); + } + + ret = bdrv_pread(bs->file, offset, c->entries[i].table, s->cluster_size); + if (ret < 0) { + return ret; + } + } + + /* Give the table some hits for the start so that it won't be replaced + * immediately. The number 32 is completely arbitrary. */ + c->entries[i].cache_hits = 32; + c->entries[i].offset = offset; + + /* And return the right table */ +found: + c->entries[i].cache_hits++; + c->entries[i].ref++; + *table = c->entries[i].table; + return 0; +} + +int qcow2_cache_get(BlockDriverState *bs, Qcow2Cache *c, uint64_t offset, + void **table) +{ + return qcow2_cache_do_get(bs, c, offset, table, true); +} + +int qcow2_cache_get_empty(BlockDriverState *bs, Qcow2Cache *c, uint64_t offset, + void **table) +{ + return qcow2_cache_do_get(bs, c, offset, table, false); +} + +int qcow2_cache_put(BlockDriverState *bs, Qcow2Cache *c, void **table) +{ + int i; + + for (i = 0; i < c->size; i++) { + if (c->entries[i].table == *table) { + goto found; + } + } + return -ENOENT; + +found: + c->entries[i].ref--; + *table = NULL; + + assert(c->entries[i].ref >= 0); + + if (c->writethrough) { + return qcow2_cache_entry_flush(bs, c, i); + } else { + return 0; + } +} + +void qcow2_cache_entry_mark_dirty(Qcow2Cache *c, void *table) +{ + int i; + + for (i = 0; i < c->size; i++) { + if (c->entries[i].table == table) { + goto found; + } + } + abort(); + +found: + c->entries[i].dirty = true; +} + diff --git a/block/qcow2-cluster.c b/block/qcow2-cluster.c new file mode 100644 index 0000000..750abe3 --- /dev/null +++ b/block/qcow2-cluster.c @@ -0,0 +1,975 @@ +/* + * Block driver for the QCOW version 2 format + * + * Copyright (c) 2004-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 + +#include "qemu-common.h" +#include "block_int.h" +#include "block/qcow2.h" + +int qcow2_grow_l1_table(BlockDriverState *bs, int min_size, bool exact_size) +{ + BDRVQcowState *s = bs->opaque; + int new_l1_size, new_l1_size2, ret, i; + uint64_t *new_l1_table; + int64_t new_l1_table_offset; + uint8_t data[12]; + + if (min_size <= s->l1_size) + return 0; + + if (exact_size) { + new_l1_size = min_size; + } else { + /* Bump size up to reduce the number of times we have to grow */ + new_l1_size = s->l1_size; + if (new_l1_size == 0) { + new_l1_size = 1; + } + while (min_size > new_l1_size) { + new_l1_size = (new_l1_size * 3 + 1) / 2; + } + } + +#ifdef DEBUG_ALLOC2 + printf("grow l1_table from %d to %d\n", s->l1_size, new_l1_size); +#endif + + new_l1_size2 = sizeof(uint64_t) * new_l1_size; + new_l1_table = qemu_mallocz(align_offset(new_l1_size2, 512)); + memcpy(new_l1_table, s->l1_table, s->l1_size * sizeof(uint64_t)); + + /* write new table (align to cluster) */ + BLKDBG_EVENT(bs->file, BLKDBG_L1_GROW_ALLOC_TABLE); + new_l1_table_offset = qcow2_alloc_clusters(bs, new_l1_size2); + if (new_l1_table_offset < 0) { + qemu_free(new_l1_table); + return new_l1_table_offset; + } + + ret = qcow2_cache_flush(bs, s->refcount_block_cache); + if (ret < 0) { + return ret; + } + + BLKDBG_EVENT(bs->file, BLKDBG_L1_GROW_WRITE_TABLE); + for(i = 0; i < s->l1_size; i++) + new_l1_table[i] = cpu_to_be64(new_l1_table[i]); + ret = bdrv_pwrite_sync(bs->file, new_l1_table_offset, new_l1_table, new_l1_size2); + if (ret < 0) + goto fail; + for(i = 0; i < s->l1_size; i++) + new_l1_table[i] = be64_to_cpu(new_l1_table[i]); + + /* set new table */ + BLKDBG_EVENT(bs->file, BLKDBG_L1_GROW_ACTIVATE_TABLE); + cpu_to_be32w((uint32_t*)data, new_l1_size); + cpu_to_be64wu((uint64_t*)(data + 4), new_l1_table_offset); + ret = bdrv_pwrite_sync(bs->file, offsetof(QCowHeader, l1_size), data,sizeof(data)); + if (ret < 0) { + goto fail; + } + qemu_free(s->l1_table); + qcow2_free_clusters(bs, s->l1_table_offset, s->l1_size * sizeof(uint64_t)); + s->l1_table_offset = new_l1_table_offset; + s->l1_table = new_l1_table; + s->l1_size = new_l1_size; + return 0; + fail: + qemu_free(new_l1_table); + qcow2_free_clusters(bs, new_l1_table_offset, new_l1_size2); + return ret; +} + +/* + * l2_load + * + * Loads a L2 table into memory. If the table is in the cache, the cache + * is used; otherwise the L2 table is loaded from the image file. + * + * Returns a pointer to the L2 table on success, or NULL if the read from + * the image file failed. + */ + +static int l2_load(BlockDriverState *bs, uint64_t l2_offset, + uint64_t **l2_table) +{ + BDRVQcowState *s = bs->opaque; + int ret; + + ret = qcow2_cache_get(bs, s->l2_table_cache, l2_offset, (void**) l2_table); + + return ret; +} + +/* + * Writes one sector of the L1 table to the disk (can't update single entries + * and we really don't want bdrv_pread to perform a read-modify-write) + */ +#define L1_ENTRIES_PER_SECTOR (512 / 8) +static int write_l1_entry(BlockDriverState *bs, int l1_index) +{ + BDRVQcowState *s = bs->opaque; + uint64_t buf[L1_ENTRIES_PER_SECTOR]; + int l1_start_index; + int i, ret; + + l1_start_index = l1_index & ~(L1_ENTRIES_PER_SECTOR - 1); + for (i = 0; i < L1_ENTRIES_PER_SECTOR; i++) { + buf[i] = cpu_to_be64(s->l1_table[l1_start_index + i]); + } + + BLKDBG_EVENT(bs->file, BLKDBG_L1_UPDATE); + ret = bdrv_pwrite_sync(bs->file, s->l1_table_offset + 8 * l1_start_index, + buf, sizeof(buf)); + if (ret < 0) { + return ret; + } + + return 0; +} + +/* + * l2_allocate + * + * Allocate a new l2 entry in the file. If l1_index points to an already + * used entry in the L2 table (i.e. we are doing a copy on write for the L2 + * table) copy the contents of the old L2 table into the newly allocated one. + * Otherwise the new table is initialized with zeros. + * + */ + +static int l2_allocate(BlockDriverState *bs, int l1_index, uint64_t **table) +{ + BDRVQcowState *s = bs->opaque; + uint64_t old_l2_offset; + uint64_t *l2_table; + int64_t l2_offset; + int ret; + + old_l2_offset = s->l1_table[l1_index]; + + /* allocate a new l2 entry */ + + l2_offset = qcow2_alloc_clusters(bs, s->l2_size * sizeof(uint64_t)); + if (l2_offset < 0) { + return l2_offset; + } + + ret = qcow2_cache_flush(bs, s->refcount_block_cache); + if (ret < 0) { + goto fail; + } + + /* allocate a new entry in the l2 cache */ + + ret = qcow2_cache_get_empty(bs, s->l2_table_cache, l2_offset, (void**) table); + if (ret < 0) { + return ret; + } + + l2_table = *table; + + if (old_l2_offset == 0) { + /* if there was no old l2 table, clear the new table */ + memset(l2_table, 0, s->l2_size * sizeof(uint64_t)); + } else { + uint64_t* old_table; + + /* if there was an old l2 table, read it from the disk */ + BLKDBG_EVENT(bs->file, BLKDBG_L2_ALLOC_COW_READ); + ret = qcow2_cache_get(bs, s->l2_table_cache, old_l2_offset, + (void**) &old_table); + if (ret < 0) { + goto fail; + } + + memcpy(l2_table, old_table, s->cluster_size); + + ret = qcow2_cache_put(bs, s->l2_table_cache, (void**) &old_table); + if (ret < 0) { + goto fail; + } + } + + /* write the l2 table to the file */ + BLKDBG_EVENT(bs->file, BLKDBG_L2_ALLOC_WRITE); + + qcow2_cache_entry_mark_dirty(s->l2_table_cache, l2_table); + ret = qcow2_cache_flush(bs, s->l2_table_cache); + if (ret < 0) { + goto fail; + } + + /* update the L1 entry */ + s->l1_table[l1_index] = l2_offset | QCOW_OFLAG_COPIED; + ret = write_l1_entry(bs, l1_index); + if (ret < 0) { + goto fail; + } + + *table = l2_table; + return 0; + +fail: + qcow2_cache_put(bs, s->l2_table_cache, (void**) table); + s->l1_table[l1_index] = old_l2_offset; + return ret; +} + +static int count_contiguous_clusters(uint64_t nb_clusters, int cluster_size, + uint64_t *l2_table, uint64_t start, uint64_t mask) +{ + int i; + uint64_t offset = be64_to_cpu(l2_table[0]) & ~mask; + + if (!offset) + return 0; + + for (i = start; i < start + nb_clusters; i++) + if (offset + (uint64_t) i * cluster_size != (be64_to_cpu(l2_table[i]) & ~mask)) + break; + + return (i - start); +} + +static int count_contiguous_free_clusters(uint64_t nb_clusters, uint64_t *l2_table) +{ + int i = 0; + + while(nb_clusters-- && l2_table[i] == 0) + i++; + + return i; +} + +/* The crypt function is compatible with the linux cryptoloop + algorithm for < 4 GB images. NOTE: out_buf == in_buf is + supported */ +void qcow2_encrypt_sectors(BDRVQcowState *s, int64_t sector_num, + uint8_t *out_buf, const uint8_t *in_buf, + int nb_sectors, int enc, + const AES_KEY *key) +{ + union { + uint64_t ll[2]; + uint8_t b[16]; + } ivec; + int i; + + for(i = 0; i < nb_sectors; i++) { + ivec.ll[0] = cpu_to_le64(sector_num); + ivec.ll[1] = 0; + AES_cbc_encrypt(in_buf, out_buf, 512, key, + ivec.b, enc); + sector_num++; + in_buf += 512; + out_buf += 512; + } +} + + +static int qcow2_read(BlockDriverState *bs, int64_t sector_num, + uint8_t *buf, int nb_sectors) +{ + BDRVQcowState *s = bs->opaque; + int ret, index_in_cluster, n, n1; + uint64_t cluster_offset; + struct iovec iov; + QEMUIOVector qiov; + + while (nb_sectors > 0) { + n = nb_sectors; + + ret = qcow2_get_cluster_offset(bs, sector_num << 9, &n, + &cluster_offset); + if (ret < 0) { + return ret; + } + + index_in_cluster = sector_num & (s->cluster_sectors - 1); + if (!cluster_offset) { + if (bs->backing_hd) { + /* read from the base image */ + iov.iov_base = buf; + iov.iov_len = n * 512; + qemu_iovec_init_external(&qiov, &iov, 1); + + n1 = qcow2_backing_read1(bs->backing_hd, &qiov, sector_num, n); + if (n1 > 0) { + BLKDBG_EVENT(bs->file, BLKDBG_READ_BACKING); + ret = bdrv_read(bs->backing_hd, sector_num, buf, n1); + if (ret < 0) + return -1; + } + } else { + memset(buf, 0, 512 * n); + } + } else if (cluster_offset & QCOW_OFLAG_COMPRESSED) { + if (qcow2_decompress_cluster(bs, cluster_offset) < 0) + return -1; + memcpy(buf, s->cluster_cache + index_in_cluster * 512, 512 * n); + } else { + BLKDBG_EVENT(bs->file, BLKDBG_READ); + ret = bdrv_pread(bs->file, cluster_offset + index_in_cluster * 512, buf, n * 512); + if (ret != n * 512) + return -1; + if (s->crypt_method) { + qcow2_encrypt_sectors(s, sector_num, buf, buf, n, 0, + &s->aes_decrypt_key); + } + } + nb_sectors -= n; + sector_num += n; + buf += n * 512; + } + return 0; +} + +static int copy_sectors(BlockDriverState *bs, uint64_t start_sect, + uint64_t cluster_offset, int n_start, int n_end) +{ + BDRVQcowState *s = bs->opaque; + int n, ret; + + n = n_end - n_start; + if (n <= 0) + return 0; + BLKDBG_EVENT(bs->file, BLKDBG_COW_READ); + ret = qcow2_read(bs, start_sect + n_start, s->cluster_data, n); + if (ret < 0) + return ret; + if (s->crypt_method) { + qcow2_encrypt_sectors(s, start_sect + n_start, + s->cluster_data, + s->cluster_data, n, 1, + &s->aes_encrypt_key); + } + BLKDBG_EVENT(bs->file, BLKDBG_COW_WRITE); + ret = bdrv_write(bs->file, (cluster_offset >> 9) + n_start, + s->cluster_data, n); + if (ret < 0) + return ret; + return 0; +} + + +/* + * get_cluster_offset + * + * For a given offset of the disk image, find the cluster offset in + * qcow2 file. The offset is stored in *cluster_offset. + * + * on entry, *num is the number of contiguous clusters we'd like to + * access following offset. + * + * on exit, *num is the number of contiguous clusters we can read. + * + * Return 0, if the offset is found + * Return -errno, otherwise. + * + */ + +int qcow2_get_cluster_offset(BlockDriverState *bs, uint64_t offset, + int *num, uint64_t *cluster_offset) +{ + BDRVQcowState *s = bs->opaque; + unsigned int l1_index, l2_index; + uint64_t l2_offset, *l2_table; + int l1_bits, c; + unsigned int index_in_cluster, nb_clusters; + uint64_t nb_available, nb_needed; + int ret; + + index_in_cluster = (offset >> 9) & (s->cluster_sectors - 1); + nb_needed = *num + index_in_cluster; + + l1_bits = s->l2_bits + s->cluster_bits; + + /* compute how many bytes there are between the offset and + * the end of the l1 entry + */ + + nb_available = (1ULL << l1_bits) - (offset & ((1ULL << l1_bits) - 1)); + + /* compute the number of available sectors */ + + nb_available = (nb_available >> 9) + index_in_cluster; + + if (nb_needed > nb_available) { + nb_needed = nb_available; + } + + *cluster_offset = 0; + + /* seek the the l2 offset in the l1 table */ + + l1_index = offset >> l1_bits; + if (l1_index >= s->l1_size) + goto out; + + l2_offset = s->l1_table[l1_index]; + + /* seek the l2 table of the given l2 offset */ + + if (!l2_offset) + goto out; + + /* load the l2 table in memory */ + + l2_offset &= ~QCOW_OFLAG_COPIED; + ret = l2_load(bs, l2_offset, &l2_table); + if (ret < 0) { + return ret; + } + + /* find the cluster offset for the given disk offset */ + + l2_index = (offset >> s->cluster_bits) & (s->l2_size - 1); + *cluster_offset = be64_to_cpu(l2_table[l2_index]); + nb_clusters = size_to_clusters(s, nb_needed << 9); + + if (!*cluster_offset) { + /* how many empty clusters ? */ + c = count_contiguous_free_clusters(nb_clusters, &l2_table[l2_index]); + } else { + /* how many allocated clusters ? */ + c = count_contiguous_clusters(nb_clusters, s->cluster_size, + &l2_table[l2_index], 0, QCOW_OFLAG_COPIED); + } + + qcow2_cache_put(bs, s->l2_table_cache, (void**) &l2_table); + + nb_available = (c * s->cluster_sectors); +out: + if (nb_available > nb_needed) + nb_available = nb_needed; + + *num = nb_available - index_in_cluster; + + *cluster_offset &=~QCOW_OFLAG_COPIED; + return 0; +} + +/* + * get_cluster_table + * + * for a given disk offset, load (and allocate if needed) + * the l2 table. + * + * the l2 table offset in the qcow2 file and the cluster index + * in the l2 table are given to the caller. + * + * Returns 0 on success, -errno in failure case + */ +static int get_cluster_table(BlockDriverState *bs, uint64_t offset, + uint64_t **new_l2_table, + uint64_t *new_l2_offset, + int *new_l2_index) +{ + BDRVQcowState *s = bs->opaque; + unsigned int l1_index, l2_index; + uint64_t l2_offset; + uint64_t *l2_table = NULL; + int ret; + + /* seek the the l2 offset in the l1 table */ + + l1_index = offset >> (s->l2_bits + s->cluster_bits); + if (l1_index >= s->l1_size) { + ret = qcow2_grow_l1_table(bs, l1_index + 1, false); + if (ret < 0) { + return ret; + } + } + l2_offset = s->l1_table[l1_index]; + + /* seek the l2 table of the given l2 offset */ + + if (l2_offset & QCOW_OFLAG_COPIED) { + /* load the l2 table in memory */ + l2_offset &= ~QCOW_OFLAG_COPIED; + ret = l2_load(bs, l2_offset, &l2_table); + if (ret < 0) { + return ret; + } + } else { + /* First allocate a new L2 table (and do COW if needed) */ + ret = l2_allocate(bs, l1_index, &l2_table); + if (ret < 0) { + return ret; + } + + /* Then decrease the refcount of the old table */ + if (l2_offset) { + qcow2_free_clusters(bs, l2_offset, s->l2_size * sizeof(uint64_t)); + } + l2_offset = s->l1_table[l1_index] & ~QCOW_OFLAG_COPIED; + } + + /* find the cluster offset for the given disk offset */ + + l2_index = (offset >> s->cluster_bits) & (s->l2_size - 1); + + *new_l2_table = l2_table; + *new_l2_offset = l2_offset; + *new_l2_index = l2_index; + + return 0; +} + +/* + * alloc_compressed_cluster_offset + * + * For a given offset of the disk image, return cluster offset in + * qcow2 file. + * + * If the offset is not found, allocate a new compressed cluster. + * + * Return the cluster offset if successful, + * Return 0, otherwise. + * + */ + +uint64_t qcow2_alloc_compressed_cluster_offset(BlockDriverState *bs, + uint64_t offset, + int compressed_size) +{ + BDRVQcowState *s = bs->opaque; + int l2_index, ret; + uint64_t l2_offset, *l2_table; + int64_t cluster_offset; + int nb_csectors; + + ret = get_cluster_table(bs, offset, &l2_table, &l2_offset, &l2_index); + if (ret < 0) { + return 0; + } + + cluster_offset = be64_to_cpu(l2_table[l2_index]); + if (cluster_offset & QCOW_OFLAG_COPIED) + return cluster_offset & ~QCOW_OFLAG_COPIED; + + if (cluster_offset) + qcow2_free_any_clusters(bs, cluster_offset, 1); + + cluster_offset = qcow2_alloc_bytes(bs, compressed_size); + if (cluster_offset < 0) { + qcow2_cache_put(bs, s->l2_table_cache, (void**) &l2_table); + return 0; + } + + nb_csectors = ((cluster_offset + compressed_size - 1) >> 9) - + (cluster_offset >> 9); + + cluster_offset |= QCOW_OFLAG_COMPRESSED | + ((uint64_t)nb_csectors << s->csize_shift); + + /* update L2 table */ + + /* compressed clusters never have the copied flag */ + + BLKDBG_EVENT(bs->file, BLKDBG_L2_UPDATE_COMPRESSED); + qcow2_cache_entry_mark_dirty(s->l2_table_cache, l2_table); + l2_table[l2_index] = cpu_to_be64(cluster_offset); + ret = qcow2_cache_put(bs, s->l2_table_cache, (void**) &l2_table); + if (ret < 0) { + return 0; + } + + return cluster_offset; +} + +int qcow2_alloc_cluster_link_l2(BlockDriverState *bs, QCowL2Meta *m) +{ + BDRVQcowState *s = bs->opaque; + int i, j = 0, l2_index, ret; + uint64_t *old_cluster, start_sect, l2_offset, *l2_table; + uint64_t cluster_offset = m->cluster_offset; + bool cow = false; + + if (m->nb_clusters == 0) + return 0; + + old_cluster = qemu_malloc(m->nb_clusters * sizeof(uint64_t)); + + /* copy content of unmodified sectors */ + start_sect = (m->offset & ~(s->cluster_size - 1)) >> 9; + if (m->n_start) { + cow = true; + ret = copy_sectors(bs, start_sect, cluster_offset, 0, m->n_start); + if (ret < 0) + goto err; + } + + if (m->nb_available & (s->cluster_sectors - 1)) { + uint64_t end = m->nb_available & ~(uint64_t)(s->cluster_sectors - 1); + cow = true; + ret = copy_sectors(bs, start_sect + end, cluster_offset + (end << 9), + m->nb_available - end, s->cluster_sectors); + if (ret < 0) + goto err; + } + + /* + * Update L2 table. + * + * Before we update the L2 table to actually point to the new cluster, we + * need to be sure that the refcounts have been increased and COW was + * handled. + */ + if (cow) { + qcow2_cache_depends_on_flush(s->l2_table_cache); + } + + qcow2_cache_set_dependency(bs, s->l2_table_cache, s->refcount_block_cache); + ret = get_cluster_table(bs, m->offset, &l2_table, &l2_offset, &l2_index); + if (ret < 0) { + goto err; + } + qcow2_cache_entry_mark_dirty(s->l2_table_cache, l2_table); + + for (i = 0; i < m->nb_clusters; i++) { + /* if two concurrent writes happen to the same unallocated cluster + * each write allocates separate cluster and writes data concurrently. + * The first one to complete updates l2 table with pointer to its + * cluster the second one has to do RMW (which is done above by + * copy_sectors()), update l2 table with its cluster pointer and free + * old cluster. This is what this loop does */ + if(l2_table[l2_index + i] != 0) + old_cluster[j++] = l2_table[l2_index + i]; + + l2_table[l2_index + i] = cpu_to_be64((cluster_offset + + (i << s->cluster_bits)) | QCOW_OFLAG_COPIED); + } + + + ret = qcow2_cache_put(bs, s->l2_table_cache, (void**) &l2_table); + if (ret < 0) { + goto err; + } + + /* + * If this was a COW, we need to decrease the refcount of the old cluster. + * Also flush bs->file to get the right order for L2 and refcount update. + */ + if (j != 0) { + for (i = 0; i < j; i++) { + qcow2_free_any_clusters(bs, + be64_to_cpu(old_cluster[i]) & ~QCOW_OFLAG_COPIED, 1); + } + } + + ret = 0; +err: + qemu_free(old_cluster); + return ret; + } + +/* + * alloc_cluster_offset + * + * For a given offset of the disk image, return cluster offset in qcow2 file. + * If the offset is not found, allocate a new cluster. + * + * If the cluster was already allocated, m->nb_clusters is set to 0, + * m->depends_on is set to NULL and the other fields in m are meaningless. + * + * If the cluster is newly allocated, m->nb_clusters is set to the number of + * contiguous clusters that have been allocated. This may be 0 if the request + * conflict with another write request in flight; in this case, m->depends_on + * is set and the remaining fields of m are meaningless. + * + * If m->nb_clusters is non-zero, the other fields of m are valid and contain + * information about the first allocated cluster. + * + * Return 0 on success and -errno in error cases + */ +int qcow2_alloc_cluster_offset(BlockDriverState *bs, uint64_t offset, + int n_start, int n_end, int *num, QCowL2Meta *m) +{ + BDRVQcowState *s = bs->opaque; + int l2_index, ret; + uint64_t l2_offset, *l2_table; + int64_t cluster_offset; + unsigned int nb_clusters, i = 0; + QCowL2Meta *old_alloc; + + ret = get_cluster_table(bs, offset, &l2_table, &l2_offset, &l2_index); + if (ret < 0) { + return ret; + } + + nb_clusters = size_to_clusters(s, n_end << 9); + + nb_clusters = MIN(nb_clusters, s->l2_size - l2_index); + + cluster_offset = be64_to_cpu(l2_table[l2_index]); + + /* We keep all QCOW_OFLAG_COPIED clusters */ + + if (cluster_offset & QCOW_OFLAG_COPIED) { + nb_clusters = count_contiguous_clusters(nb_clusters, s->cluster_size, + &l2_table[l2_index], 0, 0); + + cluster_offset &= ~QCOW_OFLAG_COPIED; + m->nb_clusters = 0; + m->depends_on = NULL; + + goto out; + } + + /* for the moment, multiple compressed clusters are not managed */ + + if (cluster_offset & QCOW_OFLAG_COMPRESSED) + nb_clusters = 1; + + /* how many available clusters ? */ + + while (i < nb_clusters) { + i += count_contiguous_clusters(nb_clusters - i, s->cluster_size, + &l2_table[l2_index], i, 0); + if ((i >= nb_clusters) || be64_to_cpu(l2_table[l2_index + i])) { + break; + } + + i += count_contiguous_free_clusters(nb_clusters - i, + &l2_table[l2_index + i]); + if (i >= nb_clusters) { + break; + } + + cluster_offset = be64_to_cpu(l2_table[l2_index + i]); + + if ((cluster_offset & QCOW_OFLAG_COPIED) || + (cluster_offset & QCOW_OFLAG_COMPRESSED)) + break; + } + assert(i <= nb_clusters); + nb_clusters = i; + + /* + * Check if there already is an AIO write request in flight which allocates + * the same cluster. In this case we need to wait until the previous + * request has completed and updated the L2 table accordingly. + */ + QLIST_FOREACH(old_alloc, &s->cluster_allocs, next_in_flight) { + + uint64_t end_offset = offset + nb_clusters * s->cluster_size; + uint64_t old_offset = old_alloc->offset; + uint64_t old_end_offset = old_alloc->offset + + old_alloc->nb_clusters * s->cluster_size; + + if (end_offset < old_offset || offset > old_end_offset) { + /* No intersection */ + } else { + if (offset < old_offset) { + /* Stop at the start of a running allocation */ + nb_clusters = (old_offset - offset) >> s->cluster_bits; + } else { + nb_clusters = 0; + } + + if (nb_clusters == 0) { + /* Set dependency and wait for a callback */ + m->depends_on = old_alloc; + m->nb_clusters = 0; + *num = 0; + ret = 0; + goto fail; + } + } + } + + if (!nb_clusters) { + abort(); + } + + QLIST_INSERT_HEAD(&s->cluster_allocs, m, next_in_flight); + + /* allocate a new cluster */ + + cluster_offset = qcow2_alloc_clusters(bs, nb_clusters * s->cluster_size); + if (cluster_offset < 0) { + QLIST_REMOVE(m, next_in_flight); + ret = cluster_offset; + goto fail; + } + + /* save info needed for meta data update */ + m->offset = offset; + m->n_start = n_start; + m->nb_clusters = nb_clusters; + +out: + ret = qcow2_cache_put(bs, s->l2_table_cache, (void**) &l2_table); + if (ret < 0) { + return ret; + } + + m->nb_available = MIN(nb_clusters << (s->cluster_bits - 9), n_end); + m->cluster_offset = cluster_offset; + + *num = m->nb_available - n_start; + + return 0; + +fail: + qcow2_cache_put(bs, s->l2_table_cache, (void**) &l2_table); + return ret; +} + +static int decompress_buffer(uint8_t *out_buf, int out_buf_size, + const uint8_t *buf, int buf_size) +{ + z_stream strm1, *strm = &strm1; + int ret, out_len; + + memset(strm, 0, sizeof(*strm)); + + strm->next_in = (uint8_t *)buf; + strm->avail_in = buf_size; + strm->next_out = out_buf; + strm->avail_out = out_buf_size; + + ret = inflateInit2(strm, -12); + if (ret != Z_OK) + return -1; + ret = inflate(strm, Z_FINISH); + out_len = strm->next_out - out_buf; + if ((ret != Z_STREAM_END && ret != Z_BUF_ERROR) || + out_len != out_buf_size) { + inflateEnd(strm); + return -1; + } + inflateEnd(strm); + return 0; +} + +int qcow2_decompress_cluster(BlockDriverState *bs, uint64_t cluster_offset) +{ + BDRVQcowState *s = bs->opaque; + int ret, csize, nb_csectors, sector_offset; + uint64_t coffset; + + coffset = cluster_offset & s->cluster_offset_mask; + if (s->cluster_cache_offset != coffset) { + nb_csectors = ((cluster_offset >> s->csize_shift) & s->csize_mask) + 1; + sector_offset = coffset & 511; + csize = nb_csectors * 512 - sector_offset; + BLKDBG_EVENT(bs->file, BLKDBG_READ_COMPRESSED); + ret = bdrv_read(bs->file, coffset >> 9, s->cluster_data, nb_csectors); + if (ret < 0) { + return ret; + } + if (decompress_buffer(s->cluster_cache, s->cluster_size, + s->cluster_data + sector_offset, csize) < 0) { + return -EIO; + } + s->cluster_cache_offset = coffset; + } + return 0; +} + +/* + * This discards as many clusters of nb_clusters as possible at once (i.e. + * all clusters in the same L2 table) and returns the number of discarded + * clusters. + */ +static int discard_single_l2(BlockDriverState *bs, uint64_t offset, + unsigned int nb_clusters) +{ + BDRVQcowState *s = bs->opaque; + uint64_t l2_offset, *l2_table; + int l2_index; + int ret; + int i; + + ret = get_cluster_table(bs, offset, &l2_table, &l2_offset, &l2_index); + if (ret < 0) { + return ret; + } + + /* Limit nb_clusters to one L2 table */ + nb_clusters = MIN(nb_clusters, s->l2_size - l2_index); + + for (i = 0; i < nb_clusters; i++) { + uint64_t old_offset; + + old_offset = be64_to_cpu(l2_table[l2_index + i]); + old_offset &= ~QCOW_OFLAG_COPIED; + + if (old_offset == 0) { + continue; + } + + /* First remove L2 entries */ + qcow2_cache_entry_mark_dirty(s->l2_table_cache, l2_table); + l2_table[l2_index + i] = cpu_to_be64(0); + + /* Then decrease the refcount */ + qcow2_free_any_clusters(bs, old_offset, 1); + } + + ret = qcow2_cache_put(bs, s->l2_table_cache, (void**) &l2_table); + if (ret < 0) { + return ret; + } + + return nb_clusters; +} + +int qcow2_discard_clusters(BlockDriverState *bs, uint64_t offset, + int nb_sectors) +{ + BDRVQcowState *s = bs->opaque; + uint64_t end_offset; + unsigned int nb_clusters; + int ret; + + end_offset = offset + (nb_sectors << BDRV_SECTOR_BITS); + + /* Round start up and end down */ + offset = align_offset(offset, s->cluster_size); + end_offset &= ~(s->cluster_size - 1); + + if (offset > end_offset) { + return 0; + } + + nb_clusters = size_to_clusters(s, end_offset - offset); + + /* Each L2 table is handled by its own loop iteration */ + while (nb_clusters > 0) { + ret = discard_single_l2(bs, offset, nb_clusters); + if (ret < 0) { + return ret; + } + + nb_clusters -= ret; + offset += (ret * s->cluster_size); + } + + return 0; +} diff --git a/block/qcow2-refcount.c b/block/qcow2-refcount.c new file mode 100644 index 0000000..915d85a --- /dev/null +++ b/block/qcow2-refcount.c @@ -0,0 +1,1166 @@ +/* + * Block driver for the QCOW version 2 format + * + * Copyright (c) 2004-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 "qemu-common.h" +#include "block_int.h" +#include "block/qcow2.h" + +static int64_t alloc_clusters_noref(BlockDriverState *bs, int64_t size); +static int QEMU_WARN_UNUSED_RESULT update_refcount(BlockDriverState *bs, + int64_t offset, int64_t length, + int addend); + + +/*********************************************************/ +/* refcount handling */ + +int qcow2_refcount_init(BlockDriverState *bs) +{ + BDRVQcowState *s = bs->opaque; + int ret, refcount_table_size2, i; + + refcount_table_size2 = s->refcount_table_size * sizeof(uint64_t); + s->refcount_table = qemu_malloc(refcount_table_size2); + if (s->refcount_table_size > 0) { + BLKDBG_EVENT(bs->file, BLKDBG_REFTABLE_LOAD); + ret = bdrv_pread(bs->file, s->refcount_table_offset, + s->refcount_table, refcount_table_size2); + if (ret != refcount_table_size2) + goto fail; + for(i = 0; i < s->refcount_table_size; i++) + be64_to_cpus(&s->refcount_table[i]); + } + return 0; + fail: + return -ENOMEM; +} + +void qcow2_refcount_close(BlockDriverState *bs) +{ + BDRVQcowState *s = bs->opaque; + qemu_free(s->refcount_table); +} + + +static int load_refcount_block(BlockDriverState *bs, + int64_t refcount_block_offset, + void **refcount_block) +{ + BDRVQcowState *s = bs->opaque; + int ret; + + BLKDBG_EVENT(bs->file, BLKDBG_REFBLOCK_LOAD); + ret = qcow2_cache_get(bs, s->refcount_block_cache, refcount_block_offset, + refcount_block); + + return ret; +} + +/* + * Returns the refcount of the cluster given by its index. Any non-negative + * return value is the refcount of the cluster, negative values are -errno + * and indicate an error. + */ +static int get_refcount(BlockDriverState *bs, int64_t cluster_index) +{ + BDRVQcowState *s = bs->opaque; + int refcount_table_index, block_index; + int64_t refcount_block_offset; + int ret; + uint16_t *refcount_block; + uint16_t refcount; + + refcount_table_index = cluster_index >> (s->cluster_bits - REFCOUNT_SHIFT); + if (refcount_table_index >= s->refcount_table_size) + return 0; + refcount_block_offset = s->refcount_table[refcount_table_index]; + if (!refcount_block_offset) + return 0; + + ret = qcow2_cache_get(bs, s->refcount_block_cache, refcount_block_offset, + (void**) &refcount_block); + if (ret < 0) { + return ret; + } + + block_index = cluster_index & + ((1 << (s->cluster_bits - REFCOUNT_SHIFT)) - 1); + refcount = be16_to_cpu(refcount_block[block_index]); + + ret = qcow2_cache_put(bs, s->refcount_block_cache, + (void**) &refcount_block); + if (ret < 0) { + return ret; + } + + return refcount; +} + +/* + * Rounds the refcount table size up to avoid growing the table for each single + * refcount block that is allocated. + */ +static unsigned int next_refcount_table_size(BDRVQcowState *s, + unsigned int min_size) +{ + unsigned int min_clusters = (min_size >> (s->cluster_bits - 3)) + 1; + unsigned int refcount_table_clusters = + MAX(1, s->refcount_table_size >> (s->cluster_bits - 3)); + + while (min_clusters > refcount_table_clusters) { + refcount_table_clusters = (refcount_table_clusters * 3 + 1) / 2; + } + + return refcount_table_clusters << (s->cluster_bits - 3); +} + + +/* Checks if two offsets are described by the same refcount block */ +static int in_same_refcount_block(BDRVQcowState *s, uint64_t offset_a, + uint64_t offset_b) +{ + uint64_t block_a = offset_a >> (2 * s->cluster_bits - REFCOUNT_SHIFT); + uint64_t block_b = offset_b >> (2 * s->cluster_bits - REFCOUNT_SHIFT); + + return (block_a == block_b); +} + +/* + * Loads a refcount block. If it doesn't exist yet, it is allocated first + * (including growing the refcount table if needed). + * + * Returns 0 on success or -errno in error case + */ +static int alloc_refcount_block(BlockDriverState *bs, + int64_t cluster_index, uint16_t **refcount_block) +{ + BDRVQcowState *s = bs->opaque; + unsigned int refcount_table_index; + int ret; + + BLKDBG_EVENT(bs->file, BLKDBG_REFBLOCK_ALLOC); + + /* Find the refcount block for the given cluster */ + refcount_table_index = cluster_index >> (s->cluster_bits - REFCOUNT_SHIFT); + + if (refcount_table_index < s->refcount_table_size) { + + uint64_t refcount_block_offset = + s->refcount_table[refcount_table_index]; + + /* If it's already there, we're done */ + if (refcount_block_offset) { + return load_refcount_block(bs, refcount_block_offset, + (void**) refcount_block); + } + } + + /* + * If we came here, we need to allocate something. Something is at least + * a cluster for the new refcount block. It may also include a new refcount + * table if the old refcount table is too small. + * + * Note that allocating clusters here needs some special care: + * + * - We can't use the normal qcow2_alloc_clusters(), it would try to + * increase the refcount and very likely we would end up with an endless + * recursion. Instead we must place the refcount blocks in a way that + * they can describe them themselves. + * + * - We need to consider that at this point we are inside update_refcounts + * and doing the initial refcount increase. This means that some clusters + * have already been allocated by the caller, but their refcount isn't + * accurate yet. free_cluster_index tells us where this allocation ends + * as long as we don't overwrite it by freeing clusters. + * + * - alloc_clusters_noref and qcow2_free_clusters may load a different + * refcount block into the cache + */ + + *refcount_block = NULL; + + /* We write to the refcount table, so we might depend on L2 tables */ + qcow2_cache_flush(bs, s->l2_table_cache); + + /* Allocate the refcount block itself and mark it as used */ + int64_t new_block = alloc_clusters_noref(bs, s->cluster_size); + if (new_block < 0) { + return new_block; + } + +#ifdef DEBUG_ALLOC2 + fprintf(stderr, "qcow2: Allocate refcount block %d for %" PRIx64 + " at %" PRIx64 "\n", + refcount_table_index, cluster_index << s->cluster_bits, new_block); +#endif + + if (in_same_refcount_block(s, new_block, cluster_index << s->cluster_bits)) { + /* Zero the new refcount block before updating it */ + ret = qcow2_cache_get_empty(bs, s->refcount_block_cache, new_block, + (void**) refcount_block); + if (ret < 0) { + goto fail_block; + } + + memset(*refcount_block, 0, s->cluster_size); + + /* The block describes itself, need to update the cache */ + int block_index = (new_block >> s->cluster_bits) & + ((1 << (s->cluster_bits - REFCOUNT_SHIFT)) - 1); + (*refcount_block)[block_index] = cpu_to_be16(1); + } else { + /* Described somewhere else. This can recurse at most twice before we + * arrive at a block that describes itself. */ + ret = update_refcount(bs, new_block, s->cluster_size, 1); + if (ret < 0) { + goto fail_block; + } + + bdrv_flush(bs->file); + + /* Initialize the new refcount block only after updating its refcount, + * update_refcount uses the refcount cache itself */ + ret = qcow2_cache_get_empty(bs, s->refcount_block_cache, new_block, + (void**) refcount_block); + if (ret < 0) { + goto fail_block; + } + + memset(*refcount_block, 0, s->cluster_size); + } + + /* Now the new refcount block needs to be written to disk */ + BLKDBG_EVENT(bs->file, BLKDBG_REFBLOCK_ALLOC_WRITE); + qcow2_cache_entry_mark_dirty(s->refcount_block_cache, *refcount_block); + ret = qcow2_cache_flush(bs, s->refcount_block_cache); + if (ret < 0) { + goto fail_block; + } + + /* If the refcount table is big enough, just hook the block up there */ + if (refcount_table_index < s->refcount_table_size) { + uint64_t data64 = cpu_to_be64(new_block); + BLKDBG_EVENT(bs->file, BLKDBG_REFBLOCK_ALLOC_HOOKUP); + ret = bdrv_pwrite_sync(bs->file, + s->refcount_table_offset + refcount_table_index * sizeof(uint64_t), + &data64, sizeof(data64)); + if (ret < 0) { + goto fail_block; + } + + s->refcount_table[refcount_table_index] = new_block; + return 0; + } + + ret = qcow2_cache_put(bs, s->refcount_block_cache, (void**) refcount_block); + if (ret < 0) { + goto fail_block; + } + + /* + * If we come here, we need to grow the refcount table. Again, a new + * refcount table needs some space and we can't simply allocate to avoid + * endless recursion. + * + * Therefore let's grab new refcount blocks at the end of the image, which + * will describe themselves and the new refcount table. This way we can + * reference them only in the new table and do the switch to the new + * refcount table at once without producing an inconsistent state in + * between. + */ + BLKDBG_EVENT(bs->file, BLKDBG_REFTABLE_GROW); + + /* Calculate the number of refcount blocks needed so far */ + uint64_t refcount_block_clusters = 1 << (s->cluster_bits - REFCOUNT_SHIFT); + uint64_t blocks_used = (s->free_cluster_index + + refcount_block_clusters - 1) / refcount_block_clusters; + + /* And now we need at least one block more for the new metadata */ + uint64_t table_size = next_refcount_table_size(s, blocks_used + 1); + uint64_t last_table_size; + uint64_t blocks_clusters; + do { + uint64_t table_clusters = size_to_clusters(s, table_size); + blocks_clusters = 1 + + ((table_clusters + refcount_block_clusters - 1) + / refcount_block_clusters); + uint64_t meta_clusters = table_clusters + blocks_clusters; + + last_table_size = table_size; + table_size = next_refcount_table_size(s, blocks_used + + ((meta_clusters + refcount_block_clusters - 1) + / refcount_block_clusters)); + + } while (last_table_size != table_size); + +#ifdef DEBUG_ALLOC2 + fprintf(stderr, "qcow2: Grow refcount table %" PRId32 " => %" PRId64 "\n", + s->refcount_table_size, table_size); +#endif + + /* Create the new refcount table and blocks */ + uint64_t meta_offset = (blocks_used * refcount_block_clusters) * + s->cluster_size; + uint64_t table_offset = meta_offset + blocks_clusters * s->cluster_size; + uint16_t *new_blocks = qemu_mallocz(blocks_clusters * s->cluster_size); + uint64_t *new_table = qemu_mallocz(table_size * sizeof(uint64_t)); + + assert(meta_offset >= (s->free_cluster_index * s->cluster_size)); + + /* Fill the new refcount table */ + memcpy(new_table, s->refcount_table, + s->refcount_table_size * sizeof(uint64_t)); + new_table[refcount_table_index] = new_block; + + int i; + for (i = 0; i < blocks_clusters; i++) { + new_table[blocks_used + i] = meta_offset + (i * s->cluster_size); + } + + /* Fill the refcount blocks */ + uint64_t table_clusters = size_to_clusters(s, table_size * sizeof(uint64_t)); + int block = 0; + for (i = 0; i < table_clusters + blocks_clusters; i++) { + new_blocks[block++] = cpu_to_be16(1); + } + + /* Write refcount blocks to disk */ + BLKDBG_EVENT(bs->file, BLKDBG_REFBLOCK_ALLOC_WRITE_BLOCKS); + ret = bdrv_pwrite_sync(bs->file, meta_offset, new_blocks, + blocks_clusters * s->cluster_size); + qemu_free(new_blocks); + if (ret < 0) { + goto fail_table; + } + + /* Write refcount table to disk */ + for(i = 0; i < table_size; i++) { + cpu_to_be64s(&new_table[i]); + } + + BLKDBG_EVENT(bs->file, BLKDBG_REFBLOCK_ALLOC_WRITE_TABLE); + ret = bdrv_pwrite_sync(bs->file, table_offset, new_table, + table_size * sizeof(uint64_t)); + if (ret < 0) { + goto fail_table; + } + + for(i = 0; i < table_size; i++) { + cpu_to_be64s(&new_table[i]); + } + + /* Hook up the new refcount table in the qcow2 header */ + uint8_t data[12]; + cpu_to_be64w((uint64_t*)data, table_offset); + cpu_to_be32w((uint32_t*)(data + 8), table_clusters); + BLKDBG_EVENT(bs->file, BLKDBG_REFBLOCK_ALLOC_SWITCH_TABLE); + ret = bdrv_pwrite_sync(bs->file, offsetof(QCowHeader, refcount_table_offset), + data, sizeof(data)); + if (ret < 0) { + goto fail_table; + } + + /* And switch it in memory */ + uint64_t old_table_offset = s->refcount_table_offset; + uint64_t old_table_size = s->refcount_table_size; + + qemu_free(s->refcount_table); + s->refcount_table = new_table; + s->refcount_table_size = table_size; + s->refcount_table_offset = table_offset; + + /* Free old table. Remember, we must not change free_cluster_index */ + uint64_t old_free_cluster_index = s->free_cluster_index; + qcow2_free_clusters(bs, old_table_offset, old_table_size * sizeof(uint64_t)); + s->free_cluster_index = old_free_cluster_index; + + ret = load_refcount_block(bs, new_block, (void**) refcount_block); + if (ret < 0) { + return ret; + } + + return new_block; + +fail_table: + qemu_free(new_table); +fail_block: + if (*refcount_block != NULL) { + qcow2_cache_put(bs, s->refcount_block_cache, (void**) refcount_block); + } + return ret; +} + +/* XXX: cache several refcount block clusters ? */ +static int QEMU_WARN_UNUSED_RESULT update_refcount(BlockDriverState *bs, + int64_t offset, int64_t length, int addend) +{ + BDRVQcowState *s = bs->opaque; + int64_t start, last, cluster_offset; + uint16_t *refcount_block = NULL; + int64_t old_table_index = -1; + int ret; + +#ifdef DEBUG_ALLOC2 + printf("update_refcount: offset=%" PRId64 " size=%" PRId64 " addend=%d\n", + offset, length, addend); +#endif + if (length < 0) { + return -EINVAL; + } else if (length == 0) { + return 0; + } + + if (addend < 0) { + qcow2_cache_set_dependency(bs, s->refcount_block_cache, + s->l2_table_cache); + } + + start = offset & ~(s->cluster_size - 1); + last = (offset + length - 1) & ~(s->cluster_size - 1); + for(cluster_offset = start; cluster_offset <= last; + cluster_offset += s->cluster_size) + { + int block_index, refcount; + int64_t cluster_index = cluster_offset >> s->cluster_bits; + int64_t table_index = + cluster_index >> (s->cluster_bits - REFCOUNT_SHIFT); + + /* Load the refcount block and allocate it if needed */ + if (table_index != old_table_index) { + if (refcount_block) { + ret = qcow2_cache_put(bs, s->refcount_block_cache, + (void**) &refcount_block); + if (ret < 0) { + goto fail; + } + } + + ret = alloc_refcount_block(bs, cluster_index, &refcount_block); + if (ret < 0) { + goto fail; + } + } + old_table_index = table_index; + + qcow2_cache_entry_mark_dirty(s->refcount_block_cache, refcount_block); + + /* we can update the count and save it */ + block_index = cluster_index & + ((1 << (s->cluster_bits - REFCOUNT_SHIFT)) - 1); + + refcount = be16_to_cpu(refcount_block[block_index]); + refcount += addend; + if (refcount < 0 || refcount > 0xffff) { + ret = -EINVAL; + goto fail; + } + if (refcount == 0 && cluster_index < s->free_cluster_index) { + s->free_cluster_index = cluster_index; + } + refcount_block[block_index] = cpu_to_be16(refcount); + } + + ret = 0; +fail: + /* Write last changed block to disk */ + if (refcount_block) { + int wret; + wret = qcow2_cache_put(bs, s->refcount_block_cache, + (void**) &refcount_block); + if (wret < 0) { + return ret < 0 ? ret : wret; + } + } + + /* + * Try do undo any updates if an error is returned (This may succeed in + * some cases like ENOSPC for allocating a new refcount block) + */ + if (ret < 0) { + int dummy; + dummy = update_refcount(bs, offset, cluster_offset - offset, -addend); + (void)dummy; + } + + return ret; +} + +/* + * Increases or decreases the refcount of a given cluster by one. + * addend must be 1 or -1. + * + * If the return value is non-negative, it is the new refcount of the cluster. + * If it is negative, it is -errno and indicates an error. + */ +static int update_cluster_refcount(BlockDriverState *bs, + int64_t cluster_index, + int addend) +{ + BDRVQcowState *s = bs->opaque; + int ret; + + ret = update_refcount(bs, cluster_index << s->cluster_bits, 1, addend); + if (ret < 0) { + return ret; + } + + bdrv_flush(bs->file); + + return get_refcount(bs, cluster_index); +} + + + +/*********************************************************/ +/* cluster allocation functions */ + + + +/* return < 0 if error */ +static int64_t alloc_clusters_noref(BlockDriverState *bs, int64_t size) +{ + BDRVQcowState *s = bs->opaque; + int i, nb_clusters, refcount; + + nb_clusters = size_to_clusters(s, size); +retry: + for(i = 0; i < nb_clusters; i++) { + int64_t next_cluster_index = s->free_cluster_index++; + refcount = get_refcount(bs, next_cluster_index); + + if (refcount < 0) { + return refcount; + } else if (refcount != 0) { + goto retry; + } + } +#ifdef DEBUG_ALLOC2 + printf("alloc_clusters: size=%" PRId64 " -> %" PRId64 "\n", + size, + (s->free_cluster_index - nb_clusters) << s->cluster_bits); +#endif + return (s->free_cluster_index - nb_clusters) << s->cluster_bits; +} + +int64_t qcow2_alloc_clusters(BlockDriverState *bs, int64_t size) +{ + int64_t offset; + int ret; + + BLKDBG_EVENT(bs->file, BLKDBG_CLUSTER_ALLOC); + offset = alloc_clusters_noref(bs, size); + if (offset < 0) { + return offset; + } + + ret = update_refcount(bs, offset, size, 1); + if (ret < 0) { + return ret; + } + + return offset; +} + +/* only used to allocate compressed sectors. We try to allocate + contiguous sectors. size must be <= cluster_size */ +int64_t qcow2_alloc_bytes(BlockDriverState *bs, int size) +{ + BDRVQcowState *s = bs->opaque; + int64_t offset, cluster_offset; + int free_in_cluster; + + BLKDBG_EVENT(bs->file, BLKDBG_CLUSTER_ALLOC_BYTES); + assert(size > 0 && size <= s->cluster_size); + if (s->free_byte_offset == 0) { + s->free_byte_offset = qcow2_alloc_clusters(bs, s->cluster_size); + if (s->free_byte_offset < 0) { + return s->free_byte_offset; + } + } + redo: + free_in_cluster = s->cluster_size - + (s->free_byte_offset & (s->cluster_size - 1)); + if (size <= free_in_cluster) { + /* enough space in current cluster */ + offset = s->free_byte_offset; + s->free_byte_offset += size; + free_in_cluster -= size; + if (free_in_cluster == 0) + s->free_byte_offset = 0; + if ((offset & (s->cluster_size - 1)) != 0) + update_cluster_refcount(bs, offset >> s->cluster_bits, 1); + } else { + offset = qcow2_alloc_clusters(bs, s->cluster_size); + if (offset < 0) { + return offset; + } + cluster_offset = s->free_byte_offset & ~(s->cluster_size - 1); + if ((cluster_offset + s->cluster_size) == offset) { + /* we are lucky: contiguous data */ + offset = s->free_byte_offset; + update_cluster_refcount(bs, offset >> s->cluster_bits, 1); + s->free_byte_offset += size; + } else { + s->free_byte_offset = offset; + goto redo; + } + } + + bdrv_flush(bs->file); + return offset; +} + +void qcow2_free_clusters(BlockDriverState *bs, + int64_t offset, int64_t size) +{ + int ret; + + BLKDBG_EVENT(bs->file, BLKDBG_CLUSTER_FREE); + ret = update_refcount(bs, offset, size, -1); + if (ret < 0) { + fprintf(stderr, "qcow2_free_clusters failed: %s\n", strerror(-ret)); + /* TODO Remember the clusters to free them later and avoid leaking */ + } +} + +/* + * free_any_clusters + * + * free clusters according to its type: compressed or not + * + */ + +void qcow2_free_any_clusters(BlockDriverState *bs, + uint64_t cluster_offset, int nb_clusters) +{ + BDRVQcowState *s = bs->opaque; + + /* free the cluster */ + + if (cluster_offset & QCOW_OFLAG_COMPRESSED) { + int nb_csectors; + nb_csectors = ((cluster_offset >> s->csize_shift) & + s->csize_mask) + 1; + qcow2_free_clusters(bs, + (cluster_offset & s->cluster_offset_mask) & ~511, + nb_csectors * 512); + return; + } + + qcow2_free_clusters(bs, cluster_offset, nb_clusters << s->cluster_bits); + + return; +} + + + +/*********************************************************/ +/* snapshots and image creation */ + + + +void qcow2_create_refcount_update(QCowCreateState *s, int64_t offset, + int64_t size) +{ + int refcount; + int64_t start, last, cluster_offset; + uint16_t *p; + + start = offset & ~(s->cluster_size - 1); + last = (offset + size - 1) & ~(s->cluster_size - 1); + for(cluster_offset = start; cluster_offset <= last; + cluster_offset += s->cluster_size) { + p = &s->refcount_block[cluster_offset >> s->cluster_bits]; + refcount = be16_to_cpu(*p); + refcount++; + *p = cpu_to_be16(refcount); + } +} + +/* update the refcounts of snapshots and the copied flag */ +int qcow2_update_snapshot_refcount(BlockDriverState *bs, + int64_t l1_table_offset, int l1_size, int addend) +{ + BDRVQcowState *s = bs->opaque; + uint64_t *l1_table, *l2_table, l2_offset, offset, l1_size2, l1_allocated; + int64_t old_offset, old_l2_offset; + int i, j, l1_modified, nb_csectors, refcount; + int ret; + + l2_table = NULL; + l1_table = NULL; + l1_size2 = l1_size * sizeof(uint64_t); + if (l1_table_offset != s->l1_table_offset) { + if (l1_size2 != 0) { + l1_table = qemu_mallocz(align_offset(l1_size2, 512)); + } else { + l1_table = NULL; + } + l1_allocated = 1; + if (bdrv_pread(bs->file, l1_table_offset, + l1_table, l1_size2) != l1_size2) + goto fail; + for(i = 0;i < l1_size; i++) + be64_to_cpus(&l1_table[i]); + } else { + assert(l1_size == s->l1_size); + l1_table = s->l1_table; + l1_allocated = 0; + } + + l1_modified = 0; + for(i = 0; i < l1_size; i++) { + l2_offset = l1_table[i]; + if (l2_offset) { + old_l2_offset = l2_offset; + l2_offset &= ~QCOW_OFLAG_COPIED; + + ret = qcow2_cache_get(bs, s->l2_table_cache, l2_offset, + (void**) &l2_table); + if (ret < 0) { + goto fail; + } + + for(j = 0; j < s->l2_size; j++) { + offset = be64_to_cpu(l2_table[j]); + if (offset != 0) { + old_offset = offset; + offset &= ~QCOW_OFLAG_COPIED; + if (offset & QCOW_OFLAG_COMPRESSED) { + nb_csectors = ((offset >> s->csize_shift) & + s->csize_mask) + 1; + if (addend != 0) { + int ret; + ret = update_refcount(bs, + (offset & s->cluster_offset_mask) & ~511, + nb_csectors * 512, addend); + if (ret < 0) { + goto fail; + } + + /* TODO Flushing once for the whole function should + * be enough */ + bdrv_flush(bs->file); + } + /* compressed clusters are never modified */ + refcount = 2; + } else { + if (addend != 0) { + refcount = update_cluster_refcount(bs, offset >> s->cluster_bits, addend); + } else { + refcount = get_refcount(bs, offset >> s->cluster_bits); + } + + if (refcount < 0) { + goto fail; + } + } + + if (refcount == 1) { + offset |= QCOW_OFLAG_COPIED; + } + if (offset != old_offset) { + if (addend > 0) { + qcow2_cache_set_dependency(bs, s->l2_table_cache, + s->refcount_block_cache); + } + l2_table[j] = cpu_to_be64(offset); + qcow2_cache_entry_mark_dirty(s->l2_table_cache, l2_table); + } + } + } + + ret = qcow2_cache_put(bs, s->l2_table_cache, (void**) &l2_table); + if (ret < 0) { + goto fail; + } + + + if (addend != 0) { + refcount = update_cluster_refcount(bs, l2_offset >> s->cluster_bits, addend); + } else { + refcount = get_refcount(bs, l2_offset >> s->cluster_bits); + } + if (refcount < 0) { + goto fail; + } else if (refcount == 1) { + l2_offset |= QCOW_OFLAG_COPIED; + } + if (l2_offset != old_l2_offset) { + l1_table[i] = l2_offset; + l1_modified = 1; + } + } + } + if (l1_modified) { + for(i = 0; i < l1_size; i++) + cpu_to_be64s(&l1_table[i]); + if (bdrv_pwrite_sync(bs->file, l1_table_offset, l1_table, + l1_size2) < 0) + goto fail; + for(i = 0; i < l1_size; i++) + be64_to_cpus(&l1_table[i]); + } + if (l1_allocated) + qemu_free(l1_table); + return 0; + fail: + if (l2_table) { + qcow2_cache_put(bs, s->l2_table_cache, (void**) &l2_table); + } + + if (l1_allocated) + qemu_free(l1_table); + return -EIO; +} + + + + +/*********************************************************/ +/* refcount checking functions */ + + + +/* + * Increases the refcount for a range of clusters in a given refcount table. + * This is used to construct a temporary refcount table out of L1 and L2 tables + * which can be compared the the refcount table saved in the image. + * + * Modifies the number of errors in res. + */ +static void inc_refcounts(BlockDriverState *bs, + BdrvCheckResult *res, + uint16_t *refcount_table, + int refcount_table_size, + int64_t offset, int64_t size) +{ + BDRVQcowState *s = bs->opaque; + int64_t start, last, cluster_offset; + int k; + + if (size <= 0) + return; + + start = offset & ~(s->cluster_size - 1); + last = (offset + size - 1) & ~(s->cluster_size - 1); + for(cluster_offset = start; cluster_offset <= last; + cluster_offset += s->cluster_size) { + k = cluster_offset >> s->cluster_bits; + if (k < 0) { + fprintf(stderr, "ERROR: invalid cluster offset=0x%" PRIx64 "\n", + cluster_offset); + res->corruptions++; + } else if (k >= refcount_table_size) { + fprintf(stderr, "Warning: cluster offset=0x%" PRIx64 " is after " + "the end of the image file, can't properly check refcounts.\n", + cluster_offset); + res->check_errors++; + } else { + if (++refcount_table[k] == 0) { + fprintf(stderr, "ERROR: overflow cluster offset=0x%" PRIx64 + "\n", cluster_offset); + res->corruptions++; + } + } + } +} + +/* + * Increases the refcount in the given refcount table for the all clusters + * referenced in the L2 table. While doing so, performs some checks on L2 + * entries. + * + * Returns the number of errors found by the checks or -errno if an internal + * error occurred. + */ +static int check_refcounts_l2(BlockDriverState *bs, BdrvCheckResult *res, + uint16_t *refcount_table, int refcount_table_size, int64_t l2_offset, + int check_copied) +{ + BDRVQcowState *s = bs->opaque; + uint64_t *l2_table, offset; + int i, l2_size, nb_csectors, refcount; + + /* Read L2 table from disk */ + l2_size = s->l2_size * sizeof(uint64_t); + l2_table = qemu_malloc(l2_size); + + if (bdrv_pread(bs->file, l2_offset, l2_table, l2_size) != l2_size) + goto fail; + + /* Do the actual checks */ + for(i = 0; i < s->l2_size; i++) { + offset = be64_to_cpu(l2_table[i]); + if (offset != 0) { + if (offset & QCOW_OFLAG_COMPRESSED) { + /* Compressed clusters don't have QCOW_OFLAG_COPIED */ + if (offset & QCOW_OFLAG_COPIED) { + fprintf(stderr, "ERROR: cluster %" PRId64 ": " + "copied flag must never be set for compressed " + "clusters\n", offset >> s->cluster_bits); + offset &= ~QCOW_OFLAG_COPIED; + res->corruptions++; + } + + /* Mark cluster as used */ + nb_csectors = ((offset >> s->csize_shift) & + s->csize_mask) + 1; + offset &= s->cluster_offset_mask; + inc_refcounts(bs, res, refcount_table, refcount_table_size, + offset & ~511, nb_csectors * 512); + } else { + /* QCOW_OFLAG_COPIED must be set iff refcount == 1 */ + if (check_copied) { + uint64_t entry = offset; + offset &= ~QCOW_OFLAG_COPIED; + refcount = get_refcount(bs, offset >> s->cluster_bits); + if (refcount < 0) { + fprintf(stderr, "Can't get refcount for offset %" + PRIx64 ": %s\n", entry, strerror(-refcount)); + goto fail; + } + if ((refcount == 1) != ((entry & QCOW_OFLAG_COPIED) != 0)) { + fprintf(stderr, "ERROR OFLAG_COPIED: offset=%" + PRIx64 " refcount=%d\n", entry, refcount); + res->corruptions++; + } + } + + /* Mark cluster as used */ + offset &= ~QCOW_OFLAG_COPIED; + inc_refcounts(bs, res, refcount_table,refcount_table_size, + offset, s->cluster_size); + + /* Correct offsets are cluster aligned */ + if (offset & (s->cluster_size - 1)) { + fprintf(stderr, "ERROR offset=%" PRIx64 ": Cluster is not " + "properly aligned; L2 entry corrupted.\n", offset); + res->corruptions++; + } + } + } + } + + qemu_free(l2_table); + return 0; + +fail: + fprintf(stderr, "ERROR: I/O error in check_refcounts_l2\n"); + qemu_free(l2_table); + return -EIO; +} + +/* + * Increases the refcount for the L1 table, its L2 tables and all referenced + * clusters in the given refcount table. While doing so, performs some checks + * on L1 and L2 entries. + * + * Returns the number of errors found by the checks or -errno if an internal + * error occurred. + */ +static int check_refcounts_l1(BlockDriverState *bs, + BdrvCheckResult *res, + uint16_t *refcount_table, + int refcount_table_size, + int64_t l1_table_offset, int l1_size, + int check_copied) +{ + BDRVQcowState *s = bs->opaque; + uint64_t *l1_table, l2_offset, l1_size2; + int i, refcount, ret; + + l1_size2 = l1_size * sizeof(uint64_t); + + /* Mark L1 table as used */ + inc_refcounts(bs, res, refcount_table, refcount_table_size, + l1_table_offset, l1_size2); + + /* Read L1 table entries from disk */ + if (l1_size2 == 0) { + l1_table = NULL; + } else { + l1_table = qemu_malloc(l1_size2); + if (bdrv_pread(bs->file, l1_table_offset, + l1_table, l1_size2) != l1_size2) + goto fail; + for(i = 0;i < l1_size; i++) + be64_to_cpus(&l1_table[i]); + } + + /* Do the actual checks */ + for(i = 0; i < l1_size; i++) { + l2_offset = l1_table[i]; + if (l2_offset) { + /* QCOW_OFLAG_COPIED must be set iff refcount == 1 */ + if (check_copied) { + refcount = get_refcount(bs, (l2_offset & ~QCOW_OFLAG_COPIED) + >> s->cluster_bits); + if (refcount < 0) { + fprintf(stderr, "Can't get refcount for l2_offset %" + PRIx64 ": %s\n", l2_offset, strerror(-refcount)); + goto fail; + } + if ((refcount == 1) != ((l2_offset & QCOW_OFLAG_COPIED) != 0)) { + fprintf(stderr, "ERROR OFLAG_COPIED: l2_offset=%" PRIx64 + " refcount=%d\n", l2_offset, refcount); + res->corruptions++; + } + } + + /* Mark L2 table as used */ + l2_offset &= ~QCOW_OFLAG_COPIED; + inc_refcounts(bs, res, refcount_table, refcount_table_size, + l2_offset, s->cluster_size); + + /* L2 tables are cluster aligned */ + if (l2_offset & (s->cluster_size - 1)) { + fprintf(stderr, "ERROR l2_offset=%" PRIx64 ": Table is not " + "cluster aligned; L1 entry corrupted\n", l2_offset); + res->corruptions++; + } + + /* Process and check L2 entries */ + ret = check_refcounts_l2(bs, res, refcount_table, + refcount_table_size, l2_offset, check_copied); + if (ret < 0) { + goto fail; + } + } + } + qemu_free(l1_table); + return 0; + +fail: + fprintf(stderr, "ERROR: I/O error in check_refcounts_l1\n"); + res->check_errors++; + qemu_free(l1_table); + return -EIO; +} + +/* + * Checks an image for refcount consistency. + * + * Returns 0 if no errors are found, the number of errors in case the image is + * detected as corrupted, and -errno when an internal error occured. + */ +int qcow2_check_refcounts(BlockDriverState *bs, BdrvCheckResult *res) +{ + BDRVQcowState *s = bs->opaque; + int64_t size; + int nb_clusters, refcount1, refcount2, i; + QCowSnapshot *sn; + uint16_t *refcount_table; + int ret; + + size = bdrv_getlength(bs->file); + nb_clusters = size_to_clusters(s, size); + refcount_table = qemu_mallocz(nb_clusters * sizeof(uint16_t)); + + /* header */ + inc_refcounts(bs, res, refcount_table, nb_clusters, + 0, s->cluster_size); + + /* current L1 table */ + ret = check_refcounts_l1(bs, res, refcount_table, nb_clusters, + s->l1_table_offset, s->l1_size, 1); + if (ret < 0) { + return ret; + } + + /* snapshots */ + for(i = 0; i < s->nb_snapshots; i++) { + sn = s->snapshots + i; + ret = check_refcounts_l1(bs, res, refcount_table, nb_clusters, + sn->l1_table_offset, sn->l1_size, 0); + if (ret < 0) { + return ret; + } + } + inc_refcounts(bs, res, refcount_table, nb_clusters, + s->snapshots_offset, s->snapshots_size); + + /* refcount data */ + inc_refcounts(bs, res, refcount_table, nb_clusters, + s->refcount_table_offset, + s->refcount_table_size * sizeof(uint64_t)); + + for(i = 0; i < s->refcount_table_size; i++) { + uint64_t offset, cluster; + offset = s->refcount_table[i]; + cluster = offset >> s->cluster_bits; + + /* Refcount blocks are cluster aligned */ + if (offset & (s->cluster_size - 1)) { + fprintf(stderr, "ERROR refcount block %d is not " + "cluster aligned; refcount table entry corrupted\n", i); + res->corruptions++; + continue; + } + + if (cluster >= nb_clusters) { + fprintf(stderr, "ERROR refcount block %d is outside image\n", i); + res->corruptions++; + continue; + } + + if (offset != 0) { + inc_refcounts(bs, res, refcount_table, nb_clusters, + offset, s->cluster_size); + if (refcount_table[cluster] != 1) { + fprintf(stderr, "ERROR refcount block %d refcount=%d\n", + i, refcount_table[cluster]); + res->corruptions++; + } + } + } + + /* compare ref counts */ + for(i = 0; i < nb_clusters; i++) { + refcount1 = get_refcount(bs, i); + if (refcount1 < 0) { + fprintf(stderr, "Can't get refcount for cluster %d: %s\n", + i, strerror(-refcount1)); + res->check_errors++; + continue; + } + + refcount2 = refcount_table[i]; + if (refcount1 != refcount2) { + fprintf(stderr, "%s cluster %d refcount=%d reference=%d\n", + refcount1 < refcount2 ? "ERROR" : "Leaked", + i, refcount1, refcount2); + if (refcount1 < refcount2) { + res->corruptions++; + } else { + res->leaks++; + } + } + } + + qemu_free(refcount_table); + + return 0; +} + diff --git a/block/qcow2-snapshot.c b/block/qcow2-snapshot.c new file mode 100644 index 0000000..74823a5 --- /dev/null +++ b/block/qcow2-snapshot.c @@ -0,0 +1,451 @@ +/* + * Block driver for the QCOW version 2 format + * + * Copyright (c) 2004-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 "qemu-common.h" +#include "block_int.h" +#include "block/qcow2.h" + +typedef struct __attribute__((packed)) QCowSnapshotHeader { + /* header is 8 byte aligned */ + uint64_t l1_table_offset; + + uint32_t l1_size; + uint16_t id_str_size; + uint16_t name_size; + + uint32_t date_sec; + uint32_t date_nsec; + + uint64_t vm_clock_nsec; + + uint32_t vm_state_size; + uint32_t extra_data_size; /* for extension */ + /* extra data follows */ + /* id_str follows */ + /* name follows */ +} QCowSnapshotHeader; + +void qcow2_free_snapshots(BlockDriverState *bs) +{ + BDRVQcowState *s = bs->opaque; + int i; + + for(i = 0; i < s->nb_snapshots; i++) { + qemu_free(s->snapshots[i].name); + qemu_free(s->snapshots[i].id_str); + } + qemu_free(s->snapshots); + s->snapshots = NULL; + s->nb_snapshots = 0; +} + +int qcow2_read_snapshots(BlockDriverState *bs) +{ + BDRVQcowState *s = bs->opaque; + QCowSnapshotHeader h; + QCowSnapshot *sn; + int i, id_str_size, name_size; + int64_t offset; + uint32_t extra_data_size; + + if (!s->nb_snapshots) { + s->snapshots = NULL; + s->snapshots_size = 0; + return 0; + } + + offset = s->snapshots_offset; + s->snapshots = qemu_mallocz(s->nb_snapshots * sizeof(QCowSnapshot)); + for(i = 0; i < s->nb_snapshots; i++) { + offset = align_offset(offset, 8); + if (bdrv_pread(bs->file, offset, &h, sizeof(h)) != sizeof(h)) + goto fail; + offset += sizeof(h); + sn = s->snapshots + i; + sn->l1_table_offset = be64_to_cpu(h.l1_table_offset); + sn->l1_size = be32_to_cpu(h.l1_size); + sn->vm_state_size = be32_to_cpu(h.vm_state_size); + sn->date_sec = be32_to_cpu(h.date_sec); + sn->date_nsec = be32_to_cpu(h.date_nsec); + sn->vm_clock_nsec = be64_to_cpu(h.vm_clock_nsec); + extra_data_size = be32_to_cpu(h.extra_data_size); + + id_str_size = be16_to_cpu(h.id_str_size); + name_size = be16_to_cpu(h.name_size); + + offset += extra_data_size; + + sn->id_str = qemu_malloc(id_str_size + 1); + if (bdrv_pread(bs->file, offset, sn->id_str, id_str_size) != id_str_size) + goto fail; + offset += id_str_size; + sn->id_str[id_str_size] = '\0'; + + sn->name = qemu_malloc(name_size + 1); + if (bdrv_pread(bs->file, offset, sn->name, name_size) != name_size) + goto fail; + offset += name_size; + sn->name[name_size] = '\0'; + } + s->snapshots_size = offset - s->snapshots_offset; + return 0; + fail: + qcow2_free_snapshots(bs); + return -1; +} + +/* add at the end of the file a new list of snapshots */ +static int qcow2_write_snapshots(BlockDriverState *bs) +{ + BDRVQcowState *s = bs->opaque; + QCowSnapshot *sn; + QCowSnapshotHeader h; + int i, name_size, id_str_size, snapshots_size; + uint64_t data64; + uint32_t data32; + int64_t offset, snapshots_offset; + + /* compute the size of the snapshots */ + offset = 0; + for(i = 0; i < s->nb_snapshots; i++) { + sn = s->snapshots + i; + offset = align_offset(offset, 8); + offset += sizeof(h); + offset += strlen(sn->id_str); + offset += strlen(sn->name); + } + snapshots_size = offset; + + snapshots_offset = qcow2_alloc_clusters(bs, snapshots_size); + bdrv_flush(bs->file); + offset = snapshots_offset; + if (offset < 0) { + return offset; + } + + for(i = 0; i < s->nb_snapshots; i++) { + sn = s->snapshots + i; + memset(&h, 0, sizeof(h)); + h.l1_table_offset = cpu_to_be64(sn->l1_table_offset); + h.l1_size = cpu_to_be32(sn->l1_size); + h.vm_state_size = cpu_to_be32(sn->vm_state_size); + h.date_sec = cpu_to_be32(sn->date_sec); + h.date_nsec = cpu_to_be32(sn->date_nsec); + h.vm_clock_nsec = cpu_to_be64(sn->vm_clock_nsec); + + id_str_size = strlen(sn->id_str); + name_size = strlen(sn->name); + h.id_str_size = cpu_to_be16(id_str_size); + h.name_size = cpu_to_be16(name_size); + offset = align_offset(offset, 8); + if (bdrv_pwrite_sync(bs->file, offset, &h, sizeof(h)) < 0) + goto fail; + offset += sizeof(h); + if (bdrv_pwrite_sync(bs->file, offset, sn->id_str, id_str_size) < 0) + goto fail; + offset += id_str_size; + if (bdrv_pwrite_sync(bs->file, offset, sn->name, name_size) < 0) + goto fail; + offset += name_size; + } + + /* update the various header fields */ + data64 = cpu_to_be64(snapshots_offset); + if (bdrv_pwrite_sync(bs->file, offsetof(QCowHeader, snapshots_offset), + &data64, sizeof(data64)) < 0) + goto fail; + data32 = cpu_to_be32(s->nb_snapshots); + if (bdrv_pwrite_sync(bs->file, offsetof(QCowHeader, nb_snapshots), + &data32, sizeof(data32)) < 0) + goto fail; + + /* free the old snapshot table */ + qcow2_free_clusters(bs, s->snapshots_offset, s->snapshots_size); + s->snapshots_offset = snapshots_offset; + s->snapshots_size = snapshots_size; + return 0; + fail: + return -1; +} + +static void find_new_snapshot_id(BlockDriverState *bs, + char *id_str, int id_str_size) +{ + BDRVQcowState *s = bs->opaque; + QCowSnapshot *sn; + int i, id, id_max = 0; + + for(i = 0; i < s->nb_snapshots; i++) { + sn = s->snapshots + i; + id = strtoul(sn->id_str, NULL, 10); + if (id > id_max) + id_max = id; + } + snprintf(id_str, id_str_size, "%d", id_max + 1); +} + +static int find_snapshot_by_id(BlockDriverState *bs, const char *id_str) +{ + BDRVQcowState *s = bs->opaque; + int i; + + for(i = 0; i < s->nb_snapshots; i++) { + if (!strcmp(s->snapshots[i].id_str, id_str)) + return i; + } + return -1; +} + +static int find_snapshot_by_id_or_name(BlockDriverState *bs, const char *name) +{ + BDRVQcowState *s = bs->opaque; + int i, ret; + + ret = find_snapshot_by_id(bs, name); + if (ret >= 0) + return ret; + for(i = 0; i < s->nb_snapshots; i++) { + if (!strcmp(s->snapshots[i].name, name)) + return i; + } + return -1; +} + +/* if no id is provided, a new one is constructed */ +int qcow2_snapshot_create(BlockDriverState *bs, QEMUSnapshotInfo *sn_info) +{ + BDRVQcowState *s = bs->opaque; + QCowSnapshot *snapshots1, sn1, *sn = &sn1; + int i, ret; + uint64_t *l1_table = NULL; + int64_t l1_table_offset; + + memset(sn, 0, sizeof(*sn)); + + if (sn_info->id_str[0] == '\0') { + /* compute a new id */ + find_new_snapshot_id(bs, sn_info->id_str, sizeof(sn_info->id_str)); + } + + /* check that the ID is unique */ + if (find_snapshot_by_id(bs, sn_info->id_str) >= 0) + return -ENOENT; + + sn->id_str = qemu_strdup(sn_info->id_str); + if (!sn->id_str) + goto fail; + sn->name = qemu_strdup(sn_info->name); + if (!sn->name) + goto fail; + sn->vm_state_size = sn_info->vm_state_size; + sn->date_sec = sn_info->date_sec; + sn->date_nsec = sn_info->date_nsec; + sn->vm_clock_nsec = sn_info->vm_clock_nsec; + + ret = qcow2_update_snapshot_refcount(bs, s->l1_table_offset, s->l1_size, 1); + if (ret < 0) + goto fail; + + /* create the L1 table of the snapshot */ + l1_table_offset = qcow2_alloc_clusters(bs, s->l1_size * sizeof(uint64_t)); + if (l1_table_offset < 0) { + goto fail; + } + bdrv_flush(bs->file); + + sn->l1_table_offset = l1_table_offset; + sn->l1_size = s->l1_size; + + if (s->l1_size != 0) { + l1_table = qemu_malloc(s->l1_size * sizeof(uint64_t)); + } else { + l1_table = NULL; + } + + for(i = 0; i < s->l1_size; i++) { + l1_table[i] = cpu_to_be64(s->l1_table[i]); + } + if (bdrv_pwrite_sync(bs->file, sn->l1_table_offset, + l1_table, s->l1_size * sizeof(uint64_t)) < 0) + goto fail; + qemu_free(l1_table); + l1_table = NULL; + + snapshots1 = qemu_malloc((s->nb_snapshots + 1) * sizeof(QCowSnapshot)); + if (s->snapshots) { + memcpy(snapshots1, s->snapshots, s->nb_snapshots * sizeof(QCowSnapshot)); + qemu_free(s->snapshots); + } + s->snapshots = snapshots1; + s->snapshots[s->nb_snapshots++] = *sn; + + if (qcow2_write_snapshots(bs) < 0) + goto fail; +#ifdef DEBUG_ALLOC + qcow2_check_refcounts(bs); +#endif + return 0; + fail: + qemu_free(sn->name); + qemu_free(l1_table); + return -1; +} + +/* copy the snapshot 'snapshot_name' into the current disk image */ +int qcow2_snapshot_goto(BlockDriverState *bs, const char *snapshot_id) +{ + BDRVQcowState *s = bs->opaque; + QCowSnapshot *sn; + int i, snapshot_index, l1_size2; + + snapshot_index = find_snapshot_by_id_or_name(bs, snapshot_id); + if (snapshot_index < 0) + return -ENOENT; + sn = &s->snapshots[snapshot_index]; + + if (qcow2_update_snapshot_refcount(bs, s->l1_table_offset, s->l1_size, -1) < 0) + goto fail; + + if (qcow2_grow_l1_table(bs, sn->l1_size, true) < 0) + goto fail; + + s->l1_size = sn->l1_size; + l1_size2 = s->l1_size * sizeof(uint64_t); + /* copy the snapshot l1 table to the current l1 table */ + if (bdrv_pread(bs->file, sn->l1_table_offset, + s->l1_table, l1_size2) != l1_size2) + goto fail; + if (bdrv_pwrite_sync(bs->file, s->l1_table_offset, + s->l1_table, l1_size2) < 0) + goto fail; + for(i = 0;i < s->l1_size; i++) { + be64_to_cpus(&s->l1_table[i]); + } + + if (qcow2_update_snapshot_refcount(bs, s->l1_table_offset, s->l1_size, 1) < 0) + goto fail; + +#ifdef DEBUG_ALLOC + qcow2_check_refcounts(bs); +#endif + return 0; + fail: + return -EIO; +} + +int qcow2_snapshot_delete(BlockDriverState *bs, const char *snapshot_id) +{ + BDRVQcowState *s = bs->opaque; + QCowSnapshot *sn; + int snapshot_index, ret; + + snapshot_index = find_snapshot_by_id_or_name(bs, snapshot_id); + if (snapshot_index < 0) + return -ENOENT; + sn = &s->snapshots[snapshot_index]; + + ret = qcow2_update_snapshot_refcount(bs, sn->l1_table_offset, sn->l1_size, -1); + if (ret < 0) + return ret; + /* must update the copied flag on the current cluster offsets */ + ret = qcow2_update_snapshot_refcount(bs, s->l1_table_offset, s->l1_size, 0); + if (ret < 0) + return ret; + qcow2_free_clusters(bs, sn->l1_table_offset, sn->l1_size * sizeof(uint64_t)); + + qemu_free(sn->id_str); + qemu_free(sn->name); + memmove(sn, sn + 1, (s->nb_snapshots - snapshot_index - 1) * sizeof(*sn)); + s->nb_snapshots--; + ret = qcow2_write_snapshots(bs); + if (ret < 0) { + /* XXX: restore snapshot if error ? */ + return ret; + } +#ifdef DEBUG_ALLOC + qcow2_check_refcounts(bs); +#endif + return 0; +} + +int qcow2_snapshot_list(BlockDriverState *bs, QEMUSnapshotInfo **psn_tab) +{ + BDRVQcowState *s = bs->opaque; + QEMUSnapshotInfo *sn_tab, *sn_info; + QCowSnapshot *sn; + int i; + + if (!s->nb_snapshots) { + *psn_tab = NULL; + return s->nb_snapshots; + } + + sn_tab = qemu_mallocz(s->nb_snapshots * sizeof(QEMUSnapshotInfo)); + for(i = 0; i < s->nb_snapshots; i++) { + sn_info = sn_tab + i; + sn = s->snapshots + i; + pstrcpy(sn_info->id_str, sizeof(sn_info->id_str), + sn->id_str); + pstrcpy(sn_info->name, sizeof(sn_info->name), + sn->name); + sn_info->vm_state_size = sn->vm_state_size; + sn_info->date_sec = sn->date_sec; + sn_info->date_nsec = sn->date_nsec; + sn_info->vm_clock_nsec = sn->vm_clock_nsec; + } + *psn_tab = sn_tab; + return s->nb_snapshots; +} + +int qcow2_snapshot_load_tmp(BlockDriverState *bs, const char *snapshot_name) +{ + int i, snapshot_index, l1_size2; + BDRVQcowState *s = bs->opaque; + QCowSnapshot *sn; + + snapshot_index = find_snapshot_by_id_or_name(bs, snapshot_name); + if (snapshot_index < 0) { + return -ENOENT; + } + + sn = &s->snapshots[snapshot_index]; + s->l1_size = sn->l1_size; + l1_size2 = s->l1_size * sizeof(uint64_t); + if (s->l1_table != NULL) { + qemu_free(s->l1_table); + } + + s->l1_table_offset = sn->l1_table_offset; + s->l1_table = qemu_mallocz(align_offset(l1_size2, 512)); + + if (bdrv_pread(bs->file, sn->l1_table_offset, + s->l1_table, l1_size2) != l1_size2) { + return -1; + } + + for(i = 0;i < s->l1_size; i++) { + be64_to_cpus(&s->l1_table[i]); + } + return 0; +} diff --git a/block/qcow2.c b/block/qcow2.c new file mode 100644 index 0000000..75b8bec --- /dev/null +++ b/block/qcow2.c @@ -0,0 +1,1397 @@ +/* + * Block driver for the QCOW version 2 format + * + * Copyright (c) 2004-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 "qemu-common.h" +#include "block_int.h" +#include "module.h" +#include +#include "aes.h" +#include "block/qcow2.h" +#include "qemu-error.h" +#include "qerror.h" + +/* + Differences with QCOW: + + - Support for multiple incremental snapshots. + - Memory management by reference counts. + - Clusters which have a reference count of one have the bit + QCOW_OFLAG_COPIED to optimize write performance. + - Size of compressed clusters is stored in sectors to reduce bit usage + in the cluster offsets. + - Support for storing additional data (such as the VM state) in the + snapshots. + - If a backing store is used, the cluster size is not constrained + (could be backported to QCOW). + - L2 tables have always a size of one cluster. +*/ + + +typedef struct { + uint32_t magic; + uint32_t len; +} QCowExtension; +#define QCOW2_EXT_MAGIC_END 0 +#define QCOW2_EXT_MAGIC_BACKING_FORMAT 0xE2792ACA + +static int qcow2_probe(const uint8_t *buf, int buf_size, const char *filename) +{ + const QCowHeader *cow_header = (const void *)buf; + + if (buf_size >= sizeof(QCowHeader) && + be32_to_cpu(cow_header->magic) == QCOW_MAGIC && + be32_to_cpu(cow_header->version) >= QCOW_VERSION) + return 100; + else + return 0; +} + + +/* + * read qcow2 extension and fill bs + * start reading from start_offset + * finish reading upon magic of value 0 or when end_offset reached + * unknown magic is skipped (future extension this version knows nothing about) + * return 0 upon success, non-0 otherwise + */ +static int qcow2_read_extensions(BlockDriverState *bs, uint64_t start_offset, + uint64_t end_offset) +{ + QCowExtension ext; + uint64_t offset; + +#ifdef DEBUG_EXT + printf("qcow2_read_extensions: start=%ld end=%ld\n", start_offset, end_offset); +#endif + offset = start_offset; + while (offset < end_offset) { + +#ifdef DEBUG_EXT + /* Sanity check */ + if (offset > s->cluster_size) + printf("qcow2_read_extension: suspicious offset %lu\n", offset); + + printf("attemting to read extended header in offset %lu\n", offset); +#endif + + if (bdrv_pread(bs->file, offset, &ext, sizeof(ext)) != sizeof(ext)) { + fprintf(stderr, "qcow2_read_extension: ERROR: " + "pread fail from offset %" PRIu64 "\n", + offset); + return 1; + } + be32_to_cpus(&ext.magic); + be32_to_cpus(&ext.len); + offset += sizeof(ext); +#ifdef DEBUG_EXT + printf("ext.magic = 0x%x\n", ext.magic); +#endif + switch (ext.magic) { + case QCOW2_EXT_MAGIC_END: + return 0; + + case QCOW2_EXT_MAGIC_BACKING_FORMAT: + if (ext.len >= sizeof(bs->backing_format)) { + fprintf(stderr, "ERROR: ext_backing_format: len=%u too large" + " (>=%zu)\n", + ext.len, sizeof(bs->backing_format)); + return 2; + } + if (bdrv_pread(bs->file, offset , bs->backing_format, + ext.len) != ext.len) + return 3; + bs->backing_format[ext.len] = '\0'; +#ifdef DEBUG_EXT + printf("Qcow2: Got format extension %s\n", bs->backing_format); +#endif + offset = ((offset + ext.len + 7) & ~7); + break; + + default: + /* unknown magic -- just skip it */ + offset = ((offset + ext.len + 7) & ~7); + break; + } + } + + return 0; +} + + +static int qcow2_open(BlockDriverState *bs, int flags) +{ + BDRVQcowState *s = bs->opaque; + int len, i, ret = 0; + QCowHeader header; + uint64_t ext_end; + bool writethrough; + + ret = bdrv_pread(bs->file, 0, &header, sizeof(header)); + if (ret < 0) { + goto fail; + } + be32_to_cpus(&header.magic); + be32_to_cpus(&header.version); + be64_to_cpus(&header.backing_file_offset); + be32_to_cpus(&header.backing_file_size); + be64_to_cpus(&header.size); + be32_to_cpus(&header.cluster_bits); + be32_to_cpus(&header.crypt_method); + be64_to_cpus(&header.l1_table_offset); + be32_to_cpus(&header.l1_size); + be64_to_cpus(&header.refcount_table_offset); + be32_to_cpus(&header.refcount_table_clusters); + be64_to_cpus(&header.snapshots_offset); + be32_to_cpus(&header.nb_snapshots); + + if (header.magic != QCOW_MAGIC) { + ret = -EINVAL; + goto fail; + } + if (header.version != QCOW_VERSION) { + char version[64]; + snprintf(version, sizeof(version), "QCOW version %d", header.version); + qerror_report(QERR_UNKNOWN_BLOCK_FORMAT_FEATURE, + bs->device_name, "qcow2", version); + ret = -ENOTSUP; + goto fail; + } + if (header.cluster_bits < MIN_CLUSTER_BITS || + header.cluster_bits > MAX_CLUSTER_BITS) { + ret = -EINVAL; + goto fail; + } + if (header.crypt_method > QCOW_CRYPT_AES) { + ret = -EINVAL; + goto fail; + } + s->crypt_method_header = header.crypt_method; + if (s->crypt_method_header) { + bs->encrypted = 1; + } + s->cluster_bits = header.cluster_bits; + s->cluster_size = 1 << s->cluster_bits; + s->cluster_sectors = 1 << (s->cluster_bits - 9); + s->l2_bits = s->cluster_bits - 3; /* L2 is always one cluster */ + s->l2_size = 1 << s->l2_bits; + bs->total_sectors = header.size / 512; + s->csize_shift = (62 - (s->cluster_bits - 8)); + s->csize_mask = (1 << (s->cluster_bits - 8)) - 1; + s->cluster_offset_mask = (1LL << s->csize_shift) - 1; + s->refcount_table_offset = header.refcount_table_offset; + s->refcount_table_size = + header.refcount_table_clusters << (s->cluster_bits - 3); + + s->snapshots_offset = header.snapshots_offset; + s->nb_snapshots = header.nb_snapshots; + + /* read the level 1 table */ + s->l1_size = header.l1_size; + s->l1_vm_state_index = size_to_l1(s, header.size); + /* the L1 table must contain at least enough entries to put + header.size bytes */ + if (s->l1_size < s->l1_vm_state_index) { + ret = -EINVAL; + goto fail; + } + s->l1_table_offset = header.l1_table_offset; + if (s->l1_size > 0) { + s->l1_table = qemu_mallocz( + align_offset(s->l1_size * sizeof(uint64_t), 512)); + ret = bdrv_pread(bs->file, s->l1_table_offset, s->l1_table, + s->l1_size * sizeof(uint64_t)); + if (ret < 0) { + goto fail; + } + for(i = 0;i < s->l1_size; i++) { + be64_to_cpus(&s->l1_table[i]); + } + } + + /* alloc L2 table/refcount block cache */ + writethrough = ((flags & BDRV_O_CACHE_MASK) == 0); + s->l2_table_cache = qcow2_cache_create(bs, L2_CACHE_SIZE, writethrough); + s->refcount_block_cache = qcow2_cache_create(bs, REFCOUNT_CACHE_SIZE, + writethrough); + + s->cluster_cache = qemu_malloc(s->cluster_size); + /* one more sector for decompressed data alignment */ + s->cluster_data = qemu_malloc(QCOW_MAX_CRYPT_CLUSTERS * s->cluster_size + + 512); + s->cluster_cache_offset = -1; + + ret = qcow2_refcount_init(bs); + if (ret != 0) { + goto fail; + } + + QLIST_INIT(&s->cluster_allocs); + + /* read qcow2 extensions */ + if (header.backing_file_offset) { + ext_end = header.backing_file_offset; + } else { + ext_end = s->cluster_size; + } + if (qcow2_read_extensions(bs, sizeof(header), ext_end)) { + ret = -EINVAL; + goto fail; + } + + /* read the backing file name */ + if (header.backing_file_offset != 0) { + len = header.backing_file_size; + if (len > 1023) { + len = 1023; + } + ret = bdrv_pread(bs->file, header.backing_file_offset, + bs->backing_file, len); + if (ret < 0) { + goto fail; + } + bs->backing_file[len] = '\0'; + } + if (qcow2_read_snapshots(bs) < 0) { + ret = -EINVAL; + goto fail; + } + +#ifdef DEBUG_ALLOC + qcow2_check_refcounts(bs); +#endif + return ret; + + fail: + qcow2_free_snapshots(bs); + qcow2_refcount_close(bs); + qemu_free(s->l1_table); + if (s->l2_table_cache) { + qcow2_cache_destroy(bs, s->l2_table_cache); + } + qemu_free(s->cluster_cache); + qemu_free(s->cluster_data); + return ret; +} + +static int qcow2_set_key(BlockDriverState *bs, const char *key) +{ + BDRVQcowState *s = bs->opaque; + uint8_t keybuf[16]; + int len, i; + + memset(keybuf, 0, 16); + len = strlen(key); + if (len > 16) + len = 16; + /* XXX: we could compress the chars to 7 bits to increase + entropy */ + for(i = 0;i < len;i++) { + keybuf[i] = key[i]; + } + s->crypt_method = s->crypt_method_header; + + if (AES_set_encrypt_key(keybuf, 128, &s->aes_encrypt_key) != 0) + return -1; + if (AES_set_decrypt_key(keybuf, 128, &s->aes_decrypt_key) != 0) + return -1; +#if 0 + /* test */ + { + uint8_t in[16]; + uint8_t out[16]; + uint8_t tmp[16]; + for(i=0;i<16;i++) + in[i] = i; + AES_encrypt(in, tmp, &s->aes_encrypt_key); + AES_decrypt(tmp, out, &s->aes_decrypt_key); + for(i = 0; i < 16; i++) + printf(" %02x", tmp[i]); + printf("\n"); + for(i = 0; i < 16; i++) + printf(" %02x", out[i]); + printf("\n"); + } +#endif + return 0; +} + +static int qcow2_is_allocated(BlockDriverState *bs, int64_t sector_num, + int nb_sectors, int *pnum) +{ + uint64_t cluster_offset; + int ret; + + *pnum = nb_sectors; + /* FIXME We can get errors here, but the bdrv_is_allocated interface can't + * pass them on today */ + ret = qcow2_get_cluster_offset(bs, sector_num << 9, pnum, &cluster_offset); + if (ret < 0) { + *pnum = 0; + } + + return (cluster_offset != 0); +} + +/* handle reading after the end of the backing file */ +int qcow2_backing_read1(BlockDriverState *bs, QEMUIOVector *qiov, + int64_t sector_num, int nb_sectors) +{ + int n1; + if ((sector_num + nb_sectors) <= bs->total_sectors) + return nb_sectors; + if (sector_num >= bs->total_sectors) + n1 = 0; + else + n1 = bs->total_sectors - sector_num; + + qemu_iovec_memset_skip(qiov, 0, 512 * (nb_sectors - n1), 512 * n1); + + return n1; +} + +typedef struct QCowAIOCB { + BlockDriverAIOCB common; + int64_t sector_num; + QEMUIOVector *qiov; + int remaining_sectors; + int cur_nr_sectors; /* number of sectors in current iteration */ + uint64_t bytes_done; + uint64_t cluster_offset; + uint8_t *cluster_data; + BlockDriverAIOCB *hd_aiocb; + QEMUIOVector hd_qiov; + QEMUBH *bh; + QCowL2Meta l2meta; + QLIST_ENTRY(QCowAIOCB) next_depend; +} QCowAIOCB; + +static void qcow2_aio_cancel(BlockDriverAIOCB *blockacb) +{ + QCowAIOCB *acb = container_of(blockacb, QCowAIOCB, common); + if (acb->hd_aiocb) + bdrv_aio_cancel(acb->hd_aiocb); + qemu_aio_release(acb); +} + +static AIOPool qcow2_aio_pool = { + .aiocb_size = sizeof(QCowAIOCB), + .cancel = qcow2_aio_cancel, +}; + +static void qcow2_aio_read_cb(void *opaque, int ret); +static void qcow2_aio_read_bh(void *opaque) +{ + QCowAIOCB *acb = opaque; + qemu_bh_delete(acb->bh); + acb->bh = NULL; + qcow2_aio_read_cb(opaque, 0); +} + +static int qcow2_schedule_bh(QEMUBHFunc *cb, QCowAIOCB *acb) +{ + if (acb->bh) + return -EIO; + + acb->bh = qemu_bh_new(cb, acb); + if (!acb->bh) + return -EIO; + + qemu_bh_schedule(acb->bh); + + return 0; +} + +static void qcow2_aio_read_cb(void *opaque, int ret) +{ + QCowAIOCB *acb = opaque; + BlockDriverState *bs = acb->common.bs; + BDRVQcowState *s = bs->opaque; + int index_in_cluster, n1; + + acb->hd_aiocb = NULL; + if (ret < 0) + goto done; + + /* post process the read buffer */ + if (!acb->cluster_offset) { + /* nothing to do */ + } else if (acb->cluster_offset & QCOW_OFLAG_COMPRESSED) { + /* nothing to do */ + } else { + if (s->crypt_method) { + qcow2_encrypt_sectors(s, acb->sector_num, acb->cluster_data, + acb->cluster_data, acb->cur_nr_sectors, 0, &s->aes_decrypt_key); + qemu_iovec_reset(&acb->hd_qiov); + qemu_iovec_copy(&acb->hd_qiov, acb->qiov, acb->bytes_done, + acb->cur_nr_sectors * 512); + qemu_iovec_from_buffer(&acb->hd_qiov, acb->cluster_data, + 512 * acb->cur_nr_sectors); + } + } + + acb->remaining_sectors -= acb->cur_nr_sectors; + acb->sector_num += acb->cur_nr_sectors; + acb->bytes_done += acb->cur_nr_sectors * 512; + + if (acb->remaining_sectors == 0) { + /* request completed */ + ret = 0; + goto done; + } + + /* prepare next AIO request */ + acb->cur_nr_sectors = acb->remaining_sectors; + if (s->crypt_method) { + acb->cur_nr_sectors = MIN(acb->cur_nr_sectors, + QCOW_MAX_CRYPT_CLUSTERS * s->cluster_sectors); + } + + ret = qcow2_get_cluster_offset(bs, acb->sector_num << 9, + &acb->cur_nr_sectors, &acb->cluster_offset); + if (ret < 0) { + goto done; + } + + index_in_cluster = acb->sector_num & (s->cluster_sectors - 1); + + qemu_iovec_reset(&acb->hd_qiov); + qemu_iovec_copy(&acb->hd_qiov, acb->qiov, acb->bytes_done, + acb->cur_nr_sectors * 512); + + if (!acb->cluster_offset) { + + if (bs->backing_hd) { + /* read from the base image */ + n1 = qcow2_backing_read1(bs->backing_hd, &acb->hd_qiov, + acb->sector_num, acb->cur_nr_sectors); + if (n1 > 0) { + BLKDBG_EVENT(bs->file, BLKDBG_READ_BACKING_AIO); + acb->hd_aiocb = bdrv_aio_readv(bs->backing_hd, acb->sector_num, + &acb->hd_qiov, n1, qcow2_aio_read_cb, acb); + if (acb->hd_aiocb == NULL) { + ret = -EIO; + goto done; + } + } else { + ret = qcow2_schedule_bh(qcow2_aio_read_bh, acb); + if (ret < 0) + goto done; + } + } else { + /* Note: in this case, no need to wait */ + qemu_iovec_memset(&acb->hd_qiov, 0, 512 * acb->cur_nr_sectors); + ret = qcow2_schedule_bh(qcow2_aio_read_bh, acb); + if (ret < 0) + goto done; + } + } else if (acb->cluster_offset & QCOW_OFLAG_COMPRESSED) { + /* add AIO support for compressed blocks ? */ + ret = qcow2_decompress_cluster(bs, acb->cluster_offset); + if (ret < 0) { + goto done; + } + + qemu_iovec_from_buffer(&acb->hd_qiov, + s->cluster_cache + index_in_cluster * 512, + 512 * acb->cur_nr_sectors); + + ret = qcow2_schedule_bh(qcow2_aio_read_bh, acb); + if (ret < 0) + goto done; + } else { + if ((acb->cluster_offset & 511) != 0) { + ret = -EIO; + goto done; + } + + if (s->crypt_method) { + /* + * For encrypted images, read everything into a temporary + * contiguous buffer on which the AES functions can work. + */ + if (!acb->cluster_data) { + acb->cluster_data = + qemu_mallocz(QCOW_MAX_CRYPT_CLUSTERS * s->cluster_size); + } + + assert(acb->cur_nr_sectors <= + QCOW_MAX_CRYPT_CLUSTERS * s->cluster_sectors); + qemu_iovec_reset(&acb->hd_qiov); + qemu_iovec_add(&acb->hd_qiov, acb->cluster_data, + 512 * acb->cur_nr_sectors); + } + + BLKDBG_EVENT(bs->file, BLKDBG_READ_AIO); + acb->hd_aiocb = bdrv_aio_readv(bs->file, + (acb->cluster_offset >> 9) + index_in_cluster, + &acb->hd_qiov, acb->cur_nr_sectors, + qcow2_aio_read_cb, acb); + if (acb->hd_aiocb == NULL) { + ret = -EIO; + goto done; + } + } + + return; +done: + acb->common.cb(acb->common.opaque, ret); + qemu_iovec_destroy(&acb->hd_qiov); + qemu_aio_release(acb); +} + +static QCowAIOCB *qcow2_aio_setup(BlockDriverState *bs, int64_t sector_num, + QEMUIOVector *qiov, int nb_sectors, + BlockDriverCompletionFunc *cb, + void *opaque, int is_write) +{ + QCowAIOCB *acb; + + acb = qemu_aio_get(&qcow2_aio_pool, bs, cb, opaque); + if (!acb) + return NULL; + acb->hd_aiocb = NULL; + acb->sector_num = sector_num; + acb->qiov = qiov; + + qemu_iovec_init(&acb->hd_qiov, qiov->niov); + + acb->bytes_done = 0; + acb->remaining_sectors = nb_sectors; + acb->cur_nr_sectors = 0; + acb->cluster_offset = 0; + acb->l2meta.nb_clusters = 0; + QLIST_INIT(&acb->l2meta.dependent_requests); + return acb; +} + +static BlockDriverAIOCB *qcow2_aio_readv(BlockDriverState *bs, + int64_t sector_num, + QEMUIOVector *qiov, int nb_sectors, + BlockDriverCompletionFunc *cb, + void *opaque) +{ + QCowAIOCB *acb; + + acb = qcow2_aio_setup(bs, sector_num, qiov, nb_sectors, cb, opaque, 0); + if (!acb) + return NULL; + + qcow2_aio_read_cb(acb, 0); + return &acb->common; +} + +static void qcow2_aio_write_cb(void *opaque, int ret); + +static void run_dependent_requests(QCowL2Meta *m) +{ + QCowAIOCB *req; + QCowAIOCB *next; + + /* Take the request off the list of running requests */ + if (m->nb_clusters != 0) { + QLIST_REMOVE(m, next_in_flight); + } + + /* Restart all dependent requests */ + QLIST_FOREACH_SAFE(req, &m->dependent_requests, next_depend, next) { + qcow2_aio_write_cb(req, 0); + } + + /* Empty the list for the next part of the request */ + QLIST_INIT(&m->dependent_requests); +} + +static void qcow2_aio_write_cb(void *opaque, int ret) +{ + QCowAIOCB *acb = opaque; + BlockDriverState *bs = acb->common.bs; + BDRVQcowState *s = bs->opaque; + int index_in_cluster; + int n_end; + + acb->hd_aiocb = NULL; + + if (ret >= 0) { + ret = qcow2_alloc_cluster_link_l2(bs, &acb->l2meta); + } + + run_dependent_requests(&acb->l2meta); + + if (ret < 0) + goto done; + + acb->remaining_sectors -= acb->cur_nr_sectors; + acb->sector_num += acb->cur_nr_sectors; + acb->bytes_done += acb->cur_nr_sectors * 512; + + if (acb->remaining_sectors == 0) { + /* request completed */ + ret = 0; + goto done; + } + + index_in_cluster = acb->sector_num & (s->cluster_sectors - 1); + n_end = index_in_cluster + acb->remaining_sectors; + if (s->crypt_method && + n_end > QCOW_MAX_CRYPT_CLUSTERS * s->cluster_sectors) + n_end = QCOW_MAX_CRYPT_CLUSTERS * s->cluster_sectors; + + ret = qcow2_alloc_cluster_offset(bs, acb->sector_num << 9, + index_in_cluster, n_end, &acb->cur_nr_sectors, &acb->l2meta); + if (ret < 0) { + goto done; + } + + acb->cluster_offset = acb->l2meta.cluster_offset; + + /* Need to wait for another request? If so, we are done for now. */ + if (acb->l2meta.nb_clusters == 0 && acb->l2meta.depends_on != NULL) { + QLIST_INSERT_HEAD(&acb->l2meta.depends_on->dependent_requests, + acb, next_depend); + return; + } + + assert((acb->cluster_offset & 511) == 0); + + qemu_iovec_reset(&acb->hd_qiov); + qemu_iovec_copy(&acb->hd_qiov, acb->qiov, acb->bytes_done, + acb->cur_nr_sectors * 512); + + if (s->crypt_method) { + if (!acb->cluster_data) { + acb->cluster_data = qemu_mallocz(QCOW_MAX_CRYPT_CLUSTERS * + s->cluster_size); + } + + assert(acb->hd_qiov.size <= QCOW_MAX_CRYPT_CLUSTERS * s->cluster_size); + qemu_iovec_to_buffer(&acb->hd_qiov, acb->cluster_data); + + qcow2_encrypt_sectors(s, acb->sector_num, acb->cluster_data, + acb->cluster_data, acb->cur_nr_sectors, 1, &s->aes_encrypt_key); + + qemu_iovec_reset(&acb->hd_qiov); + qemu_iovec_add(&acb->hd_qiov, acb->cluster_data, + acb->cur_nr_sectors * 512); + } + + BLKDBG_EVENT(bs->file, BLKDBG_WRITE_AIO); + acb->hd_aiocb = bdrv_aio_writev(bs->file, + (acb->cluster_offset >> 9) + index_in_cluster, + &acb->hd_qiov, acb->cur_nr_sectors, + qcow2_aio_write_cb, acb); + if (acb->hd_aiocb == NULL) { + ret = -EIO; + goto fail; + } + + return; + +fail: + if (acb->l2meta.nb_clusters != 0) { + QLIST_REMOVE(&acb->l2meta, next_in_flight); + } +done: + acb->common.cb(acb->common.opaque, ret); + qemu_iovec_destroy(&acb->hd_qiov); + qemu_aio_release(acb); +} + +static BlockDriverAIOCB *qcow2_aio_writev(BlockDriverState *bs, + int64_t sector_num, + QEMUIOVector *qiov, int nb_sectors, + BlockDriverCompletionFunc *cb, + void *opaque) +{ + BDRVQcowState *s = bs->opaque; + QCowAIOCB *acb; + + s->cluster_cache_offset = -1; /* disable compressed cache */ + + acb = qcow2_aio_setup(bs, sector_num, qiov, nb_sectors, cb, opaque, 1); + if (!acb) + return NULL; + + qcow2_aio_write_cb(acb, 0); + return &acb->common; +} + +static void qcow2_close(BlockDriverState *bs) +{ + BDRVQcowState *s = bs->opaque; + qemu_free(s->l1_table); + + qcow2_cache_flush(bs, s->l2_table_cache); + qcow2_cache_flush(bs, s->refcount_block_cache); + + qcow2_cache_destroy(bs, s->l2_table_cache); + qcow2_cache_destroy(bs, s->refcount_block_cache); + + qemu_free(s->cluster_cache); + qemu_free(s->cluster_data); + qcow2_refcount_close(bs); +} + +/* + * Updates the variable length parts of the qcow2 header, i.e. the backing file + * name and all extensions. qcow2 was not designed to allow such changes, so if + * we run out of space (we can only use the first cluster) this function may + * fail. + * + * Returns 0 on success, -errno in error cases. + */ +static int qcow2_update_ext_header(BlockDriverState *bs, + const char *backing_file, const char *backing_fmt) +{ + size_t backing_file_len = 0; + size_t backing_fmt_len = 0; + BDRVQcowState *s = bs->opaque; + QCowExtension ext_backing_fmt = {0, 0}; + int ret; + + /* Backing file format doesn't make sense without a backing file */ + if (backing_fmt && !backing_file) { + return -EINVAL; + } + + /* Prepare the backing file format extension if needed */ + if (backing_fmt) { + ext_backing_fmt.len = cpu_to_be32(strlen(backing_fmt)); + ext_backing_fmt.magic = cpu_to_be32(QCOW2_EXT_MAGIC_BACKING_FORMAT); + backing_fmt_len = ((sizeof(ext_backing_fmt) + + strlen(backing_fmt) + 7) & ~7); + } + + /* Check if we can fit the new header into the first cluster */ + if (backing_file) { + backing_file_len = strlen(backing_file); + } + + size_t header_size = sizeof(QCowHeader) + backing_file_len + + backing_fmt_len; + + if (header_size > s->cluster_size) { + return -ENOSPC; + } + + /* Rewrite backing file name and qcow2 extensions */ + size_t ext_size = header_size - sizeof(QCowHeader); + uint8_t buf[ext_size]; + size_t offset = 0; + size_t backing_file_offset = 0; + + if (backing_file) { + if (backing_fmt) { + int padding = backing_fmt_len - + (sizeof(ext_backing_fmt) + strlen(backing_fmt)); + + memcpy(buf + offset, &ext_backing_fmt, sizeof(ext_backing_fmt)); + offset += sizeof(ext_backing_fmt); + + memcpy(buf + offset, backing_fmt, strlen(backing_fmt)); + offset += strlen(backing_fmt); + + memset(buf + offset, 0, padding); + offset += padding; + } + + memcpy(buf + offset, backing_file, backing_file_len); + backing_file_offset = sizeof(QCowHeader) + offset; + } + + ret = bdrv_pwrite_sync(bs->file, sizeof(QCowHeader), buf, ext_size); + if (ret < 0) { + goto fail; + } + + /* Update header fields */ + uint64_t be_backing_file_offset = cpu_to_be64(backing_file_offset); + uint32_t be_backing_file_size = cpu_to_be32(backing_file_len); + + ret = bdrv_pwrite_sync(bs->file, offsetof(QCowHeader, backing_file_offset), + &be_backing_file_offset, sizeof(uint64_t)); + if (ret < 0) { + goto fail; + } + + ret = bdrv_pwrite_sync(bs->file, offsetof(QCowHeader, backing_file_size), + &be_backing_file_size, sizeof(uint32_t)); + if (ret < 0) { + goto fail; + } + + ret = 0; +fail: + return ret; +} + +static int qcow2_change_backing_file(BlockDriverState *bs, + const char *backing_file, const char *backing_fmt) +{ + return qcow2_update_ext_header(bs, backing_file, backing_fmt); +} + +static int preallocate(BlockDriverState *bs) +{ + uint64_t nb_sectors; + uint64_t offset; + int num; + int ret; + QCowL2Meta meta; + + nb_sectors = bdrv_getlength(bs) >> 9; + offset = 0; + QLIST_INIT(&meta.dependent_requests); + meta.cluster_offset = 0; + + while (nb_sectors) { + num = MIN(nb_sectors, INT_MAX >> 9); + ret = qcow2_alloc_cluster_offset(bs, offset, 0, num, &num, &meta); + if (ret < 0) { + return ret; + } + + ret = qcow2_alloc_cluster_link_l2(bs, &meta); + if (ret < 0) { + qcow2_free_any_clusters(bs, meta.cluster_offset, meta.nb_clusters); + return ret; + } + + /* There are no dependent requests, but we need to remove our request + * from the list of in-flight requests */ + run_dependent_requests(&meta); + + /* TODO Preallocate data if requested */ + + nb_sectors -= num; + offset += num << 9; + } + + /* + * It is expected that the image file is large enough to actually contain + * all of the allocated clusters (otherwise we get failing reads after + * EOF). Extend the image to the last allocated sector. + */ + if (meta.cluster_offset != 0) { + uint8_t buf[512]; + memset(buf, 0, 512); + ret = bdrv_write(bs->file, (meta.cluster_offset >> 9) + num - 1, buf, 1); + if (ret < 0) { + return ret; + } + } + + return 0; +} + +static int qcow2_create2(const char *filename, int64_t total_size, + const char *backing_file, const char *backing_format, + int flags, size_t cluster_size, int prealloc, + QEMUOptionParameter *options) +{ + /* Calulate cluster_bits */ + int cluster_bits; + cluster_bits = ffs(cluster_size) - 1; + if (cluster_bits < MIN_CLUSTER_BITS || cluster_bits > MAX_CLUSTER_BITS || + (1 << cluster_bits) != cluster_size) + { + error_report( + "Cluster size must be a power of two between %d and %dk\n", + 1 << MIN_CLUSTER_BITS, 1 << (MAX_CLUSTER_BITS - 10)); + return -EINVAL; + } + + /* + * Open the image file and write a minimal qcow2 header. + * + * We keep things simple and start with a zero-sized image. We also + * do without refcount blocks or a L1 table for now. We'll fix the + * inconsistency later. + * + * We do need a refcount table because growing the refcount table means + * allocating two new refcount blocks - the seconds of which would be at + * 2 GB for 64k clusters, and we don't want to have a 2 GB initial file + * size for any qcow2 image. + */ + BlockDriverState* bs; + QCowHeader header; + uint8_t* refcount_table; + int ret; + + ret = bdrv_create_file(filename, options); + if (ret < 0) { + return ret; + } + + ret = bdrv_file_open(&bs, filename, BDRV_O_RDWR); + if (ret < 0) { + return ret; + } + + /* Write the header */ + memset(&header, 0, sizeof(header)); + header.magic = cpu_to_be32(QCOW_MAGIC); + header.version = cpu_to_be32(QCOW_VERSION); + header.cluster_bits = cpu_to_be32(cluster_bits); + header.size = cpu_to_be64(0); + header.l1_table_offset = cpu_to_be64(0); + header.l1_size = cpu_to_be32(0); + header.refcount_table_offset = cpu_to_be64(cluster_size); + header.refcount_table_clusters = cpu_to_be32(1); + + if (flags & BLOCK_FLAG_ENCRYPT) { + header.crypt_method = cpu_to_be32(QCOW_CRYPT_AES); + } else { + header.crypt_method = cpu_to_be32(QCOW_CRYPT_NONE); + } + + ret = bdrv_pwrite(bs, 0, &header, sizeof(header)); + if (ret < 0) { + goto out; + } + + /* Write an empty refcount table */ + refcount_table = qemu_mallocz(cluster_size); + ret = bdrv_pwrite(bs, cluster_size, refcount_table, cluster_size); + qemu_free(refcount_table); + + if (ret < 0) { + goto out; + } + + bdrv_close(bs); + + /* + * And now open the image and make it consistent first (i.e. increase the + * refcount of the cluster that is occupied by the header and the refcount + * table) + */ + BlockDriver* drv = bdrv_find_format("qcow2"); + assert(drv != NULL); + ret = bdrv_open(bs, filename, + BDRV_O_RDWR | BDRV_O_CACHE_WB | BDRV_O_NO_FLUSH, drv); + if (ret < 0) { + goto out; + } + + ret = qcow2_alloc_clusters(bs, 2 * cluster_size); + if (ret < 0) { + goto out; + + } else if (ret != 0) { + error_report("Huh, first cluster in empty image is already in use?"); + abort(); + } + + /* Okay, now that we have a valid image, let's give it the right size */ + ret = bdrv_truncate(bs, total_size * BDRV_SECTOR_SIZE); + if (ret < 0) { + goto out; + } + + /* Want a backing file? There you go.*/ + if (backing_file) { + ret = bdrv_change_backing_file(bs, backing_file, backing_format); + if (ret < 0) { + goto out; + } + } + + /* And if we're supposed to preallocate metadata, do that now */ + if (prealloc) { + ret = preallocate(bs); + if (ret < 0) { + goto out; + } + } + + ret = 0; +out: + bdrv_delete(bs); + return ret; +} + +static int qcow2_create(const char *filename, QEMUOptionParameter *options) +{ + const char *backing_file = NULL; + const char *backing_fmt = NULL; + uint64_t sectors = 0; + int flags = 0; + size_t cluster_size = 65536; + int prealloc = 0; + + /* Read out options */ + while (options && options->name) { + if (!strcmp(options->name, BLOCK_OPT_SIZE)) { + sectors = options->value.n / 512; + } else if (!strcmp(options->name, BLOCK_OPT_BACKING_FILE)) { + backing_file = options->value.s; + } else if (!strcmp(options->name, BLOCK_OPT_BACKING_FMT)) { + backing_fmt = options->value.s; + } else if (!strcmp(options->name, BLOCK_OPT_ENCRYPT)) { + flags |= options->value.n ? BLOCK_FLAG_ENCRYPT : 0; + } else if (!strcmp(options->name, BLOCK_OPT_CLUSTER_SIZE)) { + if (options->value.n) { + cluster_size = options->value.n; + } + } else if (!strcmp(options->name, BLOCK_OPT_PREALLOC)) { + if (!options->value.s || !strcmp(options->value.s, "off")) { + prealloc = 0; + } else if (!strcmp(options->value.s, "metadata")) { + prealloc = 1; + } else { + fprintf(stderr, "Invalid preallocation mode: '%s'\n", + options->value.s); + return -EINVAL; + } + } + options++; + } + + if (backing_file && prealloc) { + fprintf(stderr, "Backing file and preallocation cannot be used at " + "the same time\n"); + return -EINVAL; + } + + return qcow2_create2(filename, sectors, backing_file, backing_fmt, flags, + cluster_size, prealloc, options); +} + +static int qcow2_make_empty(BlockDriverState *bs) +{ +#if 0 + /* XXX: not correct */ + BDRVQcowState *s = bs->opaque; + uint32_t l1_length = s->l1_size * sizeof(uint64_t); + int ret; + + memset(s->l1_table, 0, l1_length); + if (bdrv_pwrite(bs->file, s->l1_table_offset, s->l1_table, l1_length) < 0) + return -1; + ret = bdrv_truncate(bs->file, s->l1_table_offset + l1_length); + if (ret < 0) + return ret; + + l2_cache_reset(bs); +#endif + return 0; +} + +static int qcow2_discard(BlockDriverState *bs, int64_t sector_num, + int nb_sectors) +{ + return qcow2_discard_clusters(bs, sector_num << BDRV_SECTOR_BITS, + nb_sectors); +} + +static int qcow2_truncate(BlockDriverState *bs, int64_t offset) +{ + BDRVQcowState *s = bs->opaque; + int ret, new_l1_size; + + if (offset & 511) { + return -EINVAL; + } + + /* cannot proceed if image has snapshots */ + if (s->nb_snapshots) { + return -ENOTSUP; + } + + /* shrinking is currently not supported */ + if (offset < bs->total_sectors * 512) { + return -ENOTSUP; + } + + new_l1_size = size_to_l1(s, offset); + ret = qcow2_grow_l1_table(bs, new_l1_size, true); + if (ret < 0) { + return ret; + } + + /* write updated header.size */ + offset = cpu_to_be64(offset); + ret = bdrv_pwrite_sync(bs->file, offsetof(QCowHeader, size), + &offset, sizeof(uint64_t)); + if (ret < 0) { + return ret; + } + + s->l1_vm_state_index = new_l1_size; + return 0; +} + +/* XXX: put compressed sectors first, then all the cluster aligned + tables to avoid losing bytes in alignment */ +static int qcow2_write_compressed(BlockDriverState *bs, int64_t sector_num, + const uint8_t *buf, int nb_sectors) +{ + BDRVQcowState *s = bs->opaque; + z_stream strm; + int ret, out_len; + uint8_t *out_buf; + uint64_t cluster_offset; + + if (nb_sectors == 0) { + /* align end of file to a sector boundary to ease reading with + sector based I/Os */ + cluster_offset = bdrv_getlength(bs->file); + cluster_offset = (cluster_offset + 511) & ~511; + bdrv_truncate(bs->file, cluster_offset); + return 0; + } + + if (nb_sectors != s->cluster_sectors) + return -EINVAL; + + out_buf = qemu_malloc(s->cluster_size + (s->cluster_size / 1000) + 128); + + /* best compression, small window, no zlib header */ + memset(&strm, 0, sizeof(strm)); + ret = deflateInit2(&strm, Z_DEFAULT_COMPRESSION, + Z_DEFLATED, -12, + 9, Z_DEFAULT_STRATEGY); + if (ret != 0) { + qemu_free(out_buf); + return -1; + } + + strm.avail_in = s->cluster_size; + strm.next_in = (uint8_t *)buf; + strm.avail_out = s->cluster_size; + strm.next_out = out_buf; + + ret = deflate(&strm, Z_FINISH); + if (ret != Z_STREAM_END && ret != Z_OK) { + qemu_free(out_buf); + deflateEnd(&strm); + return -1; + } + out_len = strm.next_out - out_buf; + + deflateEnd(&strm); + + if (ret != Z_STREAM_END || out_len >= s->cluster_size) { + /* could not compress: write normal cluster */ + bdrv_write(bs, sector_num, buf, s->cluster_sectors); + } else { + cluster_offset = qcow2_alloc_compressed_cluster_offset(bs, + sector_num << 9, out_len); + if (!cluster_offset) + return -1; + cluster_offset &= s->cluster_offset_mask; + BLKDBG_EVENT(bs->file, BLKDBG_WRITE_COMPRESSED); + if (bdrv_pwrite(bs->file, cluster_offset, out_buf, out_len) != out_len) { + qemu_free(out_buf); + return -1; + } + } + + qemu_free(out_buf); + return 0; +} + +static int qcow2_flush(BlockDriverState *bs) +{ + BDRVQcowState *s = bs->opaque; + int ret; + + ret = qcow2_cache_flush(bs, s->l2_table_cache); + if (ret < 0) { + return ret; + } + + ret = qcow2_cache_flush(bs, s->refcount_block_cache); + if (ret < 0) { + return ret; + } + + return bdrv_flush(bs->file); +} + +static BlockDriverAIOCB *qcow2_aio_flush(BlockDriverState *bs, + BlockDriverCompletionFunc *cb, + void *opaque) +{ + BDRVQcowState *s = bs->opaque; + int ret; + + ret = qcow2_cache_flush(bs, s->l2_table_cache); + if (ret < 0) { + return NULL; + } + + ret = qcow2_cache_flush(bs, s->refcount_block_cache); + if (ret < 0) { + return NULL; + } + + return bdrv_aio_flush(bs->file, cb, opaque); +} + +static int64_t qcow2_vm_state_offset(BDRVQcowState *s) +{ + return (int64_t)s->l1_vm_state_index << (s->cluster_bits + s->l2_bits); +} + +static int qcow2_get_info(BlockDriverState *bs, BlockDriverInfo *bdi) +{ + BDRVQcowState *s = bs->opaque; + bdi->cluster_size = s->cluster_size; + bdi->vm_state_offset = qcow2_vm_state_offset(s); + return 0; +} + + +static int qcow2_check(BlockDriverState *bs, BdrvCheckResult *result) +{ + return qcow2_check_refcounts(bs, result); +} + +#if 0 +static void dump_refcounts(BlockDriverState *bs) +{ + BDRVQcowState *s = bs->opaque; + int64_t nb_clusters, k, k1, size; + int refcount; + + size = bdrv_getlength(bs->file); + nb_clusters = size_to_clusters(s, size); + for(k = 0; k < nb_clusters;) { + k1 = k; + refcount = get_refcount(bs, k); + k++; + while (k < nb_clusters && get_refcount(bs, k) == refcount) + k++; + printf("%" PRId64 ": refcount=%d nb=%" PRId64 "\n", k, refcount, + k - k1); + } +} +#endif + +static int qcow2_save_vmstate(BlockDriverState *bs, const uint8_t *buf, + int64_t pos, int size) +{ + BDRVQcowState *s = bs->opaque; + int growable = bs->growable; + int ret; + + BLKDBG_EVENT(bs->file, BLKDBG_VMSTATE_SAVE); + bs->growable = 1; + ret = bdrv_pwrite(bs, qcow2_vm_state_offset(s) + pos, buf, size); + bs->growable = growable; + + return ret; +} + +static int qcow2_load_vmstate(BlockDriverState *bs, uint8_t *buf, + int64_t pos, int size) +{ + BDRVQcowState *s = bs->opaque; + int growable = bs->growable; + int ret; + + BLKDBG_EVENT(bs->file, BLKDBG_VMSTATE_LOAD); + bs->growable = 1; + ret = bdrv_pread(bs, qcow2_vm_state_offset(s) + pos, buf, size); + bs->growable = growable; + + return ret; +} + +static QEMUOptionParameter qcow2_create_options[] = { + { + .name = BLOCK_OPT_SIZE, + .type = OPT_SIZE, + .help = "Virtual disk size" + }, + { + .name = BLOCK_OPT_BACKING_FILE, + .type = OPT_STRING, + .help = "File name of a base image" + }, + { + .name = BLOCK_OPT_BACKING_FMT, + .type = OPT_STRING, + .help = "Image format of the base image" + }, + { + .name = BLOCK_OPT_ENCRYPT, + .type = OPT_FLAG, + .help = "Encrypt the image" + }, + { + .name = BLOCK_OPT_CLUSTER_SIZE, + .type = OPT_SIZE, + .help = "qcow2 cluster size" + }, + { + .name = BLOCK_OPT_PREALLOC, + .type = OPT_STRING, + .help = "Preallocation mode (allowed values: off, metadata)" + }, + { NULL } +}; + +static BlockDriver bdrv_qcow2 = { + .format_name = "qcow2", + .instance_size = sizeof(BDRVQcowState), + .bdrv_probe = qcow2_probe, + .bdrv_open = qcow2_open, + .bdrv_close = qcow2_close, + .bdrv_create = qcow2_create, + .bdrv_flush = qcow2_flush, + .bdrv_is_allocated = qcow2_is_allocated, + .bdrv_set_key = qcow2_set_key, + .bdrv_make_empty = qcow2_make_empty, + + .bdrv_aio_readv = qcow2_aio_readv, + .bdrv_aio_writev = qcow2_aio_writev, + .bdrv_aio_flush = qcow2_aio_flush, + + .bdrv_discard = qcow2_discard, + .bdrv_truncate = qcow2_truncate, + .bdrv_write_compressed = qcow2_write_compressed, + + .bdrv_snapshot_create = qcow2_snapshot_create, + .bdrv_snapshot_goto = qcow2_snapshot_goto, + .bdrv_snapshot_delete = qcow2_snapshot_delete, + .bdrv_snapshot_list = qcow2_snapshot_list, + .bdrv_snapshot_load_tmp = qcow2_snapshot_load_tmp, + .bdrv_get_info = qcow2_get_info, + + .bdrv_save_vmstate = qcow2_save_vmstate, + .bdrv_load_vmstate = qcow2_load_vmstate, + + .bdrv_change_backing_file = qcow2_change_backing_file, + + .create_options = qcow2_create_options, + .bdrv_check = qcow2_check, +}; + +static void bdrv_qcow2_init(void) +{ + bdrv_register(&bdrv_qcow2); +} + +block_init(bdrv_qcow2_init); diff --git a/block/qcow2.h b/block/qcow2.h new file mode 100644 index 0000000..a019831 --- /dev/null +++ b/block/qcow2.h @@ -0,0 +1,242 @@ +/* + * Block driver for the QCOW version 2 format + * + * Copyright (c) 2004-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. + */ + +#ifndef BLOCK_QCOW2_H +#define BLOCK_QCOW2_H + +#include "aes.h" + +//#define DEBUG_ALLOC +//#define DEBUG_ALLOC2 +//#define DEBUG_EXT + +#define QCOW_MAGIC (('Q' << 24) | ('F' << 16) | ('I' << 8) | 0xfb) +#define QCOW_VERSION 2 + +#define QCOW_CRYPT_NONE 0 +#define QCOW_CRYPT_AES 1 + +#define QCOW_MAX_CRYPT_CLUSTERS 32 + +/* indicate that the refcount of the referenced cluster is exactly one. */ +#define QCOW_OFLAG_COPIED (1LL << 63) +/* indicate that the cluster is compressed (they never have the copied flag) */ +#define QCOW_OFLAG_COMPRESSED (1LL << 62) + +#define REFCOUNT_SHIFT 1 /* refcount size is 2 bytes */ + +#define MIN_CLUSTER_BITS 9 +#define MAX_CLUSTER_BITS 21 + +#define L2_CACHE_SIZE 16 + +/* Must be at least 4 to cover all cases of refcount table growth */ +#define REFCOUNT_CACHE_SIZE 4 + +typedef struct QCowHeader { + uint32_t magic; + uint32_t version; + uint64_t backing_file_offset; + uint32_t backing_file_size; + uint32_t cluster_bits; + uint64_t size; /* in bytes */ + uint32_t crypt_method; + uint32_t l1_size; /* XXX: save number of clusters instead ? */ + uint64_t l1_table_offset; + uint64_t refcount_table_offset; + uint32_t refcount_table_clusters; + uint32_t nb_snapshots; + uint64_t snapshots_offset; +} QCowHeader; + +typedef struct QCowSnapshot { + uint64_t l1_table_offset; + uint32_t l1_size; + char *id_str; + char *name; + uint32_t vm_state_size; + uint32_t date_sec; + uint32_t date_nsec; + uint64_t vm_clock_nsec; +} QCowSnapshot; + +struct Qcow2Cache; +typedef struct Qcow2Cache Qcow2Cache; + +typedef struct BDRVQcowState { + int cluster_bits; + int cluster_size; + int cluster_sectors; + int l2_bits; + int l2_size; + int l1_size; + int l1_vm_state_index; + int csize_shift; + int csize_mask; + uint64_t cluster_offset_mask; + uint64_t l1_table_offset; + uint64_t *l1_table; + + Qcow2Cache* l2_table_cache; + Qcow2Cache* refcount_block_cache; + + uint8_t *cluster_cache; + uint8_t *cluster_data; + uint64_t cluster_cache_offset; + QLIST_HEAD(QCowClusterAlloc, QCowL2Meta) cluster_allocs; + + uint64_t *refcount_table; + uint64_t refcount_table_offset; + uint32_t refcount_table_size; + int64_t free_cluster_index; + int64_t free_byte_offset; + + uint32_t crypt_method; /* current crypt method, 0 if no key yet */ + uint32_t crypt_method_header; + AES_KEY aes_encrypt_key; + AES_KEY aes_decrypt_key; + uint64_t snapshots_offset; + int snapshots_size; + int nb_snapshots; + QCowSnapshot *snapshots; +} BDRVQcowState; + +/* XXX: use std qcow open function ? */ +typedef struct QCowCreateState { + int cluster_size; + int cluster_bits; + uint16_t *refcount_block; + uint64_t *refcount_table; + int64_t l1_table_offset; + int64_t refcount_table_offset; + int64_t refcount_block_offset; +} QCowCreateState; + +struct QCowAIOCB; + +/* XXX This could be private for qcow2-cluster.c */ +typedef struct QCowL2Meta +{ + uint64_t offset; + uint64_t cluster_offset; + int n_start; + int nb_available; + int nb_clusters; + struct QCowL2Meta *depends_on; + QLIST_HEAD(QCowAioDependencies, QCowAIOCB) dependent_requests; + + QLIST_ENTRY(QCowL2Meta) next_in_flight; +} QCowL2Meta; + +static inline int size_to_clusters(BDRVQcowState *s, int64_t size) +{ + return (size + (s->cluster_size - 1)) >> s->cluster_bits; +} + +static inline int size_to_l1(BDRVQcowState *s, int64_t size) +{ + int shift = s->cluster_bits + s->l2_bits; + return (size + (1ULL << shift) - 1) >> shift; +} + +static inline int64_t align_offset(int64_t offset, int n) +{ + offset = (offset + n - 1) & ~(n - 1); + return offset; +} + + +// FIXME Need qcow2_ prefix to global functions + +/* qcow2.c functions */ +int qcow2_backing_read1(BlockDriverState *bs, QEMUIOVector *qiov, + int64_t sector_num, int nb_sectors); + +/* qcow2-refcount.c functions */ +int qcow2_refcount_init(BlockDriverState *bs); +void qcow2_refcount_close(BlockDriverState *bs); + +int64_t qcow2_alloc_clusters(BlockDriverState *bs, int64_t size); +int64_t qcow2_alloc_bytes(BlockDriverState *bs, int size); +void qcow2_free_clusters(BlockDriverState *bs, + int64_t offset, int64_t size); +void qcow2_free_any_clusters(BlockDriverState *bs, + uint64_t cluster_offset, int nb_clusters); + +void qcow2_create_refcount_update(QCowCreateState *s, int64_t offset, + int64_t size); +int qcow2_update_snapshot_refcount(BlockDriverState *bs, + int64_t l1_table_offset, int l1_size, int addend); + +int qcow2_check_refcounts(BlockDriverState *bs, BdrvCheckResult *res); + +/* qcow2-cluster.c functions */ +int qcow2_grow_l1_table(BlockDriverState *bs, int min_size, bool exact_size); +void qcow2_l2_cache_reset(BlockDriverState *bs); +int qcow2_decompress_cluster(BlockDriverState *bs, uint64_t cluster_offset); +void qcow2_encrypt_sectors(BDRVQcowState *s, int64_t sector_num, + uint8_t *out_buf, const uint8_t *in_buf, + int nb_sectors, int enc, + const AES_KEY *key); + +int qcow2_get_cluster_offset(BlockDriverState *bs, uint64_t offset, + int *num, uint64_t *cluster_offset); +int qcow2_alloc_cluster_offset(BlockDriverState *bs, uint64_t offset, + int n_start, int n_end, int *num, QCowL2Meta *m); +uint64_t qcow2_alloc_compressed_cluster_offset(BlockDriverState *bs, + uint64_t offset, + int compressed_size); + +int qcow2_alloc_cluster_link_l2(BlockDriverState *bs, QCowL2Meta *m); +int qcow2_discard_clusters(BlockDriverState *bs, uint64_t offset, + int nb_sectors); + +/* qcow2-snapshot.c functions */ +int qcow2_snapshot_create(BlockDriverState *bs, QEMUSnapshotInfo *sn_info); +int qcow2_snapshot_goto(BlockDriverState *bs, const char *snapshot_id); +int qcow2_snapshot_delete(BlockDriverState *bs, const char *snapshot_id); +int qcow2_snapshot_list(BlockDriverState *bs, QEMUSnapshotInfo **psn_tab); +int qcow2_snapshot_load_tmp(BlockDriverState *bs, const char *snapshot_name); + +void qcow2_free_snapshots(BlockDriverState *bs); +int qcow2_read_snapshots(BlockDriverState *bs); + +/* qcow2-cache.c functions */ +Qcow2Cache *qcow2_cache_create(BlockDriverState *bs, int num_tables, + bool writethrough); +int qcow2_cache_destroy(BlockDriverState* bs, Qcow2Cache *c); + +void qcow2_cache_entry_mark_dirty(Qcow2Cache *c, void *table); +int qcow2_cache_flush(BlockDriverState *bs, Qcow2Cache *c); +int qcow2_cache_set_dependency(BlockDriverState *bs, Qcow2Cache *c, + Qcow2Cache *dependency); +void qcow2_cache_depends_on_flush(Qcow2Cache *c); + +int qcow2_cache_get(BlockDriverState *bs, Qcow2Cache *c, uint64_t offset, + void **table); +int qcow2_cache_get_empty(BlockDriverState *bs, Qcow2Cache *c, uint64_t offset, + void **table); +int qcow2_cache_put(BlockDriverState *bs, Qcow2Cache *c, void **table); + +#endif diff --git a/block/qed-check.c b/block/qed-check.c new file mode 100644 index 0000000..474c847 --- /dev/null +++ b/block/qed-check.c @@ -0,0 +1,210 @@ +/* + * QEMU Enhanced Disk Format Consistency Check + * + * Copyright IBM, Corp. 2010 + * + * Authors: + * Stefan Hajnoczi + * + * 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 "qed.h" + +typedef struct { + BDRVQEDState *s; + BdrvCheckResult *result; + bool fix; /* whether to fix invalid offsets */ + + uint64_t nclusters; + uint32_t *used_clusters; /* referenced cluster bitmap */ + + QEDRequest request; +} QEDCheck; + +static bool qed_test_bit(uint32_t *bitmap, uint64_t n) { + return !!(bitmap[n / 32] & (1 << (n % 32))); +} + +static void qed_set_bit(uint32_t *bitmap, uint64_t n) { + bitmap[n / 32] |= 1 << (n % 32); +} + +/** + * Set bitmap bits for clusters + * + * @check: Check structure + * @offset: Starting offset in bytes + * @n: Number of clusters + */ +static bool qed_set_used_clusters(QEDCheck *check, uint64_t offset, + unsigned int n) +{ + uint64_t cluster = qed_bytes_to_clusters(check->s, offset); + unsigned int corruptions = 0; + + while (n-- != 0) { + /* Clusters should only be referenced once */ + if (qed_test_bit(check->used_clusters, cluster)) { + corruptions++; + } + + qed_set_bit(check->used_clusters, cluster); + cluster++; + } + + check->result->corruptions += corruptions; + return corruptions == 0; +} + +/** + * Check an L2 table + * + * @ret: Number of invalid cluster offsets + */ +static unsigned int qed_check_l2_table(QEDCheck *check, QEDTable *table) +{ + BDRVQEDState *s = check->s; + unsigned int i, num_invalid = 0; + + for (i = 0; i < s->table_nelems; i++) { + uint64_t offset = table->offsets[i]; + + if (!offset) { + continue; + } + + /* Detect invalid cluster offset */ + if (!qed_check_cluster_offset(s, offset)) { + if (check->fix) { + table->offsets[i] = 0; + } else { + check->result->corruptions++; + } + + num_invalid++; + continue; + } + + qed_set_used_clusters(check, offset, 1); + } + + return num_invalid; +} + +/** + * Descend tables and check each cluster is referenced once only + */ +static int qed_check_l1_table(QEDCheck *check, QEDTable *table) +{ + BDRVQEDState *s = check->s; + unsigned int i, num_invalid_l1 = 0; + int ret, last_error = 0; + + /* Mark L1 table clusters used */ + qed_set_used_clusters(check, s->header.l1_table_offset, + s->header.table_size); + + for (i = 0; i < s->table_nelems; i++) { + unsigned int num_invalid_l2; + uint64_t offset = table->offsets[i]; + + if (!offset) { + continue; + } + + /* Detect invalid L2 offset */ + if (!qed_check_table_offset(s, offset)) { + /* Clear invalid offset */ + if (check->fix) { + table->offsets[i] = 0; + } else { + check->result->corruptions++; + } + + num_invalid_l1++; + continue; + } + + if (!qed_set_used_clusters(check, offset, s->header.table_size)) { + continue; /* skip an invalid table */ + } + + ret = qed_read_l2_table_sync(s, &check->request, offset); + if (ret) { + check->result->check_errors++; + last_error = ret; + continue; + } + + num_invalid_l2 = qed_check_l2_table(check, + check->request.l2_table->table); + + /* Write out fixed L2 table */ + if (num_invalid_l2 > 0 && check->fix) { + ret = qed_write_l2_table_sync(s, &check->request, 0, + s->table_nelems, false); + if (ret) { + check->result->check_errors++; + last_error = ret; + continue; + } + } + } + + /* Drop reference to final table */ + qed_unref_l2_cache_entry(check->request.l2_table); + check->request.l2_table = NULL; + + /* Write out fixed L1 table */ + if (num_invalid_l1 > 0 && check->fix) { + ret = qed_write_l1_table_sync(s, 0, s->table_nelems); + if (ret) { + check->result->check_errors++; + last_error = ret; + } + } + + return last_error; +} + +/** + * Check for unreferenced (leaked) clusters + */ +static void qed_check_for_leaks(QEDCheck *check) +{ + BDRVQEDState *s = check->s; + uint64_t i; + + for (i = s->header.header_size; i < check->nclusters; i++) { + if (!qed_test_bit(check->used_clusters, i)) { + check->result->leaks++; + } + } +} + +int qed_check(BDRVQEDState *s, BdrvCheckResult *result, bool fix) +{ + QEDCheck check = { + .s = s, + .result = result, + .nclusters = qed_bytes_to_clusters(s, s->file_size), + .request = { .l2_table = NULL }, + .fix = fix, + }; + int ret; + + check.used_clusters = qemu_mallocz(((check.nclusters + 31) / 32) * + sizeof(check.used_clusters[0])); + + ret = qed_check_l1_table(&check, s->l1_table); + if (ret == 0) { + /* Only check for leaks if entire image was scanned successfully */ + qed_check_for_leaks(&check); + } + + qemu_free(check.used_clusters); + return ret; +} diff --git a/block/qed-cluster.c b/block/qed-cluster.c new file mode 100644 index 0000000..0ec864b --- /dev/null +++ b/block/qed-cluster.c @@ -0,0 +1,154 @@ +/* + * QEMU Enhanced Disk Format Cluster functions + * + * Copyright IBM, Corp. 2010 + * + * Authors: + * Stefan Hajnoczi + * Anthony Liguori + * + * 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 "qed.h" + +/** + * Count the number of contiguous data clusters + * + * @s: QED state + * @table: L2 table + * @index: First cluster index + * @n: Maximum number of clusters + * @offset: Set to first cluster offset + * + * This function scans tables for contiguous allocated or free clusters. + */ +static unsigned int qed_count_contiguous_clusters(BDRVQEDState *s, + QEDTable *table, + unsigned int index, + unsigned int n, + uint64_t *offset) +{ + unsigned int end = MIN(index + n, s->table_nelems); + uint64_t last = table->offsets[index]; + unsigned int i; + + *offset = last; + + for (i = index + 1; i < end; i++) { + if (last == 0) { + /* Counting free clusters */ + if (table->offsets[i] != 0) { + break; + } + } else { + /* Counting allocated clusters */ + if (table->offsets[i] != last + s->header.cluster_size) { + break; + } + last = table->offsets[i]; + } + } + return i - index; +} + +typedef struct { + BDRVQEDState *s; + uint64_t pos; + size_t len; + + QEDRequest *request; + + /* User callback */ + QEDFindClusterFunc *cb; + void *opaque; +} QEDFindClusterCB; + +static void qed_find_cluster_cb(void *opaque, int ret) +{ + QEDFindClusterCB *find_cluster_cb = opaque; + BDRVQEDState *s = find_cluster_cb->s; + QEDRequest *request = find_cluster_cb->request; + uint64_t offset = 0; + size_t len = 0; + unsigned int index; + unsigned int n; + + if (ret) { + goto out; + } + + index = qed_l2_index(s, find_cluster_cb->pos); + n = qed_bytes_to_clusters(s, + qed_offset_into_cluster(s, find_cluster_cb->pos) + + find_cluster_cb->len); + n = qed_count_contiguous_clusters(s, request->l2_table->table, + index, n, &offset); + + ret = offset ? QED_CLUSTER_FOUND : QED_CLUSTER_L2; + len = MIN(find_cluster_cb->len, n * s->header.cluster_size - + qed_offset_into_cluster(s, find_cluster_cb->pos)); + + if (offset && !qed_check_cluster_offset(s, offset)) { + ret = -EINVAL; + } + +out: + find_cluster_cb->cb(find_cluster_cb->opaque, ret, offset, len); + qemu_free(find_cluster_cb); +} + +/** + * Find the offset of a data cluster + * + * @s: QED state + * @request: L2 cache entry + * @pos: Byte position in device + * @len: Number of bytes + * @cb: Completion function + * @opaque: User data for completion function + * + * This function translates a position in the block device to an offset in the + * image file. It invokes the cb completion callback to report back the + * translated offset or unallocated range in the image file. + * + * If the L2 table exists, request->l2_table points to the L2 table cache entry + * and the caller must free the reference when they are finished. The cache + * entry is exposed in this way to avoid callers having to read the L2 table + * again later during request processing. If request->l2_table is non-NULL it + * will be unreferenced before taking on the new cache entry. + */ +void qed_find_cluster(BDRVQEDState *s, QEDRequest *request, uint64_t pos, + size_t len, QEDFindClusterFunc *cb, void *opaque) +{ + QEDFindClusterCB *find_cluster_cb; + uint64_t l2_offset; + + /* Limit length to L2 boundary. Requests are broken up at the L2 boundary + * so that a request acts on one L2 table at a time. + */ + len = MIN(len, (((pos >> s->l1_shift) + 1) << s->l1_shift) - pos); + + l2_offset = s->l1_table->offsets[qed_l1_index(s, pos)]; + if (!l2_offset) { + cb(opaque, QED_CLUSTER_L1, 0, len); + return; + } + if (!qed_check_table_offset(s, l2_offset)) { + cb(opaque, -EINVAL, 0, 0); + return; + } + + find_cluster_cb = qemu_malloc(sizeof(*find_cluster_cb)); + find_cluster_cb->s = s; + find_cluster_cb->pos = pos; + find_cluster_cb->len = len; + find_cluster_cb->cb = cb; + find_cluster_cb->opaque = opaque; + find_cluster_cb->request = request; + + qed_read_l2_table(s, request, l2_offset, + qed_find_cluster_cb, find_cluster_cb); +} diff --git a/block/qed-gencb.c b/block/qed-gencb.c new file mode 100644 index 0000000..1513dc6 --- /dev/null +++ b/block/qed-gencb.c @@ -0,0 +1,32 @@ +/* + * QEMU Enhanced Disk Format + * + * Copyright IBM, Corp. 2010 + * + * Authors: + * Stefan Hajnoczi + * + * 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 "qed.h" + +void *gencb_alloc(size_t len, BlockDriverCompletionFunc *cb, void *opaque) +{ + GenericCB *gencb = qemu_malloc(len); + gencb->cb = cb; + gencb->opaque = opaque; + return gencb; +} + +void gencb_complete(void *opaque, int ret) +{ + GenericCB *gencb = opaque; + BlockDriverCompletionFunc *cb = gencb->cb; + void *user_opaque = gencb->opaque; + + qemu_free(gencb); + cb(user_opaque, ret); +} diff --git a/block/qed-l2-cache.c b/block/qed-l2-cache.c new file mode 100644 index 0000000..57518a4 --- /dev/null +++ b/block/qed-l2-cache.c @@ -0,0 +1,173 @@ +/* + * QEMU Enhanced Disk Format L2 Cache + * + * Copyright IBM, Corp. 2010 + * + * Authors: + * Anthony Liguori + * + * 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. + * + */ + +/* + * L2 table cache usage is as follows: + * + * An open image has one L2 table cache that is used to avoid accessing the + * image file for recently referenced L2 tables. + * + * Cluster offset lookup translates the logical offset within the block device + * to a cluster offset within the image file. This is done by indexing into + * the L1 and L2 tables which store cluster offsets. It is here where the L2 + * table cache serves up recently referenced L2 tables. + * + * If there is a cache miss, that L2 table is read from the image file and + * committed to the cache. Subsequent accesses to that L2 table will be served + * from the cache until the table is evicted from the cache. + * + * L2 tables are also committed to the cache when new L2 tables are allocated + * in the image file. Since the L2 table cache is write-through, the new L2 + * table is first written out to the image file and then committed to the + * cache. + * + * Multiple I/O requests may be using an L2 table cache entry at any given + * time. That means an entry may be in use across several requests and + * reference counting is needed to free the entry at the correct time. In + * particular, an entry evicted from the cache will only be freed once all + * references are dropped. + * + * An in-flight I/O request will hold a reference to a L2 table cache entry for + * the period during which it needs to access the L2 table. This includes + * cluster offset lookup, L2 table allocation, and L2 table update when a new + * data cluster has been allocated. + * + * An interesting case occurs when two requests need to access an L2 table that + * is not in the cache. Since the operation to read the table from the image + * file takes some time to complete, both requests may see a cache miss and + * start reading the L2 table from the image file. The first to finish will + * commit its L2 table into the cache. When the second tries to commit its + * table will be deleted in favor of the existing cache entry. + */ + +#include "trace.h" +#include "qed.h" + +/* Each L2 holds 2GB so this let's us fully cache a 100GB disk */ +#define MAX_L2_CACHE_SIZE 50 + +/** + * Initialize the L2 cache + */ +void qed_init_l2_cache(L2TableCache *l2_cache) +{ + QTAILQ_INIT(&l2_cache->entries); + l2_cache->n_entries = 0; +} + +/** + * Free the L2 cache + */ +void qed_free_l2_cache(L2TableCache *l2_cache) +{ + CachedL2Table *entry, *next_entry; + + QTAILQ_FOREACH_SAFE(entry, &l2_cache->entries, node, next_entry) { + qemu_vfree(entry->table); + qemu_free(entry); + } +} + +/** + * Allocate an uninitialized entry from the cache + * + * The returned entry has a reference count of 1 and is owned by the caller. + * The caller must allocate the actual table field for this entry and it must + * be freeable using qemu_vfree(). + */ +CachedL2Table *qed_alloc_l2_cache_entry(L2TableCache *l2_cache) +{ + CachedL2Table *entry; + + entry = qemu_mallocz(sizeof(*entry)); + entry->ref++; + + trace_qed_alloc_l2_cache_entry(l2_cache, entry); + + return entry; +} + +/** + * Decrease an entry's reference count and free if necessary when the reference + * count drops to zero. + */ +void qed_unref_l2_cache_entry(CachedL2Table *entry) +{ + if (!entry) { + return; + } + + entry->ref--; + trace_qed_unref_l2_cache_entry(entry, entry->ref); + if (entry->ref == 0) { + qemu_vfree(entry->table); + qemu_free(entry); + } +} + +/** + * Find an entry in the L2 cache. This may return NULL and it's up to the + * caller to satisfy the cache miss. + * + * For a cached entry, this function increases the reference count and returns + * the entry. + */ +CachedL2Table *qed_find_l2_cache_entry(L2TableCache *l2_cache, uint64_t offset) +{ + CachedL2Table *entry; + + QTAILQ_FOREACH(entry, &l2_cache->entries, node) { + if (entry->offset == offset) { + trace_qed_find_l2_cache_entry(l2_cache, entry, offset, entry->ref); + entry->ref++; + return entry; + } + } + return NULL; +} + +/** + * Commit an L2 cache entry into the cache. This is meant to be used as part of + * the process to satisfy a cache miss. A caller would allocate an entry which + * is not actually in the L2 cache and then once the entry was valid and + * present on disk, the entry can be committed into the cache. + * + * Since the cache is write-through, it's important that this function is not + * called until the entry is present on disk and the L1 has been updated to + * point to the entry. + * + * N.B. This function steals a reference to the l2_table from the caller so the + * caller must obtain a new reference by issuing a call to + * qed_find_l2_cache_entry(). + */ +void qed_commit_l2_cache_entry(L2TableCache *l2_cache, CachedL2Table *l2_table) +{ + CachedL2Table *entry; + + entry = qed_find_l2_cache_entry(l2_cache, l2_table->offset); + if (entry) { + qed_unref_l2_cache_entry(entry); + qed_unref_l2_cache_entry(l2_table); + return; + } + + if (l2_cache->n_entries >= MAX_L2_CACHE_SIZE) { + entry = QTAILQ_FIRST(&l2_cache->entries); + QTAILQ_REMOVE(&l2_cache->entries, entry, node); + l2_cache->n_entries--; + qed_unref_l2_cache_entry(entry); + } + + l2_cache->n_entries++; + QTAILQ_INSERT_TAIL(&l2_cache->entries, l2_table, node); +} diff --git a/block/qed-table.c b/block/qed-table.c new file mode 100644 index 0000000..d38c673 --- /dev/null +++ b/block/qed-table.c @@ -0,0 +1,319 @@ +/* + * QEMU Enhanced Disk Format Table I/O + * + * Copyright IBM, Corp. 2010 + * + * Authors: + * Stefan Hajnoczi + * Anthony Liguori + * + * 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 "trace.h" +#include "qemu_socket.h" /* for EINPROGRESS on Windows */ +#include "qed.h" + +typedef struct { + GenericCB gencb; + BDRVQEDState *s; + QEDTable *table; + + struct iovec iov; + QEMUIOVector qiov; +} QEDReadTableCB; + +static void qed_read_table_cb(void *opaque, int ret) +{ + QEDReadTableCB *read_table_cb = opaque; + QEDTable *table = read_table_cb->table; + int noffsets = read_table_cb->iov.iov_len / sizeof(uint64_t); + int i; + + /* Handle I/O error */ + if (ret) { + goto out; + } + + /* Byteswap offsets */ + for (i = 0; i < noffsets; i++) { + table->offsets[i] = le64_to_cpu(table->offsets[i]); + } + +out: + /* Completion */ + trace_qed_read_table_cb(read_table_cb->s, read_table_cb->table, ret); + gencb_complete(&read_table_cb->gencb, ret); +} + +static void qed_read_table(BDRVQEDState *s, uint64_t offset, QEDTable *table, + BlockDriverCompletionFunc *cb, void *opaque) +{ + QEDReadTableCB *read_table_cb = gencb_alloc(sizeof(*read_table_cb), + cb, opaque); + QEMUIOVector *qiov = &read_table_cb->qiov; + BlockDriverAIOCB *aiocb; + + trace_qed_read_table(s, offset, table); + + read_table_cb->s = s; + read_table_cb->table = table; + read_table_cb->iov.iov_base = table->offsets, + read_table_cb->iov.iov_len = s->header.cluster_size * s->header.table_size, + + qemu_iovec_init_external(qiov, &read_table_cb->iov, 1); + aiocb = bdrv_aio_readv(s->bs->file, offset / BDRV_SECTOR_SIZE, qiov, + read_table_cb->iov.iov_len / BDRV_SECTOR_SIZE, + qed_read_table_cb, read_table_cb); + if (!aiocb) { + qed_read_table_cb(read_table_cb, -EIO); + } +} + +typedef struct { + GenericCB gencb; + BDRVQEDState *s; + QEDTable *orig_table; + QEDTable *table; + bool flush; /* flush after write? */ + + struct iovec iov; + QEMUIOVector qiov; +} QEDWriteTableCB; + +static void qed_write_table_cb(void *opaque, int ret) +{ + QEDWriteTableCB *write_table_cb = opaque; + + trace_qed_write_table_cb(write_table_cb->s, + write_table_cb->orig_table, + write_table_cb->flush, + ret); + + if (ret) { + goto out; + } + + if (write_table_cb->flush) { + /* We still need to flush first */ + write_table_cb->flush = false; + bdrv_aio_flush(write_table_cb->s->bs, qed_write_table_cb, + write_table_cb); + return; + } + +out: + qemu_vfree(write_table_cb->table); + gencb_complete(&write_table_cb->gencb, ret); + return; +} + +/** + * Write out an updated part or all of a table + * + * @s: QED state + * @offset: Offset of table in image file, in bytes + * @table: Table + * @index: Index of first element + * @n: Number of elements + * @flush: Whether or not to sync to disk + * @cb: Completion function + * @opaque: Argument for completion function + */ +static void qed_write_table(BDRVQEDState *s, uint64_t offset, QEDTable *table, + unsigned int index, unsigned int n, bool flush, + BlockDriverCompletionFunc *cb, void *opaque) +{ + QEDWriteTableCB *write_table_cb; + BlockDriverAIOCB *aiocb; + unsigned int sector_mask = BDRV_SECTOR_SIZE / sizeof(uint64_t) - 1; + unsigned int start, end, i; + size_t len_bytes; + + trace_qed_write_table(s, offset, table, index, n); + + /* Calculate indices of the first and one after last elements */ + start = index & ~sector_mask; + end = (index + n + sector_mask) & ~sector_mask; + + len_bytes = (end - start) * sizeof(uint64_t); + + write_table_cb = gencb_alloc(sizeof(*write_table_cb), cb, opaque); + write_table_cb->s = s; + write_table_cb->orig_table = table; + write_table_cb->flush = flush; + write_table_cb->table = qemu_blockalign(s->bs, len_bytes); + write_table_cb->iov.iov_base = write_table_cb->table->offsets; + write_table_cb->iov.iov_len = len_bytes; + qemu_iovec_init_external(&write_table_cb->qiov, &write_table_cb->iov, 1); + + /* Byteswap table */ + for (i = start; i < end; i++) { + uint64_t le_offset = cpu_to_le64(table->offsets[i]); + write_table_cb->table->offsets[i - start] = le_offset; + } + + /* Adjust for offset into table */ + offset += start * sizeof(uint64_t); + + aiocb = bdrv_aio_writev(s->bs->file, offset / BDRV_SECTOR_SIZE, + &write_table_cb->qiov, + write_table_cb->iov.iov_len / BDRV_SECTOR_SIZE, + qed_write_table_cb, write_table_cb); + if (!aiocb) { + qed_write_table_cb(write_table_cb, -EIO); + } +} + +/** + * Propagate return value from async callback + */ +static void qed_sync_cb(void *opaque, int ret) +{ + *(int *)opaque = ret; +} + +int qed_read_l1_table_sync(BDRVQEDState *s) +{ + int ret = -EINPROGRESS; + + async_context_push(); + + qed_read_table(s, s->header.l1_table_offset, + s->l1_table, qed_sync_cb, &ret); + while (ret == -EINPROGRESS) { + qemu_aio_wait(); + } + + async_context_pop(); + + return ret; +} + +void qed_write_l1_table(BDRVQEDState *s, unsigned int index, unsigned int n, + BlockDriverCompletionFunc *cb, void *opaque) +{ + BLKDBG_EVENT(s->bs->file, BLKDBG_L1_UPDATE); + qed_write_table(s, s->header.l1_table_offset, + s->l1_table, index, n, false, cb, opaque); +} + +int qed_write_l1_table_sync(BDRVQEDState *s, unsigned int index, + unsigned int n) +{ + int ret = -EINPROGRESS; + + async_context_push(); + + qed_write_l1_table(s, index, n, qed_sync_cb, &ret); + while (ret == -EINPROGRESS) { + qemu_aio_wait(); + } + + async_context_pop(); + + return ret; +} + +typedef struct { + GenericCB gencb; + BDRVQEDState *s; + uint64_t l2_offset; + QEDRequest *request; +} QEDReadL2TableCB; + +static void qed_read_l2_table_cb(void *opaque, int ret) +{ + QEDReadL2TableCB *read_l2_table_cb = opaque; + QEDRequest *request = read_l2_table_cb->request; + BDRVQEDState *s = read_l2_table_cb->s; + CachedL2Table *l2_table = request->l2_table; + + if (ret) { + /* can't trust loaded L2 table anymore */ + qed_unref_l2_cache_entry(l2_table); + request->l2_table = NULL; + } else { + l2_table->offset = read_l2_table_cb->l2_offset; + + qed_commit_l2_cache_entry(&s->l2_cache, l2_table); + + /* This is guaranteed to succeed because we just committed the entry + * to the cache. + */ + request->l2_table = qed_find_l2_cache_entry(&s->l2_cache, + l2_table->offset); + assert(request->l2_table != NULL); + } + + gencb_complete(&read_l2_table_cb->gencb, ret); +} + +void qed_read_l2_table(BDRVQEDState *s, QEDRequest *request, uint64_t offset, + BlockDriverCompletionFunc *cb, void *opaque) +{ + QEDReadL2TableCB *read_l2_table_cb; + + qed_unref_l2_cache_entry(request->l2_table); + + /* Check for cached L2 entry */ + request->l2_table = qed_find_l2_cache_entry(&s->l2_cache, offset); + if (request->l2_table) { + cb(opaque, 0); + return; + } + + request->l2_table = qed_alloc_l2_cache_entry(&s->l2_cache); + request->l2_table->table = qed_alloc_table(s); + + read_l2_table_cb = gencb_alloc(sizeof(*read_l2_table_cb), cb, opaque); + read_l2_table_cb->s = s; + read_l2_table_cb->l2_offset = offset; + read_l2_table_cb->request = request; + + BLKDBG_EVENT(s->bs->file, BLKDBG_L2_LOAD); + qed_read_table(s, offset, request->l2_table->table, + qed_read_l2_table_cb, read_l2_table_cb); +} + +int qed_read_l2_table_sync(BDRVQEDState *s, QEDRequest *request, uint64_t offset) +{ + int ret = -EINPROGRESS; + + async_context_push(); + + qed_read_l2_table(s, request, offset, qed_sync_cb, &ret); + while (ret == -EINPROGRESS) { + qemu_aio_wait(); + } + + async_context_pop(); + return ret; +} + +void qed_write_l2_table(BDRVQEDState *s, QEDRequest *request, + unsigned int index, unsigned int n, bool flush, + BlockDriverCompletionFunc *cb, void *opaque) +{ + BLKDBG_EVENT(s->bs->file, BLKDBG_L2_UPDATE); + qed_write_table(s, request->l2_table->offset, + request->l2_table->table, index, n, flush, cb, opaque); +} + +int qed_write_l2_table_sync(BDRVQEDState *s, QEDRequest *request, + unsigned int index, unsigned int n, bool flush) +{ + int ret = -EINPROGRESS; + + async_context_push(); + + qed_write_l2_table(s, request, index, n, flush, qed_sync_cb, &ret); + while (ret == -EINPROGRESS) { + qemu_aio_wait(); + } + + async_context_pop(); + return ret; +} diff --git a/block/qed.c b/block/qed.c new file mode 100644 index 0000000..75ae244 --- /dev/null +++ b/block/qed.c @@ -0,0 +1,1372 @@ +/* + * QEMU Enhanced Disk Format + * + * Copyright IBM, Corp. 2010 + * + * Authors: + * Stefan Hajnoczi + * Anthony Liguori + * + * 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 "trace.h" +#include "qed.h" +#include "qerror.h" + +static void qed_aio_cancel(BlockDriverAIOCB *blockacb) +{ + QEDAIOCB *acb = (QEDAIOCB *)blockacb; + bool finished = false; + + /* Wait for the request to finish */ + acb->finished = &finished; + while (!finished) { + qemu_aio_wait(); + } +} + +static AIOPool qed_aio_pool = { + .aiocb_size = sizeof(QEDAIOCB), + .cancel = qed_aio_cancel, +}; + +static int bdrv_qed_probe(const uint8_t *buf, int buf_size, + const char *filename) +{ + const QEDHeader *header = (const QEDHeader *)buf; + + if (buf_size < sizeof(*header)) { + return 0; + } + if (le32_to_cpu(header->magic) != QED_MAGIC) { + return 0; + } + return 100; +} + +/** + * Check whether an image format is raw + * + * @fmt: Backing file format, may be NULL + */ +static bool qed_fmt_is_raw(const char *fmt) +{ + return fmt && strcmp(fmt, "raw") == 0; +} + +static void qed_header_le_to_cpu(const QEDHeader *le, QEDHeader *cpu) +{ + cpu->magic = le32_to_cpu(le->magic); + cpu->cluster_size = le32_to_cpu(le->cluster_size); + cpu->table_size = le32_to_cpu(le->table_size); + cpu->header_size = le32_to_cpu(le->header_size); + cpu->features = le64_to_cpu(le->features); + cpu->compat_features = le64_to_cpu(le->compat_features); + cpu->autoclear_features = le64_to_cpu(le->autoclear_features); + cpu->l1_table_offset = le64_to_cpu(le->l1_table_offset); + cpu->image_size = le64_to_cpu(le->image_size); + cpu->backing_filename_offset = le32_to_cpu(le->backing_filename_offset); + cpu->backing_filename_size = le32_to_cpu(le->backing_filename_size); +} + +static void qed_header_cpu_to_le(const QEDHeader *cpu, QEDHeader *le) +{ + le->magic = cpu_to_le32(cpu->magic); + le->cluster_size = cpu_to_le32(cpu->cluster_size); + le->table_size = cpu_to_le32(cpu->table_size); + le->header_size = cpu_to_le32(cpu->header_size); + le->features = cpu_to_le64(cpu->features); + le->compat_features = cpu_to_le64(cpu->compat_features); + le->autoclear_features = cpu_to_le64(cpu->autoclear_features); + le->l1_table_offset = cpu_to_le64(cpu->l1_table_offset); + le->image_size = cpu_to_le64(cpu->image_size); + le->backing_filename_offset = cpu_to_le32(cpu->backing_filename_offset); + le->backing_filename_size = cpu_to_le32(cpu->backing_filename_size); +} + +static int qed_write_header_sync(BDRVQEDState *s) +{ + QEDHeader le; + int ret; + + qed_header_cpu_to_le(&s->header, &le); + ret = bdrv_pwrite(s->bs->file, 0, &le, sizeof(le)); + if (ret != sizeof(le)) { + return ret; + } + return 0; +} + +typedef struct { + GenericCB gencb; + BDRVQEDState *s; + struct iovec iov; + QEMUIOVector qiov; + int nsectors; + uint8_t *buf; +} QEDWriteHeaderCB; + +static void qed_write_header_cb(void *opaque, int ret) +{ + QEDWriteHeaderCB *write_header_cb = opaque; + + qemu_vfree(write_header_cb->buf); + gencb_complete(write_header_cb, ret); +} + +static void qed_write_header_read_cb(void *opaque, int ret) +{ + QEDWriteHeaderCB *write_header_cb = opaque; + BDRVQEDState *s = write_header_cb->s; + BlockDriverAIOCB *acb; + + if (ret) { + qed_write_header_cb(write_header_cb, ret); + return; + } + + /* Update header */ + qed_header_cpu_to_le(&s->header, (QEDHeader *)write_header_cb->buf); + + acb = bdrv_aio_writev(s->bs->file, 0, &write_header_cb->qiov, + write_header_cb->nsectors, qed_write_header_cb, + write_header_cb); + if (!acb) { + qed_write_header_cb(write_header_cb, -EIO); + } +} + +/** + * Update header in-place (does not rewrite backing filename or other strings) + * + * This function only updates known header fields in-place and does not affect + * extra data after the QED header. + */ +static void qed_write_header(BDRVQEDState *s, BlockDriverCompletionFunc cb, + void *opaque) +{ + /* We must write full sectors for O_DIRECT but cannot necessarily generate + * the data following the header if an unrecognized compat feature is + * active. Therefore, first read the sectors containing the header, update + * them, and write back. + */ + + BlockDriverAIOCB *acb; + int nsectors = (sizeof(QEDHeader) + BDRV_SECTOR_SIZE - 1) / + BDRV_SECTOR_SIZE; + size_t len = nsectors * BDRV_SECTOR_SIZE; + QEDWriteHeaderCB *write_header_cb = gencb_alloc(sizeof(*write_header_cb), + cb, opaque); + + write_header_cb->s = s; + write_header_cb->nsectors = nsectors; + write_header_cb->buf = qemu_blockalign(s->bs, len); + write_header_cb->iov.iov_base = write_header_cb->buf; + write_header_cb->iov.iov_len = len; + qemu_iovec_init_external(&write_header_cb->qiov, &write_header_cb->iov, 1); + + acb = bdrv_aio_readv(s->bs->file, 0, &write_header_cb->qiov, nsectors, + qed_write_header_read_cb, write_header_cb); + if (!acb) { + qed_write_header_cb(write_header_cb, -EIO); + } +} + +static uint64_t qed_max_image_size(uint32_t cluster_size, uint32_t table_size) +{ + uint64_t table_entries; + uint64_t l2_size; + + table_entries = (table_size * cluster_size) / sizeof(uint64_t); + l2_size = table_entries * cluster_size; + + return l2_size * table_entries; +} + +static bool qed_is_cluster_size_valid(uint32_t cluster_size) +{ + if (cluster_size < QED_MIN_CLUSTER_SIZE || + cluster_size > QED_MAX_CLUSTER_SIZE) { + return false; + } + if (cluster_size & (cluster_size - 1)) { + return false; /* not power of 2 */ + } + return true; +} + +static bool qed_is_table_size_valid(uint32_t table_size) +{ + if (table_size < QED_MIN_TABLE_SIZE || + table_size > QED_MAX_TABLE_SIZE) { + return false; + } + if (table_size & (table_size - 1)) { + return false; /* not power of 2 */ + } + return true; +} + +static bool qed_is_image_size_valid(uint64_t image_size, uint32_t cluster_size, + uint32_t table_size) +{ + if (image_size % BDRV_SECTOR_SIZE != 0) { + return false; /* not multiple of sector size */ + } + if (image_size > qed_max_image_size(cluster_size, table_size)) { + return false; /* image is too large */ + } + return true; +} + +/** + * Read a string of known length from the image file + * + * @file: Image file + * @offset: File offset to start of string, in bytes + * @n: String length in bytes + * @buf: Destination buffer + * @buflen: Destination buffer length in bytes + * @ret: 0 on success, -errno on failure + * + * The string is NUL-terminated. + */ +static int qed_read_string(BlockDriverState *file, uint64_t offset, size_t n, + char *buf, size_t buflen) +{ + int ret; + if (n >= buflen) { + return -EINVAL; + } + ret = bdrv_pread(file, offset, buf, n); + if (ret < 0) { + return ret; + } + buf[n] = '\0'; + return 0; +} + +/** + * Allocate new clusters + * + * @s: QED state + * @n: Number of contiguous clusters to allocate + * @ret: Offset of first allocated cluster + * + * This function only produces the offset where the new clusters should be + * written. It updates BDRVQEDState but does not make any changes to the image + * file. + */ +static uint64_t qed_alloc_clusters(BDRVQEDState *s, unsigned int n) +{ + uint64_t offset = s->file_size; + s->file_size += n * s->header.cluster_size; + return offset; +} + +QEDTable *qed_alloc_table(BDRVQEDState *s) +{ + /* Honor O_DIRECT memory alignment requirements */ + return qemu_blockalign(s->bs, + s->header.cluster_size * s->header.table_size); +} + +/** + * Allocate a new zeroed L2 table + */ +static CachedL2Table *qed_new_l2_table(BDRVQEDState *s) +{ + CachedL2Table *l2_table = qed_alloc_l2_cache_entry(&s->l2_cache); + + l2_table->table = qed_alloc_table(s); + l2_table->offset = qed_alloc_clusters(s, s->header.table_size); + + memset(l2_table->table->offsets, 0, + s->header.cluster_size * s->header.table_size); + return l2_table; +} + +static void qed_aio_next_io(void *opaque, int ret); + +static int bdrv_qed_open(BlockDriverState *bs, int flags) +{ + BDRVQEDState *s = bs->opaque; + QEDHeader le_header; + int64_t file_size; + int ret; + + s->bs = bs; + QSIMPLEQ_INIT(&s->allocating_write_reqs); + + ret = bdrv_pread(bs->file, 0, &le_header, sizeof(le_header)); + if (ret < 0) { + return ret; + } + ret = 0; /* ret should always be 0 or -errno */ + qed_header_le_to_cpu(&le_header, &s->header); + + if (s->header.magic != QED_MAGIC) { + return -EINVAL; + } + if (s->header.features & ~QED_FEATURE_MASK) { + /* image uses unsupported feature bits */ + char buf[64]; + snprintf(buf, sizeof(buf), "%" PRIx64, + s->header.features & ~QED_FEATURE_MASK); + qerror_report(QERR_UNKNOWN_BLOCK_FORMAT_FEATURE, + bs->device_name, "QED", buf); + return -ENOTSUP; + } + if (!qed_is_cluster_size_valid(s->header.cluster_size)) { + return -EINVAL; + } + + /* Round down file size to the last cluster */ + file_size = bdrv_getlength(bs->file); + if (file_size < 0) { + return file_size; + } + s->file_size = qed_start_of_cluster(s, file_size); + + if (!qed_is_table_size_valid(s->header.table_size)) { + return -EINVAL; + } + if (!qed_is_image_size_valid(s->header.image_size, + s->header.cluster_size, + s->header.table_size)) { + return -EINVAL; + } + if (!qed_check_table_offset(s, s->header.l1_table_offset)) { + return -EINVAL; + } + + s->table_nelems = (s->header.cluster_size * s->header.table_size) / + sizeof(uint64_t); + s->l2_shift = ffs(s->header.cluster_size) - 1; + s->l2_mask = s->table_nelems - 1; + s->l1_shift = s->l2_shift + ffs(s->table_nelems) - 1; + + if ((s->header.features & QED_F_BACKING_FILE)) { + if ((uint64_t)s->header.backing_filename_offset + + s->header.backing_filename_size > + s->header.cluster_size * s->header.header_size) { + return -EINVAL; + } + + ret = qed_read_string(bs->file, s->header.backing_filename_offset, + s->header.backing_filename_size, bs->backing_file, + sizeof(bs->backing_file)); + if (ret < 0) { + return ret; + } + + if (s->header.features & QED_F_BACKING_FORMAT_NO_PROBE) { + pstrcpy(bs->backing_format, sizeof(bs->backing_format), "raw"); + } + } + + /* Reset unknown autoclear feature bits. This is a backwards + * compatibility mechanism that allows images to be opened by older + * programs, which "knock out" unknown feature bits. When an image is + * opened by a newer program again it can detect that the autoclear + * feature is no longer valid. + */ + if ((s->header.autoclear_features & ~QED_AUTOCLEAR_FEATURE_MASK) != 0 && + !bdrv_is_read_only(bs->file)) { + s->header.autoclear_features &= QED_AUTOCLEAR_FEATURE_MASK; + + ret = qed_write_header_sync(s); + if (ret) { + return ret; + } + + /* From here on only known autoclear feature bits are valid */ + bdrv_flush(bs->file); + } + + s->l1_table = qed_alloc_table(s); + qed_init_l2_cache(&s->l2_cache); + + ret = qed_read_l1_table_sync(s); + if (ret) { + goto out; + } + + /* If image was not closed cleanly, check consistency */ + if (s->header.features & QED_F_NEED_CHECK) { + /* Read-only images cannot be fixed. There is no risk of corruption + * since write operations are not possible. Therefore, allow + * potentially inconsistent images to be opened read-only. This can + * aid data recovery from an otherwise inconsistent image. + */ + if (!bdrv_is_read_only(bs->file)) { + BdrvCheckResult result = {0}; + + ret = qed_check(s, &result, true); + if (!ret && !result.corruptions && !result.check_errors) { + /* Ensure fixes reach storage before clearing check bit */ + bdrv_flush(s->bs); + + s->header.features &= ~QED_F_NEED_CHECK; + qed_write_header_sync(s); + } + } + } + +out: + if (ret) { + qed_free_l2_cache(&s->l2_cache); + qemu_vfree(s->l1_table); + } + return ret; +} + +static void bdrv_qed_close(BlockDriverState *bs) +{ + BDRVQEDState *s = bs->opaque; + + /* Ensure writes reach stable storage */ + bdrv_flush(bs->file); + + /* Clean shutdown, no check required on next open */ + if (s->header.features & QED_F_NEED_CHECK) { + s->header.features &= ~QED_F_NEED_CHECK; + qed_write_header_sync(s); + } + + qed_free_l2_cache(&s->l2_cache); + qemu_vfree(s->l1_table); +} + +static int bdrv_qed_flush(BlockDriverState *bs) +{ + return bdrv_flush(bs->file); +} + +static int qed_create(const char *filename, uint32_t cluster_size, + uint64_t image_size, uint32_t table_size, + const char *backing_file, const char *backing_fmt) +{ + QEDHeader header = { + .magic = QED_MAGIC, + .cluster_size = cluster_size, + .table_size = table_size, + .header_size = 1, + .features = 0, + .compat_features = 0, + .l1_table_offset = cluster_size, + .image_size = image_size, + }; + QEDHeader le_header; + uint8_t *l1_table = NULL; + size_t l1_size = header.cluster_size * header.table_size; + int ret = 0; + BlockDriverState *bs = NULL; + + ret = bdrv_create_file(filename, NULL); + if (ret < 0) { + return ret; + } + + ret = bdrv_file_open(&bs, filename, BDRV_O_RDWR | BDRV_O_CACHE_WB); + if (ret < 0) { + return ret; + } + + /* File must start empty and grow, check truncate is supported */ + ret = bdrv_truncate(bs, 0); + if (ret < 0) { + goto out; + } + + if (backing_file) { + header.features |= QED_F_BACKING_FILE; + header.backing_filename_offset = sizeof(le_header); + header.backing_filename_size = strlen(backing_file); + + if (qed_fmt_is_raw(backing_fmt)) { + header.features |= QED_F_BACKING_FORMAT_NO_PROBE; + } + } + + qed_header_cpu_to_le(&header, &le_header); + ret = bdrv_pwrite(bs, 0, &le_header, sizeof(le_header)); + if (ret < 0) { + goto out; + } + ret = bdrv_pwrite(bs, sizeof(le_header), backing_file, + header.backing_filename_size); + if (ret < 0) { + goto out; + } + + l1_table = qemu_mallocz(l1_size); + ret = bdrv_pwrite(bs, header.l1_table_offset, l1_table, l1_size); + if (ret < 0) { + goto out; + } + + ret = 0; /* success */ +out: + qemu_free(l1_table); + bdrv_delete(bs); + return ret; +} + +static int bdrv_qed_create(const char *filename, QEMUOptionParameter *options) +{ + uint64_t image_size = 0; + uint32_t cluster_size = QED_DEFAULT_CLUSTER_SIZE; + uint32_t table_size = QED_DEFAULT_TABLE_SIZE; + const char *backing_file = NULL; + const char *backing_fmt = NULL; + + while (options && options->name) { + if (!strcmp(options->name, BLOCK_OPT_SIZE)) { + image_size = options->value.n; + } else if (!strcmp(options->name, BLOCK_OPT_BACKING_FILE)) { + backing_file = options->value.s; + } else if (!strcmp(options->name, BLOCK_OPT_BACKING_FMT)) { + backing_fmt = options->value.s; + } else if (!strcmp(options->name, BLOCK_OPT_CLUSTER_SIZE)) { + if (options->value.n) { + cluster_size = options->value.n; + } + } else if (!strcmp(options->name, BLOCK_OPT_TABLE_SIZE)) { + if (options->value.n) { + table_size = options->value.n; + } + } + options++; + } + + if (!qed_is_cluster_size_valid(cluster_size)) { + fprintf(stderr, "QED cluster size must be within range [%u, %u] and power of 2\n", + QED_MIN_CLUSTER_SIZE, QED_MAX_CLUSTER_SIZE); + return -EINVAL; + } + if (!qed_is_table_size_valid(table_size)) { + fprintf(stderr, "QED table size must be within range [%u, %u] and power of 2\n", + QED_MIN_TABLE_SIZE, QED_MAX_TABLE_SIZE); + return -EINVAL; + } + if (!qed_is_image_size_valid(image_size, cluster_size, table_size)) { + fprintf(stderr, "QED image size must be a non-zero multiple of " + "cluster size and less than %" PRIu64 " bytes\n", + qed_max_image_size(cluster_size, table_size)); + return -EINVAL; + } + + return qed_create(filename, cluster_size, image_size, table_size, + backing_file, backing_fmt); +} + +typedef struct { + int is_allocated; + int *pnum; +} QEDIsAllocatedCB; + +static void qed_is_allocated_cb(void *opaque, int ret, uint64_t offset, size_t len) +{ + QEDIsAllocatedCB *cb = opaque; + *cb->pnum = len / BDRV_SECTOR_SIZE; + cb->is_allocated = ret == QED_CLUSTER_FOUND; +} + +static int bdrv_qed_is_allocated(BlockDriverState *bs, int64_t sector_num, + int nb_sectors, int *pnum) +{ + BDRVQEDState *s = bs->opaque; + uint64_t pos = (uint64_t)sector_num * BDRV_SECTOR_SIZE; + size_t len = (size_t)nb_sectors * BDRV_SECTOR_SIZE; + QEDIsAllocatedCB cb = { + .is_allocated = -1, + .pnum = pnum, + }; + QEDRequest request = { .l2_table = NULL }; + + async_context_push(); + + qed_find_cluster(s, &request, pos, len, qed_is_allocated_cb, &cb); + + while (cb.is_allocated == -1) { + qemu_aio_wait(); + } + + async_context_pop(); + + qed_unref_l2_cache_entry(request.l2_table); + + return cb.is_allocated; +} + +static int bdrv_qed_make_empty(BlockDriverState *bs) +{ + return -ENOTSUP; +} + +static BDRVQEDState *acb_to_s(QEDAIOCB *acb) +{ + return acb->common.bs->opaque; +} + +/** + * Read from the backing file or zero-fill if no backing file + * + * @s: QED state + * @pos: Byte position in device + * @qiov: Destination I/O vector + * @cb: Completion function + * @opaque: User data for completion function + * + * This function reads qiov->size bytes starting at pos from the backing file. + * If there is no backing file then zeroes are read. + */ +static void qed_read_backing_file(BDRVQEDState *s, uint64_t pos, + QEMUIOVector *qiov, + BlockDriverCompletionFunc *cb, void *opaque) +{ + BlockDriverAIOCB *aiocb; + uint64_t backing_length = 0; + size_t size; + + /* If there is a backing file, get its length. Treat the absence of a + * backing file like a zero length backing file. + */ + if (s->bs->backing_hd) { + int64_t l = bdrv_getlength(s->bs->backing_hd); + if (l < 0) { + cb(opaque, l); + return; + } + backing_length = l; + } + + /* Zero all sectors if reading beyond the end of the backing file */ + if (pos >= backing_length || + pos + qiov->size > backing_length) { + qemu_iovec_memset(qiov, 0, qiov->size); + } + + /* Complete now if there are no backing file sectors to read */ + if (pos >= backing_length) { + cb(opaque, 0); + return; + } + + /* If the read straddles the end of the backing file, shorten it */ + size = MIN((uint64_t)backing_length - pos, qiov->size); + + BLKDBG_EVENT(s->bs->file, BLKDBG_READ_BACKING); + aiocb = bdrv_aio_readv(s->bs->backing_hd, pos / BDRV_SECTOR_SIZE, + qiov, size / BDRV_SECTOR_SIZE, cb, opaque); + if (!aiocb) { + cb(opaque, -EIO); + } +} + +typedef struct { + GenericCB gencb; + BDRVQEDState *s; + QEMUIOVector qiov; + struct iovec iov; + uint64_t offset; +} CopyFromBackingFileCB; + +static void qed_copy_from_backing_file_cb(void *opaque, int ret) +{ + CopyFromBackingFileCB *copy_cb = opaque; + qemu_vfree(copy_cb->iov.iov_base); + gencb_complete(©_cb->gencb, ret); +} + +static void qed_copy_from_backing_file_write(void *opaque, int ret) +{ + CopyFromBackingFileCB *copy_cb = opaque; + BDRVQEDState *s = copy_cb->s; + BlockDriverAIOCB *aiocb; + + if (ret) { + qed_copy_from_backing_file_cb(copy_cb, ret); + return; + } + + BLKDBG_EVENT(s->bs->file, BLKDBG_COW_WRITE); + aiocb = bdrv_aio_writev(s->bs->file, copy_cb->offset / BDRV_SECTOR_SIZE, + ©_cb->qiov, + copy_cb->qiov.size / BDRV_SECTOR_SIZE, + qed_copy_from_backing_file_cb, copy_cb); + if (!aiocb) { + qed_copy_from_backing_file_cb(copy_cb, -EIO); + } +} + +/** + * Copy data from backing file into the image + * + * @s: QED state + * @pos: Byte position in device + * @len: Number of bytes + * @offset: Byte offset in image file + * @cb: Completion function + * @opaque: User data for completion function + */ +static void qed_copy_from_backing_file(BDRVQEDState *s, uint64_t pos, + uint64_t len, uint64_t offset, + BlockDriverCompletionFunc *cb, + void *opaque) +{ + CopyFromBackingFileCB *copy_cb; + + /* Skip copy entirely if there is no work to do */ + if (len == 0) { + cb(opaque, 0); + return; + } + + copy_cb = gencb_alloc(sizeof(*copy_cb), cb, opaque); + copy_cb->s = s; + copy_cb->offset = offset; + copy_cb->iov.iov_base = qemu_blockalign(s->bs, len); + copy_cb->iov.iov_len = len; + qemu_iovec_init_external(©_cb->qiov, ©_cb->iov, 1); + + qed_read_backing_file(s, pos, ©_cb->qiov, + qed_copy_from_backing_file_write, copy_cb); +} + +/** + * Link one or more contiguous clusters into a table + * + * @s: QED state + * @table: L2 table + * @index: First cluster index + * @n: Number of contiguous clusters + * @cluster: First cluster byte offset in image file + */ +static void qed_update_l2_table(BDRVQEDState *s, QEDTable *table, int index, + unsigned int n, uint64_t cluster) +{ + int i; + for (i = index; i < index + n; i++) { + table->offsets[i] = cluster; + cluster += s->header.cluster_size; + } +} + +static void qed_aio_complete_bh(void *opaque) +{ + QEDAIOCB *acb = opaque; + BlockDriverCompletionFunc *cb = acb->common.cb; + void *user_opaque = acb->common.opaque; + int ret = acb->bh_ret; + bool *finished = acb->finished; + + qemu_bh_delete(acb->bh); + qemu_aio_release(acb); + + /* Invoke callback */ + cb(user_opaque, ret); + + /* Signal cancel completion */ + if (finished) { + *finished = true; + } +} + +static void qed_aio_complete(QEDAIOCB *acb, int ret) +{ + BDRVQEDState *s = acb_to_s(acb); + + trace_qed_aio_complete(s, acb, ret); + + /* Free resources */ + qemu_iovec_destroy(&acb->cur_qiov); + qed_unref_l2_cache_entry(acb->request.l2_table); + + /* Arrange for a bh to invoke the completion function */ + acb->bh_ret = ret; + acb->bh = qemu_bh_new(qed_aio_complete_bh, acb); + qemu_bh_schedule(acb->bh); + + /* Start next allocating write request waiting behind this one. Note that + * requests enqueue themselves when they first hit an unallocated cluster + * but they wait until the entire request is finished before waking up the + * next request in the queue. This ensures that we don't cycle through + * requests multiple times but rather finish one at a time completely. + */ + if (acb == QSIMPLEQ_FIRST(&s->allocating_write_reqs)) { + QSIMPLEQ_REMOVE_HEAD(&s->allocating_write_reqs, next); + acb = QSIMPLEQ_FIRST(&s->allocating_write_reqs); + if (acb) { + qed_aio_next_io(acb, 0); + } + } +} + +/** + * Commit the current L2 table to the cache + */ +static void qed_commit_l2_update(void *opaque, int ret) +{ + QEDAIOCB *acb = opaque; + BDRVQEDState *s = acb_to_s(acb); + CachedL2Table *l2_table = acb->request.l2_table; + + qed_commit_l2_cache_entry(&s->l2_cache, l2_table); + + /* This is guaranteed to succeed because we just committed the entry to the + * cache. + */ + acb->request.l2_table = qed_find_l2_cache_entry(&s->l2_cache, + l2_table->offset); + assert(acb->request.l2_table != NULL); + + qed_aio_next_io(opaque, ret); +} + +/** + * Update L1 table with new L2 table offset and write it out + */ +static void qed_aio_write_l1_update(void *opaque, int ret) +{ + QEDAIOCB *acb = opaque; + BDRVQEDState *s = acb_to_s(acb); + int index; + + if (ret) { + qed_aio_complete(acb, ret); + return; + } + + index = qed_l1_index(s, acb->cur_pos); + s->l1_table->offsets[index] = acb->request.l2_table->offset; + + qed_write_l1_table(s, index, 1, qed_commit_l2_update, acb); +} + +/** + * Update L2 table with new cluster offsets and write them out + */ +static void qed_aio_write_l2_update(void *opaque, int ret) +{ + QEDAIOCB *acb = opaque; + BDRVQEDState *s = acb_to_s(acb); + bool need_alloc = acb->find_cluster_ret == QED_CLUSTER_L1; + int index; + + if (ret) { + goto err; + } + + if (need_alloc) { + qed_unref_l2_cache_entry(acb->request.l2_table); + acb->request.l2_table = qed_new_l2_table(s); + } + + index = qed_l2_index(s, acb->cur_pos); + qed_update_l2_table(s, acb->request.l2_table->table, index, acb->cur_nclusters, + acb->cur_cluster); + + if (need_alloc) { + /* Write out the whole new L2 table */ + qed_write_l2_table(s, &acb->request, 0, s->table_nelems, true, + qed_aio_write_l1_update, acb); + } else { + /* Write out only the updated part of the L2 table */ + qed_write_l2_table(s, &acb->request, index, acb->cur_nclusters, false, + qed_aio_next_io, acb); + } + return; + +err: + qed_aio_complete(acb, ret); +} + +/** + * Flush new data clusters before updating the L2 table + * + * This flush is necessary when a backing file is in use. A crash during an + * allocating write could result in empty clusters in the image. If the write + * only touched a subregion of the cluster, then backing image sectors have + * been lost in the untouched region. The solution is to flush after writing a + * new data cluster and before updating the L2 table. + */ +static void qed_aio_write_flush_before_l2_update(void *opaque, int ret) +{ + QEDAIOCB *acb = opaque; + BDRVQEDState *s = acb_to_s(acb); + + if (!bdrv_aio_flush(s->bs->file, qed_aio_write_l2_update, opaque)) { + qed_aio_complete(acb, -EIO); + } +} + +/** + * Write data to the image file + */ +static void qed_aio_write_main(void *opaque, int ret) +{ + QEDAIOCB *acb = opaque; + BDRVQEDState *s = acb_to_s(acb); + uint64_t offset = acb->cur_cluster + + qed_offset_into_cluster(s, acb->cur_pos); + BlockDriverCompletionFunc *next_fn; + BlockDriverAIOCB *file_acb; + + trace_qed_aio_write_main(s, acb, ret, offset, acb->cur_qiov.size); + + if (ret) { + qed_aio_complete(acb, ret); + return; + } + + if (acb->find_cluster_ret == QED_CLUSTER_FOUND) { + next_fn = qed_aio_next_io; + } else { + if (s->bs->backing_hd) { + next_fn = qed_aio_write_flush_before_l2_update; + } else { + next_fn = qed_aio_write_l2_update; + } + } + + BLKDBG_EVENT(s->bs->file, BLKDBG_WRITE_AIO); + file_acb = bdrv_aio_writev(s->bs->file, offset / BDRV_SECTOR_SIZE, + &acb->cur_qiov, + acb->cur_qiov.size / BDRV_SECTOR_SIZE, + next_fn, acb); + if (!file_acb) { + qed_aio_complete(acb, -EIO); + } +} + +/** + * Populate back untouched region of new data cluster + */ +static void qed_aio_write_postfill(void *opaque, int ret) +{ + QEDAIOCB *acb = opaque; + BDRVQEDState *s = acb_to_s(acb); + uint64_t start = acb->cur_pos + acb->cur_qiov.size; + uint64_t len = + qed_start_of_cluster(s, start + s->header.cluster_size - 1) - start; + uint64_t offset = acb->cur_cluster + + qed_offset_into_cluster(s, acb->cur_pos) + + acb->cur_qiov.size; + + if (ret) { + qed_aio_complete(acb, ret); + return; + } + + trace_qed_aio_write_postfill(s, acb, start, len, offset); + qed_copy_from_backing_file(s, start, len, offset, + qed_aio_write_main, acb); +} + +/** + * Populate front untouched region of new data cluster + */ +static void qed_aio_write_prefill(void *opaque, int ret) +{ + QEDAIOCB *acb = opaque; + BDRVQEDState *s = acb_to_s(acb); + uint64_t start = qed_start_of_cluster(s, acb->cur_pos); + uint64_t len = qed_offset_into_cluster(s, acb->cur_pos); + + trace_qed_aio_write_prefill(s, acb, start, len, acb->cur_cluster); + qed_copy_from_backing_file(s, start, len, acb->cur_cluster, + qed_aio_write_postfill, acb); +} + +/** + * Check if the QED_F_NEED_CHECK bit should be set during allocating write + */ +static bool qed_should_set_need_check(BDRVQEDState *s) +{ + /* The flush before L2 update path ensures consistency */ + if (s->bs->backing_hd) { + return false; + } + + return !(s->header.features & QED_F_NEED_CHECK); +} + +/** + * Write new data cluster + * + * @acb: Write request + * @len: Length in bytes + * + * This path is taken when writing to previously unallocated clusters. + */ +static void qed_aio_write_alloc(QEDAIOCB *acb, size_t len) +{ + BDRVQEDState *s = acb_to_s(acb); + + /* Freeze this request if another allocating write is in progress */ + if (acb != QSIMPLEQ_FIRST(&s->allocating_write_reqs)) { + QSIMPLEQ_INSERT_TAIL(&s->allocating_write_reqs, acb, next); + } + if (acb != QSIMPLEQ_FIRST(&s->allocating_write_reqs)) { + return; /* wait for existing request to finish */ + } + + acb->cur_nclusters = qed_bytes_to_clusters(s, + qed_offset_into_cluster(s, acb->cur_pos) + len); + acb->cur_cluster = qed_alloc_clusters(s, acb->cur_nclusters); + qemu_iovec_copy(&acb->cur_qiov, acb->qiov, acb->qiov_offset, len); + + if (qed_should_set_need_check(s)) { + s->header.features |= QED_F_NEED_CHECK; + qed_write_header(s, qed_aio_write_prefill, acb); + } else { + qed_aio_write_prefill(acb, 0); + } +} + +/** + * Write data cluster in place + * + * @acb: Write request + * @offset: Cluster offset in bytes + * @len: Length in bytes + * + * This path is taken when writing to already allocated clusters. + */ +static void qed_aio_write_inplace(QEDAIOCB *acb, uint64_t offset, size_t len) +{ + /* Calculate the I/O vector */ + acb->cur_cluster = offset; + qemu_iovec_copy(&acb->cur_qiov, acb->qiov, acb->qiov_offset, len); + + /* Do the actual write */ + qed_aio_write_main(acb, 0); +} + +/** + * Write data cluster + * + * @opaque: Write request + * @ret: QED_CLUSTER_FOUND, QED_CLUSTER_L2, QED_CLUSTER_L1, + * or -errno + * @offset: Cluster offset in bytes + * @len: Length in bytes + * + * Callback from qed_find_cluster(). + */ +static void qed_aio_write_data(void *opaque, int ret, + uint64_t offset, size_t len) +{ + QEDAIOCB *acb = opaque; + + trace_qed_aio_write_data(acb_to_s(acb), acb, ret, offset, len); + + acb->find_cluster_ret = ret; + + switch (ret) { + case QED_CLUSTER_FOUND: + qed_aio_write_inplace(acb, offset, len); + break; + + case QED_CLUSTER_L2: + case QED_CLUSTER_L1: + qed_aio_write_alloc(acb, len); + break; + + default: + qed_aio_complete(acb, ret); + break; + } +} + +/** + * Read data cluster + * + * @opaque: Read request + * @ret: QED_CLUSTER_FOUND, QED_CLUSTER_L2, QED_CLUSTER_L1, + * or -errno + * @offset: Cluster offset in bytes + * @len: Length in bytes + * + * Callback from qed_find_cluster(). + */ +static void qed_aio_read_data(void *opaque, int ret, + uint64_t offset, size_t len) +{ + QEDAIOCB *acb = opaque; + BDRVQEDState *s = acb_to_s(acb); + BlockDriverState *bs = acb->common.bs; + BlockDriverAIOCB *file_acb; + + /* Adjust offset into cluster */ + offset += qed_offset_into_cluster(s, acb->cur_pos); + + trace_qed_aio_read_data(s, acb, ret, offset, len); + + if (ret < 0) { + goto err; + } + + qemu_iovec_copy(&acb->cur_qiov, acb->qiov, acb->qiov_offset, len); + + /* Handle backing file and unallocated sparse hole reads */ + if (ret != QED_CLUSTER_FOUND) { + qed_read_backing_file(s, acb->cur_pos, &acb->cur_qiov, + qed_aio_next_io, acb); + return; + } + + BLKDBG_EVENT(bs->file, BLKDBG_READ_AIO); + file_acb = bdrv_aio_readv(bs->file, offset / BDRV_SECTOR_SIZE, + &acb->cur_qiov, + acb->cur_qiov.size / BDRV_SECTOR_SIZE, + qed_aio_next_io, acb); + if (!file_acb) { + ret = -EIO; + goto err; + } + return; + +err: + qed_aio_complete(acb, ret); +} + +/** + * Begin next I/O or complete the request + */ +static void qed_aio_next_io(void *opaque, int ret) +{ + QEDAIOCB *acb = opaque; + BDRVQEDState *s = acb_to_s(acb); + QEDFindClusterFunc *io_fn = + acb->is_write ? qed_aio_write_data : qed_aio_read_data; + + trace_qed_aio_next_io(s, acb, ret, acb->cur_pos + acb->cur_qiov.size); + + /* Handle I/O error */ + if (ret) { + qed_aio_complete(acb, ret); + return; + } + + acb->qiov_offset += acb->cur_qiov.size; + acb->cur_pos += acb->cur_qiov.size; + qemu_iovec_reset(&acb->cur_qiov); + + /* Complete request */ + if (acb->cur_pos >= acb->end_pos) { + qed_aio_complete(acb, 0); + return; + } + + /* Find next cluster and start I/O */ + qed_find_cluster(s, &acb->request, + acb->cur_pos, acb->end_pos - acb->cur_pos, + io_fn, acb); +} + +static BlockDriverAIOCB *qed_aio_setup(BlockDriverState *bs, + int64_t sector_num, + QEMUIOVector *qiov, int nb_sectors, + BlockDriverCompletionFunc *cb, + void *opaque, bool is_write) +{ + QEDAIOCB *acb = qemu_aio_get(&qed_aio_pool, bs, cb, opaque); + + trace_qed_aio_setup(bs->opaque, acb, sector_num, nb_sectors, + opaque, is_write); + + acb->is_write = is_write; + acb->finished = NULL; + acb->qiov = qiov; + acb->qiov_offset = 0; + acb->cur_pos = (uint64_t)sector_num * BDRV_SECTOR_SIZE; + acb->end_pos = acb->cur_pos + nb_sectors * BDRV_SECTOR_SIZE; + acb->request.l2_table = NULL; + qemu_iovec_init(&acb->cur_qiov, qiov->niov); + + /* Start request */ + qed_aio_next_io(acb, 0); + return &acb->common; +} + +static BlockDriverAIOCB *bdrv_qed_aio_readv(BlockDriverState *bs, + int64_t sector_num, + QEMUIOVector *qiov, int nb_sectors, + BlockDriverCompletionFunc *cb, + void *opaque) +{ + return qed_aio_setup(bs, sector_num, qiov, nb_sectors, cb, opaque, false); +} + +static BlockDriverAIOCB *bdrv_qed_aio_writev(BlockDriverState *bs, + int64_t sector_num, + QEMUIOVector *qiov, int nb_sectors, + BlockDriverCompletionFunc *cb, + void *opaque) +{ + return qed_aio_setup(bs, sector_num, qiov, nb_sectors, cb, opaque, true); +} + +static BlockDriverAIOCB *bdrv_qed_aio_flush(BlockDriverState *bs, + BlockDriverCompletionFunc *cb, + void *opaque) +{ + return bdrv_aio_flush(bs->file, cb, opaque); +} + +static int bdrv_qed_truncate(BlockDriverState *bs, int64_t offset) +{ + return -ENOTSUP; +} + +static int64_t bdrv_qed_getlength(BlockDriverState *bs) +{ + BDRVQEDState *s = bs->opaque; + return s->header.image_size; +} + +static int bdrv_qed_get_info(BlockDriverState *bs, BlockDriverInfo *bdi) +{ + BDRVQEDState *s = bs->opaque; + + memset(bdi, 0, sizeof(*bdi)); + bdi->cluster_size = s->header.cluster_size; + return 0; +} + +static int bdrv_qed_change_backing_file(BlockDriverState *bs, + const char *backing_file, + const char *backing_fmt) +{ + BDRVQEDState *s = bs->opaque; + QEDHeader new_header, le_header; + void *buffer; + size_t buffer_len, backing_file_len; + int ret; + + /* Refuse to set backing filename if unknown compat feature bits are + * active. If the image uses an unknown compat feature then we may not + * know the layout of data following the header structure and cannot safely + * add a new string. + */ + if (backing_file && (s->header.compat_features & + ~QED_COMPAT_FEATURE_MASK)) { + return -ENOTSUP; + } + + memcpy(&new_header, &s->header, sizeof(new_header)); + + new_header.features &= ~(QED_F_BACKING_FILE | + QED_F_BACKING_FORMAT_NO_PROBE); + + /* Adjust feature flags */ + if (backing_file) { + new_header.features |= QED_F_BACKING_FILE; + + if (qed_fmt_is_raw(backing_fmt)) { + new_header.features |= QED_F_BACKING_FORMAT_NO_PROBE; + } + } + + /* Calculate new header size */ + backing_file_len = 0; + + if (backing_file) { + backing_file_len = strlen(backing_file); + } + + buffer_len = sizeof(new_header); + new_header.backing_filename_offset = buffer_len; + new_header.backing_filename_size = backing_file_len; + buffer_len += backing_file_len; + + /* Make sure we can rewrite header without failing */ + if (buffer_len > new_header.header_size * new_header.cluster_size) { + return -ENOSPC; + } + + /* Prepare new header */ + buffer = qemu_malloc(buffer_len); + + qed_header_cpu_to_le(&new_header, &le_header); + memcpy(buffer, &le_header, sizeof(le_header)); + buffer_len = sizeof(le_header); + + memcpy(buffer + buffer_len, backing_file, backing_file_len); + buffer_len += backing_file_len; + + /* Write new header */ + ret = bdrv_pwrite_sync(bs->file, 0, buffer, buffer_len); + qemu_free(buffer); + if (ret == 0) { + memcpy(&s->header, &new_header, sizeof(new_header)); + } + return ret; +} + +static int bdrv_qed_check(BlockDriverState *bs, BdrvCheckResult *result) +{ + BDRVQEDState *s = bs->opaque; + + return qed_check(s, result, false); +} + +static QEMUOptionParameter qed_create_options[] = { + { + .name = BLOCK_OPT_SIZE, + .type = OPT_SIZE, + .help = "Virtual disk size (in bytes)" + }, { + .name = BLOCK_OPT_BACKING_FILE, + .type = OPT_STRING, + .help = "File name of a base image" + }, { + .name = BLOCK_OPT_BACKING_FMT, + .type = OPT_STRING, + .help = "Image format of the base image" + }, { + .name = BLOCK_OPT_CLUSTER_SIZE, + .type = OPT_SIZE, + .help = "Cluster size (in bytes)" + }, { + .name = BLOCK_OPT_TABLE_SIZE, + .type = OPT_SIZE, + .help = "L1/L2 table size (in clusters)" + }, + { /* end of list */ } +}; + +static BlockDriver bdrv_qed = { + .format_name = "qed", + .instance_size = sizeof(BDRVQEDState), + .create_options = qed_create_options, + + .bdrv_probe = bdrv_qed_probe, + .bdrv_open = bdrv_qed_open, + .bdrv_close = bdrv_qed_close, + .bdrv_create = bdrv_qed_create, + .bdrv_flush = bdrv_qed_flush, + .bdrv_is_allocated = bdrv_qed_is_allocated, + .bdrv_make_empty = bdrv_qed_make_empty, + .bdrv_aio_readv = bdrv_qed_aio_readv, + .bdrv_aio_writev = bdrv_qed_aio_writev, + .bdrv_aio_flush = bdrv_qed_aio_flush, + .bdrv_truncate = bdrv_qed_truncate, + .bdrv_getlength = bdrv_qed_getlength, + .bdrv_get_info = bdrv_qed_get_info, + .bdrv_change_backing_file = bdrv_qed_change_backing_file, + .bdrv_check = bdrv_qed_check, +}; + +static void bdrv_qed_init(void) +{ + bdrv_register(&bdrv_qed); +} + +block_init(bdrv_qed_init); diff --git a/block/qed.h b/block/qed.h new file mode 100644 index 0000000..9f72ad5 --- /dev/null +++ b/block/qed.h @@ -0,0 +1,301 @@ +/* + * QEMU Enhanced Disk Format + * + * Copyright IBM, Corp. 2010 + * + * Authors: + * Stefan Hajnoczi + * Anthony Liguori + * + * 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. + * + */ + +#ifndef BLOCK_QED_H +#define BLOCK_QED_H + +#include "block_int.h" + +/* The layout of a QED file is as follows: + * + * +--------+----------+----------+----------+-----+ + * | header | L1 table | cluster0 | cluster1 | ... | + * +--------+----------+----------+----------+-----+ + * + * There is a 2-level pagetable for cluster allocation: + * + * +----------+ + * | L1 table | + * +----------+ + * ,------' | '------. + * +----------+ | +----------+ + * | L2 table | ... | L2 table | + * +----------+ +----------+ + * ,------' | '------. + * +----------+ | +----------+ + * | Data | ... | Data | + * +----------+ +----------+ + * + * The L1 table is fixed size and always present. L2 tables are allocated on + * demand. The L1 table size determines the maximum possible image size; it + * can be influenced using the cluster_size and table_size values. + * + * All fields are little-endian on disk. + */ + +enum { + QED_MAGIC = 'Q' | 'E' << 8 | 'D' << 16 | '\0' << 24, + + /* The image supports a backing file */ + QED_F_BACKING_FILE = 0x01, + + /* The image needs a consistency check before use */ + QED_F_NEED_CHECK = 0x02, + + /* The backing file format must not be probed, treat as raw image */ + QED_F_BACKING_FORMAT_NO_PROBE = 0x04, + + /* Feature bits must be used when the on-disk format changes */ + QED_FEATURE_MASK = QED_F_BACKING_FILE | /* supported feature bits */ + QED_F_NEED_CHECK | + QED_F_BACKING_FORMAT_NO_PROBE, + QED_COMPAT_FEATURE_MASK = 0, /* supported compat feature bits */ + QED_AUTOCLEAR_FEATURE_MASK = 0, /* supported autoclear feature bits */ + + /* Data is stored in groups of sectors called clusters. Cluster size must + * be large to avoid keeping too much metadata. I/O requests that have + * sub-cluster size will require read-modify-write. + */ + QED_MIN_CLUSTER_SIZE = 4 * 1024, /* in bytes */ + QED_MAX_CLUSTER_SIZE = 64 * 1024 * 1024, + QED_DEFAULT_CLUSTER_SIZE = 64 * 1024, + + /* Allocated clusters are tracked using a 2-level pagetable. Table size is + * a multiple of clusters so large maximum image sizes can be supported + * without jacking up the cluster size too much. + */ + QED_MIN_TABLE_SIZE = 1, /* in clusters */ + QED_MAX_TABLE_SIZE = 16, + QED_DEFAULT_TABLE_SIZE = 4, +}; + +typedef struct { + uint32_t magic; /* QED\0 */ + + uint32_t cluster_size; /* in bytes */ + uint32_t table_size; /* for L1 and L2 tables, in clusters */ + uint32_t header_size; /* in clusters */ + + uint64_t features; /* format feature bits */ + uint64_t compat_features; /* compatible feature bits */ + uint64_t autoclear_features; /* self-resetting feature bits */ + + uint64_t l1_table_offset; /* in bytes */ + uint64_t image_size; /* total logical image size, in bytes */ + + /* if (features & QED_F_BACKING_FILE) */ + uint32_t backing_filename_offset; /* in bytes from start of header */ + uint32_t backing_filename_size; /* in bytes */ +} QEDHeader; + +typedef struct { + uint64_t offsets[0]; /* in bytes */ +} QEDTable; + +/* The L2 cache is a simple write-through cache for L2 structures */ +typedef struct CachedL2Table { + QEDTable *table; + uint64_t offset; /* offset=0 indicates an invalidate entry */ + QTAILQ_ENTRY(CachedL2Table) node; + int ref; +} CachedL2Table; + +typedef struct { + QTAILQ_HEAD(, CachedL2Table) entries; + unsigned int n_entries; +} L2TableCache; + +typedef struct QEDRequest { + CachedL2Table *l2_table; +} QEDRequest; + +typedef struct QEDAIOCB { + BlockDriverAIOCB common; + QEMUBH *bh; + int bh_ret; /* final return status for completion bh */ + QSIMPLEQ_ENTRY(QEDAIOCB) next; /* next request */ + bool is_write; /* false - read, true - write */ + bool *finished; /* signal for cancel completion */ + uint64_t end_pos; /* request end on block device, in bytes */ + + /* User scatter-gather list */ + QEMUIOVector *qiov; + size_t qiov_offset; /* byte count already processed */ + + /* Current cluster scatter-gather list */ + QEMUIOVector cur_qiov; + uint64_t cur_pos; /* position on block device, in bytes */ + uint64_t cur_cluster; /* cluster offset in image file */ + unsigned int cur_nclusters; /* number of clusters being accessed */ + int find_cluster_ret; /* used for L1/L2 update */ + + QEDRequest request; +} QEDAIOCB; + +typedef struct { + BlockDriverState *bs; /* device */ + uint64_t file_size; /* length of image file, in bytes */ + + QEDHeader header; /* always cpu-endian */ + QEDTable *l1_table; + L2TableCache l2_cache; /* l2 table cache */ + uint32_t table_nelems; + uint32_t l1_shift; + uint32_t l2_shift; + uint32_t l2_mask; + + /* Allocating write request queue */ + QSIMPLEQ_HEAD(, QEDAIOCB) allocating_write_reqs; +} BDRVQEDState; + +enum { + QED_CLUSTER_FOUND, /* cluster found */ + QED_CLUSTER_L2, /* cluster missing in L2 */ + QED_CLUSTER_L1, /* cluster missing in L1 */ +}; + +/** + * qed_find_cluster() completion callback + * + * @opaque: User data for completion callback + * @ret: QED_CLUSTER_FOUND Success + * QED_CLUSTER_L2 Data cluster unallocated in L2 + * QED_CLUSTER_L1 L2 unallocated in L1 + * -errno POSIX error occurred + * @offset: Data cluster offset + * @len: Contiguous bytes starting from cluster offset + * + * This function is invoked when qed_find_cluster() completes. + * + * On success ret is QED_CLUSTER_FOUND and offset/len are a contiguous range + * in the image file. + * + * On failure ret is QED_CLUSTER_L2 or QED_CLUSTER_L1 for missing L2 or L1 + * table offset, respectively. len is number of contiguous unallocated bytes. + */ +typedef void QEDFindClusterFunc(void *opaque, int ret, uint64_t offset, size_t len); + +/** + * Generic callback for chaining async callbacks + */ +typedef struct { + BlockDriverCompletionFunc *cb; + void *opaque; +} GenericCB; + +void *gencb_alloc(size_t len, BlockDriverCompletionFunc *cb, void *opaque); +void gencb_complete(void *opaque, int ret); + +/** + * L2 cache functions + */ +void qed_init_l2_cache(L2TableCache *l2_cache); +void qed_free_l2_cache(L2TableCache *l2_cache); +CachedL2Table *qed_alloc_l2_cache_entry(L2TableCache *l2_cache); +void qed_unref_l2_cache_entry(CachedL2Table *entry); +CachedL2Table *qed_find_l2_cache_entry(L2TableCache *l2_cache, uint64_t offset); +void qed_commit_l2_cache_entry(L2TableCache *l2_cache, CachedL2Table *l2_table); + +/** + * Table I/O functions + */ +int qed_read_l1_table_sync(BDRVQEDState *s); +void qed_write_l1_table(BDRVQEDState *s, unsigned int index, unsigned int n, + BlockDriverCompletionFunc *cb, void *opaque); +int qed_write_l1_table_sync(BDRVQEDState *s, unsigned int index, + unsigned int n); +int qed_read_l2_table_sync(BDRVQEDState *s, QEDRequest *request, + uint64_t offset); +void qed_read_l2_table(BDRVQEDState *s, QEDRequest *request, uint64_t offset, + BlockDriverCompletionFunc *cb, void *opaque); +void qed_write_l2_table(BDRVQEDState *s, QEDRequest *request, + unsigned int index, unsigned int n, bool flush, + BlockDriverCompletionFunc *cb, void *opaque); +int qed_write_l2_table_sync(BDRVQEDState *s, QEDRequest *request, + unsigned int index, unsigned int n, bool flush); + +/** + * Cluster functions + */ +void qed_find_cluster(BDRVQEDState *s, QEDRequest *request, uint64_t pos, + size_t len, QEDFindClusterFunc *cb, void *opaque); + +/** + * Consistency check + */ +int qed_check(BDRVQEDState *s, BdrvCheckResult *result, bool fix); + +QEDTable *qed_alloc_table(BDRVQEDState *s); + +/** + * Round down to the start of a cluster + */ +static inline uint64_t qed_start_of_cluster(BDRVQEDState *s, uint64_t offset) +{ + return offset & ~(uint64_t)(s->header.cluster_size - 1); +} + +static inline uint64_t qed_offset_into_cluster(BDRVQEDState *s, uint64_t offset) +{ + return offset & (s->header.cluster_size - 1); +} + +static inline uint64_t qed_bytes_to_clusters(BDRVQEDState *s, uint64_t bytes) +{ + return qed_start_of_cluster(s, bytes + (s->header.cluster_size - 1)) / + (s->header.cluster_size - 1); +} + +static inline unsigned int qed_l1_index(BDRVQEDState *s, uint64_t pos) +{ + return pos >> s->l1_shift; +} + +static inline unsigned int qed_l2_index(BDRVQEDState *s, uint64_t pos) +{ + return (pos >> s->l2_shift) & s->l2_mask; +} + +/** + * Test if a cluster offset is valid + */ +static inline bool qed_check_cluster_offset(BDRVQEDState *s, uint64_t offset) +{ + uint64_t header_size = (uint64_t)s->header.header_size * + s->header.cluster_size; + + if (offset & (s->header.cluster_size - 1)) { + return false; + } + return offset >= header_size && offset < s->file_size; +} + +/** + * Test if a table offset is valid + */ +static inline bool qed_check_table_offset(BDRVQEDState *s, uint64_t offset) +{ + uint64_t end_offset = offset + (s->header.table_size - 1) * + s->header.cluster_size; + + /* Overflow check */ + if (end_offset <= offset) { + return false; + } + + return qed_check_cluster_offset(s, offset) && + qed_check_cluster_offset(s, end_offset); +} + +#endif /* BLOCK_QED_H */ diff --git a/block/raw-posix-aio.h b/block/raw-posix-aio.h new file mode 100644 index 0000000..dfc63b8 --- /dev/null +++ b/block/raw-posix-aio.h @@ -0,0 +1,43 @@ +/* + * QEMU Posix block I/O backend AIO support + * + * Copyright IBM, Corp. 2008 + * + * Authors: + * Anthony Liguori + * + * This work is licensed under the terms of the GNU GPL, version 2. See + * the COPYING file in the top-level directory. + * + */ +#ifndef QEMU_RAW_POSIX_AIO_H +#define QEMU_RAW_POSIX_AIO_H + +/* AIO request types */ +#define QEMU_AIO_READ 0x0001 +#define QEMU_AIO_WRITE 0x0002 +#define QEMU_AIO_IOCTL 0x0004 +#define QEMU_AIO_FLUSH 0x0008 +#define QEMU_AIO_TYPE_MASK \ + (QEMU_AIO_READ|QEMU_AIO_WRITE|QEMU_AIO_IOCTL|QEMU_AIO_FLUSH) + +/* AIO flags */ +#define QEMU_AIO_MISALIGNED 0x1000 + + +/* posix-aio-compat.c - thread pool based implementation */ +int paio_init(void); +BlockDriverAIOCB *paio_submit(BlockDriverState *bs, int fd, + int64_t sector_num, QEMUIOVector *qiov, int nb_sectors, + BlockDriverCompletionFunc *cb, void *opaque, int type); +BlockDriverAIOCB *paio_ioctl(BlockDriverState *bs, int fd, + unsigned long int req, void *buf, + BlockDriverCompletionFunc *cb, void *opaque); + +/* linux-aio.c - Linux native implementation */ +void *laio_init(void); +BlockDriverAIOCB *laio_submit(BlockDriverState *bs, void *aio_ctx, int fd, + int64_t sector_num, QEMUIOVector *qiov, int nb_sectors, + BlockDriverCompletionFunc *cb, void *opaque, int type); + +#endif /* QEMU_RAW_POSIX_AIO_H */ diff --git a/block/raw-posix.c b/block/raw-posix.c new file mode 100644 index 0000000..6b72470 --- /dev/null +++ b/block/raw-posix.c @@ -0,0 +1,1444 @@ +/* + * Block driver for RAW files (posix) + * + * 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 "qemu-common.h" +#include "qemu-timer.h" +#include "qemu-char.h" +#include "qemu-log.h" +#include "block_int.h" +#include "module.h" +#include "block/raw-posix-aio.h" + +#ifdef CONFIG_COCOA +#include +#include +#include +#include +#include +#include +#include +//#include +#include +#endif + +#ifdef __sun__ +#define _POSIX_PTHREAD_SEMANTICS 1 +#include +#include +#endif +#ifdef __linux__ +#include +#include +#include +#include +#endif +#if defined (__FreeBSD__) || defined(__FreeBSD_kernel__) +#include +#include +#include +#endif + +#ifdef __OpenBSD__ +#include +#include +#include +#endif + +#ifdef __DragonFly__ +#include +#include +#endif + +#ifdef CONFIG_XFS +#include +#endif + +//#define DEBUG_FLOPPY + +//#define DEBUG_BLOCK +#if defined(DEBUG_BLOCK) +#define DEBUG_BLOCK_PRINT(formatCstr, ...) do { if (qemu_log_enabled()) \ + { qemu_log(formatCstr, ## __VA_ARGS__); qemu_log_flush(); } } while (0) +#else +#define DEBUG_BLOCK_PRINT(formatCstr, ...) +#endif + +/* OS X does not have O_DSYNC */ +#ifndef O_DSYNC +#ifdef O_SYNC +#define O_DSYNC O_SYNC +#elif defined(O_FSYNC) +#define O_DSYNC O_FSYNC +#endif +#endif + +/* Approximate O_DIRECT with O_DSYNC if O_DIRECT isn't available */ +#ifndef O_DIRECT +#define O_DIRECT O_DSYNC +#endif + +#define FTYPE_FILE 0 +#define FTYPE_CD 1 +#define FTYPE_FD 2 + +/* if the FD is not accessed during that time (in ns), we try to + reopen it to see if the disk has been changed */ +#define FD_OPEN_TIMEOUT (1000000000) + +#define MAX_BLOCKSIZE 4096 + +typedef struct BDRVRawState { + int fd; + int type; + int open_flags; +#if defined(__linux__) + /* linux floppy specific */ + int64_t fd_open_time; + int64_t fd_error_time; + int fd_got_error; + int fd_media_changed; +#endif +#ifdef CONFIG_LINUX_AIO + int use_aio; + void *aio_ctx; +#endif + uint8_t *aligned_buf; + unsigned aligned_buf_size; +#ifdef CONFIG_XFS + bool is_xfs : 1; +#endif +} BDRVRawState; + +static int fd_open(BlockDriverState *bs); +static int64_t raw_getlength(BlockDriverState *bs); + +#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) +static int cdrom_reopen(BlockDriverState *bs); +#endif + +static int raw_open_common(BlockDriverState *bs, const char *filename, + int bdrv_flags, int open_flags) +{ + BDRVRawState *s = bs->opaque; + int fd, ret; + + s->open_flags = open_flags | O_BINARY; + s->open_flags &= ~O_ACCMODE; + if (bdrv_flags & BDRV_O_RDWR) { + s->open_flags |= O_RDWR; + } else { + s->open_flags |= O_RDONLY; + } + + /* Use O_DSYNC for write-through caching, no flags for write-back caching, + * and O_DIRECT for no caching. */ + if ((bdrv_flags & BDRV_O_NOCACHE)) + s->open_flags |= O_DIRECT; + else if (!(bdrv_flags & BDRV_O_CACHE_WB)) + s->open_flags |= O_DSYNC; + + s->fd = -1; + fd = qemu_open(filename, s->open_flags, 0644); + if (fd < 0) { + ret = -errno; + if (ret == -EROFS) + ret = -EACCES; + return ret; + } + s->fd = fd; + s->aligned_buf = NULL; + + if ((bdrv_flags & BDRV_O_NOCACHE)) { + /* + * Allocate a buffer for read/modify/write cycles. Chose the size + * pessimistically as we don't know the block size yet. + */ + s->aligned_buf_size = 32 * MAX_BLOCKSIZE; + s->aligned_buf = qemu_memalign(MAX_BLOCKSIZE, s->aligned_buf_size); + if (s->aligned_buf == NULL) { + goto out_close; + } + } + +#ifdef CONFIG_LINUX_AIO + if ((bdrv_flags & (BDRV_O_NOCACHE|BDRV_O_NATIVE_AIO)) == + (BDRV_O_NOCACHE|BDRV_O_NATIVE_AIO)) { + + /* We're falling back to POSIX AIO in some cases */ + paio_init(); + + s->aio_ctx = laio_init(); + if (!s->aio_ctx) { + goto out_free_buf; + } + s->use_aio = 1; + } else +#endif + { + if (paio_init() < 0) { + goto out_free_buf; + } +#ifdef CONFIG_LINUX_AIO + s->use_aio = 0; +#endif + } + +#ifdef CONFIG_XFS + if (platform_test_xfs_fd(s->fd)) { + s->is_xfs = 1; + } +#endif + + return 0; + +out_free_buf: + qemu_vfree(s->aligned_buf); +out_close: + close(fd); + return -errno; +} + +static int raw_open(BlockDriverState *bs, const char *filename, int flags) +{ + BDRVRawState *s = bs->opaque; + + s->type = FTYPE_FILE; + return raw_open_common(bs, filename, flags, 0); +} + +/* XXX: use host sector size if necessary with: +#ifdef DIOCGSECTORSIZE + { + unsigned int sectorsize = 512; + if (!ioctl(fd, DIOCGSECTORSIZE, §orsize) && + sectorsize > bufsize) + bufsize = sectorsize; + } +#endif +#ifdef CONFIG_COCOA + uint32_t blockSize = 512; + if ( !ioctl( fd, DKIOCGETBLOCKSIZE, &blockSize ) && blockSize > bufsize) { + bufsize = blockSize; + } +#endif +*/ + +/* + * offset and count are in bytes, but must be multiples of 512 for files + * opened with O_DIRECT. buf must be aligned to 512 bytes then. + * + * This function may be called without alignment if the caller ensures + * that O_DIRECT is not in effect. + */ +static int raw_pread_aligned(BlockDriverState *bs, int64_t offset, + uint8_t *buf, int count) +{ + BDRVRawState *s = bs->opaque; + int ret; + + ret = fd_open(bs); + if (ret < 0) + return ret; + + ret = pread(s->fd, buf, count, offset); + if (ret == count) + return ret; + + /* Allow reads beyond the end (needed for pwrite) */ + if ((ret == 0) && bs->growable) { + int64_t size = raw_getlength(bs); + if (offset >= size) { + memset(buf, 0, count); + return count; + } + } + + DEBUG_BLOCK_PRINT("raw_pread(%d:%s, %" PRId64 ", %p, %d) [%" PRId64 + "] read failed %d : %d = %s\n", + s->fd, bs->filename, offset, buf, count, + bs->total_sectors, ret, errno, strerror(errno)); + + /* Try harder for CDrom. */ + if (s->type != FTYPE_FILE) { + ret = pread(s->fd, buf, count, offset); + if (ret == count) + return ret; + ret = pread(s->fd, buf, count, offset); + if (ret == count) + return ret; + + DEBUG_BLOCK_PRINT("raw_pread(%d:%s, %" PRId64 ", %p, %d) [%" PRId64 + "] retry read failed %d : %d = %s\n", + s->fd, bs->filename, offset, buf, count, + bs->total_sectors, ret, errno, strerror(errno)); + } + + return (ret < 0) ? -errno : ret; +} + +/* + * offset and count are in bytes, but must be multiples of the sector size + * for files opened with O_DIRECT. buf must be aligned to sector size bytes + * then. + * + * This function may be called without alignment if the caller ensures + * that O_DIRECT is not in effect. + */ +static int raw_pwrite_aligned(BlockDriverState *bs, int64_t offset, + const uint8_t *buf, int count) +{ + BDRVRawState *s = bs->opaque; + int ret; + + ret = fd_open(bs); + if (ret < 0) + return -errno; + + ret = pwrite(s->fd, buf, count, offset); + if (ret == count) + return ret; + + DEBUG_BLOCK_PRINT("raw_pwrite(%d:%s, %" PRId64 ", %p, %d) [%" PRId64 + "] write failed %d : %d = %s\n", + s->fd, bs->filename, offset, buf, count, + bs->total_sectors, ret, errno, strerror(errno)); + + return (ret < 0) ? -errno : ret; +} + + +/* + * offset and count are in bytes and possibly not aligned. For files opened + * with O_DIRECT, necessary alignments are ensured before calling + * raw_pread_aligned to do the actual read. + */ +static int raw_pread(BlockDriverState *bs, int64_t offset, + uint8_t *buf, int count) +{ + BDRVRawState *s = bs->opaque; + unsigned sector_mask = bs->buffer_alignment - 1; + int size, ret, shift, sum; + + sum = 0; + + if (s->aligned_buf != NULL) { + + if (offset & sector_mask) { + /* align offset on a sector size bytes boundary */ + + shift = offset & sector_mask; + size = (shift + count + sector_mask) & ~sector_mask; + if (size > s->aligned_buf_size) + size = s->aligned_buf_size; + ret = raw_pread_aligned(bs, offset - shift, s->aligned_buf, size); + if (ret < 0) + return ret; + + size = bs->buffer_alignment - shift; + if (size > count) + size = count; + memcpy(buf, s->aligned_buf + shift, size); + + buf += size; + offset += size; + count -= size; + sum += size; + + if (count == 0) + return sum; + } + if (count & sector_mask || (uintptr_t) buf & sector_mask) { + + /* read on aligned buffer */ + + while (count) { + + size = (count + sector_mask) & ~sector_mask; + if (size > s->aligned_buf_size) + size = s->aligned_buf_size; + + ret = raw_pread_aligned(bs, offset, s->aligned_buf, size); + if (ret < 0) { + return ret; + } else if (ret == 0) { + fprintf(stderr, "raw_pread: read beyond end of file\n"); + abort(); + } + + size = ret; + if (size > count) + size = count; + + memcpy(buf, s->aligned_buf, size); + + buf += size; + offset += size; + count -= size; + sum += size; + } + + return sum; + } + } + + return raw_pread_aligned(bs, offset, buf, count) + sum; +} + +static int raw_read(BlockDriverState *bs, int64_t sector_num, + uint8_t *buf, int nb_sectors) +{ + int ret; + + ret = raw_pread(bs, sector_num * BDRV_SECTOR_SIZE, buf, + nb_sectors * BDRV_SECTOR_SIZE); + if (ret == (nb_sectors * BDRV_SECTOR_SIZE)) + ret = 0; + return ret; +} + +/* + * offset and count are in bytes and possibly not aligned. For files opened + * with O_DIRECT, necessary alignments are ensured before calling + * raw_pwrite_aligned to do the actual write. + */ +static int raw_pwrite(BlockDriverState *bs, int64_t offset, + const uint8_t *buf, int count) +{ + BDRVRawState *s = bs->opaque; + unsigned sector_mask = bs->buffer_alignment - 1; + int size, ret, shift, sum; + + sum = 0; + + if (s->aligned_buf != NULL) { + + if (offset & sector_mask) { + /* align offset on a sector size bytes boundary */ + shift = offset & sector_mask; + ret = raw_pread_aligned(bs, offset - shift, s->aligned_buf, + bs->buffer_alignment); + if (ret < 0) + return ret; + + size = bs->buffer_alignment - shift; + if (size > count) + size = count; + memcpy(s->aligned_buf + shift, buf, size); + + ret = raw_pwrite_aligned(bs, offset - shift, s->aligned_buf, + bs->buffer_alignment); + if (ret < 0) + return ret; + + buf += size; + offset += size; + count -= size; + sum += size; + + if (count == 0) + return sum; + } + if (count & sector_mask || (uintptr_t) buf & sector_mask) { + + while ((size = (count & ~sector_mask)) != 0) { + + if (size > s->aligned_buf_size) + size = s->aligned_buf_size; + + memcpy(s->aligned_buf, buf, size); + + ret = raw_pwrite_aligned(bs, offset, s->aligned_buf, size); + if (ret < 0) + return ret; + + buf += ret; + offset += ret; + count -= ret; + sum += ret; + } + /* here, count < sector_size because (count & ~sector_mask) == 0 */ + if (count) { + ret = raw_pread_aligned(bs, offset, s->aligned_buf, + bs->buffer_alignment); + if (ret < 0) + return ret; + memcpy(s->aligned_buf, buf, count); + + ret = raw_pwrite_aligned(bs, offset, s->aligned_buf, + bs->buffer_alignment); + if (ret < 0) + return ret; + if (count < ret) + ret = count; + + sum += ret; + } + return sum; + } + } + return raw_pwrite_aligned(bs, offset, buf, count) + sum; +} + +static int raw_write(BlockDriverState *bs, int64_t sector_num, + const uint8_t *buf, int nb_sectors) +{ + int ret; + ret = raw_pwrite(bs, sector_num * BDRV_SECTOR_SIZE, buf, + nb_sectors * BDRV_SECTOR_SIZE); + if (ret == (nb_sectors * BDRV_SECTOR_SIZE)) + ret = 0; + return ret; +} + +/* + * Check if all memory in this vector is sector aligned. + */ +static int qiov_is_aligned(BlockDriverState *bs, QEMUIOVector *qiov) +{ + int i; + + for (i = 0; i < qiov->niov; i++) { + if ((uintptr_t) qiov->iov[i].iov_base % bs->buffer_alignment) { + return 0; + } + } + + return 1; +} + +static BlockDriverAIOCB *raw_aio_submit(BlockDriverState *bs, + int64_t sector_num, QEMUIOVector *qiov, int nb_sectors, + BlockDriverCompletionFunc *cb, void *opaque, int type) +{ + BDRVRawState *s = bs->opaque; + + if (fd_open(bs) < 0) + return NULL; + + /* + * If O_DIRECT is used the buffer needs to be aligned on a sector + * boundary. Check if this is the case or telll the low-level + * driver that it needs to copy the buffer. + */ + if (s->aligned_buf) { + if (!qiov_is_aligned(bs, qiov)) { + type |= QEMU_AIO_MISALIGNED; +#ifdef CONFIG_LINUX_AIO + } else if (s->use_aio) { + return laio_submit(bs, s->aio_ctx, s->fd, sector_num, qiov, + nb_sectors, cb, opaque, type); +#endif + } + } + + return paio_submit(bs, s->fd, sector_num, qiov, nb_sectors, + cb, opaque, type); +} + +static BlockDriverAIOCB *raw_aio_readv(BlockDriverState *bs, + int64_t sector_num, QEMUIOVector *qiov, int nb_sectors, + BlockDriverCompletionFunc *cb, void *opaque) +{ + return raw_aio_submit(bs, sector_num, qiov, nb_sectors, + cb, opaque, QEMU_AIO_READ); +} + +static BlockDriverAIOCB *raw_aio_writev(BlockDriverState *bs, + int64_t sector_num, QEMUIOVector *qiov, int nb_sectors, + BlockDriverCompletionFunc *cb, void *opaque) +{ + return raw_aio_submit(bs, sector_num, qiov, nb_sectors, + cb, opaque, QEMU_AIO_WRITE); +} + +static BlockDriverAIOCB *raw_aio_flush(BlockDriverState *bs, + BlockDriverCompletionFunc *cb, void *opaque) +{ + BDRVRawState *s = bs->opaque; + + if (fd_open(bs) < 0) + return NULL; + + return paio_submit(bs, s->fd, 0, NULL, 0, cb, opaque, QEMU_AIO_FLUSH); +} + +static void raw_close(BlockDriverState *bs) +{ + BDRVRawState *s = bs->opaque; + if (s->fd >= 0) { + close(s->fd); + s->fd = -1; + if (s->aligned_buf != NULL) + qemu_vfree(s->aligned_buf); + } +} + +static int raw_truncate(BlockDriverState *bs, int64_t offset) +{ + BDRVRawState *s = bs->opaque; + if (s->type != FTYPE_FILE) + return -ENOTSUP; + if (ftruncate(s->fd, offset) < 0) + return -errno; + return 0; +} + +#ifdef __OpenBSD__ +static int64_t raw_getlength(BlockDriverState *bs) +{ + BDRVRawState *s = bs->opaque; + int fd = s->fd; + struct stat st; + + if (fstat(fd, &st)) + return -1; + if (S_ISCHR(st.st_mode) || S_ISBLK(st.st_mode)) { + struct disklabel dl; + + if (ioctl(fd, DIOCGDINFO, &dl)) + return -1; + return (uint64_t)dl.d_secsize * + dl.d_partitions[DISKPART(st.st_rdev)].p_size; + } else + return st.st_size; +} +#elif defined(__sun__) +static int64_t raw_getlength(BlockDriverState *bs) +{ + BDRVRawState *s = bs->opaque; + struct dk_minfo minfo; + int ret; + + ret = fd_open(bs); + if (ret < 0) { + return ret; + } + + /* + * Use the DKIOCGMEDIAINFO ioctl to read the size. + */ + ret = ioctl(s->fd, DKIOCGMEDIAINFO, &minfo); + if (ret != -1) { + return minfo.dki_lbsize * minfo.dki_capacity; + } + + /* + * There are reports that lseek on some devices fails, but + * irc discussion said that contingency on contingency was overkill. + */ + return lseek(s->fd, 0, SEEK_END); +} +#elif defined(CONFIG_BSD) +static int64_t raw_getlength(BlockDriverState *bs) +{ + BDRVRawState *s = bs->opaque; + int fd = s->fd; + int64_t size; + struct stat sb; +#if defined (__FreeBSD__) || defined(__FreeBSD_kernel__) + int reopened = 0; +#endif + int ret; + + ret = fd_open(bs); + if (ret < 0) + return ret; + +#if defined (__FreeBSD__) || defined(__FreeBSD_kernel__) +again: +#endif + if (!fstat(fd, &sb) && (S_IFCHR & sb.st_mode)) { +#ifdef DIOCGMEDIASIZE + if (ioctl(fd, DIOCGMEDIASIZE, (off_t *)&size)) +#elif defined(DIOCGPART) + { + struct partinfo pi; + if (ioctl(fd, DIOCGPART, &pi) == 0) + size = pi.media_size; + else + size = 0; + } + if (size == 0) +#endif +#ifdef CONFIG_COCOA + size = LONG_LONG_MAX; +#else + size = lseek(fd, 0LL, SEEK_END); +#endif +#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) + switch(s->type) { + case FTYPE_CD: + /* XXX FreeBSD acd returns UINT_MAX sectors for an empty drive */ + if (size == 2048LL * (unsigned)-1) + size = 0; + /* XXX no disc? maybe we need to reopen... */ + if (size <= 0 && !reopened && cdrom_reopen(bs) >= 0) { + reopened = 1; + goto again; + } + } +#endif + } else { + size = lseek(fd, 0, SEEK_END); + } + return size; +} +#else +static int64_t raw_getlength(BlockDriverState *bs) +{ + BDRVRawState *s = bs->opaque; + int ret; + + ret = fd_open(bs); + if (ret < 0) { + return ret; + } + + return lseek(s->fd, 0, SEEK_END); +} +#endif + +static int raw_create(const char *filename, QEMUOptionParameter *options) +{ + int fd; + int result = 0; + int64_t total_size = 0; + + /* Read out options */ + while (options && options->name) { + if (!strcmp(options->name, BLOCK_OPT_SIZE)) { + total_size = options->value.n / BDRV_SECTOR_SIZE; + } + options++; + } + + fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, + 0644); + if (fd < 0) { + result = -errno; + } else { + if (ftruncate(fd, total_size * BDRV_SECTOR_SIZE) != 0) { + result = -errno; + } + if (close(fd) != 0) { + result = -errno; + } + } + return result; +} + +static int raw_flush(BlockDriverState *bs) +{ + BDRVRawState *s = bs->opaque; + return qemu_fdatasync(s->fd); +} + +#ifdef CONFIG_XFS +static int xfs_discard(BDRVRawState *s, int64_t sector_num, int nb_sectors) +{ + struct xfs_flock64 fl; + + memset(&fl, 0, sizeof(fl)); + fl.l_whence = SEEK_SET; + fl.l_start = sector_num << 9; + fl.l_len = (int64_t)nb_sectors << 9; + + if (xfsctl(NULL, s->fd, XFS_IOC_UNRESVSP64, &fl) < 0) { + DEBUG_BLOCK_PRINT("cannot punch hole (%s)\n", strerror(errno)); + return -errno; + } + + return 0; +} +#endif + +static int raw_discard(BlockDriverState *bs, int64_t sector_num, int nb_sectors) +{ +#ifdef CONFIG_XFS + BDRVRawState *s = bs->opaque; + + if (s->is_xfs) { + return xfs_discard(s, sector_num, nb_sectors); + } +#endif + + return 0; +} + +static QEMUOptionParameter raw_create_options[] = { + { + .name = BLOCK_OPT_SIZE, + .type = OPT_SIZE, + .help = "Virtual disk size" + }, + { NULL } +}; + +static BlockDriver bdrv_file = { + .format_name = "file", + .protocol_name = "file", + .instance_size = sizeof(BDRVRawState), + .bdrv_probe = NULL, /* no probe for protocols */ + .bdrv_file_open = raw_open, + .bdrv_read = raw_read, + .bdrv_write = raw_write, + .bdrv_close = raw_close, + .bdrv_create = raw_create, + .bdrv_flush = raw_flush, + .bdrv_discard = raw_discard, + + .bdrv_aio_readv = raw_aio_readv, + .bdrv_aio_writev = raw_aio_writev, + .bdrv_aio_flush = raw_aio_flush, + + .bdrv_truncate = raw_truncate, + .bdrv_getlength = raw_getlength, + + .create_options = raw_create_options, +}; + +/***********************************************/ +/* host device */ + +#ifdef CONFIG_COCOA +static kern_return_t FindEjectableCDMedia( io_iterator_t *mediaIterator ); +static kern_return_t GetBSDPath( io_iterator_t mediaIterator, char *bsdPath, CFIndex maxPathSize ); + +kern_return_t FindEjectableCDMedia( io_iterator_t *mediaIterator ) +{ + kern_return_t kernResult; + mach_port_t masterPort; + CFMutableDictionaryRef classesToMatch; + + kernResult = IOMasterPort( MACH_PORT_NULL, &masterPort ); + if ( KERN_SUCCESS != kernResult ) { + printf( "IOMasterPort returned %d\n", kernResult ); + } + + classesToMatch = IOServiceMatching( kIOCDMediaClass ); + if ( classesToMatch == NULL ) { + printf( "IOServiceMatching returned a NULL dictionary.\n" ); + } else { + CFDictionarySetValue( classesToMatch, CFSTR( kIOMediaEjectableKey ), kCFBooleanTrue ); + } + kernResult = IOServiceGetMatchingServices( masterPort, classesToMatch, mediaIterator ); + if ( KERN_SUCCESS != kernResult ) + { + printf( "IOServiceGetMatchingServices returned %d\n", kernResult ); + } + + return kernResult; +} + +kern_return_t GetBSDPath( io_iterator_t mediaIterator, char *bsdPath, CFIndex maxPathSize ) +{ + io_object_t nextMedia; + kern_return_t kernResult = KERN_FAILURE; + *bsdPath = '\0'; + nextMedia = IOIteratorNext( mediaIterator ); + if ( nextMedia ) + { + CFTypeRef bsdPathAsCFString; + bsdPathAsCFString = IORegistryEntryCreateCFProperty( nextMedia, CFSTR( kIOBSDNameKey ), kCFAllocatorDefault, 0 ); + if ( bsdPathAsCFString ) { + size_t devPathLength; + strcpy( bsdPath, _PATH_DEV ); + strcat( bsdPath, "r" ); + devPathLength = strlen( bsdPath ); + if ( CFStringGetCString( bsdPathAsCFString, bsdPath + devPathLength, maxPathSize - devPathLength, kCFStringEncodingASCII ) ) { + kernResult = KERN_SUCCESS; + } + CFRelease( bsdPathAsCFString ); + } + IOObjectRelease( nextMedia ); + } + + return kernResult; +} + +#endif + +static int hdev_probe_device(const char *filename) +{ + struct stat st; + + /* allow a dedicated CD-ROM driver to match with a higher priority */ + if (strstart(filename, "/dev/cdrom", NULL)) + return 50; + + if (stat(filename, &st) >= 0 && + (S_ISCHR(st.st_mode) || S_ISBLK(st.st_mode))) { + return 100; + } + + return 0; +} + +static int hdev_open(BlockDriverState *bs, const char *filename, int flags) +{ + BDRVRawState *s = bs->opaque; + +#ifdef CONFIG_COCOA + if (strstart(filename, "/dev/cdrom", NULL)) { + kern_return_t kernResult; + io_iterator_t mediaIterator; + char bsdPath[ MAXPATHLEN ]; + int fd; + + kernResult = FindEjectableCDMedia( &mediaIterator ); + kernResult = GetBSDPath( mediaIterator, bsdPath, sizeof( bsdPath ) ); + + if ( bsdPath[ 0 ] != '\0' ) { + strcat(bsdPath,"s0"); + /* some CDs don't have a partition 0 */ + fd = open(bsdPath, O_RDONLY | O_BINARY | O_LARGEFILE); + if (fd < 0) { + bsdPath[strlen(bsdPath)-1] = '1'; + } else { + close(fd); + } + filename = bsdPath; + } + + if ( mediaIterator ) + IOObjectRelease( mediaIterator ); + } +#endif + + s->type = FTYPE_FILE; +#if defined(__linux__) + { + char resolved_path[ MAXPATHLEN ], *temp; + + temp = realpath(filename, resolved_path); + if (temp && strstart(temp, "/dev/sg", NULL)) { + bs->sg = 1; + } + } +#endif + + return raw_open_common(bs, filename, flags, 0); +} + +#if defined(__linux__) +/* Note: we do not have a reliable method to detect if the floppy is + present. The current method is to try to open the floppy at every + I/O and to keep it opened during a few hundreds of ms. */ +static int fd_open(BlockDriverState *bs) +{ + BDRVRawState *s = bs->opaque; + int last_media_present; + + if (s->type != FTYPE_FD) + return 0; + last_media_present = (s->fd >= 0); + if (s->fd >= 0 && + (get_clock() - s->fd_open_time) >= FD_OPEN_TIMEOUT) { + close(s->fd); + s->fd = -1; +#ifdef DEBUG_FLOPPY + printf("Floppy closed\n"); +#endif + } + if (s->fd < 0) { + if (s->fd_got_error && + (get_clock() - s->fd_error_time) < FD_OPEN_TIMEOUT) { +#ifdef DEBUG_FLOPPY + printf("No floppy (open delayed)\n"); +#endif + return -EIO; + } + s->fd = open(bs->filename, s->open_flags & ~O_NONBLOCK); + if (s->fd < 0) { + s->fd_error_time = get_clock(); + s->fd_got_error = 1; + if (last_media_present) + s->fd_media_changed = 1; +#ifdef DEBUG_FLOPPY + printf("No floppy\n"); +#endif + return -EIO; + } +#ifdef DEBUG_FLOPPY + printf("Floppy opened\n"); +#endif + } + if (!last_media_present) + s->fd_media_changed = 1; + s->fd_open_time = get_clock(); + s->fd_got_error = 0; + return 0; +} + +static int hdev_ioctl(BlockDriverState *bs, unsigned long int req, void *buf) +{ + BDRVRawState *s = bs->opaque; + + return ioctl(s->fd, req, buf); +} + +static BlockDriverAIOCB *hdev_aio_ioctl(BlockDriverState *bs, + unsigned long int req, void *buf, + BlockDriverCompletionFunc *cb, void *opaque) +{ + BDRVRawState *s = bs->opaque; + + if (fd_open(bs) < 0) + return NULL; + return paio_ioctl(bs, s->fd, req, buf, cb, opaque); +} + +#elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__) +static int fd_open(BlockDriverState *bs) +{ + BDRVRawState *s = bs->opaque; + + /* this is just to ensure s->fd is sane (its called by io ops) */ + if (s->fd >= 0) + return 0; + return -EIO; +} +#else /* !linux && !FreeBSD */ + +static int fd_open(BlockDriverState *bs) +{ + return 0; +} + +#endif /* !linux && !FreeBSD */ + +static int hdev_create(const char *filename, QEMUOptionParameter *options) +{ + int fd; + int ret = 0; + struct stat stat_buf; + int64_t total_size = 0; + + /* Read out options */ + while (options && options->name) { + if (!strcmp(options->name, "size")) { + total_size = options->value.n / BDRV_SECTOR_SIZE; + } + options++; + } + + fd = open(filename, O_WRONLY | O_BINARY); + if (fd < 0) + return -errno; + + if (fstat(fd, &stat_buf) < 0) + ret = -errno; + else if (!S_ISBLK(stat_buf.st_mode) && !S_ISCHR(stat_buf.st_mode)) + ret = -ENODEV; + else if (lseek(fd, 0, SEEK_END) < total_size * BDRV_SECTOR_SIZE) + ret = -ENOSPC; + + close(fd); + return ret; +} + +static int hdev_has_zero_init(BlockDriverState *bs) +{ + return 0; +} + +static BlockDriver bdrv_host_device = { + .format_name = "host_device", + .protocol_name = "host_device", + .instance_size = sizeof(BDRVRawState), + .bdrv_probe_device = hdev_probe_device, + .bdrv_file_open = hdev_open, + .bdrv_close = raw_close, + .bdrv_create = hdev_create, + .create_options = raw_create_options, + .bdrv_has_zero_init = hdev_has_zero_init, + .bdrv_flush = raw_flush, + + .bdrv_aio_readv = raw_aio_readv, + .bdrv_aio_writev = raw_aio_writev, + .bdrv_aio_flush = raw_aio_flush, + + .bdrv_read = raw_read, + .bdrv_write = raw_write, + .bdrv_getlength = raw_getlength, + + /* generic scsi device */ +#ifdef __linux__ + .bdrv_ioctl = hdev_ioctl, + .bdrv_aio_ioctl = hdev_aio_ioctl, +#endif +}; + +#ifdef __linux__ +static int floppy_open(BlockDriverState *bs, const char *filename, int flags) +{ + BDRVRawState *s = bs->opaque; + int ret; + + s->type = FTYPE_FD; + + /* open will not fail even if no floppy is inserted, so add O_NONBLOCK */ + ret = raw_open_common(bs, filename, flags, O_NONBLOCK); + if (ret) + return ret; + + /* close fd so that we can reopen it as needed */ + close(s->fd); + s->fd = -1; + s->fd_media_changed = 1; + + return 0; +} + +static int floppy_probe_device(const char *filename) +{ + int fd, ret; + int prio = 0; + struct floppy_struct fdparam; + + if (strstart(filename, "/dev/fd", NULL)) + prio = 50; + + fd = open(filename, O_RDONLY | O_NONBLOCK); + if (fd < 0) { + goto out; + } + + /* Attempt to detect via a floppy specific ioctl */ + ret = ioctl(fd, FDGETPRM, &fdparam); + if (ret >= 0) + prio = 100; + + close(fd); +out: + return prio; +} + + +static int floppy_is_inserted(BlockDriverState *bs) +{ + return fd_open(bs) >= 0; +} + +static int floppy_media_changed(BlockDriverState *bs) +{ + BDRVRawState *s = bs->opaque; + int ret; + + /* + * XXX: we do not have a true media changed indication. + * It does not work if the floppy is changed without trying to read it. + */ + fd_open(bs); + ret = s->fd_media_changed; + s->fd_media_changed = 0; +#ifdef DEBUG_FLOPPY + printf("Floppy changed=%d\n", ret); +#endif + return ret; +} + +static int floppy_eject(BlockDriverState *bs, int eject_flag) +{ + BDRVRawState *s = bs->opaque; + int fd; + + if (s->fd >= 0) { + close(s->fd); + s->fd = -1; + } + fd = open(bs->filename, s->open_flags | O_NONBLOCK); + if (fd >= 0) { + if (ioctl(fd, FDEJECT, 0) < 0) + perror("FDEJECT"); + close(fd); + } + + return 0; +} + +static BlockDriver bdrv_host_floppy = { + .format_name = "host_floppy", + .protocol_name = "host_floppy", + .instance_size = sizeof(BDRVRawState), + .bdrv_probe_device = floppy_probe_device, + .bdrv_file_open = floppy_open, + .bdrv_close = raw_close, + .bdrv_create = hdev_create, + .create_options = raw_create_options, + .bdrv_has_zero_init = hdev_has_zero_init, + .bdrv_flush = raw_flush, + + .bdrv_aio_readv = raw_aio_readv, + .bdrv_aio_writev = raw_aio_writev, + .bdrv_aio_flush = raw_aio_flush, + + .bdrv_read = raw_read, + .bdrv_write = raw_write, + .bdrv_getlength = raw_getlength, + + /* removable device support */ + .bdrv_is_inserted = floppy_is_inserted, + .bdrv_media_changed = floppy_media_changed, + .bdrv_eject = floppy_eject, +}; + +static int cdrom_open(BlockDriverState *bs, const char *filename, int flags) +{ + BDRVRawState *s = bs->opaque; + + s->type = FTYPE_CD; + + /* open will not fail even if no CD is inserted, so add O_NONBLOCK */ + return raw_open_common(bs, filename, flags, O_NONBLOCK); +} + +static int cdrom_probe_device(const char *filename) +{ + int fd, ret; + int prio = 0; + + fd = open(filename, O_RDONLY | O_NONBLOCK); + if (fd < 0) { + goto out; + } + + /* Attempt to detect via a CDROM specific ioctl */ + ret = ioctl(fd, CDROM_DRIVE_STATUS, CDSL_CURRENT); + if (ret >= 0) + prio = 100; + + close(fd); +out: + return prio; +} + +static int cdrom_is_inserted(BlockDriverState *bs) +{ + BDRVRawState *s = bs->opaque; + int ret; + + ret = ioctl(s->fd, CDROM_DRIVE_STATUS, CDSL_CURRENT); + if (ret == CDS_DISC_OK) + return 1; + return 0; +} + +static int cdrom_eject(BlockDriverState *bs, int eject_flag) +{ + BDRVRawState *s = bs->opaque; + + if (eject_flag) { + if (ioctl(s->fd, CDROMEJECT, NULL) < 0) + perror("CDROMEJECT"); + } else { + if (ioctl(s->fd, CDROMCLOSETRAY, NULL) < 0) + perror("CDROMEJECT"); + } + + return 0; +} + +static int cdrom_set_locked(BlockDriverState *bs, int locked) +{ + BDRVRawState *s = bs->opaque; + + if (ioctl(s->fd, CDROM_LOCKDOOR, locked) < 0) { + /* + * Note: an error can happen if the distribution automatically + * mounts the CD-ROM + */ + /* perror("CDROM_LOCKDOOR"); */ + } + + return 0; +} + +static BlockDriver bdrv_host_cdrom = { + .format_name = "host_cdrom", + .protocol_name = "host_cdrom", + .instance_size = sizeof(BDRVRawState), + .bdrv_probe_device = cdrom_probe_device, + .bdrv_file_open = cdrom_open, + .bdrv_close = raw_close, + .bdrv_create = hdev_create, + .create_options = raw_create_options, + .bdrv_has_zero_init = hdev_has_zero_init, + .bdrv_flush = raw_flush, + + .bdrv_aio_readv = raw_aio_readv, + .bdrv_aio_writev = raw_aio_writev, + .bdrv_aio_flush = raw_aio_flush, + + .bdrv_read = raw_read, + .bdrv_write = raw_write, + .bdrv_getlength = raw_getlength, + + /* removable device support */ + .bdrv_is_inserted = cdrom_is_inserted, + .bdrv_eject = cdrom_eject, + .bdrv_set_locked = cdrom_set_locked, + + /* generic scsi device */ + .bdrv_ioctl = hdev_ioctl, + .bdrv_aio_ioctl = hdev_aio_ioctl, +}; +#endif /* __linux__ */ + +#if defined (__FreeBSD__) || defined(__FreeBSD_kernel__) +static int cdrom_open(BlockDriverState *bs, const char *filename, int flags) +{ + BDRVRawState *s = bs->opaque; + int ret; + + s->type = FTYPE_CD; + + ret = raw_open_common(bs, filename, flags, 0); + if (ret) + return ret; + + /* make sure the door isnt locked at this time */ + ioctl(s->fd, CDIOCALLOW); + return 0; +} + +static int cdrom_probe_device(const char *filename) +{ + if (strstart(filename, "/dev/cd", NULL) || + strstart(filename, "/dev/acd", NULL)) + return 100; + return 0; +} + +static int cdrom_reopen(BlockDriverState *bs) +{ + BDRVRawState *s = bs->opaque; + int fd; + + /* + * Force reread of possibly changed/newly loaded disc, + * FreeBSD seems to not notice sometimes... + */ + if (s->fd >= 0) + close(s->fd); + fd = open(bs->filename, s->open_flags, 0644); + if (fd < 0) { + s->fd = -1; + return -EIO; + } + s->fd = fd; + + /* make sure the door isnt locked at this time */ + ioctl(s->fd, CDIOCALLOW); + return 0; +} + +static int cdrom_is_inserted(BlockDriverState *bs) +{ + return raw_getlength(bs) > 0; +} + +static int cdrom_eject(BlockDriverState *bs, int eject_flag) +{ + BDRVRawState *s = bs->opaque; + + if (s->fd < 0) + return -ENOTSUP; + + (void) ioctl(s->fd, CDIOCALLOW); + + if (eject_flag) { + if (ioctl(s->fd, CDIOCEJECT) < 0) + perror("CDIOCEJECT"); + } else { + if (ioctl(s->fd, CDIOCCLOSE) < 0) + perror("CDIOCCLOSE"); + } + + if (cdrom_reopen(bs) < 0) + return -ENOTSUP; + return 0; +} + +static int cdrom_set_locked(BlockDriverState *bs, int locked) +{ + BDRVRawState *s = bs->opaque; + + if (s->fd < 0) + return -ENOTSUP; + if (ioctl(s->fd, (locked ? CDIOCPREVENT : CDIOCALLOW)) < 0) { + /* + * Note: an error can happen if the distribution automatically + * mounts the CD-ROM + */ + /* perror("CDROM_LOCKDOOR"); */ + } + + return 0; +} + +static BlockDriver bdrv_host_cdrom = { + .format_name = "host_cdrom", + .protocol_name = "host_cdrom", + .instance_size = sizeof(BDRVRawState), + .bdrv_probe_device = cdrom_probe_device, + .bdrv_file_open = cdrom_open, + .bdrv_close = raw_close, + .bdrv_create = hdev_create, + .create_options = raw_create_options, + .bdrv_has_zero_init = hdev_has_zero_init, + .bdrv_flush = raw_flush, + + .bdrv_aio_readv = raw_aio_readv, + .bdrv_aio_writev = raw_aio_writev, + .bdrv_aio_flush = raw_aio_flush, + + .bdrv_read = raw_read, + .bdrv_write = raw_write, + .bdrv_getlength = raw_getlength, + + /* removable device support */ + .bdrv_is_inserted = cdrom_is_inserted, + .bdrv_eject = cdrom_eject, + .bdrv_set_locked = cdrom_set_locked, +}; +#endif /* __FreeBSD__ */ + +static void bdrv_file_init(void) +{ + /* + * Register all the drivers. Note that order is important, the driver + * registered last will get probed first. + */ + bdrv_register(&bdrv_file); + bdrv_register(&bdrv_host_device); +#ifdef __linux__ + bdrv_register(&bdrv_host_floppy); + bdrv_register(&bdrv_host_cdrom); +#endif +#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) + bdrv_register(&bdrv_host_cdrom); +#endif +} + +block_init(bdrv_file_init); diff --git a/block/raw-win32.c b/block/raw-win32.c new file mode 100644 index 0000000..c204a80 --- /dev/null +++ b/block/raw-win32.c @@ -0,0 +1,430 @@ +/* + * Block driver for RAW files (win32) + * + * 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 "qemu-common.h" +#include "qemu-timer.h" +#include "block_int.h" +#include "module.h" +#include +#include + +#define FTYPE_FILE 0 +#define FTYPE_CD 1 +#define FTYPE_HARDDISK 2 + +typedef struct BDRVRawState { + HANDLE hfile; + int type; + char drive_path[16]; /* format: "d:\" */ +} BDRVRawState; + +int qemu_ftruncate64(int fd, int64_t length) +{ + LARGE_INTEGER li; + LONG high; + HANDLE h; + BOOL res; + + if ((GetVersion() & 0x80000000UL) && (length >> 32) != 0) + return -1; + + h = (HANDLE)_get_osfhandle(fd); + + /* get current position, ftruncate do not change position */ + li.HighPart = 0; + li.LowPart = SetFilePointer (h, 0, &li.HighPart, FILE_CURRENT); + if (li.LowPart == 0xffffffffUL && GetLastError() != NO_ERROR) + return -1; + + high = length >> 32; + if (!SetFilePointer(h, (DWORD) length, &high, FILE_BEGIN)) + return -1; + res = SetEndOfFile(h); + + /* back to old position */ + SetFilePointer(h, li.LowPart, &li.HighPart, FILE_BEGIN); + return res ? 0 : -1; +} + +static int set_sparse(int fd) +{ + DWORD returned; + return (int) DeviceIoControl((HANDLE)_get_osfhandle(fd), FSCTL_SET_SPARSE, + NULL, 0, NULL, 0, &returned, NULL); +} + +static int raw_open(BlockDriverState *bs, const char *filename, int flags) +{ + BDRVRawState *s = bs->opaque; + int access_flags; + DWORD overlapped; + + s->type = FTYPE_FILE; + + if (flags & BDRV_O_RDWR) { + access_flags = GENERIC_READ | GENERIC_WRITE; + } else { + access_flags = GENERIC_READ; + } + + overlapped = FILE_ATTRIBUTE_NORMAL; + if ((flags & BDRV_O_NOCACHE)) + overlapped |= FILE_FLAG_NO_BUFFERING | FILE_FLAG_WRITE_THROUGH; + else if (!(flags & BDRV_O_CACHE_WB)) + overlapped |= FILE_FLAG_WRITE_THROUGH; + s->hfile = CreateFile(filename, access_flags, + FILE_SHARE_READ, NULL, + OPEN_EXISTING, overlapped, NULL); + if (s->hfile == INVALID_HANDLE_VALUE) { + int err = GetLastError(); + + if (err == ERROR_ACCESS_DENIED) + return -EACCES; + return -1; + } + return 0; +} + +static int raw_read(BlockDriverState *bs, int64_t sector_num, + uint8_t *buf, int nb_sectors) +{ + BDRVRawState *s = bs->opaque; + OVERLAPPED ov; + DWORD ret_count; + int ret; + int64_t offset = sector_num * 512; + int count = nb_sectors * 512; + + memset(&ov, 0, sizeof(ov)); + ov.Offset = offset; + ov.OffsetHigh = offset >> 32; + ret = ReadFile(s->hfile, buf, count, &ret_count, &ov); + if (!ret) + return ret_count; + if (ret_count == count) + ret_count = 0; + return ret_count; +} + +static int raw_write(BlockDriverState *bs, int64_t sector_num, + const uint8_t *buf, int nb_sectors) +{ + BDRVRawState *s = bs->opaque; + OVERLAPPED ov; + DWORD ret_count; + int ret; + int64_t offset = sector_num * 512; + int count = nb_sectors * 512; + + memset(&ov, 0, sizeof(ov)); + ov.Offset = offset; + ov.OffsetHigh = offset >> 32; + ret = WriteFile(s->hfile, buf, count, &ret_count, &ov); + if (!ret) + return ret_count; + if (ret_count == count) + ret_count = 0; + return ret_count; +} + +static int raw_flush(BlockDriverState *bs) +{ + BDRVRawState *s = bs->opaque; + int ret; + + ret = FlushFileBuffers(s->hfile); + if (ret == 0) { + return -EIO; + } + + return 0; +} + +static void raw_close(BlockDriverState *bs) +{ + BDRVRawState *s = bs->opaque; + CloseHandle(s->hfile); +} + +static int raw_truncate(BlockDriverState *bs, int64_t offset) +{ + BDRVRawState *s = bs->opaque; + LONG low, high; + + low = offset; + high = offset >> 32; + if (!SetFilePointer(s->hfile, low, &high, FILE_BEGIN)) + return -EIO; + if (!SetEndOfFile(s->hfile)) + return -EIO; + return 0; +} + +static int64_t raw_getlength(BlockDriverState *bs) +{ + BDRVRawState *s = bs->opaque; + LARGE_INTEGER l; + ULARGE_INTEGER available, total, total_free; + DISK_GEOMETRY_EX dg; + DWORD count; + BOOL status; + + switch(s->type) { + case FTYPE_FILE: + l.LowPart = GetFileSize(s->hfile, (PDWORD)&l.HighPart); + if (l.LowPart == 0xffffffffUL && GetLastError() != NO_ERROR) + return -EIO; + break; + case FTYPE_CD: + if (!GetDiskFreeSpaceEx(s->drive_path, &available, &total, &total_free)) + return -EIO; + l.QuadPart = total.QuadPart; + break; + case FTYPE_HARDDISK: + status = DeviceIoControl(s->hfile, IOCTL_DISK_GET_DRIVE_GEOMETRY_EX, + NULL, 0, &dg, sizeof(dg), &count, NULL); + if (status != 0) { + l = dg.DiskSize; + } + break; + default: + return -EIO; + } + return l.QuadPart; +} + +static int raw_create(const char *filename, QEMUOptionParameter *options) +{ + int fd; + int64_t total_size = 0; + + /* Read out options */ + while (options && options->name) { + if (!strcmp(options->name, BLOCK_OPT_SIZE)) { + total_size = options->value.n / 512; + } + options++; + } + + fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, + 0644); + if (fd < 0) + return -EIO; + set_sparse(fd); + ftruncate(fd, total_size * 512); + close(fd); + return 0; +} + +static QEMUOptionParameter raw_create_options[] = { + { + .name = BLOCK_OPT_SIZE, + .type = OPT_SIZE, + .help = "Virtual disk size" + }, + { NULL } +}; + +static BlockDriver bdrv_file = { + .format_name = "file", + .protocol_name = "file", + .instance_size = sizeof(BDRVRawState), + .bdrv_file_open = raw_open, + .bdrv_close = raw_close, + .bdrv_create = raw_create, + .bdrv_flush = raw_flush, + .bdrv_read = raw_read, + .bdrv_write = raw_write, + .bdrv_truncate = raw_truncate, + .bdrv_getlength = raw_getlength, + + .create_options = raw_create_options, +}; + +/***********************************************/ +/* host device */ + +static int find_cdrom(char *cdrom_name, int cdrom_name_size) +{ + char drives[256], *pdrv = drives; + UINT type; + + memset(drives, 0, sizeof(drives)); + GetLogicalDriveStrings(sizeof(drives), drives); + while(pdrv[0] != '\0') { + type = GetDriveType(pdrv); + switch(type) { + case DRIVE_CDROM: + snprintf(cdrom_name, cdrom_name_size, "\\\\.\\%c:", pdrv[0]); + return 0; + break; + } + pdrv += lstrlen(pdrv) + 1; + } + return -1; +} + +static int find_device_type(BlockDriverState *bs, const char *filename) +{ + BDRVRawState *s = bs->opaque; + UINT type; + const char *p; + + if (strstart(filename, "\\\\.\\", &p) || + strstart(filename, "//./", &p)) { + if (stristart(p, "PhysicalDrive", NULL)) + return FTYPE_HARDDISK; + snprintf(s->drive_path, sizeof(s->drive_path), "%c:\\", p[0]); + type = GetDriveType(s->drive_path); + switch (type) { + case DRIVE_REMOVABLE: + case DRIVE_FIXED: + return FTYPE_HARDDISK; + case DRIVE_CDROM: + return FTYPE_CD; + default: + return FTYPE_FILE; + } + } else { + return FTYPE_FILE; + } +} + +static int hdev_probe_device(const char *filename) +{ + if (strstart(filename, "/dev/cdrom", NULL)) + return 100; + if (is_windows_drive(filename)) + return 100; + return 0; +} + +static int hdev_open(BlockDriverState *bs, const char *filename, int flags) +{ + BDRVRawState *s = bs->opaque; + int access_flags, create_flags; + DWORD overlapped; + char device_name[64]; + + if (strstart(filename, "/dev/cdrom", NULL)) { + if (find_cdrom(device_name, sizeof(device_name)) < 0) + return -ENOENT; + filename = device_name; + } else { + /* transform drive letters into device name */ + if (((filename[0] >= 'a' && filename[0] <= 'z') || + (filename[0] >= 'A' && filename[0] <= 'Z')) && + filename[1] == ':' && filename[2] == '\0') { + snprintf(device_name, sizeof(device_name), "\\\\.\\%c:", filename[0]); + filename = device_name; + } + } + s->type = find_device_type(bs, filename); + + if (flags & BDRV_O_RDWR) { + access_flags = GENERIC_READ | GENERIC_WRITE; + } else { + access_flags = GENERIC_READ; + } + create_flags = OPEN_EXISTING; + + overlapped = FILE_ATTRIBUTE_NORMAL; + if ((flags & BDRV_O_NOCACHE)) + overlapped |= FILE_FLAG_NO_BUFFERING | FILE_FLAG_WRITE_THROUGH; + else if (!(flags & BDRV_O_CACHE_WB)) + overlapped |= FILE_FLAG_WRITE_THROUGH; + s->hfile = CreateFile(filename, access_flags, + FILE_SHARE_READ, NULL, + create_flags, overlapped, NULL); + if (s->hfile == INVALID_HANDLE_VALUE) { + int err = GetLastError(); + + if (err == ERROR_ACCESS_DENIED) + return -EACCES; + return -1; + } + return 0; +} + +#if 0 +/***********************************************/ +/* removable device additional commands */ + +static int raw_is_inserted(BlockDriverState *bs) +{ + return 1; +} + +static int raw_media_changed(BlockDriverState *bs) +{ + return -ENOTSUP; +} + +static int raw_eject(BlockDriverState *bs, int eject_flag) +{ + DWORD ret_count; + + if (s->type == FTYPE_FILE) + return -ENOTSUP; + if (eject_flag) { + DeviceIoControl(s->hfile, IOCTL_STORAGE_EJECT_MEDIA, + NULL, 0, NULL, 0, &lpBytesReturned, NULL); + } else { + DeviceIoControl(s->hfile, IOCTL_STORAGE_LOAD_MEDIA, + NULL, 0, NULL, 0, &lpBytesReturned, NULL); + } +} + +static int raw_set_locked(BlockDriverState *bs, int locked) +{ + return -ENOTSUP; +} +#endif + +static int hdev_has_zero_init(BlockDriverState *bs) +{ + return 0; +} + +static BlockDriver bdrv_host_device = { + .format_name = "host_device", + .protocol_name = "host_device", + .instance_size = sizeof(BDRVRawState), + .bdrv_probe_device = hdev_probe_device, + .bdrv_file_open = hdev_open, + .bdrv_close = raw_close, + .bdrv_flush = raw_flush, + .bdrv_has_zero_init = hdev_has_zero_init, + + .bdrv_read = raw_read, + .bdrv_write = raw_write, + .bdrv_getlength = raw_getlength, +}; + +static void bdrv_file_init(void) +{ + bdrv_register(&bdrv_file); + bdrv_register(&bdrv_host_device); +} + +block_init(bdrv_file_init); diff --git a/block/raw.c b/block/raw.c new file mode 100644 index 0000000..b0f72d6 --- /dev/null +++ b/block/raw.c @@ -0,0 +1,156 @@ + +#include "qemu-common.h" +#include "block_int.h" +#include "module.h" + +static int raw_open(BlockDriverState *bs, int flags) +{ + bs->sg = bs->file->sg; + return 0; +} + +static int raw_read(BlockDriverState *bs, int64_t sector_num, + uint8_t *buf, int nb_sectors) +{ + return bdrv_read(bs->file, sector_num, buf, nb_sectors); +} + +static int raw_write(BlockDriverState *bs, int64_t sector_num, + const uint8_t *buf, int nb_sectors) +{ + return bdrv_write(bs->file, sector_num, buf, nb_sectors); +} + +static BlockDriverAIOCB *raw_aio_readv(BlockDriverState *bs, + int64_t sector_num, QEMUIOVector *qiov, int nb_sectors, + BlockDriverCompletionFunc *cb, void *opaque) +{ + return bdrv_aio_readv(bs->file, sector_num, qiov, nb_sectors, cb, opaque); +} + +static BlockDriverAIOCB *raw_aio_writev(BlockDriverState *bs, + int64_t sector_num, QEMUIOVector *qiov, int nb_sectors, + BlockDriverCompletionFunc *cb, void *opaque) +{ + return bdrv_aio_writev(bs->file, sector_num, qiov, nb_sectors, cb, opaque); +} + +static void raw_close(BlockDriverState *bs) +{ +} + +static int raw_flush(BlockDriverState *bs) +{ + return bdrv_flush(bs->file); +} + +static BlockDriverAIOCB *raw_aio_flush(BlockDriverState *bs, + BlockDriverCompletionFunc *cb, void *opaque) +{ + return bdrv_aio_flush(bs->file, cb, opaque); +} + +static int64_t raw_getlength(BlockDriverState *bs) +{ + return bdrv_getlength(bs->file); +} + +static int raw_truncate(BlockDriverState *bs, int64_t offset) +{ + return bdrv_truncate(bs->file, offset); +} + +static int raw_probe(const uint8_t *buf, int buf_size, const char *filename) +{ + return 1; /* everything can be opened as raw image */ +} + +static int raw_discard(BlockDriverState *bs, int64_t sector_num, int nb_sectors) +{ + return bdrv_discard(bs->file, sector_num, nb_sectors); +} + +static int raw_is_inserted(BlockDriverState *bs) +{ + return bdrv_is_inserted(bs->file); +} + +static int raw_eject(BlockDriverState *bs, int eject_flag) +{ + return bdrv_eject(bs->file, eject_flag); +} + +static int raw_set_locked(BlockDriverState *bs, int locked) +{ + bdrv_set_locked(bs->file, locked); + return 0; +} + +static int raw_ioctl(BlockDriverState *bs, unsigned long int req, void *buf) +{ + return bdrv_ioctl(bs->file, req, buf); +} + +static BlockDriverAIOCB *raw_aio_ioctl(BlockDriverState *bs, + unsigned long int req, void *buf, + BlockDriverCompletionFunc *cb, void *opaque) +{ + return bdrv_aio_ioctl(bs->file, req, buf, cb, opaque); +} + +static int raw_create(const char *filename, QEMUOptionParameter *options) +{ + return bdrv_create_file(filename, options); +} + +static QEMUOptionParameter raw_create_options[] = { + { + .name = BLOCK_OPT_SIZE, + .type = OPT_SIZE, + .help = "Virtual disk size" + }, + { NULL } +}; + +static int raw_has_zero_init(BlockDriverState *bs) +{ + return bdrv_has_zero_init(bs->file); +} + +static BlockDriver bdrv_raw = { + .format_name = "raw", + + /* It's really 0, but we need to make qemu_malloc() happy */ + .instance_size = 1, + + .bdrv_open = raw_open, + .bdrv_close = raw_close, + .bdrv_read = raw_read, + .bdrv_write = raw_write, + .bdrv_flush = raw_flush, + .bdrv_probe = raw_probe, + .bdrv_getlength = raw_getlength, + .bdrv_truncate = raw_truncate, + + .bdrv_aio_readv = raw_aio_readv, + .bdrv_aio_writev = raw_aio_writev, + .bdrv_aio_flush = raw_aio_flush, + .bdrv_discard = raw_discard, + + .bdrv_is_inserted = raw_is_inserted, + .bdrv_eject = raw_eject, + .bdrv_set_locked = raw_set_locked, + .bdrv_ioctl = raw_ioctl, + .bdrv_aio_ioctl = raw_aio_ioctl, + + .bdrv_create = raw_create, + .create_options = raw_create_options, + .bdrv_has_zero_init = raw_has_zero_init, +}; + +static void bdrv_raw_init(void) +{ + bdrv_register(&bdrv_raw); +} + +block_init(bdrv_raw_init); diff --git a/block/rbd.c b/block/rbd.c new file mode 100644 index 0000000..249a590 --- /dev/null +++ b/block/rbd.c @@ -0,0 +1,1059 @@ +/* + * QEMU Block driver for RADOS (Ceph) + * + * Copyright (C) 2010 Christian Brunner + * + * 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-error.h" + +#include "rbd_types.h" +#include "block_int.h" + +#include + + + +/* + * When specifying the image filename use: + * + * rbd:poolname/devicename + * + * poolname must be the name of an existing rados pool + * + * devicename is the basename for all objects used to + * emulate the raw device. + * + * Metadata information (image size, ...) is stored in an + * object with the name "devicename.rbd". + * + * The raw device is split into 4MB sized objects by default. + * The sequencenumber is encoded in a 12 byte long hex-string, + * and is attached to the devicename, separated by a dot. + * e.g. "devicename.1234567890ab" + * + */ + +#define OBJ_MAX_SIZE (1UL << OBJ_DEFAULT_OBJ_ORDER) + +typedef struct RBDAIOCB { + BlockDriverAIOCB common; + QEMUBH *bh; + int ret; + QEMUIOVector *qiov; + char *bounce; + int write; + int64_t sector_num; + int aiocnt; + int error; + struct BDRVRBDState *s; + int cancelled; +} RBDAIOCB; + +typedef struct RADOSCB { + int rcbid; + RBDAIOCB *acb; + struct BDRVRBDState *s; + int done; + int64_t segsize; + char *buf; + int ret; +} RADOSCB; + +#define RBD_FD_READ 0 +#define RBD_FD_WRITE 1 + +typedef struct BDRVRBDState { + int fds[2]; + rados_pool_t pool; + rados_pool_t header_pool; + char name[RBD_MAX_OBJ_NAME_SIZE]; + char block_name[RBD_MAX_BLOCK_NAME_SIZE]; + uint64_t size; + uint64_t objsize; + int qemu_aio_count; + int event_reader_pos; + RADOSCB *event_rcb; +} BDRVRBDState; + +typedef struct rbd_obj_header_ondisk RbdHeader1; + +static void rbd_aio_bh_cb(void *opaque); + +static int rbd_next_tok(char *dst, int dst_len, + char *src, char delim, + const char *name, + char **p) +{ + int l; + char *end; + + *p = NULL; + + if (delim != '\0') { + end = strchr(src, delim); + if (end) { + *p = end + 1; + *end = '\0'; + } + } + l = strlen(src); + if (l >= dst_len) { + error_report("%s too long", name); + return -EINVAL; + } else if (l == 0) { + error_report("%s too short", name); + return -EINVAL; + } + + pstrcpy(dst, dst_len, src); + + return 0; +} + +static int rbd_parsename(const char *filename, + char *pool, int pool_len, + char *snap, int snap_len, + char *name, int name_len) +{ + const char *start; + char *p, *buf; + int ret; + + if (!strstart(filename, "rbd:", &start)) { + return -EINVAL; + } + + buf = qemu_strdup(start); + p = buf; + + ret = rbd_next_tok(pool, pool_len, p, '/', "pool name", &p); + if (ret < 0 || !p) { + ret = -EINVAL; + goto done; + } + ret = rbd_next_tok(name, name_len, p, '@', "object name", &p); + if (ret < 0) { + goto done; + } + if (!p) { + *snap = '\0'; + goto done; + } + + ret = rbd_next_tok(snap, snap_len, p, '\0', "snap name", &p); + +done: + qemu_free(buf); + return ret; +} + +static int create_tmap_op(uint8_t op, const char *name, char **tmap_desc) +{ + uint32_t len = strlen(name); + uint32_t len_le = cpu_to_le32(len); + /* total_len = encoding op + name + empty buffer */ + uint32_t total_len = 1 + (sizeof(uint32_t) + len) + sizeof(uint32_t); + uint8_t *desc = NULL; + + desc = qemu_malloc(total_len); + + *tmap_desc = (char *)desc; + + *desc = op; + desc++; + memcpy(desc, &len_le, sizeof(len_le)); + desc += sizeof(len_le); + memcpy(desc, name, len); + desc += len; + len = 0; /* no need for endian conversion for 0 */ + memcpy(desc, &len, sizeof(len)); + desc += sizeof(len); + + return (char *)desc - *tmap_desc; +} + +static void free_tmap_op(char *tmap_desc) +{ + qemu_free(tmap_desc); +} + +static int rbd_register_image(rados_pool_t pool, const char *name) +{ + char *tmap_desc; + const char *dir = RBD_DIRECTORY; + int ret; + + ret = create_tmap_op(CEPH_OSD_TMAP_SET, name, &tmap_desc); + if (ret < 0) { + return ret; + } + + ret = rados_tmap_update(pool, dir, tmap_desc, ret); + free_tmap_op(tmap_desc); + + return ret; +} + +static int touch_rbd_info(rados_pool_t pool, const char *info_oid) +{ + int r = rados_write(pool, info_oid, 0, NULL, 0); + if (r < 0) { + return r; + } + return 0; +} + +static int rbd_assign_bid(rados_pool_t pool, uint64_t *id) +{ + uint64_t out[1]; + const char *info_oid = RBD_INFO; + + *id = 0; + + int r = touch_rbd_info(pool, info_oid); + if (r < 0) { + return r; + } + + r = rados_exec(pool, info_oid, "rbd", "assign_bid", NULL, + 0, (char *)out, sizeof(out)); + if (r < 0) { + return r; + } + + le64_to_cpus(out); + *id = out[0]; + + return 0; +} + +static int rbd_create(const char *filename, QEMUOptionParameter *options) +{ + int64_t bytes = 0; + int64_t objsize; + uint64_t size; + time_t mtime; + uint8_t obj_order = RBD_DEFAULT_OBJ_ORDER; + char pool[RBD_MAX_SEG_NAME_SIZE]; + char n[RBD_MAX_SEG_NAME_SIZE]; + char name[RBD_MAX_OBJ_NAME_SIZE]; + char snap_buf[RBD_MAX_SEG_NAME_SIZE]; + char *snap = NULL; + RbdHeader1 header; + rados_pool_t p; + uint64_t bid; + uint32_t hi, lo; + int ret; + + if (rbd_parsename(filename, + pool, sizeof(pool), + snap_buf, sizeof(snap_buf), + name, sizeof(name)) < 0) { + return -EINVAL; + } + if (snap_buf[0] != '\0') { + snap = snap_buf; + } + + snprintf(n, sizeof(n), "%s%s", name, RBD_SUFFIX); + + /* Read out options */ + while (options && options->name) { + if (!strcmp(options->name, BLOCK_OPT_SIZE)) { + bytes = options->value.n; + } else if (!strcmp(options->name, BLOCK_OPT_CLUSTER_SIZE)) { + if (options->value.n) { + objsize = options->value.n; + if ((objsize - 1) & objsize) { /* not a power of 2? */ + error_report("obj size needs to be power of 2"); + return -EINVAL; + } + if (objsize < 4096) { + error_report("obj size too small"); + return -EINVAL; + } + obj_order = ffs(objsize) - 1; + } + } + options++; + } + + memset(&header, 0, sizeof(header)); + pstrcpy(header.text, sizeof(header.text), RBD_HEADER_TEXT); + pstrcpy(header.signature, sizeof(header.signature), RBD_HEADER_SIGNATURE); + pstrcpy(header.version, sizeof(header.version), RBD_HEADER_VERSION); + header.image_size = cpu_to_le64(bytes); + header.options.order = obj_order; + header.options.crypt_type = RBD_CRYPT_NONE; + header.options.comp_type = RBD_COMP_NONE; + header.snap_seq = 0; + header.snap_count = 0; + + if (rados_initialize(0, NULL) < 0) { + error_report("error initializing"); + return -EIO; + } + + if (rados_open_pool(pool, &p)) { + error_report("error opening pool %s", pool); + rados_deinitialize(); + return -EIO; + } + + /* check for existing rbd header file */ + ret = rados_stat(p, n, &size, &mtime); + if (ret == 0) { + ret=-EEXIST; + goto done; + } + + ret = rbd_assign_bid(p, &bid); + if (ret < 0) { + error_report("failed assigning block id"); + rados_deinitialize(); + return -EIO; + } + hi = bid >> 32; + lo = bid & 0xFFFFFFFF; + snprintf(header.block_name, sizeof(header.block_name), "rb.%x.%x", hi, lo); + + /* create header file */ + ret = rados_write(p, n, 0, (const char *)&header, sizeof(header)); + if (ret < 0) { + goto done; + } + + ret = rbd_register_image(p, name); +done: + rados_close_pool(p); + rados_deinitialize(); + + return ret; +} + +/* + * This aio completion is being called from rbd_aio_event_reader() and + * runs in qemu context. It schedules a bh, but just in case the aio + * was not cancelled before. + */ +static void rbd_complete_aio(RADOSCB *rcb) +{ + RBDAIOCB *acb = rcb->acb; + int64_t r; + + acb->aiocnt--; + + if (acb->cancelled) { + if (!acb->aiocnt) { + qemu_vfree(acb->bounce); + qemu_aio_release(acb); + } + goto done; + } + + r = rcb->ret; + + if (acb->write) { + if (r < 0) { + acb->ret = r; + acb->error = 1; + } else if (!acb->error) { + acb->ret += rcb->segsize; + } + } else { + if (r == -ENOENT) { + memset(rcb->buf, 0, rcb->segsize); + if (!acb->error) { + acb->ret += rcb->segsize; + } + } else if (r < 0) { + memset(rcb->buf, 0, rcb->segsize); + acb->ret = r; + acb->error = 1; + } else if (r < rcb->segsize) { + memset(rcb->buf + r, 0, rcb->segsize - r); + if (!acb->error) { + acb->ret += rcb->segsize; + } + } else if (!acb->error) { + acb->ret += r; + } + } + /* Note that acb->bh can be NULL in case where the aio was cancelled */ + if (!acb->aiocnt) { + acb->bh = qemu_bh_new(rbd_aio_bh_cb, acb); + qemu_bh_schedule(acb->bh); + } +done: + qemu_free(rcb); +} + +/* + * aio fd read handler. It runs in the qemu context and calls the + * completion handling of completed rados aio operations. + */ +static void rbd_aio_event_reader(void *opaque) +{ + BDRVRBDState *s = opaque; + + ssize_t ret; + + do { + char *p = (char *)&s->event_rcb; + + /* now read the rcb pointer that was sent from a non qemu thread */ + if ((ret = read(s->fds[RBD_FD_READ], p + s->event_reader_pos, + sizeof(s->event_rcb) - s->event_reader_pos)) > 0) { + if (ret > 0) { + s->event_reader_pos += ret; + if (s->event_reader_pos == sizeof(s->event_rcb)) { + s->event_reader_pos = 0; + rbd_complete_aio(s->event_rcb); + s->qemu_aio_count --; + } + } + } + } while (ret < 0 && errno == EINTR); +} + +static int rbd_aio_flush_cb(void *opaque) +{ + BDRVRBDState *s = opaque; + + return (s->qemu_aio_count > 0); +} + + +static int rbd_set_snapc(rados_pool_t pool, const char *snap, RbdHeader1 *header) +{ + uint32_t snap_count = le32_to_cpu(header->snap_count); + rados_snap_t *snaps = NULL; + rados_snap_t seq; + uint32_t i; + uint64_t snap_names_len = le64_to_cpu(header->snap_names_len); + int r; + rados_snap_t snapid = 0; + + if (snap_count) { + const char *header_snap = (const char *)&header->snaps[snap_count]; + const char *end = header_snap + snap_names_len; + snaps = qemu_malloc(sizeof(rados_snap_t) * header->snap_count); + + for (i=0; i < snap_count; i++) { + snaps[i] = le64_to_cpu(header->snaps[i].id); + + if (snap && strcmp(snap, header_snap) == 0) { + snapid = snaps[i]; + } + + header_snap += strlen(header_snap) + 1; + if (header_snap > end) { + error_report("bad header, snapshot list broken"); + } + } + } + + if (snap && !snapid) { + error_report("snapshot not found"); + qemu_free(snaps); + return -ENOENT; + } + seq = le32_to_cpu(header->snap_seq); + + r = rados_set_snap_context(pool, seq, snaps, snap_count); + + rados_set_snap(pool, snapid); + + qemu_free(snaps); + + return r; +} + +#define BUF_READ_START_LEN 4096 + +static int rbd_read_header(BDRVRBDState *s, char **hbuf) +{ + char *buf = NULL; + char n[RBD_MAX_SEG_NAME_SIZE]; + uint64_t len = BUF_READ_START_LEN; + int r; + + snprintf(n, sizeof(n), "%s%s", s->name, RBD_SUFFIX); + + buf = qemu_malloc(len); + + r = rados_read(s->header_pool, n, 0, buf, len); + if (r < 0) { + goto failed; + } + + if (r < len) { + goto done; + } + + qemu_free(buf); + buf = qemu_malloc(len); + + r = rados_stat(s->header_pool, n, &len, NULL); + if (r < 0) { + goto failed; + } + + r = rados_read(s->header_pool, n, 0, buf, len); + if (r < 0) { + goto failed; + } + +done: + *hbuf = buf; + return 0; + +failed: + qemu_free(buf); + return r; +} + +static int rbd_open(BlockDriverState *bs, const char *filename, int flags) +{ + BDRVRBDState *s = bs->opaque; + RbdHeader1 *header; + char pool[RBD_MAX_SEG_NAME_SIZE]; + char snap_buf[RBD_MAX_SEG_NAME_SIZE]; + char *snap = NULL; + char *hbuf = NULL; + int r; + + if (rbd_parsename(filename, pool, sizeof(pool), + snap_buf, sizeof(snap_buf), + s->name, sizeof(s->name)) < 0) { + return -EINVAL; + } + if (snap_buf[0] != '\0') { + snap = snap_buf; + } + + if ((r = rados_initialize(0, NULL)) < 0) { + error_report("error initializing"); + return r; + } + + if ((r = rados_open_pool(pool, &s->pool))) { + error_report("error opening pool %s", pool); + rados_deinitialize(); + return r; + } + + if ((r = rados_open_pool(pool, &s->header_pool))) { + error_report("error opening pool %s", pool); + rados_deinitialize(); + return r; + } + + if ((r = rbd_read_header(s, &hbuf)) < 0) { + error_report("error reading header from %s", s->name); + goto failed; + } + + if (memcmp(hbuf + 64, RBD_HEADER_SIGNATURE, 4)) { + error_report("Invalid header signature"); + r = -EMEDIUMTYPE; + goto failed; + } + + if (memcmp(hbuf + 68, RBD_HEADER_VERSION, 8)) { + error_report("Unknown image version"); + r = -EMEDIUMTYPE; + goto failed; + } + + header = (RbdHeader1 *) hbuf; + s->size = le64_to_cpu(header->image_size); + s->objsize = 1ULL << header->options.order; + memcpy(s->block_name, header->block_name, sizeof(header->block_name)); + + r = rbd_set_snapc(s->pool, snap, header); + if (r < 0) { + error_report("failed setting snap context: %s", strerror(-r)); + goto failed; + } + + bs->read_only = (snap != NULL); + + s->event_reader_pos = 0; + r = qemu_pipe(s->fds); + if (r < 0) { + error_report("error opening eventfd"); + goto failed; + } + fcntl(s->fds[0], F_SETFL, O_NONBLOCK); + fcntl(s->fds[1], F_SETFL, O_NONBLOCK); + qemu_aio_set_fd_handler(s->fds[RBD_FD_READ], rbd_aio_event_reader, NULL, + rbd_aio_flush_cb, NULL, s); + + qemu_free(hbuf); + + return 0; + +failed: + qemu_free(hbuf); + + rados_close_pool(s->header_pool); + rados_close_pool(s->pool); + rados_deinitialize(); + return r; +} + +static void rbd_close(BlockDriverState *bs) +{ + BDRVRBDState *s = bs->opaque; + + close(s->fds[0]); + close(s->fds[1]); + qemu_aio_set_fd_handler(s->fds[RBD_FD_READ], NULL , NULL, NULL, NULL, + NULL); + + rados_close_pool(s->header_pool); + rados_close_pool(s->pool); + rados_deinitialize(); +} + +/* + * Cancel aio. Since we don't reference acb in a non qemu threads, + * it is safe to access it here. + */ +static void rbd_aio_cancel(BlockDriverAIOCB *blockacb) +{ + RBDAIOCB *acb = (RBDAIOCB *) blockacb; + acb->cancelled = 1; +} + +static AIOPool rbd_aio_pool = { + .aiocb_size = sizeof(RBDAIOCB), + .cancel = rbd_aio_cancel, +}; + +/* + * This is the callback function for rados_aio_read and _write + * + * Note: this function is being called from a non qemu thread so + * we need to be careful about what we do here. Generally we only + * write to the block notification pipe, and do the rest of the + * io completion handling from rbd_aio_event_reader() which + * runs in a qemu context. + */ +static void rbd_finish_aiocb(rados_completion_t c, RADOSCB *rcb) +{ + int ret; + rcb->ret = rados_aio_get_return_value(c); + rados_aio_release(c); + while (1) { + fd_set wfd; + int fd = rcb->s->fds[RBD_FD_WRITE]; + + /* send the rcb pointer to the qemu thread that is responsible + for the aio completion. Must do it in a qemu thread context */ + ret = write(fd, (void *)&rcb, sizeof(rcb)); + if (ret >= 0) { + break; + } + if (errno == EINTR) { + continue; + } + if (errno != EAGAIN) { + break; + } + + FD_ZERO(&wfd); + FD_SET(fd, &wfd); + do { + ret = select(fd + 1, NULL, &wfd, NULL, NULL); + } while (ret < 0 && errno == EINTR); + } + + if (ret < 0) { + error_report("failed writing to acb->s->fds\n"); + qemu_free(rcb); + } +} + +/* Callback when all queued rados_aio requests are complete */ + +static void rbd_aio_bh_cb(void *opaque) +{ + RBDAIOCB *acb = opaque; + + if (!acb->write) { + qemu_iovec_from_buffer(acb->qiov, acb->bounce, acb->qiov->size); + } + qemu_vfree(acb->bounce); + acb->common.cb(acb->common.opaque, (acb->ret > 0 ? 0 : acb->ret)); + qemu_bh_delete(acb->bh); + acb->bh = NULL; + + qemu_aio_release(acb); +} + +static BlockDriverAIOCB *rbd_aio_rw_vector(BlockDriverState *bs, + int64_t sector_num, + QEMUIOVector *qiov, + int nb_sectors, + BlockDriverCompletionFunc *cb, + void *opaque, int write) +{ + RBDAIOCB *acb; + RADOSCB *rcb; + rados_completion_t c; + char n[RBD_MAX_SEG_NAME_SIZE]; + int64_t segnr, segoffs, segsize, last_segnr; + int64_t off, size; + char *buf; + + BDRVRBDState *s = bs->opaque; + + acb = qemu_aio_get(&rbd_aio_pool, bs, cb, opaque); + acb->write = write; + acb->qiov = qiov; + acb->bounce = qemu_blockalign(bs, qiov->size); + acb->aiocnt = 0; + acb->ret = 0; + acb->error = 0; + acb->s = s; + acb->cancelled = 0; + acb->bh = NULL; + + if (write) { + qemu_iovec_to_buffer(acb->qiov, acb->bounce); + } + + buf = acb->bounce; + + off = sector_num * BDRV_SECTOR_SIZE; + size = nb_sectors * BDRV_SECTOR_SIZE; + segnr = off / s->objsize; + segoffs = off % s->objsize; + segsize = s->objsize - segoffs; + + last_segnr = ((off + size - 1) / s->objsize); + acb->aiocnt = (last_segnr - segnr) + 1; + + s->qemu_aio_count += acb->aiocnt; /* All the RADOSCB */ + + while (size > 0) { + if (size < segsize) { + segsize = size; + } + + snprintf(n, sizeof(n), "%s.%012" PRIx64, s->block_name, + segnr); + + rcb = qemu_malloc(sizeof(RADOSCB)); + rcb->done = 0; + rcb->acb = acb; + rcb->segsize = segsize; + rcb->buf = buf; + rcb->s = acb->s; + + if (write) { + rados_aio_create_completion(rcb, NULL, + (rados_callback_t) rbd_finish_aiocb, + &c); + rados_aio_write(s->pool, n, segoffs, buf, segsize, c); + } else { + rados_aio_create_completion(rcb, + (rados_callback_t) rbd_finish_aiocb, + NULL, &c); + rados_aio_read(s->pool, n, segoffs, buf, segsize, c); + } + + buf += segsize; + size -= segsize; + segoffs = 0; + segsize = s->objsize; + segnr++; + } + + return &acb->common; +} + +static BlockDriverAIOCB *rbd_aio_readv(BlockDriverState * bs, + int64_t sector_num, QEMUIOVector * qiov, + int nb_sectors, + BlockDriverCompletionFunc * cb, + void *opaque) +{ + return rbd_aio_rw_vector(bs, sector_num, qiov, nb_sectors, cb, opaque, 0); +} + +static BlockDriverAIOCB *rbd_aio_writev(BlockDriverState * bs, + int64_t sector_num, QEMUIOVector * qiov, + int nb_sectors, + BlockDriverCompletionFunc * cb, + void *opaque) +{ + return rbd_aio_rw_vector(bs, sector_num, qiov, nb_sectors, cb, opaque, 1); +} + +static int rbd_getinfo(BlockDriverState * bs, BlockDriverInfo * bdi) +{ + BDRVRBDState *s = bs->opaque; + bdi->cluster_size = s->objsize; + return 0; +} + +static int64_t rbd_getlength(BlockDriverState * bs) +{ + BDRVRBDState *s = bs->opaque; + + return s->size; +} + +static int rbd_snap_create(BlockDriverState *bs, QEMUSnapshotInfo *sn_info) +{ + BDRVRBDState *s = bs->opaque; + char inbuf[512], outbuf[128]; + uint64_t snap_id; + int r; + char *p = inbuf; + char *end = inbuf + sizeof(inbuf); + char n[RBD_MAX_SEG_NAME_SIZE]; + char *hbuf = NULL; + RbdHeader1 *header; + + if (sn_info->name[0] == '\0') { + return -EINVAL; /* we need a name for rbd snapshots */ + } + + /* + * rbd snapshots are using the name as the user controlled unique identifier + * we can't use the rbd snapid for that purpose, as it can't be set + */ + if (sn_info->id_str[0] != '\0' && + strcmp(sn_info->id_str, sn_info->name) != 0) { + return -EINVAL; + } + + if (strlen(sn_info->name) >= sizeof(sn_info->id_str)) { + return -ERANGE; + } + + r = rados_selfmanaged_snap_create(s->header_pool, &snap_id); + if (r < 0) { + error_report("failed to create snap id: %s", strerror(-r)); + return r; + } + + *(uint32_t *)p = strlen(sn_info->name); + cpu_to_le32s((uint32_t *)p); + p += sizeof(uint32_t); + strncpy(p, sn_info->name, end - p); + p += strlen(p); + if (p + sizeof(snap_id) > end) { + error_report("invalid input parameter"); + return -EINVAL; + } + + *(uint64_t *)p = snap_id; + cpu_to_le64s((uint64_t *)p); + + snprintf(n, sizeof(n), "%s%s", s->name, RBD_SUFFIX); + + r = rados_exec(s->header_pool, n, "rbd", "snap_add", inbuf, + sizeof(inbuf), outbuf, sizeof(outbuf)); + if (r < 0) { + error_report("rbd.snap_add execution failed failed: %s", strerror(-r)); + return r; + } + + sprintf(sn_info->id_str, "%s", sn_info->name); + + r = rbd_read_header(s, &hbuf); + if (r < 0) { + error_report("failed reading header: %s", strerror(-r)); + return r; + } + + header = (RbdHeader1 *) hbuf; + r = rbd_set_snapc(s->pool, sn_info->name, header); + if (r < 0) { + error_report("failed setting snap context: %s", strerror(-r)); + goto failed; + } + + return 0; + +failed: + qemu_free(header); + return r; +} + +static int decode32(char **p, const char *end, uint32_t *v) +{ + if (*p + 4 > end) { + return -ERANGE; + } + + *v = *(uint32_t *)(*p); + le32_to_cpus(v); + *p += 4; + return 0; +} + +static int decode64(char **p, const char *end, uint64_t *v) +{ + if (*p + 8 > end) { + return -ERANGE; + } + + *v = *(uint64_t *)(*p); + le64_to_cpus(v); + *p += 8; + return 0; +} + +static int decode_str(char **p, const char *end, char **s) +{ + uint32_t len; + int r; + + if ((r = decode32(p, end, &len)) < 0) { + return r; + } + + *s = qemu_malloc(len + 1); + memcpy(*s, *p, len); + *p += len; + (*s)[len] = '\0'; + + return len; +} + +static int rbd_snap_list(BlockDriverState *bs, QEMUSnapshotInfo **psn_tab) +{ + BDRVRBDState *s = bs->opaque; + char n[RBD_MAX_SEG_NAME_SIZE]; + QEMUSnapshotInfo *sn_info, *sn_tab = NULL; + RbdHeader1 *header; + char *hbuf = NULL; + char *outbuf = NULL, *end, *buf; + uint64_t len; + uint64_t snap_seq; + uint32_t snap_count; + int r, i; + + /* read header to estimate how much space we need to read the snap + * list */ + if ((r = rbd_read_header(s, &hbuf)) < 0) { + goto done_err; + } + header = (RbdHeader1 *)hbuf; + len = le64_to_cpu(header->snap_names_len); + len += 1024; /* should have already been enough, but new snapshots might + already been created since we read the header. just allocate + a bit more, so that in most cases it'll suffice anyway */ + qemu_free(hbuf); + + snprintf(n, sizeof(n), "%s%s", s->name, RBD_SUFFIX); + while (1) { + qemu_free(outbuf); + outbuf = qemu_malloc(len); + + r = rados_exec(s->header_pool, n, "rbd", "snap_list", NULL, 0, + outbuf, len); + if (r < 0) { + error_report("rbd.snap_list execution failed failed: %s", strerror(-r)); + goto done_err; + } + if (r != len) { + break; + } + + /* if we're here, we probably raced with some snaps creation */ + len *= 2; + } + buf = outbuf; + end = buf + len; + + if ((r = decode64(&buf, end, &snap_seq)) < 0) { + goto done_err; + } + if ((r = decode32(&buf, end, &snap_count)) < 0) { + goto done_err; + } + + sn_tab = qemu_mallocz(snap_count * sizeof(QEMUSnapshotInfo)); + for (i = 0; i < snap_count; i++) { + uint64_t id, image_size; + char *snap_name; + + if ((r = decode64(&buf, end, &id)) < 0) { + goto done_err; + } + if ((r = decode64(&buf, end, &image_size)) < 0) { + goto done_err; + } + if ((r = decode_str(&buf, end, &snap_name)) < 0) { + goto done_err; + } + + sn_info = sn_tab + i; + pstrcpy(sn_info->id_str, sizeof(sn_info->id_str), snap_name); + pstrcpy(sn_info->name, sizeof(sn_info->name), snap_name); + qemu_free(snap_name); + + sn_info->vm_state_size = image_size; + sn_info->date_sec = 0; + sn_info->date_nsec = 0; + sn_info->vm_clock_nsec = 0; + } + *psn_tab = sn_tab; + qemu_free(outbuf); + return snap_count; +done_err: + qemu_free(sn_tab); + qemu_free(outbuf); + return r; +} + +static QEMUOptionParameter rbd_create_options[] = { + { + .name = BLOCK_OPT_SIZE, + .type = OPT_SIZE, + .help = "Virtual disk size" + }, + { + .name = BLOCK_OPT_CLUSTER_SIZE, + .type = OPT_SIZE, + .help = "RBD object size" + }, + {NULL} +}; + +static BlockDriver bdrv_rbd = { + .format_name = "rbd", + .instance_size = sizeof(BDRVRBDState), + .bdrv_file_open = rbd_open, + .bdrv_close = rbd_close, + .bdrv_create = rbd_create, + .bdrv_get_info = rbd_getinfo, + .create_options = rbd_create_options, + .bdrv_getlength = rbd_getlength, + .protocol_name = "rbd", + + .bdrv_aio_readv = rbd_aio_readv, + .bdrv_aio_writev = rbd_aio_writev, + + .bdrv_snapshot_create = rbd_snap_create, + .bdrv_snapshot_list = rbd_snap_list, +}; + +static void bdrv_rbd_init(void) +{ + bdrv_register(&bdrv_rbd); +} + +block_init(bdrv_rbd_init); diff --git a/block/rbd_types.h b/block/rbd_types.h new file mode 100644 index 0000000..f4cca99 --- /dev/null +++ b/block/rbd_types.h @@ -0,0 +1,71 @@ +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2004-2010 Sage Weil + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software + * Foundation. See file COPYING.LIB. + * + */ + +#ifndef CEPH_RBD_TYPES_H +#define CEPH_RBD_TYPES_H + + +/* + * rbd image 'foo' consists of objects + * foo.rbd - image metadata + * foo.00000000 + * foo.00000001 + * ... - data + */ + +#define RBD_SUFFIX ".rbd" +#define RBD_DIRECTORY "rbd_directory" +#define RBD_INFO "rbd_info" + +#define RBD_DEFAULT_OBJ_ORDER 22 /* 4MB */ + +#define RBD_MAX_OBJ_NAME_SIZE 96 +#define RBD_MAX_BLOCK_NAME_SIZE 24 +#define RBD_MAX_SEG_NAME_SIZE 128 + +#define RBD_COMP_NONE 0 +#define RBD_CRYPT_NONE 0 + +#define RBD_HEADER_TEXT "<<< Rados Block Device Image >>>\n" +#define RBD_HEADER_SIGNATURE "RBD" +#define RBD_HEADER_VERSION "001.005" + +struct rbd_info { + uint64_t max_id; +} __attribute__ ((packed)); + +struct rbd_obj_snap_ondisk { + uint64_t id; + uint64_t image_size; +} __attribute__((packed)); + +struct rbd_obj_header_ondisk { + char text[40]; + char block_name[RBD_MAX_BLOCK_NAME_SIZE]; + char signature[4]; + char version[8]; + struct { + uint8_t order; + uint8_t crypt_type; + uint8_t comp_type; + uint8_t unused; + } __attribute__((packed)) options; + uint64_t image_size; + uint64_t snap_seq; + uint32_t snap_count; + uint32_t reserved; + uint64_t snap_names_len; + struct rbd_obj_snap_ondisk snaps[0]; +} __attribute__((packed)); + + +#endif diff --git a/block/sheepdog.c b/block/sheepdog.c new file mode 100644 index 0000000..a54e0de --- /dev/null +++ b/block/sheepdog.c @@ -0,0 +1,2037 @@ +/* + * Copyright (C) 2009-2010 Nippon Telegraph and Telephone Corporation. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "qemu-common.h" +#include "qemu-error.h" +#include "qemu_socket.h" +#include "block_int.h" + +#define SD_PROTO_VER 0x01 + +#define SD_DEFAULT_ADDR "localhost" +#define SD_DEFAULT_PORT "7000" + +#define SD_OP_CREATE_AND_WRITE_OBJ 0x01 +#define SD_OP_READ_OBJ 0x02 +#define SD_OP_WRITE_OBJ 0x03 + +#define SD_OP_NEW_VDI 0x11 +#define SD_OP_LOCK_VDI 0x12 +#define SD_OP_RELEASE_VDI 0x13 +#define SD_OP_GET_VDI_INFO 0x14 +#define SD_OP_READ_VDIS 0x15 + +#define SD_FLAG_CMD_WRITE 0x01 +#define SD_FLAG_CMD_COW 0x02 + +#define SD_RES_SUCCESS 0x00 /* Success */ +#define SD_RES_UNKNOWN 0x01 /* Unknown error */ +#define SD_RES_NO_OBJ 0x02 /* No object found */ +#define SD_RES_EIO 0x03 /* I/O error */ +#define SD_RES_VDI_EXIST 0x04 /* Vdi exists already */ +#define SD_RES_INVALID_PARMS 0x05 /* Invalid parameters */ +#define SD_RES_SYSTEM_ERROR 0x06 /* System error */ +#define SD_RES_VDI_LOCKED 0x07 /* Vdi is locked */ +#define SD_RES_NO_VDI 0x08 /* No vdi found */ +#define SD_RES_NO_BASE_VDI 0x09 /* No base vdi found */ +#define SD_RES_VDI_READ 0x0A /* Cannot read requested vdi */ +#define SD_RES_VDI_WRITE 0x0B /* Cannot write requested vdi */ +#define SD_RES_BASE_VDI_READ 0x0C /* Cannot read base vdi */ +#define SD_RES_BASE_VDI_WRITE 0x0D /* Cannot write base vdi */ +#define SD_RES_NO_TAG 0x0E /* Requested tag is not found */ +#define SD_RES_STARTUP 0x0F /* Sheepdog is on starting up */ +#define SD_RES_VDI_NOT_LOCKED 0x10 /* Vdi is not locked */ +#define SD_RES_SHUTDOWN 0x11 /* Sheepdog is shutting down */ +#define SD_RES_NO_MEM 0x12 /* Cannot allocate memory */ +#define SD_RES_FULL_VDI 0x13 /* we already have the maximum vdis */ +#define SD_RES_VER_MISMATCH 0x14 /* Protocol version mismatch */ +#define SD_RES_NO_SPACE 0x15 /* Server has no room for new objects */ +#define SD_RES_WAIT_FOR_FORMAT 0x16 /* Waiting for a format operation */ +#define SD_RES_WAIT_FOR_JOIN 0x17 /* Waiting for other nodes joining */ +#define SD_RES_JOIN_FAILED 0x18 /* Target node had failed to join sheepdog */ + +/* + * Object ID rules + * + * 0 - 19 (20 bits): data object space + * 20 - 31 (12 bits): reserved data object space + * 32 - 55 (24 bits): vdi object space + * 56 - 59 ( 4 bits): reserved vdi object space + * 60 - 63 ( 4 bits): object type indentifier space + */ + +#define VDI_SPACE_SHIFT 32 +#define VDI_BIT (UINT64_C(1) << 63) +#define VMSTATE_BIT (UINT64_C(1) << 62) +#define MAX_DATA_OBJS (UINT64_C(1) << 20) +#define MAX_CHILDREN 1024 +#define SD_MAX_VDI_LEN 256 +#define SD_MAX_VDI_TAG_LEN 256 +#define SD_NR_VDIS (1U << 24) +#define SD_DATA_OBJ_SIZE (UINT64_C(1) << 22) +#define SD_MAX_VDI_SIZE (SD_DATA_OBJ_SIZE * MAX_DATA_OBJS) +#define SECTOR_SIZE 512 + +#define SD_INODE_SIZE (sizeof(SheepdogInode)) +#define CURRENT_VDI_ID 0 + +typedef struct SheepdogReq { + uint8_t proto_ver; + uint8_t opcode; + uint16_t flags; + uint32_t epoch; + uint32_t id; + uint32_t data_length; + uint32_t opcode_specific[8]; +} SheepdogReq; + +typedef struct SheepdogRsp { + uint8_t proto_ver; + uint8_t opcode; + uint16_t flags; + uint32_t epoch; + uint32_t id; + uint32_t data_length; + uint32_t result; + uint32_t opcode_specific[7]; +} SheepdogRsp; + +typedef struct SheepdogObjReq { + uint8_t proto_ver; + uint8_t opcode; + uint16_t flags; + uint32_t epoch; + uint32_t id; + uint32_t data_length; + uint64_t oid; + uint64_t cow_oid; + uint32_t copies; + uint32_t rsvd; + uint64_t offset; +} SheepdogObjReq; + +typedef struct SheepdogObjRsp { + uint8_t proto_ver; + uint8_t opcode; + uint16_t flags; + uint32_t epoch; + uint32_t id; + uint32_t data_length; + uint32_t result; + uint32_t copies; + uint32_t pad[6]; +} SheepdogObjRsp; + +typedef struct SheepdogVdiReq { + uint8_t proto_ver; + uint8_t opcode; + uint16_t flags; + uint32_t epoch; + uint32_t id; + uint32_t data_length; + uint64_t vdi_size; + uint32_t base_vdi_id; + uint32_t copies; + uint32_t snapid; + uint32_t pad[3]; +} SheepdogVdiReq; + +typedef struct SheepdogVdiRsp { + uint8_t proto_ver; + uint8_t opcode; + uint16_t flags; + uint32_t epoch; + uint32_t id; + uint32_t data_length; + uint32_t result; + uint32_t rsvd; + uint32_t vdi_id; + uint32_t pad[5]; +} SheepdogVdiRsp; + +typedef struct SheepdogInode { + char name[SD_MAX_VDI_LEN]; + char tag[SD_MAX_VDI_TAG_LEN]; + uint64_t ctime; + uint64_t snap_ctime; + uint64_t vm_clock_nsec; + uint64_t vdi_size; + uint64_t vm_state_size; + uint16_t copy_policy; + uint8_t nr_copies; + uint8_t block_size_shift; + uint32_t snap_id; + uint32_t vdi_id; + uint32_t parent_vdi_id; + uint32_t child_vdi_id[MAX_CHILDREN]; + uint32_t data_vdi_id[MAX_DATA_OBJS]; +} SheepdogInode; + +/* + * 64 bit FNV-1a non-zero initial basis + */ +#define FNV1A_64_INIT ((uint64_t)0xcbf29ce484222325ULL) + +/* + * 64 bit Fowler/Noll/Vo FNV-1a hash code + */ +static inline uint64_t fnv_64a_buf(void *buf, size_t len, uint64_t hval) +{ + unsigned char *bp = buf; + unsigned char *be = bp + len; + while (bp < be) { + hval ^= (uint64_t) *bp++; + hval += (hval << 1) + (hval << 4) + (hval << 5) + + (hval << 7) + (hval << 8) + (hval << 40); + } + return hval; +} + +static inline int is_data_obj_writeable(SheepdogInode *inode, unsigned int idx) +{ + return inode->vdi_id == inode->data_vdi_id[idx]; +} + +static inline int is_data_obj(uint64_t oid) +{ + return !(VDI_BIT & oid); +} + +static inline uint64_t data_oid_to_idx(uint64_t oid) +{ + return oid & (MAX_DATA_OBJS - 1); +} + +static inline uint64_t vid_to_vdi_oid(uint32_t vid) +{ + return VDI_BIT | ((uint64_t)vid << VDI_SPACE_SHIFT); +} + +static inline uint64_t vid_to_vmstate_oid(uint32_t vid, uint32_t idx) +{ + return VMSTATE_BIT | ((uint64_t)vid << VDI_SPACE_SHIFT) | idx; +} + +static inline uint64_t vid_to_data_oid(uint32_t vid, uint32_t idx) +{ + return ((uint64_t)vid << VDI_SPACE_SHIFT) | idx; +} + +static inline int is_snapshot(struct SheepdogInode *inode) +{ + return !!inode->snap_ctime; +} + +#undef dprintf +#ifdef DEBUG_SDOG +#define dprintf(fmt, args...) \ + do { \ + fprintf(stdout, "%s %d: " fmt, __func__, __LINE__, ##args); \ + } while (0) +#else +#define dprintf(fmt, args...) +#endif + +typedef struct SheepdogAIOCB SheepdogAIOCB; + +typedef struct AIOReq { + SheepdogAIOCB *aiocb; + unsigned int iov_offset; + + uint64_t oid; + uint64_t base_oid; + uint64_t offset; + unsigned int data_len; + uint8_t flags; + uint32_t id; + + QLIST_ENTRY(AIOReq) outstanding_aio_siblings; + QLIST_ENTRY(AIOReq) aioreq_siblings; +} AIOReq; + +enum AIOCBState { + AIOCB_WRITE_UDATA, + AIOCB_READ_UDATA, +}; + +struct SheepdogAIOCB { + BlockDriverAIOCB common; + + QEMUIOVector *qiov; + + int64_t sector_num; + int nb_sectors; + + int ret; + enum AIOCBState aiocb_type; + + QEMUBH *bh; + void (*aio_done_func)(SheepdogAIOCB *); + + int canceled; + + QLIST_HEAD(aioreq_head, AIOReq) aioreq_head; +}; + +typedef struct BDRVSheepdogState { + SheepdogInode inode; + + uint32_t min_dirty_data_idx; + uint32_t max_dirty_data_idx; + + char name[SD_MAX_VDI_LEN]; + int is_snapshot; + + char *addr; + char *port; + int fd; + + uint32_t aioreq_seq_num; + QLIST_HEAD(outstanding_aio_head, AIOReq) outstanding_aio_head; +} BDRVSheepdogState; + +static const char * sd_strerror(int err) +{ + int i; + + static const struct { + int err; + const char *desc; + } errors[] = { + {SD_RES_SUCCESS, "Success"}, + {SD_RES_UNKNOWN, "Unknown error"}, + {SD_RES_NO_OBJ, "No object found"}, + {SD_RES_EIO, "I/O error"}, + {SD_RES_VDI_EXIST, "VDI exists already"}, + {SD_RES_INVALID_PARMS, "Invalid parameters"}, + {SD_RES_SYSTEM_ERROR, "System error"}, + {SD_RES_VDI_LOCKED, "VDI is already locked"}, + {SD_RES_NO_VDI, "No vdi found"}, + {SD_RES_NO_BASE_VDI, "No base VDI found"}, + {SD_RES_VDI_READ, "Failed read the requested VDI"}, + {SD_RES_VDI_WRITE, "Failed to write the requested VDI"}, + {SD_RES_BASE_VDI_READ, "Failed to read the base VDI"}, + {SD_RES_BASE_VDI_WRITE, "Failed to write the base VDI"}, + {SD_RES_NO_TAG, "Failed to find the requested tag"}, + {SD_RES_STARTUP, "The system is still booting"}, + {SD_RES_VDI_NOT_LOCKED, "VDI isn't locked"}, + {SD_RES_SHUTDOWN, "The system is shutting down"}, + {SD_RES_NO_MEM, "Out of memory on the server"}, + {SD_RES_FULL_VDI, "We already have the maximum vdis"}, + {SD_RES_VER_MISMATCH, "Protocol version mismatch"}, + {SD_RES_NO_SPACE, "Server has no space for new objects"}, + {SD_RES_WAIT_FOR_FORMAT, "Sheepdog is waiting for a format operation"}, + {SD_RES_WAIT_FOR_JOIN, "Sheepdog is waiting for other nodes joining"}, + {SD_RES_JOIN_FAILED, "Target node had failed to join sheepdog"}, + }; + + for (i = 0; i < ARRAY_SIZE(errors); ++i) { + if (errors[i].err == err) { + return errors[i].desc; + } + } + + return "Invalid error code"; +} + +/* + * Sheepdog I/O handling: + * + * 1. In the sd_aio_readv/writev, read/write requests are added to the + * QEMU Bottom Halves. + * + * 2. In sd_readv_writev_bh_cb, the callbacks of BHs, we send the I/O + * requests to the server and link the requests to the + * outstanding_list in the BDRVSheepdogState. we exits the + * function without waiting for receiving the response. + * + * 3. We receive the response in aio_read_response, the fd handler to + * the sheepdog connection. If metadata update is needed, we send + * the write request to the vdi object in sd_write_done, the write + * completion function. The AIOCB callback is not called until all + * the requests belonging to the AIOCB are finished. + */ + +static inline AIOReq *alloc_aio_req(BDRVSheepdogState *s, SheepdogAIOCB *acb, + uint64_t oid, unsigned int data_len, + uint64_t offset, uint8_t flags, + uint64_t base_oid, unsigned int iov_offset) +{ + AIOReq *aio_req; + + aio_req = qemu_malloc(sizeof(*aio_req)); + aio_req->aiocb = acb; + aio_req->iov_offset = iov_offset; + aio_req->oid = oid; + aio_req->base_oid = base_oid; + aio_req->offset = offset; + aio_req->data_len = data_len; + aio_req->flags = flags; + aio_req->id = s->aioreq_seq_num++; + + QLIST_INSERT_HEAD(&s->outstanding_aio_head, aio_req, + outstanding_aio_siblings); + QLIST_INSERT_HEAD(&acb->aioreq_head, aio_req, aioreq_siblings); + + return aio_req; +} + +static inline int free_aio_req(BDRVSheepdogState *s, AIOReq *aio_req) +{ + SheepdogAIOCB *acb = aio_req->aiocb; + QLIST_REMOVE(aio_req, outstanding_aio_siblings); + QLIST_REMOVE(aio_req, aioreq_siblings); + qemu_free(aio_req); + + return !QLIST_EMPTY(&acb->aioreq_head); +} + +static void sd_finish_aiocb(SheepdogAIOCB *acb) +{ + if (!acb->canceled) { + acb->common.cb(acb->common.opaque, acb->ret); + } + qemu_aio_release(acb); +} + +static void sd_aio_cancel(BlockDriverAIOCB *blockacb) +{ + SheepdogAIOCB *acb = (SheepdogAIOCB *)blockacb; + + /* + * Sheepdog cannot cancel the requests which are already sent to + * the servers, so we just complete the request with -EIO here. + */ + acb->common.cb(acb->common.opaque, -EIO); + acb->canceled = 1; +} + +static AIOPool sd_aio_pool = { + .aiocb_size = sizeof(SheepdogAIOCB), + .cancel = sd_aio_cancel, +}; + +static SheepdogAIOCB *sd_aio_setup(BlockDriverState *bs, QEMUIOVector *qiov, + int64_t sector_num, int nb_sectors, + BlockDriverCompletionFunc *cb, void *opaque) +{ + SheepdogAIOCB *acb; + + acb = qemu_aio_get(&sd_aio_pool, bs, cb, opaque); + + acb->qiov = qiov; + + acb->sector_num = sector_num; + acb->nb_sectors = nb_sectors; + + acb->aio_done_func = NULL; + acb->canceled = 0; + acb->bh = NULL; + acb->ret = 0; + QLIST_INIT(&acb->aioreq_head); + return acb; +} + +static int sd_schedule_bh(QEMUBHFunc *cb, SheepdogAIOCB *acb) +{ + if (acb->bh) { + error_report("bug: %d %d\n", acb->aiocb_type, acb->aiocb_type); + return -EIO; + } + + acb->bh = qemu_bh_new(cb, acb); + if (!acb->bh) { + error_report("oom: %d %d\n", acb->aiocb_type, acb->aiocb_type); + return -EIO; + } + + qemu_bh_schedule(acb->bh); + + return 0; +} + +#ifdef _WIN32 + +struct msghdr { + struct iovec *msg_iov; + size_t msg_iovlen; +}; + +static ssize_t sendmsg(int s, const struct msghdr *msg, int flags) +{ + size_t size = 0; + char *buf, *p; + int i, ret; + + /* count the msg size */ + for (i = 0; i < msg->msg_iovlen; i++) { + size += msg->msg_iov[i].iov_len; + } + buf = qemu_malloc(size); + + p = buf; + for (i = 0; i < msg->msg_iovlen; i++) { + memcpy(p, msg->msg_iov[i].iov_base, msg->msg_iov[i].iov_len); + p += msg->msg_iov[i].iov_len; + } + + ret = send(s, buf, size, flags); + + qemu_free(buf); + return ret; +} + +static ssize_t recvmsg(int s, struct msghdr *msg, int flags) +{ + size_t size = 0; + char *buf, *p; + int i, ret; + + /* count the msg size */ + for (i = 0; i < msg->msg_iovlen; i++) { + size += msg->msg_iov[i].iov_len; + } + buf = qemu_malloc(size); + + ret = recv(s, buf, size, flags); + if (ret < 0) { + goto out; + } + + p = buf; + for (i = 0; i < msg->msg_iovlen; i++) { + memcpy(msg->msg_iov[i].iov_base, p, msg->msg_iov[i].iov_len); + p += msg->msg_iov[i].iov_len; + } +out: + qemu_free(buf); + return ret; +} + +#endif + +/* + * Send/recv data with iovec buffers + * + * This function send/recv data from/to the iovec buffer directly. + * The first `offset' bytes in the iovec buffer are skipped and next + * `len' bytes are used. + * + * For example, + * + * do_send_recv(sockfd, iov, len, offset, 1); + * + * is equals to + * + * char *buf = malloc(size); + * iov_to_buf(iov, iovcnt, buf, offset, size); + * send(sockfd, buf, size, 0); + * free(buf); + */ +static int do_send_recv(int sockfd, struct iovec *iov, int len, int offset, + int write) +{ + struct msghdr msg; + int ret, diff; + + memset(&msg, 0, sizeof(msg)); + msg.msg_iov = iov; + msg.msg_iovlen = 1; + + len += offset; + + while (iov->iov_len < len) { + len -= iov->iov_len; + + iov++; + msg.msg_iovlen++; + } + + diff = iov->iov_len - len; + iov->iov_len -= diff; + + while (msg.msg_iov->iov_len <= offset) { + offset -= msg.msg_iov->iov_len; + + msg.msg_iov++; + msg.msg_iovlen--; + } + + msg.msg_iov->iov_base = (char *) msg.msg_iov->iov_base + offset; + msg.msg_iov->iov_len -= offset; + + if (write) { + ret = sendmsg(sockfd, &msg, 0); + } else { + ret = recvmsg(sockfd, &msg, 0); + } + + msg.msg_iov->iov_base = (char *) msg.msg_iov->iov_base - offset; + msg.msg_iov->iov_len += offset; + + iov->iov_len += diff; + return ret; +} + +static int connect_to_sdog(const char *addr, const char *port) +{ + char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV]; + int fd, ret; + struct addrinfo hints, *res, *res0; + + if (!addr) { + addr = SD_DEFAULT_ADDR; + port = SD_DEFAULT_PORT; + } + + memset(&hints, 0, sizeof(hints)); + hints.ai_socktype = SOCK_STREAM; + + ret = getaddrinfo(addr, port, &hints, &res0); + if (ret) { + error_report("unable to get address info %s, %s\n", + addr, strerror(errno)); + return -1; + } + + for (res = res0; res; res = res->ai_next) { + ret = getnameinfo(res->ai_addr, res->ai_addrlen, hbuf, sizeof(hbuf), + sbuf, sizeof(sbuf), NI_NUMERICHOST | NI_NUMERICSERV); + if (ret) { + continue; + } + + fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol); + if (fd < 0) { + continue; + } + + reconnect: + ret = connect(fd, res->ai_addr, res->ai_addrlen); + if (ret < 0) { + if (errno == EINTR) { + goto reconnect; + } + break; + } + + dprintf("connected to %s:%s\n", addr, port); + goto success; + } + fd = -1; + error_report("failed connect to %s:%s\n", addr, port); +success: + freeaddrinfo(res0); + return fd; +} + +static int do_readv_writev(int sockfd, struct iovec *iov, int len, + int iov_offset, int write) +{ + int ret; +again: + ret = do_send_recv(sockfd, iov, len, iov_offset, write); + if (ret < 0) { + if (errno == EINTR || errno == EAGAIN) { + goto again; + } + error_report("failed to recv a rsp, %s\n", strerror(errno)); + return 1; + } + + iov_offset += ret; + len -= ret; + if (len) { + goto again; + } + + return 0; +} + +static int do_readv(int sockfd, struct iovec *iov, int len, int iov_offset) +{ + return do_readv_writev(sockfd, iov, len, iov_offset, 0); +} + +static int do_writev(int sockfd, struct iovec *iov, int len, int iov_offset) +{ + return do_readv_writev(sockfd, iov, len, iov_offset, 1); +} + +static int do_read_write(int sockfd, void *buf, int len, int write) +{ + struct iovec iov; + + iov.iov_base = buf; + iov.iov_len = len; + + return do_readv_writev(sockfd, &iov, len, 0, write); +} + +static int do_read(int sockfd, void *buf, int len) +{ + return do_read_write(sockfd, buf, len, 0); +} + +static int do_write(int sockfd, void *buf, int len) +{ + return do_read_write(sockfd, buf, len, 1); +} + +static int send_req(int sockfd, SheepdogReq *hdr, void *data, + unsigned int *wlen) +{ + int ret; + struct iovec iov[2]; + + iov[0].iov_base = hdr; + iov[0].iov_len = sizeof(*hdr); + + if (*wlen) { + iov[1].iov_base = data; + iov[1].iov_len = *wlen; + } + + ret = do_writev(sockfd, iov, sizeof(*hdr) + *wlen, 0); + if (ret) { + error_report("failed to send a req, %s\n", strerror(errno)); + ret = -1; + } + + return ret; +} + +static int do_req(int sockfd, SheepdogReq *hdr, void *data, + unsigned int *wlen, unsigned int *rlen) +{ + int ret; + + ret = send_req(sockfd, hdr, data, wlen); + if (ret) { + ret = -1; + goto out; + } + + ret = do_read(sockfd, hdr, sizeof(*hdr)); + if (ret) { + error_report("failed to get a rsp, %s\n", strerror(errno)); + ret = -1; + goto out; + } + + if (*rlen > hdr->data_length) { + *rlen = hdr->data_length; + } + + if (*rlen) { + ret = do_read(sockfd, data, *rlen); + if (ret) { + error_report("failed to get the data, %s\n", strerror(errno)); + ret = -1; + goto out; + } + } + ret = 0; +out: + return ret; +} + +static int add_aio_request(BDRVSheepdogState *s, AIOReq *aio_req, + struct iovec *iov, int niov, int create, + enum AIOCBState aiocb_type); + +/* + * This function searchs pending requests to the object `oid', and + * sends them. + */ +static void send_pending_req(BDRVSheepdogState *s, uint64_t oid, uint32_t id) +{ + AIOReq *aio_req, *next; + SheepdogAIOCB *acb; + int ret; + + QLIST_FOREACH_SAFE(aio_req, &s->outstanding_aio_head, + outstanding_aio_siblings, next) { + if (id == aio_req->id) { + continue; + } + if (aio_req->oid != oid) { + continue; + } + + acb = aio_req->aiocb; + ret = add_aio_request(s, aio_req, acb->qiov->iov, + acb->qiov->niov, 0, acb->aiocb_type); + if (ret < 0) { + error_report("add_aio_request is failed\n"); + free_aio_req(s, aio_req); + if (QLIST_EMPTY(&acb->aioreq_head)) { + sd_finish_aiocb(acb); + } + } + } +} + +/* + * Receive responses of the I/O requests. + * + * This function is registered as a fd handler, and called from the + * main loop when s->fd is ready for reading responses. + */ +static void aio_read_response(void *opaque) +{ + SheepdogObjRsp rsp; + BDRVSheepdogState *s = opaque; + int fd = s->fd; + int ret; + AIOReq *aio_req = NULL; + SheepdogAIOCB *acb; + int rest; + unsigned long idx; + + if (QLIST_EMPTY(&s->outstanding_aio_head)) { + return; + } + + /* read a header */ + ret = do_read(fd, &rsp, sizeof(rsp)); + if (ret) { + error_report("failed to get the header, %s\n", strerror(errno)); + return; + } + + /* find the right aio_req from the outstanding_aio list */ + QLIST_FOREACH(aio_req, &s->outstanding_aio_head, outstanding_aio_siblings) { + if (aio_req->id == rsp.id) { + break; + } + } + if (!aio_req) { + error_report("cannot find aio_req %x\n", rsp.id); + return; + } + + acb = aio_req->aiocb; + + switch (acb->aiocb_type) { + case AIOCB_WRITE_UDATA: + if (!is_data_obj(aio_req->oid)) { + break; + } + idx = data_oid_to_idx(aio_req->oid); + + if (s->inode.data_vdi_id[idx] != s->inode.vdi_id) { + /* + * If the object is newly created one, we need to update + * the vdi object (metadata object). min_dirty_data_idx + * and max_dirty_data_idx are changed to include updated + * index between them. + */ + s->inode.data_vdi_id[idx] = s->inode.vdi_id; + s->max_dirty_data_idx = MAX(idx, s->max_dirty_data_idx); + s->min_dirty_data_idx = MIN(idx, s->min_dirty_data_idx); + + /* + * Some requests may be blocked because simultaneous + * create requests are not allowed, so we search the + * pending requests here. + */ + send_pending_req(s, vid_to_data_oid(s->inode.vdi_id, idx), rsp.id); + } + break; + case AIOCB_READ_UDATA: + ret = do_readv(fd, acb->qiov->iov, rsp.data_length, + aio_req->iov_offset); + if (ret) { + error_report("failed to get the data, %s\n", strerror(errno)); + return; + } + break; + } + + if (rsp.result != SD_RES_SUCCESS) { + acb->ret = -EIO; + error_report("%s\n", sd_strerror(rsp.result)); + } + + rest = free_aio_req(s, aio_req); + if (!rest) { + /* + * We've finished all requests which belong to the AIOCB, so + * we can call the callback now. + */ + acb->aio_done_func(acb); + } +} + +static int aio_flush_request(void *opaque) +{ + BDRVSheepdogState *s = opaque; + + return !QLIST_EMPTY(&s->outstanding_aio_head); +} + +#if !defined(SOL_TCP) || !defined(TCP_CORK) + +static int set_cork(int fd, int v) +{ + return 0; +} + +#else + +static int set_cork(int fd, int v) +{ + return setsockopt(fd, SOL_TCP, TCP_CORK, &v, sizeof(v)); +} + +#endif + +static int set_nodelay(int fd) +{ + int ret, opt; + + opt = 1; + ret = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char *)&opt, sizeof(opt)); + return ret; +} + +/* + * Return a socket discriptor to read/write objects. + * + * We cannot use this discriptor for other operations because + * the block driver may be on waiting response from the server. + */ +static int get_sheep_fd(BDRVSheepdogState *s) +{ + int ret, fd; + + fd = connect_to_sdog(s->addr, s->port); + if (fd < 0) { + error_report("%s\n", strerror(errno)); + return -1; + } + + socket_set_nonblock(fd); + + ret = set_nodelay(fd); + if (ret) { + error_report("%s\n", strerror(errno)); + closesocket(fd); + return -1; + } + + qemu_aio_set_fd_handler(fd, aio_read_response, NULL, aio_flush_request, + NULL, s); + return fd; +} + +/* + * Parse a filename + * + * filename must be one of the following formats: + * 1. [vdiname] + * 2. [vdiname]:[snapid] + * 3. [vdiname]:[tag] + * 4. [hostname]:[port]:[vdiname] + * 5. [hostname]:[port]:[vdiname]:[snapid] + * 6. [hostname]:[port]:[vdiname]:[tag] + * + * You can boot from the snapshot images by specifying `snapid` or + * `tag'. + * + * You can run VMs outside the Sheepdog cluster by specifying + * `hostname' and `port' (experimental). + */ +static int parse_vdiname(BDRVSheepdogState *s, const char *filename, + char *vdi, uint32_t *snapid, char *tag) +{ + char *p, *q; + int nr_sep; + + p = q = qemu_strdup(filename); + + /* count the number of separators */ + nr_sep = 0; + while (*p) { + if (*p == ':') { + nr_sep++; + } + p++; + } + p = q; + + /* use the first two tokens as hostname and port number. */ + if (nr_sep >= 2) { + s->addr = p; + p = strchr(p, ':'); + *p++ = '\0'; + + s->port = p; + p = strchr(p, ':'); + *p++ = '\0'; + } else { + s->addr = NULL; + s->port = 0; + } + + strncpy(vdi, p, SD_MAX_VDI_LEN); + + p = strchr(vdi, ':'); + if (p) { + *p++ = '\0'; + *snapid = strtoul(p, NULL, 10); + if (*snapid == 0) { + strncpy(tag, p, SD_MAX_VDI_TAG_LEN); + } + } else { + *snapid = CURRENT_VDI_ID; /* search current vdi */ + } + + if (s->addr == NULL) { + qemu_free(q); + } + + return 0; +} + +static int find_vdi_name(BDRVSheepdogState *s, char *filename, uint32_t snapid, + char *tag, uint32_t *vid, int for_snapshot) +{ + int ret, fd; + SheepdogVdiReq hdr; + SheepdogVdiRsp *rsp = (SheepdogVdiRsp *)&hdr; + unsigned int wlen, rlen = 0; + char buf[SD_MAX_VDI_LEN + SD_MAX_VDI_TAG_LEN]; + + fd = connect_to_sdog(s->addr, s->port); + if (fd < 0) { + return -1; + } + + memset(buf, 0, sizeof(buf)); + strncpy(buf, filename, SD_MAX_VDI_LEN); + strncpy(buf + SD_MAX_VDI_LEN, tag, SD_MAX_VDI_TAG_LEN); + + memset(&hdr, 0, sizeof(hdr)); + if (for_snapshot) { + hdr.opcode = SD_OP_GET_VDI_INFO; + } else { + hdr.opcode = SD_OP_LOCK_VDI; + } + wlen = SD_MAX_VDI_LEN + SD_MAX_VDI_TAG_LEN; + hdr.proto_ver = SD_PROTO_VER; + hdr.data_length = wlen; + hdr.snapid = snapid; + hdr.flags = SD_FLAG_CMD_WRITE; + + ret = do_req(fd, (SheepdogReq *)&hdr, buf, &wlen, &rlen); + if (ret) { + ret = -1; + goto out; + } + + if (rsp->result != SD_RES_SUCCESS) { + error_report("cannot get vdi info, %s, %s %d %s\n", + sd_strerror(rsp->result), filename, snapid, tag); + ret = -1; + goto out; + } + *vid = rsp->vdi_id; + + ret = 0; +out: + closesocket(fd); + return ret; +} + +static int add_aio_request(BDRVSheepdogState *s, AIOReq *aio_req, + struct iovec *iov, int niov, int create, + enum AIOCBState aiocb_type) +{ + int nr_copies = s->inode.nr_copies; + SheepdogObjReq hdr; + unsigned int wlen; + int ret; + uint64_t oid = aio_req->oid; + unsigned int datalen = aio_req->data_len; + uint64_t offset = aio_req->offset; + uint8_t flags = aio_req->flags; + uint64_t old_oid = aio_req->base_oid; + + if (!nr_copies) { + error_report("bug\n"); + } + + memset(&hdr, 0, sizeof(hdr)); + + if (aiocb_type == AIOCB_READ_UDATA) { + wlen = 0; + hdr.opcode = SD_OP_READ_OBJ; + hdr.flags = flags; + } else if (create) { + wlen = datalen; + hdr.opcode = SD_OP_CREATE_AND_WRITE_OBJ; + hdr.flags = SD_FLAG_CMD_WRITE | flags; + } else { + wlen = datalen; + hdr.opcode = SD_OP_WRITE_OBJ; + hdr.flags = SD_FLAG_CMD_WRITE | flags; + } + + hdr.oid = oid; + hdr.cow_oid = old_oid; + hdr.copies = s->inode.nr_copies; + + hdr.data_length = datalen; + hdr.offset = offset; + + hdr.id = aio_req->id; + + set_cork(s->fd, 1); + + /* send a header */ + ret = do_write(s->fd, &hdr, sizeof(hdr)); + if (ret) { + error_report("failed to send a req, %s\n", strerror(errno)); + return -EIO; + } + + if (wlen) { + ret = do_writev(s->fd, iov, wlen, aio_req->iov_offset); + if (ret) { + error_report("failed to send a data, %s\n", strerror(errno)); + return -EIO; + } + } + + set_cork(s->fd, 0); + + return 0; +} + +static int read_write_object(int fd, char *buf, uint64_t oid, int copies, + unsigned int datalen, uint64_t offset, + int write, int create) +{ + SheepdogObjReq hdr; + SheepdogObjRsp *rsp = (SheepdogObjRsp *)&hdr; + unsigned int wlen, rlen; + int ret; + + memset(&hdr, 0, sizeof(hdr)); + + if (write) { + wlen = datalen; + rlen = 0; + hdr.flags = SD_FLAG_CMD_WRITE; + if (create) { + hdr.opcode = SD_OP_CREATE_AND_WRITE_OBJ; + } else { + hdr.opcode = SD_OP_WRITE_OBJ; + } + } else { + wlen = 0; + rlen = datalen; + hdr.opcode = SD_OP_READ_OBJ; + } + hdr.oid = oid; + hdr.data_length = datalen; + hdr.offset = offset; + hdr.copies = copies; + + ret = do_req(fd, (SheepdogReq *)&hdr, buf, &wlen, &rlen); + if (ret) { + error_report("failed to send a request to the sheep\n"); + return -1; + } + + switch (rsp->result) { + case SD_RES_SUCCESS: + return 0; + default: + error_report("%s\n", sd_strerror(rsp->result)); + return -1; + } +} + +static int read_object(int fd, char *buf, uint64_t oid, int copies, + unsigned int datalen, uint64_t offset) +{ + return read_write_object(fd, buf, oid, copies, datalen, offset, 0, 0); +} + +static int write_object(int fd, char *buf, uint64_t oid, int copies, + unsigned int datalen, uint64_t offset, int create) +{ + return read_write_object(fd, buf, oid, copies, datalen, offset, 1, create); +} + +static int sd_open(BlockDriverState *bs, const char *filename, int flags) +{ + int ret, fd; + uint32_t vid = 0; + BDRVSheepdogState *s = bs->opaque; + char vdi[SD_MAX_VDI_LEN], tag[SD_MAX_VDI_TAG_LEN]; + uint32_t snapid; + char *buf = NULL; + + strstart(filename, "sheepdog:", (const char **)&filename); + + QLIST_INIT(&s->outstanding_aio_head); + s->fd = -1; + + memset(vdi, 0, sizeof(vdi)); + memset(tag, 0, sizeof(tag)); + if (parse_vdiname(s, filename, vdi, &snapid, tag) < 0) { + goto out; + } + s->fd = get_sheep_fd(s); + if (s->fd < 0) { + goto out; + } + + ret = find_vdi_name(s, vdi, snapid, tag, &vid, 0); + if (ret) { + goto out; + } + + if (snapid) { + dprintf("%" PRIx32 " snapshot inode was open.\n", vid); + s->is_snapshot = 1; + } + + fd = connect_to_sdog(s->addr, s->port); + if (fd < 0) { + error_report("failed to connect\n"); + goto out; + } + + buf = qemu_malloc(SD_INODE_SIZE); + ret = read_object(fd, buf, vid_to_vdi_oid(vid), 0, SD_INODE_SIZE, 0); + + closesocket(fd); + + if (ret) { + goto out; + } + + memcpy(&s->inode, buf, sizeof(s->inode)); + s->min_dirty_data_idx = UINT32_MAX; + s->max_dirty_data_idx = 0; + + bs->total_sectors = s->inode.vdi_size / SECTOR_SIZE; + strncpy(s->name, vdi, sizeof(s->name)); + qemu_free(buf); + return 0; +out: + qemu_aio_set_fd_handler(s->fd, NULL, NULL, NULL, NULL, NULL); + if (s->fd >= 0) { + closesocket(s->fd); + } + qemu_free(buf); + return -1; +} + +static int do_sd_create(char *filename, int64_t vdi_size, + uint32_t base_vid, uint32_t *vdi_id, int snapshot, + const char *addr, const char *port) +{ + SheepdogVdiReq hdr; + SheepdogVdiRsp *rsp = (SheepdogVdiRsp *)&hdr; + int fd, ret; + unsigned int wlen, rlen = 0; + char buf[SD_MAX_VDI_LEN]; + + fd = connect_to_sdog(addr, port); + if (fd < 0) { + return -EIO; + } + + memset(buf, 0, sizeof(buf)); + strncpy(buf, filename, SD_MAX_VDI_LEN); + + memset(&hdr, 0, sizeof(hdr)); + hdr.opcode = SD_OP_NEW_VDI; + hdr.base_vdi_id = base_vid; + + wlen = SD_MAX_VDI_LEN; + + hdr.flags = SD_FLAG_CMD_WRITE; + hdr.snapid = snapshot; + + hdr.data_length = wlen; + hdr.vdi_size = vdi_size; + + ret = do_req(fd, (SheepdogReq *)&hdr, buf, &wlen, &rlen); + + closesocket(fd); + + if (ret) { + return -EIO; + } + + if (rsp->result != SD_RES_SUCCESS) { + error_report("%s, %s\n", sd_strerror(rsp->result), filename); + return -EIO; + } + + if (vdi_id) { + *vdi_id = rsp->vdi_id; + } + + return 0; +} + +static int sd_create(const char *filename, QEMUOptionParameter *options) +{ + int ret; + uint32_t vid = 0, base_vid = 0; + int64_t vdi_size = 0; + char *backing_file = NULL; + BDRVSheepdogState s; + char vdi[SD_MAX_VDI_LEN], tag[SD_MAX_VDI_TAG_LEN]; + uint32_t snapid; + + strstart(filename, "sheepdog:", (const char **)&filename); + + memset(&s, 0, sizeof(s)); + memset(vdi, 0, sizeof(vdi)); + memset(tag, 0, sizeof(tag)); + if (parse_vdiname(&s, filename, vdi, &snapid, tag) < 0) { + error_report("invalid filename\n"); + return -EINVAL; + } + + while (options && options->name) { + if (!strcmp(options->name, BLOCK_OPT_SIZE)) { + vdi_size = options->value.n; + } else if (!strcmp(options->name, BLOCK_OPT_BACKING_FILE)) { + backing_file = options->value.s; + } + options++; + } + + if (vdi_size > SD_MAX_VDI_SIZE) { + error_report("too big image size\n"); + return -EINVAL; + } + + if (backing_file) { + BlockDriverState *bs; + BDRVSheepdogState *s; + BlockDriver *drv; + + /* Currently, only Sheepdog backing image is supported. */ + drv = bdrv_find_protocol(backing_file); + if (!drv || strcmp(drv->protocol_name, "sheepdog") != 0) { + error_report("backing_file must be a sheepdog image\n"); + return -EINVAL; + } + + ret = bdrv_file_open(&bs, backing_file, 0); + if (ret < 0) + return -EIO; + + s = bs->opaque; + + if (!is_snapshot(&s->inode)) { + error_report("cannot clone from a non snapshot vdi\n"); + bdrv_delete(bs); + return -EINVAL; + } + + base_vid = s->inode.vdi_id; + bdrv_delete(bs); + } + + return do_sd_create((char *)vdi, vdi_size, base_vid, &vid, 0, s.addr, s.port); +} + +static void sd_close(BlockDriverState *bs) +{ + BDRVSheepdogState *s = bs->opaque; + SheepdogVdiReq hdr; + SheepdogVdiRsp *rsp = (SheepdogVdiRsp *)&hdr; + unsigned int wlen, rlen = 0; + int fd, ret; + + dprintf("%s\n", s->name); + + fd = connect_to_sdog(s->addr, s->port); + if (fd < 0) { + return; + } + + memset(&hdr, 0, sizeof(hdr)); + + hdr.opcode = SD_OP_RELEASE_VDI; + wlen = strlen(s->name) + 1; + hdr.data_length = wlen; + hdr.flags = SD_FLAG_CMD_WRITE; + + ret = do_req(fd, (SheepdogReq *)&hdr, s->name, &wlen, &rlen); + + closesocket(fd); + + if (!ret && rsp->result != SD_RES_SUCCESS && + rsp->result != SD_RES_VDI_NOT_LOCKED) { + error_report("%s, %s\n", sd_strerror(rsp->result), s->name); + } + + qemu_aio_set_fd_handler(s->fd, NULL, NULL, NULL, NULL, NULL); + closesocket(s->fd); + qemu_free(s->addr); +} + +static int64_t sd_getlength(BlockDriverState *bs) +{ + BDRVSheepdogState *s = bs->opaque; + + return s->inode.vdi_size; +} + +static int sd_truncate(BlockDriverState *bs, int64_t offset) +{ + BDRVSheepdogState *s = bs->opaque; + int ret, fd; + unsigned int datalen; + + if (offset < s->inode.vdi_size) { + error_report("shrinking is not supported\n"); + return -EINVAL; + } else if (offset > SD_MAX_VDI_SIZE) { + error_report("too big image size\n"); + return -EINVAL; + } + + fd = connect_to_sdog(s->addr, s->port); + if (fd < 0) { + return -EIO; + } + + /* we don't need to update entire object */ + datalen = SD_INODE_SIZE - sizeof(s->inode.data_vdi_id); + s->inode.vdi_size = offset; + ret = write_object(fd, (char *)&s->inode, vid_to_vdi_oid(s->inode.vdi_id), + s->inode.nr_copies, datalen, 0, 0); + close(fd); + + if (ret < 0) { + error_report("failed to update an inode.\n"); + return -EIO; + } + + return 0; +} + +/* + * This function is called after writing data objects. If we need to + * update metadata, this sends a write request to the vdi object. + * Otherwise, this calls the AIOCB callback. + */ +static void sd_write_done(SheepdogAIOCB *acb) +{ + int ret; + BDRVSheepdogState *s = acb->common.bs->opaque; + struct iovec iov; + AIOReq *aio_req; + uint32_t offset, data_len, mn, mx; + + mn = s->min_dirty_data_idx; + mx = s->max_dirty_data_idx; + if (mn <= mx) { + /* we need to update the vdi object. */ + offset = sizeof(s->inode) - sizeof(s->inode.data_vdi_id) + + mn * sizeof(s->inode.data_vdi_id[0]); + data_len = (mx - mn + 1) * sizeof(s->inode.data_vdi_id[0]); + + s->min_dirty_data_idx = UINT32_MAX; + s->max_dirty_data_idx = 0; + + iov.iov_base = &s->inode; + iov.iov_len = sizeof(s->inode); + aio_req = alloc_aio_req(s, acb, vid_to_vdi_oid(s->inode.vdi_id), + data_len, offset, 0, 0, offset); + ret = add_aio_request(s, aio_req, &iov, 1, 0, AIOCB_WRITE_UDATA); + if (ret) { + free_aio_req(s, aio_req); + acb->ret = -EIO; + goto out; + } + + acb->aio_done_func = sd_finish_aiocb; + acb->aiocb_type = AIOCB_WRITE_UDATA; + return; + } +out: + sd_finish_aiocb(acb); +} + +/* + * Create a writable VDI from a snapshot + */ +static int sd_create_branch(BDRVSheepdogState *s) +{ + int ret, fd; + uint32_t vid; + char *buf; + + dprintf("%" PRIx32 " is snapshot.\n", s->inode.vdi_id); + + buf = qemu_malloc(SD_INODE_SIZE); + + ret = do_sd_create(s->name, s->inode.vdi_size, s->inode.vdi_id, &vid, 1, + s->addr, s->port); + if (ret) { + goto out; + } + + dprintf("%" PRIx32 " is created.\n", vid); + + fd = connect_to_sdog(s->addr, s->port); + if (fd < 0) { + error_report("failed to connect\n"); + goto out; + } + + ret = read_object(fd, buf, vid_to_vdi_oid(vid), s->inode.nr_copies, + SD_INODE_SIZE, 0); + + closesocket(fd); + + if (ret < 0) { + goto out; + } + + memcpy(&s->inode, buf, sizeof(s->inode)); + + s->is_snapshot = 0; + ret = 0; + dprintf("%" PRIx32 " was newly created.\n", s->inode.vdi_id); + +out: + qemu_free(buf); + + return ret; +} + +/* + * Send I/O requests to the server. + * + * This function sends requests to the server, links the requests to + * the outstanding_list in BDRVSheepdogState, and exits without + * waiting the response. The responses are received in the + * `aio_read_response' function which is called from the main loop as + * a fd handler. + */ +static void sd_readv_writev_bh_cb(void *p) +{ + SheepdogAIOCB *acb = p; + int ret = 0; + unsigned long len, done = 0, total = acb->nb_sectors * SECTOR_SIZE; + unsigned long idx = acb->sector_num * SECTOR_SIZE / SD_DATA_OBJ_SIZE; + uint64_t oid; + uint64_t offset = (acb->sector_num * SECTOR_SIZE) % SD_DATA_OBJ_SIZE; + BDRVSheepdogState *s = acb->common.bs->opaque; + SheepdogInode *inode = &s->inode; + AIOReq *aio_req; + + qemu_bh_delete(acb->bh); + acb->bh = NULL; + + if (acb->aiocb_type == AIOCB_WRITE_UDATA && s->is_snapshot) { + /* + * In the case we open the snapshot VDI, Sheepdog creates the + * writable VDI when we do a write operation first. + */ + ret = sd_create_branch(s); + if (ret) { + acb->ret = -EIO; + goto out; + } + } + + while (done != total) { + uint8_t flags = 0; + uint64_t old_oid = 0; + int create = 0; + + oid = vid_to_data_oid(inode->data_vdi_id[idx], idx); + + len = MIN(total - done, SD_DATA_OBJ_SIZE - offset); + + if (!inode->data_vdi_id[idx]) { + if (acb->aiocb_type == AIOCB_READ_UDATA) { + goto done; + } + + create = 1; + } else if (acb->aiocb_type == AIOCB_WRITE_UDATA + && !is_data_obj_writeable(inode, idx)) { + /* Copy-On-Write */ + create = 1; + old_oid = oid; + flags = SD_FLAG_CMD_COW; + } + + if (create) { + dprintf("update ino (%" PRIu32") %" PRIu64 " %" PRIu64 + " %" PRIu64 "\n", inode->vdi_id, oid, + vid_to_data_oid(inode->data_vdi_id[idx], idx), idx); + oid = vid_to_data_oid(inode->vdi_id, idx); + dprintf("new oid %lx\n", oid); + } + + aio_req = alloc_aio_req(s, acb, oid, len, offset, flags, old_oid, done); + + if (create) { + AIOReq *areq; + QLIST_FOREACH(areq, &s->outstanding_aio_head, + outstanding_aio_siblings) { + if (areq == aio_req) { + continue; + } + if (areq->oid == oid) { + /* + * Sheepdog cannot handle simultaneous create + * requests to the same object. So we cannot send + * the request until the previous request + * finishes. + */ + aio_req->flags = 0; + aio_req->base_oid = 0; + goto done; + } + } + } + + ret = add_aio_request(s, aio_req, acb->qiov->iov, acb->qiov->niov, + create, acb->aiocb_type); + if (ret < 0) { + error_report("add_aio_request is failed\n"); + free_aio_req(s, aio_req); + acb->ret = -EIO; + goto out; + } + done: + offset = 0; + idx++; + done += len; + } +out: + if (QLIST_EMPTY(&acb->aioreq_head)) { + sd_finish_aiocb(acb); + } +} + +static BlockDriverAIOCB *sd_aio_writev(BlockDriverState *bs, int64_t sector_num, + QEMUIOVector *qiov, int nb_sectors, + BlockDriverCompletionFunc *cb, + void *opaque) +{ + SheepdogAIOCB *acb; + + if (bs->growable && sector_num + nb_sectors > bs->total_sectors) { + /* TODO: shouldn't block here */ + if (sd_truncate(bs, (sector_num + nb_sectors) * SECTOR_SIZE) < 0) { + return NULL; + } + bs->total_sectors = sector_num + nb_sectors; + } + + acb = sd_aio_setup(bs, qiov, sector_num, nb_sectors, cb, opaque); + acb->aio_done_func = sd_write_done; + acb->aiocb_type = AIOCB_WRITE_UDATA; + + sd_schedule_bh(sd_readv_writev_bh_cb, acb); + return &acb->common; +} + +static BlockDriverAIOCB *sd_aio_readv(BlockDriverState *bs, int64_t sector_num, + QEMUIOVector *qiov, int nb_sectors, + BlockDriverCompletionFunc *cb, + void *opaque) +{ + SheepdogAIOCB *acb; + int i; + + acb = sd_aio_setup(bs, qiov, sector_num, nb_sectors, cb, opaque); + acb->aiocb_type = AIOCB_READ_UDATA; + acb->aio_done_func = sd_finish_aiocb; + + /* + * TODO: we can do better; we don't need to initialize + * blindly. + */ + for (i = 0; i < qiov->niov; i++) { + memset(qiov->iov[i].iov_base, 0, qiov->iov[i].iov_len); + } + + sd_schedule_bh(sd_readv_writev_bh_cb, acb); + return &acb->common; +} + +static int sd_snapshot_create(BlockDriverState *bs, QEMUSnapshotInfo *sn_info) +{ + BDRVSheepdogState *s = bs->opaque; + int ret, fd; + uint32_t new_vid; + SheepdogInode *inode; + unsigned int datalen; + + dprintf("sn_info: name %s id_str %s s: name %s vm_state_size %d " + "is_snapshot %d\n", sn_info->name, sn_info->id_str, + s->name, sn_info->vm_state_size, s->is_snapshot); + + if (s->is_snapshot) { + error_report("You can't create a snapshot of a snapshot VDI, " + "%s (%" PRIu32 ").\n", s->name, s->inode.vdi_id); + + return -EINVAL; + } + + dprintf("%s %s\n", sn_info->name, sn_info->id_str); + + s->inode.vm_state_size = sn_info->vm_state_size; + s->inode.vm_clock_nsec = sn_info->vm_clock_nsec; + strncpy(s->inode.tag, sn_info->name, sizeof(s->inode.tag)); + /* we don't need to update entire object */ + datalen = SD_INODE_SIZE - sizeof(s->inode.data_vdi_id); + + /* refresh inode. */ + fd = connect_to_sdog(s->addr, s->port); + if (fd < 0) { + ret = -EIO; + goto cleanup; + } + + ret = write_object(fd, (char *)&s->inode, vid_to_vdi_oid(s->inode.vdi_id), + s->inode.nr_copies, datalen, 0, 0); + if (ret < 0) { + error_report("failed to write snapshot's inode.\n"); + ret = -EIO; + goto cleanup; + } + + ret = do_sd_create(s->name, s->inode.vdi_size, s->inode.vdi_id, &new_vid, 1, + s->addr, s->port); + if (ret < 0) { + error_report("failed to create inode for snapshot. %s\n", + strerror(errno)); + ret = -EIO; + goto cleanup; + } + + inode = (SheepdogInode *)qemu_malloc(datalen); + + ret = read_object(fd, (char *)inode, vid_to_vdi_oid(new_vid), + s->inode.nr_copies, datalen, 0); + + if (ret < 0) { + error_report("failed to read new inode info. %s\n", strerror(errno)); + ret = -EIO; + goto cleanup; + } + + memcpy(&s->inode, inode, datalen); + dprintf("s->inode: name %s snap_id %x oid %x\n", + s->inode.name, s->inode.snap_id, s->inode.vdi_id); + +cleanup: + closesocket(fd); + return ret; +} + +static int sd_snapshot_goto(BlockDriverState *bs, const char *snapshot_id) +{ + BDRVSheepdogState *s = bs->opaque; + BDRVSheepdogState *old_s; + char vdi[SD_MAX_VDI_LEN], tag[SD_MAX_VDI_TAG_LEN]; + char *buf = NULL; + uint32_t vid; + uint32_t snapid = 0; + int ret = -ENOENT, fd; + + old_s = qemu_malloc(sizeof(BDRVSheepdogState)); + + memcpy(old_s, s, sizeof(BDRVSheepdogState)); + + memset(vdi, 0, sizeof(vdi)); + strncpy(vdi, s->name, sizeof(vdi)); + + memset(tag, 0, sizeof(tag)); + snapid = strtoul(snapshot_id, NULL, 10); + if (!snapid) { + strncpy(tag, s->name, sizeof(tag)); + } + + ret = find_vdi_name(s, vdi, snapid, tag, &vid, 1); + if (ret) { + error_report("Failed to find_vdi_name\n"); + ret = -ENOENT; + goto out; + } + + fd = connect_to_sdog(s->addr, s->port); + if (fd < 0) { + error_report("failed to connect\n"); + goto out; + } + + buf = qemu_malloc(SD_INODE_SIZE); + ret = read_object(fd, buf, vid_to_vdi_oid(vid), s->inode.nr_copies, + SD_INODE_SIZE, 0); + + closesocket(fd); + + if (ret) { + ret = -ENOENT; + goto out; + } + + memcpy(&s->inode, buf, sizeof(s->inode)); + + if (!s->inode.vm_state_size) { + error_report("Invalid snapshot\n"); + ret = -ENOENT; + goto out; + } + + s->is_snapshot = 1; + + qemu_free(buf); + qemu_free(old_s); + + return 0; +out: + /* recover bdrv_sd_state */ + memcpy(s, old_s, sizeof(BDRVSheepdogState)); + qemu_free(buf); + qemu_free(old_s); + + error_report("failed to open. recover old bdrv_sd_state.\n"); + + return ret; +} + +static int sd_snapshot_delete(BlockDriverState *bs, const char *snapshot_id) +{ + /* FIXME: Delete specified snapshot id. */ + return 0; +} + +#define DIV_ROUND_UP(n,d) (((n) + (d) - 1) / (d)) +#define BITS_PER_BYTE 8 +#define BITS_TO_LONGS(nr) DIV_ROUND_UP(nr, BITS_PER_BYTE * sizeof(long)) +#define DECLARE_BITMAP(name,bits) \ + unsigned long name[BITS_TO_LONGS(bits)] + +#define BITS_PER_LONG (BITS_PER_BYTE * sizeof(long)) + +static inline int test_bit(unsigned int nr, const unsigned long *addr) +{ + return ((1UL << (nr % BITS_PER_LONG)) & + (((unsigned long *)addr)[nr / BITS_PER_LONG])) != 0; +} + +static int sd_snapshot_list(BlockDriverState *bs, QEMUSnapshotInfo **psn_tab) +{ + BDRVSheepdogState *s = bs->opaque; + SheepdogReq req; + int fd, nr = 1024, ret, max = BITS_TO_LONGS(SD_NR_VDIS) * sizeof(long); + QEMUSnapshotInfo *sn_tab = NULL; + unsigned wlen, rlen; + int found = 0; + static SheepdogInode inode; + unsigned long *vdi_inuse; + unsigned int start_nr; + uint64_t hval; + uint32_t vid; + + vdi_inuse = qemu_malloc(max); + + fd = connect_to_sdog(s->addr, s->port); + if (fd < 0) { + goto out; + } + + rlen = max; + wlen = 0; + + memset(&req, 0, sizeof(req)); + + req.opcode = SD_OP_READ_VDIS; + req.data_length = max; + + ret = do_req(fd, (SheepdogReq *)&req, vdi_inuse, &wlen, &rlen); + + closesocket(fd); + if (ret) { + goto out; + } + + sn_tab = qemu_mallocz(nr * sizeof(*sn_tab)); + + /* calculate a vdi id with hash function */ + hval = fnv_64a_buf(s->name, strlen(s->name), FNV1A_64_INIT); + start_nr = hval & (SD_NR_VDIS - 1); + + fd = connect_to_sdog(s->addr, s->port); + if (fd < 0) { + error_report("failed to connect\n"); + goto out; + } + + for (vid = start_nr; found < nr; vid = (vid + 1) % SD_NR_VDIS) { + if (!test_bit(vid, vdi_inuse)) { + break; + } + + /* we don't need to read entire object */ + ret = read_object(fd, (char *)&inode, vid_to_vdi_oid(vid), + 0, SD_INODE_SIZE - sizeof(inode.data_vdi_id), 0); + + if (ret) { + continue; + } + + if (!strcmp(inode.name, s->name) && is_snapshot(&inode)) { + sn_tab[found].date_sec = inode.snap_ctime >> 32; + sn_tab[found].date_nsec = inode.snap_ctime & 0xffffffff; + sn_tab[found].vm_state_size = inode.vm_state_size; + sn_tab[found].vm_clock_nsec = inode.vm_clock_nsec; + + snprintf(sn_tab[found].id_str, sizeof(sn_tab[found].id_str), "%u", + inode.snap_id); + strncpy(sn_tab[found].name, inode.tag, + MIN(sizeof(sn_tab[found].name), sizeof(inode.tag))); + found++; + } + } + + closesocket(fd); +out: + *psn_tab = sn_tab; + + qemu_free(vdi_inuse); + + return found; +} + +static int do_load_save_vmstate(BDRVSheepdogState *s, uint8_t *data, + int64_t pos, int size, int load) +{ + int fd, create; + int ret = 0; + unsigned int data_len; + uint64_t vmstate_oid; + uint32_t vdi_index; + uint64_t offset; + + fd = connect_to_sdog(s->addr, s->port); + if (fd < 0) { + ret = -EIO; + goto cleanup; + } + + while (size) { + vdi_index = pos / SD_DATA_OBJ_SIZE; + offset = pos % SD_DATA_OBJ_SIZE; + + data_len = MIN(size, SD_DATA_OBJ_SIZE); + + vmstate_oid = vid_to_vmstate_oid(s->inode.vdi_id, vdi_index); + + create = (offset == 0); + if (load) { + ret = read_object(fd, (char *)data, vmstate_oid, + s->inode.nr_copies, data_len, offset); + } else { + ret = write_object(fd, (char *)data, vmstate_oid, + s->inode.nr_copies, data_len, offset, create); + } + + if (ret < 0) { + error_report("failed to save vmstate %s\n", strerror(errno)); + ret = -EIO; + goto cleanup; + } + + pos += data_len; + size -= data_len; + ret += data_len; + } +cleanup: + closesocket(fd); + return ret; +} + +static int sd_save_vmstate(BlockDriverState *bs, const uint8_t *data, + int64_t pos, int size) +{ + BDRVSheepdogState *s = bs->opaque; + + return do_load_save_vmstate(s, (uint8_t *)data, pos, size, 0); +} + +static int sd_load_vmstate(BlockDriverState *bs, uint8_t *data, + int64_t pos, int size) +{ + BDRVSheepdogState *s = bs->opaque; + + return do_load_save_vmstate(s, data, pos, size, 1); +} + + +static QEMUOptionParameter sd_create_options[] = { + { + .name = BLOCK_OPT_SIZE, + .type = OPT_SIZE, + .help = "Virtual disk size" + }, + { + .name = BLOCK_OPT_BACKING_FILE, + .type = OPT_STRING, + .help = "File name of a base image" + }, + { NULL } +}; + +BlockDriver bdrv_sheepdog = { + .format_name = "sheepdog", + .protocol_name = "sheepdog", + .instance_size = sizeof(BDRVSheepdogState), + .bdrv_file_open = sd_open, + .bdrv_close = sd_close, + .bdrv_create = sd_create, + .bdrv_getlength = sd_getlength, + .bdrv_truncate = sd_truncate, + + .bdrv_aio_readv = sd_aio_readv, + .bdrv_aio_writev = sd_aio_writev, + + .bdrv_snapshot_create = sd_snapshot_create, + .bdrv_snapshot_goto = sd_snapshot_goto, + .bdrv_snapshot_delete = sd_snapshot_delete, + .bdrv_snapshot_list = sd_snapshot_list, + + .bdrv_save_vmstate = sd_save_vmstate, + .bdrv_load_vmstate = sd_load_vmstate, + + .create_options = sd_create_options, +}; + +static void bdrv_sheepdog_init(void) +{ + bdrv_register(&bdrv_sheepdog); +} +block_init(bdrv_sheepdog_init); diff --git a/block/vdi.c b/block/vdi.c new file mode 100644 index 0000000..116b25b --- /dev/null +++ b/block/vdi.c @@ -0,0 +1,961 @@ +/* + * Block driver for the Virtual Disk Image (VDI) format + * + * Copyright (c) 2009 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) 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 . + * + * Reference: + * http://forums.virtualbox.org/viewtopic.php?t=8046 + * + * This driver supports create / read / write operations on VDI images. + * + * Todo (see also TODO in code): + * + * Some features like snapshots are still missing. + * + * Deallocation of zero-filled blocks and shrinking images are missing, too + * (might be added to common block layer). + * + * Allocation of blocks could be optimized (less writes to block map and + * header). + * + * Read and write of adjacents blocks could be done in one operation + * (current code uses one operation per block (1 MiB). + * + * The code is not thread safe (missing locks for changes in header and + * block table, no problem with current QEMU). + * + * Hints: + * + * Blocks (VDI documentation) correspond to clusters (QEMU). + * QEMU's backing files could be implemented using VDI snapshot files (TODO). + * VDI snapshot files may also contain the complete machine state. + * Maybe this machine state can be converted to QEMU PC machine snapshot data. + * + * The driver keeps a block cache (little endian entries) in memory. + * For the standard block size (1 MiB), a 1 TiB disk will use 4 MiB RAM, + * so this seems to be reasonable. + */ + +#include "qemu-common.h" +#include "block_int.h" +#include "module.h" + +#if defined(CONFIG_UUID) +#include +#else +/* TODO: move uuid emulation to some central place in QEMU. */ +#include "sysemu.h" /* UUID_FMT */ +typedef unsigned char uuid_t[16]; +void uuid_generate(uuid_t out); +int uuid_is_null(const uuid_t uu); +void uuid_unparse(const uuid_t uu, char *out); +#endif + +/* Code configuration options. */ + +/* Enable debug messages. */ +//~ #define CONFIG_VDI_DEBUG + +/* Support write operations on VDI images. */ +#define CONFIG_VDI_WRITE + +/* Support non-standard block (cluster) size. This is untested. + * Maybe it will be needed for very large images. + */ +//~ #define CONFIG_VDI_BLOCK_SIZE + +/* Support static (fixed, pre-allocated) images. */ +#define CONFIG_VDI_STATIC_IMAGE + +/* Command line option for static images. */ +#define BLOCK_OPT_STATIC "static" + +#define KiB 1024 +#define MiB (KiB * KiB) + +#define SECTOR_SIZE 512 + +#if defined(CONFIG_VDI_DEBUG) +#define logout(fmt, ...) \ + fprintf(stderr, "vdi\t%-24s" fmt, __func__, ##__VA_ARGS__) +#else +#define logout(fmt, ...) ((void)0) +#endif + +/* Image signature. */ +#define VDI_SIGNATURE 0xbeda107f + +/* Image version. */ +#define VDI_VERSION_1_1 0x00010001 + +/* Image type. */ +#define VDI_TYPE_DYNAMIC 1 +#define VDI_TYPE_STATIC 2 + +/* Innotek / SUN images use these strings in header.text: + * "<<< innotek VirtualBox Disk Image >>>\n" + * "<<< Sun xVM VirtualBox Disk Image >>>\n" + * "<<< Sun VirtualBox Disk Image >>>\n" + * The value does not matter, so QEMU created images use a different text. + */ +#define VDI_TEXT "<<< QEMU VM Virtual Disk Image >>>\n" + +/* Unallocated blocks use this index (no need to convert endianess). */ +#define VDI_UNALLOCATED UINT32_MAX + +#if !defined(CONFIG_UUID) +void uuid_generate(uuid_t out) +{ + memset(out, 0, sizeof(uuid_t)); +} + +int uuid_is_null(const uuid_t uu) +{ + uuid_t null_uuid = { 0 }; + return memcmp(uu, null_uuid, sizeof(uuid_t)) == 0; +} + +void uuid_unparse(const uuid_t uu, char *out) +{ + snprintf(out, 37, UUID_FMT, + uu[0], uu[1], uu[2], uu[3], uu[4], uu[5], uu[6], uu[7], + uu[8], uu[9], uu[10], uu[11], uu[12], uu[13], uu[14], uu[15]); +} +#endif + +typedef struct { + BlockDriverAIOCB common; + int64_t sector_num; + QEMUIOVector *qiov; + uint8_t *buf; + /* Total number of sectors. */ + int nb_sectors; + /* Number of sectors for current AIO. */ + int n_sectors; + /* New allocated block map entry. */ + uint32_t bmap_first; + uint32_t bmap_last; + /* Buffer for new allocated block. */ + void *block_buffer; + void *orig_buf; + int header_modified; + BlockDriverAIOCB *hd_aiocb; + struct iovec hd_iov; + QEMUIOVector hd_qiov; + QEMUBH *bh; +} VdiAIOCB; + +typedef struct { + char text[0x40]; + uint32_t signature; + uint32_t version; + uint32_t header_size; + uint32_t image_type; + uint32_t image_flags; + char description[256]; + uint32_t offset_bmap; + uint32_t offset_data; + uint32_t cylinders; /* disk geometry, unused here */ + uint32_t heads; /* disk geometry, unused here */ + uint32_t sectors; /* disk geometry, unused here */ + uint32_t sector_size; + uint32_t unused1; + uint64_t disk_size; + uint32_t block_size; + uint32_t block_extra; /* unused here */ + uint32_t blocks_in_image; + uint32_t blocks_allocated; + uuid_t uuid_image; + uuid_t uuid_last_snap; + uuid_t uuid_link; + uuid_t uuid_parent; + uint64_t unused2[7]; +} VdiHeader; + +typedef struct { + /* The block map entries are little endian (even in memory). */ + uint32_t *bmap; + /* Size of block (bytes). */ + uint32_t block_size; + /* Size of block (sectors). */ + uint32_t block_sectors; + /* First sector of block map. */ + uint32_t bmap_sector; + /* VDI header (converted to host endianess). */ + VdiHeader header; +} BDRVVdiState; + +/* Change UUID from little endian (IPRT = VirtualBox format) to big endian + * format (network byte order, standard, see RFC 4122) and vice versa. + */ +static void uuid_convert(uuid_t uuid) +{ + bswap32s((uint32_t *)&uuid[0]); + bswap16s((uint16_t *)&uuid[4]); + bswap16s((uint16_t *)&uuid[6]); +} + +static void vdi_header_to_cpu(VdiHeader *header) +{ + le32_to_cpus(&header->signature); + le32_to_cpus(&header->version); + le32_to_cpus(&header->header_size); + le32_to_cpus(&header->image_type); + le32_to_cpus(&header->image_flags); + le32_to_cpus(&header->offset_bmap); + le32_to_cpus(&header->offset_data); + le32_to_cpus(&header->cylinders); + le32_to_cpus(&header->heads); + le32_to_cpus(&header->sectors); + le32_to_cpus(&header->sector_size); + le64_to_cpus(&header->disk_size); + le32_to_cpus(&header->block_size); + le32_to_cpus(&header->block_extra); + le32_to_cpus(&header->blocks_in_image); + le32_to_cpus(&header->blocks_allocated); + uuid_convert(header->uuid_image); + uuid_convert(header->uuid_last_snap); + uuid_convert(header->uuid_link); + uuid_convert(header->uuid_parent); +} + +static void vdi_header_to_le(VdiHeader *header) +{ + cpu_to_le32s(&header->signature); + cpu_to_le32s(&header->version); + cpu_to_le32s(&header->header_size); + cpu_to_le32s(&header->image_type); + cpu_to_le32s(&header->image_flags); + cpu_to_le32s(&header->offset_bmap); + cpu_to_le32s(&header->offset_data); + cpu_to_le32s(&header->cylinders); + cpu_to_le32s(&header->heads); + cpu_to_le32s(&header->sectors); + cpu_to_le32s(&header->sector_size); + cpu_to_le64s(&header->disk_size); + cpu_to_le32s(&header->block_size); + cpu_to_le32s(&header->block_extra); + cpu_to_le32s(&header->blocks_in_image); + cpu_to_le32s(&header->blocks_allocated); + cpu_to_le32s(&header->blocks_allocated); + uuid_convert(header->uuid_image); + uuid_convert(header->uuid_last_snap); + uuid_convert(header->uuid_link); + uuid_convert(header->uuid_parent); +} + +#if defined(CONFIG_VDI_DEBUG) +static void vdi_header_print(VdiHeader *header) +{ + char uuid[37]; + logout("text %s", header->text); + logout("signature 0x%04x\n", header->signature); + logout("header size 0x%04x\n", header->header_size); + logout("image type 0x%04x\n", header->image_type); + logout("image flags 0x%04x\n", header->image_flags); + logout("description %s\n", header->description); + logout("offset bmap 0x%04x\n", header->offset_bmap); + logout("offset data 0x%04x\n", header->offset_data); + logout("cylinders 0x%04x\n", header->cylinders); + logout("heads 0x%04x\n", header->heads); + logout("sectors 0x%04x\n", header->sectors); + logout("sector size 0x%04x\n", header->sector_size); + logout("image size 0x%" PRIx64 " B (%" PRIu64 " MiB)\n", + header->disk_size, header->disk_size / MiB); + logout("block size 0x%04x\n", header->block_size); + logout("block extra 0x%04x\n", header->block_extra); + logout("blocks tot. 0x%04x\n", header->blocks_in_image); + logout("blocks all. 0x%04x\n", header->blocks_allocated); + uuid_unparse(header->uuid_image, uuid); + logout("uuid image %s\n", uuid); + uuid_unparse(header->uuid_last_snap, uuid); + logout("uuid snap %s\n", uuid); + uuid_unparse(header->uuid_link, uuid); + logout("uuid link %s\n", uuid); + uuid_unparse(header->uuid_parent, uuid); + logout("uuid parent %s\n", uuid); +} +#endif + +static int vdi_check(BlockDriverState *bs, BdrvCheckResult *res) +{ + /* TODO: additional checks possible. */ + BDRVVdiState *s = (BDRVVdiState *)bs->opaque; + uint32_t blocks_allocated = 0; + uint32_t block; + uint32_t *bmap; + logout("\n"); + + bmap = qemu_malloc(s->header.blocks_in_image * sizeof(uint32_t)); + memset(bmap, 0xff, s->header.blocks_in_image * sizeof(uint32_t)); + + /* Check block map and value of blocks_allocated. */ + for (block = 0; block < s->header.blocks_in_image; block++) { + uint32_t bmap_entry = le32_to_cpu(s->bmap[block]); + if (bmap_entry != VDI_UNALLOCATED) { + if (bmap_entry < s->header.blocks_in_image) { + blocks_allocated++; + if (bmap[bmap_entry] == VDI_UNALLOCATED) { + bmap[bmap_entry] = bmap_entry; + } else { + fprintf(stderr, "ERROR: block index %" PRIu32 + " also used by %" PRIu32 "\n", bmap[bmap_entry], bmap_entry); + res->corruptions++; + } + } else { + fprintf(stderr, "ERROR: block index %" PRIu32 + " too large, is %" PRIu32 "\n", block, bmap_entry); + res->corruptions++; + } + } + } + if (blocks_allocated != s->header.blocks_allocated) { + fprintf(stderr, "ERROR: allocated blocks mismatch, is %" PRIu32 + ", should be %" PRIu32 "\n", + blocks_allocated, s->header.blocks_allocated); + res->corruptions++; + } + + qemu_free(bmap); + + return 0; +} + +static int vdi_get_info(BlockDriverState *bs, BlockDriverInfo *bdi) +{ + /* TODO: vdi_get_info would be needed for machine snapshots. + vm_state_offset is still missing. */ + BDRVVdiState *s = (BDRVVdiState *)bs->opaque; + logout("\n"); + bdi->cluster_size = s->block_size; + bdi->vm_state_offset = 0; + return 0; +} + +static int vdi_make_empty(BlockDriverState *bs) +{ + /* TODO: missing code. */ + logout("\n"); + /* The return value for missing code must be 0, see block.c. */ + return 0; +} + +static int vdi_probe(const uint8_t *buf, int buf_size, const char *filename) +{ + const VdiHeader *header = (const VdiHeader *)buf; + int result = 0; + + logout("\n"); + + if (buf_size < sizeof(*header)) { + /* Header too small, no VDI. */ + } else if (le32_to_cpu(header->signature) == VDI_SIGNATURE) { + result = 100; + } + + if (result == 0) { + logout("no vdi image\n"); + } else { + logout("%s", header->text); + } + + return result; +} + +static int vdi_open(BlockDriverState *bs, int flags) +{ + BDRVVdiState *s = bs->opaque; + VdiHeader header; + size_t bmap_size; + + logout("\n"); + + if (bdrv_read(bs->file, 0, (uint8_t *)&header, 1) < 0) { + goto fail; + } + + vdi_header_to_cpu(&header); +#if defined(CONFIG_VDI_DEBUG) + vdi_header_print(&header); +#endif + + if (header.disk_size % SECTOR_SIZE != 0) { + /* 'VBoxManage convertfromraw' can create images with odd disk sizes. + We accept them but round the disk size to the next multiple of + SECTOR_SIZE. */ + logout("odd disk size %" PRIu64 " B, round up\n", header.disk_size); + header.disk_size += SECTOR_SIZE - 1; + header.disk_size &= ~(SECTOR_SIZE - 1); + } + + if (header.version != VDI_VERSION_1_1) { + logout("unsupported version %u.%u\n", + header.version >> 16, header.version & 0xffff); + goto fail; + } else if (header.offset_bmap % SECTOR_SIZE != 0) { + /* We only support block maps which start on a sector boundary. */ + logout("unsupported block map offset 0x%x B\n", header.offset_bmap); + goto fail; + } else if (header.offset_data % SECTOR_SIZE != 0) { + /* We only support data blocks which start on a sector boundary. */ + logout("unsupported data offset 0x%x B\n", header.offset_data); + goto fail; + } else if (header.sector_size != SECTOR_SIZE) { + logout("unsupported sector size %u B\n", header.sector_size); + goto fail; + } else if (header.block_size != 1 * MiB) { + logout("unsupported block size %u B\n", header.block_size); + goto fail; + } else if (header.disk_size > + (uint64_t)header.blocks_in_image * header.block_size) { + logout("unsupported disk size %" PRIu64 " B\n", header.disk_size); + goto fail; + } else if (!uuid_is_null(header.uuid_link)) { + logout("link uuid != 0, unsupported\n"); + goto fail; + } else if (!uuid_is_null(header.uuid_parent)) { + logout("parent uuid != 0, unsupported\n"); + goto fail; + } + + bs->total_sectors = header.disk_size / SECTOR_SIZE; + + s->block_size = header.block_size; + s->block_sectors = header.block_size / SECTOR_SIZE; + s->bmap_sector = header.offset_bmap / SECTOR_SIZE; + s->header = header; + + bmap_size = header.blocks_in_image * sizeof(uint32_t); + bmap_size = (bmap_size + SECTOR_SIZE - 1) / SECTOR_SIZE; + if (bmap_size > 0) { + s->bmap = qemu_malloc(bmap_size * SECTOR_SIZE); + } + if (bdrv_read(bs->file, s->bmap_sector, (uint8_t *)s->bmap, bmap_size) < 0) { + goto fail_free_bmap; + } + + return 0; + + fail_free_bmap: + qemu_free(s->bmap); + + fail: + return -1; +} + +static int vdi_is_allocated(BlockDriverState *bs, int64_t sector_num, + int nb_sectors, int *pnum) +{ + /* TODO: Check for too large sector_num (in bdrv_is_allocated or here). */ + BDRVVdiState *s = (BDRVVdiState *)bs->opaque; + size_t bmap_index = sector_num / s->block_sectors; + size_t sector_in_block = sector_num % s->block_sectors; + int n_sectors = s->block_sectors - sector_in_block; + uint32_t bmap_entry = le32_to_cpu(s->bmap[bmap_index]); + logout("%p, %" PRId64 ", %d, %p\n", bs, sector_num, nb_sectors, pnum); + if (n_sectors > nb_sectors) { + n_sectors = nb_sectors; + } + *pnum = n_sectors; + return bmap_entry != VDI_UNALLOCATED; +} + +static void vdi_aio_cancel(BlockDriverAIOCB *blockacb) +{ + /* TODO: This code is untested. How can I get it executed? */ + VdiAIOCB *acb = container_of(blockacb, VdiAIOCB, common); + logout("\n"); + if (acb->hd_aiocb) { + bdrv_aio_cancel(acb->hd_aiocb); + } + qemu_aio_release(acb); +} + +static AIOPool vdi_aio_pool = { + .aiocb_size = sizeof(VdiAIOCB), + .cancel = vdi_aio_cancel, +}; + +static VdiAIOCB *vdi_aio_setup(BlockDriverState *bs, int64_t sector_num, + QEMUIOVector *qiov, int nb_sectors, + BlockDriverCompletionFunc *cb, void *opaque, int is_write) +{ + VdiAIOCB *acb; + + logout("%p, %" PRId64 ", %p, %d, %p, %p, %d\n", + bs, sector_num, qiov, nb_sectors, cb, opaque, is_write); + + acb = qemu_aio_get(&vdi_aio_pool, bs, cb, opaque); + if (acb) { + acb->hd_aiocb = NULL; + acb->sector_num = sector_num; + acb->qiov = qiov; + if (qiov->niov > 1) { + acb->buf = qemu_blockalign(bs, qiov->size); + acb->orig_buf = acb->buf; + if (is_write) { + qemu_iovec_to_buffer(qiov, acb->buf); + } + } else { + acb->buf = (uint8_t *)qiov->iov->iov_base; + } + acb->nb_sectors = nb_sectors; + acb->n_sectors = 0; + acb->bmap_first = VDI_UNALLOCATED; + acb->bmap_last = VDI_UNALLOCATED; + acb->block_buffer = NULL; + acb->header_modified = 0; + } + return acb; +} + +static int vdi_schedule_bh(QEMUBHFunc *cb, VdiAIOCB *acb) +{ + logout("\n"); + + if (acb->bh) { + return -EIO; + } + + acb->bh = qemu_bh_new(cb, acb); + if (!acb->bh) { + return -EIO; + } + + qemu_bh_schedule(acb->bh); + + return 0; +} + +static void vdi_aio_read_cb(void *opaque, int ret); + +static void vdi_aio_read_bh(void *opaque) +{ + VdiAIOCB *acb = opaque; + logout("\n"); + qemu_bh_delete(acb->bh); + acb->bh = NULL; + vdi_aio_read_cb(opaque, 0); +} + +static void vdi_aio_read_cb(void *opaque, int ret) +{ + VdiAIOCB *acb = opaque; + BlockDriverState *bs = acb->common.bs; + BDRVVdiState *s = bs->opaque; + uint32_t bmap_entry; + uint32_t block_index; + uint32_t sector_in_block; + uint32_t n_sectors; + + logout("%u sectors read\n", acb->n_sectors); + + acb->hd_aiocb = NULL; + + if (ret < 0) { + goto done; + } + + acb->nb_sectors -= acb->n_sectors; + + if (acb->nb_sectors == 0) { + /* request completed */ + ret = 0; + goto done; + } + + acb->sector_num += acb->n_sectors; + acb->buf += acb->n_sectors * SECTOR_SIZE; + + block_index = acb->sector_num / s->block_sectors; + sector_in_block = acb->sector_num % s->block_sectors; + n_sectors = s->block_sectors - sector_in_block; + if (n_sectors > acb->nb_sectors) { + n_sectors = acb->nb_sectors; + } + + logout("will read %u sectors starting at sector %" PRIu64 "\n", + n_sectors, acb->sector_num); + + /* prepare next AIO request */ + acb->n_sectors = n_sectors; + bmap_entry = le32_to_cpu(s->bmap[block_index]); + if (bmap_entry == VDI_UNALLOCATED) { + /* Block not allocated, return zeros, no need to wait. */ + memset(acb->buf, 0, n_sectors * SECTOR_SIZE); + ret = vdi_schedule_bh(vdi_aio_read_bh, acb); + if (ret < 0) { + goto done; + } + } else { + uint64_t offset = s->header.offset_data / SECTOR_SIZE + + (uint64_t)bmap_entry * s->block_sectors + + sector_in_block; + acb->hd_iov.iov_base = (void *)acb->buf; + acb->hd_iov.iov_len = n_sectors * SECTOR_SIZE; + qemu_iovec_init_external(&acb->hd_qiov, &acb->hd_iov, 1); + acb->hd_aiocb = bdrv_aio_readv(bs->file, offset, &acb->hd_qiov, + n_sectors, vdi_aio_read_cb, acb); + if (acb->hd_aiocb == NULL) { + goto done; + } + } + return; +done: + if (acb->qiov->niov > 1) { + qemu_iovec_from_buffer(acb->qiov, acb->orig_buf, acb->qiov->size); + qemu_vfree(acb->orig_buf); + } + acb->common.cb(acb->common.opaque, ret); + qemu_aio_release(acb); +} + +static BlockDriverAIOCB *vdi_aio_readv(BlockDriverState *bs, + int64_t sector_num, QEMUIOVector *qiov, int nb_sectors, + BlockDriverCompletionFunc *cb, void *opaque) +{ + VdiAIOCB *acb; + logout("\n"); + acb = vdi_aio_setup(bs, sector_num, qiov, nb_sectors, cb, opaque, 0); + if (!acb) { + return NULL; + } + vdi_aio_read_cb(acb, 0); + return &acb->common; +} + +static void vdi_aio_write_cb(void *opaque, int ret) +{ + VdiAIOCB *acb = opaque; + BlockDriverState *bs = acb->common.bs; + BDRVVdiState *s = bs->opaque; + uint32_t bmap_entry; + uint32_t block_index; + uint32_t sector_in_block; + uint32_t n_sectors; + + acb->hd_aiocb = NULL; + + if (ret < 0) { + goto done; + } + + acb->nb_sectors -= acb->n_sectors; + acb->sector_num += acb->n_sectors; + acb->buf += acb->n_sectors * SECTOR_SIZE; + + if (acb->nb_sectors == 0) { + logout("finished data write\n"); + acb->n_sectors = 0; + if (acb->header_modified) { + VdiHeader *header = acb->block_buffer; + logout("now writing modified header\n"); + assert(acb->bmap_first != VDI_UNALLOCATED); + *header = s->header; + vdi_header_to_le(header); + acb->header_modified = 0; + acb->hd_iov.iov_base = acb->block_buffer; + acb->hd_iov.iov_len = SECTOR_SIZE; + qemu_iovec_init_external(&acb->hd_qiov, &acb->hd_iov, 1); + acb->hd_aiocb = bdrv_aio_writev(bs->file, 0, &acb->hd_qiov, 1, + vdi_aio_write_cb, acb); + if (acb->hd_aiocb == NULL) { + goto done; + } + return; + } else if (acb->bmap_first != VDI_UNALLOCATED) { + /* One or more new blocks were allocated. */ + uint64_t offset; + uint32_t bmap_first; + uint32_t bmap_last; + qemu_free(acb->block_buffer); + acb->block_buffer = NULL; + bmap_first = acb->bmap_first; + bmap_last = acb->bmap_last; + logout("now writing modified block map entry %u...%u\n", + bmap_first, bmap_last); + /* Write modified sectors from block map. */ + bmap_first /= (SECTOR_SIZE / sizeof(uint32_t)); + bmap_last /= (SECTOR_SIZE / sizeof(uint32_t)); + n_sectors = bmap_last - bmap_first + 1; + offset = s->bmap_sector + bmap_first; + acb->bmap_first = VDI_UNALLOCATED; + acb->hd_iov.iov_base = (void *)((uint8_t *)&s->bmap[0] + + bmap_first * SECTOR_SIZE); + acb->hd_iov.iov_len = n_sectors * SECTOR_SIZE; + qemu_iovec_init_external(&acb->hd_qiov, &acb->hd_iov, 1); + logout("will write %u block map sectors starting from entry %u\n", + n_sectors, bmap_first); + acb->hd_aiocb = bdrv_aio_writev(bs->file, offset, &acb->hd_qiov, + n_sectors, vdi_aio_write_cb, acb); + if (acb->hd_aiocb == NULL) { + goto done; + } + return; + } + ret = 0; + goto done; + } + + logout("%u sectors written\n", acb->n_sectors); + + block_index = acb->sector_num / s->block_sectors; + sector_in_block = acb->sector_num % s->block_sectors; + n_sectors = s->block_sectors - sector_in_block; + if (n_sectors > acb->nb_sectors) { + n_sectors = acb->nb_sectors; + } + + logout("will write %u sectors starting at sector %" PRIu64 "\n", + n_sectors, acb->sector_num); + + /* prepare next AIO request */ + acb->n_sectors = n_sectors; + bmap_entry = le32_to_cpu(s->bmap[block_index]); + if (bmap_entry == VDI_UNALLOCATED) { + /* Allocate new block and write to it. */ + uint64_t offset; + uint8_t *block; + bmap_entry = s->header.blocks_allocated; + s->bmap[block_index] = cpu_to_le32(bmap_entry); + s->header.blocks_allocated++; + offset = s->header.offset_data / SECTOR_SIZE + + (uint64_t)bmap_entry * s->block_sectors; + block = acb->block_buffer; + if (block == NULL) { + block = qemu_mallocz(s->block_size); + acb->block_buffer = block; + acb->bmap_first = block_index; + assert(!acb->header_modified); + acb->header_modified = 1; + } + acb->bmap_last = block_index; + memcpy(block + sector_in_block * SECTOR_SIZE, + acb->buf, n_sectors * SECTOR_SIZE); + acb->hd_iov.iov_base = (void *)block; + acb->hd_iov.iov_len = s->block_size; + qemu_iovec_init_external(&acb->hd_qiov, &acb->hd_iov, 1); + acb->hd_aiocb = bdrv_aio_writev(bs->file, offset, + &acb->hd_qiov, s->block_sectors, + vdi_aio_write_cb, acb); + if (acb->hd_aiocb == NULL) { + goto done; + } + } else { + uint64_t offset = s->header.offset_data / SECTOR_SIZE + + (uint64_t)bmap_entry * s->block_sectors + + sector_in_block; + acb->hd_iov.iov_base = (void *)acb->buf; + acb->hd_iov.iov_len = n_sectors * SECTOR_SIZE; + qemu_iovec_init_external(&acb->hd_qiov, &acb->hd_iov, 1); + acb->hd_aiocb = bdrv_aio_writev(bs->file, offset, &acb->hd_qiov, + n_sectors, vdi_aio_write_cb, acb); + if (acb->hd_aiocb == NULL) { + goto done; + } + } + + return; + +done: + if (acb->qiov->niov > 1) { + qemu_vfree(acb->orig_buf); + } + acb->common.cb(acb->common.opaque, ret); + qemu_aio_release(acb); +} + +static BlockDriverAIOCB *vdi_aio_writev(BlockDriverState *bs, + int64_t sector_num, QEMUIOVector *qiov, int nb_sectors, + BlockDriverCompletionFunc *cb, void *opaque) +{ + VdiAIOCB *acb; + logout("\n"); + acb = vdi_aio_setup(bs, sector_num, qiov, nb_sectors, cb, opaque, 1); + if (!acb) { + return NULL; + } + vdi_aio_write_cb(acb, 0); + return &acb->common; +} + +static int vdi_create(const char *filename, QEMUOptionParameter *options) +{ + int fd; + int result = 0; + uint64_t bytes = 0; + uint32_t blocks; + size_t block_size = 1 * MiB; + uint32_t image_type = VDI_TYPE_DYNAMIC; + VdiHeader header; + size_t i; + size_t bmap_size; + uint32_t *bmap; + + logout("\n"); + + /* Read out options. */ + while (options && options->name) { + if (!strcmp(options->name, BLOCK_OPT_SIZE)) { + bytes = options->value.n; +#if defined(CONFIG_VDI_BLOCK_SIZE) + } else if (!strcmp(options->name, BLOCK_OPT_CLUSTER_SIZE)) { + if (options->value.n) { + /* TODO: Additional checks (SECTOR_SIZE * 2^n, ...). */ + block_size = options->value.n; + } +#endif +#if defined(CONFIG_VDI_STATIC_IMAGE) + } else if (!strcmp(options->name, BLOCK_OPT_STATIC)) { + if (options->value.n) { + image_type = VDI_TYPE_STATIC; + } +#endif + } + options++; + } + + fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY | O_LARGEFILE, + 0644); + if (fd < 0) { + return -errno; + } + + /* We need enough blocks to store the given disk size, + so always round up. */ + blocks = (bytes + block_size - 1) / block_size; + + bmap_size = blocks * sizeof(uint32_t); + bmap_size = ((bmap_size + SECTOR_SIZE - 1) & ~(SECTOR_SIZE -1)); + + memset(&header, 0, sizeof(header)); + pstrcpy(header.text, sizeof(header.text), VDI_TEXT); + header.signature = VDI_SIGNATURE; + header.version = VDI_VERSION_1_1; + header.header_size = 0x180; + header.image_type = image_type; + header.offset_bmap = 0x200; + header.offset_data = 0x200 + bmap_size; + header.sector_size = SECTOR_SIZE; + header.disk_size = bytes; + header.block_size = block_size; + header.blocks_in_image = blocks; + if (image_type == VDI_TYPE_STATIC) { + header.blocks_allocated = blocks; + } + uuid_generate(header.uuid_image); + uuid_generate(header.uuid_last_snap); + /* There is no need to set header.uuid_link or header.uuid_parent here. */ +#if defined(CONFIG_VDI_DEBUG) + vdi_header_print(&header); +#endif + vdi_header_to_le(&header); + if (write(fd, &header, sizeof(header)) < 0) { + result = -errno; + } + + bmap = NULL; + if (bmap_size > 0) { + bmap = (uint32_t *)qemu_mallocz(bmap_size); + } + for (i = 0; i < blocks; i++) { + if (image_type == VDI_TYPE_STATIC) { + bmap[i] = i; + } else { + bmap[i] = VDI_UNALLOCATED; + } + } + if (write(fd, bmap, bmap_size) < 0) { + result = -errno; + } + qemu_free(bmap); + if (image_type == VDI_TYPE_STATIC) { + if (ftruncate(fd, sizeof(header) + bmap_size + blocks * block_size)) { + result = -errno; + } + } + + if (close(fd) < 0) { + result = -errno; + } + + return result; +} + +static void vdi_close(BlockDriverState *bs) +{ +} + +static int vdi_flush(BlockDriverState *bs) +{ + logout("\n"); + return bdrv_flush(bs->file); +} + + +static QEMUOptionParameter vdi_create_options[] = { + { + .name = BLOCK_OPT_SIZE, + .type = OPT_SIZE, + .help = "Virtual disk size" + }, +#if defined(CONFIG_VDI_BLOCK_SIZE) + { + .name = BLOCK_OPT_CLUSTER_SIZE, + .type = OPT_SIZE, + .help = "VDI cluster (block) size" + }, +#endif +#if defined(CONFIG_VDI_STATIC_IMAGE) + { + .name = BLOCK_OPT_STATIC, + .type = OPT_FLAG, + .help = "VDI static (pre-allocated) image" + }, +#endif + /* TODO: An additional option to set UUID values might be useful. */ + { NULL } +}; + +static BlockDriver bdrv_vdi = { + .format_name = "vdi", + .instance_size = sizeof(BDRVVdiState), + .bdrv_probe = vdi_probe, + .bdrv_open = vdi_open, + .bdrv_close = vdi_close, + .bdrv_create = vdi_create, + .bdrv_flush = vdi_flush, + .bdrv_is_allocated = vdi_is_allocated, + .bdrv_make_empty = vdi_make_empty, + + .bdrv_aio_readv = vdi_aio_readv, +#if defined(CONFIG_VDI_WRITE) + .bdrv_aio_writev = vdi_aio_writev, +#endif + + .bdrv_get_info = vdi_get_info, + + .create_options = vdi_create_options, + .bdrv_check = vdi_check, +}; + +static void bdrv_vdi_init(void) +{ + logout("\n"); + bdrv_register(&bdrv_vdi); +} + +block_init(bdrv_vdi_init); diff --git a/block/vmdk.c b/block/vmdk.c new file mode 100644 index 0000000..8fc9d67 --- /dev/null +++ b/block/vmdk.c @@ -0,0 +1,870 @@ +/* + * Block driver for the VMDK format + * + * Copyright (c) 2004 Fabrice Bellard + * Copyright (c) 2005 Filip Navara + * + * 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 "block_int.h" +#include "module.h" + +#define VMDK3_MAGIC (('C' << 24) | ('O' << 16) | ('W' << 8) | 'D') +#define VMDK4_MAGIC (('K' << 24) | ('D' << 16) | ('M' << 8) | 'V') + +typedef struct { + uint32_t version; + uint32_t flags; + uint32_t disk_sectors; + uint32_t granularity; + uint32_t l1dir_offset; + uint32_t l1dir_size; + uint32_t file_sectors; + uint32_t cylinders; + uint32_t heads; + uint32_t sectors_per_track; +} VMDK3Header; + +typedef struct { + uint32_t version; + uint32_t flags; + int64_t capacity; + int64_t granularity; + int64_t desc_offset; + int64_t desc_size; + int32_t num_gtes_per_gte; + int64_t rgd_offset; + int64_t gd_offset; + int64_t grain_offset; + char filler[1]; + char check_bytes[4]; +} __attribute__((packed)) VMDK4Header; + +#define L2_CACHE_SIZE 16 + +typedef struct BDRVVmdkState { + int64_t l1_table_offset; + int64_t l1_backup_table_offset; + uint32_t *l1_table; + uint32_t *l1_backup_table; + unsigned int l1_size; + uint32_t l1_entry_sectors; + + unsigned int l2_size; + uint32_t *l2_cache; + uint32_t l2_cache_offsets[L2_CACHE_SIZE]; + uint32_t l2_cache_counts[L2_CACHE_SIZE]; + + unsigned int cluster_sectors; + uint32_t parent_cid; +} BDRVVmdkState; + +typedef struct VmdkMetaData { + uint32_t offset; + unsigned int l1_index; + unsigned int l2_index; + unsigned int l2_offset; + int valid; +} VmdkMetaData; + +static int vmdk_probe(const uint8_t *buf, int buf_size, const char *filename) +{ + uint32_t magic; + + if (buf_size < 4) + return 0; + magic = be32_to_cpu(*(uint32_t *)buf); + if (magic == VMDK3_MAGIC || + magic == VMDK4_MAGIC) + return 100; + else + return 0; +} + +#define CHECK_CID 1 + +#define SECTOR_SIZE 512 +#define DESC_SIZE 20*SECTOR_SIZE // 20 sectors of 512 bytes each +#define HEADER_SIZE 512 // first sector of 512 bytes + +static uint32_t vmdk_read_cid(BlockDriverState *bs, int parent) +{ + char desc[DESC_SIZE]; + uint32_t cid; + const char *p_name, *cid_str; + size_t cid_str_size; + + /* the descriptor offset = 0x200 */ + if (bdrv_pread(bs->file, 0x200, desc, DESC_SIZE) != DESC_SIZE) + return 0; + + if (parent) { + cid_str = "parentCID"; + cid_str_size = sizeof("parentCID"); + } else { + cid_str = "CID"; + cid_str_size = sizeof("CID"); + } + + if ((p_name = strstr(desc,cid_str)) != NULL) { + p_name += cid_str_size; + sscanf(p_name,"%x",&cid); + } + + return cid; +} + +static int vmdk_write_cid(BlockDriverState *bs, uint32_t cid) +{ + char desc[DESC_SIZE], tmp_desc[DESC_SIZE]; + char *p_name, *tmp_str; + + /* the descriptor offset = 0x200 */ + if (bdrv_pread(bs->file, 0x200, desc, DESC_SIZE) != DESC_SIZE) + return -1; + + tmp_str = strstr(desc,"parentCID"); + pstrcpy(tmp_desc, sizeof(tmp_desc), tmp_str); + if ((p_name = strstr(desc,"CID")) != NULL) { + p_name += sizeof("CID"); + snprintf(p_name, sizeof(desc) - (p_name - desc), "%x\n", cid); + pstrcat(desc, sizeof(desc), tmp_desc); + } + + if (bdrv_pwrite_sync(bs->file, 0x200, desc, DESC_SIZE) < 0) + return -1; + return 0; +} + +static int vmdk_is_cid_valid(BlockDriverState *bs) +{ +#ifdef CHECK_CID + BDRVVmdkState *s = bs->opaque; + BlockDriverState *p_bs = bs->backing_hd; + uint32_t cur_pcid; + + if (p_bs) { + cur_pcid = vmdk_read_cid(p_bs,0); + if (s->parent_cid != cur_pcid) + // CID not valid + return 0; + } +#endif + // CID valid + return 1; +} + +static int vmdk_snapshot_create(const char *filename, const char *backing_file) +{ + int snp_fd, p_fd; + int ret; + uint32_t p_cid; + char *p_name, *gd_buf, *rgd_buf; + const char *real_filename, *temp_str; + VMDK4Header header; + uint32_t gde_entries, gd_size; + int64_t gd_offset, rgd_offset, capacity, gt_size; + char p_desc[DESC_SIZE], s_desc[DESC_SIZE], hdr[HEADER_SIZE]; + static const char desc_template[] = + "# Disk DescriptorFile\n" + "version=1\n" + "CID=%x\n" + "parentCID=%x\n" + "createType=\"monolithicSparse\"\n" + "parentFileNameHint=\"%s\"\n" + "\n" + "# Extent description\n" + "RW %u SPARSE \"%s\"\n" + "\n" + "# The Disk Data Base \n" + "#DDB\n" + "\n"; + + snp_fd = open(filename, O_RDWR | O_CREAT | O_TRUNC | O_BINARY | O_LARGEFILE, 0644); + if (snp_fd < 0) + return -errno; + p_fd = open(backing_file, O_RDONLY | O_BINARY | O_LARGEFILE); + if (p_fd < 0) { + close(snp_fd); + return -errno; + } + + /* read the header */ + if (lseek(p_fd, 0x0, SEEK_SET) == -1) { + ret = -errno; + goto fail; + } + if (read(p_fd, hdr, HEADER_SIZE) != HEADER_SIZE) { + ret = -errno; + goto fail; + } + + /* write the header */ + if (lseek(snp_fd, 0x0, SEEK_SET) == -1) { + ret = -errno; + goto fail; + } + if (write(snp_fd, hdr, HEADER_SIZE) == -1) { + ret = -errno; + goto fail; + } + + memset(&header, 0, sizeof(header)); + memcpy(&header,&hdr[4], sizeof(header)); // skip the VMDK4_MAGIC + + if (ftruncate(snp_fd, header.grain_offset << 9)) { + ret = -errno; + goto fail; + } + /* the descriptor offset = 0x200 */ + if (lseek(p_fd, 0x200, SEEK_SET) == -1) { + ret = -errno; + goto fail; + } + if (read(p_fd, p_desc, DESC_SIZE) != DESC_SIZE) { + ret = -errno; + goto fail; + } + + if ((p_name = strstr(p_desc,"CID")) != NULL) { + p_name += sizeof("CID"); + sscanf(p_name,"%x",&p_cid); + } + + real_filename = filename; + if ((temp_str = strrchr(real_filename, '\\')) != NULL) + real_filename = temp_str + 1; + if ((temp_str = strrchr(real_filename, '/')) != NULL) + real_filename = temp_str + 1; + if ((temp_str = strrchr(real_filename, ':')) != NULL) + real_filename = temp_str + 1; + + snprintf(s_desc, sizeof(s_desc), desc_template, p_cid, p_cid, backing_file, + (uint32_t)header.capacity, real_filename); + + /* write the descriptor */ + if (lseek(snp_fd, 0x200, SEEK_SET) == -1) { + ret = -errno; + goto fail; + } + if (write(snp_fd, s_desc, strlen(s_desc)) == -1) { + ret = -errno; + goto fail; + } + + gd_offset = header.gd_offset * SECTOR_SIZE; // offset of GD table + rgd_offset = header.rgd_offset * SECTOR_SIZE; // offset of RGD table + capacity = header.capacity * SECTOR_SIZE; // Extent size + /* + * Each GDE span 32M disk, means: + * 512 GTE per GT, each GTE points to grain + */ + gt_size = (int64_t)header.num_gtes_per_gte * header.granularity * SECTOR_SIZE; + if (!gt_size) { + ret = -EINVAL; + goto fail; + } + gde_entries = (uint32_t)(capacity / gt_size); // number of gde/rgde + gd_size = gde_entries * sizeof(uint32_t); + + /* write RGD */ + rgd_buf = qemu_malloc(gd_size); + if (lseek(p_fd, rgd_offset, SEEK_SET) == -1) { + ret = -errno; + goto fail_rgd; + } + if (read(p_fd, rgd_buf, gd_size) != gd_size) { + ret = -errno; + goto fail_rgd; + } + if (lseek(snp_fd, rgd_offset, SEEK_SET) == -1) { + ret = -errno; + goto fail_rgd; + } + if (write(snp_fd, rgd_buf, gd_size) == -1) { + ret = -errno; + goto fail_rgd; + } + + /* write GD */ + gd_buf = qemu_malloc(gd_size); + if (lseek(p_fd, gd_offset, SEEK_SET) == -1) { + ret = -errno; + goto fail_gd; + } + if (read(p_fd, gd_buf, gd_size) != gd_size) { + ret = -errno; + goto fail_gd; + } + if (lseek(snp_fd, gd_offset, SEEK_SET) == -1) { + ret = -errno; + goto fail_gd; + } + if (write(snp_fd, gd_buf, gd_size) == -1) { + ret = -errno; + goto fail_gd; + } + ret = 0; + +fail_gd: + qemu_free(gd_buf); +fail_rgd: + qemu_free(rgd_buf); +fail: + close(p_fd); + close(snp_fd); + return ret; +} + +static int vmdk_parent_open(BlockDriverState *bs) +{ + char *p_name; + char desc[DESC_SIZE]; + + /* the descriptor offset = 0x200 */ + if (bdrv_pread(bs->file, 0x200, desc, DESC_SIZE) != DESC_SIZE) + return -1; + + if ((p_name = strstr(desc,"parentFileNameHint")) != NULL) { + char *end_name; + + p_name += sizeof("parentFileNameHint") + 1; + if ((end_name = strchr(p_name,'\"')) == NULL) + return -1; + if ((end_name - p_name) > sizeof (bs->backing_file) - 1) + return -1; + + pstrcpy(bs->backing_file, end_name - p_name + 1, p_name); + } + + return 0; +} + +static int vmdk_open(BlockDriverState *bs, int flags) +{ + BDRVVmdkState *s = bs->opaque; + uint32_t magic; + int l1_size, i; + + if (bdrv_pread(bs->file, 0, &magic, sizeof(magic)) != sizeof(magic)) + goto fail; + + magic = be32_to_cpu(magic); + if (magic == VMDK3_MAGIC) { + VMDK3Header header; + + if (bdrv_pread(bs->file, sizeof(magic), &header, sizeof(header)) != sizeof(header)) + goto fail; + s->cluster_sectors = le32_to_cpu(header.granularity); + s->l2_size = 1 << 9; + s->l1_size = 1 << 6; + bs->total_sectors = le32_to_cpu(header.disk_sectors); + s->l1_table_offset = le32_to_cpu(header.l1dir_offset) << 9; + s->l1_backup_table_offset = 0; + s->l1_entry_sectors = s->l2_size * s->cluster_sectors; + } else if (magic == VMDK4_MAGIC) { + VMDK4Header header; + + if (bdrv_pread(bs->file, sizeof(magic), &header, sizeof(header)) != sizeof(header)) + goto fail; + bs->total_sectors = le64_to_cpu(header.capacity); + s->cluster_sectors = le64_to_cpu(header.granularity); + s->l2_size = le32_to_cpu(header.num_gtes_per_gte); + s->l1_entry_sectors = s->l2_size * s->cluster_sectors; + if (s->l1_entry_sectors <= 0) + goto fail; + s->l1_size = (bs->total_sectors + s->l1_entry_sectors - 1) + / s->l1_entry_sectors; + s->l1_table_offset = le64_to_cpu(header.rgd_offset) << 9; + s->l1_backup_table_offset = le64_to_cpu(header.gd_offset) << 9; + + // try to open parent images, if exist + if (vmdk_parent_open(bs) != 0) + goto fail; + // write the CID once after the image creation + s->parent_cid = vmdk_read_cid(bs,1); + } else { + goto fail; + } + + /* read the L1 table */ + l1_size = s->l1_size * sizeof(uint32_t); + s->l1_table = qemu_malloc(l1_size); + if (bdrv_pread(bs->file, s->l1_table_offset, s->l1_table, l1_size) != l1_size) + goto fail; + for(i = 0; i < s->l1_size; i++) { + le32_to_cpus(&s->l1_table[i]); + } + + if (s->l1_backup_table_offset) { + s->l1_backup_table = qemu_malloc(l1_size); + if (bdrv_pread(bs->file, s->l1_backup_table_offset, s->l1_backup_table, l1_size) != l1_size) + goto fail; + for(i = 0; i < s->l1_size; i++) { + le32_to_cpus(&s->l1_backup_table[i]); + } + } + + s->l2_cache = qemu_malloc(s->l2_size * L2_CACHE_SIZE * sizeof(uint32_t)); + return 0; + fail: + qemu_free(s->l1_backup_table); + qemu_free(s->l1_table); + qemu_free(s->l2_cache); + return -1; +} + +static uint64_t get_cluster_offset(BlockDriverState *bs, VmdkMetaData *m_data, + uint64_t offset, int allocate); + +static int get_whole_cluster(BlockDriverState *bs, uint64_t cluster_offset, + uint64_t offset, int allocate) +{ + BDRVVmdkState *s = bs->opaque; + uint8_t whole_grain[s->cluster_sectors*512]; // 128 sectors * 512 bytes each = grain size 64KB + + // we will be here if it's first write on non-exist grain(cluster). + // try to read from parent image, if exist + if (bs->backing_hd) { + int ret; + + if (!vmdk_is_cid_valid(bs)) + return -1; + + ret = bdrv_read(bs->backing_hd, offset >> 9, whole_grain, + s->cluster_sectors); + if (ret < 0) { + return -1; + } + + //Write grain only into the active image + ret = bdrv_write(bs->file, cluster_offset, whole_grain, + s->cluster_sectors); + if (ret < 0) { + return -1; + } + } + return 0; +} + +static int vmdk_L2update(BlockDriverState *bs, VmdkMetaData *m_data) +{ + BDRVVmdkState *s = bs->opaque; + + /* update L2 table */ + if (bdrv_pwrite_sync(bs->file, ((int64_t)m_data->l2_offset * 512) + (m_data->l2_index * sizeof(m_data->offset)), + &(m_data->offset), sizeof(m_data->offset)) < 0) + return -1; + /* update backup L2 table */ + if (s->l1_backup_table_offset != 0) { + m_data->l2_offset = s->l1_backup_table[m_data->l1_index]; + if (bdrv_pwrite_sync(bs->file, ((int64_t)m_data->l2_offset * 512) + (m_data->l2_index * sizeof(m_data->offset)), + &(m_data->offset), sizeof(m_data->offset)) < 0) + return -1; + } + + return 0; +} + +static uint64_t get_cluster_offset(BlockDriverState *bs, VmdkMetaData *m_data, + uint64_t offset, int allocate) +{ + BDRVVmdkState *s = bs->opaque; + unsigned int l1_index, l2_offset, l2_index; + int min_index, i, j; + uint32_t min_count, *l2_table, tmp = 0; + uint64_t cluster_offset; + + if (m_data) + m_data->valid = 0; + + l1_index = (offset >> 9) / s->l1_entry_sectors; + if (l1_index >= s->l1_size) + return 0; + l2_offset = s->l1_table[l1_index]; + if (!l2_offset) + return 0; + for(i = 0; i < L2_CACHE_SIZE; i++) { + if (l2_offset == s->l2_cache_offsets[i]) { + /* increment the hit count */ + if (++s->l2_cache_counts[i] == 0xffffffff) { + for(j = 0; j < L2_CACHE_SIZE; j++) { + s->l2_cache_counts[j] >>= 1; + } + } + l2_table = s->l2_cache + (i * s->l2_size); + goto found; + } + } + /* not found: load a new entry in the least used one */ + min_index = 0; + min_count = 0xffffffff; + for(i = 0; i < L2_CACHE_SIZE; i++) { + if (s->l2_cache_counts[i] < min_count) { + min_count = s->l2_cache_counts[i]; + min_index = i; + } + } + l2_table = s->l2_cache + (min_index * s->l2_size); + if (bdrv_pread(bs->file, (int64_t)l2_offset * 512, l2_table, s->l2_size * sizeof(uint32_t)) != + s->l2_size * sizeof(uint32_t)) + return 0; + + s->l2_cache_offsets[min_index] = l2_offset; + s->l2_cache_counts[min_index] = 1; + found: + l2_index = ((offset >> 9) / s->cluster_sectors) % s->l2_size; + cluster_offset = le32_to_cpu(l2_table[l2_index]); + + if (!cluster_offset) { + if (!allocate) + return 0; + + // Avoid the L2 tables update for the images that have snapshots. + cluster_offset = bdrv_getlength(bs->file); + bdrv_truncate(bs->file, cluster_offset + (s->cluster_sectors << 9)); + + cluster_offset >>= 9; + tmp = cpu_to_le32(cluster_offset); + l2_table[l2_index] = tmp; + + /* First of all we write grain itself, to avoid race condition + * that may to corrupt the image. + * This problem may occur because of insufficient space on host disk + * or inappropriate VM shutdown. + */ + if (get_whole_cluster(bs, cluster_offset, offset, allocate) == -1) + return 0; + + if (m_data) { + m_data->offset = tmp; + m_data->l1_index = l1_index; + m_data->l2_index = l2_index; + m_data->l2_offset = l2_offset; + m_data->valid = 1; + } + } + cluster_offset <<= 9; + return cluster_offset; +} + +static int vmdk_is_allocated(BlockDriverState *bs, int64_t sector_num, + int nb_sectors, int *pnum) +{ + BDRVVmdkState *s = bs->opaque; + int index_in_cluster, n; + uint64_t cluster_offset; + + cluster_offset = get_cluster_offset(bs, NULL, sector_num << 9, 0); + index_in_cluster = sector_num % s->cluster_sectors; + n = s->cluster_sectors - index_in_cluster; + if (n > nb_sectors) + n = nb_sectors; + *pnum = n; + return (cluster_offset != 0); +} + +static int vmdk_read(BlockDriverState *bs, int64_t sector_num, + uint8_t *buf, int nb_sectors) +{ + BDRVVmdkState *s = bs->opaque; + int index_in_cluster, n, ret; + uint64_t cluster_offset; + + while (nb_sectors > 0) { + cluster_offset = get_cluster_offset(bs, NULL, sector_num << 9, 0); + index_in_cluster = sector_num % s->cluster_sectors; + n = s->cluster_sectors - index_in_cluster; + if (n > nb_sectors) + n = nb_sectors; + if (!cluster_offset) { + // try to read from parent image, if exist + if (bs->backing_hd) { + if (!vmdk_is_cid_valid(bs)) + return -1; + ret = bdrv_read(bs->backing_hd, sector_num, buf, n); + if (ret < 0) + return -1; + } else { + memset(buf, 0, 512 * n); + } + } else { + if(bdrv_pread(bs->file, cluster_offset + index_in_cluster * 512, buf, n * 512) != n * 512) + return -1; + } + nb_sectors -= n; + sector_num += n; + buf += n * 512; + } + return 0; +} + +static int vmdk_write(BlockDriverState *bs, int64_t sector_num, + const uint8_t *buf, int nb_sectors) +{ + BDRVVmdkState *s = bs->opaque; + VmdkMetaData m_data; + int index_in_cluster, n; + uint64_t cluster_offset; + static int cid_update = 0; + + if (sector_num > bs->total_sectors) { + fprintf(stderr, + "(VMDK) Wrong offset: sector_num=0x%" PRIx64 + " total_sectors=0x%" PRIx64 "\n", + sector_num, bs->total_sectors); + return -1; + } + + while (nb_sectors > 0) { + index_in_cluster = sector_num & (s->cluster_sectors - 1); + n = s->cluster_sectors - index_in_cluster; + if (n > nb_sectors) + n = nb_sectors; + cluster_offset = get_cluster_offset(bs, &m_data, sector_num << 9, 1); + if (!cluster_offset) + return -1; + + if (bdrv_pwrite(bs->file, cluster_offset + index_in_cluster * 512, buf, n * 512) != n * 512) + return -1; + if (m_data.valid) { + /* update L2 tables */ + if (vmdk_L2update(bs, &m_data) == -1) + return -1; + } + nb_sectors -= n; + sector_num += n; + buf += n * 512; + + // update CID on the first write every time the virtual disk is opened + if (!cid_update) { + vmdk_write_cid(bs, time(NULL)); + cid_update++; + } + } + return 0; +} + +static int vmdk_create(const char *filename, QEMUOptionParameter *options) +{ + int fd, i; + VMDK4Header header; + uint32_t tmp, magic, grains, gd_size, gt_size, gt_count; + static const char desc_template[] = + "# Disk DescriptorFile\n" + "version=1\n" + "CID=%x\n" + "parentCID=ffffffff\n" + "createType=\"monolithicSparse\"\n" + "\n" + "# Extent description\n" + "RW %" PRId64 " SPARSE \"%s\"\n" + "\n" + "# The Disk Data Base \n" + "#DDB\n" + "\n" + "ddb.virtualHWVersion = \"%d\"\n" + "ddb.geometry.cylinders = \"%" PRId64 "\"\n" + "ddb.geometry.heads = \"16\"\n" + "ddb.geometry.sectors = \"63\"\n" + "ddb.adapterType = \"ide\"\n"; + char desc[1024]; + const char *real_filename, *temp_str; + int64_t total_size = 0; + const char *backing_file = NULL; + int flags = 0; + int ret; + + // Read out options + while (options && options->name) { + if (!strcmp(options->name, BLOCK_OPT_SIZE)) { + total_size = options->value.n / 512; + } else if (!strcmp(options->name, BLOCK_OPT_BACKING_FILE)) { + backing_file = options->value.s; + } else if (!strcmp(options->name, BLOCK_OPT_COMPAT6)) { + flags |= options->value.n ? BLOCK_FLAG_COMPAT6: 0; + } + options++; + } + + /* XXX: add support for backing file */ + if (backing_file) { + return vmdk_snapshot_create(filename, backing_file); + } + + fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY | O_LARGEFILE, + 0644); + if (fd < 0) + return -errno; + magic = cpu_to_be32(VMDK4_MAGIC); + memset(&header, 0, sizeof(header)); + header.version = cpu_to_le32(1); + header.flags = cpu_to_le32(3); /* ?? */ + header.capacity = cpu_to_le64(total_size); + header.granularity = cpu_to_le64(128); + header.num_gtes_per_gte = cpu_to_le32(512); + + grains = (total_size + header.granularity - 1) / header.granularity; + gt_size = ((header.num_gtes_per_gte * sizeof(uint32_t)) + 511) >> 9; + gt_count = (grains + header.num_gtes_per_gte - 1) / header.num_gtes_per_gte; + gd_size = (gt_count * sizeof(uint32_t) + 511) >> 9; + + header.desc_offset = 1; + header.desc_size = 20; + header.rgd_offset = header.desc_offset + header.desc_size; + header.gd_offset = header.rgd_offset + gd_size + (gt_size * gt_count); + header.grain_offset = + ((header.gd_offset + gd_size + (gt_size * gt_count) + + header.granularity - 1) / header.granularity) * + header.granularity; + + header.desc_offset = cpu_to_le64(header.desc_offset); + header.desc_size = cpu_to_le64(header.desc_size); + header.rgd_offset = cpu_to_le64(header.rgd_offset); + header.gd_offset = cpu_to_le64(header.gd_offset); + header.grain_offset = cpu_to_le64(header.grain_offset); + + header.check_bytes[0] = 0xa; + header.check_bytes[1] = 0x20; + header.check_bytes[2] = 0xd; + header.check_bytes[3] = 0xa; + + /* write all the data */ + ret = qemu_write_full(fd, &magic, sizeof(magic)); + if (ret != sizeof(magic)) { + ret = -errno; + goto exit; + } + ret = qemu_write_full(fd, &header, sizeof(header)); + if (ret != sizeof(header)) { + ret = -errno; + goto exit; + } + + ret = ftruncate(fd, header.grain_offset << 9); + if (ret < 0) { + ret = -errno; + goto exit; + } + + /* write grain directory */ + lseek(fd, le64_to_cpu(header.rgd_offset) << 9, SEEK_SET); + for (i = 0, tmp = header.rgd_offset + gd_size; + i < gt_count; i++, tmp += gt_size) { + ret = qemu_write_full(fd, &tmp, sizeof(tmp)); + if (ret != sizeof(tmp)) { + ret = -errno; + goto exit; + } + } + + /* write backup grain directory */ + lseek(fd, le64_to_cpu(header.gd_offset) << 9, SEEK_SET); + for (i = 0, tmp = header.gd_offset + gd_size; + i < gt_count; i++, tmp += gt_size) { + ret = qemu_write_full(fd, &tmp, sizeof(tmp)); + if (ret != sizeof(tmp)) { + ret = -errno; + goto exit; + } + } + + /* compose the descriptor */ + real_filename = filename; + if ((temp_str = strrchr(real_filename, '\\')) != NULL) + real_filename = temp_str + 1; + if ((temp_str = strrchr(real_filename, '/')) != NULL) + real_filename = temp_str + 1; + if ((temp_str = strrchr(real_filename, ':')) != NULL) + real_filename = temp_str + 1; + snprintf(desc, sizeof(desc), desc_template, (unsigned int)time(NULL), + total_size, real_filename, + (flags & BLOCK_FLAG_COMPAT6 ? 6 : 4), + total_size / (int64_t)(63 * 16)); + + /* write the descriptor */ + lseek(fd, le64_to_cpu(header.desc_offset) << 9, SEEK_SET); + ret = qemu_write_full(fd, desc, strlen(desc)); + if (ret != strlen(desc)) { + ret = -errno; + goto exit; + } + + ret = 0; +exit: + close(fd); + return ret; +} + +static void vmdk_close(BlockDriverState *bs) +{ + BDRVVmdkState *s = bs->opaque; + + qemu_free(s->l1_table); + qemu_free(s->l2_cache); +} + +static int vmdk_flush(BlockDriverState *bs) +{ + return bdrv_flush(bs->file); +} + + +static QEMUOptionParameter vmdk_create_options[] = { + { + .name = BLOCK_OPT_SIZE, + .type = OPT_SIZE, + .help = "Virtual disk size" + }, + { + .name = BLOCK_OPT_BACKING_FILE, + .type = OPT_STRING, + .help = "File name of a base image" + }, + { + .name = BLOCK_OPT_COMPAT6, + .type = OPT_FLAG, + .help = "VMDK version 6 image" + }, + { NULL } +}; + +static BlockDriver bdrv_vmdk = { + .format_name = "vmdk", + .instance_size = sizeof(BDRVVmdkState), + .bdrv_probe = vmdk_probe, + .bdrv_open = vmdk_open, + .bdrv_read = vmdk_read, + .bdrv_write = vmdk_write, + .bdrv_close = vmdk_close, + .bdrv_create = vmdk_create, + .bdrv_flush = vmdk_flush, + .bdrv_is_allocated = vmdk_is_allocated, + + .create_options = vmdk_create_options, +}; + +static void bdrv_vmdk_init(void) +{ + bdrv_register(&bdrv_vmdk); +} + +block_init(bdrv_vmdk_init); diff --git a/block/vpc.c b/block/vpc.c new file mode 100644 index 0000000..7b025be --- /dev/null +++ b/block/vpc.c @@ -0,0 +1,654 @@ +/* + * Block driver for Connectix / Microsoft Virtual PC images + * + * Copyright (c) 2005 Alex Beregszaszi + * Copyright (c) 2009 Kevin Wolf + * + * 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 "block_int.h" +#include "module.h" + +/**************************************************************/ + +#define HEADER_SIZE 512 + +//#define CACHE + +enum vhd_type { + VHD_FIXED = 2, + VHD_DYNAMIC = 3, + VHD_DIFFERENCING = 4, +}; + +// Seconds since Jan 1, 2000 0:00:00 (UTC) +#define VHD_TIMESTAMP_BASE 946684800 + +// always big-endian +struct vhd_footer { + char creator[8]; // "conectix" + uint32_t features; + uint32_t version; + + // Offset of next header structure, 0xFFFFFFFF if none + uint64_t data_offset; + + // Seconds since Jan 1, 2000 0:00:00 (UTC) + uint32_t timestamp; + + char creator_app[4]; // "vpc " + uint16_t major; + uint16_t minor; + char creator_os[4]; // "Wi2k" + + uint64_t orig_size; + uint64_t size; + + uint16_t cyls; + uint8_t heads; + uint8_t secs_per_cyl; + + uint32_t type; + + // Checksum of the Hard Disk Footer ("one's complement of the sum of all + // the bytes in the footer without the checksum field") + uint32_t checksum; + + // UUID used to identify a parent hard disk (backing file) + uint8_t uuid[16]; + + uint8_t in_saved_state; +}; + +struct vhd_dyndisk_header { + char magic[8]; // "cxsparse" + + // Offset of next header structure, 0xFFFFFFFF if none + uint64_t data_offset; + + // Offset of the Block Allocation Table (BAT) + uint64_t table_offset; + + uint32_t version; + uint32_t max_table_entries; // 32bit/entry + + // 2 MB by default, must be a power of two + uint32_t block_size; + + uint32_t checksum; + uint8_t parent_uuid[16]; + uint32_t parent_timestamp; + uint32_t reserved; + + // Backing file name (in UTF-16) + uint8_t parent_name[512]; + + struct { + uint32_t platform; + uint32_t data_space; + uint32_t data_length; + uint32_t reserved; + uint64_t data_offset; + } parent_locator[8]; +}; + +typedef struct BDRVVPCState { + uint8_t footer_buf[HEADER_SIZE]; + uint64_t free_data_block_offset; + int max_table_entries; + uint32_t *pagetable; + uint64_t bat_offset; + uint64_t last_bitmap_offset; + + uint32_t block_size; + uint32_t bitmap_size; + +#ifdef CACHE + uint8_t *pageentry_u8; + uint32_t *pageentry_u32; + uint16_t *pageentry_u16; + + uint64_t last_bitmap; +#endif +} BDRVVPCState; + +static uint32_t vpc_checksum(uint8_t* buf, size_t size) +{ + uint32_t res = 0; + int i; + + for (i = 0; i < size; i++) + res += buf[i]; + + return ~res; +} + + +static int vpc_probe(const uint8_t *buf, int buf_size, const char *filename) +{ + if (buf_size >= 8 && !strncmp((char *)buf, "conectix", 8)) + return 100; + return 0; +} + +static int vpc_open(BlockDriverState *bs, int flags) +{ + BDRVVPCState *s = bs->opaque; + int i; + struct vhd_footer* footer; + struct vhd_dyndisk_header* dyndisk_header; + uint8_t buf[HEADER_SIZE]; + uint32_t checksum; + + if (bdrv_pread(bs->file, 0, s->footer_buf, HEADER_SIZE) != HEADER_SIZE) + goto fail; + + footer = (struct vhd_footer*) s->footer_buf; + if (strncmp(footer->creator, "conectix", 8)) + goto fail; + + checksum = be32_to_cpu(footer->checksum); + footer->checksum = 0; + if (vpc_checksum(s->footer_buf, HEADER_SIZE) != checksum) + fprintf(stderr, "block-vpc: The header checksum of '%s' is " + "incorrect.\n", bs->filename); + + // The visible size of a image in Virtual PC depends on the geometry + // rather than on the size stored in the footer (the size in the footer + // is too large usually) + bs->total_sectors = (int64_t) + be16_to_cpu(footer->cyls) * footer->heads * footer->secs_per_cyl; + + if (bdrv_pread(bs->file, be64_to_cpu(footer->data_offset), buf, HEADER_SIZE) + != HEADER_SIZE) + goto fail; + + dyndisk_header = (struct vhd_dyndisk_header*) buf; + + if (strncmp(dyndisk_header->magic, "cxsparse", 8)) + goto fail; + + + s->block_size = be32_to_cpu(dyndisk_header->block_size); + s->bitmap_size = ((s->block_size / (8 * 512)) + 511) & ~511; + + s->max_table_entries = be32_to_cpu(dyndisk_header->max_table_entries); + s->pagetable = qemu_malloc(s->max_table_entries * 4); + + s->bat_offset = be64_to_cpu(dyndisk_header->table_offset); + if (bdrv_pread(bs->file, s->bat_offset, s->pagetable, + s->max_table_entries * 4) != s->max_table_entries * 4) + goto fail; + + s->free_data_block_offset = + (s->bat_offset + (s->max_table_entries * 4) + 511) & ~511; + + for (i = 0; i < s->max_table_entries; i++) { + be32_to_cpus(&s->pagetable[i]); + if (s->pagetable[i] != 0xFFFFFFFF) { + int64_t next = (512 * (int64_t) s->pagetable[i]) + + s->bitmap_size + s->block_size; + + if (next> s->free_data_block_offset) + s->free_data_block_offset = next; + } + } + + s->last_bitmap_offset = (int64_t) -1; + +#ifdef CACHE + s->pageentry_u8 = qemu_malloc(512); + s->pageentry_u32 = s->pageentry_u8; + s->pageentry_u16 = s->pageentry_u8; + s->last_pagetable = -1; +#endif + + return 0; + fail: + return -1; +} + +/* + * Returns the absolute byte offset of the given sector in the image file. + * If the sector is not allocated, -1 is returned instead. + * + * The parameter write must be 1 if the offset will be used for a write + * operation (the block bitmaps is updated then), 0 otherwise. + */ +static inline int64_t get_sector_offset(BlockDriverState *bs, + int64_t sector_num, int write) +{ + BDRVVPCState *s = bs->opaque; + uint64_t offset = sector_num * 512; + uint64_t bitmap_offset, block_offset; + uint32_t pagetable_index, pageentry_index; + + pagetable_index = offset / s->block_size; + pageentry_index = (offset % s->block_size) / 512; + + if (pagetable_index >= s->max_table_entries || s->pagetable[pagetable_index] == 0xffffffff) + return -1; // not allocated + + bitmap_offset = 512 * (uint64_t) s->pagetable[pagetable_index]; + block_offset = bitmap_offset + s->bitmap_size + (512 * pageentry_index); + + // We must ensure that we don't write to any sectors which are marked as + // unused in the bitmap. We get away with setting all bits in the block + // bitmap each time we write to a new block. This might cause Virtual PC to + // miss sparse read optimization, but it's not a problem in terms of + // correctness. + if (write && (s->last_bitmap_offset != bitmap_offset)) { + uint8_t bitmap[s->bitmap_size]; + + s->last_bitmap_offset = bitmap_offset; + memset(bitmap, 0xff, s->bitmap_size); + bdrv_pwrite_sync(bs->file, bitmap_offset, bitmap, s->bitmap_size); + } + +// printf("sector: %" PRIx64 ", index: %x, offset: %x, bioff: %" PRIx64 ", bloff: %" PRIx64 "\n", +// sector_num, pagetable_index, pageentry_index, +// bitmap_offset, block_offset); + +// disabled by reason +#if 0 +#ifdef CACHE + if (bitmap_offset != s->last_bitmap) + { + lseek(s->fd, bitmap_offset, SEEK_SET); + + s->last_bitmap = bitmap_offset; + + // Scary! Bitmap is stored as big endian 32bit entries, + // while we used to look it up byte by byte + read(s->fd, s->pageentry_u8, 512); + for (i = 0; i < 128; i++) + be32_to_cpus(&s->pageentry_u32[i]); + } + + if ((s->pageentry_u8[pageentry_index / 8] >> (pageentry_index % 8)) & 1) + return -1; +#else + lseek(s->fd, bitmap_offset + (pageentry_index / 8), SEEK_SET); + + read(s->fd, &bitmap_entry, 1); + + if ((bitmap_entry >> (pageentry_index % 8)) & 1) + return -1; // not allocated +#endif +#endif + + return block_offset; +} + +/* + * Writes the footer to the end of the image file. This is needed when the + * file grows as it overwrites the old footer + * + * Returns 0 on success and < 0 on error + */ +static int rewrite_footer(BlockDriverState* bs) +{ + int ret; + BDRVVPCState *s = bs->opaque; + int64_t offset = s->free_data_block_offset; + + ret = bdrv_pwrite_sync(bs->file, offset, s->footer_buf, HEADER_SIZE); + if (ret < 0) + return ret; + + return 0; +} + +/* + * Allocates a new block. This involves writing a new footer and updating + * the Block Allocation Table to use the space at the old end of the image + * file (overwriting the old footer) + * + * Returns the sectors' offset in the image file on success and < 0 on error + */ +static int64_t alloc_block(BlockDriverState* bs, int64_t sector_num) +{ + BDRVVPCState *s = bs->opaque; + int64_t bat_offset; + uint32_t index, bat_value; + int ret; + uint8_t bitmap[s->bitmap_size]; + + // Check if sector_num is valid + if ((sector_num < 0) || (sector_num > bs->total_sectors)) + return -1; + + // Write entry into in-memory BAT + index = (sector_num * 512) / s->block_size; + if (s->pagetable[index] != 0xFFFFFFFF) + return -1; + + s->pagetable[index] = s->free_data_block_offset / 512; + + // Initialize the block's bitmap + memset(bitmap, 0xff, s->bitmap_size); + bdrv_pwrite_sync(bs->file, s->free_data_block_offset, bitmap, + s->bitmap_size); + + // Write new footer (the old one will be overwritten) + s->free_data_block_offset += s->block_size + s->bitmap_size; + ret = rewrite_footer(bs); + if (ret < 0) + goto fail; + + // Write BAT entry to disk + bat_offset = s->bat_offset + (4 * index); + bat_value = be32_to_cpu(s->pagetable[index]); + ret = bdrv_pwrite_sync(bs->file, bat_offset, &bat_value, 4); + if (ret < 0) + goto fail; + + return get_sector_offset(bs, sector_num, 0); + +fail: + s->free_data_block_offset -= (s->block_size + s->bitmap_size); + return -1; +} + +static int vpc_read(BlockDriverState *bs, int64_t sector_num, + uint8_t *buf, int nb_sectors) +{ + BDRVVPCState *s = bs->opaque; + int ret; + int64_t offset; + int64_t sectors, sectors_per_block; + + while (nb_sectors > 0) { + offset = get_sector_offset(bs, sector_num, 0); + + sectors_per_block = s->block_size >> BDRV_SECTOR_BITS; + sectors = sectors_per_block - (sector_num % sectors_per_block); + if (sectors > nb_sectors) { + sectors = nb_sectors; + } + + if (offset == -1) { + memset(buf, 0, sectors * BDRV_SECTOR_SIZE); + } else { + ret = bdrv_pread(bs->file, offset, buf, + sectors * BDRV_SECTOR_SIZE); + if (ret != sectors * BDRV_SECTOR_SIZE) { + return -1; + } + } + + nb_sectors -= sectors; + sector_num += sectors; + buf += sectors * BDRV_SECTOR_SIZE; + } + return 0; +} + +static int vpc_write(BlockDriverState *bs, int64_t sector_num, + const uint8_t *buf, int nb_sectors) +{ + BDRVVPCState *s = bs->opaque; + int64_t offset; + int64_t sectors, sectors_per_block; + int ret; + + while (nb_sectors > 0) { + offset = get_sector_offset(bs, sector_num, 1); + + sectors_per_block = s->block_size >> BDRV_SECTOR_BITS; + sectors = sectors_per_block - (sector_num % sectors_per_block); + if (sectors > nb_sectors) { + sectors = nb_sectors; + } + + if (offset == -1) { + offset = alloc_block(bs, sector_num); + if (offset < 0) + return -1; + } + + ret = bdrv_pwrite(bs->file, offset, buf, sectors * BDRV_SECTOR_SIZE); + if (ret != sectors * BDRV_SECTOR_SIZE) { + return -1; + } + + nb_sectors -= sectors; + sector_num += sectors; + buf += sectors * BDRV_SECTOR_SIZE; + } + + return 0; +} + +static int vpc_flush(BlockDriverState *bs) +{ + return bdrv_flush(bs->file); +} + +/* + * Calculates the number of cylinders, heads and sectors per cylinder + * based on a given number of sectors. This is the algorithm described + * in the VHD specification. + * + * Note that the geometry doesn't always exactly match total_sectors but + * may round it down. + * + * Returns 0 on success, -EFBIG if the size is larger than 127 GB + */ +static int calculate_geometry(int64_t total_sectors, uint16_t* cyls, + uint8_t* heads, uint8_t* secs_per_cyl) +{ + uint32_t cyls_times_heads; + + if (total_sectors > 65535 * 16 * 255) + return -EFBIG; + + if (total_sectors > 65535 * 16 * 63) { + *secs_per_cyl = 255; + *heads = 16; + cyls_times_heads = total_sectors / *secs_per_cyl; + } else { + *secs_per_cyl = 17; + cyls_times_heads = total_sectors / *secs_per_cyl; + *heads = (cyls_times_heads + 1023) / 1024; + + if (*heads < 4) + *heads = 4; + + if (cyls_times_heads >= (*heads * 1024) || *heads > 16) { + *secs_per_cyl = 31; + *heads = 16; + cyls_times_heads = total_sectors / *secs_per_cyl; + } + + if (cyls_times_heads >= (*heads * 1024)) { + *secs_per_cyl = 63; + *heads = 16; + cyls_times_heads = total_sectors / *secs_per_cyl; + } + } + + *cyls = cyls_times_heads / *heads; + + return 0; +} + +static int vpc_create(const char *filename, QEMUOptionParameter *options) +{ + uint8_t buf[1024]; + struct vhd_footer* footer = (struct vhd_footer*) buf; + struct vhd_dyndisk_header* dyndisk_header = + (struct vhd_dyndisk_header*) buf; + int fd, i; + uint16_t cyls = 0; + uint8_t heads = 0; + uint8_t secs_per_cyl = 0; + size_t block_size, num_bat_entries; + int64_t total_sectors = 0; + int ret = -EIO; + + // Read out options + while (options && options->name) { + if (!strcmp(options->name, "size")) { + total_sectors = options->value.n / 512; + } + options++; + } + + // Create the file + fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0644); + if (fd < 0) + return -EIO; + + /* Calculate matching total_size and geometry. Increase the number of + sectors requested until we get enough (or fail). */ + for (i = 0; total_sectors > (int64_t)cyls * heads * secs_per_cyl; i++) { + if (calculate_geometry(total_sectors + i, + &cyls, &heads, &secs_per_cyl)) { + ret = -EFBIG; + goto fail; + } + } + total_sectors = (int64_t) cyls * heads * secs_per_cyl; + + // Prepare the Hard Disk Footer + memset(buf, 0, 1024); + + memcpy(footer->creator, "conectix", 8); + // TODO Check if "qemu" creator_app is ok for VPC + memcpy(footer->creator_app, "qemu", 4); + memcpy(footer->creator_os, "Wi2k", 4); + + footer->features = be32_to_cpu(0x02); + footer->version = be32_to_cpu(0x00010000); + footer->data_offset = be64_to_cpu(HEADER_SIZE); + footer->timestamp = be32_to_cpu(time(NULL) - VHD_TIMESTAMP_BASE); + + // Version of Virtual PC 2007 + footer->major = be16_to_cpu(0x0005); + footer->minor =be16_to_cpu(0x0003); + + footer->orig_size = be64_to_cpu(total_sectors * 512); + footer->size = be64_to_cpu(total_sectors * 512); + + footer->cyls = be16_to_cpu(cyls); + footer->heads = heads; + footer->secs_per_cyl = secs_per_cyl; + + footer->type = be32_to_cpu(VHD_DYNAMIC); + + // TODO uuid is missing + + footer->checksum = be32_to_cpu(vpc_checksum(buf, HEADER_SIZE)); + + // Write the footer (twice: at the beginning and at the end) + block_size = 0x200000; + num_bat_entries = (total_sectors + block_size / 512) / (block_size / 512); + + if (write(fd, buf, HEADER_SIZE) != HEADER_SIZE) { + goto fail; + } + + if (lseek(fd, 1536 + ((num_bat_entries * 4 + 511) & ~511), SEEK_SET) < 0) { + goto fail; + } + if (write(fd, buf, HEADER_SIZE) != HEADER_SIZE) { + goto fail; + } + + // Write the initial BAT + if (lseek(fd, 3 * 512, SEEK_SET) < 0) { + goto fail; + } + + memset(buf, 0xFF, 512); + for (i = 0; i < (num_bat_entries * 4 + 511) / 512; i++) { + if (write(fd, buf, 512) != 512) { + goto fail; + } + } + + + // Prepare the Dynamic Disk Header + memset(buf, 0, 1024); + + memcpy(dyndisk_header->magic, "cxsparse", 8); + + dyndisk_header->data_offset = be64_to_cpu(0xFFFFFFFF); + dyndisk_header->table_offset = be64_to_cpu(3 * 512); + dyndisk_header->version = be32_to_cpu(0x00010000); + dyndisk_header->block_size = be32_to_cpu(block_size); + dyndisk_header->max_table_entries = be32_to_cpu(num_bat_entries); + + dyndisk_header->checksum = be32_to_cpu(vpc_checksum(buf, 1024)); + + // Write the header + if (lseek(fd, 512, SEEK_SET) < 0) { + goto fail; + } + + if (write(fd, buf, 1024) != 1024) { + goto fail; + } + ret = 0; + + fail: + close(fd); + return ret; +} + +static void vpc_close(BlockDriverState *bs) +{ + BDRVVPCState *s = bs->opaque; + qemu_free(s->pagetable); +#ifdef CACHE + qemu_free(s->pageentry_u8); +#endif +} + +static QEMUOptionParameter vpc_create_options[] = { + { + .name = BLOCK_OPT_SIZE, + .type = OPT_SIZE, + .help = "Virtual disk size" + }, + { NULL } +}; + +static BlockDriver bdrv_vpc = { + .format_name = "vpc", + .instance_size = sizeof(BDRVVPCState), + .bdrv_probe = vpc_probe, + .bdrv_open = vpc_open, + .bdrv_read = vpc_read, + .bdrv_write = vpc_write, + .bdrv_flush = vpc_flush, + .bdrv_close = vpc_close, + .bdrv_create = vpc_create, + + .create_options = vpc_create_options, +}; + +static void bdrv_vpc_init(void) +{ + bdrv_register(&bdrv_vpc); +} + +block_init(bdrv_vpc_init); diff --git a/block/vvfat.c b/block/vvfat.c new file mode 100644 index 0000000..fe568fe --- /dev/null +++ b/block/vvfat.c @@ -0,0 +1,2888 @@ +/* vim:set shiftwidth=4 ts=8: */ +/* + * QEMU Block driver for virtual VFAT (shadows a local directory) + * + * Copyright (c) 2004,2005 Johannes E. Schindelin + * + * 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 +#include +#include "qemu-common.h" +#include "block_int.h" +#include "module.h" + +#ifndef S_IWGRP +#define S_IWGRP 0 +#endif +#ifndef S_IWOTH +#define S_IWOTH 0 +#endif + +/* TODO: add ":bootsector=blabla.img:" */ +/* LATER TODO: add automatic boot sector generation from + BOOTEASY.ASM and Ranish Partition Manager + Note that DOS assumes the system files to be the first files in the + file system (test if the boot sector still relies on that fact)! */ +/* MAYBE TODO: write block-visofs.c */ +/* TODO: call try_commit() only after a timeout */ + +/* #define DEBUG */ + +#ifdef DEBUG + +#define DLOG(a) a + +#undef stderr +#define stderr STDERR +FILE* stderr = NULL; + +static void checkpoint(void); + +#ifdef __MINGW32__ +void nonono(const char* file, int line, const char* msg) { + fprintf(stderr, "Nonono! %s:%d %s\n", file, line, msg); + exit(-5); +} +#undef assert +#define assert(a) do {if (!(a)) nonono(__FILE__, __LINE__, #a);}while(0) +#endif + +#else + +#define DLOG(a) + +#endif + +/* dynamic array functions */ +typedef struct array_t { + char* pointer; + unsigned int size,next,item_size; +} array_t; + +static inline void array_init(array_t* array,unsigned int item_size) +{ + array->pointer = NULL; + array->size=0; + array->next=0; + array->item_size=item_size; +} + +static inline void array_free(array_t* array) +{ + if(array->pointer) + free(array->pointer); + array->size=array->next=0; +} + +/* does not automatically grow */ +static inline void* array_get(array_t* array,unsigned int index) { + assert(index < array->next); + return array->pointer + index * array->item_size; +} + +static inline int array_ensure_allocated(array_t* array, int index) +{ + if((index + 1) * array->item_size > array->size) { + int new_size = (index + 32) * array->item_size; + array->pointer = qemu_realloc(array->pointer, new_size); + if (!array->pointer) + return -1; + array->size = new_size; + array->next = index + 1; + } + + return 0; +} + +static inline void* array_get_next(array_t* array) { + unsigned int next = array->next; + void* result; + + if (array_ensure_allocated(array, next) < 0) + return NULL; + + array->next = next + 1; + result = array_get(array, next); + + return result; +} + +static inline void* array_insert(array_t* array,unsigned int index,unsigned int count) { + if((array->next+count)*array->item_size>array->size) { + int increment=count*array->item_size; + array->pointer=qemu_realloc(array->pointer,array->size+increment); + if(!array->pointer) + return NULL; + array->size+=increment; + } + memmove(array->pointer+(index+count)*array->item_size, + array->pointer+index*array->item_size, + (array->next-index)*array->item_size); + array->next+=count; + return array->pointer+index*array->item_size; +} + +/* this performs a "roll", so that the element which was at index_from becomes + * index_to, but the order of all other elements is preserved. */ +static inline int array_roll(array_t* array,int index_to,int index_from,int count) +{ + char* buf; + char* from; + char* to; + int is; + + if(!array || + index_to<0 || index_to>=array->next || + index_from<0 || index_from>=array->next) + return -1; + + if(index_to==index_from) + return 0; + + is=array->item_size; + from=array->pointer+index_from*is; + to=array->pointer+index_to*is; + buf=qemu_malloc(is*count); + memcpy(buf,from,is*count); + + if(index_to=0); + assert(count > 0); + assert(index + count <= array->next); + if(array_roll(array,array->next-1,index,count)) + return -1; + array->next -= count; + return 0; +} + +static int array_remove(array_t* array,int index) +{ + return array_remove_slice(array, index, 1); +} + +/* return the index for a given member */ +static int array_index(array_t* array, void* pointer) +{ + size_t offset = (char*)pointer - array->pointer; + assert((offset % array->item_size) == 0); + assert(offset/array->item_size < array->next); + return offset/array->item_size; +} + +/* These structures are used to fake a disk and the VFAT filesystem. + * For this reason we need to use __attribute__((packed)). */ + +typedef struct bootsector_t { + uint8_t jump[3]; + uint8_t name[8]; + uint16_t sector_size; + uint8_t sectors_per_cluster; + uint16_t reserved_sectors; + uint8_t number_of_fats; + uint16_t root_entries; + uint16_t total_sectors16; + uint8_t media_type; + uint16_t sectors_per_fat; + uint16_t sectors_per_track; + uint16_t number_of_heads; + uint32_t hidden_sectors; + uint32_t total_sectors; + union { + struct { + uint8_t drive_number; + uint8_t current_head; + uint8_t signature; + uint32_t id; + uint8_t volume_label[11]; + } __attribute__((packed)) fat16; + struct { + uint32_t sectors_per_fat; + uint16_t flags; + uint8_t major,minor; + uint32_t first_cluster_of_root_directory; + uint16_t info_sector; + uint16_t backup_boot_sector; + uint16_t ignored; + } __attribute__((packed)) fat32; + } u; + uint8_t fat_type[8]; + uint8_t ignored[0x1c0]; + uint8_t magic[2]; +} __attribute__((packed)) bootsector_t; + +typedef struct { + uint8_t head; + uint8_t sector; + uint8_t cylinder; +} mbr_chs_t; + +typedef struct partition_t { + uint8_t attributes; /* 0x80 = bootable */ + mbr_chs_t start_CHS; + uint8_t fs_type; /* 0x1 = FAT12, 0x6 = FAT16, 0xe = FAT16_LBA, 0xb = FAT32, 0xc = FAT32_LBA */ + mbr_chs_t end_CHS; + uint32_t start_sector_long; + uint32_t length_sector_long; +} __attribute__((packed)) partition_t; + +typedef struct mbr_t { + uint8_t ignored[0x1b8]; + uint32_t nt_id; + uint8_t ignored2[2]; + partition_t partition[4]; + uint8_t magic[2]; +} __attribute__((packed)) mbr_t; + +typedef struct direntry_t { + uint8_t name[8]; + uint8_t extension[3]; + uint8_t attributes; + uint8_t reserved[2]; + uint16_t ctime; + uint16_t cdate; + uint16_t adate; + uint16_t begin_hi; + uint16_t mtime; + uint16_t mdate; + uint16_t begin; + uint32_t size; +} __attribute__((packed)) direntry_t; + +/* this structure are used to transparently access the files */ + +typedef struct mapping_t { + /* begin is the first cluster, end is the last+1 */ + uint32_t begin,end; + /* as s->directory is growable, no pointer may be used here */ + unsigned int dir_index; + /* the clusters of a file may be in any order; this points to the first */ + int first_mapping_index; + union { + /* offset is + * - the offset in the file (in clusters) for a file, or + * - the next cluster of the directory for a directory, and + * - the address of the buffer for a faked entry + */ + struct { + uint32_t offset; + } file; + struct { + int parent_mapping_index; + int first_dir_index; + } dir; + } info; + /* path contains the full path, i.e. it always starts with s->path */ + char* path; + + enum { MODE_UNDEFINED = 0, MODE_NORMAL = 1, MODE_MODIFIED = 2, + MODE_DIRECTORY = 4, MODE_FAKED = 8, + MODE_DELETED = 16, MODE_RENAMED = 32 } mode; + int read_only; +} mapping_t; + +#ifdef DEBUG +static void print_direntry(const struct direntry_t*); +static void print_mapping(const struct mapping_t* mapping); +#endif + +/* here begins the real VVFAT driver */ + +typedef struct BDRVVVFATState { + BlockDriverState* bs; /* pointer to parent */ + unsigned int first_sectors_number; /* 1 for a single partition, 0x40 for a disk with partition table */ + unsigned char first_sectors[0x40*0x200]; + + int fat_type; /* 16 or 32 */ + array_t fat,directory,mapping; + + unsigned int cluster_size; + unsigned int sectors_per_cluster; + unsigned int sectors_per_fat; + unsigned int sectors_of_root_directory; + uint32_t last_cluster_of_root_directory; + unsigned int faked_sectors; /* how many sectors are faked before file data */ + uint32_t sector_count; /* total number of sectors of the partition */ + uint32_t cluster_count; /* total number of clusters of this partition */ + uint32_t max_fat_value; + + int current_fd; + mapping_t* current_mapping; + unsigned char* cluster; /* points to current cluster */ + unsigned char* cluster_buffer; /* points to a buffer to hold temp data */ + unsigned int current_cluster; + + /* write support */ + BlockDriverState* write_target; + char* qcow_filename; + BlockDriverState* qcow; + void* fat2; + char* used_clusters; + array_t commits; + const char* path; + int downcase_short_names; +} BDRVVVFATState; + +/* take the sector position spos and convert it to Cylinder/Head/Sector position + * if the position is outside the specified geometry, fill maximum value for CHS + * and return 1 to signal overflow. + */ +static int sector2CHS(BlockDriverState* bs, mbr_chs_t * chs, int spos){ + int head,sector; + sector = spos % (bs->secs); spos/= bs->secs; + head = spos % (bs->heads); spos/= bs->heads; + if(spos >= bs->cyls){ + /* Overflow, + it happens if 32bit sector positions are used, while CHS is only 24bit. + Windows/Dos is said to take 1023/255/63 as nonrepresentable CHS */ + chs->head = 0xFF; + chs->sector = 0xFF; + chs->cylinder = 0xFF; + return 1; + } + chs->head = (uint8_t)head; + chs->sector = (uint8_t)( (sector+1) | ((spos>>8)<<6) ); + chs->cylinder = (uint8_t)spos; + return 0; +} + +static void init_mbr(BDRVVVFATState* s) +{ + /* TODO: if the files mbr.img and bootsect.img exist, use them */ + mbr_t* real_mbr=(mbr_t*)s->first_sectors; + partition_t* partition = &(real_mbr->partition[0]); + int lba; + + memset(s->first_sectors,0,512); + + /* Win NT Disk Signature */ + real_mbr->nt_id= cpu_to_le32(0xbe1afdfa); + + partition->attributes=0x80; /* bootable */ + + /* LBA is used when partition is outside the CHS geometry */ + lba = sector2CHS(s->bs, &partition->start_CHS, s->first_sectors_number-1); + lba|= sector2CHS(s->bs, &partition->end_CHS, s->sector_count); + + /*LBA partitions are identified only by start/length_sector_long not by CHS*/ + partition->start_sector_long =cpu_to_le32(s->first_sectors_number-1); + partition->length_sector_long=cpu_to_le32(s->sector_count - s->first_sectors_number+1); + + /* FAT12/FAT16/FAT32 */ + /* DOS uses different types when partition is LBA, + probably to prevent older versions from using CHS on them */ + partition->fs_type= s->fat_type==12 ? 0x1: + s->fat_type==16 ? (lba?0xe:0x06): + /*fat_tyoe==32*/ (lba?0xc:0x0b); + + real_mbr->magic[0]=0x55; real_mbr->magic[1]=0xaa; +} + +/* direntry functions */ + +/* dest is assumed to hold 258 bytes, and pads with 0xffff up to next multiple of 26 */ +static inline int short2long_name(char* dest,const char* src) +{ + int i; + int len; + for(i=0;i<129 && src[i];i++) { + dest[2*i]=src[i]; + dest[2*i+1]=0; + } + len=2*i; + dest[2*i]=dest[2*i+1]=0; + for(i=2*i+2;(i%26);i++) + dest[i]=0xff; + return len; +} + +static inline direntry_t* create_long_filename(BDRVVVFATState* s,const char* filename) +{ + char buffer[258]; + int length=short2long_name(buffer,filename), + number_of_entries=(length+25)/26,i; + direntry_t* entry; + + for(i=0;idirectory)); + entry->attributes=0xf; + entry->reserved[0]=0; + entry->begin=0; + entry->name[0]=(number_of_entries-i)|(i==0?0x40:0); + } + for(i=0;i<26*number_of_entries;i++) { + int offset=(i%26); + if(offset<10) offset=1+offset; + else if(offset<22) offset=14+offset-10; + else offset=28+offset-22; + entry=array_get(&(s->directory),s->directory.next-1-(i/26)); + entry->name[offset]=buffer[i]; + } + return array_get(&(s->directory),s->directory.next-number_of_entries); +} + +static char is_free(const direntry_t* direntry) +{ + return direntry->name[0]==0xe5 || direntry->name[0]==0x00; +} + +static char is_volume_label(const direntry_t* direntry) +{ + return direntry->attributes == 0x28; +} + +static char is_long_name(const direntry_t* direntry) +{ + return direntry->attributes == 0xf; +} + +static char is_short_name(const direntry_t* direntry) +{ + return !is_volume_label(direntry) && !is_long_name(direntry) + && !is_free(direntry); +} + +static char is_directory(const direntry_t* direntry) +{ + return direntry->attributes & 0x10 && direntry->name[0] != 0xe5; +} + +static inline char is_dot(const direntry_t* direntry) +{ + return is_short_name(direntry) && direntry->name[0] == '.'; +} + +static char is_file(const direntry_t* direntry) +{ + return is_short_name(direntry) && !is_directory(direntry); +} + +static inline uint32_t begin_of_direntry(const direntry_t* direntry) +{ + return le16_to_cpu(direntry->begin)|(le16_to_cpu(direntry->begin_hi)<<16); +} + +static inline uint32_t filesize_of_direntry(const direntry_t* direntry) +{ + return le32_to_cpu(direntry->size); +} + +static void set_begin_of_direntry(direntry_t* direntry, uint32_t begin) +{ + direntry->begin = cpu_to_le16(begin & 0xffff); + direntry->begin_hi = cpu_to_le16((begin >> 16) & 0xffff); +} + +/* fat functions */ + +static inline uint8_t fat_chksum(const direntry_t* entry) +{ + uint8_t chksum=0; + int i; + + for(i=0;i<11;i++) { + unsigned char c; + + c = (i < 8) ? entry->name[i] : entry->extension[i-8]; + chksum=(((chksum&0xfe)>>1)|((chksum&0x01)?0x80:0)) + c; + } + + return chksum; +} + +/* if return_time==0, this returns the fat_date, else the fat_time */ +static uint16_t fat_datetime(time_t time,int return_time) { + struct tm* t; +#ifdef _WIN32 + t=localtime(&time); /* this is not thread safe */ +#else + struct tm t1; + t = &t1; + localtime_r(&time,t); +#endif + if(return_time) + return cpu_to_le16((t->tm_sec/2)|(t->tm_min<<5)|(t->tm_hour<<11)); + return cpu_to_le16((t->tm_mday)|((t->tm_mon+1)<<5)|((t->tm_year-80)<<9)); +} + +static inline void fat_set(BDRVVVFATState* s,unsigned int cluster,uint32_t value) +{ + if(s->fat_type==32) { + uint32_t* entry=array_get(&(s->fat),cluster); + *entry=cpu_to_le32(value); + } else if(s->fat_type==16) { + uint16_t* entry=array_get(&(s->fat),cluster); + *entry=cpu_to_le16(value&0xffff); + } else { + int offset = (cluster*3/2); + unsigned char* p = array_get(&(s->fat), offset); + switch (cluster&1) { + case 0: + p[0] = value&0xff; + p[1] = (p[1]&0xf0) | ((value>>8)&0xf); + break; + case 1: + p[0] = (p[0]&0xf) | ((value&0xf)<<4); + p[1] = (value>>4); + break; + } + } +} + +static inline uint32_t fat_get(BDRVVVFATState* s,unsigned int cluster) +{ + if(s->fat_type==32) { + uint32_t* entry=array_get(&(s->fat),cluster); + return le32_to_cpu(*entry); + } else if(s->fat_type==16) { + uint16_t* entry=array_get(&(s->fat),cluster); + return le16_to_cpu(*entry); + } else { + const uint8_t* x=(uint8_t*)(s->fat.pointer)+cluster*3/2; + return ((x[0]|(x[1]<<8))>>(cluster&1?4:0))&0x0fff; + } +} + +static inline int fat_eof(BDRVVVFATState* s,uint32_t fat_entry) +{ + if(fat_entry>s->max_fat_value-8) + return -1; + return 0; +} + +static inline void init_fat(BDRVVVFATState* s) +{ + if (s->fat_type == 12) { + array_init(&(s->fat),1); + array_ensure_allocated(&(s->fat), + s->sectors_per_fat * 0x200 * 3 / 2 - 1); + } else { + array_init(&(s->fat),(s->fat_type==32?4:2)); + array_ensure_allocated(&(s->fat), + s->sectors_per_fat * 0x200 / s->fat.item_size - 1); + } + memset(s->fat.pointer,0,s->fat.size); + + switch(s->fat_type) { + case 12: s->max_fat_value=0xfff; break; + case 16: s->max_fat_value=0xffff; break; + case 32: s->max_fat_value=0x0fffffff; break; + default: s->max_fat_value=0; /* error... */ + } + +} + +/* TODO: in create_short_filename, 0xe5->0x05 is not yet handled! */ +/* TODO: in parse_short_filename, 0x05->0xe5 is not yet handled! */ +static inline direntry_t* create_short_and_long_name(BDRVVVFATState* s, + unsigned int directory_start, const char* filename, int is_dot) +{ + int i,j,long_index=s->directory.next; + direntry_t* entry = NULL; + direntry_t* entry_long = NULL; + + if(is_dot) { + entry=array_get_next(&(s->directory)); + memset(entry->name,0x20,11); + memcpy(entry->name,filename,strlen(filename)); + return entry; + } + + entry_long=create_long_filename(s,filename); + + i = strlen(filename); + for(j = i - 1; j>0 && filename[j]!='.';j--); + if (j > 0) + i = (j > 8 ? 8 : j); + else if (i > 8) + i = 8; + + entry=array_get_next(&(s->directory)); + memset(entry->name,0x20,11); + memcpy(entry->name, filename, i); + + if(j > 0) + for (i = 0; i < 3 && filename[j+1+i]; i++) + entry->extension[i] = filename[j+1+i]; + + /* upcase & remove unwanted characters */ + for(i=10;i>=0;i--) { + if(i==10 || i==7) for(;i>0 && entry->name[i]==' ';i--); + if(entry->name[i]<=' ' || entry->name[i]>0x7f + || strchr(".*?<>|\":/\\[];,+='",entry->name[i])) + entry->name[i]='_'; + else if(entry->name[i]>='a' && entry->name[i]<='z') + entry->name[i]+='A'-'a'; + } + + /* mangle duplicates */ + while(1) { + direntry_t* entry1=array_get(&(s->directory),directory_start); + int j; + + for(;entry1name,entry->name,11)) + break; /* found dupe */ + if(entry1==entry) /* no dupe found */ + break; + + /* use all 8 characters of name */ + if(entry->name[7]==' ') { + int j; + for(j=6;j>0 && entry->name[j]==' ';j--) + entry->name[j]='~'; + } + + /* increment number */ + for(j=7;j>0 && entry->name[j]=='9';j--) + entry->name[j]='0'; + if(j>0) { + if(entry->name[j]<'0' || entry->name[j]>'9') + entry->name[j]='0'; + else + entry->name[j]++; + } + } + + /* calculate checksum; propagate to long name */ + if(entry_long) { + uint8_t chksum=fat_chksum(entry); + + /* calculate anew, because realloc could have taken place */ + entry_long=array_get(&(s->directory),long_index); + while(entry_longreserved[1]=chksum; + entry_long++; + } + } + + return entry; +} + +/* + * Read a directory. (the index of the corresponding mapping must be passed). + */ +static int read_directory(BDRVVVFATState* s, int mapping_index) +{ + mapping_t* mapping = array_get(&(s->mapping), mapping_index); + direntry_t* direntry; + const char* dirname = mapping->path; + int first_cluster = mapping->begin; + int parent_index = mapping->info.dir.parent_mapping_index; + mapping_t* parent_mapping = (mapping_t*) + (parent_index >= 0 ? array_get(&(s->mapping), parent_index) : NULL); + int first_cluster_of_parent = parent_mapping ? parent_mapping->begin : -1; + + DIR* dir=opendir(dirname); + struct dirent* entry; + int i; + + assert(mapping->mode & MODE_DIRECTORY); + + if(!dir) { + mapping->end = mapping->begin; + return -1; + } + + i = mapping->info.dir.first_dir_index = + first_cluster == 0 ? 0 : s->directory.next; + + /* actually read the directory, and allocate the mappings */ + while((entry=readdir(dir))) { + unsigned int length=strlen(dirname)+2+strlen(entry->d_name); + char* buffer; + direntry_t* direntry; + struct stat st; + int is_dot=!strcmp(entry->d_name,"."); + int is_dotdot=!strcmp(entry->d_name,".."); + + if(first_cluster == 0 && (is_dotdot || is_dot)) + continue; + + buffer=(char*)qemu_malloc(length); + snprintf(buffer,length,"%s/%s",dirname,entry->d_name); + + if(stat(buffer,&st)<0) { + free(buffer); + continue; + } + + /* create directory entry for this file */ + direntry=create_short_and_long_name(s, i, entry->d_name, + is_dot || is_dotdot); + direntry->attributes=(S_ISDIR(st.st_mode)?0x10:0x20); + direntry->reserved[0]=direntry->reserved[1]=0; + direntry->ctime=fat_datetime(st.st_ctime,1); + direntry->cdate=fat_datetime(st.st_ctime,0); + direntry->adate=fat_datetime(st.st_atime,0); + direntry->begin_hi=0; + direntry->mtime=fat_datetime(st.st_mtime,1); + direntry->mdate=fat_datetime(st.st_mtime,0); + if(is_dotdot) + set_begin_of_direntry(direntry, first_cluster_of_parent); + else if(is_dot) + set_begin_of_direntry(direntry, first_cluster); + else + direntry->begin=0; /* do that later */ + if (st.st_size > 0x7fffffff) { + fprintf(stderr, "File %s is larger than 2GB\n", buffer); + free(buffer); + closedir(dir); + return -2; + } + direntry->size=cpu_to_le32(S_ISDIR(st.st_mode)?0:st.st_size); + + /* create mapping for this file */ + if(!is_dot && !is_dotdot && (S_ISDIR(st.st_mode) || st.st_size)) { + s->current_mapping=(mapping_t*)array_get_next(&(s->mapping)); + s->current_mapping->begin=0; + s->current_mapping->end=st.st_size; + /* + * we get the direntry of the most recent direntry, which + * contains the short name and all the relevant information. + */ + s->current_mapping->dir_index=s->directory.next-1; + s->current_mapping->first_mapping_index = -1; + if (S_ISDIR(st.st_mode)) { + s->current_mapping->mode = MODE_DIRECTORY; + s->current_mapping->info.dir.parent_mapping_index = + mapping_index; + } else { + s->current_mapping->mode = MODE_UNDEFINED; + s->current_mapping->info.file.offset = 0; + } + s->current_mapping->path=buffer; + s->current_mapping->read_only = + (st.st_mode & (S_IWUSR | S_IWGRP | S_IWOTH)) == 0; + } + } + closedir(dir); + + /* fill with zeroes up to the end of the cluster */ + while(s->directory.next%(0x10*s->sectors_per_cluster)) { + direntry_t* direntry=array_get_next(&(s->directory)); + memset(direntry,0,sizeof(direntry_t)); + } + +/* TODO: if there are more entries, bootsector has to be adjusted! */ +#define ROOT_ENTRIES (0x02 * 0x10 * s->sectors_per_cluster) + if (mapping_index == 0 && s->directory.next < ROOT_ENTRIES) { + /* root directory */ + int cur = s->directory.next; + array_ensure_allocated(&(s->directory), ROOT_ENTRIES - 1); + memset(array_get(&(s->directory), cur), 0, + (ROOT_ENTRIES - cur) * sizeof(direntry_t)); + } + + /* reget the mapping, since s->mapping was possibly realloc()ed */ + mapping = (mapping_t*)array_get(&(s->mapping), mapping_index); + first_cluster += (s->directory.next - mapping->info.dir.first_dir_index) + * 0x20 / s->cluster_size; + mapping->end = first_cluster; + + direntry = (direntry_t*)array_get(&(s->directory), mapping->dir_index); + set_begin_of_direntry(direntry, mapping->begin); + + return 0; +} + +static inline uint32_t sector2cluster(BDRVVVFATState* s,off_t sector_num) +{ + return (sector_num-s->faked_sectors)/s->sectors_per_cluster; +} + +static inline off_t cluster2sector(BDRVVVFATState* s, uint32_t cluster_num) +{ + return s->faked_sectors + s->sectors_per_cluster * cluster_num; +} + +static inline uint32_t sector_offset_in_cluster(BDRVVVFATState* s,off_t sector_num) +{ + return (sector_num-s->first_sectors_number-2*s->sectors_per_fat)%s->sectors_per_cluster; +} + +#ifdef DBG +static direntry_t* get_direntry_for_mapping(BDRVVVFATState* s,mapping_t* mapping) +{ + if(mapping->mode==MODE_UNDEFINED) + return 0; + return (direntry_t*)(s->directory.pointer+sizeof(direntry_t)*mapping->dir_index); +} +#endif + +static int init_directories(BDRVVVFATState* s, + const char* dirname) +{ + bootsector_t* bootsector; + mapping_t* mapping; + unsigned int i; + unsigned int cluster; + + memset(&(s->first_sectors[0]),0,0x40*0x200); + + s->cluster_size=s->sectors_per_cluster*0x200; + s->cluster_buffer=qemu_malloc(s->cluster_size); + + /* + * The formula: sc = spf+1+spf*spc*(512*8/fat_type), + * where sc is sector_count, + * spf is sectors_per_fat, + * spc is sectors_per_clusters, and + * fat_type = 12, 16 or 32. + */ + i = 1+s->sectors_per_cluster*0x200*8/s->fat_type; + s->sectors_per_fat=(s->sector_count+i)/i; /* round up */ + + array_init(&(s->mapping),sizeof(mapping_t)); + array_init(&(s->directory),sizeof(direntry_t)); + + /* add volume label */ + { + direntry_t* entry=array_get_next(&(s->directory)); + entry->attributes=0x28; /* archive | volume label */ + memcpy(entry->name,"QEMU VVF",8); + memcpy(entry->extension,"AT ",3); + } + + /* Now build FAT, and write back information into directory */ + init_fat(s); + + s->faked_sectors=s->first_sectors_number+s->sectors_per_fat*2; + s->cluster_count=sector2cluster(s, s->sector_count); + + mapping = array_get_next(&(s->mapping)); + mapping->begin = 0; + mapping->dir_index = 0; + mapping->info.dir.parent_mapping_index = -1; + mapping->first_mapping_index = -1; + mapping->path = qemu_strdup(dirname); + i = strlen(mapping->path); + if (i > 0 && mapping->path[i - 1] == '/') + mapping->path[i - 1] = '\0'; + mapping->mode = MODE_DIRECTORY; + mapping->read_only = 0; + s->path = mapping->path; + + for (i = 0, cluster = 0; i < s->mapping.next; i++) { + /* MS-DOS expects the FAT to be 0 for the root directory + * (except for the media byte). */ + /* LATER TODO: still true for FAT32? */ + int fix_fat = (i != 0); + mapping = array_get(&(s->mapping), i); + + if (mapping->mode & MODE_DIRECTORY) { + mapping->begin = cluster; + if(read_directory(s, i)) { + fprintf(stderr, "Could not read directory %s\n", + mapping->path); + return -1; + } + mapping = array_get(&(s->mapping), i); + } else { + assert(mapping->mode == MODE_UNDEFINED); + mapping->mode=MODE_NORMAL; + mapping->begin = cluster; + if (mapping->end > 0) { + direntry_t* direntry = array_get(&(s->directory), + mapping->dir_index); + + mapping->end = cluster + 1 + (mapping->end-1)/s->cluster_size; + set_begin_of_direntry(direntry, mapping->begin); + } else { + mapping->end = cluster + 1; + fix_fat = 0; + } + } + + assert(mapping->begin < mapping->end); + + /* next free cluster */ + cluster = mapping->end; + + if(cluster > s->cluster_count) { + fprintf(stderr,"Directory does not fit in FAT%d (capacity %s)\n", + s->fat_type, + s->fat_type == 12 ? s->sector_count == 2880 ? "1.44 MB" + : "2.88 MB" + : "504MB"); + return -EINVAL; + } + + /* fix fat for entry */ + if (fix_fat) { + int j; + for(j = mapping->begin; j < mapping->end - 1; j++) + fat_set(s, j, j+1); + fat_set(s, mapping->end - 1, s->max_fat_value); + } + } + + mapping = array_get(&(s->mapping), 0); + s->sectors_of_root_directory = mapping->end * s->sectors_per_cluster; + s->last_cluster_of_root_directory = mapping->end; + + /* the FAT signature */ + fat_set(s,0,s->max_fat_value); + fat_set(s,1,s->max_fat_value); + + s->current_mapping = NULL; + + bootsector=(bootsector_t*)(s->first_sectors+(s->first_sectors_number-1)*0x200); + bootsector->jump[0]=0xeb; + bootsector->jump[1]=0x3e; + bootsector->jump[2]=0x90; + memcpy(bootsector->name,"QEMU ",8); + bootsector->sector_size=cpu_to_le16(0x200); + bootsector->sectors_per_cluster=s->sectors_per_cluster; + bootsector->reserved_sectors=cpu_to_le16(1); + bootsector->number_of_fats=0x2; /* number of FATs */ + bootsector->root_entries=cpu_to_le16(s->sectors_of_root_directory*0x10); + bootsector->total_sectors16=s->sector_count>0xffff?0:cpu_to_le16(s->sector_count); + bootsector->media_type=(s->fat_type!=12?0xf8:s->sector_count==5760?0xf9:0xf8); /* media descriptor */ + s->fat.pointer[0] = bootsector->media_type; + bootsector->sectors_per_fat=cpu_to_le16(s->sectors_per_fat); + bootsector->sectors_per_track=cpu_to_le16(s->bs->secs); + bootsector->number_of_heads=cpu_to_le16(s->bs->heads); + bootsector->hidden_sectors=cpu_to_le32(s->first_sectors_number==1?0:0x3f); + bootsector->total_sectors=cpu_to_le32(s->sector_count>0xffff?s->sector_count:0); + + /* LATER TODO: if FAT32, this is wrong */ + bootsector->u.fat16.drive_number=s->fat_type==12?0:0x80; /* assume this is hda (TODO) */ + bootsector->u.fat16.current_head=0; + bootsector->u.fat16.signature=0x29; + bootsector->u.fat16.id=cpu_to_le32(0xfabe1afd); + + memcpy(bootsector->u.fat16.volume_label,"QEMU VVFAT ",11); + memcpy(bootsector->fat_type,(s->fat_type==12?"FAT12 ":s->fat_type==16?"FAT16 ":"FAT32 "),8); + bootsector->magic[0]=0x55; bootsector->magic[1]=0xaa; + + return 0; +} + +#ifdef DEBUG +static BDRVVVFATState *vvv = NULL; +#endif + +static int enable_write_target(BDRVVVFATState *s); +static int is_consistent(BDRVVVFATState *s); + +static int vvfat_open(BlockDriverState *bs, const char* dirname, int flags) +{ + BDRVVVFATState *s = bs->opaque; + int floppy = 0; + int i; + +#ifdef DEBUG + vvv = s; +#endif + +DLOG(if (stderr == NULL) { + stderr = fopen("vvfat.log", "a"); + setbuf(stderr, NULL); +}) + + s->bs = bs; + + s->fat_type=16; + /* LATER TODO: if FAT32, adjust */ + s->sectors_per_cluster=0x10; + /* 504MB disk*/ + bs->cyls=1024; bs->heads=16; bs->secs=63; + + s->current_cluster=0xffffffff; + + s->first_sectors_number=0x40; + /* read only is the default for safety */ + bs->read_only = 1; + s->qcow = s->write_target = NULL; + s->qcow_filename = NULL; + s->fat2 = NULL; + s->downcase_short_names = 1; + + if (!strstart(dirname, "fat:", NULL)) + return -1; + + if (strstr(dirname, ":floppy:")) { + floppy = 1; + s->fat_type = 12; + s->first_sectors_number = 1; + s->sectors_per_cluster=2; + bs->cyls = 80; bs->heads = 2; bs->secs = 36; + } + + s->sector_count=bs->cyls*bs->heads*bs->secs; + + if (strstr(dirname, ":32:")) { + fprintf(stderr, "Big fat greek warning: FAT32 has not been tested. You are welcome to do so!\n"); + s->fat_type = 32; + } else if (strstr(dirname, ":16:")) { + s->fat_type = 16; + } else if (strstr(dirname, ":12:")) { + s->fat_type = 12; + s->sector_count=2880; + } + + if (strstr(dirname, ":rw:")) { + if (enable_write_target(s)) + return -1; + bs->read_only = 0; + } + + i = strrchr(dirname, ':') - dirname; + assert(i >= 3); + if (dirname[i-2] == ':' && qemu_isalpha(dirname[i-1])) + /* workaround for DOS drive names */ + dirname += i-1; + else + dirname += i+1; + + bs->total_sectors=bs->cyls*bs->heads*bs->secs; + + if(init_directories(s, dirname)) + return -1; + + s->sector_count = s->faked_sectors + s->sectors_per_cluster*s->cluster_count; + + if(s->first_sectors_number==0x40) + init_mbr(s); + + /* for some reason or other, MS-DOS does not like to know about CHS... */ + if (floppy) + bs->heads = bs->cyls = bs->secs = 0; + + // assert(is_consistent(s)); + return 0; +} + +static inline void vvfat_close_current_file(BDRVVVFATState *s) +{ + if(s->current_mapping) { + s->current_mapping = NULL; + if (s->current_fd) { + close(s->current_fd); + s->current_fd = 0; + } + } + s->current_cluster = -1; +} + +/* mappings between index1 and index2-1 are supposed to be ordered + * return value is the index of the last mapping for which end>cluster_num + */ +static inline int find_mapping_for_cluster_aux(BDRVVVFATState* s,int cluster_num,int index1,int index2) +{ + while(1) { + int index3; + mapping_t* mapping; + index3=(index1+index2)/2; + mapping=array_get(&(s->mapping),index3); + assert(mapping->begin < mapping->end); + if(mapping->begin>=cluster_num) { + assert(index2!=index3 || index2==0); + if(index2==index3) + return index1; + index2=index3; + } else { + if(index1==index3) + return mapping->end<=cluster_num ? index2 : index1; + index1=index3; + } + assert(index1<=index2); + DLOG(mapping=array_get(&(s->mapping),index1); + assert(mapping->begin<=cluster_num); + assert(index2 >= s->mapping.next || + ((mapping = array_get(&(s->mapping),index2)) && + mapping->end>cluster_num))); + } +} + +static inline mapping_t* find_mapping_for_cluster(BDRVVVFATState* s,int cluster_num) +{ + int index=find_mapping_for_cluster_aux(s,cluster_num,0,s->mapping.next); + mapping_t* mapping; + if(index>=s->mapping.next) + return NULL; + mapping=array_get(&(s->mapping),index); + if(mapping->begin>cluster_num) + return NULL; + assert(mapping->begin<=cluster_num && mapping->end>cluster_num); + return mapping; +} + +/* + * This function simply compares path == mapping->path. Since the mappings + * are sorted by cluster, this is expensive: O(n). + */ +static inline mapping_t* find_mapping_for_path(BDRVVVFATState* s, + const char* path) +{ + int i; + + for (i = 0; i < s->mapping.next; i++) { + mapping_t* mapping = array_get(&(s->mapping), i); + if (mapping->first_mapping_index < 0 && + !strcmp(path, mapping->path)) + return mapping; + } + + return NULL; +} + +static int open_file(BDRVVVFATState* s,mapping_t* mapping) +{ + if(!mapping) + return -1; + if(!s->current_mapping || + strcmp(s->current_mapping->path,mapping->path)) { + /* open file */ + int fd = open(mapping->path, O_RDONLY | O_BINARY | O_LARGEFILE); + if(fd<0) + return -1; + vvfat_close_current_file(s); + s->current_fd = fd; + s->current_mapping = mapping; + } + return 0; +} + +static inline int read_cluster(BDRVVVFATState *s,int cluster_num) +{ + if(s->current_cluster != cluster_num) { + int result=0; + off_t offset; + assert(!s->current_mapping || s->current_fd || (s->current_mapping->mode & MODE_DIRECTORY)); + if(!s->current_mapping + || s->current_mapping->begin>cluster_num + || s->current_mapping->end<=cluster_num) { + /* binary search of mappings for file */ + mapping_t* mapping=find_mapping_for_cluster(s,cluster_num); + + assert(!mapping || (cluster_num>=mapping->begin && cluster_numend)); + + if (mapping && mapping->mode & MODE_DIRECTORY) { + vvfat_close_current_file(s); + s->current_mapping = mapping; +read_cluster_directory: + offset = s->cluster_size*(cluster_num-s->current_mapping->begin); + s->cluster = (unsigned char*)s->directory.pointer+offset + + 0x20*s->current_mapping->info.dir.first_dir_index; + assert(((s->cluster-(unsigned char*)s->directory.pointer)%s->cluster_size)==0); + assert((char*)s->cluster+s->cluster_size <= s->directory.pointer+s->directory.next*s->directory.item_size); + s->current_cluster = cluster_num; + return 0; + } + + if(open_file(s,mapping)) + return -2; + } else if (s->current_mapping->mode & MODE_DIRECTORY) + goto read_cluster_directory; + + assert(s->current_fd); + + offset=s->cluster_size*(cluster_num-s->current_mapping->begin)+s->current_mapping->info.file.offset; + if(lseek(s->current_fd, offset, SEEK_SET)!=offset) + return -3; + s->cluster=s->cluster_buffer; + result=read(s->current_fd,s->cluster,s->cluster_size); + if(result<0) { + s->current_cluster = -1; + return -1; + } + s->current_cluster = cluster_num; + } + return 0; +} + +#ifdef DEBUG +static void hexdump(const void* address, uint32_t len) +{ + const unsigned char* p = address; + int i, j; + + for (i = 0; i < len; i += 16) { + for (j = 0; j < 16 && i + j < len; j++) + fprintf(stderr, "%02x ", p[i + j]); + for (; j < 16; j++) + fprintf(stderr, " "); + fprintf(stderr, " "); + for (j = 0; j < 16 && i + j < len; j++) + fprintf(stderr, "%c", (p[i + j] < ' ' || p[i + j] > 0x7f) ? '.' : p[i + j]); + fprintf(stderr, "\n"); + } +} + +static void print_direntry(const direntry_t* direntry) +{ + int j = 0; + char buffer[1024]; + + fprintf(stderr, "direntry %p: ", direntry); + if(!direntry) + return; + if(is_long_name(direntry)) { + unsigned char* c=(unsigned char*)direntry; + int i; + for(i=1;i<11 && c[i] && c[i]!=0xff;i+=2) +#define ADD_CHAR(c) {buffer[j] = (c); if (buffer[j] < ' ') buffer[j] = 0xb0; j++;} + ADD_CHAR(c[i]); + for(i=14;i<26 && c[i] && c[i]!=0xff;i+=2) + ADD_CHAR(c[i]); + for(i=28;i<32 && c[i] && c[i]!=0xff;i+=2) + ADD_CHAR(c[i]); + buffer[j] = 0; + fprintf(stderr, "%s\n", buffer); + } else { + int i; + for(i=0;i<11;i++) + ADD_CHAR(direntry->name[i]); + buffer[j] = 0; + fprintf(stderr,"%s attributes=0x%02x begin=%d size=%d\n", + buffer, + direntry->attributes, + begin_of_direntry(direntry),le32_to_cpu(direntry->size)); + } +} + +static void print_mapping(const mapping_t* mapping) +{ + fprintf(stderr, "mapping (%p): begin, end = %d, %d, dir_index = %d, " + "first_mapping_index = %d, name = %s, mode = 0x%x, " , + mapping, mapping->begin, mapping->end, mapping->dir_index, + mapping->first_mapping_index, mapping->path, mapping->mode); + + if (mapping->mode & MODE_DIRECTORY) + fprintf(stderr, "parent_mapping_index = %d, first_dir_index = %d\n", mapping->info.dir.parent_mapping_index, mapping->info.dir.first_dir_index); + else + fprintf(stderr, "offset = %d\n", mapping->info.file.offset); +} +#endif + +static int vvfat_read(BlockDriverState *bs, int64_t sector_num, + uint8_t *buf, int nb_sectors) +{ + BDRVVVFATState *s = bs->opaque; + int i; + + for(i=0;i= s->sector_count) + return -1; + if (s->qcow) { + int n; + if (s->qcow->drv->bdrv_is_allocated(s->qcow, + sector_num, nb_sectors-i, &n)) { +DLOG(fprintf(stderr, "sectors %d+%d allocated\n", (int)sector_num, n)); + if (s->qcow->drv->bdrv_read(s->qcow, sector_num, buf+i*0x200, n)) + return -1; + i += n - 1; + sector_num += n - 1; + continue; + } +DLOG(fprintf(stderr, "sector %d not allocated\n", (int)sector_num)); + } + if(sector_numfaked_sectors) { + if(sector_numfirst_sectors_number) + memcpy(buf+i*0x200,&(s->first_sectors[sector_num*0x200]),0x200); + else if(sector_num-s->first_sectors_numbersectors_per_fat) + memcpy(buf+i*0x200,&(s->fat.pointer[(sector_num-s->first_sectors_number)*0x200]),0x200); + else if(sector_num-s->first_sectors_number-s->sectors_per_fatsectors_per_fat) + memcpy(buf+i*0x200,&(s->fat.pointer[(sector_num-s->first_sectors_number-s->sectors_per_fat)*0x200]),0x200); + } else { + uint32_t sector=sector_num-s->faked_sectors, + sector_offset_in_cluster=(sector%s->sectors_per_cluster), + cluster_num=sector/s->sectors_per_cluster; + if(read_cluster(s, cluster_num) != 0) { + /* LATER TODO: strict: return -1; */ + memset(buf+i*0x200,0,0x200); + continue; + } + memcpy(buf+i*0x200,s->cluster+sector_offset_in_cluster*0x200,0x200); + } + } + return 0; +} + +/* LATER TODO: statify all functions */ + +/* + * Idea of the write support (use snapshot): + * + * 1. check if all data is consistent, recording renames, modifications, + * new files and directories (in s->commits). + * + * 2. if the data is not consistent, stop committing + * + * 3. handle renames, and create new files and directories (do not yet + * write their contents) + * + * 4. walk the directories, fixing the mapping and direntries, and marking + * the handled mappings as not deleted + * + * 5. commit the contents of the files + * + * 6. handle deleted files and directories + * + */ + +typedef struct commit_t { + char* path; + union { + struct { uint32_t cluster; } rename; + struct { int dir_index; uint32_t modified_offset; } writeout; + struct { uint32_t first_cluster; } new_file; + struct { uint32_t cluster; } mkdir; + } param; + /* DELETEs and RMDIRs are handled differently: see handle_deletes() */ + enum { + ACTION_RENAME, ACTION_WRITEOUT, ACTION_NEW_FILE, ACTION_MKDIR + } action; +} commit_t; + +static void clear_commits(BDRVVVFATState* s) +{ + int i; +DLOG(fprintf(stderr, "clear_commits (%d commits)\n", s->commits.next)); + for (i = 0; i < s->commits.next; i++) { + commit_t* commit = array_get(&(s->commits), i); + assert(commit->path || commit->action == ACTION_WRITEOUT); + if (commit->action != ACTION_WRITEOUT) { + assert(commit->path); + free(commit->path); + } else + assert(commit->path == NULL); + } + s->commits.next = 0; +} + +static void schedule_rename(BDRVVVFATState* s, + uint32_t cluster, char* new_path) +{ + commit_t* commit = array_get_next(&(s->commits)); + commit->path = new_path; + commit->param.rename.cluster = cluster; + commit->action = ACTION_RENAME; +} + +static void schedule_writeout(BDRVVVFATState* s, + int dir_index, uint32_t modified_offset) +{ + commit_t* commit = array_get_next(&(s->commits)); + commit->path = NULL; + commit->param.writeout.dir_index = dir_index; + commit->param.writeout.modified_offset = modified_offset; + commit->action = ACTION_WRITEOUT; +} + +static void schedule_new_file(BDRVVVFATState* s, + char* path, uint32_t first_cluster) +{ + commit_t* commit = array_get_next(&(s->commits)); + commit->path = path; + commit->param.new_file.first_cluster = first_cluster; + commit->action = ACTION_NEW_FILE; +} + +static void schedule_mkdir(BDRVVVFATState* s, uint32_t cluster, char* path) +{ + commit_t* commit = array_get_next(&(s->commits)); + commit->path = path; + commit->param.mkdir.cluster = cluster; + commit->action = ACTION_MKDIR; +} + +typedef struct { + /* + * Since the sequence number is at most 0x3f, and the filename + * length is at most 13 times the sequence number, the maximal + * filename length is 0x3f * 13 bytes. + */ + unsigned char name[0x3f * 13 + 1]; + int checksum, len; + int sequence_number; +} long_file_name; + +static void lfn_init(long_file_name* lfn) +{ + lfn->sequence_number = lfn->len = 0; + lfn->checksum = 0x100; +} + +/* return 0 if parsed successfully, > 0 if no long name, < 0 if error */ +static int parse_long_name(long_file_name* lfn, + const direntry_t* direntry) +{ + int i, j, offset; + const unsigned char* pointer = (const unsigned char*)direntry; + + if (!is_long_name(direntry)) + return 1; + + if (pointer[0] & 0x40) { + lfn->sequence_number = pointer[0] & 0x3f; + lfn->checksum = pointer[13]; + lfn->name[0] = 0; + lfn->name[lfn->sequence_number * 13] = 0; + } else if ((pointer[0] & 0x3f) != --lfn->sequence_number) + return -1; + else if (pointer[13] != lfn->checksum) + return -2; + else if (pointer[12] || pointer[26] || pointer[27]) + return -3; + + offset = 13 * (lfn->sequence_number - 1); + for (i = 0, j = 1; i < 13; i++, j+=2) { + if (j == 11) + j = 14; + else if (j == 26) + j = 28; + + if (pointer[j+1] == 0) + lfn->name[offset + i] = pointer[j]; + else if (pointer[j+1] != 0xff || (pointer[0] & 0x40) == 0) + return -4; + else + lfn->name[offset + i] = 0; + } + + if (pointer[0] & 0x40) + lfn->len = offset + strlen((char*)lfn->name + offset); + + return 0; +} + +/* returns 0 if successful, >0 if no short_name, and <0 on error */ +static int parse_short_name(BDRVVVFATState* s, + long_file_name* lfn, direntry_t* direntry) +{ + int i, j; + + if (!is_short_name(direntry)) + return 1; + + for (j = 7; j >= 0 && direntry->name[j] == ' '; j--); + for (i = 0; i <= j; i++) { + if (direntry->name[i] <= ' ' || direntry->name[i] > 0x7f) + return -1; + else if (s->downcase_short_names) + lfn->name[i] = qemu_tolower(direntry->name[i]); + else + lfn->name[i] = direntry->name[i]; + } + + for (j = 2; j >= 0 && direntry->extension[j] == ' '; j--); + if (j >= 0) { + lfn->name[i++] = '.'; + lfn->name[i + j + 1] = '\0'; + for (;j >= 0; j--) { + if (direntry->extension[j] <= ' ' || direntry->extension[j] > 0x7f) + return -2; + else if (s->downcase_short_names) + lfn->name[i + j] = qemu_tolower(direntry->extension[j]); + else + lfn->name[i + j] = direntry->extension[j]; + } + } else + lfn->name[i + j + 1] = '\0'; + + lfn->len = strlen((char*)lfn->name); + + return 0; +} + +static inline uint32_t modified_fat_get(BDRVVVFATState* s, + unsigned int cluster) +{ + if (cluster < s->last_cluster_of_root_directory) { + if (cluster + 1 == s->last_cluster_of_root_directory) + return s->max_fat_value; + else + return cluster + 1; + } + + if (s->fat_type==32) { + uint32_t* entry=((uint32_t*)s->fat2)+cluster; + return le32_to_cpu(*entry); + } else if (s->fat_type==16) { + uint16_t* entry=((uint16_t*)s->fat2)+cluster; + return le16_to_cpu(*entry); + } else { + const uint8_t* x=s->fat2+cluster*3/2; + return ((x[0]|(x[1]<<8))>>(cluster&1?4:0))&0x0fff; + } +} + +static inline int cluster_was_modified(BDRVVVFATState* s, uint32_t cluster_num) +{ + int was_modified = 0; + int i, dummy; + + if (s->qcow == NULL) + return 0; + + for (i = 0; !was_modified && i < s->sectors_per_cluster; i++) + was_modified = s->qcow->drv->bdrv_is_allocated(s->qcow, + cluster2sector(s, cluster_num) + i, 1, &dummy); + + return was_modified; +} + +static const char* get_basename(const char* path) +{ + char* basename = strrchr(path, '/'); + if (basename == NULL) + return path; + else + return basename + 1; /* strip '/' */ +} + +/* + * The array s->used_clusters holds the states of the clusters. If it is + * part of a file, it has bit 2 set, in case of a directory, bit 1. If it + * was modified, bit 3 is set. + * If any cluster is allocated, but not part of a file or directory, this + * driver refuses to commit. + */ +typedef enum { + USED_DIRECTORY = 1, USED_FILE = 2, USED_ANY = 3, USED_ALLOCATED = 4 +} used_t; + +/* + * get_cluster_count_for_direntry() not only determines how many clusters + * are occupied by direntry, but also if it was renamed or modified. + * + * A file is thought to be renamed *only* if there already was a file with + * exactly the same first cluster, but a different name. + * + * Further, the files/directories handled by this function are + * assumed to be *not* deleted (and *only* those). + */ +static uint32_t get_cluster_count_for_direntry(BDRVVVFATState* s, + direntry_t* direntry, const char* path) +{ + /* + * This is a little bit tricky: + * IF the guest OS just inserts a cluster into the file chain, + * and leaves the rest alone, (i.e. the original file had clusters + * 15 -> 16, but now has 15 -> 32 -> 16), then the following happens: + * + * - do_commit will write the cluster into the file at the given + * offset, but + * + * - the cluster which is overwritten should be moved to a later + * position in the file. + * + * I am not aware that any OS does something as braindead, but this + * situation could happen anyway when not committing for a long time. + * Just to be sure that this does not bite us, detect it, and copy the + * contents of the clusters to-be-overwritten into the qcow. + */ + int copy_it = 0; + int was_modified = 0; + int32_t ret = 0; + + uint32_t cluster_num = begin_of_direntry(direntry); + uint32_t offset = 0; + int first_mapping_index = -1; + mapping_t* mapping = NULL; + const char* basename2 = NULL; + + vvfat_close_current_file(s); + + /* the root directory */ + if (cluster_num == 0) + return 0; + + /* write support */ + if (s->qcow) { + basename2 = get_basename(path); + + mapping = find_mapping_for_cluster(s, cluster_num); + + if (mapping) { + const char* basename; + + assert(mapping->mode & MODE_DELETED); + mapping->mode &= ~MODE_DELETED; + + basename = get_basename(mapping->path); + + assert(mapping->mode & MODE_NORMAL); + + /* rename */ + if (strcmp(basename, basename2)) + schedule_rename(s, cluster_num, qemu_strdup(path)); + } else if (is_file(direntry)) + /* new file */ + schedule_new_file(s, qemu_strdup(path), cluster_num); + else { + abort(); + return 0; + } + } + + while(1) { + if (s->qcow) { + if (!copy_it && cluster_was_modified(s, cluster_num)) { + if (mapping == NULL || + mapping->begin > cluster_num || + mapping->end <= cluster_num) + mapping = find_mapping_for_cluster(s, cluster_num); + + + if (mapping && + (mapping->mode & MODE_DIRECTORY) == 0) { + + /* was modified in qcow */ + if (offset != mapping->info.file.offset + s->cluster_size + * (cluster_num - mapping->begin)) { + /* offset of this cluster in file chain has changed */ + abort(); + copy_it = 1; + } else if (offset == 0) { + const char* basename = get_basename(mapping->path); + + if (strcmp(basename, basename2)) + copy_it = 1; + first_mapping_index = array_index(&(s->mapping), mapping); + } + + if (mapping->first_mapping_index != first_mapping_index + && mapping->info.file.offset > 0) { + abort(); + copy_it = 1; + } + + /* need to write out? */ + if (!was_modified && is_file(direntry)) { + was_modified = 1; + schedule_writeout(s, mapping->dir_index, offset); + } + } + } + + if (copy_it) { + int i, dummy; + /* + * This is horribly inefficient, but that is okay, since + * it is rarely executed, if at all. + */ + int64_t offset = cluster2sector(s, cluster_num); + + vvfat_close_current_file(s); + for (i = 0; i < s->sectors_per_cluster; i++) + if (!s->qcow->drv->bdrv_is_allocated(s->qcow, + offset + i, 1, &dummy)) { + if (vvfat_read(s->bs, + offset, s->cluster_buffer, 1)) + return -1; + if (s->qcow->drv->bdrv_write(s->qcow, + offset, s->cluster_buffer, 1)) + return -2; + } + } + } + + ret++; + if (s->used_clusters[cluster_num] & USED_ANY) + return 0; + s->used_clusters[cluster_num] = USED_FILE; + + cluster_num = modified_fat_get(s, cluster_num); + + if (fat_eof(s, cluster_num)) + return ret; + else if (cluster_num < 2 || cluster_num > s->max_fat_value - 16) + return -1; + + offset += s->cluster_size; + } +} + +/* + * This function looks at the modified data (qcow). + * It returns 0 upon inconsistency or error, and the number of clusters + * used by the directory, its subdirectories and their files. + */ +static int check_directory_consistency(BDRVVVFATState *s, + int cluster_num, const char* path) +{ + int ret = 0; + unsigned char* cluster = qemu_malloc(s->cluster_size); + direntry_t* direntries = (direntry_t*)cluster; + mapping_t* mapping = find_mapping_for_cluster(s, cluster_num); + + long_file_name lfn; + int path_len = strlen(path); + char path2[PATH_MAX]; + + assert(path_len < PATH_MAX); /* len was tested before! */ + pstrcpy(path2, sizeof(path2), path); + path2[path_len] = '/'; + path2[path_len + 1] = '\0'; + + if (mapping) { + const char* basename = get_basename(mapping->path); + const char* basename2 = get_basename(path); + + assert(mapping->mode & MODE_DIRECTORY); + + assert(mapping->mode & MODE_DELETED); + mapping->mode &= ~MODE_DELETED; + + if (strcmp(basename, basename2)) + schedule_rename(s, cluster_num, qemu_strdup(path)); + } else + /* new directory */ + schedule_mkdir(s, cluster_num, qemu_strdup(path)); + + lfn_init(&lfn); + do { + int i; + int subret = 0; + + ret++; + + if (s->used_clusters[cluster_num] & USED_ANY) { + fprintf(stderr, "cluster %d used more than once\n", (int)cluster_num); + return 0; + } + s->used_clusters[cluster_num] = USED_DIRECTORY; + +DLOG(fprintf(stderr, "read cluster %d (sector %d)\n", (int)cluster_num, (int)cluster2sector(s, cluster_num))); + subret = vvfat_read(s->bs, cluster2sector(s, cluster_num), cluster, + s->sectors_per_cluster); + if (subret) { + fprintf(stderr, "Error fetching direntries\n"); + fail: + free(cluster); + return 0; + } + + for (i = 0; i < 0x10 * s->sectors_per_cluster; i++) { + int cluster_count = 0; + +DLOG(fprintf(stderr, "check direntry %d: \n", i); print_direntry(direntries + i)); + if (is_volume_label(direntries + i) || is_dot(direntries + i) || + is_free(direntries + i)) + continue; + + subret = parse_long_name(&lfn, direntries + i); + if (subret < 0) { + fprintf(stderr, "Error in long name\n"); + goto fail; + } + if (subret == 0 || is_free(direntries + i)) + continue; + + if (fat_chksum(direntries+i) != lfn.checksum) { + subret = parse_short_name(s, &lfn, direntries + i); + if (subret < 0) { + fprintf(stderr, "Error in short name (%d)\n", subret); + goto fail; + } + if (subret > 0 || !strcmp((char*)lfn.name, ".") + || !strcmp((char*)lfn.name, "..")) + continue; + } + lfn.checksum = 0x100; /* cannot use long name twice */ + + if (path_len + 1 + lfn.len >= PATH_MAX) { + fprintf(stderr, "Name too long: %s/%s\n", path, lfn.name); + goto fail; + } + pstrcpy(path2 + path_len + 1, sizeof(path2) - path_len - 1, + (char*)lfn.name); + + if (is_directory(direntries + i)) { + if (begin_of_direntry(direntries + i) == 0) { + DLOG(fprintf(stderr, "invalid begin for directory: %s\n", path2); print_direntry(direntries + i)); + goto fail; + } + cluster_count = check_directory_consistency(s, + begin_of_direntry(direntries + i), path2); + if (cluster_count == 0) { + DLOG(fprintf(stderr, "problem in directory %s:\n", path2); print_direntry(direntries + i)); + goto fail; + } + } else if (is_file(direntries + i)) { + /* check file size with FAT */ + cluster_count = get_cluster_count_for_direntry(s, direntries + i, path2); + if (cluster_count != + (le32_to_cpu(direntries[i].size) + s->cluster_size + - 1) / s->cluster_size) { + DLOG(fprintf(stderr, "Cluster count mismatch\n")); + goto fail; + } + } else + abort(); /* cluster_count = 0; */ + + ret += cluster_count; + } + + cluster_num = modified_fat_get(s, cluster_num); + } while(!fat_eof(s, cluster_num)); + + free(cluster); + return ret; +} + +/* returns 1 on success */ +static int is_consistent(BDRVVVFATState* s) +{ + int i, check; + int used_clusters_count = 0; + +DLOG(checkpoint()); + /* + * - get modified FAT + * - compare the two FATs (TODO) + * - get buffer for marking used clusters + * - recurse direntries from root (using bs->bdrv_read to make + * sure to get the new data) + * - check that the FAT agrees with the size + * - count the number of clusters occupied by this directory and + * its files + * - check that the cumulative used cluster count agrees with the + * FAT + * - if all is fine, return number of used clusters + */ + if (s->fat2 == NULL) { + int size = 0x200 * s->sectors_per_fat; + s->fat2 = qemu_malloc(size); + memcpy(s->fat2, s->fat.pointer, size); + } + check = vvfat_read(s->bs, + s->first_sectors_number, s->fat2, s->sectors_per_fat); + if (check) { + fprintf(stderr, "Could not copy fat\n"); + return 0; + } + assert (s->used_clusters); + for (i = 0; i < sector2cluster(s, s->sector_count); i++) + s->used_clusters[i] &= ~USED_ANY; + + clear_commits(s); + + /* mark every mapped file/directory as deleted. + * (check_directory_consistency() will unmark those still present). */ + if (s->qcow) + for (i = 0; i < s->mapping.next; i++) { + mapping_t* mapping = array_get(&(s->mapping), i); + if (mapping->first_mapping_index < 0) + mapping->mode |= MODE_DELETED; + } + + used_clusters_count = check_directory_consistency(s, 0, s->path); + if (used_clusters_count <= 0) { + DLOG(fprintf(stderr, "problem in directory\n")); + return 0; + } + + check = s->last_cluster_of_root_directory; + for (i = check; i < sector2cluster(s, s->sector_count); i++) { + if (modified_fat_get(s, i)) { + if(!s->used_clusters[i]) { + DLOG(fprintf(stderr, "FAT was modified (%d), but cluster is not used?\n", i)); + return 0; + } + check++; + } + + if (s->used_clusters[i] == USED_ALLOCATED) { + /* allocated, but not used... */ + DLOG(fprintf(stderr, "unused, modified cluster: %d\n", i)); + return 0; + } + } + + if (check != used_clusters_count) + return 0; + + return used_clusters_count; +} + +static inline void adjust_mapping_indices(BDRVVVFATState* s, + int offset, int adjust) +{ + int i; + + for (i = 0; i < s->mapping.next; i++) { + mapping_t* mapping = array_get(&(s->mapping), i); + +#define ADJUST_MAPPING_INDEX(name) \ + if (mapping->name >= offset) \ + mapping->name += adjust + + ADJUST_MAPPING_INDEX(first_mapping_index); + if (mapping->mode & MODE_DIRECTORY) + ADJUST_MAPPING_INDEX(info.dir.parent_mapping_index); + } +} + +/* insert or update mapping */ +static mapping_t* insert_mapping(BDRVVVFATState* s, + uint32_t begin, uint32_t end) +{ + /* + * - find mapping where mapping->begin >= begin, + * - if mapping->begin > begin: insert + * - adjust all references to mappings! + * - else: adjust + * - replace name + */ + int index = find_mapping_for_cluster_aux(s, begin, 0, s->mapping.next); + mapping_t* mapping = NULL; + mapping_t* first_mapping = array_get(&(s->mapping), 0); + + if (index < s->mapping.next && (mapping = array_get(&(s->mapping), index)) + && mapping->begin < begin) { + mapping->end = begin; + index++; + mapping = array_get(&(s->mapping), index); + } + if (index >= s->mapping.next || mapping->begin > begin) { + mapping = array_insert(&(s->mapping), index, 1); + mapping->path = NULL; + adjust_mapping_indices(s, index, +1); + } + + mapping->begin = begin; + mapping->end = end; + +DLOG(mapping_t* next_mapping; +assert(index + 1 >= s->mapping.next || +((next_mapping = array_get(&(s->mapping), index + 1)) && + next_mapping->begin >= end))); + + if (s->current_mapping && first_mapping != (mapping_t*)s->mapping.pointer) + s->current_mapping = array_get(&(s->mapping), + s->current_mapping - first_mapping); + + return mapping; +} + +static int remove_mapping(BDRVVVFATState* s, int mapping_index) +{ + mapping_t* mapping = array_get(&(s->mapping), mapping_index); + mapping_t* first_mapping = array_get(&(s->mapping), 0); + + /* free mapping */ + if (mapping->first_mapping_index < 0) + free(mapping->path); + + /* remove from s->mapping */ + array_remove(&(s->mapping), mapping_index); + + /* adjust all references to mappings */ + adjust_mapping_indices(s, mapping_index, -1); + + if (s->current_mapping && first_mapping != (mapping_t*)s->mapping.pointer) + s->current_mapping = array_get(&(s->mapping), + s->current_mapping - first_mapping); + + return 0; +} + +static void adjust_dirindices(BDRVVVFATState* s, int offset, int adjust) +{ + int i; + for (i = 0; i < s->mapping.next; i++) { + mapping_t* mapping = array_get(&(s->mapping), i); + if (mapping->dir_index >= offset) + mapping->dir_index += adjust; + if ((mapping->mode & MODE_DIRECTORY) && + mapping->info.dir.first_dir_index >= offset) + mapping->info.dir.first_dir_index += adjust; + } +} + +static direntry_t* insert_direntries(BDRVVVFATState* s, + int dir_index, int count) +{ + /* + * make room in s->directory, + * adjust_dirindices + */ + direntry_t* result = array_insert(&(s->directory), dir_index, count); + if (result == NULL) + return NULL; + adjust_dirindices(s, dir_index, count); + return result; +} + +static int remove_direntries(BDRVVVFATState* s, int dir_index, int count) +{ + int ret = array_remove_slice(&(s->directory), dir_index, count); + if (ret) + return ret; + adjust_dirindices(s, dir_index, -count); + return 0; +} + +/* + * Adapt the mappings of the cluster chain starting at first cluster + * (i.e. if a file starts at first_cluster, the chain is followed according + * to the modified fat, and the corresponding entries in s->mapping are + * adjusted) + */ +static int commit_mappings(BDRVVVFATState* s, + uint32_t first_cluster, int dir_index) +{ + mapping_t* mapping = find_mapping_for_cluster(s, first_cluster); + direntry_t* direntry = array_get(&(s->directory), dir_index); + uint32_t cluster = first_cluster; + + vvfat_close_current_file(s); + + assert(mapping); + assert(mapping->begin == first_cluster); + mapping->first_mapping_index = -1; + mapping->dir_index = dir_index; + mapping->mode = (dir_index <= 0 || is_directory(direntry)) ? + MODE_DIRECTORY : MODE_NORMAL; + + while (!fat_eof(s, cluster)) { + uint32_t c, c1; + + for (c = cluster, c1 = modified_fat_get(s, c); c + 1 == c1; + c = c1, c1 = modified_fat_get(s, c1)); + + c++; + if (c > mapping->end) { + int index = array_index(&(s->mapping), mapping); + int i, max_i = s->mapping.next - index; + for (i = 1; i < max_i && mapping[i].begin < c; i++); + while (--i > 0) + remove_mapping(s, index + 1); + } + assert(mapping == array_get(&(s->mapping), s->mapping.next - 1) + || mapping[1].begin >= c); + mapping->end = c; + + if (!fat_eof(s, c1)) { + int i = find_mapping_for_cluster_aux(s, c1, 0, s->mapping.next); + mapping_t* next_mapping = i >= s->mapping.next ? NULL : + array_get(&(s->mapping), i); + + if (next_mapping == NULL || next_mapping->begin > c1) { + int i1 = array_index(&(s->mapping), mapping); + + next_mapping = insert_mapping(s, c1, c1+1); + + if (c1 < c) + i1++; + mapping = array_get(&(s->mapping), i1); + } + + next_mapping->dir_index = mapping->dir_index; + next_mapping->first_mapping_index = + mapping->first_mapping_index < 0 ? + array_index(&(s->mapping), mapping) : + mapping->first_mapping_index; + next_mapping->path = mapping->path; + next_mapping->mode = mapping->mode; + next_mapping->read_only = mapping->read_only; + if (mapping->mode & MODE_DIRECTORY) { + next_mapping->info.dir.parent_mapping_index = + mapping->info.dir.parent_mapping_index; + next_mapping->info.dir.first_dir_index = + mapping->info.dir.first_dir_index + + 0x10 * s->sectors_per_cluster * + (mapping->end - mapping->begin); + } else + next_mapping->info.file.offset = mapping->info.file.offset + + mapping->end - mapping->begin; + + mapping = next_mapping; + } + + cluster = c1; + } + + return 0; +} + +static int commit_direntries(BDRVVVFATState* s, + int dir_index, int parent_mapping_index) +{ + direntry_t* direntry = array_get(&(s->directory), dir_index); + uint32_t first_cluster = dir_index == 0 ? 0 : begin_of_direntry(direntry); + mapping_t* mapping = find_mapping_for_cluster(s, first_cluster); + + int factor = 0x10 * s->sectors_per_cluster; + int old_cluster_count, new_cluster_count; + int current_dir_index = mapping->info.dir.first_dir_index; + int first_dir_index = current_dir_index; + int ret, i; + uint32_t c; + +DLOG(fprintf(stderr, "commit_direntries for %s, parent_mapping_index %d\n", mapping->path, parent_mapping_index)); + + assert(direntry); + assert(mapping); + assert(mapping->begin == first_cluster); + assert(mapping->info.dir.first_dir_index < s->directory.next); + assert(mapping->mode & MODE_DIRECTORY); + assert(dir_index == 0 || is_directory(direntry)); + + mapping->info.dir.parent_mapping_index = parent_mapping_index; + + if (first_cluster == 0) { + old_cluster_count = new_cluster_count = + s->last_cluster_of_root_directory; + } else { + for (old_cluster_count = 0, c = first_cluster; !fat_eof(s, c); + c = fat_get(s, c)) + old_cluster_count++; + + for (new_cluster_count = 0, c = first_cluster; !fat_eof(s, c); + c = modified_fat_get(s, c)) + new_cluster_count++; + } + + if (new_cluster_count > old_cluster_count) { + if (insert_direntries(s, + current_dir_index + factor * old_cluster_count, + factor * (new_cluster_count - old_cluster_count)) == NULL) + return -1; + } else if (new_cluster_count < old_cluster_count) + remove_direntries(s, + current_dir_index + factor * new_cluster_count, + factor * (old_cluster_count - new_cluster_count)); + + for (c = first_cluster; !fat_eof(s, c); c = modified_fat_get(s, c)) { + void* direntry = array_get(&(s->directory), current_dir_index); + int ret = vvfat_read(s->bs, cluster2sector(s, c), direntry, + s->sectors_per_cluster); + if (ret) + return ret; + assert(!strncmp(s->directory.pointer, "QEMU", 4)); + current_dir_index += factor; + } + + ret = commit_mappings(s, first_cluster, dir_index); + if (ret) + return ret; + + /* recurse */ + for (i = 0; i < factor * new_cluster_count; i++) { + direntry = array_get(&(s->directory), first_dir_index + i); + if (is_directory(direntry) && !is_dot(direntry)) { + mapping = find_mapping_for_cluster(s, first_cluster); + assert(mapping->mode & MODE_DIRECTORY); + ret = commit_direntries(s, first_dir_index + i, + array_index(&(s->mapping), mapping)); + if (ret) + return ret; + } + } + + return 0; +} + +/* commit one file (adjust contents, adjust mapping), + return first_mapping_index */ +static int commit_one_file(BDRVVVFATState* s, + int dir_index, uint32_t offset) +{ + direntry_t* direntry = array_get(&(s->directory), dir_index); + uint32_t c = begin_of_direntry(direntry); + uint32_t first_cluster = c; + mapping_t* mapping = find_mapping_for_cluster(s, c); + uint32_t size = filesize_of_direntry(direntry); + char* cluster = qemu_malloc(s->cluster_size); + uint32_t i; + int fd = 0; + + assert(offset < size); + assert((offset % s->cluster_size) == 0); + + for (i = s->cluster_size; i < offset; i += s->cluster_size) + c = modified_fat_get(s, c); + + fd = open(mapping->path, O_RDWR | O_CREAT | O_BINARY, 0666); + if (fd < 0) { + fprintf(stderr, "Could not open %s... (%s, %d)\n", mapping->path, + strerror(errno), errno); + return fd; + } + if (offset > 0) + if (lseek(fd, offset, SEEK_SET) != offset) + return -3; + + while (offset < size) { + uint32_t c1; + int rest_size = (size - offset > s->cluster_size ? + s->cluster_size : size - offset); + int ret; + + c1 = modified_fat_get(s, c); + + assert((size - offset == 0 && fat_eof(s, c)) || + (size > offset && c >=2 && !fat_eof(s, c))); + + ret = vvfat_read(s->bs, cluster2sector(s, c), + (uint8_t*)cluster, (rest_size + 0x1ff) / 0x200); + + if (ret < 0) + return ret; + + if (write(fd, cluster, rest_size) < 0) + return -2; + + offset += rest_size; + c = c1; + } + + if (ftruncate(fd, size)) { + perror("ftruncate()"); + close(fd); + return -4; + } + close(fd); + + return commit_mappings(s, first_cluster, dir_index); +} + +#ifdef DEBUG +/* test, if all mappings point to valid direntries */ +static void check1(BDRVVVFATState* s) +{ + int i; + for (i = 0; i < s->mapping.next; i++) { + mapping_t* mapping = array_get(&(s->mapping), i); + if (mapping->mode & MODE_DELETED) { + fprintf(stderr, "deleted\n"); + continue; + } + assert(mapping->dir_index < s->directory.next); + direntry_t* direntry = array_get(&(s->directory), mapping->dir_index); + assert(mapping->begin == begin_of_direntry(direntry) || mapping->first_mapping_index >= 0); + if (mapping->mode & MODE_DIRECTORY) { + assert(mapping->info.dir.first_dir_index + 0x10 * s->sectors_per_cluster * (mapping->end - mapping->begin) <= s->directory.next); + assert((mapping->info.dir.first_dir_index % (0x10 * s->sectors_per_cluster)) == 0); + } + } +} + +/* test, if all direntries have mappings */ +static void check2(BDRVVVFATState* s) +{ + int i; + int first_mapping = -1; + + for (i = 0; i < s->directory.next; i++) { + direntry_t* direntry = array_get(&(s->directory), i); + + if (is_short_name(direntry) && begin_of_direntry(direntry)) { + mapping_t* mapping = find_mapping_for_cluster(s, begin_of_direntry(direntry)); + assert(mapping); + assert(mapping->dir_index == i || is_dot(direntry)); + assert(mapping->begin == begin_of_direntry(direntry) || is_dot(direntry)); + } + + if ((i % (0x10 * s->sectors_per_cluster)) == 0) { + /* cluster start */ + int j, count = 0; + + for (j = 0; j < s->mapping.next; j++) { + mapping_t* mapping = array_get(&(s->mapping), j); + if (mapping->mode & MODE_DELETED) + continue; + if (mapping->mode & MODE_DIRECTORY) { + if (mapping->info.dir.first_dir_index <= i && mapping->info.dir.first_dir_index + 0x10 * s->sectors_per_cluster > i) { + assert(++count == 1); + if (mapping->first_mapping_index == -1) + first_mapping = array_index(&(s->mapping), mapping); + else + assert(first_mapping == mapping->first_mapping_index); + if (mapping->info.dir.parent_mapping_index < 0) + assert(j == 0); + else { + mapping_t* parent = array_get(&(s->mapping), mapping->info.dir.parent_mapping_index); + assert(parent->mode & MODE_DIRECTORY); + assert(parent->info.dir.first_dir_index < mapping->info.dir.first_dir_index); + } + } + } + } + if (count == 0) + first_mapping = -1; + } + } +} +#endif + +static int handle_renames_and_mkdirs(BDRVVVFATState* s) +{ + int i; + +#ifdef DEBUG + fprintf(stderr, "handle_renames\n"); + for (i = 0; i < s->commits.next; i++) { + commit_t* commit = array_get(&(s->commits), i); + fprintf(stderr, "%d, %s (%d, %d)\n", i, commit->path ? commit->path : "(null)", commit->param.rename.cluster, commit->action); + } +#endif + + for (i = 0; i < s->commits.next;) { + commit_t* commit = array_get(&(s->commits), i); + if (commit->action == ACTION_RENAME) { + mapping_t* mapping = find_mapping_for_cluster(s, + commit->param.rename.cluster); + char* old_path = mapping->path; + + assert(commit->path); + mapping->path = commit->path; + if (rename(old_path, mapping->path)) + return -2; + + if (mapping->mode & MODE_DIRECTORY) { + int l1 = strlen(mapping->path); + int l2 = strlen(old_path); + int diff = l1 - l2; + direntry_t* direntry = array_get(&(s->directory), + mapping->info.dir.first_dir_index); + uint32_t c = mapping->begin; + int i = 0; + + /* recurse */ + while (!fat_eof(s, c)) { + do { + direntry_t* d = direntry + i; + + if (is_file(d) || (is_directory(d) && !is_dot(d))) { + mapping_t* m = find_mapping_for_cluster(s, + begin_of_direntry(d)); + int l = strlen(m->path); + char* new_path = qemu_malloc(l + diff + 1); + + assert(!strncmp(m->path, mapping->path, l2)); + + pstrcpy(new_path, l + diff + 1, mapping->path); + pstrcpy(new_path + l1, l + diff + 1 - l1, + m->path + l2); + + schedule_rename(s, m->begin, new_path); + } + i++; + } while((i % (0x10 * s->sectors_per_cluster)) != 0); + c = fat_get(s, c); + } + } + + free(old_path); + array_remove(&(s->commits), i); + continue; + } else if (commit->action == ACTION_MKDIR) { + mapping_t* mapping; + int j, parent_path_len; + +#ifdef __MINGW32__ + if (mkdir(commit->path)) + return -5; +#else + if (mkdir(commit->path, 0755)) + return -5; +#endif + + mapping = insert_mapping(s, commit->param.mkdir.cluster, + commit->param.mkdir.cluster + 1); + if (mapping == NULL) + return -6; + + mapping->mode = MODE_DIRECTORY; + mapping->read_only = 0; + mapping->path = commit->path; + j = s->directory.next; + assert(j); + insert_direntries(s, s->directory.next, + 0x10 * s->sectors_per_cluster); + mapping->info.dir.first_dir_index = j; + + parent_path_len = strlen(commit->path) + - strlen(get_basename(commit->path)) - 1; + for (j = 0; j < s->mapping.next; j++) { + mapping_t* m = array_get(&(s->mapping), j); + if (m->first_mapping_index < 0 && m != mapping && + !strncmp(m->path, mapping->path, parent_path_len) && + strlen(m->path) == parent_path_len) + break; + } + assert(j < s->mapping.next); + mapping->info.dir.parent_mapping_index = j; + + array_remove(&(s->commits), i); + continue; + } + + i++; + } + return 0; +} + +/* + * TODO: make sure that the short name is not matching *another* file + */ +static int handle_commits(BDRVVVFATState* s) +{ + int i, fail = 0; + + vvfat_close_current_file(s); + + for (i = 0; !fail && i < s->commits.next; i++) { + commit_t* commit = array_get(&(s->commits), i); + switch(commit->action) { + case ACTION_RENAME: case ACTION_MKDIR: + abort(); + fail = -2; + break; + case ACTION_WRITEOUT: { +#ifndef NDEBUG + /* these variables are only used by assert() below */ + direntry_t* entry = array_get(&(s->directory), + commit->param.writeout.dir_index); + uint32_t begin = begin_of_direntry(entry); + mapping_t* mapping = find_mapping_for_cluster(s, begin); +#endif + + assert(mapping); + assert(mapping->begin == begin); + assert(commit->path == NULL); + + if (commit_one_file(s, commit->param.writeout.dir_index, + commit->param.writeout.modified_offset)) + fail = -3; + + break; + } + case ACTION_NEW_FILE: { + int begin = commit->param.new_file.first_cluster; + mapping_t* mapping = find_mapping_for_cluster(s, begin); + direntry_t* entry; + int i; + + /* find direntry */ + for (i = 0; i < s->directory.next; i++) { + entry = array_get(&(s->directory), i); + if (is_file(entry) && begin_of_direntry(entry) == begin) + break; + } + + if (i >= s->directory.next) { + fail = -6; + continue; + } + + /* make sure there exists an initial mapping */ + if (mapping && mapping->begin != begin) { + mapping->end = begin; + mapping = NULL; + } + if (mapping == NULL) { + mapping = insert_mapping(s, begin, begin+1); + } + /* most members will be fixed in commit_mappings() */ + assert(commit->path); + mapping->path = commit->path; + mapping->read_only = 0; + mapping->mode = MODE_NORMAL; + mapping->info.file.offset = 0; + + if (commit_one_file(s, i, 0)) + fail = -7; + + break; + } + default: + abort(); + } + } + if (i > 0 && array_remove_slice(&(s->commits), 0, i)) + return -1; + return fail; +} + +static int handle_deletes(BDRVVVFATState* s) +{ + int i, deferred = 1, deleted = 1; + + /* delete files corresponding to mappings marked as deleted */ + /* handle DELETEs and unused mappings (modified_fat_get(s, mapping->begin) == 0) */ + while (deferred && deleted) { + deferred = 0; + deleted = 0; + + for (i = 1; i < s->mapping.next; i++) { + mapping_t* mapping = array_get(&(s->mapping), i); + if (mapping->mode & MODE_DELETED) { + direntry_t* entry = array_get(&(s->directory), + mapping->dir_index); + + if (is_free(entry)) { + /* remove file/directory */ + if (mapping->mode & MODE_DIRECTORY) { + int j, next_dir_index = s->directory.next, + first_dir_index = mapping->info.dir.first_dir_index; + + if (rmdir(mapping->path) < 0) { + if (errno == ENOTEMPTY) { + deferred++; + continue; + } else + return -5; + } + + for (j = 1; j < s->mapping.next; j++) { + mapping_t* m = array_get(&(s->mapping), j); + if (m->mode & MODE_DIRECTORY && + m->info.dir.first_dir_index > + first_dir_index && + m->info.dir.first_dir_index < + next_dir_index) + next_dir_index = + m->info.dir.first_dir_index; + } + remove_direntries(s, first_dir_index, + next_dir_index - first_dir_index); + + deleted++; + } + } else { + if (unlink(mapping->path)) + return -4; + deleted++; + } + DLOG(fprintf(stderr, "DELETE (%d)\n", i); print_mapping(mapping); print_direntry(entry)); + remove_mapping(s, i); + } + } + } + + return 0; +} + +/* + * synchronize mapping with new state: + * + * - copy FAT (with bdrv_read) + * - mark all filenames corresponding to mappings as deleted + * - recurse direntries from root (using bs->bdrv_read) + * - delete files corresponding to mappings marked as deleted + */ +static int do_commit(BDRVVVFATState* s) +{ + int ret = 0; + + /* the real meat are the commits. Nothing to do? Move along! */ + if (s->commits.next == 0) + return 0; + + vvfat_close_current_file(s); + + ret = handle_renames_and_mkdirs(s); + if (ret) { + fprintf(stderr, "Error handling renames (%d)\n", ret); + abort(); + return ret; + } + + /* copy FAT (with bdrv_read) */ + memcpy(s->fat.pointer, s->fat2, 0x200 * s->sectors_per_fat); + + /* recurse direntries from root (using bs->bdrv_read) */ + ret = commit_direntries(s, 0, -1); + if (ret) { + fprintf(stderr, "Fatal: error while committing (%d)\n", ret); + abort(); + return ret; + } + + ret = handle_commits(s); + if (ret) { + fprintf(stderr, "Error handling commits (%d)\n", ret); + abort(); + return ret; + } + + ret = handle_deletes(s); + if (ret) { + fprintf(stderr, "Error deleting\n"); + abort(); + return ret; + } + + s->qcow->drv->bdrv_make_empty(s->qcow); + + memset(s->used_clusters, 0, sector2cluster(s, s->sector_count)); + +DLOG(checkpoint()); + return 0; +} + +static int try_commit(BDRVVVFATState* s) +{ + vvfat_close_current_file(s); +DLOG(checkpoint()); + if(!is_consistent(s)) + return -1; + return do_commit(s); +} + +static int vvfat_write(BlockDriverState *bs, int64_t sector_num, + const uint8_t *buf, int nb_sectors) +{ + BDRVVVFATState *s = bs->opaque; + int i, ret; + +DLOG(checkpoint()); + + /* Check if we're operating in read-only mode */ + if (s->qcow == NULL) { + return -EACCES; + } + + vvfat_close_current_file(s); + + /* + * Some sanity checks: + * - do not allow writing to the boot sector + * - do not allow to write non-ASCII filenames + */ + + if (sector_num < s->first_sectors_number) + return -1; + + for (i = sector2cluster(s, sector_num); + i <= sector2cluster(s, sector_num + nb_sectors - 1);) { + mapping_t* mapping = find_mapping_for_cluster(s, i); + if (mapping) { + if (mapping->read_only) { + fprintf(stderr, "Tried to write to write-protected file %s\n", + mapping->path); + return -1; + } + + if (mapping->mode & MODE_DIRECTORY) { + int begin = cluster2sector(s, i); + int end = begin + s->sectors_per_cluster, k; + int dir_index; + const direntry_t* direntries; + long_file_name lfn; + + lfn_init(&lfn); + + if (begin < sector_num) + begin = sector_num; + if (end > sector_num + nb_sectors) + end = sector_num + nb_sectors; + dir_index = mapping->dir_index + + 0x10 * (begin - mapping->begin * s->sectors_per_cluster); + direntries = (direntry_t*)(buf + 0x200 * (begin - sector_num)); + + for (k = 0; k < (end - begin) * 0x10; k++) { + /* do not allow non-ASCII filenames */ + if (parse_long_name(&lfn, direntries + k) < 0) { + fprintf(stderr, "Warning: non-ASCII filename\n"); + return -1; + } + /* no access to the direntry of a read-only file */ + else if (is_short_name(direntries+k) && + (direntries[k].attributes & 1)) { + if (memcmp(direntries + k, + array_get(&(s->directory), dir_index + k), + sizeof(direntry_t))) { + fprintf(stderr, "Warning: tried to write to write-protected file\n"); + return -1; + } + } + } + } + i = mapping->end; + } else + i++; + } + + /* + * Use qcow backend. Commit later. + */ +DLOG(fprintf(stderr, "Write to qcow backend: %d + %d\n", (int)sector_num, nb_sectors)); + ret = s->qcow->drv->bdrv_write(s->qcow, sector_num, buf, nb_sectors); + if (ret < 0) { + fprintf(stderr, "Error writing to qcow backend\n"); + return ret; + } + + for (i = sector2cluster(s, sector_num); + i <= sector2cluster(s, sector_num + nb_sectors - 1); i++) + if (i >= 0) + s->used_clusters[i] |= USED_ALLOCATED; + +DLOG(checkpoint()); + /* TODO: add timeout */ + try_commit(s); + +DLOG(checkpoint()); + return 0; +} + +static int vvfat_is_allocated(BlockDriverState *bs, + int64_t sector_num, int nb_sectors, int* n) +{ + BDRVVVFATState* s = bs->opaque; + *n = s->sector_count - sector_num; + if (*n > nb_sectors) + *n = nb_sectors; + else if (*n < 0) + return 0; + return 1; +} + +static int write_target_commit(BlockDriverState *bs, int64_t sector_num, + const uint8_t* buffer, int nb_sectors) { + BDRVVVFATState* s = *((BDRVVVFATState**) bs->opaque); + return try_commit(s); +} + +static void write_target_close(BlockDriverState *bs) { + BDRVVVFATState* s = *((BDRVVVFATState**) bs->opaque); + bdrv_delete(s->qcow); + free(s->qcow_filename); +} + +static BlockDriver vvfat_write_target = { + .format_name = "vvfat_write_target", + .bdrv_write = write_target_commit, + .bdrv_close = write_target_close, +}; + +static int enable_write_target(BDRVVVFATState *s) +{ + BlockDriver *bdrv_qcow; + QEMUOptionParameter *options; + int ret; + int size = sector2cluster(s, s->sector_count); + s->used_clusters = calloc(size, 1); + + array_init(&(s->commits), sizeof(commit_t)); + + s->qcow_filename = qemu_malloc(1024); + get_tmp_filename(s->qcow_filename, 1024); + + bdrv_qcow = bdrv_find_format("qcow"); + options = parse_option_parameters("", bdrv_qcow->create_options, NULL); + set_option_parameter_int(options, BLOCK_OPT_SIZE, s->sector_count * 512); + set_option_parameter(options, BLOCK_OPT_BACKING_FILE, "fat:"); + + if (bdrv_create(bdrv_qcow, s->qcow_filename, options) < 0) + return -1; + + s->qcow = bdrv_new(""); + if (s->qcow == NULL) { + return -1; + } + + ret = bdrv_open(s->qcow, s->qcow_filename, + BDRV_O_RDWR | BDRV_O_CACHE_WB | BDRV_O_NO_FLUSH, bdrv_qcow); + if (ret < 0) { + return ret; + } + +#ifndef _WIN32 + unlink(s->qcow_filename); +#endif + + s->bs->backing_hd = calloc(sizeof(BlockDriverState), 1); + s->bs->backing_hd->drv = &vvfat_write_target; + s->bs->backing_hd->opaque = qemu_malloc(sizeof(void*)); + *(void**)s->bs->backing_hd->opaque = s; + + return 0; +} + +static void vvfat_close(BlockDriverState *bs) +{ + BDRVVVFATState *s = bs->opaque; + + vvfat_close_current_file(s); + array_free(&(s->fat)); + array_free(&(s->directory)); + array_free(&(s->mapping)); + if(s->cluster_buffer) + free(s->cluster_buffer); +} + +static BlockDriver bdrv_vvfat = { + .format_name = "vvfat", + .instance_size = sizeof(BDRVVVFATState), + .bdrv_file_open = vvfat_open, + .bdrv_read = vvfat_read, + .bdrv_write = vvfat_write, + .bdrv_close = vvfat_close, + .bdrv_is_allocated = vvfat_is_allocated, + .protocol_name = "fat", +}; + +static void bdrv_vvfat_init(void) +{ + bdrv_register(&bdrv_vvfat); +} + +block_init(bdrv_vvfat_init); + +#ifdef DEBUG +static void checkpoint(void) { + assert(((mapping_t*)array_get(&(vvv->mapping), 0))->end == 2); + check1(vvv); + check2(vvv); + assert(!vvv->current_mapping || vvv->current_fd || (vvv->current_mapping->mode & MODE_DIRECTORY)); +#if 0 + if (((direntry_t*)vvv->directory.pointer)[1].attributes != 0xf) + fprintf(stderr, "Nonono!\n"); + mapping_t* mapping; + direntry_t* direntry; + assert(vvv->mapping.size >= vvv->mapping.item_size * vvv->mapping.next); + assert(vvv->directory.size >= vvv->directory.item_size * vvv->directory.next); + if (vvv->mapping.next<47) + return; + assert((mapping = array_get(&(vvv->mapping), 47))); + assert(mapping->dir_index < vvv->directory.next); + direntry = array_get(&(vvv->directory), mapping->dir_index); + assert(!memcmp(direntry->name, "USB H ", 11) || direntry->name[0]==0); +#endif + return; + /* avoid compiler warnings: */ + hexdump(NULL, 100); + remove_mapping(vvv, 0); + print_mapping(NULL); + print_direntry(NULL); +} +#endif diff --git a/block_int.h b/block_int.h new file mode 100644 index 0000000..545ad11 --- /dev/null +++ b/block_int.h @@ -0,0 +1,265 @@ +/* + * QEMU System Emulator block driver + * + * 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. + */ +#ifndef BLOCK_INT_H +#define BLOCK_INT_H + +#include "block.h" +#include "qemu-option.h" +#include "qemu-queue.h" + +#define BLOCK_FLAG_ENCRYPT 1 +#define BLOCK_FLAG_COMPAT6 4 + +#define BLOCK_OPT_SIZE "size" +#define BLOCK_OPT_ENCRYPT "encryption" +#define BLOCK_OPT_COMPAT6 "compat6" +#define BLOCK_OPT_BACKING_FILE "backing_file" +#define BLOCK_OPT_BACKING_FMT "backing_fmt" +#define BLOCK_OPT_CLUSTER_SIZE "cluster_size" +#define BLOCK_OPT_TABLE_SIZE "table_size" +#define BLOCK_OPT_PREALLOC "preallocation" + +typedef struct AIOPool { + void (*cancel)(BlockDriverAIOCB *acb); + int aiocb_size; + BlockDriverAIOCB *free_aiocb; +} AIOPool; + +struct BlockDriver { + const char *format_name; + int instance_size; + int (*bdrv_probe)(const uint8_t *buf, int buf_size, const char *filename); + int (*bdrv_probe_device)(const char *filename); + int (*bdrv_open)(BlockDriverState *bs, int flags); + int (*bdrv_file_open)(BlockDriverState *bs, const char *filename, int flags); + int (*bdrv_read)(BlockDriverState *bs, int64_t sector_num, + uint8_t *buf, int nb_sectors); + int (*bdrv_write)(BlockDriverState *bs, int64_t sector_num, + const uint8_t *buf, int nb_sectors); + void (*bdrv_close)(BlockDriverState *bs); + int (*bdrv_create)(const char *filename, QEMUOptionParameter *options); + int (*bdrv_flush)(BlockDriverState *bs); + int (*bdrv_is_allocated)(BlockDriverState *bs, int64_t sector_num, + int nb_sectors, int *pnum); + int (*bdrv_set_key)(BlockDriverState *bs, const char *key); + int (*bdrv_make_empty)(BlockDriverState *bs); + /* aio */ + BlockDriverAIOCB *(*bdrv_aio_readv)(BlockDriverState *bs, + int64_t sector_num, QEMUIOVector *qiov, int nb_sectors, + BlockDriverCompletionFunc *cb, void *opaque); + BlockDriverAIOCB *(*bdrv_aio_writev)(BlockDriverState *bs, + int64_t sector_num, QEMUIOVector *qiov, int nb_sectors, + BlockDriverCompletionFunc *cb, void *opaque); + BlockDriverAIOCB *(*bdrv_aio_flush)(BlockDriverState *bs, + BlockDriverCompletionFunc *cb, void *opaque); + int (*bdrv_discard)(BlockDriverState *bs, int64_t sector_num, + int nb_sectors); + + int (*bdrv_aio_multiwrite)(BlockDriverState *bs, BlockRequest *reqs, + int num_reqs); + int (*bdrv_merge_requests)(BlockDriverState *bs, BlockRequest* a, + BlockRequest *b); + + + const char *protocol_name; + int (*bdrv_truncate)(BlockDriverState *bs, int64_t offset); + int64_t (*bdrv_getlength)(BlockDriverState *bs); + int (*bdrv_write_compressed)(BlockDriverState *bs, int64_t sector_num, + const uint8_t *buf, int nb_sectors); + + int (*bdrv_snapshot_create)(BlockDriverState *bs, + QEMUSnapshotInfo *sn_info); + int (*bdrv_snapshot_goto)(BlockDriverState *bs, + const char *snapshot_id); + int (*bdrv_snapshot_delete)(BlockDriverState *bs, const char *snapshot_id); + int (*bdrv_snapshot_list)(BlockDriverState *bs, + QEMUSnapshotInfo **psn_info); + int (*bdrv_snapshot_load_tmp)(BlockDriverState *bs, + const char *snapshot_name); + int (*bdrv_get_info)(BlockDriverState *bs, BlockDriverInfo *bdi); + + int (*bdrv_save_vmstate)(BlockDriverState *bs, const uint8_t *buf, + int64_t pos, int size); + int (*bdrv_load_vmstate)(BlockDriverState *bs, uint8_t *buf, + int64_t pos, int size); + + int (*bdrv_change_backing_file)(BlockDriverState *bs, + const char *backing_file, const char *backing_fmt); + + /* removable device specific */ + int (*bdrv_is_inserted)(BlockDriverState *bs); + int (*bdrv_media_changed)(BlockDriverState *bs); + int (*bdrv_eject)(BlockDriverState *bs, int eject_flag); + int (*bdrv_set_locked)(BlockDriverState *bs, int locked); + + /* to control generic scsi devices */ + int (*bdrv_ioctl)(BlockDriverState *bs, unsigned long int req, void *buf); + BlockDriverAIOCB *(*bdrv_aio_ioctl)(BlockDriverState *bs, + unsigned long int req, void *buf, + BlockDriverCompletionFunc *cb, void *opaque); + + /* List of options for creating images, terminated by name == NULL */ + QEMUOptionParameter *create_options; + + + /* + * Returns 0 for completed check, -errno for internal errors. + * The check results are stored in result. + */ + int (*bdrv_check)(BlockDriverState* bs, BdrvCheckResult *result); + + void (*bdrv_debug_event)(BlockDriverState *bs, BlkDebugEvent event); + + /* + * Returns 1 if newly created images are guaranteed to contain only + * zeros, 0 otherwise. + */ + int (*bdrv_has_zero_init)(BlockDriverState *bs); + + QLIST_ENTRY(BlockDriver) list; +}; + +struct BlockDriverState { + int64_t total_sectors; /* if we are reading a disk image, give its + size in sectors */ + int read_only; /* if true, the media is read only */ + int keep_read_only; /* if true, the media was requested to stay read only */ + int open_flags; /* flags used to open the file, re-used for re-open */ + int removable; /* if true, the media can be removed */ + int locked; /* if true, the media cannot temporarily be ejected */ + int tray_open; /* if true, the virtual tray is open */ + int encrypted; /* if true, the media is encrypted */ + int valid_key; /* if true, a valid encryption key has been set */ + int sg; /* if true, the device is a /dev/sg* */ + /* event callback when inserting/removing */ + void (*change_cb)(void *opaque, int reason); + void *change_opaque; + + BlockDriver *drv; /* NULL means no media */ + void *opaque; + + DeviceState *peer; + + char filename[1024]; + char backing_file[1024]; /* if non zero, the image is a diff of + this file image */ + char backing_format[16]; /* if non-zero and backing_file exists */ + int is_temporary; + int media_changed; + + BlockDriverState *backing_hd; + BlockDriverState *file; + + /* async read/write emulation */ + + void *sync_aiocb; + + /* I/O stats (display with "info blockstats"). */ + uint64_t rd_bytes; + uint64_t wr_bytes; + uint64_t rd_ops; + uint64_t wr_ops; + uint64_t wr_highest_sector; + + /* Whether the disk can expand beyond total_sectors */ + int growable; + + /* the memory alignment required for the buffers handled by this driver */ + int buffer_alignment; + + /* do we need to tell the quest if we have a volatile write cache? */ + int enable_write_cache; + + /* NOTE: the following infos are only hints for real hardware + drivers. They are not used by the block driver */ + int cyls, heads, secs, translation; + int type; + BlockErrorAction on_read_error, on_write_error; + char device_name[32]; + unsigned long *dirty_bitmap; + int64_t dirty_count; + int in_use; /* users other than guest access, eg. block migration */ + QTAILQ_ENTRY(BlockDriverState) list; + void *private; +}; + +#define CHANGE_MEDIA 0x01 +#define CHANGE_SIZE 0x02 + +struct BlockDriverAIOCB { + AIOPool *pool; + BlockDriverState *bs; + BlockDriverCompletionFunc *cb; + void *opaque; + BlockDriverAIOCB *next; +}; + +void get_tmp_filename(char *filename, int size); + +void *qemu_aio_get(AIOPool *pool, BlockDriverState *bs, + BlockDriverCompletionFunc *cb, void *opaque); +void qemu_aio_release(void *p); + +void *qemu_blockalign(BlockDriverState *bs, size_t size); + +#ifdef _WIN32 +int is_windows_drive(const char *filename); +#endif + +typedef struct BlockConf { + BlockDriverState *bs; + uint16_t physical_block_size; + uint16_t logical_block_size; + uint16_t min_io_size; + uint32_t opt_io_size; + int32_t bootindex; + uint32_t discard_granularity; +} BlockConf; + +static inline unsigned int get_physical_block_exp(BlockConf *conf) +{ + unsigned int exp = 0, size; + + for (size = conf->physical_block_size; + size > conf->logical_block_size; + size >>= 1) { + exp++; + } + + return exp; +} + +#define DEFINE_BLOCK_PROPERTIES(_state, _conf) \ + DEFINE_PROP_DRIVE("drive", _state, _conf.bs), \ + DEFINE_PROP_UINT16("logical_block_size", _state, \ + _conf.logical_block_size, 512), \ + DEFINE_PROP_UINT16("physical_block_size", _state, \ + _conf.physical_block_size, 512), \ + DEFINE_PROP_UINT16("min_io_size", _state, _conf.min_io_size, 0), \ + DEFINE_PROP_UINT32("opt_io_size", _state, _conf.opt_io_size, 0), \ + DEFINE_PROP_INT32("bootindex", _state, _conf.bootindex, -1), \ + DEFINE_PROP_UINT32("discard_granularity", _state, \ + _conf.discard_granularity, 0) + +#endif /* BLOCK_INT_H */ diff --git a/blockdev.c b/blockdev.c new file mode 100644 index 0000000..8c96754 --- /dev/null +++ b/blockdev.c @@ -0,0 +1,787 @@ +/* + * QEMU host block devices + * + * Copyright (c) 2003-2008 Fabrice Bellard + * + * 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 "block.h" +#include "blockdev.h" +#include "monitor.h" +#include "qerror.h" +#include "qemu-option.h" +#include "qemu-config.h" +#include "sysemu.h" +#include "hw/qdev.h" +#include "block_int.h" + +static QTAILQ_HEAD(drivelist, DriveInfo) drives = QTAILQ_HEAD_INITIALIZER(drives); + +static const char *const if_name[IF_COUNT] = { + [IF_NONE] = "none", + [IF_IDE] = "ide", + [IF_SCSI] = "scsi", + [IF_FLOPPY] = "floppy", + [IF_PFLASH] = "pflash", + [IF_MTD] = "mtd", + [IF_SD] = "sd", + [IF_VIRTIO] = "virtio", + [IF_XEN] = "xen", +}; + +static const int if_max_devs[IF_COUNT] = { + /* + * Do not change these numbers! They govern how drive option + * index maps to unit and bus. That mapping is ABI. + * + * All controllers used to imlement if=T drives need to support + * if_max_devs[T] units, for any T with if_max_devs[T] != 0. + * Otherwise, some index values map to "impossible" bus, unit + * values. + * + * For instance, if you change [IF_SCSI] to 255, -drive + * if=scsi,index=12 no longer means bus=1,unit=5, but + * bus=0,unit=12. With an lsi53c895a controller (7 units max), + * the drive can't be set up. Regression. + */ + [IF_IDE] = 2, + [IF_SCSI] = 7, +}; + +/* + * We automatically delete the drive when a device using it gets + * unplugged. Questionable feature, but we can't just drop it. + * Device models call blockdev_mark_auto_del() to schedule the + * automatic deletion, and generic qdev code calls blockdev_auto_del() + * when deletion is actually safe. + */ +void blockdev_mark_auto_del(BlockDriverState *bs) +{ + DriveInfo *dinfo = drive_get_by_blockdev(bs); + + if (dinfo) { + dinfo->auto_del = 1; + } +} + +void blockdev_auto_del(BlockDriverState *bs) +{ + DriveInfo *dinfo = drive_get_by_blockdev(bs); + + if (dinfo && dinfo->auto_del) { + drive_put_ref(dinfo); + } +} + +static int drive_index_to_bus_id(BlockInterfaceType type, int index) +{ + int max_devs = if_max_devs[type]; + return max_devs ? index / max_devs : 0; +} + +static int drive_index_to_unit_id(BlockInterfaceType type, int index) +{ + int max_devs = if_max_devs[type]; + return max_devs ? index % max_devs : index; +} + +QemuOpts *drive_def(const char *optstr) +{ + return qemu_opts_parse(qemu_find_opts("drive"), optstr, 0); +} + +QemuOpts *drive_add(BlockInterfaceType type, int index, const char *file, + const char *optstr) +{ + QemuOpts *opts; + char buf[32]; + + opts = drive_def(optstr); + if (!opts) { + return NULL; + } + if (type != IF_DEFAULT) { + qemu_opt_set(opts, "if", if_name[type]); + } + if (index >= 0) { + snprintf(buf, sizeof(buf), "%d", index); + qemu_opt_set(opts, "index", buf); + } + if (file) + qemu_opt_set(opts, "file", file); + return opts; +} + +DriveInfo *drive_get(BlockInterfaceType type, int bus, int unit) +{ + DriveInfo *dinfo; + + /* seek interface, bus and unit */ + + QTAILQ_FOREACH(dinfo, &drives, next) { + if (dinfo->type == type && + dinfo->bus == bus && + dinfo->unit == unit) + return dinfo; + } + + return NULL; +} + +DriveInfo *drive_get_by_index(BlockInterfaceType type, int index) +{ + return drive_get(type, + drive_index_to_bus_id(type, index), + drive_index_to_unit_id(type, index)); +} + +int drive_get_max_bus(BlockInterfaceType type) +{ + int max_bus; + DriveInfo *dinfo; + + max_bus = -1; + QTAILQ_FOREACH(dinfo, &drives, next) { + if(dinfo->type == type && + dinfo->bus > max_bus) + max_bus = dinfo->bus; + } + return max_bus; +} + +/* Get a block device. This should only be used for single-drive devices + (e.g. SD/Floppy/MTD). Multi-disk devices (scsi/ide) should use the + appropriate bus. */ +DriveInfo *drive_get_next(BlockInterfaceType type) +{ + static int next_block_unit[IF_COUNT]; + + return drive_get(type, 0, next_block_unit[type]++); +} + +DriveInfo *drive_get_by_blockdev(BlockDriverState *bs) +{ + DriveInfo *dinfo; + + QTAILQ_FOREACH(dinfo, &drives, next) { + if (dinfo->bdrv == bs) { + return dinfo; + } + } + return NULL; +} + +static void bdrv_format_print(void *opaque, const char *name) +{ + error_printf(" %s", name); +} + +static void drive_uninit(DriveInfo *dinfo) +{ + qemu_opts_del(dinfo->opts); + bdrv_delete(dinfo->bdrv); + qemu_free(dinfo->id); + QTAILQ_REMOVE(&drives, dinfo, next); + qemu_free(dinfo); +} + +void drive_put_ref(DriveInfo *dinfo) +{ + assert(dinfo->refcount); + if (--dinfo->refcount == 0) { + drive_uninit(dinfo); + } +} + +void drive_get_ref(DriveInfo *dinfo) +{ + dinfo->refcount++; +} + +static int parse_block_error_action(const char *buf, int is_read) +{ + if (!strcmp(buf, "ignore")) { + return BLOCK_ERR_IGNORE; + } else if (!is_read && !strcmp(buf, "enospc")) { + return BLOCK_ERR_STOP_ENOSPC; + } else if (!strcmp(buf, "stop")) { + return BLOCK_ERR_STOP_ANY; + } else if (!strcmp(buf, "report")) { + return BLOCK_ERR_REPORT; + } else { + error_report("'%s' invalid %s error action", + buf, is_read ? "read" : "write"); + return -1; + } +} + +DriveInfo *drive_init(QemuOpts *opts, int default_to_scsi) +{ + const char *buf; + const char *file = NULL; + char devname[128]; + const char *serial; + const char *mediastr = ""; + BlockInterfaceType type; + enum { MEDIA_DISK, MEDIA_CDROM } media; + int bus_id, unit_id; + int cyls, heads, secs, translation; + BlockDriver *drv = NULL; + int max_devs; + int index; + int ro = 0; + int bdrv_flags = 0; + int on_read_error, on_write_error; + const char *devaddr; + DriveInfo *dinfo; + int snapshot = 0; + int ret; + + translation = BIOS_ATA_TRANSLATION_AUTO; + + if (default_to_scsi) { + type = IF_SCSI; + pstrcpy(devname, sizeof(devname), "scsi"); + } else { + type = IF_IDE; + pstrcpy(devname, sizeof(devname), "ide"); + } + media = MEDIA_DISK; + + /* extract parameters */ + bus_id = qemu_opt_get_number(opts, "bus", 0); + unit_id = qemu_opt_get_number(opts, "unit", -1); + index = qemu_opt_get_number(opts, "index", -1); + + cyls = qemu_opt_get_number(opts, "cyls", 0); + heads = qemu_opt_get_number(opts, "heads", 0); + secs = qemu_opt_get_number(opts, "secs", 0); + + snapshot = qemu_opt_get_bool(opts, "snapshot", 0); + ro = qemu_opt_get_bool(opts, "readonly", 0); + + file = qemu_opt_get(opts, "file"); + serial = qemu_opt_get(opts, "serial"); + + if ((buf = qemu_opt_get(opts, "if")) != NULL) { + pstrcpy(devname, sizeof(devname), buf); + for (type = 0; type < IF_COUNT && strcmp(buf, if_name[type]); type++) + ; + if (type == IF_COUNT) { + error_report("unsupported bus type '%s'", buf); + return NULL; + } + } + max_devs = if_max_devs[type]; + + if (cyls || heads || secs) { + if (cyls < 1 || (type == IF_IDE && cyls > 16383)) { + error_report("invalid physical cyls number"); + return NULL; + } + if (heads < 1 || (type == IF_IDE && heads > 16)) { + error_report("invalid physical heads number"); + return NULL; + } + if (secs < 1 || (type == IF_IDE && secs > 63)) { + error_report("invalid physical secs number"); + return NULL; + } + } + + if ((buf = qemu_opt_get(opts, "trans")) != NULL) { + if (!cyls) { + error_report("'%s' trans must be used with cyls,heads and secs", + buf); + return NULL; + } + if (!strcmp(buf, "none")) + translation = BIOS_ATA_TRANSLATION_NONE; + else if (!strcmp(buf, "lba")) + translation = BIOS_ATA_TRANSLATION_LBA; + else if (!strcmp(buf, "auto")) + translation = BIOS_ATA_TRANSLATION_AUTO; + else { + error_report("'%s' invalid translation type", buf); + return NULL; + } + } + + if ((buf = qemu_opt_get(opts, "media")) != NULL) { + if (!strcmp(buf, "disk")) { + media = MEDIA_DISK; + } else if (!strcmp(buf, "cdrom")) { + if (cyls || secs || heads) { + error_report("'%s' invalid physical CHS format", buf); + return NULL; + } + media = MEDIA_CDROM; + } else { + error_report("'%s' invalid media", buf); + return NULL; + } + } + + if ((buf = qemu_opt_get(opts, "cache")) != NULL) { + if (!strcmp(buf, "off") || !strcmp(buf, "none")) { + bdrv_flags |= BDRV_O_NOCACHE; + } else if (!strcmp(buf, "writeback")) { + bdrv_flags |= BDRV_O_CACHE_WB; + } else if (!strcmp(buf, "unsafe")) { + bdrv_flags |= BDRV_O_CACHE_WB; + bdrv_flags |= BDRV_O_NO_FLUSH; + } else if (!strcmp(buf, "writethrough")) { + /* this is the default */ + } else { + error_report("invalid cache option"); + return NULL; + } + } + +#ifdef CONFIG_LINUX_AIO + if ((buf = qemu_opt_get(opts, "aio")) != NULL) { + if (!strcmp(buf, "native")) { + bdrv_flags |= BDRV_O_NATIVE_AIO; + } else if (!strcmp(buf, "threads")) { + /* this is the default */ + } else { + error_report("invalid aio option"); + return NULL; + } + } +#endif + + if ((buf = qemu_opt_get(opts, "format")) != NULL) { + if (strcmp(buf, "?") == 0) { + error_printf("Supported formats:"); + bdrv_iterate_format(bdrv_format_print, NULL); + error_printf("\n"); + return NULL; + } + drv = bdrv_find_whitelisted_format(buf); + if (!drv) { + error_report("'%s' invalid format", buf); + return NULL; + } + } + + on_write_error = BLOCK_ERR_STOP_ENOSPC; + if ((buf = qemu_opt_get(opts, "werror")) != NULL) { + if (type != IF_IDE && type != IF_SCSI && type != IF_VIRTIO && type != IF_NONE) { + error_report("werror is not supported by this bus type"); + return NULL; + } + + on_write_error = parse_block_error_action(buf, 0); + if (on_write_error < 0) { + return NULL; + } + } + + on_read_error = BLOCK_ERR_REPORT; + if ((buf = qemu_opt_get(opts, "rerror")) != NULL) { + if (type != IF_IDE && type != IF_VIRTIO && type != IF_SCSI && type != IF_NONE) { + error_report("rerror is not supported by this bus type"); + return NULL; + } + + on_read_error = parse_block_error_action(buf, 1); + if (on_read_error < 0) { + return NULL; + } + } + + if ((devaddr = qemu_opt_get(opts, "addr")) != NULL) { + if (type != IF_VIRTIO) { + error_report("addr is not supported by this bus type"); + return NULL; + } + } + + /* compute bus and unit according index */ + + if (index != -1) { + if (bus_id != 0 || unit_id != -1) { + error_report("index cannot be used with bus and unit"); + return NULL; + } + bus_id = drive_index_to_bus_id(type, index); + unit_id = drive_index_to_unit_id(type, index); + } + + /* if user doesn't specify a unit_id, + * try to find the first free + */ + + if (unit_id == -1) { + unit_id = 0; + while (drive_get(type, bus_id, unit_id) != NULL) { + unit_id++; + if (max_devs && unit_id >= max_devs) { + unit_id -= max_devs; + bus_id++; + } + } + } + + /* check unit id */ + + if (max_devs && unit_id >= max_devs) { + error_report("unit %d too big (max is %d)", + unit_id, max_devs - 1); + return NULL; + } + + /* + * catch multiple definitions + */ + + if (drive_get(type, bus_id, unit_id) != NULL) { + error_report("drive with bus=%d, unit=%d (index=%d) exists", + bus_id, unit_id, index); + return NULL; + } + + /* init */ + + dinfo = qemu_mallocz(sizeof(*dinfo)); + if ((buf = qemu_opts_id(opts)) != NULL) { + dinfo->id = qemu_strdup(buf); + } else { + /* no id supplied -> create one */ + dinfo->id = qemu_mallocz(32); + if (type == IF_IDE || type == IF_SCSI) + mediastr = (media == MEDIA_CDROM) ? "-cd" : "-hd"; + if (max_devs) + snprintf(dinfo->id, 32, "%s%i%s%i", + devname, bus_id, mediastr, unit_id); + else + snprintf(dinfo->id, 32, "%s%s%i", + devname, mediastr, unit_id); + } + dinfo->bdrv = bdrv_new(dinfo->id); + dinfo->devaddr = devaddr; + dinfo->type = type; + dinfo->bus = bus_id; + dinfo->unit = unit_id; + dinfo->opts = opts; + dinfo->refcount = 1; + if (serial) + strncpy(dinfo->serial, serial, sizeof(dinfo->serial) - 1); + QTAILQ_INSERT_TAIL(&drives, dinfo, next); + + bdrv_set_on_error(dinfo->bdrv, on_read_error, on_write_error); + + switch(type) { + case IF_IDE: + case IF_SCSI: + case IF_XEN: + case IF_NONE: + switch(media) { + case MEDIA_DISK: + if (cyls != 0) { + bdrv_set_geometry_hint(dinfo->bdrv, cyls, heads, secs); + bdrv_set_translation_hint(dinfo->bdrv, translation); + } + break; + case MEDIA_CDROM: + bdrv_set_type_hint(dinfo->bdrv, BDRV_TYPE_CDROM); + break; + } + break; + case IF_SD: + /* FIXME: This isn't really a floppy, but it's a reasonable + approximation. */ + case IF_FLOPPY: + bdrv_set_type_hint(dinfo->bdrv, BDRV_TYPE_FLOPPY); + break; + case IF_PFLASH: + case IF_MTD: + break; + case IF_VIRTIO: + /* add virtio block device */ + opts = qemu_opts_create(qemu_find_opts("device"), NULL, 0); + qemu_opt_set(opts, "driver", "virtio-blk-pci"); + qemu_opt_set(opts, "drive", dinfo->id); + if (devaddr) + qemu_opt_set(opts, "addr", devaddr); + break; + default: + abort(); + } + if (!file || !*file) { + return dinfo; + } + if (snapshot) { + /* always use cache=unsafe with snapshot */ + bdrv_flags &= ~BDRV_O_CACHE_MASK; + bdrv_flags |= (BDRV_O_SNAPSHOT|BDRV_O_CACHE_WB|BDRV_O_NO_FLUSH); + } + + if (media == MEDIA_CDROM) { + /* CDROM is fine for any interface, don't check. */ + ro = 1; + } else if (ro == 1) { + if (type != IF_SCSI && type != IF_VIRTIO && type != IF_FLOPPY && type != IF_NONE) { + error_report("readonly not supported by this bus type"); + goto err; + } + } + + bdrv_flags |= ro ? 0 : BDRV_O_RDWR; + + ret = bdrv_open(dinfo->bdrv, file, bdrv_flags, drv); + if (ret < 0) { + error_report("could not open disk image %s: %s", + file, strerror(-ret)); + goto err; + } + + if (bdrv_key_required(dinfo->bdrv)) + autostart = 0; + return dinfo; + +err: + bdrv_delete(dinfo->bdrv); + qemu_free(dinfo->id); + QTAILQ_REMOVE(&drives, dinfo, next); + qemu_free(dinfo); + return NULL; +} + +void do_commit(Monitor *mon, const QDict *qdict) +{ + const char *device = qdict_get_str(qdict, "device"); + BlockDriverState *bs; + + if (!strcmp(device, "all")) { + bdrv_commit_all(); + } else { + bs = bdrv_find(device); + if (!bs) { + qerror_report(QERR_DEVICE_NOT_FOUND, device); + return; + } + bdrv_commit(bs); + } +} + +int do_snapshot_blkdev(Monitor *mon, const QDict *qdict, QObject **ret_data) +{ + const char *device = qdict_get_str(qdict, "device"); + const char *filename = qdict_get_try_str(qdict, "snapshot_file"); + const char *format = qdict_get_try_str(qdict, "format"); + BlockDriverState *bs; + BlockDriver *drv, *proto_drv; + int ret = 0; + int flags; + + if (!filename) { + qerror_report(QERR_MISSING_PARAMETER, "snapshot_file"); + ret = -1; + goto out; + } + + bs = bdrv_find(device); + if (!bs) { + qerror_report(QERR_DEVICE_NOT_FOUND, device); + ret = -1; + goto out; + } + + if (!format) { + format = "qcow2"; + } + + drv = bdrv_find_format(format); + if (!drv) { + qerror_report(QERR_INVALID_BLOCK_FORMAT, format); + ret = -1; + goto out; + } + + proto_drv = bdrv_find_protocol(filename); + if (!proto_drv) { + qerror_report(QERR_INVALID_BLOCK_FORMAT, format); + ret = -1; + goto out; + } + + ret = bdrv_img_create(filename, format, bs->filename, + bs->drv->format_name, NULL, -1, bs->open_flags); + if (ret) { + goto out; + } + + qemu_aio_flush(); + bdrv_flush(bs); + + flags = bs->open_flags; + bdrv_close(bs); + ret = bdrv_open(bs, filename, flags, drv); + /* + * If reopening the image file we just created fails, we really + * are in trouble :( + */ + if (ret != 0) { + abort(); + } +out: + if (ret) { + ret = -1; + } + + return ret; +} + +static int eject_device(Monitor *mon, BlockDriverState *bs, int force) +{ + if (!force) { + if (!bdrv_is_removable(bs)) { + qerror_report(QERR_DEVICE_NOT_REMOVABLE, + bdrv_get_device_name(bs)); + return -1; + } + if (bdrv_is_locked(bs)) { + qerror_report(QERR_DEVICE_LOCKED, bdrv_get_device_name(bs)); + return -1; + } + } + bdrv_close(bs); + return 0; +} + +int do_eject(Monitor *mon, const QDict *qdict, QObject **ret_data) +{ + BlockDriverState *bs; + int force = qdict_get_try_bool(qdict, "force", 0); + const char *filename = qdict_get_str(qdict, "device"); + + bs = bdrv_find(filename); + if (!bs) { + qerror_report(QERR_DEVICE_NOT_FOUND, filename); + return -1; + } + return eject_device(mon, bs, force); +} + +int do_block_set_passwd(Monitor *mon, const QDict *qdict, + QObject **ret_data) +{ + BlockDriverState *bs; + int err; + + bs = bdrv_find(qdict_get_str(qdict, "device")); + if (!bs) { + qerror_report(QERR_DEVICE_NOT_FOUND, qdict_get_str(qdict, "device")); + return -1; + } + + err = bdrv_set_key(bs, qdict_get_str(qdict, "password")); + if (err == -EINVAL) { + qerror_report(QERR_DEVICE_NOT_ENCRYPTED, bdrv_get_device_name(bs)); + return -1; + } else if (err < 0) { + qerror_report(QERR_INVALID_PASSWORD); + return -1; + } + + return 0; +} + +int do_change_block(Monitor *mon, const char *device, + const char *filename, const char *fmt) +{ + BlockDriverState *bs; + BlockDriver *drv = NULL; + int bdrv_flags; + + bs = bdrv_find(device); + if (!bs) { + qerror_report(QERR_DEVICE_NOT_FOUND, device); + return -1; + } + if (fmt) { + drv = bdrv_find_whitelisted_format(fmt); + if (!drv) { + qerror_report(QERR_INVALID_BLOCK_FORMAT, fmt); + return -1; + } + } + if (eject_device(mon, bs, 0) < 0) { + return -1; + } + bdrv_flags = bdrv_is_read_only(bs) ? 0 : BDRV_O_RDWR; + bdrv_flags |= bdrv_is_snapshot(bs) ? BDRV_O_SNAPSHOT : 0; + if (bdrv_open(bs, filename, bdrv_flags, drv) < 0) { + qerror_report(QERR_OPEN_FILE_FAILED, filename); + return -1; + } + return monitor_read_bdrv_key_start(mon, bs, NULL, NULL); +} + +int do_drive_del(Monitor *mon, const QDict *qdict, QObject **ret_data) +{ + const char *id = qdict_get_str(qdict, "id"); + BlockDriverState *bs; + + bs = bdrv_find(id); + if (!bs) { + qerror_report(QERR_DEVICE_NOT_FOUND, id); + return -1; + } + if (bdrv_in_use(bs)) { + qerror_report(QERR_DEVICE_IN_USE, id); + return -1; + } + + /* quiesce block driver; prevent further io */ + qemu_aio_flush(); + bdrv_flush(bs); + bdrv_close(bs); + + /* if we have a device associated with this BlockDriverState (bs->peer) + * then we need to make the drive anonymous until the device + * can be removed. If this is a drive with no device backing + * then we can just get rid of the block driver state right here. + */ + if (bs->peer) { + bdrv_make_anon(bs); + } else { + drive_uninit(drive_get_by_blockdev(bs)); + } + + return 0; +} + +/* + * XXX: replace the QERR_UNDEFINED_ERROR errors with real values once the + * existing QERR_ macro mess is cleaned up. A good example for better + * error reports can be found in the qemu-img resize code. + */ +int do_block_resize(Monitor *mon, const QDict *qdict, QObject **ret_data) +{ + const char *device = qdict_get_str(qdict, "device"); + int64_t size = qdict_get_int(qdict, "size"); + BlockDriverState *bs; + + bs = bdrv_find(device); + if (!bs) { + qerror_report(QERR_DEVICE_NOT_FOUND, device); + return -1; + } + + if (size < 0) { + qerror_report(QERR_UNDEFINED_ERROR); + return -1; + } + + if (bdrv_truncate(bs, size)) { + qerror_report(QERR_UNDEFINED_ERROR); + return -1; + } + + return 0; +} diff --git a/blockdev.h b/blockdev.h new file mode 100644 index 0000000..2c9e780 --- /dev/null +++ b/blockdev.h @@ -0,0 +1,68 @@ +/* + * QEMU host block devices + * + * Copyright (c) 2003-2008 Fabrice Bellard + * + * 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 BLOCKDEV_H +#define BLOCKDEV_H + +#include "block.h" +#include "qemu-queue.h" + +void blockdev_mark_auto_del(BlockDriverState *bs); +void blockdev_auto_del(BlockDriverState *bs); + +#define BLOCK_SERIAL_STRLEN 20 + +typedef enum { + IF_DEFAULT = -1, /* for use with drive_add() only */ + IF_NONE, + IF_IDE, IF_SCSI, IF_FLOPPY, IF_PFLASH, IF_MTD, IF_SD, IF_VIRTIO, IF_XEN, + IF_COUNT +} BlockInterfaceType; + +struct DriveInfo { + BlockDriverState *bdrv; + char *id; + const char *devaddr; + BlockInterfaceType type; + int bus; + int unit; + int auto_del; /* see blockdev_mark_auto_del() */ + QemuOpts *opts; + char serial[BLOCK_SERIAL_STRLEN + 1]; + QTAILQ_ENTRY(DriveInfo) next; + int refcount; +}; + +DriveInfo *drive_get(BlockInterfaceType type, int bus, int unit); +DriveInfo *drive_get_by_index(BlockInterfaceType type, int index); +int drive_get_max_bus(BlockInterfaceType type); +DriveInfo *drive_get_next(BlockInterfaceType type); +void drive_get_ref(DriveInfo *dinfo); +void drive_put_ref(DriveInfo *dinfo); +DriveInfo *drive_get_by_blockdev(BlockDriverState *bs); + +QemuOpts *drive_def(const char *optstr); +QemuOpts *drive_add(BlockInterfaceType type, int index, const char *file, + const char *optstr); +DriveInfo *drive_init(QemuOpts *arg, int default_to_scsi); + +/* device-hotplug */ + +DriveInfo *add_init_drive(const char *opts); + +void do_commit(Monitor *mon, const QDict *qdict); +int do_eject(Monitor *mon, const QDict *qdict, QObject **ret_data); +int do_block_set_passwd(Monitor *mon, const QDict *qdict, QObject **ret_data); +int do_change_block(Monitor *mon, const char *device, + const char *filename, const char *fmt); +int do_drive_del(Monitor *mon, const QDict *qdict, QObject **ret_data); +int do_snapshot_blkdev(Monitor *mon, const QDict *qdict, QObject **ret_data); +int do_block_resize(Monitor *mon, const QDict *qdict, QObject **ret_data); + +#endif diff --git a/bsd-user/bsd-mman.h b/bsd-user/bsd-mman.h new file mode 100644 index 0000000..910e8c1 --- /dev/null +++ b/bsd-user/bsd-mman.h @@ -0,0 +1,121 @@ +/*- + * Copyright (c) 1982, 1986, 1993 + * The Regents of the University of California. 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. + * 4. Neither the name of the University 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 REGENTS 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 REGENTS 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. + * + * @(#)mman.h 8.2 (Berkeley) 1/9/95 + * $FreeBSD: src/sys/sys/mman.h,v 1.42 2008/03/28 04:29:27 ps Exp $ + */ + +#define TARGET_FREEBSD_MAP_RESERVED0080 0x0080 /* previously misimplemented MAP_INHERIT */ +#define TARGET_FREEBSD_MAP_RESERVED0100 0x0100 /* previously unimplemented MAP_NOEXTEND */ +#define TARGET_FREEBSD_MAP_STACK 0x0400 /* region grows down, like a stack */ +#define TARGET_FREEBSD_MAP_NOSYNC 0x0800 /* page to but do not sync underlying file */ + +#define TARGET_FREEBSD_MAP_FLAGMASK 0x1ff7 + +/* $NetBSD: mman.h,v 1.42 2008/11/18 22:13:49 ad Exp $ */ + +/*- + * Copyright (c) 1982, 1986, 1993 + * The Regents of the University of California. 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. + * 3. Neither the name of the University 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 REGENTS 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 REGENTS 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. + * + * @(#)mman.h 8.2 (Berkeley) 1/9/95 + */ +#define TARGET_NETBSD_MAP_INHERIT 0x0080 /* region is retained after exec */ +#define TARGET_NETBSD_MAP_TRYFIXED 0x0400 /* attempt hint address, even within break */ +#define TARGET_NETBSD_MAP_WIRED 0x0800 /* mlock() mapping when it is established */ + +#define TARGET_NETBSD_MAP_STACK 0x2000 /* allocated from memory, swap space (stack) */ + +#define TARGET_NETBSD_MAP_FLAGMASK 0x3ff7 + +/* $OpenBSD: mman.h,v 1.18 2003/07/21 22:52:19 tedu Exp $ */ +/* $NetBSD: mman.h,v 1.11 1995/03/26 20:24:23 jtc Exp $ */ + +/*- + * Copyright (c) 1982, 1986, 1993 + * The Regents of the University of California. 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. + * 3. Neither the name of the University 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 REGENTS 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 REGENTS 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. + * + * @(#)mman.h 8.1 (Berkeley) 6/2/93 + */ + +#define TARGET_OPENBSD_MAP_INHERIT 0x0080 /* region is retained after exec */ +#define TARGET_OPENBSD_MAP_NOEXTEND 0x0100 /* for MAP_FILE, don't change file size */ +#define TARGET_OPENBSD_MAP_TRYFIXED 0x0400 /* attempt hint address, even within heap */ + +#define TARGET_OPENBSD_MAP_FLAGMASK 0x17f7 + +// XXX +#define TARGET_BSD_MAP_FLAGMASK 0x3ff7 diff --git a/bsd-user/bsdload.c b/bsd-user/bsdload.c new file mode 100644 index 0000000..6d9bb6f --- /dev/null +++ b/bsd-user/bsdload.c @@ -0,0 +1,202 @@ +/* Code for loading BSD executables. Mostly linux kernel code. */ + +#include +#include +#include +#include +#include +#include +#include + +#include "qemu.h" + +#define TARGET_NGROUPS 32 + +/* ??? This should really be somewhere else. */ +abi_long memcpy_to_target(abi_ulong dest, const void *src, + unsigned long len) +{ + void *host_ptr; + + host_ptr = lock_user(VERIFY_WRITE, dest, len, 0); + if (!host_ptr) + return -TARGET_EFAULT; + memcpy(host_ptr, src, len); + unlock_user(host_ptr, dest, 1); + return 0; +} + +static int in_group_p(gid_t g) +{ + /* return TRUE if we're in the specified group, FALSE otherwise */ + int ngroup; + int i; + gid_t grouplist[TARGET_NGROUPS]; + + ngroup = getgroups(TARGET_NGROUPS, grouplist); + for(i = 0; i < ngroup; i++) { + if(grouplist[i] == g) { + return 1; + } + } + return 0; +} + +static int count(char ** vec) +{ + int i; + + for(i = 0; *vec; i++) { + vec++; + } + + return(i); +} + +static int prepare_binprm(struct linux_binprm *bprm) +{ + struct stat st; + int mode; + int retval, id_change; + + if(fstat(bprm->fd, &st) < 0) { + return(-errno); + } + + mode = st.st_mode; + if(!S_ISREG(mode)) { /* Must be regular file */ + return(-EACCES); + } + if(!(mode & 0111)) { /* Must have at least one execute bit set */ + return(-EACCES); + } + + bprm->e_uid = geteuid(); + bprm->e_gid = getegid(); + id_change = 0; + + /* Set-uid? */ + if(mode & S_ISUID) { + bprm->e_uid = st.st_uid; + if(bprm->e_uid != geteuid()) { + id_change = 1; + } + } + + /* Set-gid? */ + /* + * If setgid is set but no group execute bit then this + * is a candidate for mandatory locking, not a setgid + * executable. + */ + if ((mode & (S_ISGID | S_IXGRP)) == (S_ISGID | S_IXGRP)) { + bprm->e_gid = st.st_gid; + if (!in_group_p(bprm->e_gid)) { + id_change = 1; + } + } + + memset(bprm->buf, 0, sizeof(bprm->buf)); + retval = lseek(bprm->fd, 0L, SEEK_SET); + if(retval >= 0) { + retval = read(bprm->fd, bprm->buf, 128); + } + if(retval < 0) { + perror("prepare_binprm"); + exit(-1); + /* return(-errno); */ + } + else { + return(retval); + } +} + +/* Construct the envp and argv tables on the target stack. */ +abi_ulong loader_build_argptr(int envc, int argc, abi_ulong sp, + abi_ulong stringp, int push_ptr) +{ + int n = sizeof(abi_ulong); + abi_ulong envp; + abi_ulong argv; + + sp -= (envc + 1) * n; + envp = sp; + sp -= (argc + 1) * n; + argv = sp; + if (push_ptr) { + /* FIXME - handle put_user() failures */ + sp -= n; + put_user_ual(envp, sp); + sp -= n; + put_user_ual(argv, sp); + } + sp -= n; + /* FIXME - handle put_user() failures */ + put_user_ual(argc, sp); + + while (argc-- > 0) { + /* FIXME - handle put_user() failures */ + put_user_ual(stringp, argv); + argv += n; + stringp += target_strlen(stringp) + 1; + } + /* FIXME - handle put_user() failures */ + put_user_ual(0, argv); + while (envc-- > 0) { + /* FIXME - handle put_user() failures */ + put_user_ual(stringp, envp); + envp += n; + stringp += target_strlen(stringp) + 1; + } + /* FIXME - handle put_user() failures */ + put_user_ual(0, envp); + + return sp; +} + +int loader_exec(const char * filename, char ** argv, char ** envp, + struct target_pt_regs * regs, struct image_info *infop) +{ + struct linux_binprm bprm; + int retval; + int i; + + bprm.p = TARGET_PAGE_SIZE*MAX_ARG_PAGES-sizeof(unsigned int); + for (i=0 ; i=0) { + if (bprm.buf[0] == 0x7f + && bprm.buf[1] == 'E' + && bprm.buf[2] == 'L' + && bprm.buf[3] == 'F') { + retval = load_elf_binary(&bprm,regs,infop); + } else { + fprintf(stderr, "Unknown binary format\n"); + return -1; + } + } + + if(retval>=0) { + /* success. Initialize important registers */ + do_init_thread(regs, infop); + return retval; + } + + /* Something went wrong, return the inode and free the argument pages*/ + for (i=0 ; i +#include +#include +#include +#include +#include +#include +#include + +#include "qemu.h" +#include "disas.h" + +#ifdef _ARCH_PPC64 +#undef ARCH_DLINFO +#undef ELF_PLATFORM +#undef ELF_HWCAP +#undef ELF_CLASS +#undef ELF_DATA +#undef ELF_ARCH +#endif + +/* from personality.h */ + +/* + * Flags for bug emulation. + * + * These occupy the top three bytes. + */ +enum { + ADDR_NO_RANDOMIZE = 0x0040000, /* disable randomization of VA space */ + FDPIC_FUNCPTRS = 0x0080000, /* userspace function ptrs point to descriptors + * (signal handling) + */ + MMAP_PAGE_ZERO = 0x0100000, + ADDR_COMPAT_LAYOUT = 0x0200000, + READ_IMPLIES_EXEC = 0x0400000, + ADDR_LIMIT_32BIT = 0x0800000, + SHORT_INODE = 0x1000000, + WHOLE_SECONDS = 0x2000000, + STICKY_TIMEOUTS = 0x4000000, + ADDR_LIMIT_3GB = 0x8000000, +}; + +/* + * Personality types. + * + * These go in the low byte. Avoid using the top bit, it will + * conflict with error returns. + */ +enum { + PER_LINUX = 0x0000, + PER_LINUX_32BIT = 0x0000 | ADDR_LIMIT_32BIT, + PER_LINUX_FDPIC = 0x0000 | FDPIC_FUNCPTRS, + PER_SVR4 = 0x0001 | STICKY_TIMEOUTS | MMAP_PAGE_ZERO, + PER_SVR3 = 0x0002 | STICKY_TIMEOUTS | SHORT_INODE, + PER_SCOSVR3 = 0x0003 | STICKY_TIMEOUTS | + WHOLE_SECONDS | SHORT_INODE, + PER_OSR5 = 0x0003 | STICKY_TIMEOUTS | WHOLE_SECONDS, + PER_WYSEV386 = 0x0004 | STICKY_TIMEOUTS | SHORT_INODE, + PER_ISCR4 = 0x0005 | STICKY_TIMEOUTS, + PER_BSD = 0x0006, + PER_SUNOS = 0x0006 | STICKY_TIMEOUTS, + PER_XENIX = 0x0007 | STICKY_TIMEOUTS | SHORT_INODE, + PER_LINUX32 = 0x0008, + PER_LINUX32_3GB = 0x0008 | ADDR_LIMIT_3GB, + PER_IRIX32 = 0x0009 | STICKY_TIMEOUTS,/* IRIX5 32-bit */ + PER_IRIXN32 = 0x000a | STICKY_TIMEOUTS,/* IRIX6 new 32-bit */ + PER_IRIX64 = 0x000b | STICKY_TIMEOUTS,/* IRIX6 64-bit */ + PER_RISCOS = 0x000c, + PER_SOLARIS = 0x000d | STICKY_TIMEOUTS, + PER_UW7 = 0x000e | STICKY_TIMEOUTS | MMAP_PAGE_ZERO, + PER_OSF4 = 0x000f, /* OSF/1 v4 */ + PER_HPUX = 0x0010, + PER_MASK = 0x00ff, +}; + +/* + * Return the base personality without flags. + */ +#define personality(pers) (pers & PER_MASK) + +/* this flag is uneffective under linux too, should be deleted */ +#ifndef MAP_DENYWRITE +#define MAP_DENYWRITE 0 +#endif + +/* should probably go in elf.h */ +#ifndef ELIBBAD +#define ELIBBAD 80 +#endif + +#ifdef TARGET_I386 + +#define ELF_PLATFORM get_elf_platform() + +static const char *get_elf_platform(void) +{ + static char elf_platform[] = "i386"; + int family = (thread_env->cpuid_version >> 8) & 0xff; + if (family > 6) + family = 6; + if (family >= 3) + elf_platform[1] = '0' + family; + return elf_platform; +} + +#define ELF_HWCAP get_elf_hwcap() + +static uint32_t get_elf_hwcap(void) +{ + return thread_env->cpuid_features; +} + +#ifdef TARGET_X86_64 +#define ELF_START_MMAP 0x2aaaaab000ULL +#define elf_check_arch(x) ( ((x) == ELF_ARCH) ) + +#define ELF_CLASS ELFCLASS64 +#define ELF_DATA ELFDATA2LSB +#define ELF_ARCH EM_X86_64 + +static inline void init_thread(struct target_pt_regs *regs, struct image_info *infop) +{ + regs->rax = 0; + regs->rsp = infop->start_stack; + regs->rip = infop->entry; + if (bsd_type == target_freebsd) { + regs->rdi = infop->start_stack; + } +} + +#else + +#define ELF_START_MMAP 0x80000000 + +/* + * This is used to ensure we don't load something for the wrong architecture. + */ +#define elf_check_arch(x) ( ((x) == EM_386) || ((x) == EM_486) ) + +/* + * These are used to set parameters in the core dumps. + */ +#define ELF_CLASS ELFCLASS32 +#define ELF_DATA ELFDATA2LSB +#define ELF_ARCH EM_386 + +static inline void init_thread(struct target_pt_regs *regs, struct image_info *infop) +{ + regs->esp = infop->start_stack; + regs->eip = infop->entry; + + /* SVR4/i386 ABI (pages 3-31, 3-32) says that when the program + starts %edx contains a pointer to a function which might be + registered using `atexit'. This provides a mean for the + dynamic linker to call DT_FINI functions for shared libraries + that have been loaded before the code runs. + + A value of 0 tells we have no such handler. */ + regs->edx = 0; +} +#endif + +#define USE_ELF_CORE_DUMP +#define ELF_EXEC_PAGESIZE 4096 + +#endif + +#ifdef TARGET_ARM + +#define ELF_START_MMAP 0x80000000 + +#define elf_check_arch(x) ( (x) == EM_ARM ) + +#define ELF_CLASS ELFCLASS32 +#ifdef TARGET_WORDS_BIGENDIAN +#define ELF_DATA ELFDATA2MSB +#else +#define ELF_DATA ELFDATA2LSB +#endif +#define ELF_ARCH EM_ARM + +static inline void init_thread(struct target_pt_regs *regs, struct image_info *infop) +{ + abi_long stack = infop->start_stack; + memset(regs, 0, sizeof(*regs)); + regs->ARM_cpsr = 0x10; + if (infop->entry & 1) + regs->ARM_cpsr |= CPSR_T; + regs->ARM_pc = infop->entry & 0xfffffffe; + regs->ARM_sp = infop->start_stack; + /* FIXME - what to for failure of get_user()? */ + get_user_ual(regs->ARM_r2, stack + 8); /* envp */ + get_user_ual(regs->ARM_r1, stack + 4); /* envp */ + /* XXX: it seems that r0 is zeroed after ! */ + regs->ARM_r0 = 0; + /* For uClinux PIC binaries. */ + /* XXX: Linux does this only on ARM with no MMU (do we care ?) */ + regs->ARM_r10 = infop->start_data; +} + +#define USE_ELF_CORE_DUMP +#define ELF_EXEC_PAGESIZE 4096 + +enum +{ + ARM_HWCAP_ARM_SWP = 1 << 0, + ARM_HWCAP_ARM_HALF = 1 << 1, + ARM_HWCAP_ARM_THUMB = 1 << 2, + ARM_HWCAP_ARM_26BIT = 1 << 3, + ARM_HWCAP_ARM_FAST_MULT = 1 << 4, + ARM_HWCAP_ARM_FPA = 1 << 5, + ARM_HWCAP_ARM_VFP = 1 << 6, + ARM_HWCAP_ARM_EDSP = 1 << 7, +}; + +#define ELF_HWCAP (ARM_HWCAP_ARM_SWP | ARM_HWCAP_ARM_HALF \ + | ARM_HWCAP_ARM_THUMB | ARM_HWCAP_ARM_FAST_MULT \ + | ARM_HWCAP_ARM_FPA | ARM_HWCAP_ARM_VFP) + +#endif + +#ifdef TARGET_SPARC +#ifdef TARGET_SPARC64 + +#define ELF_START_MMAP 0x80000000 + +#ifndef TARGET_ABI32 +#define elf_check_arch(x) ( (x) == EM_SPARCV9 || (x) == EM_SPARC32PLUS ) +#else +#define elf_check_arch(x) ( (x) == EM_SPARC32PLUS || (x) == EM_SPARC ) +#endif + +#define ELF_CLASS ELFCLASS64 +#define ELF_DATA ELFDATA2MSB +#define ELF_ARCH EM_SPARCV9 + +#define STACK_BIAS 2047 + +static inline void init_thread(struct target_pt_regs *regs, struct image_info *infop) +{ +#ifndef TARGET_ABI32 + regs->tstate = 0; +#endif + regs->pc = infop->entry; + regs->npc = regs->pc + 4; + regs->y = 0; +#ifdef TARGET_ABI32 + regs->u_regs[14] = infop->start_stack - 16 * 4; +#else + if (personality(infop->personality) == PER_LINUX32) + regs->u_regs[14] = infop->start_stack - 16 * 4; + else { + regs->u_regs[14] = infop->start_stack - 16 * 8 - STACK_BIAS; + if (bsd_type == target_freebsd) { + regs->u_regs[8] = infop->start_stack; + regs->u_regs[11] = infop->start_stack; + } + } +#endif +} + +#else +#define ELF_START_MMAP 0x80000000 + +#define elf_check_arch(x) ( (x) == EM_SPARC ) + +#define ELF_CLASS ELFCLASS32 +#define ELF_DATA ELFDATA2MSB +#define ELF_ARCH EM_SPARC + +static inline void init_thread(struct target_pt_regs *regs, struct image_info *infop) +{ + regs->psr = 0; + regs->pc = infop->entry; + regs->npc = regs->pc + 4; + regs->y = 0; + regs->u_regs[14] = infop->start_stack - 16 * 4; +} + +#endif +#endif + +#ifdef TARGET_PPC + +#define ELF_START_MMAP 0x80000000 + +#if defined(TARGET_PPC64) && !defined(TARGET_ABI32) + +#define elf_check_arch(x) ( (x) == EM_PPC64 ) + +#define ELF_CLASS ELFCLASS64 + +#else + +#define elf_check_arch(x) ( (x) == EM_PPC ) + +#define ELF_CLASS ELFCLASS32 + +#endif + +#ifdef TARGET_WORDS_BIGENDIAN +#define ELF_DATA ELFDATA2MSB +#else +#define ELF_DATA ELFDATA2LSB +#endif +#define ELF_ARCH EM_PPC + +/* + * We need to put in some extra aux table entries to tell glibc what + * the cache block size is, so it can use the dcbz instruction safely. + */ +#define AT_DCACHEBSIZE 19 +#define AT_ICACHEBSIZE 20 +#define AT_UCACHEBSIZE 21 +/* A special ignored type value for PPC, for glibc compatibility. */ +#define AT_IGNOREPPC 22 +/* + * The requirements here are: + * - keep the final alignment of sp (sp & 0xf) + * - make sure the 32-bit value at the first 16 byte aligned position of + * AUXV is greater than 16 for glibc compatibility. + * AT_IGNOREPPC is used for that. + * - for compatibility with glibc ARCH_DLINFO must always be defined on PPC, + * even if DLINFO_ARCH_ITEMS goes to zero or is undefined. + */ +#define DLINFO_ARCH_ITEMS 5 +#define ARCH_DLINFO \ +do { \ + NEW_AUX_ENT(AT_DCACHEBSIZE, 0x20); \ + NEW_AUX_ENT(AT_ICACHEBSIZE, 0x20); \ + NEW_AUX_ENT(AT_UCACHEBSIZE, 0); \ + /* \ + * Now handle glibc compatibility. \ + */ \ + NEW_AUX_ENT(AT_IGNOREPPC, AT_IGNOREPPC); \ + NEW_AUX_ENT(AT_IGNOREPPC, AT_IGNOREPPC); \ + } while (0) + +static inline void init_thread(struct target_pt_regs *_regs, struct image_info *infop) +{ + abi_ulong pos = infop->start_stack; + abi_ulong tmp; +#if defined(TARGET_PPC64) && !defined(TARGET_ABI32) + abi_ulong entry, toc; +#endif + + _regs->gpr[1] = infop->start_stack; +#if defined(TARGET_PPC64) && !defined(TARGET_ABI32) + entry = ldq_raw(infop->entry) + infop->load_addr; + toc = ldq_raw(infop->entry + 8) + infop->load_addr; + _regs->gpr[2] = toc; + infop->entry = entry; +#endif + _regs->nip = infop->entry; + /* Note that isn't exactly what regular kernel does + * but this is what the ABI wants and is needed to allow + * execution of PPC BSD programs. + */ + /* FIXME - what to for failure of get_user()? */ + get_user_ual(_regs->gpr[3], pos); + pos += sizeof(abi_ulong); + _regs->gpr[4] = pos; + for (tmp = 1; tmp != 0; pos += sizeof(abi_ulong)) + tmp = ldl(pos); + _regs->gpr[5] = pos; +} + +#define USE_ELF_CORE_DUMP +#define ELF_EXEC_PAGESIZE 4096 + +#endif + +#ifdef TARGET_MIPS + +#define ELF_START_MMAP 0x80000000 + +#define elf_check_arch(x) ( (x) == EM_MIPS ) + +#ifdef TARGET_MIPS64 +#define ELF_CLASS ELFCLASS64 +#else +#define ELF_CLASS ELFCLASS32 +#endif +#ifdef TARGET_WORDS_BIGENDIAN +#define ELF_DATA ELFDATA2MSB +#else +#define ELF_DATA ELFDATA2LSB +#endif +#define ELF_ARCH EM_MIPS + +static inline void init_thread(struct target_pt_regs *regs, struct image_info *infop) +{ + regs->cp0_status = 2 << CP0St_KSU; + regs->cp0_epc = infop->entry; + regs->regs[29] = infop->start_stack; +} + +#define USE_ELF_CORE_DUMP +#define ELF_EXEC_PAGESIZE 4096 + +#endif /* TARGET_MIPS */ + +#ifdef TARGET_SH4 + +#define ELF_START_MMAP 0x80000000 + +#define elf_check_arch(x) ( (x) == EM_SH ) + +#define ELF_CLASS ELFCLASS32 +#define ELF_DATA ELFDATA2LSB +#define ELF_ARCH EM_SH + +static inline void init_thread(struct target_pt_regs *regs, struct image_info *infop) +{ + /* Check other registers XXXXX */ + regs->pc = infop->entry; + regs->regs[15] = infop->start_stack; +} + +#define USE_ELF_CORE_DUMP +#define ELF_EXEC_PAGESIZE 4096 + +#endif + +#ifdef TARGET_CRIS + +#define ELF_START_MMAP 0x80000000 + +#define elf_check_arch(x) ( (x) == EM_CRIS ) + +#define ELF_CLASS ELFCLASS32 +#define ELF_DATA ELFDATA2LSB +#define ELF_ARCH EM_CRIS + +static inline void init_thread(struct target_pt_regs *regs, struct image_info *infop) +{ + regs->erp = infop->entry; +} + +#define USE_ELF_CORE_DUMP +#define ELF_EXEC_PAGESIZE 8192 + +#endif + +#ifdef TARGET_M68K + +#define ELF_START_MMAP 0x80000000 + +#define elf_check_arch(x) ( (x) == EM_68K ) + +#define ELF_CLASS ELFCLASS32 +#define ELF_DATA ELFDATA2MSB +#define ELF_ARCH EM_68K + +/* ??? Does this need to do anything? +#define ELF_PLAT_INIT(_r) */ + +static inline void init_thread(struct target_pt_regs *regs, struct image_info *infop) +{ + regs->usp = infop->start_stack; + regs->sr = 0; + regs->pc = infop->entry; +} + +#define USE_ELF_CORE_DUMP +#define ELF_EXEC_PAGESIZE 8192 + +#endif + +#ifdef TARGET_ALPHA + +#define ELF_START_MMAP (0x30000000000ULL) + +#define elf_check_arch(x) ( (x) == ELF_ARCH ) + +#define ELF_CLASS ELFCLASS64 +#define ELF_DATA ELFDATA2MSB +#define ELF_ARCH EM_ALPHA + +static inline void init_thread(struct target_pt_regs *regs, struct image_info *infop) +{ + regs->pc = infop->entry; + regs->ps = 8; + regs->usp = infop->start_stack; + regs->unique = infop->start_data; /* ? */ + printf("Set unique value to " TARGET_FMT_lx " (" TARGET_FMT_lx ")\n", + regs->unique, infop->start_data); +} + +#define USE_ELF_CORE_DUMP +#define ELF_EXEC_PAGESIZE 8192 + +#endif /* TARGET_ALPHA */ + +#ifndef ELF_PLATFORM +#define ELF_PLATFORM (NULL) +#endif + +#ifndef ELF_HWCAP +#define ELF_HWCAP 0 +#endif + +#ifdef TARGET_ABI32 +#undef ELF_CLASS +#define ELF_CLASS ELFCLASS32 +#undef bswaptls +#define bswaptls(ptr) bswap32s(ptr) +#endif + +#include "elf.h" + +struct exec +{ + unsigned int a_info; /* Use macros N_MAGIC, etc for access */ + unsigned int a_text; /* length of text, in bytes */ + unsigned int a_data; /* length of data, in bytes */ + unsigned int a_bss; /* length of uninitialized data area, in bytes */ + unsigned int a_syms; /* length of symbol table data in file, in bytes */ + unsigned int a_entry; /* start address */ + unsigned int a_trsize; /* length of relocation info for text, in bytes */ + unsigned int a_drsize; /* length of relocation info for data, in bytes */ +}; + + +#define N_MAGIC(exec) ((exec).a_info & 0xffff) +#define OMAGIC 0407 +#define NMAGIC 0410 +#define ZMAGIC 0413 +#define QMAGIC 0314 + +/* max code+data+bss space allocated to elf interpreter */ +#define INTERP_MAP_SIZE (32 * 1024 * 1024) + +/* max code+data+bss+brk space allocated to ET_DYN executables */ +#define ET_DYN_MAP_SIZE (128 * 1024 * 1024) + +/* Necessary parameters */ +#define TARGET_ELF_EXEC_PAGESIZE TARGET_PAGE_SIZE +#define TARGET_ELF_PAGESTART(_v) ((_v) & ~(unsigned long)(TARGET_ELF_EXEC_PAGESIZE-1)) +#define TARGET_ELF_PAGEOFFSET(_v) ((_v) & (TARGET_ELF_EXEC_PAGESIZE-1)) + +#define INTERPRETER_NONE 0 +#define INTERPRETER_AOUT 1 +#define INTERPRETER_ELF 2 + +#define DLINFO_ITEMS 12 + +static inline void memcpy_fromfs(void * to, const void * from, unsigned long n) +{ + memcpy(to, from, n); +} + +static int load_aout_interp(void * exptr, int interp_fd); + +#ifdef BSWAP_NEEDED +static void bswap_ehdr(struct elfhdr *ehdr) +{ + bswap16s(&ehdr->e_type); /* Object file type */ + bswap16s(&ehdr->e_machine); /* Architecture */ + bswap32s(&ehdr->e_version); /* Object file version */ + bswaptls(&ehdr->e_entry); /* Entry point virtual address */ + bswaptls(&ehdr->e_phoff); /* Program header table file offset */ + bswaptls(&ehdr->e_shoff); /* Section header table file offset */ + bswap32s(&ehdr->e_flags); /* Processor-specific flags */ + bswap16s(&ehdr->e_ehsize); /* ELF header size in bytes */ + bswap16s(&ehdr->e_phentsize); /* Program header table entry size */ + bswap16s(&ehdr->e_phnum); /* Program header table entry count */ + bswap16s(&ehdr->e_shentsize); /* Section header table entry size */ + bswap16s(&ehdr->e_shnum); /* Section header table entry count */ + bswap16s(&ehdr->e_shstrndx); /* Section header string table index */ +} + +static void bswap_phdr(struct elf_phdr *phdr) +{ + bswap32s(&phdr->p_type); /* Segment type */ + bswaptls(&phdr->p_offset); /* Segment file offset */ + bswaptls(&phdr->p_vaddr); /* Segment virtual address */ + bswaptls(&phdr->p_paddr); /* Segment physical address */ + bswaptls(&phdr->p_filesz); /* Segment size in file */ + bswaptls(&phdr->p_memsz); /* Segment size in memory */ + bswap32s(&phdr->p_flags); /* Segment flags */ + bswaptls(&phdr->p_align); /* Segment alignment */ +} + +static void bswap_shdr(struct elf_shdr *shdr) +{ + bswap32s(&shdr->sh_name); + bswap32s(&shdr->sh_type); + bswaptls(&shdr->sh_flags); + bswaptls(&shdr->sh_addr); + bswaptls(&shdr->sh_offset); + bswaptls(&shdr->sh_size); + bswap32s(&shdr->sh_link); + bswap32s(&shdr->sh_info); + bswaptls(&shdr->sh_addralign); + bswaptls(&shdr->sh_entsize); +} + +static void bswap_sym(struct elf_sym *sym) +{ + bswap32s(&sym->st_name); + bswaptls(&sym->st_value); + bswaptls(&sym->st_size); + bswap16s(&sym->st_shndx); +} +#endif + +/* + * 'copy_elf_strings()' copies argument/envelope strings from user + * memory to free pages in kernel mem. These are in a format ready + * to be put directly into the top of new user memory. + * + */ +static abi_ulong copy_elf_strings(int argc,char ** argv, void **page, + abi_ulong p) +{ + char *tmp, *tmp1, *pag = NULL; + int len, offset = 0; + + if (!p) { + return 0; /* bullet-proofing */ + } + while (argc-- > 0) { + tmp = argv[argc]; + if (!tmp) { + fprintf(stderr, "VFS: argc is wrong"); + exit(-1); + } + tmp1 = tmp; + while (*tmp++); + len = tmp - tmp1; + if (p < len) { /* this shouldn't happen - 128kB */ + return 0; + } + while (len) { + --p; --tmp; --len; + if (--offset < 0) { + offset = p % TARGET_PAGE_SIZE; + pag = (char *)page[p/TARGET_PAGE_SIZE]; + if (!pag) { + pag = (char *)malloc(TARGET_PAGE_SIZE); + memset(pag, 0, TARGET_PAGE_SIZE); + page[p/TARGET_PAGE_SIZE] = pag; + if (!pag) + return 0; + } + } + if (len == 0 || offset == 0) { + *(pag + offset) = *tmp; + } + else { + int bytes_to_copy = (len > offset) ? offset : len; + tmp -= bytes_to_copy; + p -= bytes_to_copy; + offset -= bytes_to_copy; + len -= bytes_to_copy; + memcpy_fromfs(pag + offset, tmp, bytes_to_copy + 1); + } + } + } + return p; +} + +static abi_ulong setup_arg_pages(abi_ulong p, struct linux_binprm *bprm, + struct image_info *info) +{ + abi_ulong stack_base, size, error; + int i; + + /* Create enough stack to hold everything. If we don't use + * it for args, we'll use it for something else... + */ + size = x86_stack_size; + if (size < MAX_ARG_PAGES*TARGET_PAGE_SIZE) + size = MAX_ARG_PAGES*TARGET_PAGE_SIZE; + error = target_mmap(0, + size + qemu_host_page_size, + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANON, + -1, 0); + if (error == -1) { + perror("stk mmap"); + exit(-1); + } + /* we reserve one extra page at the top of the stack as guard */ + target_mprotect(error + size, qemu_host_page_size, PROT_NONE); + + stack_base = error + size - MAX_ARG_PAGES*TARGET_PAGE_SIZE; + p += stack_base; + + for (i = 0 ; i < MAX_ARG_PAGES ; i++) { + if (bprm->page[i]) { + info->rss++; + /* FIXME - check return value of memcpy_to_target() for failure */ + memcpy_to_target(stack_base, bprm->page[i], TARGET_PAGE_SIZE); + free(bprm->page[i]); + } + stack_base += TARGET_PAGE_SIZE; + } + return p; +} + +static void set_brk(abi_ulong start, abi_ulong end) +{ + /* page-align the start and end addresses... */ + start = HOST_PAGE_ALIGN(start); + end = HOST_PAGE_ALIGN(end); + if (end <= start) + return; + if(target_mmap(start, end - start, + PROT_READ | PROT_WRITE | PROT_EXEC, + MAP_FIXED | MAP_PRIVATE | MAP_ANON, -1, 0) == -1) { + perror("cannot mmap brk"); + exit(-1); + } +} + + +/* We need to explicitly zero any fractional pages after the data + section (i.e. bss). This would contain the junk from the file that + should not be in memory. */ +static void padzero(abi_ulong elf_bss, abi_ulong last_bss) +{ + abi_ulong nbyte; + + if (elf_bss >= last_bss) + return; + + /* XXX: this is really a hack : if the real host page size is + smaller than the target page size, some pages after the end + of the file may not be mapped. A better fix would be to + patch target_mmap(), but it is more complicated as the file + size must be known */ + if (qemu_real_host_page_size < qemu_host_page_size) { + abi_ulong end_addr, end_addr1; + end_addr1 = (elf_bss + qemu_real_host_page_size - 1) & + ~(qemu_real_host_page_size - 1); + end_addr = HOST_PAGE_ALIGN(elf_bss); + if (end_addr1 < end_addr) { + mmap((void *)g2h(end_addr1), end_addr - end_addr1, + PROT_READ|PROT_WRITE|PROT_EXEC, + MAP_FIXED|MAP_PRIVATE|MAP_ANON, -1, 0); + } + } + + nbyte = elf_bss & (qemu_host_page_size-1); + if (nbyte) { + nbyte = qemu_host_page_size - nbyte; + do { + /* FIXME - what to do if put_user() fails? */ + put_user_u8(0, elf_bss); + elf_bss++; + } while (--nbyte); + } +} + + +static abi_ulong create_elf_tables(abi_ulong p, int argc, int envc, + struct elfhdr * exec, + abi_ulong load_addr, + abi_ulong load_bias, + abi_ulong interp_load_addr, int ibcs, + struct image_info *info) +{ + abi_ulong sp; + int size; + abi_ulong u_platform; + const char *k_platform; + const int n = sizeof(elf_addr_t); + + sp = p; + u_platform = 0; + k_platform = ELF_PLATFORM; + if (k_platform) { + size_t len = strlen(k_platform) + 1; + sp -= (len + n - 1) & ~(n - 1); + u_platform = sp; + /* FIXME - check return value of memcpy_to_target() for failure */ + memcpy_to_target(sp, k_platform, len); + } + /* + * Force 16 byte _final_ alignment here for generality. + */ + sp = sp &~ (abi_ulong)15; + size = (DLINFO_ITEMS + 1) * 2; + if (k_platform) + size += 2; +#ifdef DLINFO_ARCH_ITEMS + size += DLINFO_ARCH_ITEMS * 2; +#endif + size += envc + argc + 2; + size += (!ibcs ? 3 : 1); /* argc itself */ + size *= n; + if (size & 15) + sp -= 16 - (size & 15); + + /* This is correct because Linux defines + * elf_addr_t as Elf32_Off / Elf64_Off + */ +#define NEW_AUX_ENT(id, val) do { \ + sp -= n; put_user_ual(val, sp); \ + sp -= n; put_user_ual(id, sp); \ + } while(0) + + NEW_AUX_ENT (AT_NULL, 0); + + /* There must be exactly DLINFO_ITEMS entries here. */ + NEW_AUX_ENT(AT_PHDR, (abi_ulong)(load_addr + exec->e_phoff)); + NEW_AUX_ENT(AT_PHENT, (abi_ulong)(sizeof (struct elf_phdr))); + NEW_AUX_ENT(AT_PHNUM, (abi_ulong)(exec->e_phnum)); + NEW_AUX_ENT(AT_PAGESZ, (abi_ulong)(TARGET_PAGE_SIZE)); + NEW_AUX_ENT(AT_BASE, (abi_ulong)(interp_load_addr)); + NEW_AUX_ENT(AT_FLAGS, (abi_ulong)0); + NEW_AUX_ENT(AT_ENTRY, load_bias + exec->e_entry); + NEW_AUX_ENT(AT_UID, (abi_ulong) getuid()); + NEW_AUX_ENT(AT_EUID, (abi_ulong) geteuid()); + NEW_AUX_ENT(AT_GID, (abi_ulong) getgid()); + NEW_AUX_ENT(AT_EGID, (abi_ulong) getegid()); + NEW_AUX_ENT(AT_HWCAP, (abi_ulong) ELF_HWCAP); + NEW_AUX_ENT(AT_CLKTCK, (abi_ulong) sysconf(_SC_CLK_TCK)); + if (k_platform) + NEW_AUX_ENT(AT_PLATFORM, u_platform); +#ifdef ARCH_DLINFO + /* + * ARCH_DLINFO must come last so platform specific code can enforce + * special alignment requirements on the AUXV if necessary (eg. PPC). + */ + ARCH_DLINFO; +#endif +#undef NEW_AUX_ENT + + sp = loader_build_argptr(envc, argc, sp, p, !ibcs); + return sp; +} + + +static abi_ulong load_elf_interp(struct elfhdr * interp_elf_ex, + int interpreter_fd, + abi_ulong *interp_load_addr) +{ + struct elf_phdr *elf_phdata = NULL; + struct elf_phdr *eppnt; + abi_ulong load_addr = 0; + int load_addr_set = 0; + int retval; + abi_ulong last_bss, elf_bss; + abi_ulong error; + int i; + + elf_bss = 0; + last_bss = 0; + error = 0; + +#ifdef BSWAP_NEEDED + bswap_ehdr(interp_elf_ex); +#endif + /* First of all, some simple consistency checks */ + if ((interp_elf_ex->e_type != ET_EXEC && + interp_elf_ex->e_type != ET_DYN) || + !elf_check_arch(interp_elf_ex->e_machine)) { + return ~((abi_ulong)0UL); + } + + + /* Now read in all of the header information */ + + if (sizeof(struct elf_phdr) * interp_elf_ex->e_phnum > TARGET_PAGE_SIZE) + return ~(abi_ulong)0UL; + + elf_phdata = (struct elf_phdr *) + malloc(sizeof(struct elf_phdr) * interp_elf_ex->e_phnum); + + if (!elf_phdata) + return ~((abi_ulong)0UL); + + /* + * If the size of this structure has changed, then punt, since + * we will be doing the wrong thing. + */ + if (interp_elf_ex->e_phentsize != sizeof(struct elf_phdr)) { + free(elf_phdata); + return ~((abi_ulong)0UL); + } + + retval = lseek(interpreter_fd, interp_elf_ex->e_phoff, SEEK_SET); + if(retval >= 0) { + retval = read(interpreter_fd, + (char *) elf_phdata, + sizeof(struct elf_phdr) * interp_elf_ex->e_phnum); + } + if (retval < 0) { + perror("load_elf_interp"); + exit(-1); + free (elf_phdata); + return retval; + } +#ifdef BSWAP_NEEDED + eppnt = elf_phdata; + for (i=0; ie_phnum; i++, eppnt++) { + bswap_phdr(eppnt); + } +#endif + + if (interp_elf_ex->e_type == ET_DYN) { + /* in order to avoid hardcoding the interpreter load + address in qemu, we allocate a big enough memory zone */ + error = target_mmap(0, INTERP_MAP_SIZE, + PROT_NONE, MAP_PRIVATE | MAP_ANON, + -1, 0); + if (error == -1) { + perror("mmap"); + exit(-1); + } + load_addr = error; + load_addr_set = 1; + } + + eppnt = elf_phdata; + for(i=0; ie_phnum; i++, eppnt++) + if (eppnt->p_type == PT_LOAD) { + int elf_type = MAP_PRIVATE | MAP_DENYWRITE; + int elf_prot = 0; + abi_ulong vaddr = 0; + abi_ulong k; + + if (eppnt->p_flags & PF_R) elf_prot = PROT_READ; + if (eppnt->p_flags & PF_W) elf_prot |= PROT_WRITE; + if (eppnt->p_flags & PF_X) elf_prot |= PROT_EXEC; + if (interp_elf_ex->e_type == ET_EXEC || load_addr_set) { + elf_type |= MAP_FIXED; + vaddr = eppnt->p_vaddr; + } + error = target_mmap(load_addr+TARGET_ELF_PAGESTART(vaddr), + eppnt->p_filesz + TARGET_ELF_PAGEOFFSET(eppnt->p_vaddr), + elf_prot, + elf_type, + interpreter_fd, + eppnt->p_offset - TARGET_ELF_PAGEOFFSET(eppnt->p_vaddr)); + + if (error == -1) { + /* Real error */ + close(interpreter_fd); + free(elf_phdata); + return ~((abi_ulong)0UL); + } + + if (!load_addr_set && interp_elf_ex->e_type == ET_DYN) { + load_addr = error; + load_addr_set = 1; + } + + /* + * Find the end of the file mapping for this phdr, and keep + * track of the largest address we see for this. + */ + k = load_addr + eppnt->p_vaddr + eppnt->p_filesz; + if (k > elf_bss) elf_bss = k; + + /* + * Do the same thing for the memory mapping - between + * elf_bss and last_bss is the bss section. + */ + k = load_addr + eppnt->p_memsz + eppnt->p_vaddr; + if (k > last_bss) last_bss = k; + } + + /* Now use mmap to map the library into memory. */ + + close(interpreter_fd); + + /* + * Now fill out the bss section. First pad the last page up + * to the page boundary, and then perform a mmap to make sure + * that there are zeromapped pages up to and including the last + * bss page. + */ + padzero(elf_bss, last_bss); + elf_bss = TARGET_ELF_PAGESTART(elf_bss + qemu_host_page_size - 1); /* What we have mapped so far */ + + /* Map the last of the bss segment */ + if (last_bss > elf_bss) { + target_mmap(elf_bss, last_bss-elf_bss, + PROT_READ|PROT_WRITE|PROT_EXEC, + MAP_FIXED|MAP_PRIVATE|MAP_ANON, -1, 0); + } + free(elf_phdata); + + *interp_load_addr = load_addr; + return ((abi_ulong) interp_elf_ex->e_entry) + load_addr; +} + +static int symfind(const void *s0, const void *s1) +{ + struct elf_sym *key = (struct elf_sym *)s0; + struct elf_sym *sym = (struct elf_sym *)s1; + int result = 0; + if (key->st_value < sym->st_value) { + result = -1; + } else if (key->st_value > sym->st_value + sym->st_size) { + result = 1; + } + return result; +} + +static const char *lookup_symbolxx(struct syminfo *s, target_ulong orig_addr) +{ +#if ELF_CLASS == ELFCLASS32 + struct elf_sym *syms = s->disas_symtab.elf32; +#else + struct elf_sym *syms = s->disas_symtab.elf64; +#endif + + // binary search + struct elf_sym key; + struct elf_sym *sym; + + key.st_value = orig_addr; + + sym = bsearch(&key, syms, s->disas_num_syms, sizeof(*syms), symfind); + if (sym != NULL) { + return s->disas_strtab + sym->st_name; + } + + return ""; +} + +/* FIXME: This should use elf_ops.h */ +static int symcmp(const void *s0, const void *s1) +{ + struct elf_sym *sym0 = (struct elf_sym *)s0; + struct elf_sym *sym1 = (struct elf_sym *)s1; + return (sym0->st_value < sym1->st_value) + ? -1 + : ((sym0->st_value > sym1->st_value) ? 1 : 0); +} + +/* Best attempt to load symbols from this ELF object. */ +static void load_symbols(struct elfhdr *hdr, int fd) +{ + unsigned int i, nsyms; + struct elf_shdr sechdr, symtab, strtab; + char *strings; + struct syminfo *s; + struct elf_sym *syms, *new_syms; + + lseek(fd, hdr->e_shoff, SEEK_SET); + for (i = 0; i < hdr->e_shnum; i++) { + if (read(fd, &sechdr, sizeof(sechdr)) != sizeof(sechdr)) + return; +#ifdef BSWAP_NEEDED + bswap_shdr(&sechdr); +#endif + if (sechdr.sh_type == SHT_SYMTAB) { + symtab = sechdr; + lseek(fd, hdr->e_shoff + + sizeof(sechdr) * sechdr.sh_link, SEEK_SET); + if (read(fd, &strtab, sizeof(strtab)) + != sizeof(strtab)) + return; +#ifdef BSWAP_NEEDED + bswap_shdr(&strtab); +#endif + goto found; + } + } + return; /* Shouldn't happen... */ + + found: + /* Now know where the strtab and symtab are. Snarf them. */ + s = malloc(sizeof(*s)); + syms = malloc(symtab.sh_size); + if (!syms) { + free(s); + return; + } + s->disas_strtab = strings = malloc(strtab.sh_size); + if (!s->disas_strtab) { + free(s); + free(syms); + return; + } + + lseek(fd, symtab.sh_offset, SEEK_SET); + if (read(fd, syms, symtab.sh_size) != symtab.sh_size) { + free(s); + free(syms); + free(strings); + return; + } + + nsyms = symtab.sh_size / sizeof(struct elf_sym); + + i = 0; + while (i < nsyms) { +#ifdef BSWAP_NEEDED + bswap_sym(syms + i); +#endif + // Throw away entries which we do not need. + if (syms[i].st_shndx == SHN_UNDEF || + syms[i].st_shndx >= SHN_LORESERVE || + ELF_ST_TYPE(syms[i].st_info) != STT_FUNC) { + nsyms--; + if (i < nsyms) { + syms[i] = syms[nsyms]; + } + continue; + } +#if defined(TARGET_ARM) || defined (TARGET_MIPS) + /* The bottom address bit marks a Thumb or MIPS16 symbol. */ + syms[i].st_value &= ~(target_ulong)1; +#endif + i++; + } + + /* Attempt to free the storage associated with the local symbols + that we threw away. Whether or not this has any effect on the + memory allocation depends on the malloc implementation and how + many symbols we managed to discard. */ + new_syms = realloc(syms, nsyms * sizeof(*syms)); + if (new_syms == NULL) { + free(s); + free(syms); + free(strings); + return; + } + syms = new_syms; + + qsort(syms, nsyms, sizeof(*syms), symcmp); + + lseek(fd, strtab.sh_offset, SEEK_SET); + if (read(fd, strings, strtab.sh_size) != strtab.sh_size) { + free(s); + free(syms); + free(strings); + return; + } + s->disas_num_syms = nsyms; +#if ELF_CLASS == ELFCLASS32 + s->disas_symtab.elf32 = syms; + s->lookup_symbol = (lookup_symbol_t)lookup_symbolxx; +#else + s->disas_symtab.elf64 = syms; + s->lookup_symbol = (lookup_symbol_t)lookup_symbolxx; +#endif + s->next = syminfos; + syminfos = s; +} + +int load_elf_binary(struct linux_binprm * bprm, struct target_pt_regs * regs, + struct image_info * info) +{ + struct elfhdr elf_ex; + struct elfhdr interp_elf_ex; + struct exec interp_ex; + int interpreter_fd = -1; /* avoid warning */ + abi_ulong load_addr, load_bias; + int load_addr_set = 0; + unsigned int interpreter_type = INTERPRETER_NONE; + unsigned char ibcs2_interpreter; + int i; + abi_ulong mapped_addr; + struct elf_phdr * elf_ppnt; + struct elf_phdr *elf_phdata; + abi_ulong elf_bss, k, elf_brk; + int retval; + char * elf_interpreter; + abi_ulong elf_entry, interp_load_addr = 0; + int status; + abi_ulong start_code, end_code, start_data, end_data; + abi_ulong reloc_func_desc = 0; + abi_ulong elf_stack; + char passed_fileno[6]; + + ibcs2_interpreter = 0; + status = 0; + load_addr = 0; + load_bias = 0; + elf_ex = *((struct elfhdr *) bprm->buf); /* exec-header */ +#ifdef BSWAP_NEEDED + bswap_ehdr(&elf_ex); +#endif + + /* First of all, some simple consistency checks */ + if ((elf_ex.e_type != ET_EXEC && elf_ex.e_type != ET_DYN) || + (! elf_check_arch(elf_ex.e_machine))) { + return -ENOEXEC; + } + + bprm->p = copy_elf_strings(1, &bprm->filename, bprm->page, bprm->p); + bprm->p = copy_elf_strings(bprm->envc,bprm->envp,bprm->page,bprm->p); + bprm->p = copy_elf_strings(bprm->argc,bprm->argv,bprm->page,bprm->p); + if (!bprm->p) { + retval = -E2BIG; + } + + /* Now read in all of the header information */ + elf_phdata = (struct elf_phdr *)malloc(elf_ex.e_phentsize*elf_ex.e_phnum); + if (elf_phdata == NULL) { + return -ENOMEM; + } + + retval = lseek(bprm->fd, elf_ex.e_phoff, SEEK_SET); + if(retval > 0) { + retval = read(bprm->fd, (char *) elf_phdata, + elf_ex.e_phentsize * elf_ex.e_phnum); + } + + if (retval < 0) { + perror("load_elf_binary"); + exit(-1); + free (elf_phdata); + return -errno; + } + +#ifdef BSWAP_NEEDED + elf_ppnt = elf_phdata; + for (i=0; ip_type == PT_INTERP) { + if ( elf_interpreter != NULL ) + { + free (elf_phdata); + free(elf_interpreter); + close(bprm->fd); + return -EINVAL; + } + + /* This is the program interpreter used for + * shared libraries - for now assume that this + * is an a.out format binary + */ + + elf_interpreter = (char *)malloc(elf_ppnt->p_filesz); + + if (elf_interpreter == NULL) { + free (elf_phdata); + close(bprm->fd); + return -ENOMEM; + } + + retval = lseek(bprm->fd, elf_ppnt->p_offset, SEEK_SET); + if(retval >= 0) { + retval = read(bprm->fd, elf_interpreter, elf_ppnt->p_filesz); + } + if(retval < 0) { + perror("load_elf_binary2"); + exit(-1); + } + + /* If the program interpreter is one of these two, + then assume an iBCS2 image. Otherwise assume + a native linux image. */ + + /* JRP - Need to add X86 lib dir stuff here... */ + + if (strcmp(elf_interpreter,"/usr/lib/libc.so.1") == 0 || + strcmp(elf_interpreter,"/usr/lib/ld.so.1") == 0) { + ibcs2_interpreter = 1; + } + +#if 0 + printf("Using ELF interpreter %s\n", path(elf_interpreter)); +#endif + if (retval >= 0) { + retval = open(path(elf_interpreter), O_RDONLY); + if(retval >= 0) { + interpreter_fd = retval; + } + else { + perror(elf_interpreter); + exit(-1); + /* retval = -errno; */ + } + } + + if (retval >= 0) { + retval = lseek(interpreter_fd, 0, SEEK_SET); + if(retval >= 0) { + retval = read(interpreter_fd,bprm->buf,128); + } + } + if (retval >= 0) { + interp_ex = *((struct exec *) bprm->buf); /* aout exec-header */ + interp_elf_ex = *((struct elfhdr *) bprm->buf); /* elf exec-header */ + } + if (retval < 0) { + perror("load_elf_binary3"); + exit(-1); + free (elf_phdata); + free(elf_interpreter); + close(bprm->fd); + return retval; + } + } + elf_ppnt++; + } + + /* Some simple consistency checks for the interpreter */ + if (elf_interpreter){ + interpreter_type = INTERPRETER_ELF | INTERPRETER_AOUT; + + /* Now figure out which format our binary is */ + if ((N_MAGIC(interp_ex) != OMAGIC) && (N_MAGIC(interp_ex) != ZMAGIC) && + (N_MAGIC(interp_ex) != QMAGIC)) { + interpreter_type = INTERPRETER_ELF; + } + + if (interp_elf_ex.e_ident[0] != 0x7f || + strncmp((char *)&interp_elf_ex.e_ident[1], "ELF",3) != 0) { + interpreter_type &= ~INTERPRETER_ELF; + } + + if (!interpreter_type) { + free(elf_interpreter); + free(elf_phdata); + close(bprm->fd); + return -ELIBBAD; + } + } + + /* OK, we are done with that, now set up the arg stuff, + and then start this sucker up */ + + { + char * passed_p; + + if (interpreter_type == INTERPRETER_AOUT) { + snprintf(passed_fileno, sizeof(passed_fileno), "%d", bprm->fd); + passed_p = passed_fileno; + + if (elf_interpreter) { + bprm->p = copy_elf_strings(1,&passed_p,bprm->page,bprm->p); + bprm->argc++; + } + } + if (!bprm->p) { + if (elf_interpreter) { + free(elf_interpreter); + } + free (elf_phdata); + close(bprm->fd); + return -E2BIG; + } + } + + /* OK, This is the point of no return */ + info->end_data = 0; + info->end_code = 0; + info->start_mmap = (abi_ulong)ELF_START_MMAP; + info->mmap = 0; + elf_entry = (abi_ulong) elf_ex.e_entry; + +#if defined(CONFIG_USE_GUEST_BASE) + /* + * In case where user has not explicitly set the guest_base, we + * probe here that should we set it automatically. + */ + if (!have_guest_base) { + /* + * Go through ELF program header table and find out whether + * any of the segments drop below our current mmap_min_addr and + * in that case set guest_base to corresponding address. + */ + for (i = 0, elf_ppnt = elf_phdata; i < elf_ex.e_phnum; + i++, elf_ppnt++) { + if (elf_ppnt->p_type != PT_LOAD) + continue; + if (HOST_PAGE_ALIGN(elf_ppnt->p_vaddr) < mmap_min_addr) { + guest_base = HOST_PAGE_ALIGN(mmap_min_addr); + break; + } + } + } +#endif /* CONFIG_USE_GUEST_BASE */ + + /* Do this so that we can load the interpreter, if need be. We will + change some of these later */ + info->rss = 0; + bprm->p = setup_arg_pages(bprm->p, bprm, info); + info->start_stack = bprm->p; + + /* Now we do a little grungy work by mmaping the ELF image into + * the correct location in memory. At this point, we assume that + * the image should be loaded at fixed address, not at a variable + * address. + */ + + for(i = 0, elf_ppnt = elf_phdata; i < elf_ex.e_phnum; i++, elf_ppnt++) { + int elf_prot = 0; + int elf_flags = 0; + abi_ulong error; + + if (elf_ppnt->p_type != PT_LOAD) + continue; + + if (elf_ppnt->p_flags & PF_R) elf_prot |= PROT_READ; + if (elf_ppnt->p_flags & PF_W) elf_prot |= PROT_WRITE; + if (elf_ppnt->p_flags & PF_X) elf_prot |= PROT_EXEC; + elf_flags = MAP_PRIVATE | MAP_DENYWRITE; + if (elf_ex.e_type == ET_EXEC || load_addr_set) { + elf_flags |= MAP_FIXED; + } else if (elf_ex.e_type == ET_DYN) { + /* Try and get dynamic programs out of the way of the default mmap + base, as well as whatever program they might try to exec. This + is because the brk will follow the loader, and is not movable. */ + /* NOTE: for qemu, we do a big mmap to get enough space + without hardcoding any address */ + error = target_mmap(0, ET_DYN_MAP_SIZE, + PROT_NONE, MAP_PRIVATE | MAP_ANON, + -1, 0); + if (error == -1) { + perror("mmap"); + exit(-1); + } + load_bias = TARGET_ELF_PAGESTART(error - elf_ppnt->p_vaddr); + } + + error = target_mmap(TARGET_ELF_PAGESTART(load_bias + elf_ppnt->p_vaddr), + (elf_ppnt->p_filesz + + TARGET_ELF_PAGEOFFSET(elf_ppnt->p_vaddr)), + elf_prot, + (MAP_FIXED | MAP_PRIVATE | MAP_DENYWRITE), + bprm->fd, + (elf_ppnt->p_offset - + TARGET_ELF_PAGEOFFSET(elf_ppnt->p_vaddr))); + if (error == -1) { + perror("mmap"); + exit(-1); + } + +#ifdef LOW_ELF_STACK + if (TARGET_ELF_PAGESTART(elf_ppnt->p_vaddr) < elf_stack) + elf_stack = TARGET_ELF_PAGESTART(elf_ppnt->p_vaddr); +#endif + + if (!load_addr_set) { + load_addr_set = 1; + load_addr = elf_ppnt->p_vaddr - elf_ppnt->p_offset; + if (elf_ex.e_type == ET_DYN) { + load_bias += error - + TARGET_ELF_PAGESTART(load_bias + elf_ppnt->p_vaddr); + load_addr += load_bias; + reloc_func_desc = load_bias; + } + } + k = elf_ppnt->p_vaddr; + if (k < start_code) + start_code = k; + if (start_data < k) + start_data = k; + k = elf_ppnt->p_vaddr + elf_ppnt->p_filesz; + if (k > elf_bss) + elf_bss = k; + if ((elf_ppnt->p_flags & PF_X) && end_code < k) + end_code = k; + if (end_data < k) + end_data = k; + k = elf_ppnt->p_vaddr + elf_ppnt->p_memsz; + if (k > elf_brk) elf_brk = k; + } + + elf_entry += load_bias; + elf_bss += load_bias; + elf_brk += load_bias; + start_code += load_bias; + end_code += load_bias; + start_data += load_bias; + end_data += load_bias; + + if (elf_interpreter) { + if (interpreter_type & 1) { + elf_entry = load_aout_interp(&interp_ex, interpreter_fd); + } + else if (interpreter_type & 2) { + elf_entry = load_elf_interp(&interp_elf_ex, interpreter_fd, + &interp_load_addr); + } + reloc_func_desc = interp_load_addr; + + close(interpreter_fd); + free(elf_interpreter); + + if (elf_entry == ~((abi_ulong)0UL)) { + printf("Unable to load interpreter\n"); + free(elf_phdata); + exit(-1); + return 0; + } + } + + free(elf_phdata); + + if (qemu_log_enabled()) + load_symbols(&elf_ex, bprm->fd); + + if (interpreter_type != INTERPRETER_AOUT) close(bprm->fd); + info->personality = (ibcs2_interpreter ? PER_SVR4 : PER_LINUX); + +#ifdef LOW_ELF_STACK + info->start_stack = bprm->p = elf_stack - 4; +#endif + bprm->p = create_elf_tables(bprm->p, + bprm->argc, + bprm->envc, + &elf_ex, + load_addr, load_bias, + interp_load_addr, + (interpreter_type == INTERPRETER_AOUT ? 0 : 1), + info); + info->load_addr = reloc_func_desc; + info->start_brk = info->brk = elf_brk; + info->end_code = end_code; + info->start_code = start_code; + info->start_data = start_data; + info->end_data = end_data; + info->start_stack = bprm->p; + + /* Calling set_brk effectively mmaps the pages that we need for the bss and break + sections */ + set_brk(elf_bss, elf_brk); + + padzero(elf_bss, elf_brk); + +#if 0 + printf("(start_brk) %x\n" , info->start_brk); + printf("(end_code) %x\n" , info->end_code); + printf("(start_code) %x\n" , info->start_code); + printf("(end_data) %x\n" , info->end_data); + printf("(start_stack) %x\n" , info->start_stack); + printf("(brk) %x\n" , info->brk); +#endif + + if ( info->personality == PER_SVR4 ) + { + /* Why this, you ask??? Well SVr4 maps page 0 as read-only, + and some applications "depend" upon this behavior. + Since we do not have the power to recompile these, we + emulate the SVr4 behavior. Sigh. */ + mapped_addr = target_mmap(0, qemu_host_page_size, PROT_READ | PROT_EXEC, + MAP_FIXED | MAP_PRIVATE, -1, 0); + } + + info->entry = elf_entry; + + return 0; +} + +static int load_aout_interp(void * exptr, int interp_fd) +{ + printf("a.out interpreter not yet supported\n"); + return(0); +} + +void do_init_thread(struct target_pt_regs *regs, struct image_info *infop) +{ + init_thread(regs, infop); +} diff --git a/bsd-user/errno_defs.h b/bsd-user/errno_defs.h new file mode 100644 index 0000000..1efa502 --- /dev/null +++ b/bsd-user/errno_defs.h @@ -0,0 +1,149 @@ +/* $OpenBSD: errno.h,v 1.20 2007/09/03 14:37:52 millert Exp $ */ +/* $NetBSD: errno.h,v 1.10 1996/01/20 01:33:53 jtc Exp $ */ + +/* + * Copyright (c) 1982, 1986, 1989, 1993 + * The Regents of the University of California. All rights reserved. + * (c) UNIX System Laboratories, Inc. + * All or some portions of this file are derived from material licensed + * to the University of California by American Telephone and Telegraph + * Co. or Unix System Laboratories, Inc. and are reproduced herein with + * the permission of UNIX System Laboratories, Inc. + * + * 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. + * 3. Neither the name of the University 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 REGENTS 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 REGENTS 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. + * + * @(#)errno.h 8.5 (Berkeley) 1/21/94 + */ + +#define TARGET_EPERM 1 /* Operation not permitted */ +#define TARGET_ENOENT 2 /* No such file or directory */ +#define TARGET_ESRCH 3 /* No such process */ +#define TARGET_EINTR 4 /* Interrupted system call */ +#define TARGET_EIO 5 /* Input/output error */ +#define TARGET_ENXIO 6 /* Device not configured */ +#define TARGET_E2BIG 7 /* Argument list too long */ +#define TARGET_ENOEXEC 8 /* Exec format error */ +#define TARGET_EBADF 9 /* Bad file descriptor */ +#define TARGET_ECHILD 10 /* No child processes */ +#define TARGET_EDEADLK 11 /* Resource deadlock avoided */ + /* 11 was EAGAIN */ +#define TARGET_ENOMEM 12 /* Cannot allocate memory */ +#define TARGET_EACCES 13 /* Permission denied */ +#define TARGET_EFAULT 14 /* Bad address */ +#define TARGET_ENOTBLK 15 /* Block device required */ +#define TARGET_EBUSY 16 /* Device busy */ +#define TARGET_EEXIST 17 /* File exists */ +#define TARGET_EXDEV 18 /* Cross-device link */ +#define TARGET_ENODEV 19 /* Operation not supported by device */ +#define TARGET_ENOTDIR 20 /* Not a directory */ +#define TARGET_EISDIR 21 /* Is a directory */ +#define TARGET_EINVAL 22 /* Invalid argument */ +#define TARGET_ENFILE 23 /* Too many open files in system */ +#define TARGET_EMFILE 24 /* Too many open files */ +#define TARGET_ENOTTY 25 /* Inappropriate ioctl for device */ +#define TARGET_ETXTBSY 26 /* Text file busy */ +#define TARGET_EFBIG 27 /* File too large */ +#define TARGET_ENOSPC 28 /* No space left on device */ +#define TARGET_ESPIPE 29 /* Illegal seek */ +#define TARGET_EROFS 30 /* Read-only file system */ +#define TARGET_EMLINK 31 /* Too many links */ +#define TARGET_EPIPE 32 /* Broken pipe */ + +/* math software */ +#define TARGET_EDOM 33 /* Numerical argument out of domain */ +#define TARGET_ERANGE 34 /* Result too large */ + +/* non-blocking and interrupt i/o */ +#define TARGET_EAGAIN 35 /* Resource temporarily unavailable */ +#define TARGET_EWOULDBLOCK EAGAIN /* Operation would block */ +#define TARGET_EINPROGRESS 36 /* Operation now in progress */ +#define TARGET_EALREADY 37 /* Operation already in progress */ + +/* ipc/network software -- argument errors */ +#define TARGET_ENOTSOCK 38 /* Socket operation on non-socket */ +#define TARGET_EDESTADDRREQ 39 /* Destination address required */ +#define TARGET_EMSGSIZE 40 /* Message too long */ +#define TARGET_EPROTOTYPE 41 /* Protocol wrong type for socket */ +#define TARGET_ENOPROTOOPT 42 /* Protocol not available */ +#define TARGET_EPROTONOSUPPORT 43 /* Protocol not supported */ +#define TARGET_ESOCKTNOSUPPORT 44 /* Socket type not supported */ +#define TARGET_EOPNOTSUPP 45 /* Operation not supported */ +#define TARGET_EPFNOSUPPORT 46 /* Protocol family not supported */ +#define TARGET_EAFNOSUPPORT 47 /* Address family not supported by protocol family */ +#define TARGET_EADDRINUSE 48 /* Address already in use */ +#define TARGET_EADDRNOTAVAIL 49 /* Can't assign requested address */ + +/* ipc/network software -- operational errors */ +#define TARGET_ENETDOWN 50 /* Network is down */ +#define TARGET_ENETUNREACH 51 /* Network is unreachable */ +#define TARGET_ENETRESET 52 /* Network dropped connection on reset */ +#define TARGET_ECONNABORTED 53 /* Software caused connection abort */ +#define TARGET_ECONNRESET 54 /* Connection reset by peer */ +#define TARGET_ENOBUFS 55 /* No buffer space available */ +#define TARGET_EISCONN 56 /* Socket is already connected */ +#define TARGET_ENOTCONN 57 /* Socket is not connected */ +#define TARGET_ESHUTDOWN 58 /* Can't send after socket shutdown */ +#define TARGET_ETOOMANYREFS 59 /* Too many references: can't splice */ +#define TARGET_ETIMEDOUT 60 /* Operation timed out */ +#define TARGET_ECONNREFUSED 61 /* Connection refused */ + +#define TARGET_ELOOP 62 /* Too many levels of symbolic links */ +#define TARGET_ENAMETOOLONG 63 /* File name too long */ + +/* should be rearranged */ +#define TARGET_EHOSTDOWN 64 /* Host is down */ +#define TARGET_EHOSTUNREACH 65 /* No route to host */ +#define TARGET_ENOTEMPTY 66 /* Directory not empty */ + +/* quotas & mush */ +#define TARGET_EPROCLIM 67 /* Too many processes */ +#define TARGET_EUSERS 68 /* Too many users */ +#define TARGET_EDQUOT 69 /* Disk quota exceeded */ + +/* Network File System */ +#define TARGET_ESTALE 70 /* Stale NFS file handle */ +#define TARGET_EREMOTE 71 /* Too many levels of remote in path */ +#define TARGET_EBADRPC 72 /* RPC struct is bad */ +#define TARGET_ERPCMISMATCH 73 /* RPC version wrong */ +#define TARGET_EPROGUNAVAIL 74 /* RPC prog. not avail */ +#define TARGET_EPROGMISMATCH 75 /* Program version wrong */ +#define TARGET_EPROCUNAVAIL 76 /* Bad procedure for program */ + +#define TARGET_ENOLCK 77 /* No locks available */ +#define TARGET_ENOSYS 78 /* Function not implemented */ + +#define TARGET_EFTYPE 79 /* Inappropriate file type or format */ +#define TARGET_EAUTH 80 /* Authentication error */ +#define TARGET_ENEEDAUTH 81 /* Need authenticator */ +#define TARGET_EIPSEC 82 /* IPsec processing failure */ +#define TARGET_ENOATTR 83 /* Attribute not found */ +#define TARGET_EILSEQ 84 /* Illegal byte sequence */ +#define TARGET_ENOMEDIUM 85 /* No medium found */ +#define TARGET_EMEDIUMTYPE 86 /* Wrong Medium Type */ +#define TARGET_EOVERFLOW 87 /* Conversion overflow */ +#define TARGET_ECANCELED 88 /* Operation canceled */ +#define TARGET_EIDRM 89 /* Identifier removed */ +#define TARGET_ENOMSG 90 /* No message of desired type */ +#define TARGET_ELAST 90 /* Must be equal largest errno */ diff --git a/bsd-user/freebsd/strace.list b/bsd-user/freebsd/strace.list new file mode 100644 index 0000000..1edf412 --- /dev/null +++ b/bsd-user/freebsd/strace.list @@ -0,0 +1,171 @@ +{ TARGET_FREEBSD_NR___getcwd, "__getcwd", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR___semctl, "__semctl", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR___syscall, "__syscall", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR___sysctl, "__sysctl", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_accept, "accept", "%s(%d,%#x,%#x)", NULL, NULL }, +{ TARGET_FREEBSD_NR_access, "access", "%s(\"%s\",%#o)", NULL, NULL }, +{ TARGET_FREEBSD_NR_acct, "acct", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_adjtime, "adjtime", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_bind, "bind", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_break, "break", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_chdir, "chdir", "%s(\"%s\")", NULL, NULL }, +{ TARGET_FREEBSD_NR_chflags, "chflags", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_chmod, "chmod", "%s(\"%s\",%#o)", NULL, NULL }, +{ TARGET_FREEBSD_NR_chown, "chown", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_chroot, "chroot", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_clock_getres, "clock_getres", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_clock_gettime, "clock_gettime", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_clock_settime, "clock_settime", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_close, "close", "%s(%d)", NULL, NULL }, +{ TARGET_FREEBSD_NR_connect, "connect", "%s(%d,%#x,%d)", NULL, NULL }, +{ TARGET_FREEBSD_NR_dup, "dup", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_dup2, "dup2", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_execve, "execve", NULL, print_execve, NULL }, +{ TARGET_FREEBSD_NR_exit, "exit", "%s(%d)\n", NULL, NULL }, +{ TARGET_FREEBSD_NR_fchdir, "fchdir", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_fchflags, "fchflags", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_fchmod, "fchmod", "%s(%d,%#o)", NULL, NULL }, +{ TARGET_FREEBSD_NR_fchown, "fchown", "%s(\"%s\",%d,%d)", NULL, NULL }, +{ TARGET_FREEBSD_NR_fcntl, "fcntl", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_fhopen, "fhopen", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_fhstat, "fhstat", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_fhstatfs, "fhstatfs", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_flock, "flock", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_fork, "fork", "%s()", NULL, NULL }, +{ TARGET_FREEBSD_NR_fpathconf, "fpathconf", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_fstat, "fstat", "%s(%d,%p)", NULL, NULL }, +{ TARGET_FREEBSD_NR_fstatfs, "fstatfs", "%s(%d,%p)", NULL, NULL }, +{ TARGET_FREEBSD_NR_fsync, "fsync", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_ftruncate, "ftruncate", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_futimes, "futimes", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_getdirentries, "getdirentries", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_freebsd6_mmap, "freebsd6_mmap", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_getegid, "getegid", "%s()", NULL, NULL }, +{ TARGET_FREEBSD_NR_geteuid, "geteuid", "%s()", NULL, NULL }, +{ TARGET_FREEBSD_NR_getfh, "getfh", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_getfsstat, "getfsstat", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_getgid, "getgid", "%s()", NULL, NULL }, +{ TARGET_FREEBSD_NR_getgroups, "getgroups", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_getitimer, "getitimer", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_getlogin, "getlogin", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_getpeername, "getpeername", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_getpgid, "getpgid", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_getpgrp, "getpgrp", "%s()", NULL, NULL }, +{ TARGET_FREEBSD_NR_getpid, "getpid", "%s()", NULL, NULL }, +{ TARGET_FREEBSD_NR_getppid, "getppid", "%s()", NULL, NULL }, +{ TARGET_FREEBSD_NR_getpriority, "getpriority", "%s(%#x,%#x)", NULL, NULL }, +{ TARGET_FREEBSD_NR_getresgid, "getresgid", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_getresuid, "getresuid", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_getrlimit, "getrlimit", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_getrusage, "getrusage", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_getsid, "getsid", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_getsockname, "getsockname", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_getsockopt, "getsockopt", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_gettimeofday, "gettimeofday", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_getuid, "getuid", "%s()", NULL, NULL }, +{ TARGET_FREEBSD_NR_ioctl, "ioctl", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_issetugid, "issetugid", "%s()", NULL, NULL }, +{ TARGET_FREEBSD_NR_kevent, "kevent", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_kill, "kill", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_kqueue, "kqueue", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_ktrace, "ktrace", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_lchown, "lchown", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_link, "link", "%s(\"%s\",\"%s\")", NULL, NULL }, +{ TARGET_FREEBSD_NR_listen, "listen", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_lseek, "lseek", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_lstat, "lstat", "%s(\"%s\",%p)", NULL, NULL }, +{ TARGET_FREEBSD_NR_madvise, "madvise", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_mincore, "mincore", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_minherit, "minherit", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_mkdir, "mkdir", "%s(\"%s\",%#o)", NULL, NULL }, +{ TARGET_FREEBSD_NR_mkfifo, "mkfifo", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_mknod, "mknod", "%s(\"%s\",%#o,%#x)", NULL, NULL }, +{ TARGET_FREEBSD_NR_mlock, "mlock", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_mlockall, "mlockall", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_mmap, "mmap", NULL, NULL, print_syscall_ret_addr }, +{ TARGET_FREEBSD_NR_mount, "mount", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_mprotect, "mprotect", "%s(%#x,%#x,%d)", NULL, NULL }, +{ TARGET_FREEBSD_NR_msgctl, "msgctl", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_msgget, "msgget", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_msgrcv, "msgrcv", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_msgsnd, "msgsnd", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_msync, "msync", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_munlock, "munlock", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_munlockall, "munlockall", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_munmap, "munmap", "%s(%p,%d)", NULL, NULL }, +{ TARGET_FREEBSD_NR_nanosleep, "nanosleep", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_nfssvc, "nfssvc", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_open, "open", "%s(\"%s\",%#x,%#o)", NULL, NULL }, +{ TARGET_FREEBSD_NR_pathconf, "pathconf", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_pipe, "pipe", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_poll, "poll", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_pread, "pread", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_preadv, "preadv", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_profil, "profil", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_ptrace, "ptrace", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_pwrite, "pwrite", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_pwritev, "pwritev", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_quotactl, "quotactl", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_read, "read", "%s(%d,%#x,%d)", NULL, NULL }, +{ TARGET_FREEBSD_NR_readlink, "readlink", "%s(\"%s\",%p,%d)", NULL, NULL }, +{ TARGET_FREEBSD_NR_readv, "readv", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_reboot, "reboot", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_recvfrom, "recvfrom", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_recvmsg, "recvmsg", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_rename, "rename", "%s(\"%s\",\"%s\")", NULL, NULL }, +{ TARGET_FREEBSD_NR_revoke, "revoke", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_rfork, "rfork", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_rmdir, "rmdir", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_sbrk, "sbrk", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_sched_yield, "sched_yield", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_select, "select", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_semget, "semget", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_semop, "semop", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_sendmsg, "sendmsg", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_sendto, "sendto", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_setegid, "setegid", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_seteuid, "seteuid", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_setgid, "setgid", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_setgroups, "setgroups", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_setitimer, "setitimer", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_setlogin, "setlogin", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_setpgid, "setpgid", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_setpriority, "setpriority", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_setregid, "setregid", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_setresgid, "setresgid", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_setresuid, "setresuid", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_setreuid, "setreuid", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_setrlimit, "setrlimit", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_setsid, "setsid", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_setsockopt, "setsockopt", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_settimeofday, "settimeofday", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_setuid, "setuid", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_shmat, "shmat", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_shmctl, "shmctl", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_shmdt, "shmdt", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_shmget, "shmget", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_shutdown, "shutdown", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_sigaction, "sigaction", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_sigaltstack, "sigaltstack", "%s(%p,%p)", NULL, NULL }, +{ TARGET_FREEBSD_NR_sigpending, "sigpending", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_sigprocmask, "sigprocmask", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_sigreturn, "sigreturn", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_sigsuspend, "sigsuspend", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_socket, "socket", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_socketpair, "socketpair", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_sstk, "sstk", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_stat, "stat", "%s(\"%s\",%p)", NULL, NULL }, +{ TARGET_FREEBSD_NR_statfs, "statfs", "%s(\"%s\",%p)", NULL, NULL }, +{ TARGET_FREEBSD_NR_symlink, "symlink", "%s(\"%s\",\"%s\")", NULL, NULL }, +{ TARGET_FREEBSD_NR_sync, "sync", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_sysarch, "sysarch", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_syscall, "syscall", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_truncate, "truncate", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_umask, "umask", "%s(%#o)", NULL, NULL }, +{ TARGET_FREEBSD_NR_unlink, "unlink", "%s(\"%s\")", NULL, NULL }, +{ TARGET_FREEBSD_NR_unmount, "unmount", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_utimes, "utimes", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_vfork, "vfork", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_wait4, "wait4", NULL, NULL, NULL }, +{ TARGET_FREEBSD_NR_write, "write", "%s(%d,%#x,%d)", NULL, NULL }, +{ TARGET_FREEBSD_NR_writev, "writev", "%s(%d,%p,%#x)", NULL, NULL }, diff --git a/bsd-user/freebsd/syscall_nr.h b/bsd-user/freebsd/syscall_nr.h new file mode 100644 index 0000000..36336ab --- /dev/null +++ b/bsd-user/freebsd/syscall_nr.h @@ -0,0 +1,373 @@ +/* + * System call numbers. + * + * $FreeBSD: src/sys/sys/syscall.h,v 1.224 2008/08/24 21:23:08 rwatson Exp $ + * created from FreeBSD: head/sys/kern/syscalls.master 182123 2008-08-24 21:20:35Z rwatson + */ + +#define TARGET_FREEBSD_NR_syscall 0 +#define TARGET_FREEBSD_NR_exit 1 +#define TARGET_FREEBSD_NR_fork 2 +#define TARGET_FREEBSD_NR_read 3 +#define TARGET_FREEBSD_NR_write 4 +#define TARGET_FREEBSD_NR_open 5 +#define TARGET_FREEBSD_NR_close 6 +#define TARGET_FREEBSD_NR_wait4 7 +#define TARGET_FREEBSD_NR_link 9 +#define TARGET_FREEBSD_NR_unlink 10 +#define TARGET_FREEBSD_NR_chdir 12 +#define TARGET_FREEBSD_NR_fchdir 13 +#define TARGET_FREEBSD_NR_mknod 14 +#define TARGET_FREEBSD_NR_chmod 15 +#define TARGET_FREEBSD_NR_chown 16 +#define TARGET_FREEBSD_NR_break 17 +#define TARGET_FREEBSD_NR_freebsd4_getfsstat 18 +#define TARGET_FREEBSD_NR_getpid 20 +#define TARGET_FREEBSD_NR_mount 21 +#define TARGET_FREEBSD_NR_unmount 22 +#define TARGET_FREEBSD_NR_setuid 23 +#define TARGET_FREEBSD_NR_getuid 24 +#define TARGET_FREEBSD_NR_geteuid 25 +#define TARGET_FREEBSD_NR_ptrace 26 +#define TARGET_FREEBSD_NR_recvmsg 27 +#define TARGET_FREEBSD_NR_sendmsg 28 +#define TARGET_FREEBSD_NR_recvfrom 29 +#define TARGET_FREEBSD_NR_accept 30 +#define TARGET_FREEBSD_NR_getpeername 31 +#define TARGET_FREEBSD_NR_getsockname 32 +#define TARGET_FREEBSD_NR_access 33 +#define TARGET_FREEBSD_NR_chflags 34 +#define TARGET_FREEBSD_NR_fchflags 35 +#define TARGET_FREEBSD_NR_sync 36 +#define TARGET_FREEBSD_NR_kill 37 +#define TARGET_FREEBSD_NR_getppid 39 +#define TARGET_FREEBSD_NR_dup 41 +#define TARGET_FREEBSD_NR_pipe 42 +#define TARGET_FREEBSD_NR_getegid 43 +#define TARGET_FREEBSD_NR_profil 44 +#define TARGET_FREEBSD_NR_ktrace 45 +#define TARGET_FREEBSD_NR_getgid 47 +#define TARGET_FREEBSD_NR_getlogin 49 +#define TARGET_FREEBSD_NR_setlogin 50 +#define TARGET_FREEBSD_NR_acct 51 +#define TARGET_FREEBSD_NR_sigaltstack 53 +#define TARGET_FREEBSD_NR_ioctl 54 +#define TARGET_FREEBSD_NR_reboot 55 +#define TARGET_FREEBSD_NR_revoke 56 +#define TARGET_FREEBSD_NR_symlink 57 +#define TARGET_FREEBSD_NR_readlink 58 +#define TARGET_FREEBSD_NR_execve 59 +#define TARGET_FREEBSD_NR_umask 60 +#define TARGET_FREEBSD_NR_chroot 61 +#define TARGET_FREEBSD_NR_msync 65 +#define TARGET_FREEBSD_NR_vfork 66 +#define TARGET_FREEBSD_NR_sbrk 69 +#define TARGET_FREEBSD_NR_sstk 70 +#define TARGET_FREEBSD_NR_vadvise 72 +#define TARGET_FREEBSD_NR_munmap 73 +#define TARGET_FREEBSD_NR_mprotect 74 +#define TARGET_FREEBSD_NR_madvise 75 +#define TARGET_FREEBSD_NR_mincore 78 +#define TARGET_FREEBSD_NR_getgroups 79 +#define TARGET_FREEBSD_NR_setgroups 80 +#define TARGET_FREEBSD_NR_getpgrp 81 +#define TARGET_FREEBSD_NR_setpgid 82 +#define TARGET_FREEBSD_NR_setitimer 83 +#define TARGET_FREEBSD_NR_swapon 85 +#define TARGET_FREEBSD_NR_getitimer 86 +#define TARGET_FREEBSD_NR_getdtablesize 89 +#define TARGET_FREEBSD_NR_dup2 90 +#define TARGET_FREEBSD_NR_fcntl 92 +#define TARGET_FREEBSD_NR_select 93 +#define TARGET_FREEBSD_NR_fsync 95 +#define TARGET_FREEBSD_NR_setpriority 96 +#define TARGET_FREEBSD_NR_socket 97 +#define TARGET_FREEBSD_NR_connect 98 +#define TARGET_FREEBSD_NR_getpriority 100 +#define TARGET_FREEBSD_NR_bind 104 +#define TARGET_FREEBSD_NR_setsockopt 105 +#define TARGET_FREEBSD_NR_listen 106 +#define TARGET_FREEBSD_NR_gettimeofday 116 +#define TARGET_FREEBSD_NR_getrusage 117 +#define TARGET_FREEBSD_NR_getsockopt 118 +#define TARGET_FREEBSD_NR_readv 120 +#define TARGET_FREEBSD_NR_writev 121 +#define TARGET_FREEBSD_NR_settimeofday 122 +#define TARGET_FREEBSD_NR_fchown 123 +#define TARGET_FREEBSD_NR_fchmod 124 +#define TARGET_FREEBSD_NR_setreuid 126 +#define TARGET_FREEBSD_NR_setregid 127 +#define TARGET_FREEBSD_NR_rename 128 +#define TARGET_FREEBSD_NR_flock 131 +#define TARGET_FREEBSD_NR_mkfifo 132 +#define TARGET_FREEBSD_NR_sendto 133 +#define TARGET_FREEBSD_NR_shutdown 134 +#define TARGET_FREEBSD_NR_socketpair 135 +#define TARGET_FREEBSD_NR_mkdir 136 +#define TARGET_FREEBSD_NR_rmdir 137 +#define TARGET_FREEBSD_NR_utimes 138 +#define TARGET_FREEBSD_NR_adjtime 140 +#define TARGET_FREEBSD_NR_setsid 147 +#define TARGET_FREEBSD_NR_quotactl 148 +#define TARGET_FREEBSD_NR_nlm_syscall 154 +#define TARGET_FREEBSD_NR_nfssvc 155 +#define TARGET_FREEBSD_NR_freebsd4_statfs 157 +#define TARGET_FREEBSD_NR_freebsd4_fstatfs 158 +#define TARGET_FREEBSD_NR_lgetfh 160 +#define TARGET_FREEBSD_NR_getfh 161 +#define TARGET_FREEBSD_NR_getdomainname 162 +#define TARGET_FREEBSD_NR_setdomainname 163 +#define TARGET_FREEBSD_NR_uname 164 +#define TARGET_FREEBSD_NR_sysarch 165 +#define TARGET_FREEBSD_NR_rtprio 166 +#define TARGET_FREEBSD_NR_semsys 169 +#define TARGET_FREEBSD_NR_msgsys 170 +#define TARGET_FREEBSD_NR_shmsys 171 +#define TARGET_FREEBSD_NR_freebsd6_pread 173 +#define TARGET_FREEBSD_NR_freebsd6_pwrite 174 +#define TARGET_FREEBSD_NR_setfib 175 +#define TARGET_FREEBSD_NR_ntp_adjtime 176 +#define TARGET_FREEBSD_NR_setgid 181 +#define TARGET_FREEBSD_NR_setegid 182 +#define TARGET_FREEBSD_NR_seteuid 183 +#define TARGET_FREEBSD_NR_stat 188 +#define TARGET_FREEBSD_NR_fstat 189 +#define TARGET_FREEBSD_NR_lstat 190 +#define TARGET_FREEBSD_NR_pathconf 191 +#define TARGET_FREEBSD_NR_fpathconf 192 +#define TARGET_FREEBSD_NR_getrlimit 194 +#define TARGET_FREEBSD_NR_setrlimit 195 +#define TARGET_FREEBSD_NR_getdirentries 196 +#define TARGET_FREEBSD_NR_freebsd6_mmap 197 +#define TARGET_FREEBSD_NR___syscall 198 +#define TARGET_FREEBSD_NR_freebsd6_lseek 199 +#define TARGET_FREEBSD_NR_freebsd6_truncate 200 +#define TARGET_FREEBSD_NR_freebsd6_ftruncate 201 +#define TARGET_FREEBSD_NR___sysctl 202 +#define TARGET_FREEBSD_NR_mlock 203 +#define TARGET_FREEBSD_NR_munlock 204 +#define TARGET_FREEBSD_NR_undelete 205 +#define TARGET_FREEBSD_NR_futimes 206 +#define TARGET_FREEBSD_NR_getpgid 207 +#define TARGET_FREEBSD_NR_poll 209 +#define TARGET_FREEBSD_NR___semctl 220 +#define TARGET_FREEBSD_NR_semget 221 +#define TARGET_FREEBSD_NR_semop 222 +#define TARGET_FREEBSD_NR_msgctl 224 +#define TARGET_FREEBSD_NR_msgget 225 +#define TARGET_FREEBSD_NR_msgsnd 226 +#define TARGET_FREEBSD_NR_msgrcv 227 +#define TARGET_FREEBSD_NR_shmat 228 +#define TARGET_FREEBSD_NR_shmctl 229 +#define TARGET_FREEBSD_NR_shmdt 230 +#define TARGET_FREEBSD_NR_shmget 231 +#define TARGET_FREEBSD_NR_clock_gettime 232 +#define TARGET_FREEBSD_NR_clock_settime 233 +#define TARGET_FREEBSD_NR_clock_getres 234 +#define TARGET_FREEBSD_NR_ktimer_create 235 +#define TARGET_FREEBSD_NR_ktimer_delete 236 +#define TARGET_FREEBSD_NR_ktimer_settime 237 +#define TARGET_FREEBSD_NR_ktimer_gettime 238 +#define TARGET_FREEBSD_NR_ktimer_getoverrun 239 +#define TARGET_FREEBSD_NR_nanosleep 240 +#define TARGET_FREEBSD_NR_ntp_gettime 248 +#define TARGET_FREEBSD_NR_minherit 250 +#define TARGET_FREEBSD_NR_rfork 251 +#define TARGET_FREEBSD_NR_openbsd_poll 252 +#define TARGET_FREEBSD_NR_issetugid 253 +#define TARGET_FREEBSD_NR_lchown 254 +#define TARGET_FREEBSD_NR_aio_read 255 +#define TARGET_FREEBSD_NR_aio_write 256 +#define TARGET_FREEBSD_NR_lio_listio 257 +#define TARGET_FREEBSD_NR_getdents 272 +#define TARGET_FREEBSD_NR_lchmod 274 +#define TARGET_FREEBSD_NR_netbsd_lchown 275 +#define TARGET_FREEBSD_NR_lutimes 276 +#define TARGET_FREEBSD_NR_netbsd_msync 277 +#define TARGET_FREEBSD_NR_nstat 278 +#define TARGET_FREEBSD_NR_nfstat 279 +#define TARGET_FREEBSD_NR_nlstat 280 +#define TARGET_FREEBSD_NR_preadv 289 +#define TARGET_FREEBSD_NR_pwritev 290 +#define TARGET_FREEBSD_NR_freebsd4_fhstatfs 297 +#define TARGET_FREEBSD_NR_fhopen 298 +#define TARGET_FREEBSD_NR_fhstat 299 +#define TARGET_FREEBSD_NR_modnext 300 +#define TARGET_FREEBSD_NR_modstat 301 +#define TARGET_FREEBSD_NR_modfnext 302 +#define TARGET_FREEBSD_NR_modfind 303 +#define TARGET_FREEBSD_NR_kldload 304 +#define TARGET_FREEBSD_NR_kldunload 305 +#define TARGET_FREEBSD_NR_kldfind 306 +#define TARGET_FREEBSD_NR_kldnext 307 +#define TARGET_FREEBSD_NR_kldstat 308 +#define TARGET_FREEBSD_NR_kldfirstmod 309 +#define TARGET_FREEBSD_NR_getsid 310 +#define TARGET_FREEBSD_NR_setresuid 311 +#define TARGET_FREEBSD_NR_setresgid 312 +#define TARGET_FREEBSD_NR_aio_return 314 +#define TARGET_FREEBSD_NR_aio_suspend 315 +#define TARGET_FREEBSD_NR_aio_cancel 316 +#define TARGET_FREEBSD_NR_aio_error 317 +#define TARGET_FREEBSD_NR_oaio_read 318 +#define TARGET_FREEBSD_NR_oaio_write 319 +#define TARGET_FREEBSD_NR_olio_listio 320 +#define TARGET_FREEBSD_NR_yield 321 +#define TARGET_FREEBSD_NR_mlockall 324 +#define TARGET_FREEBSD_NR_munlockall 325 +#define TARGET_FREEBSD_NR___getcwd 326 +#define TARGET_FREEBSD_NR_sched_setparam 327 +#define TARGET_FREEBSD_NR_sched_getparam 328 +#define TARGET_FREEBSD_NR_sched_setscheduler 329 +#define TARGET_FREEBSD_NR_sched_getscheduler 330 +#define TARGET_FREEBSD_NR_sched_yield 331 +#define TARGET_FREEBSD_NR_sched_get_priority_max 332 +#define TARGET_FREEBSD_NR_sched_get_priority_min 333 +#define TARGET_FREEBSD_NR_sched_rr_get_interval 334 +#define TARGET_FREEBSD_NR_utrace 335 +#define TARGET_FREEBSD_NR_freebsd4_sendfile 336 +#define TARGET_FREEBSD_NR_kldsym 337 +#define TARGET_FREEBSD_NR_jail 338 +#define TARGET_FREEBSD_NR_sigprocmask 340 +#define TARGET_FREEBSD_NR_sigsuspend 341 +#define TARGET_FREEBSD_NR_freebsd4_sigaction 342 +#define TARGET_FREEBSD_NR_sigpending 343 +#define TARGET_FREEBSD_NR_freebsd4_sigreturn 344 +#define TARGET_FREEBSD_NR_sigtimedwait 345 +#define TARGET_FREEBSD_NR_sigwaitinfo 346 +#define TARGET_FREEBSD_NR___acl_get_file 347 +#define TARGET_FREEBSD_NR___acl_set_file 348 +#define TARGET_FREEBSD_NR___acl_get_fd 349 +#define TARGET_FREEBSD_NR___acl_set_fd 350 +#define TARGET_FREEBSD_NR___acl_delete_file 351 +#define TARGET_FREEBSD_NR___acl_delete_fd 352 +#define TARGET_FREEBSD_NR___acl_aclcheck_file 353 +#define TARGET_FREEBSD_NR___acl_aclcheck_fd 354 +#define TARGET_FREEBSD_NR_extattrctl 355 +#define TARGET_FREEBSD_NR_extattr_set_file 356 +#define TARGET_FREEBSD_NR_extattr_get_file 357 +#define TARGET_FREEBSD_NR_extattr_delete_file 358 +#define TARGET_FREEBSD_NR_aio_waitcomplete 359 +#define TARGET_FREEBSD_NR_getresuid 360 +#define TARGET_FREEBSD_NR_getresgid 361 +#define TARGET_FREEBSD_NR_kqueue 362 +#define TARGET_FREEBSD_NR_kevent 363 +#define TARGET_FREEBSD_NR_extattr_set_fd 371 +#define TARGET_FREEBSD_NR_extattr_get_fd 372 +#define TARGET_FREEBSD_NR_extattr_delete_fd 373 +#define TARGET_FREEBSD_NR___setugid 374 +#define TARGET_FREEBSD_NR_nfsclnt 375 +#define TARGET_FREEBSD_NR_eaccess 376 +#define TARGET_FREEBSD_NR_nmount 378 +#define TARGET_FREEBSD_NR___mac_get_proc 384 +#define TARGET_FREEBSD_NR___mac_set_proc 385 +#define TARGET_FREEBSD_NR___mac_get_fd 386 +#define TARGET_FREEBSD_NR___mac_get_file 387 +#define TARGET_FREEBSD_NR___mac_set_fd 388 +#define TARGET_FREEBSD_NR___mac_set_file 389 +#define TARGET_FREEBSD_NR_kenv 390 +#define TARGET_FREEBSD_NR_lchflags 391 +#define TARGET_FREEBSD_NR_uuidgen 392 +#define TARGET_FREEBSD_NR_sendfile 393 +#define TARGET_FREEBSD_NR_mac_syscall 394 +#define TARGET_FREEBSD_NR_getfsstat 395 +#define TARGET_FREEBSD_NR_statfs 396 +#define TARGET_FREEBSD_NR_fstatfs 397 +#define TARGET_FREEBSD_NR_fhstatfs 398 +#define TARGET_FREEBSD_NR_ksem_close 400 +#define TARGET_FREEBSD_NR_ksem_post 401 +#define TARGET_FREEBSD_NR_ksem_wait 402 +#define TARGET_FREEBSD_NR_ksem_trywait 403 +#define TARGET_FREEBSD_NR_ksem_init 404 +#define TARGET_FREEBSD_NR_ksem_open 405 +#define TARGET_FREEBSD_NR_ksem_unlink 406 +#define TARGET_FREEBSD_NR_ksem_getvalue 407 +#define TARGET_FREEBSD_NR_ksem_destroy 408 +#define TARGET_FREEBSD_NR___mac_get_pid 409 +#define TARGET_FREEBSD_NR___mac_get_link 410 +#define TARGET_FREEBSD_NR___mac_set_link 411 +#define TARGET_FREEBSD_NR_extattr_set_link 412 +#define TARGET_FREEBSD_NR_extattr_get_link 413 +#define TARGET_FREEBSD_NR_extattr_delete_link 414 +#define TARGET_FREEBSD_NR___mac_execve 415 +#define TARGET_FREEBSD_NR_sigaction 416 +#define TARGET_FREEBSD_NR_sigreturn 417 +#define TARGET_FREEBSD_NR_getcontext 421 +#define TARGET_FREEBSD_NR_setcontext 422 +#define TARGET_FREEBSD_NR_swapcontext 423 +#define TARGET_FREEBSD_NR_swapoff 424 +#define TARGET_FREEBSD_NR___acl_get_link 425 +#define TARGET_FREEBSD_NR___acl_set_link 426 +#define TARGET_FREEBSD_NR___acl_delete_link 427 +#define TARGET_FREEBSD_NR___acl_aclcheck_link 428 +#define TARGET_FREEBSD_NR_sigwait 429 +#define TARGET_FREEBSD_NR_thr_create 430 +#define TARGET_FREEBSD_NR_thr_exit 431 +#define TARGET_FREEBSD_NR_thr_self 432 +#define TARGET_FREEBSD_NR_thr_kill 433 +#define TARGET_FREEBSD_NR__umtx_lock 434 +#define TARGET_FREEBSD_NR__umtx_unlock 435 +#define TARGET_FREEBSD_NR_jail_attach 436 +#define TARGET_FREEBSD_NR_extattr_list_fd 437 +#define TARGET_FREEBSD_NR_extattr_list_file 438 +#define TARGET_FREEBSD_NR_extattr_list_link 439 +#define TARGET_FREEBSD_NR_ksem_timedwait 441 +#define TARGET_FREEBSD_NR_thr_suspend 442 +#define TARGET_FREEBSD_NR_thr_wake 443 +#define TARGET_FREEBSD_NR_kldunloadf 444 +#define TARGET_FREEBSD_NR_audit 445 +#define TARGET_FREEBSD_NR_auditon 446 +#define TARGET_FREEBSD_NR_getauid 447 +#define TARGET_FREEBSD_NR_setauid 448 +#define TARGET_FREEBSD_NR_getaudit 449 +#define TARGET_FREEBSD_NR_setaudit 450 +#define TARGET_FREEBSD_NR_getaudit_addr 451 +#define TARGET_FREEBSD_NR_setaudit_addr 452 +#define TARGET_FREEBSD_NR_auditctl 453 +#define TARGET_FREEBSD_NR__umtx_op 454 +#define TARGET_FREEBSD_NR_thr_new 455 +#define TARGET_FREEBSD_NR_sigqueue 456 +#define TARGET_FREEBSD_NR_kmq_open 457 +#define TARGET_FREEBSD_NR_kmq_setattr 458 +#define TARGET_FREEBSD_NR_kmq_timedreceive 459 +#define TARGET_FREEBSD_NR_kmq_timedsend 460 +#define TARGET_FREEBSD_NR_kmq_notify 461 +#define TARGET_FREEBSD_NR_kmq_unlink 462 +#define TARGET_FREEBSD_NR_abort2 463 +#define TARGET_FREEBSD_NR_thr_set_name 464 +#define TARGET_FREEBSD_NR_aio_fsync 465 +#define TARGET_FREEBSD_NR_rtprio_thread 466 +#define TARGET_FREEBSD_NR_sctp_peeloff 471 +#define TARGET_FREEBSD_NR_sctp_generic_sendmsg 472 +#define TARGET_FREEBSD_NR_sctp_generic_sendmsg_iov 473 +#define TARGET_FREEBSD_NR_sctp_generic_recvmsg 474 +#define TARGET_FREEBSD_NR_pread 475 +#define TARGET_FREEBSD_NR_pwrite 476 +#define TARGET_FREEBSD_NR_mmap 477 +#define TARGET_FREEBSD_NR_lseek 478 +#define TARGET_FREEBSD_NR_truncate 479 +#define TARGET_FREEBSD_NR_ftruncate 480 +#define TARGET_FREEBSD_NR_thr_kill2 481 +#define TARGET_FREEBSD_NR_shm_open 482 +#define TARGET_FREEBSD_NR_shm_unlink 483 +#define TARGET_FREEBSD_NR_cpuset 484 +#define TARGET_FREEBSD_NR_cpuset_setid 485 +#define TARGET_FREEBSD_NR_cpuset_getid 486 +#define TARGET_FREEBSD_NR_cpuset_getaffinity 487 +#define TARGET_FREEBSD_NR_cpuset_setaffinity 488 +#define TARGET_FREEBSD_NR_faccessat 489 +#define TARGET_FREEBSD_NR_fchmodat 490 +#define TARGET_FREEBSD_NR_fchownat 491 +#define TARGET_FREEBSD_NR_fexecve 492 +#define TARGET_FREEBSD_NR_fstatat 493 +#define TARGET_FREEBSD_NR_futimesat 494 +#define TARGET_FREEBSD_NR_linkat 495 +#define TARGET_FREEBSD_NR_mkdirat 496 +#define TARGET_FREEBSD_NR_mkfifoat 497 +#define TARGET_FREEBSD_NR_mknodat 498 +#define TARGET_FREEBSD_NR_openat 499 +#define TARGET_FREEBSD_NR_readlinkat 500 +#define TARGET_FREEBSD_NR_renameat 501 +#define TARGET_FREEBSD_NR_symlinkat 502 +#define TARGET_FREEBSD_NR_unlinkat 503 +#define TARGET_FREEBSD_NR_posix_openpt 504 diff --git a/bsd-user/i386/syscall.h b/bsd-user/i386/syscall.h new file mode 100644 index 0000000..9b34c61 --- /dev/null +++ b/bsd-user/i386/syscall.h @@ -0,0 +1,161 @@ +/* default linux values for the selectors */ +#define __USER_CS (0x23) +#define __USER_DS (0x2B) + +struct target_pt_regs { + long ebx; + long ecx; + long edx; + long esi; + long edi; + long ebp; + long eax; + int xds; + int xes; + long orig_eax; + long eip; + int xcs; + long eflags; + long esp; + int xss; +}; + +/* ioctls */ + +#define TARGET_LDT_ENTRIES 8192 +#define TARGET_LDT_ENTRY_SIZE 8 + +#define TARGET_GDT_ENTRIES 9 +#define TARGET_GDT_ENTRY_TLS_ENTRIES 3 +#define TARGET_GDT_ENTRY_TLS_MIN 6 +#define TARGET_GDT_ENTRY_TLS_MAX (TARGET_GDT_ENTRY_TLS_MIN + TARGET_GDT_ENTRY_TLS_ENTRIES - 1) + +struct target_modify_ldt_ldt_s { + unsigned int entry_number; + abi_ulong base_addr; + unsigned int limit; + unsigned int flags; +}; + +/* vm86 defines */ + +#define TARGET_BIOSSEG 0x0f000 + +#define TARGET_CPU_086 0 +#define TARGET_CPU_186 1 +#define TARGET_CPU_286 2 +#define TARGET_CPU_386 3 +#define TARGET_CPU_486 4 +#define TARGET_CPU_586 5 + +#define TARGET_VM86_SIGNAL 0 /* return due to signal */ +#define TARGET_VM86_UNKNOWN 1 /* unhandled GP fault - IO-instruction or similar */ +#define TARGET_VM86_INTx 2 /* int3/int x instruction (ARG = x) */ +#define TARGET_VM86_STI 3 /* sti/popf/iret instruction enabled virtual interrupts */ + +/* + * Additional return values when invoking new vm86() + */ +#define TARGET_VM86_PICRETURN 4 /* return due to pending PIC request */ +#define TARGET_VM86_TRAP 6 /* return due to DOS-debugger request */ + +/* + * function codes when invoking new vm86() + */ +#define TARGET_VM86_PLUS_INSTALL_CHECK 0 +#define TARGET_VM86_ENTER 1 +#define TARGET_VM86_ENTER_NO_BYPASS 2 +#define TARGET_VM86_REQUEST_IRQ 3 +#define TARGET_VM86_FREE_IRQ 4 +#define TARGET_VM86_GET_IRQ_BITS 5 +#define TARGET_VM86_GET_AND_RESET_IRQ 6 + +/* + * This is the stack-layout seen by the user space program when we have + * done a translation of "SAVE_ALL" from vm86 mode. The real kernel layout + * is 'kernel_vm86_regs' (see below). + */ + +struct target_vm86_regs { +/* + * normal regs, with special meaning for the segment descriptors.. + */ + abi_long ebx; + abi_long ecx; + abi_long edx; + abi_long esi; + abi_long edi; + abi_long ebp; + abi_long eax; + abi_long __null_ds; + abi_long __null_es; + abi_long __null_fs; + abi_long __null_gs; + abi_long orig_eax; + abi_long eip; + unsigned short cs, __csh; + abi_long eflags; + abi_long esp; + unsigned short ss, __ssh; +/* + * these are specific to v86 mode: + */ + unsigned short es, __esh; + unsigned short ds, __dsh; + unsigned short fs, __fsh; + unsigned short gs, __gsh; +}; + +struct target_revectored_struct { + abi_ulong __map[8]; /* 256 bits */ +}; + +struct target_vm86_struct { + struct target_vm86_regs regs; + abi_ulong flags; + abi_ulong screen_bitmap; + abi_ulong cpu_type; + struct target_revectored_struct int_revectored; + struct target_revectored_struct int21_revectored; +}; + +/* + * flags masks + */ +#define TARGET_VM86_SCREEN_BITMAP 0x0001 + +struct target_vm86plus_info_struct { + abi_ulong flags; +#define TARGET_force_return_for_pic (1 << 0) +#define TARGET_vm86dbg_active (1 << 1) /* for debugger */ +#define TARGET_vm86dbg_TFpendig (1 << 2) /* for debugger */ +#define TARGET_is_vm86pus (1 << 31) /* for vm86 internal use */ + unsigned char vm86dbg_intxxtab[32]; /* for debugger */ +}; + +struct target_vm86plus_struct { + struct target_vm86_regs regs; + abi_ulong flags; + abi_ulong screen_bitmap; + abi_ulong cpu_type; + struct target_revectored_struct int_revectored; + struct target_revectored_struct int21_revectored; + struct target_vm86plus_info_struct vm86plus; +}; + +/* FreeBSD sysarch(2) */ +#define TARGET_FREEBSD_I386_GET_LDT 0 +#define TARGET_FREEBSD_I386_SET_LDT 1 + /* I386_IOPL */ +#define TARGET_FREEBSD_I386_GET_IOPERM 3 +#define TARGET_FREEBSD_I386_SET_IOPERM 4 + /* xxxxx */ +#define TARGET_FREEBSD_I386_VM86 6 +#define TARGET_FREEBSD_I386_GET_FSBASE 7 +#define TARGET_FREEBSD_I386_SET_FSBASE 8 +#define TARGET_FREEBSD_I386_GET_GSBASE 9 +#define TARGET_FREEBSD_I386_SET_GSBASE 10 + + +#define UNAME_MACHINE "i386" + diff --git a/bsd-user/i386/target_signal.h b/bsd-user/i386/target_signal.h new file mode 100644 index 0000000..2ef36d1 --- /dev/null +++ b/bsd-user/i386/target_signal.h @@ -0,0 +1,20 @@ +#ifndef TARGET_SIGNAL_H +#define TARGET_SIGNAL_H + +#include "cpu.h" + +/* this struct defines a stack used during syscall handling */ + +typedef struct target_sigaltstack { + abi_ulong ss_sp; + abi_long ss_flags; + abi_ulong ss_size; +} target_stack_t; + + +static inline abi_ulong get_sp_from_cpustate(CPUX86State *state) +{ + return state->regs[R_ESP]; +} + +#endif /* TARGET_SIGNAL_H */ diff --git a/bsd-user/main.c b/bsd-user/main.c new file mode 100644 index 0000000..6b12f8b --- /dev/null +++ b/bsd-user/main.c @@ -0,0 +1,1131 @@ +/* + * qemu user main + * + * Copyright (c) 2003-2008 Fabrice Bellard + * + * 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 . + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "qemu.h" +#include "qemu-common.h" +/* For tb_lock */ +#include "exec-all.h" +#include "tcg.h" +#include "qemu-timer.h" +#include "envlist.h" + +#define DEBUG_LOGFILE "/tmp/qemu.log" + +int singlestep; +#if defined(CONFIG_USE_GUEST_BASE) +unsigned long mmap_min_addr; +unsigned long guest_base; +int have_guest_base; +#endif + +static const char *interp_prefix = CONFIG_QEMU_INTERP_PREFIX; +const char *qemu_uname_release = CONFIG_UNAME_RELEASE; +extern char **environ; +enum BSDType bsd_type; + +/* XXX: on x86 MAP_GROWSDOWN only works if ESP <= address + 32, so + we allocate a bigger stack. Need a better solution, for example + by remapping the process stack directly at the right place */ +unsigned long x86_stack_size = 512 * 1024; + +void gemu_log(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); +} + +#if defined(TARGET_I386) +int cpu_get_pic_interrupt(CPUState *env) +{ + return -1; +} +#endif + +/* These are no-ops because we are not threadsafe. */ +static inline void cpu_exec_start(CPUState *env) +{ +} + +static inline void cpu_exec_end(CPUState *env) +{ +} + +static inline void start_exclusive(void) +{ +} + +static inline void end_exclusive(void) +{ +} + +void fork_start(void) +{ +} + +void fork_end(int child) +{ + if (child) { + gdbserver_fork(thread_env); + } +} + +void cpu_list_lock(void) +{ +} + +void cpu_list_unlock(void) +{ +} + +#ifdef TARGET_I386 +/***********************************************************/ +/* CPUX86 core interface */ + +void cpu_smm_update(CPUState *env) +{ +} + +uint64_t cpu_get_tsc(CPUX86State *env) +{ + return cpu_get_real_ticks(); +} + +static void write_dt(void *ptr, unsigned long addr, unsigned long limit, + int flags) +{ + unsigned int e1, e2; + uint32_t *p; + e1 = (addr << 16) | (limit & 0xffff); + e2 = ((addr >> 16) & 0xff) | (addr & 0xff000000) | (limit & 0x000f0000); + e2 |= flags; + p = ptr; + p[0] = tswap32(e1); + p[1] = tswap32(e2); +} + +static uint64_t *idt_table; +#ifdef TARGET_X86_64 +static void set_gate64(void *ptr, unsigned int type, unsigned int dpl, + uint64_t addr, unsigned int sel) +{ + uint32_t *p, e1, e2; + e1 = (addr & 0xffff) | (sel << 16); + e2 = (addr & 0xffff0000) | 0x8000 | (dpl << 13) | (type << 8); + p = ptr; + p[0] = tswap32(e1); + p[1] = tswap32(e2); + p[2] = tswap32(addr >> 32); + p[3] = 0; +} +/* only dpl matters as we do only user space emulation */ +static void set_idt(int n, unsigned int dpl) +{ + set_gate64(idt_table + n * 2, 0, dpl, 0, 0); +} +#else +static void set_gate(void *ptr, unsigned int type, unsigned int dpl, + uint32_t addr, unsigned int sel) +{ + uint32_t *p, e1, e2; + e1 = (addr & 0xffff) | (sel << 16); + e2 = (addr & 0xffff0000) | 0x8000 | (dpl << 13) | (type << 8); + p = ptr; + p[0] = tswap32(e1); + p[1] = tswap32(e2); +} + +/* only dpl matters as we do only user space emulation */ +static void set_idt(int n, unsigned int dpl) +{ + set_gate(idt_table + n, 0, dpl, 0, 0); +} +#endif + +void cpu_loop(CPUX86State *env) +{ + int trapnr; + abi_ulong pc; + //target_siginfo_t info; + + for(;;) { + trapnr = cpu_x86_exec(env); + switch(trapnr) { + case 0x80: + /* syscall from int $0x80 */ + if (bsd_type == target_freebsd) { + abi_ulong params = (abi_ulong) env->regs[R_ESP] + + sizeof(int32_t); + int32_t syscall_nr = env->regs[R_EAX]; + int32_t arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8; + + if (syscall_nr == TARGET_FREEBSD_NR_syscall) { + get_user_s32(syscall_nr, params); + params += sizeof(int32_t); + } else if (syscall_nr == TARGET_FREEBSD_NR___syscall) { + get_user_s32(syscall_nr, params); + params += sizeof(int64_t); + } + get_user_s32(arg1, params); + params += sizeof(int32_t); + get_user_s32(arg2, params); + params += sizeof(int32_t); + get_user_s32(arg3, params); + params += sizeof(int32_t); + get_user_s32(arg4, params); + params += sizeof(int32_t); + get_user_s32(arg5, params); + params += sizeof(int32_t); + get_user_s32(arg6, params); + params += sizeof(int32_t); + get_user_s32(arg7, params); + params += sizeof(int32_t); + get_user_s32(arg8, params); + env->regs[R_EAX] = do_freebsd_syscall(env, + syscall_nr, + arg1, + arg2, + arg3, + arg4, + arg5, + arg6, + arg7, + arg8); + } else { //if (bsd_type == target_openbsd) + env->regs[R_EAX] = do_openbsd_syscall(env, + env->regs[R_EAX], + env->regs[R_EBX], + env->regs[R_ECX], + env->regs[R_EDX], + env->regs[R_ESI], + env->regs[R_EDI], + env->regs[R_EBP]); + } + if (((abi_ulong)env->regs[R_EAX]) >= (abi_ulong)(-515)) { + env->regs[R_EAX] = -env->regs[R_EAX]; + env->eflags |= CC_C; + } else { + env->eflags &= ~CC_C; + } + break; +#ifndef TARGET_ABI32 + case EXCP_SYSCALL: + /* syscall from syscall intruction */ + if (bsd_type == target_freebsd) + env->regs[R_EAX] = do_freebsd_syscall(env, + env->regs[R_EAX], + env->regs[R_EDI], + env->regs[R_ESI], + env->regs[R_EDX], + env->regs[R_ECX], + env->regs[8], + env->regs[9], 0, 0); + else { //if (bsd_type == target_openbsd) + env->regs[R_EAX] = do_openbsd_syscall(env, + env->regs[R_EAX], + env->regs[R_EDI], + env->regs[R_ESI], + env->regs[R_EDX], + env->regs[10], + env->regs[8], + env->regs[9]); + } + env->eip = env->exception_next_eip; + if (((abi_ulong)env->regs[R_EAX]) >= (abi_ulong)(-515)) { + env->regs[R_EAX] = -env->regs[R_EAX]; + env->eflags |= CC_C; + } else { + env->eflags &= ~CC_C; + } + break; +#endif +#if 0 + case EXCP0B_NOSEG: + case EXCP0C_STACK: + info.si_signo = SIGBUS; + info.si_errno = 0; + info.si_code = TARGET_SI_KERNEL; + info._sifields._sigfault._addr = 0; + queue_signal(env, info.si_signo, &info); + break; + case EXCP0D_GPF: + /* XXX: potential problem if ABI32 */ +#ifndef TARGET_X86_64 + if (env->eflags & VM_MASK) { + handle_vm86_fault(env); + } else +#endif + { + info.si_signo = SIGSEGV; + info.si_errno = 0; + info.si_code = TARGET_SI_KERNEL; + info._sifields._sigfault._addr = 0; + queue_signal(env, info.si_signo, &info); + } + break; + case EXCP0E_PAGE: + info.si_signo = SIGSEGV; + info.si_errno = 0; + if (!(env->error_code & 1)) + info.si_code = TARGET_SEGV_MAPERR; + else + info.si_code = TARGET_SEGV_ACCERR; + info._sifields._sigfault._addr = env->cr[2]; + queue_signal(env, info.si_signo, &info); + break; + case EXCP00_DIVZ: +#ifndef TARGET_X86_64 + if (env->eflags & VM_MASK) { + handle_vm86_trap(env, trapnr); + } else +#endif + { + /* division by zero */ + info.si_signo = SIGFPE; + info.si_errno = 0; + info.si_code = TARGET_FPE_INTDIV; + info._sifields._sigfault._addr = env->eip; + queue_signal(env, info.si_signo, &info); + } + break; + case EXCP01_DB: + case EXCP03_INT3: +#ifndef TARGET_X86_64 + if (env->eflags & VM_MASK) { + handle_vm86_trap(env, trapnr); + } else +#endif + { + info.si_signo = SIGTRAP; + info.si_errno = 0; + if (trapnr == EXCP01_DB) { + info.si_code = TARGET_TRAP_BRKPT; + info._sifields._sigfault._addr = env->eip; + } else { + info.si_code = TARGET_SI_KERNEL; + info._sifields._sigfault._addr = 0; + } + queue_signal(env, info.si_signo, &info); + } + break; + case EXCP04_INTO: + case EXCP05_BOUND: +#ifndef TARGET_X86_64 + if (env->eflags & VM_MASK) { + handle_vm86_trap(env, trapnr); + } else +#endif + { + info.si_signo = SIGSEGV; + info.si_errno = 0; + info.si_code = TARGET_SI_KERNEL; + info._sifields._sigfault._addr = 0; + queue_signal(env, info.si_signo, &info); + } + break; + case EXCP06_ILLOP: + info.si_signo = SIGILL; + info.si_errno = 0; + info.si_code = TARGET_ILL_ILLOPN; + info._sifields._sigfault._addr = env->eip; + queue_signal(env, info.si_signo, &info); + break; +#endif + case EXCP_INTERRUPT: + /* just indicate that signals should be handled asap */ + break; +#if 0 + case EXCP_DEBUG: + { + int sig; + + sig = gdb_handlesig (env, TARGET_SIGTRAP); + if (sig) + { + info.si_signo = sig; + info.si_errno = 0; + info.si_code = TARGET_TRAP_BRKPT; + queue_signal(env, info.si_signo, &info); + } + } + break; +#endif + default: + pc = env->segs[R_CS].base + env->eip; + fprintf(stderr, "qemu: 0x%08lx: unhandled CPU exception 0x%x - aborting\n", + (long)pc, trapnr); + abort(); + } + process_pending_signals(env); + } +} +#endif + +#ifdef TARGET_SPARC +#define SPARC64_STACK_BIAS 2047 + +//#define DEBUG_WIN +/* WARNING: dealing with register windows _is_ complicated. More info + can be found at http://www.sics.se/~psm/sparcstack.html */ +static inline int get_reg_index(CPUSPARCState *env, int cwp, int index) +{ + index = (index + cwp * 16) % (16 * env->nwindows); + /* wrap handling : if cwp is on the last window, then we use the + registers 'after' the end */ + if (index < 8 && env->cwp == env->nwindows - 1) + index += 16 * env->nwindows; + return index; +} + +/* save the register window 'cwp1' */ +static inline void save_window_offset(CPUSPARCState *env, int cwp1) +{ + unsigned int i; + abi_ulong sp_ptr; + + sp_ptr = env->regbase[get_reg_index(env, cwp1, 6)]; +#ifdef TARGET_SPARC64 + if (sp_ptr & 3) + sp_ptr += SPARC64_STACK_BIAS; +#endif +#if defined(DEBUG_WIN) + printf("win_overflow: sp_ptr=0x" TARGET_ABI_FMT_lx " save_cwp=%d\n", + sp_ptr, cwp1); +#endif + for(i = 0; i < 16; i++) { + /* FIXME - what to do if put_user() fails? */ + put_user_ual(env->regbase[get_reg_index(env, cwp1, 8 + i)], sp_ptr); + sp_ptr += sizeof(abi_ulong); + } +} + +static void save_window(CPUSPARCState *env) +{ +#ifndef TARGET_SPARC64 + unsigned int new_wim; + new_wim = ((env->wim >> 1) | (env->wim << (env->nwindows - 1))) & + ((1LL << env->nwindows) - 1); + save_window_offset(env, cpu_cwp_dec(env, env->cwp - 2)); + env->wim = new_wim; +#else + save_window_offset(env, cpu_cwp_dec(env, env->cwp - 2)); + env->cansave++; + env->canrestore--; +#endif +} + +static void restore_window(CPUSPARCState *env) +{ +#ifndef TARGET_SPARC64 + unsigned int new_wim; +#endif + unsigned int i, cwp1; + abi_ulong sp_ptr; + +#ifndef TARGET_SPARC64 + new_wim = ((env->wim << 1) | (env->wim >> (env->nwindows - 1))) & + ((1LL << env->nwindows) - 1); +#endif + + /* restore the invalid window */ + cwp1 = cpu_cwp_inc(env, env->cwp + 1); + sp_ptr = env->regbase[get_reg_index(env, cwp1, 6)]; +#ifdef TARGET_SPARC64 + if (sp_ptr & 3) + sp_ptr += SPARC64_STACK_BIAS; +#endif +#if defined(DEBUG_WIN) + printf("win_underflow: sp_ptr=0x" TARGET_ABI_FMT_lx " load_cwp=%d\n", + sp_ptr, cwp1); +#endif + for(i = 0; i < 16; i++) { + /* FIXME - what to do if get_user() fails? */ + get_user_ual(env->regbase[get_reg_index(env, cwp1, 8 + i)], sp_ptr); + sp_ptr += sizeof(abi_ulong); + } +#ifdef TARGET_SPARC64 + env->canrestore++; + if (env->cleanwin < env->nwindows - 1) + env->cleanwin++; + env->cansave--; +#else + env->wim = new_wim; +#endif +} + +static void flush_windows(CPUSPARCState *env) +{ + int offset, cwp1; + + offset = 1; + for(;;) { + /* if restore would invoke restore_window(), then we can stop */ + cwp1 = cpu_cwp_inc(env, env->cwp + offset); +#ifndef TARGET_SPARC64 + if (env->wim & (1 << cwp1)) + break; +#else + if (env->canrestore == 0) + break; + env->cansave++; + env->canrestore--; +#endif + save_window_offset(env, cwp1); + offset++; + } + cwp1 = cpu_cwp_inc(env, env->cwp + 1); +#ifndef TARGET_SPARC64 + /* set wim so that restore will reload the registers */ + env->wim = 1 << cwp1; +#endif +#if defined(DEBUG_WIN) + printf("flush_windows: nb=%d\n", offset - 1); +#endif +} + +void cpu_loop(CPUSPARCState *env) +{ + int trapnr, ret, syscall_nr; + //target_siginfo_t info; + + while (1) { + trapnr = cpu_sparc_exec (env); + + switch (trapnr) { +#ifndef TARGET_SPARC64 + case 0x80: +#else + /* FreeBSD uses 0x141 for syscalls too */ + case 0x141: + if (bsd_type != target_freebsd) + goto badtrap; + case 0x100: +#endif + syscall_nr = env->gregs[1]; + if (bsd_type == target_freebsd) + ret = do_freebsd_syscall(env, syscall_nr, + env->regwptr[0], env->regwptr[1], + env->regwptr[2], env->regwptr[3], + env->regwptr[4], env->regwptr[5], 0, 0); + else if (bsd_type == target_netbsd) + ret = do_netbsd_syscall(env, syscall_nr, + env->regwptr[0], env->regwptr[1], + env->regwptr[2], env->regwptr[3], + env->regwptr[4], env->regwptr[5]); + else { //if (bsd_type == target_openbsd) +#if defined(TARGET_SPARC64) + syscall_nr &= ~(TARGET_OPENBSD_SYSCALL_G7RFLAG | + TARGET_OPENBSD_SYSCALL_G2RFLAG); +#endif + ret = do_openbsd_syscall(env, syscall_nr, + env->regwptr[0], env->regwptr[1], + env->regwptr[2], env->regwptr[3], + env->regwptr[4], env->regwptr[5]); + } + if ((unsigned int)ret >= (unsigned int)(-515)) { + ret = -ret; +#if defined(TARGET_SPARC64) && !defined(TARGET_ABI32) + env->xcc |= PSR_CARRY; +#else + env->psr |= PSR_CARRY; +#endif + } else { +#if defined(TARGET_SPARC64) && !defined(TARGET_ABI32) + env->xcc &= ~PSR_CARRY; +#else + env->psr &= ~PSR_CARRY; +#endif + } + env->regwptr[0] = ret; + /* next instruction */ +#if defined(TARGET_SPARC64) + if (bsd_type == target_openbsd && + env->gregs[1] & TARGET_OPENBSD_SYSCALL_G2RFLAG) { + env->pc = env->gregs[2]; + env->npc = env->pc + 4; + } else if (bsd_type == target_openbsd && + env->gregs[1] & TARGET_OPENBSD_SYSCALL_G7RFLAG) { + env->pc = env->gregs[7]; + env->npc = env->pc + 4; + } else { + env->pc = env->npc; + env->npc = env->npc + 4; + } +#else + env->pc = env->npc; + env->npc = env->npc + 4; +#endif + break; + case 0x83: /* flush windows */ +#ifdef TARGET_ABI32 + case 0x103: +#endif + flush_windows(env); + /* next instruction */ + env->pc = env->npc; + env->npc = env->npc + 4; + break; +#ifndef TARGET_SPARC64 + case TT_WIN_OVF: /* window overflow */ + save_window(env); + break; + case TT_WIN_UNF: /* window underflow */ + restore_window(env); + break; + case TT_TFAULT: + case TT_DFAULT: +#if 0 + { + info.si_signo = SIGSEGV; + info.si_errno = 0; + /* XXX: check env->error_code */ + info.si_code = TARGET_SEGV_MAPERR; + info._sifields._sigfault._addr = env->mmuregs[4]; + queue_signal(env, info.si_signo, &info); + } +#endif + break; +#else + case TT_SPILL: /* window overflow */ + save_window(env); + break; + case TT_FILL: /* window underflow */ + restore_window(env); + break; + case TT_TFAULT: + case TT_DFAULT: +#if 0 + { + info.si_signo = SIGSEGV; + info.si_errno = 0; + /* XXX: check env->error_code */ + info.si_code = TARGET_SEGV_MAPERR; + if (trapnr == TT_DFAULT) + info._sifields._sigfault._addr = env->dmmuregs[4]; + else + info._sifields._sigfault._addr = env->tsptr->tpc; + //queue_signal(env, info.si_signo, &info); + } +#endif + break; +#endif + case EXCP_INTERRUPT: + /* just indicate that signals should be handled asap */ + break; + case EXCP_DEBUG: + { + int sig; + + sig = gdb_handlesig (env, TARGET_SIGTRAP); +#if 0 + if (sig) + { + info.si_signo = sig; + info.si_errno = 0; + info.si_code = TARGET_TRAP_BRKPT; + //queue_signal(env, info.si_signo, &info); + } +#endif + } + break; + default: +#ifdef TARGET_SPARC64 + badtrap: +#endif + printf ("Unhandled trap: 0x%x\n", trapnr); + cpu_dump_state(env, stderr, fprintf, 0); + exit (1); + } + process_pending_signals (env); + } +} + +#endif + +static void usage(void) +{ + printf("qemu-" TARGET_ARCH " version " QEMU_VERSION ", Copyright (c) 2003-2008 Fabrice Bellard\n" + "usage: qemu-" TARGET_ARCH " [options] program [arguments...]\n" + "BSD CPU emulator (compiled for %s emulation)\n" + "\n" + "Standard options:\n" + "-h print this help\n" + "-g port wait gdb connection to port\n" + "-L path set the elf interpreter prefix (default=%s)\n" + "-s size set the stack size in bytes (default=%ld)\n" + "-cpu model select CPU (-cpu ? for list)\n" + "-drop-ld-preload drop LD_PRELOAD for target process\n" + "-E var=value sets/modifies targets environment variable(s)\n" + "-U var unsets targets environment variable(s)\n" +#if defined(CONFIG_USE_GUEST_BASE) + "-B address set guest_base address to address\n" +#endif + "-bsd type select emulated BSD type FreeBSD/NetBSD/OpenBSD (default)\n" + "\n" + "Debug options:\n" + "-d options activate log (logfile=%s)\n" + "-p pagesize set the host page size to 'pagesize'\n" + "-singlestep always run in singlestep mode\n" + "-strace log system calls\n" + "\n" + "Environment variables:\n" + "QEMU_STRACE Print system calls and arguments similar to the\n" + " 'strace' program. Enable by setting to any value.\n" + "You can use -E and -U options to set/unset environment variables\n" + "for target process. It is possible to provide several variables\n" + "by repeating the option. For example:\n" + " -E var1=val2 -E var2=val2 -U LD_PRELOAD -U LD_DEBUG\n" + "Note that if you provide several changes to single variable\n" + "last change will stay in effect.\n" + , + TARGET_ARCH, + interp_prefix, + x86_stack_size, + DEBUG_LOGFILE); + exit(1); +} + +THREAD CPUState *thread_env; + +/* Assumes contents are already zeroed. */ +void init_task_state(TaskState *ts) +{ + int i; + + ts->used = 1; + ts->first_free = ts->sigqueue_table; + for (i = 0; i < MAX_SIGQUEUE_SIZE - 1; i++) { + ts->sigqueue_table[i].next = &ts->sigqueue_table[i + 1]; + } + ts->sigqueue_table[i].next = NULL; +} + +int main(int argc, char **argv) +{ + const char *filename; + const char *cpu_model; + struct target_pt_regs regs1, *regs = ®s1; + struct image_info info1, *info = &info1; + TaskState ts1, *ts = &ts1; + CPUState *env; + int optind; + const char *r; + int gdbstub_port = 0; + char **target_environ, **wrk; + envlist_t *envlist = NULL; + bsd_type = target_openbsd; + + if (argc <= 1) + usage(); + + /* init debug */ + cpu_set_log_filename(DEBUG_LOGFILE); + + if ((envlist = envlist_create()) == NULL) { + (void) fprintf(stderr, "Unable to allocate envlist\n"); + exit(1); + } + + /* add current environment into the list */ + for (wrk = environ; *wrk != NULL; wrk++) { + (void) envlist_setenv(envlist, *wrk); + } + + cpu_model = NULL; +#if defined(cpudef_setup) + cpudef_setup(); /* parse cpu definitions in target config file (TBD) */ +#endif + + optind = 1; + for(;;) { + if (optind >= argc) + break; + r = argv[optind]; + if (r[0] != '-') + break; + optind++; + r++; + if (!strcmp(r, "-")) { + break; + } else if (!strcmp(r, "d")) { + int mask; + const CPULogItem *item; + + if (optind >= argc) + break; + + r = argv[optind++]; + mask = cpu_str_to_log_mask(r); + if (!mask) { + printf("Log items (comma separated):\n"); + for(item = cpu_log_items; item->mask != 0; item++) { + printf("%-10s %s\n", item->name, item->help); + } + exit(1); + } + cpu_set_log(mask); + } else if (!strcmp(r, "E")) { + r = argv[optind++]; + if (envlist_setenv(envlist, r) != 0) + usage(); + } else if (!strcmp(r, "ignore-environment")) { + envlist_free(envlist); + if ((envlist = envlist_create()) == NULL) { + (void) fprintf(stderr, "Unable to allocate envlist\n"); + exit(1); + } + } else if (!strcmp(r, "U")) { + r = argv[optind++]; + if (envlist_unsetenv(envlist, r) != 0) + usage(); + } else if (!strcmp(r, "s")) { + r = argv[optind++]; + x86_stack_size = strtol(r, (char **)&r, 0); + if (x86_stack_size <= 0) + usage(); + if (*r == 'M') + x86_stack_size *= 1024 * 1024; + else if (*r == 'k' || *r == 'K') + x86_stack_size *= 1024; + } else if (!strcmp(r, "L")) { + interp_prefix = argv[optind++]; + } else if (!strcmp(r, "p")) { + qemu_host_page_size = atoi(argv[optind++]); + if (qemu_host_page_size == 0 || + (qemu_host_page_size & (qemu_host_page_size - 1)) != 0) { + fprintf(stderr, "page size must be a power of two\n"); + exit(1); + } + } else if (!strcmp(r, "g")) { + gdbstub_port = atoi(argv[optind++]); + } else if (!strcmp(r, "r")) { + qemu_uname_release = argv[optind++]; + } else if (!strcmp(r, "cpu")) { + cpu_model = argv[optind++]; + if (strcmp(cpu_model, "?") == 0) { +/* XXX: implement xxx_cpu_list for targets that still miss it */ +#if defined(cpu_list) + cpu_list(stdout, &fprintf); +#endif + exit(1); + } +#if defined(CONFIG_USE_GUEST_BASE) + } else if (!strcmp(r, "B")) { + guest_base = strtol(argv[optind++], NULL, 0); + have_guest_base = 1; +#endif + } else if (!strcmp(r, "drop-ld-preload")) { + (void) envlist_unsetenv(envlist, "LD_PRELOAD"); + } else if (!strcmp(r, "bsd")) { + if (!strcasecmp(argv[optind], "freebsd")) { + bsd_type = target_freebsd; + } else if (!strcasecmp(argv[optind], "netbsd")) { + bsd_type = target_netbsd; + } else if (!strcasecmp(argv[optind], "openbsd")) { + bsd_type = target_openbsd; + } else { + usage(); + } + optind++; + } else if (!strcmp(r, "singlestep")) { + singlestep = 1; + } else if (!strcmp(r, "strace")) { + do_strace = 1; + } else + { + usage(); + } + } + if (optind >= argc) + usage(); + filename = argv[optind]; + + /* Zero out regs */ + memset(regs, 0, sizeof(struct target_pt_regs)); + + /* Zero out image_info */ + memset(info, 0, sizeof(struct image_info)); + + /* Scan interp_prefix dir for replacement files. */ + init_paths(interp_prefix); + + if (cpu_model == NULL) { +#if defined(TARGET_I386) +#ifdef TARGET_X86_64 + cpu_model = "qemu64"; +#else + cpu_model = "qemu32"; +#endif +#elif defined(TARGET_SPARC) +#ifdef TARGET_SPARC64 + cpu_model = "TI UltraSparc II"; +#else + cpu_model = "Fujitsu MB86904"; +#endif +#else + cpu_model = "any"; +#endif + } + cpu_exec_init_all(0); + /* NOTE: we need to init the CPU at this stage to get + qemu_host_page_size */ + env = cpu_init(cpu_model); + if (!env) { + fprintf(stderr, "Unable to find CPU definition\n"); + exit(1); + } +#if defined(TARGET_I386) || defined(TARGET_SPARC) || defined(TARGET_PPC) + cpu_reset(env); +#endif + thread_env = env; + + if (getenv("QEMU_STRACE")) { + do_strace = 1; + } + + target_environ = envlist_to_environ(envlist, NULL); + envlist_free(envlist); + +#if defined(CONFIG_USE_GUEST_BASE) + /* + * Now that page sizes are configured in cpu_init() we can do + * proper page alignment for guest_base. + */ + guest_base = HOST_PAGE_ALIGN(guest_base); + + /* + * Read in mmap_min_addr kernel parameter. This value is used + * When loading the ELF image to determine whether guest_base + * is needed. + * + * When user has explicitly set the quest base, we skip this + * test. + */ + if (!have_guest_base) { + FILE *fp; + + if ((fp = fopen("/proc/sys/vm/mmap_min_addr", "r")) != NULL) { + unsigned long tmp; + if (fscanf(fp, "%lu", &tmp) == 1) { + mmap_min_addr = tmp; + qemu_log("host mmap_min_addr=0x%lx\n", mmap_min_addr); + } + fclose(fp); + } + } +#endif /* CONFIG_USE_GUEST_BASE */ + + if (loader_exec(filename, argv+optind, target_environ, regs, info) != 0) { + printf("Error loading %s\n", filename); + _exit(1); + } + + for (wrk = target_environ; *wrk; wrk++) { + free(*wrk); + } + + free(target_environ); + + if (qemu_log_enabled()) { +#if defined(CONFIG_USE_GUEST_BASE) + qemu_log("guest_base 0x%lx\n", guest_base); +#endif + log_page_dump(); + + qemu_log("start_brk 0x" TARGET_ABI_FMT_lx "\n", info->start_brk); + qemu_log("end_code 0x" TARGET_ABI_FMT_lx "\n", info->end_code); + qemu_log("start_code 0x" TARGET_ABI_FMT_lx "\n", + info->start_code); + qemu_log("start_data 0x" TARGET_ABI_FMT_lx "\n", + info->start_data); + qemu_log("end_data 0x" TARGET_ABI_FMT_lx "\n", info->end_data); + qemu_log("start_stack 0x" TARGET_ABI_FMT_lx "\n", + info->start_stack); + qemu_log("brk 0x" TARGET_ABI_FMT_lx "\n", info->brk); + qemu_log("entry 0x" TARGET_ABI_FMT_lx "\n", info->entry); + } + + target_set_brk(info->brk); + syscall_init(); + signal_init(); + +#if defined(CONFIG_USE_GUEST_BASE) + /* Now that we've loaded the binary, GUEST_BASE is fixed. Delay + generating the prologue until now so that the prologue can take + the real value of GUEST_BASE into account. */ + tcg_prologue_init(&tcg_ctx); +#endif + + /* build Task State */ + memset(ts, 0, sizeof(TaskState)); + init_task_state(ts); + ts->info = info; + env->opaque = ts; + +#if defined(TARGET_I386) + cpu_x86_set_cpl(env, 3); + + env->cr[0] = CR0_PG_MASK | CR0_WP_MASK | CR0_PE_MASK; + env->hflags |= HF_PE_MASK; + if (env->cpuid_features & CPUID_SSE) { + env->cr[4] |= CR4_OSFXSR_MASK; + env->hflags |= HF_OSFXSR_MASK; + } +#ifndef TARGET_ABI32 + /* enable 64 bit mode if possible */ + if (!(env->cpuid_ext2_features & CPUID_EXT2_LM)) { + fprintf(stderr, "The selected x86 CPU does not support 64 bit mode\n"); + exit(1); + } + env->cr[4] |= CR4_PAE_MASK; + env->efer |= MSR_EFER_LMA | MSR_EFER_LME; + env->hflags |= HF_LMA_MASK; +#endif + + /* flags setup : we activate the IRQs by default as in user mode */ + env->eflags |= IF_MASK; + + /* linux register setup */ +#ifndef TARGET_ABI32 + env->regs[R_EAX] = regs->rax; + env->regs[R_EBX] = regs->rbx; + env->regs[R_ECX] = regs->rcx; + env->regs[R_EDX] = regs->rdx; + env->regs[R_ESI] = regs->rsi; + env->regs[R_EDI] = regs->rdi; + env->regs[R_EBP] = regs->rbp; + env->regs[R_ESP] = regs->rsp; + env->eip = regs->rip; +#else + env->regs[R_EAX] = regs->eax; + env->regs[R_EBX] = regs->ebx; + env->regs[R_ECX] = regs->ecx; + env->regs[R_EDX] = regs->edx; + env->regs[R_ESI] = regs->esi; + env->regs[R_EDI] = regs->edi; + env->regs[R_EBP] = regs->ebp; + env->regs[R_ESP] = regs->esp; + env->eip = regs->eip; +#endif + + /* linux interrupt setup */ +#ifndef TARGET_ABI32 + env->idt.limit = 511; +#else + env->idt.limit = 255; +#endif + env->idt.base = target_mmap(0, sizeof(uint64_t) * (env->idt.limit + 1), + PROT_READ|PROT_WRITE, + MAP_ANONYMOUS|MAP_PRIVATE, -1, 0); + idt_table = g2h(env->idt.base); + set_idt(0, 0); + set_idt(1, 0); + set_idt(2, 0); + set_idt(3, 3); + set_idt(4, 3); + set_idt(5, 0); + set_idt(6, 0); + set_idt(7, 0); + set_idt(8, 0); + set_idt(9, 0); + set_idt(10, 0); + set_idt(11, 0); + set_idt(12, 0); + set_idt(13, 0); + set_idt(14, 0); + set_idt(15, 0); + set_idt(16, 0); + set_idt(17, 0); + set_idt(18, 0); + set_idt(19, 0); + set_idt(0x80, 3); + + /* linux segment setup */ + { + uint64_t *gdt_table; + env->gdt.base = target_mmap(0, sizeof(uint64_t) * TARGET_GDT_ENTRIES, + PROT_READ|PROT_WRITE, + MAP_ANONYMOUS|MAP_PRIVATE, -1, 0); + env->gdt.limit = sizeof(uint64_t) * TARGET_GDT_ENTRIES - 1; + gdt_table = g2h(env->gdt.base); +#ifdef TARGET_ABI32 + write_dt(&gdt_table[__USER_CS >> 3], 0, 0xfffff, + DESC_G_MASK | DESC_B_MASK | DESC_P_MASK | DESC_S_MASK | + (3 << DESC_DPL_SHIFT) | (0xa << DESC_TYPE_SHIFT)); +#else + /* 64 bit code segment */ + write_dt(&gdt_table[__USER_CS >> 3], 0, 0xfffff, + DESC_G_MASK | DESC_B_MASK | DESC_P_MASK | DESC_S_MASK | + DESC_L_MASK | + (3 << DESC_DPL_SHIFT) | (0xa << DESC_TYPE_SHIFT)); +#endif + write_dt(&gdt_table[__USER_DS >> 3], 0, 0xfffff, + DESC_G_MASK | DESC_B_MASK | DESC_P_MASK | DESC_S_MASK | + (3 << DESC_DPL_SHIFT) | (0x2 << DESC_TYPE_SHIFT)); + } + + cpu_x86_load_seg(env, R_CS, __USER_CS); + cpu_x86_load_seg(env, R_SS, __USER_DS); +#ifdef TARGET_ABI32 + cpu_x86_load_seg(env, R_DS, __USER_DS); + cpu_x86_load_seg(env, R_ES, __USER_DS); + cpu_x86_load_seg(env, R_FS, __USER_DS); + cpu_x86_load_seg(env, R_GS, __USER_DS); + /* This hack makes Wine work... */ + env->segs[R_FS].selector = 0; +#else + cpu_x86_load_seg(env, R_DS, 0); + cpu_x86_load_seg(env, R_ES, 0); + cpu_x86_load_seg(env, R_FS, 0); + cpu_x86_load_seg(env, R_GS, 0); +#endif +#elif defined(TARGET_SPARC) + { + int i; + env->pc = regs->pc; + env->npc = regs->npc; + env->y = regs->y; + for(i = 0; i < 8; i++) + env->gregs[i] = regs->u_regs[i]; + for(i = 0; i < 8; i++) + env->regwptr[i] = regs->u_regs[i + 8]; + } +#else +#error unsupported target CPU +#endif + + if (gdbstub_port) { + gdbserver_start (gdbstub_port); + gdb_handlesig(env, 0); + } + cpu_loop(env); + /* never exits */ + return 0; +} diff --git a/bsd-user/mmap.c b/bsd-user/mmap.c new file mode 100644 index 0000000..207c774 --- /dev/null +++ b/bsd-user/mmap.c @@ -0,0 +1,559 @@ +/* + * mmap support for qemu + * + * Copyright (c) 2003 - 2008 Fabrice Bellard + * + * 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 . + */ +#include +#include +#include +#include +#include +#include +#include + +#include "qemu.h" +#include "qemu-common.h" +#include "bsd-mman.h" + +//#define DEBUG_MMAP + +#if defined(CONFIG_USE_NPTL) +pthread_mutex_t mmap_mutex; +static int __thread mmap_lock_count; + +void mmap_lock(void) +{ + if (mmap_lock_count++ == 0) { + pthread_mutex_lock(&mmap_mutex); + } +} + +void mmap_unlock(void) +{ + if (--mmap_lock_count == 0) { + pthread_mutex_unlock(&mmap_mutex); + } +} + +/* Grab lock to make sure things are in a consistent state after fork(). */ +void mmap_fork_start(void) +{ + if (mmap_lock_count) + abort(); + pthread_mutex_lock(&mmap_mutex); +} + +void mmap_fork_end(int child) +{ + if (child) + pthread_mutex_init(&mmap_mutex, NULL); + else + pthread_mutex_unlock(&mmap_mutex); +} +#else +/* We aren't threadsafe to start with, so no need to worry about locking. */ +void mmap_lock(void) +{ +} + +void mmap_unlock(void) +{ +} +#endif + +void *qemu_vmalloc(size_t size) +{ + void *p; + mmap_lock(); + /* Use map and mark the pages as used. */ + p = mmap(NULL, size, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANON, -1, 0); + + if (h2g_valid(p)) { + /* Allocated region overlaps guest address space. + This may recurse. */ + abi_ulong addr = h2g(p); + page_set_flags(addr & TARGET_PAGE_MASK, TARGET_PAGE_ALIGN(addr + size), + PAGE_RESERVED); + } + + mmap_unlock(); + return p; +} + +void *qemu_malloc(size_t size) +{ + char * p; + size += 16; + p = qemu_vmalloc(size); + *(size_t *)p = size; + return p + 16; +} + +/* We use map, which is always zero initialized. */ +void * qemu_mallocz(size_t size) +{ + return qemu_malloc(size); +} + +void qemu_free(void *ptr) +{ + /* FIXME: We should unmark the reserved pages here. However this gets + complicated when one target page spans multiple host pages, so we + don't bother. */ + size_t *p; + p = (size_t *)((char *)ptr - 16); + munmap(p, *p); +} + +void *qemu_realloc(void *ptr, size_t size) +{ + size_t old_size, copy; + void *new_ptr; + + if (!ptr) + return qemu_malloc(size); + old_size = *(size_t *)((char *)ptr - 16); + copy = old_size < size ? old_size : size; + new_ptr = qemu_malloc(size); + memcpy(new_ptr, ptr, copy); + qemu_free(ptr); + return new_ptr; +} + +/* NOTE: all the constants are the HOST ones, but addresses are target. */ +int target_mprotect(abi_ulong start, abi_ulong len, int prot) +{ + abi_ulong end, host_start, host_end, addr; + int prot1, ret; + +#ifdef DEBUG_MMAP + printf("mprotect: start=0x" TARGET_FMT_lx + " len=0x" TARGET_FMT_lx " prot=%c%c%c\n", start, len, + prot & PROT_READ ? 'r' : '-', + prot & PROT_WRITE ? 'w' : '-', + prot & PROT_EXEC ? 'x' : '-'); +#endif + + if ((start & ~TARGET_PAGE_MASK) != 0) + return -EINVAL; + len = TARGET_PAGE_ALIGN(len); + end = start + len; + if (end < start) + return -EINVAL; + prot &= PROT_READ | PROT_WRITE | PROT_EXEC; + if (len == 0) + return 0; + + mmap_lock(); + host_start = start & qemu_host_page_mask; + host_end = HOST_PAGE_ALIGN(end); + if (start > host_start) { + /* handle host page containing start */ + prot1 = prot; + for(addr = host_start; addr < start; addr += TARGET_PAGE_SIZE) { + prot1 |= page_get_flags(addr); + } + if (host_end == host_start + qemu_host_page_size) { + for(addr = end; addr < host_end; addr += TARGET_PAGE_SIZE) { + prot1 |= page_get_flags(addr); + } + end = host_end; + } + ret = mprotect(g2h(host_start), qemu_host_page_size, prot1 & PAGE_BITS); + if (ret != 0) + goto error; + host_start += qemu_host_page_size; + } + if (end < host_end) { + prot1 = prot; + for(addr = end; addr < host_end; addr += TARGET_PAGE_SIZE) { + prot1 |= page_get_flags(addr); + } + ret = mprotect(g2h(host_end - qemu_host_page_size), qemu_host_page_size, + prot1 & PAGE_BITS); + if (ret != 0) + goto error; + host_end -= qemu_host_page_size; + } + + /* handle the pages in the middle */ + if (host_start < host_end) { + ret = mprotect(g2h(host_start), host_end - host_start, prot); + if (ret != 0) + goto error; + } + page_set_flags(start, start + len, prot | PAGE_VALID); + mmap_unlock(); + return 0; +error: + mmap_unlock(); + return ret; +} + +/* map an incomplete host page */ +static int mmap_frag(abi_ulong real_start, + abi_ulong start, abi_ulong end, + int prot, int flags, int fd, abi_ulong offset) +{ + abi_ulong real_end, addr; + void *host_start; + int prot1, prot_new; + + real_end = real_start + qemu_host_page_size; + host_start = g2h(real_start); + + /* get the protection of the target pages outside the mapping */ + prot1 = 0; + for(addr = real_start; addr < real_end; addr++) { + if (addr < start || addr >= end) + prot1 |= page_get_flags(addr); + } + + if (prot1 == 0) { + /* no page was there, so we allocate one */ + void *p = mmap(host_start, qemu_host_page_size, prot, + flags | MAP_ANON, -1, 0); + if (p == MAP_FAILED) + return -1; + prot1 = prot; + } + prot1 &= PAGE_BITS; + + prot_new = prot | prot1; + if (!(flags & MAP_ANON)) { + /* msync() won't work here, so we return an error if write is + possible while it is a shared mapping */ + if ((flags & TARGET_BSD_MAP_FLAGMASK) == MAP_SHARED && + (prot & PROT_WRITE)) + return -1; + + /* adjust protection to be able to read */ + if (!(prot1 & PROT_WRITE)) + mprotect(host_start, qemu_host_page_size, prot1 | PROT_WRITE); + + /* read the corresponding file data */ + pread(fd, g2h(start), end - start, offset); + + /* put final protection */ + if (prot_new != (prot1 | PROT_WRITE)) + mprotect(host_start, qemu_host_page_size, prot_new); + } else { + /* just update the protection */ + if (prot_new != prot1) { + mprotect(host_start, qemu_host_page_size, prot_new); + } + } + return 0; +} + +#if defined(__CYGWIN__) +/* Cygwin doesn't have a whole lot of address space. */ +static abi_ulong mmap_next_start = 0x18000000; +#else +static abi_ulong mmap_next_start = 0x40000000; +#endif + +unsigned long last_brk; + +/* find a free memory area of size 'size'. The search starts at + 'start'. If 'start' == 0, then a default start address is used. + Return -1 if error. +*/ +/* page_init() marks pages used by the host as reserved to be sure not + to use them. */ +static abi_ulong mmap_find_vma(abi_ulong start, abi_ulong size) +{ + abi_ulong addr, addr1, addr_start; + int prot; + unsigned long new_brk; + + new_brk = (unsigned long)sbrk(0); + if (last_brk && last_brk < new_brk && last_brk == (target_ulong)last_brk) { + /* This is a hack to catch the host allocating memory with brk(). + If it uses mmap then we loose. + FIXME: We really want to avoid the host allocating memory in + the first place, and maybe leave some slack to avoid switching + to mmap. */ + page_set_flags(last_brk & TARGET_PAGE_MASK, + TARGET_PAGE_ALIGN(new_brk), + PAGE_RESERVED); + } + last_brk = new_brk; + + size = HOST_PAGE_ALIGN(size); + start = start & qemu_host_page_mask; + addr = start; + if (addr == 0) + addr = mmap_next_start; + addr_start = addr; + for(;;) { + prot = 0; + for(addr1 = addr; addr1 < (addr + size); addr1 += TARGET_PAGE_SIZE) { + prot |= page_get_flags(addr1); + } + if (prot == 0) + break; + addr += qemu_host_page_size; + /* we found nothing */ + if (addr == addr_start) + return (abi_ulong)-1; + } + if (start == 0) + mmap_next_start = addr + size; + return addr; +} + +/* NOTE: all the constants are the HOST ones */ +abi_long target_mmap(abi_ulong start, abi_ulong len, int prot, + int flags, int fd, abi_ulong offset) +{ + abi_ulong ret, end, real_start, real_end, retaddr, host_offset, host_len; + unsigned long host_start; + + mmap_lock(); +#ifdef DEBUG_MMAP + { + printf("mmap: start=0x" TARGET_FMT_lx + " len=0x" TARGET_FMT_lx " prot=%c%c%c flags=", + start, len, + prot & PROT_READ ? 'r' : '-', + prot & PROT_WRITE ? 'w' : '-', + prot & PROT_EXEC ? 'x' : '-'); + if (flags & MAP_FIXED) + printf("MAP_FIXED "); + if (flags & MAP_ANON) + printf("MAP_ANON "); + switch(flags & TARGET_BSD_MAP_FLAGMASK) { + case MAP_PRIVATE: + printf("MAP_PRIVATE "); + break; + case MAP_SHARED: + printf("MAP_SHARED "); + break; + default: + printf("[MAP_FLAGMASK=0x%x] ", flags & TARGET_BSD_MAP_FLAGMASK); + break; + } + printf("fd=%d offset=" TARGET_FMT_lx "\n", fd, offset); + } +#endif + + if (offset & ~TARGET_PAGE_MASK) { + errno = EINVAL; + goto fail; + } + + len = TARGET_PAGE_ALIGN(len); + if (len == 0) + goto the_end; + real_start = start & qemu_host_page_mask; + + if (!(flags & MAP_FIXED)) { + abi_ulong mmap_start; + void *p; + host_offset = offset & qemu_host_page_mask; + host_len = len + offset - host_offset; + host_len = HOST_PAGE_ALIGN(host_len); + mmap_start = mmap_find_vma(real_start, host_len); + if (mmap_start == (abi_ulong)-1) { + errno = ENOMEM; + goto fail; + } + /* Note: we prefer to control the mapping address. It is + especially important if qemu_host_page_size > + qemu_real_host_page_size */ + p = mmap(g2h(mmap_start), + host_len, prot, flags | MAP_FIXED, fd, host_offset); + if (p == MAP_FAILED) + goto fail; + /* update start so that it points to the file position at 'offset' */ + host_start = (unsigned long)p; + if (!(flags & MAP_ANON)) + host_start += offset - host_offset; + start = h2g(host_start); + } else { + int flg; + target_ulong addr; + + if (start & ~TARGET_PAGE_MASK) { + errno = EINVAL; + goto fail; + } + end = start + len; + real_end = HOST_PAGE_ALIGN(end); + + for(addr = real_start; addr < real_end; addr += TARGET_PAGE_SIZE) { + flg = page_get_flags(addr); + if (flg & PAGE_RESERVED) { + errno = ENXIO; + goto fail; + } + } + + /* worst case: we cannot map the file because the offset is not + aligned, so we read it */ + if (!(flags & MAP_ANON) && + (offset & ~qemu_host_page_mask) != (start & ~qemu_host_page_mask)) { + /* msync() won't work here, so we return an error if write is + possible while it is a shared mapping */ + if ((flags & TARGET_BSD_MAP_FLAGMASK) == MAP_SHARED && + (prot & PROT_WRITE)) { + errno = EINVAL; + goto fail; + } + retaddr = target_mmap(start, len, prot | PROT_WRITE, + MAP_FIXED | MAP_PRIVATE | MAP_ANON, + -1, 0); + if (retaddr == -1) + goto fail; + pread(fd, g2h(start), len, offset); + if (!(prot & PROT_WRITE)) { + ret = target_mprotect(start, len, prot); + if (ret != 0) { + start = ret; + goto the_end; + } + } + goto the_end; + } + + /* handle the start of the mapping */ + if (start > real_start) { + if (real_end == real_start + qemu_host_page_size) { + /* one single host page */ + ret = mmap_frag(real_start, start, end, + prot, flags, fd, offset); + if (ret == -1) + goto fail; + goto the_end1; + } + ret = mmap_frag(real_start, start, real_start + qemu_host_page_size, + prot, flags, fd, offset); + if (ret == -1) + goto fail; + real_start += qemu_host_page_size; + } + /* handle the end of the mapping */ + if (end < real_end) { + ret = mmap_frag(real_end - qemu_host_page_size, + real_end - qemu_host_page_size, real_end, + prot, flags, fd, + offset + real_end - qemu_host_page_size - start); + if (ret == -1) + goto fail; + real_end -= qemu_host_page_size; + } + + /* map the middle (easier) */ + if (real_start < real_end) { + void *p; + unsigned long offset1; + if (flags & MAP_ANON) + offset1 = 0; + else + offset1 = offset + real_start - start; + p = mmap(g2h(real_start), real_end - real_start, + prot, flags, fd, offset1); + if (p == MAP_FAILED) + goto fail; + } + } + the_end1: + page_set_flags(start, start + len, prot | PAGE_VALID); + the_end: +#ifdef DEBUG_MMAP + printf("ret=0x" TARGET_FMT_lx "\n", start); + page_dump(stdout); + printf("\n"); +#endif + mmap_unlock(); + return start; +fail: + mmap_unlock(); + return -1; +} + +int target_munmap(abi_ulong start, abi_ulong len) +{ + abi_ulong end, real_start, real_end, addr; + int prot, ret; + +#ifdef DEBUG_MMAP + printf("munmap: start=0x%lx len=0x%lx\n", start, len); +#endif + if (start & ~TARGET_PAGE_MASK) + return -EINVAL; + len = TARGET_PAGE_ALIGN(len); + if (len == 0) + return -EINVAL; + mmap_lock(); + end = start + len; + real_start = start & qemu_host_page_mask; + real_end = HOST_PAGE_ALIGN(end); + + if (start > real_start) { + /* handle host page containing start */ + prot = 0; + for(addr = real_start; addr < start; addr += TARGET_PAGE_SIZE) { + prot |= page_get_flags(addr); + } + if (real_end == real_start + qemu_host_page_size) { + for(addr = end; addr < real_end; addr += TARGET_PAGE_SIZE) { + prot |= page_get_flags(addr); + } + end = real_end; + } + if (prot != 0) + real_start += qemu_host_page_size; + } + if (end < real_end) { + prot = 0; + for(addr = end; addr < real_end; addr += TARGET_PAGE_SIZE) { + prot |= page_get_flags(addr); + } + if (prot != 0) + real_end -= qemu_host_page_size; + } + + ret = 0; + /* unmap what we can */ + if (real_start < real_end) { + ret = munmap(g2h(real_start), real_end - real_start); + } + + if (ret == 0) + page_set_flags(start, start + len, 0); + mmap_unlock(); + return ret; +} + +int target_msync(abi_ulong start, abi_ulong len, int flags) +{ + abi_ulong end; + + if (start & ~TARGET_PAGE_MASK) + return -EINVAL; + len = TARGET_PAGE_ALIGN(len); + end = start + len; + if (end < start) + return -EINVAL; + if (end == start) + return 0; + + start &= qemu_host_page_mask; + return msync(g2h(start), end - start, flags); +} diff --git a/bsd-user/netbsd/strace.list b/bsd-user/netbsd/strace.list new file mode 100644 index 0000000..5609d70 --- /dev/null +++ b/bsd-user/netbsd/strace.list @@ -0,0 +1,145 @@ +{ TARGET_NETBSD_NR___getcwd, "__getcwd", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR___syscall, "__syscall", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR___sysctl, "__sysctl", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_accept, "accept", "%s(%d,%#x,%#x)", NULL, NULL }, +{ TARGET_NETBSD_NR_access, "access", "%s(\"%s\",%#o)", NULL, NULL }, +{ TARGET_NETBSD_NR_acct, "acct", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_adjtime, "adjtime", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_bind, "bind", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_break, "break", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_chdir, "chdir", "%s(\"%s\")", NULL, NULL }, +{ TARGET_NETBSD_NR_chflags, "chflags", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_chmod, "chmod", "%s(\"%s\",%#o)", NULL, NULL }, +{ TARGET_NETBSD_NR_chown, "chown", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_chroot, "chroot", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_clock_getres, "clock_getres", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_clock_gettime, "clock_gettime", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_clock_settime, "clock_settime", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_close, "close", "%s(%d)", NULL, NULL }, +{ TARGET_NETBSD_NR_connect, "connect", "%s(%d,%#x,%d)", NULL, NULL }, +{ TARGET_NETBSD_NR_dup, "dup", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_dup2, "dup2", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_execve, "execve", NULL, print_execve, NULL }, +{ TARGET_NETBSD_NR_exit, "exit", "%s(%d)\n", NULL, NULL }, +{ TARGET_NETBSD_NR_fchdir, "fchdir", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_fchflags, "fchflags", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_fchmod, "fchmod", "%s(%d,%#o)", NULL, NULL }, +{ TARGET_NETBSD_NR_fchown, "fchown", "%s(\"%s\",%d,%d)", NULL, NULL }, +{ TARGET_NETBSD_NR_fcntl, "fcntl", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_flock, "flock", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_fork, "fork", "%s()", NULL, NULL }, +{ TARGET_NETBSD_NR_fpathconf, "fpathconf", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_fsync, "fsync", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_ftruncate, "ftruncate", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_futimes, "futimes", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_getegid, "getegid", "%s()", NULL, NULL }, +{ TARGET_NETBSD_NR_geteuid, "geteuid", "%s()", NULL, NULL }, +{ TARGET_NETBSD_NR_getgid, "getgid", "%s()", NULL, NULL }, +{ TARGET_NETBSD_NR_getgroups, "getgroups", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_getitimer, "getitimer", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_getpeername, "getpeername", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_getpgid, "getpgid", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_getpgrp, "getpgrp", "%s()", NULL, NULL }, +{ TARGET_NETBSD_NR_getpid, "getpid", "%s()", NULL, NULL }, +{ TARGET_NETBSD_NR_getppid, "getppid", "%s()", NULL, NULL }, +{ TARGET_NETBSD_NR_getpriority, "getpriority", "%s(%#x,%#x)", NULL, NULL }, +{ TARGET_NETBSD_NR_getrlimit, "getrlimit", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_getrusage, "getrusage", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_getsid, "getsid", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_getsockname, "getsockname", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_getsockopt, "getsockopt", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_gettimeofday, "gettimeofday", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_getuid, "getuid", "%s()", NULL, NULL }, +{ TARGET_NETBSD_NR_ioctl, "ioctl", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_issetugid, "issetugid", "%s()", NULL, NULL }, +{ TARGET_NETBSD_NR_kevent, "kevent", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_kill, "kill", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_kqueue, "kqueue", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_ktrace, "ktrace", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_lchown, "lchown", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_lfs_bmapv, "lfs_bmapv", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_lfs_markv, "lfs_markv", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_lfs_segclean, "lfs_segclean", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_lfs_segwait, "lfs_segwait", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_link, "link", "%s(\"%s\",\"%s\")", NULL, NULL }, +{ TARGET_NETBSD_NR_listen, "listen", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_lseek, "lseek", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_madvise, "madvise", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_mincore, "mincore", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_minherit, "minherit", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_mkdir, "mkdir", "%s(\"%s\",%#o)", NULL, NULL }, +{ TARGET_NETBSD_NR_mkfifo, "mkfifo", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_mknod, "mknod", "%s(\"%s\",%#o,%#x)", NULL, NULL }, +{ TARGET_NETBSD_NR_mlock, "mlock", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_mlockall, "mlockall", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_mmap, "mmap", NULL, NULL, print_syscall_ret_addr }, +{ TARGET_NETBSD_NR_mprotect, "mprotect", "%s(%#x,%#x,%d)", NULL, NULL }, +{ TARGET_NETBSD_NR_msgget, "msgget", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_msgrcv, "msgrcv", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_msgsnd, "msgsnd", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_munlock, "munlock", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_munlockall, "munlockall", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_munmap, "munmap", "%s(%p,%d)", NULL, NULL }, +{ TARGET_NETBSD_NR_nanosleep, "nanosleep", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_nfssvc, "nfssvc", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_open, "open", "%s(\"%s\",%#x,%#o)", NULL, NULL }, +{ TARGET_NETBSD_NR_pathconf, "pathconf", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_pipe, "pipe", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_poll, "poll", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_pread, "pread", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_preadv, "preadv", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_profil, "profil", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_ptrace, "ptrace", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_pwrite, "pwrite", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_pwritev, "pwritev", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_quotactl, "quotactl", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_read, "read", "%s(%d,%#x,%d)", NULL, NULL }, +{ TARGET_NETBSD_NR_readlink, "readlink", "%s(\"%s\",%p,%d)", NULL, NULL }, +{ TARGET_NETBSD_NR_readv, "readv", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_reboot, "reboot", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_recvfrom, "recvfrom", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_recvmsg, "recvmsg", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_rename, "rename", "%s(\"%s\",\"%s\")", NULL, NULL }, +{ TARGET_NETBSD_NR_revoke, "revoke", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_rmdir, "rmdir", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_sbrk, "sbrk", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_sched_yield, "sched_yield", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_select, "select", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_semget, "semget", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_semop, "semop", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_sendmsg, "sendmsg", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_sendto, "sendto", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_setegid, "setegid", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_seteuid, "seteuid", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_setgid, "setgid", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_setgroups, "setgroups", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_setitimer, "setitimer", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_setpgid, "setpgid", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_setpriority, "setpriority", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_setregid, "setregid", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_setreuid, "setreuid", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_setrlimit, "setrlimit", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_setsid, "setsid", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_setsockopt, "setsockopt", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_settimeofday, "settimeofday", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_setuid, "setuid", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_shmat, "shmat", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_shmdt, "shmdt", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_shmget, "shmget", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_shutdown, "shutdown", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_socketpair, "socketpair", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_sstk, "sstk", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_swapctl, "swapctl", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_symlink, "symlink", "%s(\"%s\",\"%s\")", NULL, NULL }, +{ TARGET_NETBSD_NR_sync, "sync", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_sysarch, "sysarch", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_syscall, "syscall", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_truncate, "truncate", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_umask, "umask", "%s(%#o)", NULL, NULL }, +{ TARGET_NETBSD_NR_unlink, "unlink", "%s(\"%s\")", NULL, NULL }, +{ TARGET_NETBSD_NR_unmount, "unmount", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_utimes, "utimes", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_vfork, "vfork", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_wait4, "wait4", NULL, NULL, NULL }, +{ TARGET_NETBSD_NR_write, "write", "%s(%d,%#x,%d)", NULL, NULL }, +{ TARGET_NETBSD_NR_writev, "writev", "%s(%d,%p,%#x)", NULL, NULL }, diff --git a/bsd-user/netbsd/syscall_nr.h b/bsd-user/netbsd/syscall_nr.h new file mode 100644 index 0000000..2e9ab53 --- /dev/null +++ b/bsd-user/netbsd/syscall_nr.h @@ -0,0 +1,373 @@ +/* $NetBSD: syscall.h,v 1.215 2008/06/17 16:07:57 tsutsui Exp $ */ + +/* + * System call numbers. + * + * created from NetBSD: syscalls.master,v 1.204 2008/06/17 16:05:23 tsutsui Exp + */ + +#define TARGET_NETBSD_NR_syscall 0 +#define TARGET_NETBSD_NR_exit 1 +#define TARGET_NETBSD_NR_fork 2 +#define TARGET_NETBSD_NR_read 3 +#define TARGET_NETBSD_NR_write 4 +#define TARGET_NETBSD_NR_open 5 +#define TARGET_NETBSD_NR_close 6 +#define TARGET_NETBSD_NR_wait4 7 +#define TARGET_NETBSD_NR_compat_43_ocreat 8 +#define TARGET_NETBSD_NR_link 9 +#define TARGET_NETBSD_NR_unlink 10 +#define TARGET_NETBSD_NR_chdir 12 +#define TARGET_NETBSD_NR_fchdir 13 +#define TARGET_NETBSD_NR_mknod 14 +#define TARGET_NETBSD_NR_chmod 15 +#define TARGET_NETBSD_NR_chown 16 +#define TARGET_NETBSD_NR_break 17 +#define TARGET_NETBSD_NR_compat_20_getfsstat 18 +#define TARGET_NETBSD_NR_compat_43_olseek 19 +#define TARGET_NETBSD_NR_getpid 20 +#define TARGET_NETBSD_NR_getpid 20 +#define TARGET_NETBSD_NR_compat_40_mount 21 +#define TARGET_NETBSD_NR_unmount 22 +#define TARGET_NETBSD_NR_setuid 23 +#define TARGET_NETBSD_NR_getuid 24 +#define TARGET_NETBSD_NR_getuid 24 +#define TARGET_NETBSD_NR_geteuid 25 +#define TARGET_NETBSD_NR_ptrace 26 +#define TARGET_NETBSD_NR_recvmsg 27 +#define TARGET_NETBSD_NR_sendmsg 28 +#define TARGET_NETBSD_NR_recvfrom 29 +#define TARGET_NETBSD_NR_accept 30 +#define TARGET_NETBSD_NR_getpeername 31 +#define TARGET_NETBSD_NR_getsockname 32 +#define TARGET_NETBSD_NR_access 33 +#define TARGET_NETBSD_NR_chflags 34 +#define TARGET_NETBSD_NR_fchflags 35 +#define TARGET_NETBSD_NR_sync 36 +#define TARGET_NETBSD_NR_kill 37 +#define TARGET_NETBSD_NR_compat_43_stat43 38 +#define TARGET_NETBSD_NR_getppid 39 +#define TARGET_NETBSD_NR_compat_43_lstat43 40 +#define TARGET_NETBSD_NR_dup 41 +#define TARGET_NETBSD_NR_pipe 42 +#define TARGET_NETBSD_NR_getegid 43 +#define TARGET_NETBSD_NR_profil 44 +#define TARGET_NETBSD_NR_ktrace 45 +#define TARGET_NETBSD_NR_compat_13_sigaction13 46 +#define TARGET_NETBSD_NR_getgid 47 +#define TARGET_NETBSD_NR_getgid 47 +#define TARGET_NETBSD_NR_compat_13_sigprocmask13 48 +#define TARGET_NETBSD_NR___getlogin 49 +#define TARGET_NETBSD_NR___setlogin 50 +#define TARGET_NETBSD_NR_acct 51 +#define TARGET_NETBSD_NR_compat_13_sigpending13 52 +#define TARGET_NETBSD_NR_compat_13_sigaltstack13 53 +#define TARGET_NETBSD_NR_ioctl 54 +#define TARGET_NETBSD_NR_compat_12_oreboot 55 +#define TARGET_NETBSD_NR_revoke 56 +#define TARGET_NETBSD_NR_symlink 57 +#define TARGET_NETBSD_NR_readlink 58 +#define TARGET_NETBSD_NR_execve 59 +#define TARGET_NETBSD_NR_umask 60 +#define TARGET_NETBSD_NR_chroot 61 +#define TARGET_NETBSD_NR_compat_43_fstat43 62 +#define TARGET_NETBSD_NR_compat_43_ogetkerninfo 63 +#define TARGET_NETBSD_NR_compat_43_ogetpagesize 64 +#define TARGET_NETBSD_NR_compat_12_msync 65 +#define TARGET_NETBSD_NR_vfork 66 +#define TARGET_NETBSD_NR_sbrk 69 +#define TARGET_NETBSD_NR_sstk 70 +#define TARGET_NETBSD_NR_compat_43_ommap 71 +#define TARGET_NETBSD_NR_vadvise 72 +#define TARGET_NETBSD_NR_munmap 73 +#define TARGET_NETBSD_NR_mprotect 74 +#define TARGET_NETBSD_NR_madvise 75 +#define TARGET_NETBSD_NR_mincore 78 +#define TARGET_NETBSD_NR_getgroups 79 +#define TARGET_NETBSD_NR_setgroups 80 +#define TARGET_NETBSD_NR_getpgrp 81 +#define TARGET_NETBSD_NR_setpgid 82 +#define TARGET_NETBSD_NR_setitimer 83 +#define TARGET_NETBSD_NR_compat_43_owait 84 +#define TARGET_NETBSD_NR_compat_12_oswapon 85 +#define TARGET_NETBSD_NR_getitimer 86 +#define TARGET_NETBSD_NR_compat_43_ogethostname 87 +#define TARGET_NETBSD_NR_compat_43_osethostname 88 +#define TARGET_NETBSD_NR_compat_43_ogetdtablesize 89 +#define TARGET_NETBSD_NR_dup2 90 +#define TARGET_NETBSD_NR_fcntl 92 +#define TARGET_NETBSD_NR_select 93 +#define TARGET_NETBSD_NR_fsync 95 +#define TARGET_NETBSD_NR_setpriority 96 +#define TARGET_NETBSD_NR_compat_30_socket 97 +#define TARGET_NETBSD_NR_connect 98 +#define TARGET_NETBSD_NR_compat_43_oaccept 99 +#define TARGET_NETBSD_NR_getpriority 100 +#define TARGET_NETBSD_NR_compat_43_osend 101 +#define TARGET_NETBSD_NR_compat_43_orecv 102 +#define TARGET_NETBSD_NR_compat_13_sigreturn13 103 +#define TARGET_NETBSD_NR_bind 104 +#define TARGET_NETBSD_NR_setsockopt 105 +#define TARGET_NETBSD_NR_listen 106 +#define TARGET_NETBSD_NR_compat_43_osigvec 108 +#define TARGET_NETBSD_NR_compat_43_osigblock 109 +#define TARGET_NETBSD_NR_compat_43_osigsetmask 110 +#define TARGET_NETBSD_NR_compat_13_sigsuspend13 111 +#define TARGET_NETBSD_NR_compat_43_osigstack 112 +#define TARGET_NETBSD_NR_compat_43_orecvmsg 113 +#define TARGET_NETBSD_NR_compat_43_osendmsg 114 +#define TARGET_NETBSD_NR_gettimeofday 116 +#define TARGET_NETBSD_NR_getrusage 117 +#define TARGET_NETBSD_NR_getsockopt 118 +#define TARGET_NETBSD_NR_readv 120 +#define TARGET_NETBSD_NR_writev 121 +#define TARGET_NETBSD_NR_settimeofday 122 +#define TARGET_NETBSD_NR_fchown 123 +#define TARGET_NETBSD_NR_fchmod 124 +#define TARGET_NETBSD_NR_compat_43_orecvfrom 125 +#define TARGET_NETBSD_NR_setreuid 126 +#define TARGET_NETBSD_NR_setregid 127 +#define TARGET_NETBSD_NR_rename 128 +#define TARGET_NETBSD_NR_compat_43_otruncate 129 +#define TARGET_NETBSD_NR_compat_43_oftruncate 130 +#define TARGET_NETBSD_NR_flock 131 +#define TARGET_NETBSD_NR_mkfifo 132 +#define TARGET_NETBSD_NR_sendto 133 +#define TARGET_NETBSD_NR_shutdown 134 +#define TARGET_NETBSD_NR_socketpair 135 +#define TARGET_NETBSD_NR_mkdir 136 +#define TARGET_NETBSD_NR_rmdir 137 +#define TARGET_NETBSD_NR_utimes 138 +#define TARGET_NETBSD_NR_adjtime 140 +#define TARGET_NETBSD_NR_compat_43_ogetpeername 141 +#define TARGET_NETBSD_NR_compat_43_ogethostid 142 +#define TARGET_NETBSD_NR_compat_43_osethostid 143 +#define TARGET_NETBSD_NR_compat_43_ogetrlimit 144 +#define TARGET_NETBSD_NR_compat_43_osetrlimit 145 +#define TARGET_NETBSD_NR_compat_43_okillpg 146 +#define TARGET_NETBSD_NR_setsid 147 +#define TARGET_NETBSD_NR_quotactl 148 +#define TARGET_NETBSD_NR_compat_43_oquota 149 +#define TARGET_NETBSD_NR_compat_43_ogetsockname 150 +#define TARGET_NETBSD_NR_nfssvc 155 +#define TARGET_NETBSD_NR_compat_43_ogetdirentries 156 +#define TARGET_NETBSD_NR_compat_20_statfs 157 +#define TARGET_NETBSD_NR_compat_20_fstatfs 158 +#define TARGET_NETBSD_NR_compat_30_getfh 161 +#define TARGET_NETBSD_NR_compat_09_ogetdomainname 162 +#define TARGET_NETBSD_NR_compat_09_osetdomainname 163 +#define TARGET_NETBSD_NR_compat_09_ouname 164 +#define TARGET_NETBSD_NR_sysarch 165 +#define TARGET_NETBSD_NR_compat_10_osemsys 169 +#define TARGET_NETBSD_NR_compat_10_omsgsys 170 +#define TARGET_NETBSD_NR_compat_10_oshmsys 171 +#define TARGET_NETBSD_NR_pread 173 +#define TARGET_NETBSD_NR_pwrite 174 +#define TARGET_NETBSD_NR_compat_30_ntp_gettime 175 +#define TARGET_NETBSD_NR_ntp_adjtime 176 +#define TARGET_NETBSD_NR_setgid 181 +#define TARGET_NETBSD_NR_setegid 182 +#define TARGET_NETBSD_NR_seteuid 183 +#define TARGET_NETBSD_NR_lfs_bmapv 184 +#define TARGET_NETBSD_NR_lfs_markv 185 +#define TARGET_NETBSD_NR_lfs_segclean 186 +#define TARGET_NETBSD_NR_lfs_segwait 187 +#define TARGET_NETBSD_NR_compat_12_stat12 188 +#define TARGET_NETBSD_NR_compat_12_fstat12 189 +#define TARGET_NETBSD_NR_compat_12_lstat12 190 +#define TARGET_NETBSD_NR_pathconf 191 +#define TARGET_NETBSD_NR_fpathconf 192 +#define TARGET_NETBSD_NR_getrlimit 194 +#define TARGET_NETBSD_NR_setrlimit 195 +#define TARGET_NETBSD_NR_compat_12_getdirentries 196 +#define TARGET_NETBSD_NR_mmap 197 +#define TARGET_NETBSD_NR___syscall 198 +#define TARGET_NETBSD_NR_lseek 199 +#define TARGET_NETBSD_NR_truncate 200 +#define TARGET_NETBSD_NR_ftruncate 201 +#define TARGET_NETBSD_NR___sysctl 202 +#define TARGET_NETBSD_NR_mlock 203 +#define TARGET_NETBSD_NR_munlock 204 +#define TARGET_NETBSD_NR_undelete 205 +#define TARGET_NETBSD_NR_futimes 206 +#define TARGET_NETBSD_NR_getpgid 207 +#define TARGET_NETBSD_NR_reboot 208 +#define TARGET_NETBSD_NR_poll 209 +#define TARGET_NETBSD_NR_compat_14___semctl 220 +#define TARGET_NETBSD_NR_semget 221 +#define TARGET_NETBSD_NR_semop 222 +#define TARGET_NETBSD_NR_semconfig 223 +#define TARGET_NETBSD_NR_compat_14_msgctl 224 +#define TARGET_NETBSD_NR_msgget 225 +#define TARGET_NETBSD_NR_msgsnd 226 +#define TARGET_NETBSD_NR_msgrcv 227 +#define TARGET_NETBSD_NR_shmat 228 +#define TARGET_NETBSD_NR_compat_14_shmctl 229 +#define TARGET_NETBSD_NR_shmdt 230 +#define TARGET_NETBSD_NR_shmget 231 +#define TARGET_NETBSD_NR_clock_gettime 232 +#define TARGET_NETBSD_NR_clock_settime 233 +#define TARGET_NETBSD_NR_clock_getres 234 +#define TARGET_NETBSD_NR_timer_create 235 +#define TARGET_NETBSD_NR_timer_delete 236 +#define TARGET_NETBSD_NR_timer_settime 237 +#define TARGET_NETBSD_NR_timer_gettime 238 +#define TARGET_NETBSD_NR_timer_getoverrun 239 +#define TARGET_NETBSD_NR_nanosleep 240 +#define TARGET_NETBSD_NR_fdatasync 241 +#define TARGET_NETBSD_NR_mlockall 242 +#define TARGET_NETBSD_NR_munlockall 243 +#define TARGET_NETBSD_NR___sigtimedwait 244 +#define TARGET_NETBSD_NR_modctl 246 +#define TARGET_NETBSD_NR__ksem_init 247 +#define TARGET_NETBSD_NR__ksem_open 248 +#define TARGET_NETBSD_NR__ksem_unlink 249 +#define TARGET_NETBSD_NR__ksem_close 250 +#define TARGET_NETBSD_NR__ksem_post 251 +#define TARGET_NETBSD_NR__ksem_wait 252 +#define TARGET_NETBSD_NR__ksem_trywait 253 +#define TARGET_NETBSD_NR__ksem_getvalue 254 +#define TARGET_NETBSD_NR__ksem_destroy 255 +#define TARGET_NETBSD_NR_mq_open 257 +#define TARGET_NETBSD_NR_mq_close 258 +#define TARGET_NETBSD_NR_mq_unlink 259 +#define TARGET_NETBSD_NR_mq_getattr 260 +#define TARGET_NETBSD_NR_mq_setattr 261 +#define TARGET_NETBSD_NR_mq_notify 262 +#define TARGET_NETBSD_NR_mq_send 263 +#define TARGET_NETBSD_NR_mq_receive 264 +#define TARGET_NETBSD_NR_mq_timedsend 265 +#define TARGET_NETBSD_NR_mq_timedreceive 266 +#define TARGET_NETBSD_NR___posix_rename 270 +#define TARGET_NETBSD_NR_swapctl 271 +#define TARGET_NETBSD_NR_compat_30_getdents 272 +#define TARGET_NETBSD_NR_minherit 273 +#define TARGET_NETBSD_NR_lchmod 274 +#define TARGET_NETBSD_NR_lchown 275 +#define TARGET_NETBSD_NR_lutimes 276 +#define TARGET_NETBSD_NR___msync13 277 +#define TARGET_NETBSD_NR_compat_30___stat13 278 +#define TARGET_NETBSD_NR_compat_30___fstat13 279 +#define TARGET_NETBSD_NR_compat_30___lstat13 280 +#define TARGET_NETBSD_NR___sigaltstack14 281 +#define TARGET_NETBSD_NR___vfork14 282 +#define TARGET_NETBSD_NR___posix_chown 283 +#define TARGET_NETBSD_NR___posix_fchown 284 +#define TARGET_NETBSD_NR___posix_lchown 285 +#define TARGET_NETBSD_NR_getsid 286 +#define TARGET_NETBSD_NR___clone 287 +#define TARGET_NETBSD_NR_fktrace 288 +#define TARGET_NETBSD_NR_preadv 289 +#define TARGET_NETBSD_NR_pwritev 290 +#define TARGET_NETBSD_NR_compat_16___sigaction14 291 +#define TARGET_NETBSD_NR___sigpending14 292 +#define TARGET_NETBSD_NR___sigprocmask14 293 +#define TARGET_NETBSD_NR___sigsuspend14 294 +#define TARGET_NETBSD_NR_compat_16___sigreturn14 295 +#define TARGET_NETBSD_NR___getcwd 296 +#define TARGET_NETBSD_NR_fchroot 297 +#define TARGET_NETBSD_NR_compat_30_fhopen 298 +#define TARGET_NETBSD_NR_compat_30_fhstat 299 +#define TARGET_NETBSD_NR_compat_20_fhstatfs 300 +#define TARGET_NETBSD_NR_____semctl13 301 +#define TARGET_NETBSD_NR___msgctl13 302 +#define TARGET_NETBSD_NR___shmctl13 303 +#define TARGET_NETBSD_NR_lchflags 304 +#define TARGET_NETBSD_NR_issetugid 305 +#define TARGET_NETBSD_NR_utrace 306 +#define TARGET_NETBSD_NR_getcontext 307 +#define TARGET_NETBSD_NR_setcontext 308 +#define TARGET_NETBSD_NR__lwp_create 309 +#define TARGET_NETBSD_NR__lwp_exit 310 +#define TARGET_NETBSD_NR__lwp_self 311 +#define TARGET_NETBSD_NR__lwp_wait 312 +#define TARGET_NETBSD_NR__lwp_suspend 313 +#define TARGET_NETBSD_NR__lwp_continue 314 +#define TARGET_NETBSD_NR__lwp_wakeup 315 +#define TARGET_NETBSD_NR__lwp_getprivate 316 +#define TARGET_NETBSD_NR__lwp_setprivate 317 +#define TARGET_NETBSD_NR__lwp_kill 318 +#define TARGET_NETBSD_NR__lwp_detach 319 +#define TARGET_NETBSD_NR__lwp_park 320 +#define TARGET_NETBSD_NR__lwp_unpark 321 +#define TARGET_NETBSD_NR__lwp_unpark_all 322 +#define TARGET_NETBSD_NR__lwp_setname 323 +#define TARGET_NETBSD_NR__lwp_getname 324 +#define TARGET_NETBSD_NR__lwp_ctl 325 +#define TARGET_NETBSD_NR_sa_register 330 +#define TARGET_NETBSD_NR_sa_stacks 331 +#define TARGET_NETBSD_NR_sa_enable 332 +#define TARGET_NETBSD_NR_sa_setconcurrency 333 +#define TARGET_NETBSD_NR_sa_yield 334 +#define TARGET_NETBSD_NR_sa_preempt 335 +#define TARGET_NETBSD_NR_sa_unblockyield 336 +#define TARGET_NETBSD_NR___sigaction_sigtramp 340 +#define TARGET_NETBSD_NR_pmc_get_info 341 +#define TARGET_NETBSD_NR_pmc_control 342 +#define TARGET_NETBSD_NR_rasctl 343 +#define TARGET_NETBSD_NR_kqueue 344 +#define TARGET_NETBSD_NR_kevent 345 +#define TARGET_NETBSD_NR__sched_setparam 346 +#define TARGET_NETBSD_NR__sched_getparam 347 +#define TARGET_NETBSD_NR__sched_setaffinity 348 +#define TARGET_NETBSD_NR__sched_getaffinity 349 +#define TARGET_NETBSD_NR_sched_yield 350 +#define TARGET_NETBSD_NR_fsync_range 354 +#define TARGET_NETBSD_NR_uuidgen 355 +#define TARGET_NETBSD_NR_getvfsstat 356 +#define TARGET_NETBSD_NR_statvfs1 357 +#define TARGET_NETBSD_NR_fstatvfs1 358 +#define TARGET_NETBSD_NR_compat_30_fhstatvfs1 359 +#define TARGET_NETBSD_NR_extattrctl 360 +#define TARGET_NETBSD_NR_extattr_set_file 361 +#define TARGET_NETBSD_NR_extattr_get_file 362 +#define TARGET_NETBSD_NR_extattr_delete_file 363 +#define TARGET_NETBSD_NR_extattr_set_fd 364 +#define TARGET_NETBSD_NR_extattr_get_fd 365 +#define TARGET_NETBSD_NR_extattr_delete_fd 366 +#define TARGET_NETBSD_NR_extattr_set_link 367 +#define TARGET_NETBSD_NR_extattr_get_link 368 +#define TARGET_NETBSD_NR_extattr_delete_link 369 +#define TARGET_NETBSD_NR_extattr_list_fd 370 +#define TARGET_NETBSD_NR_extattr_list_file 371 +#define TARGET_NETBSD_NR_extattr_list_link 372 +#define TARGET_NETBSD_NR_pselect 373 +#define TARGET_NETBSD_NR_pollts 374 +#define TARGET_NETBSD_NR_setxattr 375 +#define TARGET_NETBSD_NR_lsetxattr 376 +#define TARGET_NETBSD_NR_fsetxattr 377 +#define TARGET_NETBSD_NR_getxattr 378 +#define TARGET_NETBSD_NR_lgetxattr 379 +#define TARGET_NETBSD_NR_fgetxattr 380 +#define TARGET_NETBSD_NR_listxattr 381 +#define TARGET_NETBSD_NR_llistxattr 382 +#define TARGET_NETBSD_NR_flistxattr 383 +#define TARGET_NETBSD_NR_removexattr 384 +#define TARGET_NETBSD_NR_lremovexattr 385 +#define TARGET_NETBSD_NR_fremovexattr 386 +#define TARGET_NETBSD_NR___stat30 387 +#define TARGET_NETBSD_NR___fstat30 388 +#define TARGET_NETBSD_NR___lstat30 389 +#define TARGET_NETBSD_NR___getdents30 390 +#define TARGET_NETBSD_NR_compat_30___fhstat30 392 +#define TARGET_NETBSD_NR___ntp_gettime30 393 +#define TARGET_NETBSD_NR___socket30 394 +#define TARGET_NETBSD_NR___getfh30 395 +#define TARGET_NETBSD_NR___fhopen40 396 +#define TARGET_NETBSD_NR___fhstatvfs140 397 +#define TARGET_NETBSD_NR___fhstat40 398 +#define TARGET_NETBSD_NR_aio_cancel 399 +#define TARGET_NETBSD_NR_aio_error 400 +#define TARGET_NETBSD_NR_aio_fsync 401 +#define TARGET_NETBSD_NR_aio_read 402 +#define TARGET_NETBSD_NR_aio_return 403 +#define TARGET_NETBSD_NR_aio_suspend 404 +#define TARGET_NETBSD_NR_aio_write 405 +#define TARGET_NETBSD_NR_lio_listio 406 +#define TARGET_NETBSD_NR___mount50 410 +#define TARGET_NETBSD_NR_mremap 411 +#define TARGET_NETBSD_NR_pset_create 412 +#define TARGET_NETBSD_NR_pset_destroy 413 +#define TARGET_NETBSD_NR_pset_assign 414 +#define TARGET_NETBSD_NR__pset_bind 415 +#define TARGET_NETBSD_NR___posix_fadvise50 416 diff --git a/bsd-user/openbsd/strace.list b/bsd-user/openbsd/strace.list new file mode 100644 index 0000000..1f0a331 --- /dev/null +++ b/bsd-user/openbsd/strace.list @@ -0,0 +1,187 @@ +{ TARGET_OPENBSD_NR___getcwd, "__getcwd", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR___semctl, "__semctl", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR___syscall, "__syscall", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR___sysctl, "__sysctl", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_accept, "accept", "%s(%d,%#x,%#x)", NULL, NULL }, +{ TARGET_OPENBSD_NR_access, "access", "%s(\"%s\",%#o)", NULL, NULL }, +{ TARGET_OPENBSD_NR_acct, "acct", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_adjfreq, "adjfreq", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_adjtime, "adjtime", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_bind, "bind", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_break, "break", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_chdir, "chdir", "%s(\"%s\")", NULL, NULL }, +{ TARGET_OPENBSD_NR_chflags, "chflags", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_chmod, "chmod", "%s(\"%s\",%#o)", NULL, NULL }, +{ TARGET_OPENBSD_NR_chown, "chown", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_chroot, "chroot", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_clock_getres, "clock_getres", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_clock_gettime, "clock_gettime", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_clock_settime, "clock_settime", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_close, "close", "%s(%d)", NULL, NULL }, +{ TARGET_OPENBSD_NR_closefrom, "closefrom", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_connect, "connect", "%s(%d,%#x,%d)", NULL, NULL }, +{ TARGET_OPENBSD_NR_dup, "dup", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_dup2, "dup2", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_execve, "execve", NULL, print_execve, NULL }, +{ TARGET_OPENBSD_NR_exit, "exit", "%s(%d)\n", NULL, NULL }, +{ TARGET_OPENBSD_NR_fchdir, "fchdir", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_fchflags, "fchflags", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_fchmod, "fchmod", "%s(%d,%#o)", NULL, NULL }, +{ TARGET_OPENBSD_NR_fchown, "fchown", "%s(\"%s\",%d,%d)", NULL, NULL }, +{ TARGET_OPENBSD_NR_fcntl, "fcntl", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_fhopen, "fhopen", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_fhstat, "fhstat", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_fhstatfs, "fhstatfs", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_flock, "flock", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_fork, "fork", "%s()", NULL, NULL }, +{ TARGET_OPENBSD_NR_fpathconf, "fpathconf", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_fstat, "fstat", "%s(%d,%p)", NULL, NULL }, +{ TARGET_OPENBSD_NR_fstatfs, "fstatfs", "%s(%d,%p)", NULL, NULL }, +{ TARGET_OPENBSD_NR_fsync, "fsync", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_ftruncate, "ftruncate", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_futimes, "futimes", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_getdirentries, "getdirentries", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_getegid, "getegid", "%s()", NULL, NULL }, +{ TARGET_OPENBSD_NR_geteuid, "geteuid", "%s()", NULL, NULL }, +{ TARGET_OPENBSD_NR_getfh, "getfh", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_getfsstat, "getfsstat", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_getgid, "getgid", "%s()", NULL, NULL }, +{ TARGET_OPENBSD_NR_getgroups, "getgroups", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_getitimer, "getitimer", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_getlogin, "getlogin", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_getpeereid, "getpeereid", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_getpeername, "getpeername", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_getpgid, "getpgid", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_getpgrp, "getpgrp", "%s()", NULL, NULL }, +{ TARGET_OPENBSD_NR_getpid, "getpid", "%s()", NULL, NULL }, +{ TARGET_OPENBSD_NR_getppid, "getppid", "%s()", NULL, NULL }, +{ TARGET_OPENBSD_NR_getpriority, "getpriority", "%s(%#x,%#x)", NULL, NULL }, +{ TARGET_OPENBSD_NR_getresgid, "getresgid", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_getresuid, "getresuid", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_getrlimit, "getrlimit", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_getrusage, "getrusage", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_getsid, "getsid", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_getsockname, "getsockname", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_getsockopt, "getsockopt", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_getthrid, "getthrid", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_gettimeofday, "gettimeofday", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_getuid, "getuid", "%s()", NULL, NULL }, +{ TARGET_OPENBSD_NR_ioctl, "ioctl", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_issetugid, "issetugid", "%s()", NULL, NULL }, +{ TARGET_OPENBSD_NR_kevent, "kevent", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_kill, "kill", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_kqueue, "kqueue", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_ktrace, "ktrace", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_lchown, "lchown", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_lfs_bmapv, "lfs_bmapv", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_lfs_markv, "lfs_markv", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_lfs_segclean, "lfs_segclean", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_lfs_segwait, "lfs_segwait", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_link, "link", "%s(\"%s\",\"%s\")", NULL, NULL }, +{ TARGET_OPENBSD_NR_listen, "listen", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_lseek, "lseek", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_lstat, "lstat", "%s(\"%s\",%p)", NULL, NULL }, +{ TARGET_OPENBSD_NR_madvise, "madvise", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_mincore, "mincore", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_minherit, "minherit", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_mkdir, "mkdir", "%s(\"%s\",%#o)", NULL, NULL }, +{ TARGET_OPENBSD_NR_mkfifo, "mkfifo", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_mknod, "mknod", "%s(\"%s\",%#o,%#x)", NULL, NULL }, +{ TARGET_OPENBSD_NR_mlock, "mlock", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_mlockall, "mlockall", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_mmap, "mmap", NULL, NULL, print_syscall_ret_addr }, +{ TARGET_OPENBSD_NR_mount, "mount", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_mprotect, "mprotect", "%s(%#x,%#x,%d)", NULL, NULL }, +{ TARGET_OPENBSD_NR_mquery, "mquery", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_msgctl, "msgctl", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_msgget, "msgget", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_msgrcv, "msgrcv", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_msgsnd, "msgsnd", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_msync, "msync", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_munlock, "munlock", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_munlockall, "munlockall", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_munmap, "munmap", "%s(%p,%d)", NULL, NULL }, +{ TARGET_OPENBSD_NR_nanosleep, "nanosleep", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_nfssvc, "nfssvc", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_open, "open", "%s(\"%s\",%#x,%#o)", NULL, NULL }, +{ TARGET_OPENBSD_NR_opipe, "opipe", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_osigaltstack, "osigaltstack", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_pathconf, "pathconf", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_pipe, "pipe", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_poll, "poll", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_pread, "pread", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_preadv, "preadv", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_profil, "profil", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_ptrace, "ptrace", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_pwrite, "pwrite", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_pwritev, "pwritev", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_quotactl, "quotactl", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_read, "read", "%s(%d,%#x,%d)", NULL, NULL }, +{ TARGET_OPENBSD_NR_readlink, "readlink", "%s(\"%s\",%p,%d)", NULL, NULL }, +{ TARGET_OPENBSD_NR_readv, "readv", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_reboot, "reboot", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_recvfrom, "recvfrom", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_recvmsg, "recvmsg", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_rename, "rename", "%s(\"%s\",\"%s\")", NULL, NULL }, +{ TARGET_OPENBSD_NR_revoke, "revoke", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_rfork, "rfork", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_rmdir, "rmdir", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_sbrk, "sbrk", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_sched_yield, "sched_yield", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_select, "select", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_semget, "semget", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_semop, "semop", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_sendmsg, "sendmsg", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_sendto, "sendto", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_setegid, "setegid", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_seteuid, "seteuid", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_setgid, "setgid", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_setgroups, "setgroups", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_setitimer, "setitimer", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_setlogin, "setlogin", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_setpgid, "setpgid", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_setpriority, "setpriority", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_setregid, "setregid", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_setresgid, "setresgid", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_setresuid, "setresuid", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_setreuid, "setreuid", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_setrlimit, "setrlimit", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_setsid, "setsid", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_setsockopt, "setsockopt", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_settimeofday, "settimeofday", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_setuid, "setuid", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_shmat, "shmat", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_shmctl, "shmctl", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_shmdt, "shmdt", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_shmget, "shmget", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_shutdown, "shutdown", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_sigaction, "sigaction", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_sigaltstack, "sigaltstack", "%s(%p,%p)", NULL, NULL }, +{ TARGET_OPENBSD_NR_sigpending, "sigpending", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_sigprocmask, "sigprocmask", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_sigreturn, "sigreturn", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_sigsuspend, "sigsuspend", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_socket, "socket", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_socketpair, "socketpair", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_sstk, "sstk", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_stat, "stat", "%s(\"%s\",%p)", NULL, NULL }, +{ TARGET_OPENBSD_NR_statfs, "statfs", "%s(\"%s\",%p)", NULL, NULL }, +{ TARGET_OPENBSD_NR_swapctl, "swapctl", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_symlink, "symlink", "%s(\"%s\",\"%s\")", NULL, NULL }, +{ TARGET_OPENBSD_NR_sync, "sync", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_sysarch, "sysarch", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_syscall, "syscall", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_threxit, "threxit", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_thrsigdivert, "thrsigdivert", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_thrsleep, "thrsleep", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_thrwakeup, "thrwakeup", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_truncate, "truncate", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_umask, "umask", "%s(%#o)", NULL, NULL }, +{ TARGET_OPENBSD_NR_unlink, "unlink", "%s(\"%s\")", NULL, NULL }, +{ TARGET_OPENBSD_NR_unmount, "unmount", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_utimes, "utimes", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_vfork, "vfork", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_wait4, "wait4", NULL, NULL, NULL }, +{ TARGET_OPENBSD_NR_write, "write", "%s(%d,%#x,%d)", NULL, NULL }, +{ TARGET_OPENBSD_NR_writev, "writev", "%s(%d,%p,%#x)", NULL, NULL }, +{ TARGET_OPENBSD_NR_xfspioctl, "xfspioctl", NULL, NULL, NULL }, diff --git a/bsd-user/openbsd/syscall_nr.h b/bsd-user/openbsd/syscall_nr.h new file mode 100644 index 0000000..dececfd --- /dev/null +++ b/bsd-user/openbsd/syscall_nr.h @@ -0,0 +1,225 @@ +/* $OpenBSD: syscall.h,v 1.101 2008/03/16 19:43:41 otto Exp $ */ + +/* + * System call numbers. + * + * created from; OpenBSD: syscalls.master,v 1.90 2008/03/16 19:42:57 otto Exp + */ + +#define TARGET_OPENBSD_NR_syscall 0 +#define TARGET_OPENBSD_NR_exit 1 +#define TARGET_OPENBSD_NR_fork 2 +#define TARGET_OPENBSD_NR_read 3 +#define TARGET_OPENBSD_NR_write 4 +#define TARGET_OPENBSD_NR_open 5 +#define TARGET_OPENBSD_NR_close 6 +#define TARGET_OPENBSD_NR_wait4 7 +#define TARGET_OPENBSD_NR_link 9 +#define TARGET_OPENBSD_NR_unlink 10 +#define TARGET_OPENBSD_NR_chdir 12 +#define TARGET_OPENBSD_NR_fchdir 13 +#define TARGET_OPENBSD_NR_mknod 14 +#define TARGET_OPENBSD_NR_chmod 15 +#define TARGET_OPENBSD_NR_chown 16 +#define TARGET_OPENBSD_NR_break 17 +#define TARGET_OPENBSD_NR_getpid 20 +#define TARGET_OPENBSD_NR_mount 21 +#define TARGET_OPENBSD_NR_unmount 22 +#define TARGET_OPENBSD_NR_setuid 23 +#define TARGET_OPENBSD_NR_getuid 24 +#define TARGET_OPENBSD_NR_geteuid 25 +#define TARGET_OPENBSD_NR_ptrace 26 +#define TARGET_OPENBSD_NR_recvmsg 27 +#define TARGET_OPENBSD_NR_sendmsg 28 +#define TARGET_OPENBSD_NR_recvfrom 29 +#define TARGET_OPENBSD_NR_accept 30 +#define TARGET_OPENBSD_NR_getpeername 31 +#define TARGET_OPENBSD_NR_getsockname 32 +#define TARGET_OPENBSD_NR_access 33 +#define TARGET_OPENBSD_NR_chflags 34 +#define TARGET_OPENBSD_NR_fchflags 35 +#define TARGET_OPENBSD_NR_sync 36 +#define TARGET_OPENBSD_NR_kill 37 +#define TARGET_OPENBSD_NR_getppid 39 +#define TARGET_OPENBSD_NR_dup 41 +#define TARGET_OPENBSD_NR_opipe 42 +#define TARGET_OPENBSD_NR_getegid 43 +#define TARGET_OPENBSD_NR_profil 44 +#define TARGET_OPENBSD_NR_ktrace 45 +#define TARGET_OPENBSD_NR_sigaction 46 +#define TARGET_OPENBSD_NR_getgid 47 +#define TARGET_OPENBSD_NR_sigprocmask 48 +#define TARGET_OPENBSD_NR_getlogin 49 +#define TARGET_OPENBSD_NR_setlogin 50 +#define TARGET_OPENBSD_NR_acct 51 +#define TARGET_OPENBSD_NR_sigpending 52 +#define TARGET_OPENBSD_NR_osigaltstack 53 +#define TARGET_OPENBSD_NR_ioctl 54 +#define TARGET_OPENBSD_NR_reboot 55 +#define TARGET_OPENBSD_NR_revoke 56 +#define TARGET_OPENBSD_NR_symlink 57 +#define TARGET_OPENBSD_NR_readlink 58 +#define TARGET_OPENBSD_NR_execve 59 +#define TARGET_OPENBSD_NR_umask 60 +#define TARGET_OPENBSD_NR_chroot 61 +#define TARGET_OPENBSD_NR_vfork 66 +#define TARGET_OPENBSD_NR_sbrk 69 +#define TARGET_OPENBSD_NR_sstk 70 +#define TARGET_OPENBSD_NR_munmap 73 +#define TARGET_OPENBSD_NR_mprotect 74 +#define TARGET_OPENBSD_NR_madvise 75 +#define TARGET_OPENBSD_NR_mincore 78 +#define TARGET_OPENBSD_NR_getgroups 79 +#define TARGET_OPENBSD_NR_setgroups 80 +#define TARGET_OPENBSD_NR_getpgrp 81 +#define TARGET_OPENBSD_NR_setpgid 82 +#define TARGET_OPENBSD_NR_setitimer 83 +#define TARGET_OPENBSD_NR_getitimer 86 +#define TARGET_OPENBSD_NR_dup2 90 +#define TARGET_OPENBSD_NR_fcntl 92 +#define TARGET_OPENBSD_NR_select 93 +#define TARGET_OPENBSD_NR_fsync 95 +#define TARGET_OPENBSD_NR_setpriority 96 +#define TARGET_OPENBSD_NR_socket 97 +#define TARGET_OPENBSD_NR_connect 98 +#define TARGET_OPENBSD_NR_getpriority 100 +#define TARGET_OPENBSD_NR_sigreturn 103 +#define TARGET_OPENBSD_NR_bind 104 +#define TARGET_OPENBSD_NR_setsockopt 105 +#define TARGET_OPENBSD_NR_listen 106 +#define TARGET_OPENBSD_NR_sigsuspend 111 +#define TARGET_OPENBSD_NR_gettimeofday 116 +#define TARGET_OPENBSD_NR_getrusage 117 +#define TARGET_OPENBSD_NR_getsockopt 118 +#define TARGET_OPENBSD_NR_readv 120 +#define TARGET_OPENBSD_NR_writev 121 +#define TARGET_OPENBSD_NR_settimeofday 122 +#define TARGET_OPENBSD_NR_fchown 123 +#define TARGET_OPENBSD_NR_fchmod 124 +#define TARGET_OPENBSD_NR_setreuid 126 +#define TARGET_OPENBSD_NR_setregid 127 +#define TARGET_OPENBSD_NR_rename 128 +#define TARGET_OPENBSD_NR_flock 131 +#define TARGET_OPENBSD_NR_mkfifo 132 +#define TARGET_OPENBSD_NR_sendto 133 +#define TARGET_OPENBSD_NR_shutdown 134 +#define TARGET_OPENBSD_NR_socketpair 135 +#define TARGET_OPENBSD_NR_mkdir 136 +#define TARGET_OPENBSD_NR_rmdir 137 +#define TARGET_OPENBSD_NR_utimes 138 +#define TARGET_OPENBSD_NR_adjtime 140 +#define TARGET_OPENBSD_NR_setsid 147 +#define TARGET_OPENBSD_NR_quotactl 148 +#define TARGET_OPENBSD_NR_nfssvc 155 +#define TARGET_OPENBSD_NR_getfh 161 +#define TARGET_OPENBSD_NR_sysarch 165 +#define TARGET_OPENBSD_NR_pread 173 +#define TARGET_OPENBSD_NR_pwrite 174 +#define TARGET_OPENBSD_NR_setgid 181 +#define TARGET_OPENBSD_NR_setegid 182 +#define TARGET_OPENBSD_NR_seteuid 183 +#define TARGET_OPENBSD_NR_lfs_bmapv 184 +#define TARGET_OPENBSD_NR_lfs_markv 185 +#define TARGET_OPENBSD_NR_lfs_segclean 186 +#define TARGET_OPENBSD_NR_lfs_segwait 187 +#define TARGET_OPENBSD_NR_pathconf 191 +#define TARGET_OPENBSD_NR_fpathconf 192 +#define TARGET_OPENBSD_NR_swapctl 193 +#define TARGET_OPENBSD_NR_getrlimit 194 +#define TARGET_OPENBSD_NR_setrlimit 195 +#define TARGET_OPENBSD_NR_getdirentries 196 +#define TARGET_OPENBSD_NR_mmap 197 +#define TARGET_OPENBSD_NR___syscall 198 +#define TARGET_OPENBSD_NR_lseek 199 +#define TARGET_OPENBSD_NR_truncate 200 +#define TARGET_OPENBSD_NR_ftruncate 201 +#define TARGET_OPENBSD_NR___sysctl 202 +#define TARGET_OPENBSD_NR_mlock 203 +#define TARGET_OPENBSD_NR_munlock 204 +#define TARGET_OPENBSD_NR_futimes 206 +#define TARGET_OPENBSD_NR_getpgid 207 +#define TARGET_OPENBSD_NR_xfspioctl 208 +#define TARGET_OPENBSD_NR_semget 221 +#define TARGET_OPENBSD_NR_msgget 225 +#define TARGET_OPENBSD_NR_msgsnd 226 +#define TARGET_OPENBSD_NR_msgrcv 227 +#define TARGET_OPENBSD_NR_shmat 228 +#define TARGET_OPENBSD_NR_shmdt 230 +#define TARGET_OPENBSD_NR_clock_gettime 232 +#define TARGET_OPENBSD_NR_clock_settime 233 +#define TARGET_OPENBSD_NR_clock_getres 234 +#define TARGET_OPENBSD_NR_nanosleep 240 +#define TARGET_OPENBSD_NR_minherit 250 +#define TARGET_OPENBSD_NR_rfork 251 +#define TARGET_OPENBSD_NR_poll 252 +#define TARGET_OPENBSD_NR_issetugid 253 +#define TARGET_OPENBSD_NR_lchown 254 +#define TARGET_OPENBSD_NR_getsid 255 +#define TARGET_OPENBSD_NR_msync 256 +#define TARGET_OPENBSD_NR_pipe 263 +#define TARGET_OPENBSD_NR_fhopen 264 +#define TARGET_OPENBSD_NR_preadv 267 +#define TARGET_OPENBSD_NR_pwritev 268 +#define TARGET_OPENBSD_NR_kqueue 269 +#define TARGET_OPENBSD_NR_kevent 270 +#define TARGET_OPENBSD_NR_mlockall 271 +#define TARGET_OPENBSD_NR_munlockall 272 +#define TARGET_OPENBSD_NR_getpeereid 273 +#define TARGET_OPENBSD_NR_getresuid 281 +#define TARGET_OPENBSD_NR_setresuid 282 +#define TARGET_OPENBSD_NR_getresgid 283 +#define TARGET_OPENBSD_NR_setresgid 284 +#define TARGET_OPENBSD_NR_mquery 286 +#define TARGET_OPENBSD_NR_closefrom 287 +#define TARGET_OPENBSD_NR_sigaltstack 288 +#define TARGET_OPENBSD_NR_shmget 289 +#define TARGET_OPENBSD_NR_semop 290 +#define TARGET_OPENBSD_NR_stat 291 +#define TARGET_OPENBSD_NR_fstat 292 +#define TARGET_OPENBSD_NR_lstat 293 +#define TARGET_OPENBSD_NR_fhstat 294 +#define TARGET_OPENBSD_NR___semctl 295 +#define TARGET_OPENBSD_NR_shmctl 296 +#define TARGET_OPENBSD_NR_msgctl 297 +#define TARGET_OPENBSD_NR_sched_yield 298 +#define TARGET_OPENBSD_NR_getthrid 299 +#define TARGET_OPENBSD_NR_thrsleep 300 +#define TARGET_OPENBSD_NR_thrwakeup 301 +#define TARGET_OPENBSD_NR_threxit 302 +#define TARGET_OPENBSD_NR_thrsigdivert 303 +#define TARGET_OPENBSD_NR___getcwd 304 +#define TARGET_OPENBSD_NR_adjfreq 305 +#define TARGET_OPENBSD_NR_getfsstat 306 +#define TARGET_OPENBSD_NR_statfs 307 +#define TARGET_OPENBSD_NR_fstatfs 308 +#define TARGET_OPENBSD_NR_fhstatfs 309 + +/* syscall flags from machine/trap.h */ + +/* $OpenBSD: trap.h,v 1.4 2008/07/04 22:04:37 kettenis Exp $ */ +/* $NetBSD: trap.h,v 1.4 1999/06/07 05:28:04 eeh Exp $ */ + +/* + * Copyright (c) 1996-1999 Eduardo Horvath + * + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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. + * + */ +#define TARGET_OPENBSD_SYSCALL_G2RFLAG 0x400 /* on success, return to %g2 rather than npc */ +#define TARGET_OPENBSD_SYSCALL_G7RFLAG 0x800 /* use %g7 as above (deprecated) */ diff --git a/bsd-user/qemu-types.h b/bsd-user/qemu-types.h new file mode 100644 index 0000000..1adda9f --- /dev/null +++ b/bsd-user/qemu-types.h @@ -0,0 +1,24 @@ +#ifndef QEMU_TYPES_H +#define QEMU_TYPES_H +#include "cpu.h" + +#ifdef TARGET_ABI32 +typedef uint32_t abi_ulong; +typedef int32_t abi_long; +#define TARGET_ABI_FMT_lx "%08x" +#define TARGET_ABI_FMT_ld "%d" +#define TARGET_ABI_FMT_lu "%u" +#define TARGET_ABI_BITS 32 +#else +typedef target_ulong abi_ulong; +typedef target_long abi_long; +#define TARGET_ABI_FMT_lx TARGET_FMT_lx +#define TARGET_ABI_FMT_ld TARGET_FMT_ld +#define TARGET_ABI_FMT_lu TARGET_FMT_lu +#define TARGET_ABI_BITS TARGET_LONG_BITS +/* for consistency, define ABI32 too */ +#if TARGET_ABI_BITS == 32 +#define TARGET_ABI32 1 +#endif +#endif +#endif diff --git a/bsd-user/qemu.h b/bsd-user/qemu.h new file mode 100644 index 0000000..e343894 --- /dev/null +++ b/bsd-user/qemu.h @@ -0,0 +1,394 @@ +#ifndef QEMU_H +#define QEMU_H + +#include +#include + +#include "cpu.h" + +#undef DEBUG_REMAP +#ifdef DEBUG_REMAP +#include +#endif /* DEBUG_REMAP */ + +#include "qemu-types.h" + +enum BSDType { + target_freebsd, + target_netbsd, + target_openbsd, +}; +extern enum BSDType bsd_type; + +#include "syscall_defs.h" +#include "syscall.h" +#include "target_signal.h" +#include "gdbstub.h" + +#if defined(CONFIG_USE_NPTL) +#define THREAD __thread +#else +#define THREAD +#endif + +/* This struct is used to hold certain information about the image. + * Basically, it replicates in user space what would be certain + * task_struct fields in the kernel + */ +struct image_info { + abi_ulong load_addr; + abi_ulong start_code; + abi_ulong end_code; + abi_ulong start_data; + abi_ulong end_data; + abi_ulong start_brk; + abi_ulong brk; + abi_ulong start_mmap; + abi_ulong mmap; + abi_ulong rss; + abi_ulong start_stack; + abi_ulong entry; + abi_ulong code_offset; + abi_ulong data_offset; + int personality; +}; + +#define MAX_SIGQUEUE_SIZE 1024 + +struct sigqueue { + struct sigqueue *next; + //target_siginfo_t info; +}; + +struct emulated_sigtable { + int pending; /* true if signal is pending */ + struct sigqueue *first; + struct sigqueue info; /* in order to always have memory for the + first signal, we put it here */ +}; + +/* NOTE: we force a big alignment so that the stack stored after is + aligned too */ +typedef struct TaskState { + struct TaskState *next; + int used; /* non zero if used */ + struct image_info *info; + + struct emulated_sigtable sigtab[TARGET_NSIG]; + struct sigqueue sigqueue_table[MAX_SIGQUEUE_SIZE]; /* siginfo queue */ + struct sigqueue *first_free; /* first free siginfo queue entry */ + int signal_pending; /* non zero if a signal may be pending */ + + uint8_t stack[0]; +} __attribute__((aligned(16))) TaskState; + +void init_task_state(TaskState *ts); +extern const char *qemu_uname_release; +#if defined(CONFIG_USE_GUEST_BASE) +extern unsigned long mmap_min_addr; +#endif + +/* ??? See if we can avoid exposing so much of the loader internals. */ +/* + * MAX_ARG_PAGES defines the number of pages allocated for arguments + * and envelope for the new program. 32 should suffice, this gives + * a maximum env+arg of 128kB w/4KB pages! + */ +#define MAX_ARG_PAGES 32 + +/* + * This structure is used to hold the arguments that are + * used when loading binaries. + */ +struct linux_binprm { + char buf[128]; + void *page[MAX_ARG_PAGES]; + abi_ulong p; + int fd; + int e_uid, e_gid; + int argc, envc; + char **argv; + char **envp; + char * filename; /* Name of binary */ +}; + +void do_init_thread(struct target_pt_regs *regs, struct image_info *infop); +abi_ulong loader_build_argptr(int envc, int argc, abi_ulong sp, + abi_ulong stringp, int push_ptr); +int loader_exec(const char * filename, char ** argv, char ** envp, + struct target_pt_regs * regs, struct image_info *infop); + +int load_elf_binary(struct linux_binprm * bprm, struct target_pt_regs * regs, + struct image_info * info); +int load_flt_binary(struct linux_binprm * bprm, struct target_pt_regs * regs, + struct image_info * info); + +abi_long memcpy_to_target(abi_ulong dest, const void *src, + unsigned long len); +void target_set_brk(abi_ulong new_brk); +abi_long do_brk(abi_ulong new_brk); +void syscall_init(void); +abi_long do_freebsd_syscall(void *cpu_env, int num, abi_long arg1, + abi_long arg2, abi_long arg3, abi_long arg4, + abi_long arg5, abi_long arg6, abi_long arg7, + abi_long arg8); +abi_long do_netbsd_syscall(void *cpu_env, int num, abi_long arg1, + abi_long arg2, abi_long arg3, abi_long arg4, + abi_long arg5, abi_long arg6); +abi_long do_openbsd_syscall(void *cpu_env, int num, abi_long arg1, + abi_long arg2, abi_long arg3, abi_long arg4, + abi_long arg5, abi_long arg6); +void gemu_log(const char *fmt, ...) GCC_FMT_ATTR(1, 2); +extern THREAD CPUState *thread_env; +void cpu_loop(CPUState *env); +char *target_strerror(int err); +int get_osversion(void); +void fork_start(void); +void fork_end(int child); + +#include "qemu-log.h" + +/* strace.c */ +void +print_freebsd_syscall(int num, + abi_long arg1, abi_long arg2, abi_long arg3, + abi_long arg4, abi_long arg5, abi_long arg6); +void print_freebsd_syscall_ret(int num, abi_long ret); +void +print_netbsd_syscall(int num, + abi_long arg1, abi_long arg2, abi_long arg3, + abi_long arg4, abi_long arg5, abi_long arg6); +void print_netbsd_syscall_ret(int num, abi_long ret); +void +print_openbsd_syscall(int num, + abi_long arg1, abi_long arg2, abi_long arg3, + abi_long arg4, abi_long arg5, abi_long arg6); +void print_openbsd_syscall_ret(int num, abi_long ret); +extern int do_strace; + +/* signal.c */ +void process_pending_signals(CPUState *cpu_env); +void signal_init(void); +//int queue_signal(CPUState *env, int sig, target_siginfo_t *info); +//void host_to_target_siginfo(target_siginfo_t *tinfo, const siginfo_t *info); +//void target_to_host_siginfo(siginfo_t *info, const target_siginfo_t *tinfo); +long do_sigreturn(CPUState *env); +long do_rt_sigreturn(CPUState *env); +abi_long do_sigaltstack(abi_ulong uss_addr, abi_ulong uoss_addr, abi_ulong sp); + +/* mmap.c */ +int target_mprotect(abi_ulong start, abi_ulong len, int prot); +abi_long target_mmap(abi_ulong start, abi_ulong len, int prot, + int flags, int fd, abi_ulong offset); +int target_munmap(abi_ulong start, abi_ulong len); +abi_long target_mremap(abi_ulong old_addr, abi_ulong old_size, + abi_ulong new_size, unsigned long flags, + abi_ulong new_addr); +int target_msync(abi_ulong start, abi_ulong len, int flags); +extern unsigned long last_brk; +void mmap_lock(void); +void mmap_unlock(void); +void cpu_list_lock(void); +void cpu_list_unlock(void); +#if defined(CONFIG_USE_NPTL) +void mmap_fork_start(void); +void mmap_fork_end(int child); +#endif + +/* main.c */ +extern unsigned long x86_stack_size; + +/* user access */ + +#define VERIFY_READ 0 +#define VERIFY_WRITE 1 /* implies read access */ + +static inline int access_ok(int type, abi_ulong addr, abi_ulong size) +{ + return page_check_range((target_ulong)addr, size, + (type == VERIFY_READ) ? PAGE_READ : (PAGE_READ | PAGE_WRITE)) == 0; +} + +/* NOTE __get_user and __put_user use host pointers and don't check access. */ +/* These are usually used to access struct data members once the + * struct has been locked - usually with lock_user_struct(). + */ +#define __put_user(x, hptr)\ +({\ + int size = sizeof(*hptr);\ + switch(size) {\ + case 1:\ + *(uint8_t *)(hptr) = (uint8_t)(typeof(*hptr))(x);\ + break;\ + case 2:\ + *(uint16_t *)(hptr) = tswap16((typeof(*hptr))(x));\ + break;\ + case 4:\ + *(uint32_t *)(hptr) = tswap32((typeof(*hptr))(x));\ + break;\ + case 8:\ + *(uint64_t *)(hptr) = tswap64((typeof(*hptr))(x));\ + break;\ + default:\ + abort();\ + }\ + 0;\ +}) + +#define __get_user(x, hptr) \ +({\ + int size = sizeof(*hptr);\ + switch(size) {\ + case 1:\ + x = (typeof(*hptr))*(uint8_t *)(hptr);\ + break;\ + case 2:\ + x = (typeof(*hptr))tswap16(*(uint16_t *)(hptr));\ + break;\ + case 4:\ + x = (typeof(*hptr))tswap32(*(uint32_t *)(hptr));\ + break;\ + case 8:\ + x = (typeof(*hptr))tswap64(*(uint64_t *)(hptr));\ + break;\ + default:\ + /* avoid warning */\ + x = 0;\ + abort();\ + }\ + 0;\ +}) + +/* put_user()/get_user() take a guest address and check access */ +/* These are usually used to access an atomic data type, such as an int, + * that has been passed by address. These internally perform locking + * and unlocking on the data type. + */ +#define put_user(x, gaddr, target_type) \ +({ \ + abi_ulong __gaddr = (gaddr); \ + target_type *__hptr; \ + abi_long __ret; \ + if ((__hptr = lock_user(VERIFY_WRITE, __gaddr, sizeof(target_type), 0))) { \ + __ret = __put_user((x), __hptr); \ + unlock_user(__hptr, __gaddr, sizeof(target_type)); \ + } else \ + __ret = -TARGET_EFAULT; \ + __ret; \ +}) + +#define get_user(x, gaddr, target_type) \ +({ \ + abi_ulong __gaddr = (gaddr); \ + target_type *__hptr; \ + abi_long __ret; \ + if ((__hptr = lock_user(VERIFY_READ, __gaddr, sizeof(target_type), 1))) { \ + __ret = __get_user((x), __hptr); \ + unlock_user(__hptr, __gaddr, 0); \ + } else { \ + /* avoid warning */ \ + (x) = 0; \ + __ret = -TARGET_EFAULT; \ + } \ + __ret; \ +}) + +#define put_user_ual(x, gaddr) put_user((x), (gaddr), abi_ulong) +#define put_user_sal(x, gaddr) put_user((x), (gaddr), abi_long) +#define put_user_u64(x, gaddr) put_user((x), (gaddr), uint64_t) +#define put_user_s64(x, gaddr) put_user((x), (gaddr), int64_t) +#define put_user_u32(x, gaddr) put_user((x), (gaddr), uint32_t) +#define put_user_s32(x, gaddr) put_user((x), (gaddr), int32_t) +#define put_user_u16(x, gaddr) put_user((x), (gaddr), uint16_t) +#define put_user_s16(x, gaddr) put_user((x), (gaddr), int16_t) +#define put_user_u8(x, gaddr) put_user((x), (gaddr), uint8_t) +#define put_user_s8(x, gaddr) put_user((x), (gaddr), int8_t) + +#define get_user_ual(x, gaddr) get_user((x), (gaddr), abi_ulong) +#define get_user_sal(x, gaddr) get_user((x), (gaddr), abi_long) +#define get_user_u64(x, gaddr) get_user((x), (gaddr), uint64_t) +#define get_user_s64(x, gaddr) get_user((x), (gaddr), int64_t) +#define get_user_u32(x, gaddr) get_user((x), (gaddr), uint32_t) +#define get_user_s32(x, gaddr) get_user((x), (gaddr), int32_t) +#define get_user_u16(x, gaddr) get_user((x), (gaddr), uint16_t) +#define get_user_s16(x, gaddr) get_user((x), (gaddr), int16_t) +#define get_user_u8(x, gaddr) get_user((x), (gaddr), uint8_t) +#define get_user_s8(x, gaddr) get_user((x), (gaddr), int8_t) + +/* copy_from_user() and copy_to_user() are usually used to copy data + * buffers between the target and host. These internally perform + * locking/unlocking of the memory. + */ +abi_long copy_from_user(void *hptr, abi_ulong gaddr, size_t len); +abi_long copy_to_user(abi_ulong gaddr, void *hptr, size_t len); + +/* Functions for accessing guest memory. The tget and tput functions + read/write single values, byteswapping as neccessary. The lock_user + gets a pointer to a contiguous area of guest memory, but does not perform + and byteswapping. lock_user may return either a pointer to the guest + memory, or a temporary buffer. */ + +/* Lock an area of guest memory into the host. If copy is true then the + host area will have the same contents as the guest. */ +static inline void *lock_user(int type, abi_ulong guest_addr, long len, int copy) +{ + if (!access_ok(type, guest_addr, len)) + return NULL; +#ifdef DEBUG_REMAP + { + void *addr; + addr = malloc(len); + if (copy) + memcpy(addr, g2h(guest_addr), len); + else + memset(addr, 0, len); + return addr; + } +#else + return g2h(guest_addr); +#endif +} + +/* Unlock an area of guest memory. The first LEN bytes must be + flushed back to guest memory. host_ptr = NULL is explicitly + allowed and does nothing. */ +static inline void unlock_user(void *host_ptr, abi_ulong guest_addr, + long len) +{ + +#ifdef DEBUG_REMAP + if (!host_ptr) + return; + if (host_ptr == g2h(guest_addr)) + return; + if (len > 0) + memcpy(g2h(guest_addr), host_ptr, len); + free(host_ptr); +#endif +} + +/* Return the length of a string in target memory or -TARGET_EFAULT if + access error. */ +abi_long target_strlen(abi_ulong gaddr); + +/* Like lock_user but for null terminated strings. */ +static inline void *lock_user_string(abi_ulong guest_addr) +{ + abi_long len; + len = target_strlen(guest_addr); + if (len < 0) + return NULL; + return lock_user(VERIFY_READ, guest_addr, (long)(len + 1), 1); +} + +/* Helper macros for locking/ulocking a target struct. */ +#define lock_user_struct(type, host_ptr, guest_addr, copy) \ + (host_ptr = lock_user(type, guest_addr, sizeof(*host_ptr), copy)) +#define unlock_user_struct(host_ptr, guest_addr, copy) \ + unlock_user(host_ptr, guest_addr, (copy) ? sizeof(*host_ptr) : 0) + +#if defined(CONFIG_USE_NPTL) +#include +#endif + +#endif /* QEMU_H */ diff --git a/bsd-user/signal.c b/bsd-user/signal.c new file mode 100644 index 0000000..40313c8 --- /dev/null +++ b/bsd-user/signal.c @@ -0,0 +1,38 @@ +/* + * Emulation of BSD signals + * + * Copyright (c) 2003 - 2008 Fabrice Bellard + * + * 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 . + */ +#include +#include +#include +#include +#include +#include +#include + +#include "qemu.h" +#include "target_signal.h" + +//#define DEBUG_SIGNAL + +void signal_init(void) +{ +} + +void process_pending_signals(CPUState *cpu_env) +{ +} diff --git a/bsd-user/sparc/syscall.h b/bsd-user/sparc/syscall.h new file mode 100644 index 0000000..5a9bb7e --- /dev/null +++ b/bsd-user/sparc/syscall.h @@ -0,0 +1,9 @@ +struct target_pt_regs { + abi_ulong psr; + abi_ulong pc; + abi_ulong npc; + abi_ulong y; + abi_ulong u_regs[16]; +}; + +#define UNAME_MACHINE "sun4" diff --git a/bsd-user/sparc/target_signal.h b/bsd-user/sparc/target_signal.h new file mode 100644 index 0000000..5b2abba --- /dev/null +++ b/bsd-user/sparc/target_signal.h @@ -0,0 +1,27 @@ +#ifndef TARGET_SIGNAL_H +#define TARGET_SIGNAL_H + +#include "cpu.h" + +/* this struct defines a stack used during syscall handling */ + +typedef struct target_sigaltstack { + abi_ulong ss_sp; + abi_long ss_flags; + abi_ulong ss_size; +} target_stack_t; + + +#ifndef UREG_I6 +#define UREG_I6 6 +#endif +#ifndef UREG_FP +#define UREG_FP UREG_I6 +#endif + +static inline abi_ulong get_sp_from_cpustate(CPUSPARCState *state) +{ + return state->regwptr[UREG_FP]; +} + +#endif /* TARGET_SIGNAL_H */ diff --git a/bsd-user/sparc64/syscall.h b/bsd-user/sparc64/syscall.h new file mode 100644 index 0000000..81a816d --- /dev/null +++ b/bsd-user/sparc64/syscall.h @@ -0,0 +1,10 @@ +struct target_pt_regs { + abi_ulong u_regs[16]; + abi_ulong tstate; + abi_ulong pc; + abi_ulong npc; + abi_ulong y; + abi_ulong fprs; +}; + +#define UNAME_MACHINE "sun4u" diff --git a/bsd-user/sparc64/target_signal.h b/bsd-user/sparc64/target_signal.h new file mode 100644 index 0000000..5b2abba --- /dev/null +++ b/bsd-user/sparc64/target_signal.h @@ -0,0 +1,27 @@ +#ifndef TARGET_SIGNAL_H +#define TARGET_SIGNAL_H + +#include "cpu.h" + +/* this struct defines a stack used during syscall handling */ + +typedef struct target_sigaltstack { + abi_ulong ss_sp; + abi_long ss_flags; + abi_ulong ss_size; +} target_stack_t; + + +#ifndef UREG_I6 +#define UREG_I6 6 +#endif +#ifndef UREG_FP +#define UREG_FP UREG_I6 +#endif + +static inline abi_ulong get_sp_from_cpustate(CPUSPARCState *state) +{ + return state->regwptr[UREG_FP]; +} + +#endif /* TARGET_SIGNAL_H */ diff --git a/bsd-user/strace.c b/bsd-user/strace.c new file mode 100644 index 0000000..d73bbca --- /dev/null +++ b/bsd-user/strace.c @@ -0,0 +1,191 @@ +#include +#include +#include +#include +#include +#include +#include "qemu.h" + +int do_strace=0; + +struct syscallname { + int nr; + const char *name; + const char *format; + void (*call)(const struct syscallname *, + abi_long, abi_long, abi_long, + abi_long, abi_long, abi_long); + void (*result)(const struct syscallname *, abi_long); +}; + +/* + * Utility functions + */ + +static void +print_execve(const struct syscallname *name, + abi_long arg1, abi_long arg2, abi_long arg3, + abi_long arg4, abi_long arg5, abi_long arg6) +{ + abi_ulong arg_ptr_addr; + char *s; + + if (!(s = lock_user_string(arg1))) + return; + gemu_log("%s(\"%s\",{", name->name, s); + unlock_user(s, arg1, 0); + + for (arg_ptr_addr = arg2; ; arg_ptr_addr += sizeof(abi_ulong)) { + abi_ulong *arg_ptr, arg_addr; + + arg_ptr = lock_user(VERIFY_READ, arg_ptr_addr, sizeof(abi_ulong), 1); + if (!arg_ptr) + return; + arg_addr = tswapl(*arg_ptr); + unlock_user(arg_ptr, arg_ptr_addr, 0); + if (!arg_addr) + break; + if ((s = lock_user_string(arg_addr))) { + gemu_log("\"%s\",", s); + unlock_user(s, arg_addr, 0); + } + } + + gemu_log("NULL})"); +} + +/* + * Variants for the return value output function + */ + +static void +print_syscall_ret_addr(const struct syscallname *name, abi_long ret) +{ +if( ret == -1 ) { + gemu_log(" = -1 errno=%d (%s)\n", errno, strerror(errno)); + } else { + gemu_log(" = 0x" TARGET_ABI_FMT_lx "\n", ret); + } +} + +#if 0 /* currently unused */ +static void +print_syscall_ret_raw(struct syscallname *name, abi_long ret) +{ + gemu_log(" = 0x" TARGET_ABI_FMT_lx "\n", ret); +} +#endif + +/* + * An array of all of the syscalls we know about + */ + +static const struct syscallname freebsd_scnames[] = { +#include "freebsd/strace.list" +}; +static const struct syscallname netbsd_scnames[] = { +#include "netbsd/strace.list" +}; +static const struct syscallname openbsd_scnames[] = { +#include "openbsd/strace.list" +}; + +static void +print_syscall(int num, const struct syscallname *scnames, unsigned int nscnames, + abi_long arg1, abi_long arg2, abi_long arg3, + abi_long arg4, abi_long arg5, abi_long arg6) +{ + unsigned int i; + const char *format="%s(" TARGET_ABI_FMT_ld "," TARGET_ABI_FMT_ld "," + TARGET_ABI_FMT_ld "," TARGET_ABI_FMT_ld "," TARGET_ABI_FMT_ld "," + TARGET_ABI_FMT_ld ")"; + + gemu_log("%d ", getpid() ); + + for (i = 0; i < nscnames; i++) + if (scnames[i].nr == num) { + if (scnames[i].call != NULL) { + scnames[i].call(&scnames[i], arg1, arg2, arg3, arg4, arg5, + arg6); + } else { + /* XXX: this format system is broken because it uses + host types and host pointers for strings */ + if (scnames[i].format != NULL) + format = scnames[i].format; + gemu_log(format, scnames[i].name, arg1, arg2, arg3, arg4, + arg5, arg6); + } + return; + } + gemu_log("Unknown syscall %d\n", num); +} + +static void +print_syscall_ret(int num, abi_long ret, const struct syscallname *scnames, + unsigned int nscnames) +{ + unsigned int i; + + for (i = 0; i < nscnames; i++) + if (scnames[i].nr == num) { + if (scnames[i].result != NULL) { + scnames[i].result(&scnames[i], ret); + } else { + if( ret < 0 ) { + gemu_log(" = -1 errno=" TARGET_ABI_FMT_ld " (%s)\n", -ret, + strerror(-ret)); + } else { + gemu_log(" = " TARGET_ABI_FMT_ld "\n", ret); + } + } + break; + } +} + +/* + * The public interface to this module. + */ +void +print_freebsd_syscall(int num, + abi_long arg1, abi_long arg2, abi_long arg3, + abi_long arg4, abi_long arg5, abi_long arg6) +{ + print_syscall(num, freebsd_scnames, ARRAY_SIZE(freebsd_scnames), + arg1, arg2, arg3, arg4, arg5, arg6); +} + +void +print_freebsd_syscall_ret(int num, abi_long ret) +{ + print_syscall_ret(num, ret, freebsd_scnames, ARRAY_SIZE(freebsd_scnames)); +} + +void +print_netbsd_syscall(int num, + abi_long arg1, abi_long arg2, abi_long arg3, + abi_long arg4, abi_long arg5, abi_long arg6) +{ + print_syscall(num, netbsd_scnames, ARRAY_SIZE(netbsd_scnames), + arg1, arg2, arg3, arg4, arg5, arg6); +} + +void +print_netbsd_syscall_ret(int num, abi_long ret) +{ + print_syscall_ret(num, ret, netbsd_scnames, ARRAY_SIZE(netbsd_scnames)); +} + +void +print_openbsd_syscall(int num, + abi_long arg1, abi_long arg2, abi_long arg3, + abi_long arg4, abi_long arg5, abi_long arg6) +{ + print_syscall(num, openbsd_scnames, ARRAY_SIZE(openbsd_scnames), + arg1, arg2, arg3, arg4, arg5, arg6); +} + +void +print_openbsd_syscall_ret(int num, abi_long ret) +{ + print_syscall_ret(num, ret, openbsd_scnames, ARRAY_SIZE(openbsd_scnames)); +} diff --git a/bsd-user/syscall.c b/bsd-user/syscall.c new file mode 100644 index 0000000..eb1cdf2 --- /dev/null +++ b/bsd-user/syscall.c @@ -0,0 +1,560 @@ +/* + * BSD syscalls + * + * Copyright (c) 2003 - 2008 Fabrice Bellard + * + * 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 . + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "qemu.h" +#include "qemu-common.h" + +//#define DEBUG + +static abi_ulong target_brk; +static abi_ulong target_original_brk; + +static inline abi_long get_errno(abi_long ret) +{ + if (ret == -1) + /* XXX need to translate host -> target errnos here */ + return -(errno); + else + return ret; +} + +#define target_to_host_bitmask(x, tbl) (x) + +static inline int is_error(abi_long ret) +{ + return (abi_ulong)ret >= (abi_ulong)(-4096); +} + +void target_set_brk(abi_ulong new_brk) +{ + target_original_brk = target_brk = HOST_PAGE_ALIGN(new_brk); +} + +/* do_obreak() must return target errnos. */ +static abi_long do_obreak(abi_ulong new_brk) +{ + abi_ulong brk_page; + abi_long mapped_addr; + int new_alloc_size; + + if (!new_brk) + return 0; + if (new_brk < target_original_brk) + return -TARGET_EINVAL; + + brk_page = HOST_PAGE_ALIGN(target_brk); + + /* If the new brk is less than this, set it and we're done... */ + if (new_brk < brk_page) { + target_brk = new_brk; + return 0; + } + + /* We need to allocate more memory after the brk... */ + new_alloc_size = HOST_PAGE_ALIGN(new_brk - brk_page + 1); + mapped_addr = get_errno(target_mmap(brk_page, new_alloc_size, + PROT_READ|PROT_WRITE, + MAP_ANON|MAP_FIXED|MAP_PRIVATE, -1, 0)); + + if (!is_error(mapped_addr)) + target_brk = new_brk; + else + return mapped_addr; + + return 0; +} + +#if defined(TARGET_I386) +static abi_long do_freebsd_sysarch(CPUX86State *env, int op, abi_ulong parms) +{ + abi_long ret = 0; + abi_ulong val; + int idx; + + switch(op) { +#ifdef TARGET_ABI32 + case TARGET_FREEBSD_I386_SET_GSBASE: + case TARGET_FREEBSD_I386_SET_FSBASE: + if (op == TARGET_FREEBSD_I386_SET_GSBASE) +#else + case TARGET_FREEBSD_AMD64_SET_GSBASE: + case TARGET_FREEBSD_AMD64_SET_FSBASE: + if (op == TARGET_FREEBSD_AMD64_SET_GSBASE) +#endif + idx = R_GS; + else + idx = R_FS; + if (get_user(val, parms, abi_ulong)) + return -TARGET_EFAULT; + cpu_x86_load_seg(env, idx, 0); + env->segs[idx].base = val; + break; +#ifdef TARGET_ABI32 + case TARGET_FREEBSD_I386_GET_GSBASE: + case TARGET_FREEBSD_I386_GET_FSBASE: + if (op == TARGET_FREEBSD_I386_GET_GSBASE) +#else + case TARGET_FREEBSD_AMD64_GET_GSBASE: + case TARGET_FREEBSD_AMD64_GET_FSBASE: + if (op == TARGET_FREEBSD_AMD64_GET_GSBASE) +#endif + idx = R_GS; + else + idx = R_FS; + val = env->segs[idx].base; + if (put_user(val, parms, abi_ulong)) + return -TARGET_EFAULT; + break; + /* XXX handle the others... */ + default: + ret = -TARGET_EINVAL; + break; + } + return ret; +} +#endif + +#ifdef TARGET_SPARC +static abi_long do_freebsd_sysarch(void *env, int op, abi_ulong parms) +{ + /* XXX handle + * TARGET_FREEBSD_SPARC_UTRAP_INSTALL, + * TARGET_FREEBSD_SPARC_SIGTRAMP_INSTALL + */ + return -TARGET_EINVAL; +} +#endif + +#ifdef __FreeBSD__ +/* + * XXX this uses the undocumented oidfmt interface to find the kind of + * a requested sysctl, see /sys/kern/kern_sysctl.c:sysctl_sysctl_oidfmt() + * (this is mostly copied from src/sbin/sysctl/sysctl.c) + */ +static int +oidfmt(int *oid, int len, char *fmt, uint32_t *kind) +{ + int qoid[CTL_MAXNAME+2]; + uint8_t buf[BUFSIZ]; + int i; + size_t j; + + qoid[0] = 0; + qoid[1] = 4; + memcpy(qoid + 2, oid, len * sizeof(int)); + + j = sizeof(buf); + i = sysctl(qoid, len + 2, buf, &j, 0, 0); + if (i) + return i; + + if (kind) + *kind = *(uint32_t *)buf; + + if (fmt) + strcpy(fmt, (char *)(buf + sizeof(uint32_t))); + return (0); +} + +/* + * try and convert sysctl return data for the target. + * XXX doesn't handle CTLTYPE_OPAQUE and CTLTYPE_STRUCT. + */ +static int sysctl_oldcvt(void *holdp, size_t holdlen, uint32_t kind) +{ + switch (kind & CTLTYPE) { + case CTLTYPE_INT: + case CTLTYPE_UINT: + *(uint32_t *)holdp = tswap32(*(uint32_t *)holdp); + break; +#ifdef TARGET_ABI32 + case CTLTYPE_LONG: + case CTLTYPE_ULONG: + *(uint32_t *)holdp = tswap32(*(long *)holdp); + break; +#else + case CTLTYPE_LONG: + *(uint64_t *)holdp = tswap64(*(long *)holdp); + case CTLTYPE_ULONG: + *(uint64_t *)holdp = tswap64(*(unsigned long *)holdp); + break; +#endif + case CTLTYPE_QUAD: + *(uint64_t *)holdp = tswap64(*(uint64_t *)holdp); + break; + case CTLTYPE_STRING: + break; + default: + /* XXX unhandled */ + return -1; + } + return 0; +} + +/* XXX this needs to be emulated on non-FreeBSD hosts... */ +static abi_long do_freebsd_sysctl(abi_ulong namep, int32_t namelen, abi_ulong oldp, + abi_ulong oldlenp, abi_ulong newp, abi_ulong newlen) +{ + abi_long ret; + void *hnamep, *holdp, *hnewp = NULL; + size_t holdlen; + abi_ulong oldlen = 0; + int32_t *snamep = qemu_malloc(sizeof(int32_t) * namelen), *p, *q, i; + uint32_t kind = 0; + + if (oldlenp) + get_user_ual(oldlen, oldlenp); + if (!(hnamep = lock_user(VERIFY_READ, namep, namelen, 1))) + return -TARGET_EFAULT; + if (newp && !(hnewp = lock_user(VERIFY_READ, newp, newlen, 1))) + return -TARGET_EFAULT; + if (!(holdp = lock_user(VERIFY_WRITE, oldp, oldlen, 0))) + return -TARGET_EFAULT; + holdlen = oldlen; + for (p = hnamep, q = snamep, i = 0; i < namelen; p++, i++) + *q++ = tswap32(*p); + oidfmt(snamep, namelen, NULL, &kind); + /* XXX swap hnewp */ + ret = get_errno(sysctl(snamep, namelen, holdp, &holdlen, hnewp, newlen)); + if (!ret) + sysctl_oldcvt(holdp, holdlen, kind); + put_user_ual(holdlen, oldlenp); + unlock_user(hnamep, namep, 0); + unlock_user(holdp, oldp, holdlen); + if (hnewp) + unlock_user(hnewp, newp, 0); + qemu_free(snamep); + return ret; +} +#endif + +/* FIXME + * lock_iovec()/unlock_iovec() have a return code of 0 for success where + * other lock functions have a return code of 0 for failure. + */ +static abi_long lock_iovec(int type, struct iovec *vec, abi_ulong target_addr, + int count, int copy) +{ + struct target_iovec *target_vec; + abi_ulong base; + int i; + + target_vec = lock_user(VERIFY_READ, target_addr, count * sizeof(struct target_iovec), 1); + if (!target_vec) + return -TARGET_EFAULT; + for(i = 0;i < count; i++) { + base = tswapl(target_vec[i].iov_base); + vec[i].iov_len = tswapl(target_vec[i].iov_len); + if (vec[i].iov_len != 0) { + vec[i].iov_base = lock_user(type, base, vec[i].iov_len, copy); + /* Don't check lock_user return value. We must call writev even + if a element has invalid base address. */ + } else { + /* zero length pointer is ignored */ + vec[i].iov_base = NULL; + } + } + unlock_user (target_vec, target_addr, 0); + return 0; +} + +static abi_long unlock_iovec(struct iovec *vec, abi_ulong target_addr, + int count, int copy) +{ + struct target_iovec *target_vec; + abi_ulong base; + int i; + + target_vec = lock_user(VERIFY_READ, target_addr, count * sizeof(struct target_iovec), 1); + if (!target_vec) + return -TARGET_EFAULT; + for(i = 0;i < count; i++) { + if (target_vec[i].iov_base) { + base = tswapl(target_vec[i].iov_base); + unlock_user(vec[i].iov_base, base, copy ? vec[i].iov_len : 0); + } + } + unlock_user (target_vec, target_addr, 0); + + return 0; +} + +/* do_syscall() should always have a single exit point at the end so + that actions, such as logging of syscall results, can be performed. + All errnos that do_syscall() returns must be -TARGET_. */ +abi_long do_freebsd_syscall(void *cpu_env, int num, abi_long arg1, + abi_long arg2, abi_long arg3, abi_long arg4, + abi_long arg5, abi_long arg6, abi_long arg7, + abi_long arg8) +{ + abi_long ret; + void *p; + +#ifdef DEBUG + gemu_log("freebsd syscall %d\n", num); +#endif + if(do_strace) + print_freebsd_syscall(num, arg1, arg2, arg3, arg4, arg5, arg6); + + switch(num) { + case TARGET_FREEBSD_NR_exit: +#ifdef TARGET_GPROF + _mcleanup(); +#endif + gdb_exit(cpu_env, arg1); + /* XXX: should free thread stack and CPU env */ + _exit(arg1); + ret = 0; /* avoid warning */ + break; + case TARGET_FREEBSD_NR_read: + if (!(p = lock_user(VERIFY_WRITE, arg2, arg3, 0))) + goto efault; + ret = get_errno(read(arg1, p, arg3)); + unlock_user(p, arg2, ret); + break; + case TARGET_FREEBSD_NR_write: + if (!(p = lock_user(VERIFY_READ, arg2, arg3, 1))) + goto efault; + ret = get_errno(write(arg1, p, arg3)); + unlock_user(p, arg2, 0); + break; + case TARGET_FREEBSD_NR_writev: + { + int count = arg3; + struct iovec *vec; + + vec = alloca(count * sizeof(struct iovec)); + if (lock_iovec(VERIFY_READ, vec, arg2, count, 1) < 0) + goto efault; + ret = get_errno(writev(arg1, vec, count)); + unlock_iovec(vec, arg2, count, 0); + } + break; + case TARGET_FREEBSD_NR_open: + if (!(p = lock_user_string(arg1))) + goto efault; + ret = get_errno(open(path(p), + target_to_host_bitmask(arg2, fcntl_flags_tbl), + arg3)); + unlock_user(p, arg1, 0); + break; + case TARGET_FREEBSD_NR_mmap: + ret = get_errno(target_mmap(arg1, arg2, arg3, + target_to_host_bitmask(arg4, mmap_flags_tbl), + arg5, + arg6)); + break; + case TARGET_FREEBSD_NR_mprotect: + ret = get_errno(target_mprotect(arg1, arg2, arg3)); + break; + case TARGET_FREEBSD_NR_break: + ret = do_obreak(arg1); + break; +#ifdef __FreeBSD__ + case TARGET_FREEBSD_NR___sysctl: + ret = do_freebsd_sysctl(arg1, arg2, arg3, arg4, arg5, arg6); + break; +#endif + case TARGET_FREEBSD_NR_sysarch: + ret = do_freebsd_sysarch(cpu_env, arg1, arg2); + break; + case TARGET_FREEBSD_NR_syscall: + case TARGET_FREEBSD_NR___syscall: + ret = do_freebsd_syscall(cpu_env,arg1 & 0xffff,arg2,arg3,arg4,arg5,arg6,arg7,arg8,0); + break; + default: + ret = get_errno(syscall(num, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8)); + break; + } + fail: +#ifdef DEBUG + gemu_log(" = %ld\n", ret); +#endif + if (do_strace) + print_freebsd_syscall_ret(num, ret); + return ret; + efault: + ret = -TARGET_EFAULT; + goto fail; +} + +abi_long do_netbsd_syscall(void *cpu_env, int num, abi_long arg1, + abi_long arg2, abi_long arg3, abi_long arg4, + abi_long arg5, abi_long arg6) +{ + abi_long ret; + void *p; + +#ifdef DEBUG + gemu_log("netbsd syscall %d\n", num); +#endif + if(do_strace) + print_netbsd_syscall(num, arg1, arg2, arg3, arg4, arg5, arg6); + + switch(num) { + case TARGET_NETBSD_NR_exit: +#ifdef TARGET_GPROF + _mcleanup(); +#endif + gdb_exit(cpu_env, arg1); + /* XXX: should free thread stack and CPU env */ + _exit(arg1); + ret = 0; /* avoid warning */ + break; + case TARGET_NETBSD_NR_read: + if (!(p = lock_user(VERIFY_WRITE, arg2, arg3, 0))) + goto efault; + ret = get_errno(read(arg1, p, arg3)); + unlock_user(p, arg2, ret); + break; + case TARGET_NETBSD_NR_write: + if (!(p = lock_user(VERIFY_READ, arg2, arg3, 1))) + goto efault; + ret = get_errno(write(arg1, p, arg3)); + unlock_user(p, arg2, 0); + break; + case TARGET_NETBSD_NR_open: + if (!(p = lock_user_string(arg1))) + goto efault; + ret = get_errno(open(path(p), + target_to_host_bitmask(arg2, fcntl_flags_tbl), + arg3)); + unlock_user(p, arg1, 0); + break; + case TARGET_NETBSD_NR_mmap: + ret = get_errno(target_mmap(arg1, arg2, arg3, + target_to_host_bitmask(arg4, mmap_flags_tbl), + arg5, + arg6)); + break; + case TARGET_NETBSD_NR_mprotect: + ret = get_errno(target_mprotect(arg1, arg2, arg3)); + break; + case TARGET_NETBSD_NR_syscall: + case TARGET_NETBSD_NR___syscall: + ret = do_netbsd_syscall(cpu_env,arg1 & 0xffff,arg2,arg3,arg4,arg5,arg6,0); + break; + default: + ret = syscall(num, arg1, arg2, arg3, arg4, arg5, arg6); + break; + } + fail: +#ifdef DEBUG + gemu_log(" = %ld\n", ret); +#endif + if (do_strace) + print_netbsd_syscall_ret(num, ret); + return ret; + efault: + ret = -TARGET_EFAULT; + goto fail; +} + +abi_long do_openbsd_syscall(void *cpu_env, int num, abi_long arg1, + abi_long arg2, abi_long arg3, abi_long arg4, + abi_long arg5, abi_long arg6) +{ + abi_long ret; + void *p; + +#ifdef DEBUG + gemu_log("openbsd syscall %d\n", num); +#endif + if(do_strace) + print_openbsd_syscall(num, arg1, arg2, arg3, arg4, arg5, arg6); + + switch(num) { + case TARGET_OPENBSD_NR_exit: +#ifdef TARGET_GPROF + _mcleanup(); +#endif + gdb_exit(cpu_env, arg1); + /* XXX: should free thread stack and CPU env */ + _exit(arg1); + ret = 0; /* avoid warning */ + break; + case TARGET_OPENBSD_NR_read: + if (!(p = lock_user(VERIFY_WRITE, arg2, arg3, 0))) + goto efault; + ret = get_errno(read(arg1, p, arg3)); + unlock_user(p, arg2, ret); + break; + case TARGET_OPENBSD_NR_write: + if (!(p = lock_user(VERIFY_READ, arg2, arg3, 1))) + goto efault; + ret = get_errno(write(arg1, p, arg3)); + unlock_user(p, arg2, 0); + break; + case TARGET_OPENBSD_NR_open: + if (!(p = lock_user_string(arg1))) + goto efault; + ret = get_errno(open(path(p), + target_to_host_bitmask(arg2, fcntl_flags_tbl), + arg3)); + unlock_user(p, arg1, 0); + break; + case TARGET_OPENBSD_NR_mmap: + ret = get_errno(target_mmap(arg1, arg2, arg3, + target_to_host_bitmask(arg4, mmap_flags_tbl), + arg5, + arg6)); + break; + case TARGET_OPENBSD_NR_mprotect: + ret = get_errno(target_mprotect(arg1, arg2, arg3)); + break; + case TARGET_OPENBSD_NR_syscall: + case TARGET_OPENBSD_NR___syscall: + ret = do_openbsd_syscall(cpu_env,arg1 & 0xffff,arg2,arg3,arg4,arg5,arg6,0); + break; + default: + ret = syscall(num, arg1, arg2, arg3, arg4, arg5, arg6); + break; + } + fail: +#ifdef DEBUG + gemu_log(" = %ld\n", ret); +#endif + if (do_strace) + print_openbsd_syscall_ret(num, ret); + return ret; + efault: + ret = -TARGET_EFAULT; + goto fail; +} + +void syscall_init(void) +{ +} diff --git a/bsd-user/syscall_defs.h b/bsd-user/syscall_defs.h new file mode 100644 index 0000000..207ddee --- /dev/null +++ b/bsd-user/syscall_defs.h @@ -0,0 +1,114 @@ +/* $OpenBSD: signal.h,v 1.19 2006/01/08 14:20:16 millert Exp $ */ +/* $NetBSD: signal.h,v 1.21 1996/02/09 18:25:32 christos Exp $ */ + +/* + * Copyright (c) 1982, 1986, 1989, 1991, 1993 + * The Regents of the University of California. All rights reserved. + * (c) UNIX System Laboratories, Inc. + * All or some portions of this file are derived from material licensed + * to the University of California by American Telephone and Telegraph + * Co. or Unix System Laboratories, Inc. and are reproduced herein with + * the permission of UNIX System Laboratories, Inc. + * + * 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. + * 3. Neither the name of the University 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 REGENTS 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 REGENTS 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. + * + * @(#)signal.h 8.2 (Berkeley) 1/21/94 + */ + +#define TARGET_NSIG 32 /* counting 0; could be 33 (mask is 1-32) */ + +#define TARGET_SIGHUP 1 /* hangup */ +#define TARGET_SIGINT 2 /* interrupt */ +#define TARGET_SIGQUIT 3 /* quit */ +#define TARGET_SIGILL 4 /* illegal instruction (not reset when caught) */ +#define TARGET_SIGTRAP 5 /* trace trap (not reset when caught) */ +#define TARGET_SIGABRT 6 /* abort() */ +#define TARGET_SIGIOT SIGABRT /* compatibility */ +#define TARGET_SIGEMT 7 /* EMT instruction */ +#define TARGET_SIGFPE 8 /* floating point exception */ +#define TARGET_SIGKILL 9 /* kill (cannot be caught or ignored) */ +#define TARGET_SIGBUS 10 /* bus error */ +#define TARGET_SIGSEGV 11 /* segmentation violation */ +#define TARGET_SIGSYS 12 /* bad argument to system call */ +#define TARGET_SIGPIPE 13 /* write on a pipe with no one to read it */ +#define TARGET_SIGALRM 14 /* alarm clock */ +#define TARGET_SIGTERM 15 /* software termination signal from kill */ +#define TARGET_SIGURG 16 /* urgent condition on IO channel */ +#define TARGET_SIGSTOP 17 /* sendable stop signal not from tty */ +#define TARGET_SIGTSTP 18 /* stop signal from tty */ +#define TARGET_SIGCONT 19 /* continue a stopped process */ +#define TARGET_SIGCHLD 20 /* to parent on child stop or exit */ +#define TARGET_SIGTTIN 21 /* to readers pgrp upon background tty read */ +#define TARGET_SIGTTOU 22 /* like TTIN for output if (tp->t_local<OSTOP) */ +#define TARGET_SIGIO 23 /* input/output possible signal */ +#define TARGET_SIGXCPU 24 /* exceeded CPU time limit */ +#define TARGET_SIGXFSZ 25 /* exceeded file size limit */ +#define TARGET_SIGVTALRM 26 /* virtual time alarm */ +#define TARGET_SIGPROF 27 /* profiling time alarm */ +#define TARGET_SIGWINCH 28 /* window size changes */ +#define TARGET_SIGINFO 29 /* information request */ +#define TARGET_SIGUSR1 30 /* user defined signal 1 */ +#define TARGET_SIGUSR2 31 /* user defined signal 2 */ + +/* + * Language spec says we must list exactly one parameter, even though we + * actually supply three. Ugh! + */ +#define TARGET_SIG_DFL (void (*)(int))0 +#define TARGET_SIG_IGN (void (*)(int))1 +#define TARGET_SIG_ERR (void (*)(int))-1 + +#define TARGET_SA_ONSTACK 0x0001 /* take signal on signal stack */ +#define TARGET_SA_RESTART 0x0002 /* restart system on signal return */ +#define TARGET_SA_RESETHAND 0x0004 /* reset to SIG_DFL when taking signal */ +#define TARGET_SA_NODEFER 0x0010 /* don't mask the signal we're delivering */ +#define TARGET_SA_NOCLDWAIT 0x0020 /* don't create zombies (assign to pid 1) */ +#define TARGET_SA_USERTRAMP 0x0100 /* do not bounce off kernel's sigtramp */ +#define TARGET_SA_NOCLDSTOP 0x0008 /* do not generate SIGCHLD on child stop */ +#define TARGET_SA_SIGINFO 0x0040 /* generate siginfo_t */ + +/* + * Flags for sigprocmask: + */ +#define TARGET_SIG_BLOCK 1 /* block specified signal set */ +#define TARGET_SIG_UNBLOCK 2 /* unblock specified signal set */ +#define TARGET_SIG_SETMASK 3 /* set specified signal set */ + +#define TARGET_BADSIG SIG_ERR + +#define TARGET_SS_ONSTACK 0x0001 /* take signals on alternate stack */ +#define TARGET_SS_DISABLE 0x0004 /* disable taking signals on alternate stack */ + +#include "errno_defs.h" + +#include "freebsd/syscall_nr.h" +#include "netbsd/syscall_nr.h" +#include "openbsd/syscall_nr.h" + +struct target_iovec { + abi_long iov_base; /* Starting address */ + abi_long iov_len; /* Number of bytes */ +}; + diff --git a/bsd-user/uaccess.c b/bsd-user/uaccess.c new file mode 100644 index 0000000..677f19c --- /dev/null +++ b/bsd-user/uaccess.c @@ -0,0 +1,65 @@ +/* User memory access */ +#include +#include + +#include "qemu.h" + +/* copy_from_user() and copy_to_user() are usually used to copy data + * buffers between the target and host. These internally perform + * locking/unlocking of the memory. + */ +abi_long copy_from_user(void *hptr, abi_ulong gaddr, size_t len) +{ + abi_long ret = 0; + void *ghptr; + + if ((ghptr = lock_user(VERIFY_READ, gaddr, len, 1))) { + memcpy(hptr, ghptr, len); + unlock_user(ghptr, gaddr, 0); + } else + ret = -TARGET_EFAULT; + + return ret; +} + + +abi_long copy_to_user(abi_ulong gaddr, void *hptr, size_t len) +{ + abi_long ret = 0; + void *ghptr; + + if ((ghptr = lock_user(VERIFY_WRITE, gaddr, len, 0))) { + memcpy(ghptr, hptr, len); + unlock_user(ghptr, gaddr, len); + } else + ret = -TARGET_EFAULT; + + return ret; +} + +/* Return the length of a string in target memory or -TARGET_EFAULT if + access error */ +abi_long target_strlen(abi_ulong guest_addr1) +{ + uint8_t *ptr; + abi_ulong guest_addr; + int max_len, len; + + guest_addr = guest_addr1; + for(;;) { + max_len = TARGET_PAGE_SIZE - (guest_addr & ~TARGET_PAGE_MASK); + ptr = lock_user(VERIFY_READ, guest_addr, max_len, 1); + if (!ptr) + return -TARGET_EFAULT; + len = qemu_strnlen((char *)ptr, max_len); + unlock_user(ptr, guest_addr, 0); + guest_addr += len; + /* we don't allow wrapping or integer overflow */ + if (guest_addr == 0 || + (guest_addr - guest_addr1) > 0x7fffffff) + return -TARGET_EFAULT; + if (len != max_len) + break; + } + return guest_addr - guest_addr1; +} diff --git a/bsd-user/x86_64/syscall.h b/bsd-user/x86_64/syscall.h new file mode 100644 index 0000000..5f71b7c --- /dev/null +++ b/bsd-user/x86_64/syscall.h @@ -0,0 +1,116 @@ +#define __USER_CS (0x33) +#define __USER_DS (0x2B) + +struct target_pt_regs { + abi_ulong r15; + abi_ulong r14; + abi_ulong r13; + abi_ulong r12; + abi_ulong rbp; + abi_ulong rbx; +/* arguments: non interrupts/non tracing syscalls only save upto here*/ + abi_ulong r11; + abi_ulong r10; + abi_ulong r9; + abi_ulong r8; + abi_ulong rax; + abi_ulong rcx; + abi_ulong rdx; + abi_ulong rsi; + abi_ulong rdi; + abi_ulong orig_rax; +/* end of arguments */ +/* cpu exception frame or undefined */ + abi_ulong rip; + abi_ulong cs; + abi_ulong eflags; + abi_ulong rsp; + abi_ulong ss; +/* top of stack page */ +}; + +/* Maximum number of LDT entries supported. */ +#define TARGET_LDT_ENTRIES 8192 +/* The size of each LDT entry. */ +#define TARGET_LDT_ENTRY_SIZE 8 + +#define TARGET_GDT_ENTRIES 16 +#define TARGET_GDT_ENTRY_TLS_ENTRIES 3 +#define TARGET_GDT_ENTRY_TLS_MIN 12 +#define TARGET_GDT_ENTRY_TLS_MAX 14 + +#if 0 // Redefine this +struct target_modify_ldt_ldt_s { + unsigned int entry_number; + abi_ulong base_addr; + unsigned int limit; + unsigned int seg_32bit:1; + unsigned int contents:2; + unsigned int read_exec_only:1; + unsigned int limit_in_pages:1; + unsigned int seg_not_present:1; + unsigned int useable:1; + unsigned int lm:1; +}; +#else +struct target_modify_ldt_ldt_s { + unsigned int entry_number; + abi_ulong base_addr; + unsigned int limit; + unsigned int flags; +}; +#endif + +struct target_ipc64_perm +{ + int key; + uint32_t uid; + uint32_t gid; + uint32_t cuid; + uint32_t cgid; + unsigned short mode; + unsigned short __pad1; + unsigned short seq; + unsigned short __pad2; + abi_ulong __unused1; + abi_ulong __unused2; +}; + +struct target_msqid64_ds { + struct target_ipc64_perm msg_perm; + unsigned int msg_stime; /* last msgsnd time */ + unsigned int msg_rtime; /* last msgrcv time */ + unsigned int msg_ctime; /* last change time */ + abi_ulong msg_cbytes; /* current number of bytes on queue */ + abi_ulong msg_qnum; /* number of messages in queue */ + abi_ulong msg_qbytes; /* max number of bytes on queue */ + unsigned int msg_lspid; /* pid of last msgsnd */ + unsigned int msg_lrpid; /* last receive pid */ + abi_ulong __unused4; + abi_ulong __unused5; +}; + +/* FreeBSD sysarch(2) */ +#define TARGET_FREEBSD_I386_GET_LDT 0 +#define TARGET_FREEBSD_I386_SET_LDT 1 + /* I386_IOPL */ +#define TARGET_FREEBSD_I386_GET_IOPERM 3 +#define TARGET_FREEBSD_I386_SET_IOPERM 4 + /* xxxxx */ +#define TARGET_FREEBSD_I386_GET_FSBASE 7 +#define TARGET_FREEBSD_I386_SET_FSBASE 8 +#define TARGET_FREEBSD_I386_GET_GSBASE 9 +#define TARGET_FREEBSD_I386_SET_GSBASE 10 + +#define TARGET_FREEBSD_AMD64_GET_FSBASE 128 +#define TARGET_FREEBSD_AMD64_SET_FSBASE 129 +#define TARGET_FREEBSD_AMD64_GET_GSBASE 130 +#define TARGET_FREEBSD_AMD64_SET_GSBASE 131 + + +#define UNAME_MACHINE "x86_64" + +#define TARGET_ARCH_SET_GS 0x1001 +#define TARGET_ARCH_SET_FS 0x1002 +#define TARGET_ARCH_GET_FS 0x1003 +#define TARGET_ARCH_GET_GS 0x1004 diff --git a/bsd-user/x86_64/target_signal.h b/bsd-user/x86_64/target_signal.h new file mode 100644 index 0000000..659cd40 --- /dev/null +++ b/bsd-user/x86_64/target_signal.h @@ -0,0 +1,19 @@ +#ifndef TARGET_SIGNAL_H +#define TARGET_SIGNAL_H + +#include "cpu.h" + +/* this struct defines a stack used during syscall handling */ + +typedef struct target_sigaltstack { + abi_ulong ss_sp; + abi_long ss_flags; + abi_ulong ss_size; +} target_stack_t; + +static inline abi_ulong get_sp_from_cpustate(CPUX86State *state) +{ + return state->regs[R_ESP]; +} + +#endif /* TARGET_SIGNAL_H */ diff --git a/bswap.h b/bswap.h new file mode 100644 index 0000000..82a7951 --- /dev/null +++ b/bswap.h @@ -0,0 +1,240 @@ +#ifndef BSWAP_H +#define BSWAP_H + +#include "config-host.h" + +#include + +#ifdef CONFIG_MACHINE_BSWAP_H +#include +#include +#include +#else + +#ifdef CONFIG_BYTESWAP_H +#include +#else + +#define bswap_16(x) \ +({ \ + uint16_t __x = (x); \ + ((uint16_t)( \ + (((uint16_t)(__x) & (uint16_t)0x00ffU) << 8) | \ + (((uint16_t)(__x) & (uint16_t)0xff00U) >> 8) )); \ +}) + +#define bswap_32(x) \ +({ \ + uint32_t __x = (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) )); \ +}) + +#define bswap_64(x) \ +({ \ + uint64_t __x = (x); \ + ((uint64_t)( \ + (uint64_t)(((uint64_t)(__x) & (uint64_t)0x00000000000000ffULL) << 56) | \ + (uint64_t)(((uint64_t)(__x) & (uint64_t)0x000000000000ff00ULL) << 40) | \ + (uint64_t)(((uint64_t)(__x) & (uint64_t)0x0000000000ff0000ULL) << 24) | \ + (uint64_t)(((uint64_t)(__x) & (uint64_t)0x00000000ff000000ULL) << 8) | \ + (uint64_t)(((uint64_t)(__x) & (uint64_t)0x000000ff00000000ULL) >> 8) | \ + (uint64_t)(((uint64_t)(__x) & (uint64_t)0x0000ff0000000000ULL) >> 24) | \ + (uint64_t)(((uint64_t)(__x) & (uint64_t)0x00ff000000000000ULL) >> 40) | \ + (uint64_t)(((uint64_t)(__x) & (uint64_t)0xff00000000000000ULL) >> 56) )); \ +}) + +#endif /* !CONFIG_BYTESWAP_H */ + +static inline uint16_t bswap16(uint16_t x) +{ + return bswap_16(x); +} + +static inline uint32_t bswap32(uint32_t x) +{ + return bswap_32(x); +} + +static inline uint64_t bswap64(uint64_t x) +{ + return bswap_64(x); +} + +#endif /* ! CONFIG_MACHINE_BSWAP_H */ + +static inline void bswap16s(uint16_t *s) +{ + *s = bswap16(*s); +} + +static inline void bswap32s(uint32_t *s) +{ + *s = bswap32(*s); +} + +static inline void bswap64s(uint64_t *s) +{ + *s = bswap64(*s); +} + +#if defined(HOST_WORDS_BIGENDIAN) +#define be_bswap(v, size) (v) +#define le_bswap(v, size) bswap ## size(v) +#define be_bswaps(v, size) +#define le_bswaps(p, size) *p = bswap ## size(*p); +#else +#define le_bswap(v, size) (v) +#define be_bswap(v, size) bswap ## size(v) +#define le_bswaps(v, size) +#define be_bswaps(p, size) *p = bswap ## size(*p); +#endif + +#define CPU_CONVERT(endian, size, type)\ +static inline type endian ## size ## _to_cpu(type v)\ +{\ + return endian ## _bswap(v, size);\ +}\ +\ +static inline type cpu_to_ ## endian ## size(type v)\ +{\ + return endian ## _bswap(v, size);\ +}\ +\ +static inline void endian ## size ## _to_cpus(type *p)\ +{\ + endian ## _bswaps(p, size)\ +}\ +\ +static inline void cpu_to_ ## endian ## size ## s(type *p)\ +{\ + endian ## _bswaps(p, size)\ +}\ +\ +static inline type endian ## size ## _to_cpup(const type *p)\ +{\ + return endian ## size ## _to_cpu(*p);\ +}\ +\ +static inline void cpu_to_ ## endian ## size ## w(type *p, type v)\ +{\ + *p = cpu_to_ ## endian ## size(v);\ +} + +CPU_CONVERT(be, 16, uint16_t) +CPU_CONVERT(be, 32, uint32_t) +CPU_CONVERT(be, 64, uint64_t) + +CPU_CONVERT(le, 16, uint16_t) +CPU_CONVERT(le, 32, uint32_t) +CPU_CONVERT(le, 64, uint64_t) + +/* unaligned versions (optimized for frequent unaligned accesses)*/ + +#if defined(__i386__) || defined(_ARCH_PPC) + +#define cpu_to_le16wu(p, v) cpu_to_le16w(p, v) +#define cpu_to_le32wu(p, v) cpu_to_le32w(p, v) +#define le16_to_cpupu(p) le16_to_cpup(p) +#define le32_to_cpupu(p) le32_to_cpup(p) +#define be32_to_cpupu(p) be32_to_cpup(p) + +#define cpu_to_be16wu(p, v) cpu_to_be16w(p, v) +#define cpu_to_be32wu(p, v) cpu_to_be32w(p, v) +#define cpu_to_be64wu(p, v) cpu_to_be64w(p, v) + +#else + +static inline void cpu_to_le16wu(uint16_t *p, uint16_t v) +{ + uint8_t *p1 = (uint8_t *)p; + + p1[0] = v & 0xff; + p1[1] = v >> 8; +} + +static inline void cpu_to_le32wu(uint32_t *p, uint32_t v) +{ + uint8_t *p1 = (uint8_t *)p; + + p1[0] = v & 0xff; + p1[1] = v >> 8; + p1[2] = v >> 16; + p1[3] = v >> 24; +} + +static inline uint16_t le16_to_cpupu(const uint16_t *p) +{ + const uint8_t *p1 = (const uint8_t *)p; + return p1[0] | (p1[1] << 8); +} + +static inline uint32_t le32_to_cpupu(const uint32_t *p) +{ + const uint8_t *p1 = (const uint8_t *)p; + return p1[0] | (p1[1] << 8) | (p1[2] << 16) | (p1[3] << 24); +} + +static inline uint32_t be32_to_cpupu(const uint32_t *p) +{ + const uint8_t *p1 = (const uint8_t *)p; + return p1[3] | (p1[2] << 8) | (p1[1] << 16) | (p1[0] << 24); +} + +static inline void cpu_to_be16wu(uint16_t *p, uint16_t v) +{ + uint8_t *p1 = (uint8_t *)p; + + p1[0] = v >> 8; + p1[1] = v & 0xff; +} + +static inline void cpu_to_be32wu(uint32_t *p, uint32_t v) +{ + uint8_t *p1 = (uint8_t *)p; + + p1[0] = v >> 24; + p1[1] = v >> 16; + p1[2] = v >> 8; + p1[3] = v & 0xff; +} + +static inline void cpu_to_be64wu(uint64_t *p, uint64_t v) +{ + uint8_t *p1 = (uint8_t *)p; + + p1[0] = v >> 56; + p1[1] = v >> 48; + p1[2] = v >> 40; + p1[3] = v >> 32; + p1[4] = v >> 24; + p1[5] = v >> 16; + p1[6] = v >> 8; + p1[7] = v & 0xff; +} + +#endif + +#ifdef HOST_WORDS_BIGENDIAN +#define cpu_to_32wu cpu_to_be32wu +#define leul_to_cpu(v) glue(glue(le,HOST_LONG_BITS),_to_cpu)(v) +#else +#define cpu_to_32wu cpu_to_le32wu +#define leul_to_cpu(v) (v) +#endif + +#undef le_bswap +#undef be_bswap +#undef le_bswaps +#undef be_bswaps + +/* len must be one of 1, 2, 4 */ +static inline uint32_t qemu_bswap_len(uint32_t value, int len) +{ + return bswap32(value) >> (32 - 8 * len); +} + +#endif /* BSWAP_H */ diff --git a/bt-host.c b/bt-host.c new file mode 100644 index 0000000..6931e7c --- /dev/null +++ b/bt-host.c @@ -0,0 +1,199 @@ +/* + * Wrap a host Bluetooth HCI socket in a struct HCIInfo. + * + * Copyright (C) 2008 Andrzej Zaborowski + * + * 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 . + */ + +#include "qemu-common.h" +#include "qemu-char.h" +#include "sysemu.h" +#include "net.h" +#include "bt-host.h" + +#ifndef _WIN32 +# include +# include +# include +# ifdef CONFIG_BLUEZ +# include +# include +# include +# else +# include "hw/bt.h" +# define HCI_MAX_FRAME_SIZE 1028 +# endif + +struct bt_host_hci_s { + struct HCIInfo hci; + int fd; + + uint8_t hdr[HCI_MAX_FRAME_SIZE]; + int len; +}; + +static void bt_host_send(struct HCIInfo *hci, + int type, const uint8_t *data, int len) +{ + struct bt_host_hci_s *s = (struct bt_host_hci_s *) hci; + uint8_t pkt = type; + struct iovec iv[2]; + + iv[0].iov_base = (void *)&pkt; + iv[0].iov_len = 1; + iv[1].iov_base = (void *) data; + iv[1].iov_len = len; + + while (writev(s->fd, iv, 2) < 0) { + if (errno != EAGAIN && errno != EINTR) { + fprintf(stderr, "qemu: error %i writing bluetooth packet.\n", + errno); + return; + } + } +} + +static void bt_host_cmd(struct HCIInfo *hci, const uint8_t *data, int len) +{ + bt_host_send(hci, HCI_COMMAND_PKT, data, len); +} + +static void bt_host_acl(struct HCIInfo *hci, const uint8_t *data, int len) +{ + bt_host_send(hci, HCI_ACLDATA_PKT, data, len); +} + +static void bt_host_sco(struct HCIInfo *hci, const uint8_t *data, int len) +{ + bt_host_send(hci, HCI_SCODATA_PKT, data, len); +} + +static void bt_host_read(void *opaque) +{ + struct bt_host_hci_s *s = (struct bt_host_hci_s *) opaque; + uint8_t *pkt; + int pktlen; + + /* Seems that we can't read only the header first and then the amount + * of data indicated in the header because Linux will discard everything + * that's not been read in one go. */ + s->len = read(s->fd, s->hdr, sizeof(s->hdr)); + + if (s->len < 0) { + fprintf(stderr, "qemu: error %i reading HCI frame\n", errno); + return; + } + + pkt = s->hdr; + while (s->len --) + switch (*pkt ++) { + case HCI_EVENT_PKT: + if (s->len < 2) + goto bad_pkt; + + pktlen = MIN(pkt[1] + 2, s->len); + s->hci.evt_recv(s->hci.opaque, pkt, pktlen); + s->len -= pktlen; + pkt += pktlen; + + /* TODO: if this is an Inquiry Result event, it's also + * interpreted by Linux kernel before we received it, possibly + * we should clean the kernel Inquiry cache through + * ioctl(s->fd, HCI_INQUIRY, ...). */ + break; + + case HCI_ACLDATA_PKT: + if (s->len < 4) + goto bad_pkt; + + pktlen = MIN(((pkt[3] << 8) | pkt[2]) + 4, s->len); + s->hci.acl_recv(s->hci.opaque, pkt, pktlen); + s->len -= pktlen; + pkt += pktlen; + break; + + case HCI_SCODATA_PKT: + if (s->len < 3) + goto bad_pkt; + + pktlen = MIN(pkt[2] + 3, s->len); + s->len -= pktlen; + pkt += pktlen; + + default: + bad_pkt: + fprintf(stderr, "qemu: bad HCI packet type %02x\n", pkt[-1]); + } +} + +static int bt_host_bdaddr_set(struct HCIInfo *hci, const uint8_t *bd_addr) +{ + return -ENOTSUP; +} + +struct HCIInfo *bt_host_hci(const char *id) +{ + struct bt_host_hci_s *s; + int fd = -1; +# ifdef CONFIG_BLUEZ + int dev_id = hci_devid(id); + struct hci_filter flt; + + if (dev_id < 0) { + fprintf(stderr, "qemu: `%s' not available\n", id); + return 0; + } + + fd = hci_open_dev(dev_id); + + /* XXX: can we ensure nobody else has the device opened? */ +# endif + + if (fd < 0) { + fprintf(stderr, "qemu: Can't open `%s': %s (%i)\n", + id, strerror(errno), errno); + return NULL; + } + +# ifdef CONFIG_BLUEZ + hci_filter_clear(&flt); + hci_filter_all_ptypes(&flt); + hci_filter_all_events(&flt); + + if (setsockopt(fd, SOL_HCI, HCI_FILTER, &flt, sizeof(flt)) < 0) { + fprintf(stderr, "qemu: Can't set HCI filter on socket (%i)\n", errno); + return 0; + } +# endif + + s = qemu_mallocz(sizeof(struct bt_host_hci_s)); + s->fd = fd; + s->hci.cmd_send = bt_host_cmd; + s->hci.sco_send = bt_host_sco; + s->hci.acl_send = bt_host_acl; + s->hci.bdaddr_set = bt_host_bdaddr_set; + + qemu_set_fd_handler(s->fd, bt_host_read, NULL, s); + + return &s->hci; +} +#else +struct HCIInfo *bt_host_hci(const char *id) +{ + fprintf(stderr, "qemu: bluetooth passthrough not supported (yet)\n"); + + return 0; +} +#endif diff --git a/bt-host.h b/bt-host.h new file mode 100644 index 0000000..f1eff65 --- /dev/null +++ b/bt-host.h @@ -0,0 +1,9 @@ +#ifndef BT_HOST_H +#define BT_HOST_H + +struct HCIInfo; + +/* bt-host.c */ +struct HCIInfo *bt_host_hci(const char *id); + +#endif diff --git a/bt-vhci.c b/bt-vhci.c new file mode 100644 index 0000000..679c5e0 --- /dev/null +++ b/bt-vhci.c @@ -0,0 +1,168 @@ +/* + * Support for host VHCIs inside qemu scatternets. + * + * Copyright (C) 2008 Andrzej Zaborowski + * + * 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 . + */ + +#include "qemu-common.h" +#include "qemu-char.h" +#include "sysemu.h" +#include "net.h" +#include "hw/bt.h" + +#define VHCI_DEV "/dev/vhci" +#define VHCI_UDEV "/dev/hci_vhci" + +struct bt_vhci_s { + int fd; + struct HCIInfo *info; + + uint8_t hdr[4096]; + int len; +}; + +static void vhci_read(void *opaque) +{ + struct bt_vhci_s *s = (struct bt_vhci_s *) opaque; + uint8_t *pkt; + int pktlen; + + /* Seems that we can't read only the header first and then the amount + * of data indicated in the header because Linux will discard everything + * that's not been read in one go. */ + s->len = read(s->fd, s->hdr, sizeof(s->hdr)); + + if (s->len < 0) { + fprintf(stderr, "qemu: error %i reading the PDU\n", errno); + return; + } + + pkt = s->hdr; + while (s->len --) + switch (*pkt ++) { + case HCI_COMMAND_PKT: + if (s->len < 3) + goto bad_pkt; + + pktlen = MIN(pkt[2] + 3, s->len); + s->info->cmd_send(s->info, pkt, pktlen); + s->len -= pktlen; + pkt += pktlen; + break; + + case HCI_ACLDATA_PKT: + if (s->len < 4) + goto bad_pkt; + + pktlen = MIN(((pkt[3] << 8) | pkt[2]) + 4, s->len); + s->info->acl_send(s->info, pkt, pktlen); + s->len -= pktlen; + pkt += pktlen; + break; + + case HCI_SCODATA_PKT: + if (s->len < 3) + goto bad_pkt; + + pktlen = MIN(pkt[2] + 3, s->len); + s->info->sco_send(s->info, pkt, pktlen); + s->len -= pktlen; + pkt += pktlen; + break; + + default: + bad_pkt: + fprintf(stderr, "qemu: bad HCI packet type %02x\n", pkt[-1]); + } +} + +static void vhci_host_send(void *opaque, + int type, const uint8_t *data, int len) +{ + struct bt_vhci_s *s = (struct bt_vhci_s *) opaque; +#if 0 + uint8_t pkt = type; + struct iovec iv[2]; + + iv[0].iov_base = &pkt; + iv[0].iov_len = 1; + iv[1].iov_base = (void *) data; + iv[1].iov_len = len; + + while (writev(s->fd, iv, 2) < 0) + if (errno != EAGAIN && errno != EINTR) { + fprintf(stderr, "qemu: error %i writing bluetooth packet.\n", + errno); + return; + } +#else + /* Apparently VHCI wants us to write everything in one chunk :-( */ + static uint8_t buf[4096]; + + buf[0] = type; + memcpy(buf + 1, data, len); + + while (write(s->fd, buf, len + 1) < 0) + if (errno != EAGAIN && errno != EINTR) { + fprintf(stderr, "qemu: error %i writing bluetooth packet.\n", + errno); + return; + } +#endif +} + +static void vhci_out_hci_packet_event(void *opaque, + const uint8_t *data, int len) +{ + vhci_host_send(opaque, HCI_EVENT_PKT, data, len); +} + +static void vhci_out_hci_packet_acl(void *opaque, + const uint8_t *data, int len) +{ + vhci_host_send(opaque, HCI_ACLDATA_PKT, data, len); +} + +void bt_vhci_init(struct HCIInfo *info) +{ + struct bt_vhci_s *s; + int err[2]; + int fd; + + fd = open(VHCI_DEV, O_RDWR); + err[0] = errno; + if (fd < 0) { + fd = open(VHCI_UDEV, O_RDWR); + err[1] = errno; + } + + if (fd < 0) { + fprintf(stderr, "qemu: Can't open `%s': %s (%i)\n", + VHCI_DEV, strerror(err[0]), err[0]); + fprintf(stderr, "qemu: Can't open `%s': %s (%i)\n", + VHCI_UDEV, strerror(err[1]), err[1]); + exit(-1); + } + + s = qemu_mallocz(sizeof(struct bt_vhci_s)); + s->fd = fd; + s->info = info ?: qemu_next_hci(); + s->info->opaque = s; + s->info->evt_recv = vhci_out_hci_packet_event; + s->info->acl_recv = vhci_out_hci_packet_acl; + + qemu_set_fd_handler(s->fd, vhci_read, NULL, s); +} diff --git a/buffered_file.c b/buffered_file.c new file mode 100644 index 0000000..8435a31 --- /dev/null +++ b/buffered_file.c @@ -0,0 +1,282 @@ +/* + * QEMU buffered QEMUFile + * + * Copyright IBM, Corp. 2008 + * + * Authors: + * Anthony Liguori + * + * 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 "hw/hw.h" +#include "qemu-timer.h" +#include "sysemu.h" +#include "qemu-char.h" +#include "buffered_file.h" + +//#define DEBUG_BUFFERED_FILE + +typedef struct QEMUFileBuffered +{ + BufferedPutFunc *put_buffer; + BufferedPutReadyFunc *put_ready; + BufferedWaitForUnfreezeFunc *wait_for_unfreeze; + BufferedCloseFunc *close; + void *opaque; + QEMUFile *file; + int has_error; + int freeze_output; + size_t bytes_xfer; + size_t xfer_limit; + uint8_t *buffer; + size_t buffer_size; + size_t buffer_capacity; + QEMUTimer *timer; +} QEMUFileBuffered; + +#ifdef DEBUG_BUFFERED_FILE +#define DPRINTF(fmt, ...) \ + do { printf("buffered-file: " fmt, ## __VA_ARGS__); } while (0) +#else +#define DPRINTF(fmt, ...) \ + do { } while (0) +#endif + +static void buffered_append(QEMUFileBuffered *s, + const uint8_t *buf, size_t size) +{ + if (size > (s->buffer_capacity - s->buffer_size)) { + void *tmp; + + DPRINTF("increasing buffer capacity from %zu by %zu\n", + s->buffer_capacity, size + 1024); + + s->buffer_capacity += size + 1024; + + tmp = qemu_realloc(s->buffer, s->buffer_capacity); + if (tmp == NULL) { + fprintf(stderr, "qemu file buffer expansion failed\n"); + exit(1); + } + + s->buffer = tmp; + } + + memcpy(s->buffer + s->buffer_size, buf, size); + s->buffer_size += size; +} + +static void buffered_flush(QEMUFileBuffered *s) +{ + size_t offset = 0; + + if (s->has_error) { + DPRINTF("flush when error, bailing\n"); + return; + } + + DPRINTF("flushing %zu byte(s) of data\n", s->buffer_size); + + while (offset < s->buffer_size) { + ssize_t ret; + + ret = s->put_buffer(s->opaque, s->buffer + offset, + s->buffer_size - offset); + if (ret == -EAGAIN) { + DPRINTF("backend not ready, freezing\n"); + s->freeze_output = 1; + break; + } + + if (ret <= 0) { + DPRINTF("error flushing data, %zd\n", ret); + s->has_error = 1; + break; + } else { + DPRINTF("flushed %zd byte(s)\n", ret); + offset += ret; + } + } + + DPRINTF("flushed %zu of %zu byte(s)\n", offset, s->buffer_size); + memmove(s->buffer, s->buffer + offset, s->buffer_size - offset); + s->buffer_size -= offset; +} + +static int buffered_put_buffer(void *opaque, const uint8_t *buf, int64_t pos, int size) +{ + QEMUFileBuffered *s = opaque; + int offset = 0; + ssize_t ret; + + DPRINTF("putting %d bytes at %" PRId64 "\n", size, pos); + + if (s->has_error) { + DPRINTF("flush when error, bailing\n"); + return -EINVAL; + } + + DPRINTF("unfreezing output\n"); + s->freeze_output = 0; + + buffered_flush(s); + + while (!s->freeze_output && offset < size) { + if (s->bytes_xfer > s->xfer_limit) { + DPRINTF("transfer limit exceeded when putting\n"); + break; + } + + ret = s->put_buffer(s->opaque, buf + offset, size - offset); + if (ret == -EAGAIN) { + DPRINTF("backend not ready, freezing\n"); + s->freeze_output = 1; + break; + } + + if (ret <= 0) { + DPRINTF("error putting\n"); + s->has_error = 1; + offset = -EINVAL; + break; + } + + DPRINTF("put %zd byte(s)\n", ret); + offset += ret; + s->bytes_xfer += ret; + } + + if (offset >= 0) { + DPRINTF("buffering %d bytes\n", size - offset); + buffered_append(s, buf + offset, size - offset); + offset = size; + } + + if (pos == 0 && size == 0) { + DPRINTF("file is ready\n"); + if (s->bytes_xfer <= s->xfer_limit) { + DPRINTF("notifying client\n"); + s->put_ready(s->opaque); + } + } + + return offset; +} + +static int buffered_close(void *opaque) +{ + QEMUFileBuffered *s = opaque; + int ret; + + DPRINTF("closing\n"); + + while (!s->has_error && s->buffer_size) { + buffered_flush(s); + if (s->freeze_output) + s->wait_for_unfreeze(s); + } + + ret = s->close(s->opaque); + + qemu_del_timer(s->timer); + qemu_free_timer(s->timer); + qemu_free(s->buffer); + qemu_free(s); + + return ret; +} + +static int buffered_rate_limit(void *opaque) +{ + QEMUFileBuffered *s = opaque; + + if (s->has_error) + return 0; + + if (s->freeze_output) + return 1; + + if (s->bytes_xfer > s->xfer_limit) + return 1; + + return 0; +} + +static int64_t buffered_set_rate_limit(void *opaque, int64_t new_rate) +{ + QEMUFileBuffered *s = opaque; + if (s->has_error) + goto out; + + if (new_rate > SIZE_MAX) { + new_rate = SIZE_MAX; + } + + s->xfer_limit = new_rate / 10; + +out: + return s->xfer_limit; +} + +static int64_t buffered_get_rate_limit(void *opaque) +{ + QEMUFileBuffered *s = opaque; + + return s->xfer_limit; +} + +static void buffered_rate_tick(void *opaque) +{ + QEMUFileBuffered *s = opaque; + + if (s->has_error) { + buffered_close(s); + return; + } + + qemu_mod_timer(s->timer, qemu_get_clock(rt_clock) + 100); + + if (s->freeze_output) + return; + + s->bytes_xfer = 0; + + buffered_flush(s); + + /* Add some checks around this */ + s->put_ready(s->opaque); +} + +QEMUFile *qemu_fopen_ops_buffered(void *opaque, + size_t bytes_per_sec, + BufferedPutFunc *put_buffer, + BufferedPutReadyFunc *put_ready, + BufferedWaitForUnfreezeFunc *wait_for_unfreeze, + BufferedCloseFunc *close) +{ + QEMUFileBuffered *s; + + s = qemu_mallocz(sizeof(*s)); + + s->opaque = opaque; + s->xfer_limit = bytes_per_sec / 10; + s->put_buffer = put_buffer; + s->put_ready = put_ready; + s->wait_for_unfreeze = wait_for_unfreeze; + s->close = close; + + s->file = qemu_fopen_ops(s, buffered_put_buffer, NULL, + buffered_close, buffered_rate_limit, + buffered_set_rate_limit, + buffered_get_rate_limit); + + s->timer = qemu_new_timer(rt_clock, buffered_rate_tick, s); + + qemu_mod_timer(s->timer, qemu_get_clock(rt_clock) + 100); + + return s->file; +} diff --git a/buffered_file.h b/buffered_file.h new file mode 100644 index 0000000..98d358b --- /dev/null +++ b/buffered_file.h @@ -0,0 +1,30 @@ +/* + * QEMU buffered QEMUFile + * + * Copyright IBM, Corp. 2008 + * + * Authors: + * Anthony Liguori + * + * This work is licensed under the terms of the GNU GPL, version 2. See + * the COPYING file in the top-level directory. + * + */ + +#ifndef QEMU_BUFFERED_FILE_H +#define QEMU_BUFFERED_FILE_H + +#include "hw/hw.h" + +typedef ssize_t (BufferedPutFunc)(void *opaque, const void *data, size_t size); +typedef void (BufferedPutReadyFunc)(void *opaque); +typedef void (BufferedWaitForUnfreezeFunc)(void *opaque); +typedef int (BufferedCloseFunc)(void *opaque); + +QEMUFile *qemu_fopen_ops_buffered(void *opaque, size_t xfer_limit, + BufferedPutFunc *put_buffer, + BufferedPutReadyFunc *put_ready, + BufferedWaitForUnfreezeFunc *wait_for_unfreeze, + BufferedCloseFunc *close); + +#endif diff --git a/cache-utils.c b/cache-utils.c new file mode 100644 index 0000000..2db5af2 --- /dev/null +++ b/cache-utils.c @@ -0,0 +1,97 @@ +#include "cache-utils.h" + +#if defined(_ARCH_PPC) +struct qemu_cache_conf qemu_cache_conf = { + .dcache_bsize = 16, + .icache_bsize = 16 +}; + +#if defined _AIX +#include + +static void ppc_init_cacheline_sizes(void) +{ + qemu_cache_conf.icache_bsize = _system_configuration.icache_line; + qemu_cache_conf.dcache_bsize = _system_configuration.dcache_line; +} + +#elif defined __linux__ + +#define QEMU_AT_NULL 0 +#define QEMU_AT_DCACHEBSIZE 19 +#define QEMU_AT_ICACHEBSIZE 20 + +static void ppc_init_cacheline_sizes(char **envp) +{ + unsigned long *auxv; + + while (*envp++); + + for (auxv = (unsigned long *) envp; *auxv != QEMU_AT_NULL; auxv += 2) { + switch (*auxv) { + case QEMU_AT_DCACHEBSIZE: qemu_cache_conf.dcache_bsize = auxv[1]; break; + case QEMU_AT_ICACHEBSIZE: qemu_cache_conf.icache_bsize = auxv[1]; break; + default: break; + } + } +} + +#elif defined __APPLE__ +#include +#include +#include + +static void ppc_init_cacheline_sizes(void) +{ + size_t len; + unsigned cacheline; + int name[2] = { CTL_HW, HW_CACHELINE }; + + len = sizeof(cacheline); + if (sysctl(name, 2, &cacheline, &len, NULL, 0)) { + perror("sysctl CTL_HW HW_CACHELINE failed"); + } else { + qemu_cache_conf.dcache_bsize = cacheline; + qemu_cache_conf.icache_bsize = cacheline; + } +} +#endif + +#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) +#include +#include +#include +#include +#include +#include + +static void ppc_init_cacheline_sizes(void) +{ + size_t len = 4; + unsigned cacheline; + + if (sysctlbyname ("machdep.cacheline_size", &cacheline, &len, NULL, 0)) { + fprintf(stderr, "sysctlbyname machdep.cacheline_size failed: %s\n", + strerror(errno)); + exit(1); + } + + qemu_cache_conf.dcache_bsize = cacheline; + qemu_cache_conf.icache_bsize = cacheline; +} +#endif + +#ifdef __linux__ +void qemu_cache_utils_init(char **envp) +{ + ppc_init_cacheline_sizes(envp); +} +#else +void qemu_cache_utils_init(char **envp) +{ + (void) envp; + ppc_init_cacheline_sizes(); +} +#endif + +#endif /* _ARCH_PPC */ diff --git a/cache-utils.h b/cache-utils.h new file mode 100644 index 0000000..0b65907 --- /dev/null +++ b/cache-utils.h @@ -0,0 +1,41 @@ +#ifndef QEMU_CACHE_UTILS_H +#define QEMU_CACHE_UTILS_H + +#if defined(_ARCH_PPC) +struct qemu_cache_conf { + unsigned long dcache_bsize; + unsigned long icache_bsize; +}; + +extern struct qemu_cache_conf qemu_cache_conf; + +void qemu_cache_utils_init(char **envp); + +/* mildly adjusted code from tcg-dyngen.c */ +static inline void flush_icache_range(unsigned long start, unsigned long stop) +{ + unsigned long p, start1, stop1; + unsigned long dsize = qemu_cache_conf.dcache_bsize; + unsigned long isize = qemu_cache_conf.icache_bsize; + + start1 = start & ~(dsize - 1); + stop1 = (stop + dsize - 1) & ~(dsize - 1); + for (p = start1; p < stop1; p += dsize) { + asm volatile ("dcbst 0,%0" : : "r"(p) : "memory"); + } + asm volatile ("sync" : : : "memory"); + + start &= start & ~(isize - 1); + stop1 = (stop + isize - 1) & ~(isize - 1); + for (p = start1; p < stop1; p += isize) { + asm volatile ("icbi 0,%0" : : "r"(p) : "memory"); + } + asm volatile ("sync" : : : "memory"); + asm volatile ("isync" : : : "memory"); +} + +#else +#define qemu_cache_utils_init(envp) do { (void) (envp); } while (0) +#endif + +#endif /* QEMU_CACHE_UTILS_H */ diff --git a/check-qdict.c b/check-qdict.c new file mode 100644 index 0000000..6afce5a --- /dev/null +++ b/check-qdict.c @@ -0,0 +1,401 @@ +/* + * QDict unit-tests. + * + * Copyright (C) 2009 Red Hat Inc. + * + * Authors: + * Luiz Capitulino + * + * This work is licensed under the terms of the GNU LGPL, version 2.1 or later. + * See the COPYING.LIB file in the top-level directory. + */ +#include + +#include "qint.h" +#include "qdict.h" +#include "qstring.h" +#include "qemu-common.h" + +/* + * Public Interface test-cases + * + * (with some violations to access 'private' data) + */ + +START_TEST(qdict_new_test) +{ + QDict *qdict; + + qdict = qdict_new(); + fail_unless(qdict != NULL); + fail_unless(qdict_size(qdict) == 0); + fail_unless(qdict->base.refcnt == 1); + fail_unless(qobject_type(QOBJECT(qdict)) == QTYPE_QDICT); + + // destroy doesn't exit yet + free(qdict); +} +END_TEST + +START_TEST(qdict_put_obj_test) +{ + QInt *qi; + QDict *qdict; + QDictEntry *ent; + const int num = 42; + + qdict = qdict_new(); + + // key "" will have tdb hash 12345 + qdict_put_obj(qdict, "", QOBJECT(qint_from_int(num))); + + fail_unless(qdict_size(qdict) == 1); + ent = QLIST_FIRST(&qdict->table[12345 % QDICT_BUCKET_MAX]); + qi = qobject_to_qint(ent->value); + fail_unless(qint_get_int(qi) == num); + + // destroy doesn't exit yet + QDECREF(qi); + qemu_free(ent->key); + qemu_free(ent); + qemu_free(qdict); +} +END_TEST + +START_TEST(qdict_destroy_simple_test) +{ + QDict *qdict; + + qdict = qdict_new(); + qdict_put_obj(qdict, "num", QOBJECT(qint_from_int(0))); + qdict_put_obj(qdict, "str", QOBJECT(qstring_from_str("foo"))); + + QDECREF(qdict); +} +END_TEST + +static QDict *tests_dict = NULL; + +static void qdict_setup(void) +{ + tests_dict = qdict_new(); + fail_unless(tests_dict != NULL); +} + +static void qdict_teardown(void) +{ + QDECREF(tests_dict); + tests_dict = NULL; +} + +START_TEST(qdict_get_test) +{ + QInt *qi; + QObject *obj; + const int value = -42; + const char *key = "test"; + + qdict_put(tests_dict, key, qint_from_int(value)); + + obj = qdict_get(tests_dict, key); + fail_unless(obj != NULL); + + qi = qobject_to_qint(obj); + fail_unless(qint_get_int(qi) == value); +} +END_TEST + +START_TEST(qdict_get_int_test) +{ + int ret; + const int value = 100; + const char *key = "int"; + + qdict_put(tests_dict, key, qint_from_int(value)); + + ret = qdict_get_int(tests_dict, key); + fail_unless(ret == value); +} +END_TEST + +START_TEST(qdict_get_try_int_test) +{ + int ret; + const int value = 100; + const char *key = "int"; + + qdict_put(tests_dict, key, qint_from_int(value)); + + ret = qdict_get_try_int(tests_dict, key, 0); + fail_unless(ret == value); +} +END_TEST + +START_TEST(qdict_get_str_test) +{ + const char *p; + const char *key = "key"; + const char *str = "string"; + + qdict_put(tests_dict, key, qstring_from_str(str)); + + p = qdict_get_str(tests_dict, key); + fail_unless(p != NULL); + fail_unless(strcmp(p, str) == 0); +} +END_TEST + +START_TEST(qdict_get_try_str_test) +{ + const char *p; + const char *key = "key"; + const char *str = "string"; + + qdict_put(tests_dict, key, qstring_from_str(str)); + + p = qdict_get_try_str(tests_dict, key); + fail_unless(p != NULL); + fail_unless(strcmp(p, str) == 0); +} +END_TEST + +START_TEST(qdict_haskey_not_test) +{ + fail_unless(qdict_haskey(tests_dict, "test") == 0); +} +END_TEST + +START_TEST(qdict_haskey_test) +{ + const char *key = "test"; + + qdict_put(tests_dict, key, qint_from_int(0)); + fail_unless(qdict_haskey(tests_dict, key) == 1); +} +END_TEST + +START_TEST(qdict_del_test) +{ + const char *key = "key test"; + + qdict_put(tests_dict, key, qstring_from_str("foo")); + fail_unless(qdict_size(tests_dict) == 1); + + qdict_del(tests_dict, key); + + fail_unless(qdict_size(tests_dict) == 0); + fail_unless(qdict_haskey(tests_dict, key) == 0); +} +END_TEST + +START_TEST(qobject_to_qdict_test) +{ + fail_unless(qobject_to_qdict(QOBJECT(tests_dict)) == tests_dict); +} +END_TEST + +START_TEST(qdict_iterapi_test) +{ + int count; + const QDictEntry *ent; + + fail_unless(qdict_first(tests_dict) == NULL); + + qdict_put(tests_dict, "key1", qint_from_int(1)); + qdict_put(tests_dict, "key2", qint_from_int(2)); + qdict_put(tests_dict, "key3", qint_from_int(3)); + + count = 0; + for (ent = qdict_first(tests_dict); ent; ent = qdict_next(tests_dict, ent)){ + fail_unless(qdict_haskey(tests_dict, qdict_entry_key(ent)) == 1); + count++; + } + + fail_unless(count == qdict_size(tests_dict)); + + /* Do it again to test restarting */ + count = 0; + for (ent = qdict_first(tests_dict); ent; ent = qdict_next(tests_dict, ent)){ + fail_unless(qdict_haskey(tests_dict, qdict_entry_key(ent)) == 1); + count++; + } + + fail_unless(count == qdict_size(tests_dict)); +} +END_TEST + +/* + * Errors test-cases + */ + +START_TEST(qdict_put_exists_test) +{ + int value; + const char *key = "exists"; + + qdict_put(tests_dict, key, qint_from_int(1)); + qdict_put(tests_dict, key, qint_from_int(2)); + + value = qdict_get_int(tests_dict, key); + fail_unless(value == 2); + + fail_unless(qdict_size(tests_dict) == 1); +} +END_TEST + +START_TEST(qdict_get_not_exists_test) +{ + fail_unless(qdict_get(tests_dict, "foo") == NULL); +} +END_TEST + +/* + * Stress test-case + * + * This is a lot big for a unit-test, but there is no other place + * to have it. + */ + +static void remove_dots(char *string) +{ + char *p = strchr(string, ':'); + if (p) + *p = '\0'; +} + +static QString *read_line(FILE *file, char *key) +{ + char value[128]; + + if (fscanf(file, "%s%s", key, value) == EOF) + return NULL; + remove_dots(key); + return qstring_from_str(value); +} + +#define reset_file(file) fseek(file, 0L, SEEK_SET) + +START_TEST(qdict_stress_test) +{ + size_t lines; + char key[128]; + FILE *test_file; + QDict *qdict; + QString *value; + const char *test_file_path = "qdict-test-data.txt"; + + test_file = fopen(test_file_path, "r"); + fail_unless(test_file != NULL); + + // Create the dict + qdict = qdict_new(); + fail_unless(qdict != NULL); + + // Add everything from the test file + for (lines = 0;; lines++) { + value = read_line(test_file, key); + if (!value) + break; + + qdict_put(qdict, key, value); + } + fail_unless(qdict_size(qdict) == lines); + + // Check if everything is really in there + reset_file(test_file); + for (;;) { + const char *str1, *str2; + + value = read_line(test_file, key); + if (!value) + break; + + str1 = qstring_get_str(value); + + str2 = qdict_get_str(qdict, key); + fail_unless(str2 != NULL); + + fail_unless(strcmp(str1, str2) == 0); + + QDECREF(value); + } + + // Delete everything + reset_file(test_file); + for (;;) { + value = read_line(test_file, key); + if (!value) + break; + + qdict_del(qdict, key); + QDECREF(value); + + fail_unless(qdict_haskey(qdict, key) == 0); + } + fclose(test_file); + + fail_unless(qdict_size(qdict) == 0); + QDECREF(qdict); +} +END_TEST + +static Suite *qdict_suite(void) +{ + Suite *s; + TCase *qdict_public_tcase; + TCase *qdict_public2_tcase; + TCase *qdict_stress_tcase; + TCase *qdict_errors_tcase; + + s = suite_create("QDict test-suite"); + + qdict_public_tcase = tcase_create("Public Interface"); + suite_add_tcase(s, qdict_public_tcase); + tcase_add_test(qdict_public_tcase, qdict_new_test); + tcase_add_test(qdict_public_tcase, qdict_put_obj_test); + tcase_add_test(qdict_public_tcase, qdict_destroy_simple_test); + + /* Continue, but now with fixtures */ + qdict_public2_tcase = tcase_create("Public Interface (2)"); + suite_add_tcase(s, qdict_public2_tcase); + tcase_add_checked_fixture(qdict_public2_tcase, qdict_setup, qdict_teardown); + tcase_add_test(qdict_public2_tcase, qdict_get_test); + tcase_add_test(qdict_public2_tcase, qdict_get_int_test); + tcase_add_test(qdict_public2_tcase, qdict_get_try_int_test); + tcase_add_test(qdict_public2_tcase, qdict_get_str_test); + tcase_add_test(qdict_public2_tcase, qdict_get_try_str_test); + tcase_add_test(qdict_public2_tcase, qdict_haskey_not_test); + tcase_add_test(qdict_public2_tcase, qdict_haskey_test); + tcase_add_test(qdict_public2_tcase, qdict_del_test); + tcase_add_test(qdict_public2_tcase, qobject_to_qdict_test); + tcase_add_test(qdict_public2_tcase, qdict_iterapi_test); + + qdict_errors_tcase = tcase_create("Errors"); + suite_add_tcase(s, qdict_errors_tcase); + tcase_add_checked_fixture(qdict_errors_tcase, qdict_setup, qdict_teardown); + tcase_add_test(qdict_errors_tcase, qdict_put_exists_test); + tcase_add_test(qdict_errors_tcase, qdict_get_not_exists_test); + + /* The Big one */ + qdict_stress_tcase = tcase_create("Stress Test"); + suite_add_tcase(s, qdict_stress_tcase); + tcase_add_test(qdict_stress_tcase, qdict_stress_test); + + return s; +} + +int main(void) +{ + int nf; + Suite *s; + SRunner *sr; + + s = qdict_suite(); + sr = srunner_create(s); + + srunner_run_all(sr, CK_NORMAL); + nf = srunner_ntests_failed(sr); + srunner_free(sr); + + return (nf == 0) ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/check-qfloat.c b/check-qfloat.c new file mode 100644 index 0000000..b71d983 --- /dev/null +++ b/check-qfloat.c @@ -0,0 +1,76 @@ +/* + * QFloat unit-tests. + * + * Copyright IBM, Corp. 2009 + * + * Authors: + * Anthony Liguori + * + * This work is licensed under the terms of the GNU LGPL, version 2.1 or later. + * See the COPYING.LIB file in the top-level directory. + * + */ +#include + +#include "qfloat.h" +#include "qemu-common.h" + +/* + * Public Interface test-cases + * + * (with some violations to access 'private' data) + */ + +START_TEST(qfloat_from_double_test) +{ + QFloat *qf; + const double value = -42.23423; + + qf = qfloat_from_double(value); + fail_unless(qf != NULL); + fail_unless(qf->value == value); + fail_unless(qf->base.refcnt == 1); + fail_unless(qobject_type(QOBJECT(qf)) == QTYPE_QFLOAT); + + // destroy doesn't exit yet + qemu_free(qf); +} +END_TEST + +START_TEST(qfloat_destroy_test) +{ + QFloat *qf = qfloat_from_double(0.0); + QDECREF(qf); +} +END_TEST + +static Suite *qfloat_suite(void) +{ + Suite *s; + TCase *qfloat_public_tcase; + + s = suite_create("QFloat test-suite"); + + qfloat_public_tcase = tcase_create("Public Interface"); + suite_add_tcase(s, qfloat_public_tcase); + tcase_add_test(qfloat_public_tcase, qfloat_from_double_test); + tcase_add_test(qfloat_public_tcase, qfloat_destroy_test); + + return s; +} + +int main(void) +{ + int nf; + Suite *s; + SRunner *sr; + + s = qfloat_suite(); + sr = srunner_create(s); + + srunner_run_all(sr, CK_NORMAL); + nf = srunner_ntests_failed(sr); + srunner_free(sr); + + return (nf == 0) ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/check-qint.c b/check-qint.c new file mode 100644 index 0000000..f3b0316 --- /dev/null +++ b/check-qint.c @@ -0,0 +1,113 @@ +/* + * QInt unit-tests. + * + * Copyright (C) 2009 Red Hat Inc. + * + * Authors: + * Luiz Capitulino + * + * This work is licensed under the terms of the GNU LGPL, version 2.1 or later. + * See the COPYING.LIB file in the top-level directory. + */ +#include + +#include "qint.h" +#include "qemu-common.h" + +/* + * Public Interface test-cases + * + * (with some violations to access 'private' data) + */ + +START_TEST(qint_from_int_test) +{ + QInt *qi; + const int value = -42; + + qi = qint_from_int(value); + fail_unless(qi != NULL); + fail_unless(qi->value == value); + fail_unless(qi->base.refcnt == 1); + fail_unless(qobject_type(QOBJECT(qi)) == QTYPE_QINT); + + // destroy doesn't exit yet + qemu_free(qi); +} +END_TEST + +START_TEST(qint_destroy_test) +{ + QInt *qi = qint_from_int(0); + QDECREF(qi); +} +END_TEST + +START_TEST(qint_from_int64_test) +{ + QInt *qi; + const int64_t value = 0x1234567890abcdefLL; + + qi = qint_from_int(value); + fail_unless((int64_t) qi->value == value); + + QDECREF(qi); +} +END_TEST + +START_TEST(qint_get_int_test) +{ + QInt *qi; + const int value = 123456; + + qi = qint_from_int(value); + fail_unless(qint_get_int(qi) == value); + + QDECREF(qi); +} +END_TEST + +START_TEST(qobject_to_qint_test) +{ + QInt *qi; + + qi = qint_from_int(0); + fail_unless(qobject_to_qint(QOBJECT(qi)) == qi); + + QDECREF(qi); +} +END_TEST + +static Suite *qint_suite(void) +{ + Suite *s; + TCase *qint_public_tcase; + + s = suite_create("QInt test-suite"); + + qint_public_tcase = tcase_create("Public Interface"); + suite_add_tcase(s, qint_public_tcase); + tcase_add_test(qint_public_tcase, qint_from_int_test); + tcase_add_test(qint_public_tcase, qint_destroy_test); + tcase_add_test(qint_public_tcase, qint_from_int64_test); + tcase_add_test(qint_public_tcase, qint_get_int_test); + tcase_add_test(qint_public_tcase, qobject_to_qint_test); + + return s; +} + +int main(void) +{ + int nf; + Suite *s; + SRunner *sr; + + s = qint_suite(); + sr = srunner_create(s); + + srunner_run_all(sr, CK_NORMAL); + nf = srunner_ntests_failed(sr); + srunner_free(sr); + + return (nf == 0) ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/check-qjson.c b/check-qjson.c new file mode 100644 index 0000000..64fcdcb --- /dev/null +++ b/check-qjson.c @@ -0,0 +1,794 @@ +/* + * Copyright IBM, Corp. 2009 + * + * Authors: + * Anthony Liguori + * + * This work is licensed under the terms of the GNU LGPL, version 2.1 or later. + * See the COPYING.LIB file in the top-level directory. + * + */ +#include + +#include "qstring.h" +#include "qint.h" +#include "qdict.h" +#include "qlist.h" +#include "qfloat.h" +#include "qbool.h" +#include "qjson.h" + +#include "qemu-common.h" + +START_TEST(escaped_string) +{ + int i; + struct { + const char *encoded; + const char *decoded; + int skip; + } test_cases[] = { + { "\"\\b\"", "\b" }, + { "\"\\f\"", "\f" }, + { "\"\\n\"", "\n" }, + { "\"\\r\"", "\r" }, + { "\"\\t\"", "\t" }, + { "\"\\/\"", "\\/" }, + { "\"\\\\\"", "\\" }, + { "\"\\\"\"", "\"" }, + { "\"hello world \\\"embedded string\\\"\"", + "hello world \"embedded string\"" }, + { "\"hello world\\nwith new line\"", "hello world\nwith new line" }, + { "\"single byte utf-8 \\u0020\"", "single byte utf-8 ", .skip = 1 }, + { "\"double byte utf-8 \\u00A2\"", "double byte utf-8 \xc2\xa2" }, + { "\"triple byte utf-8 \\u20AC\"", "triple byte utf-8 \xe2\x82\xac" }, + {} + }; + + for (i = 0; test_cases[i].encoded; i++) { + QObject *obj; + QString *str; + + obj = qobject_from_json(test_cases[i].encoded); + + fail_unless(obj != NULL); + fail_unless(qobject_type(obj) == QTYPE_QSTRING); + + str = qobject_to_qstring(obj); + fail_unless(strcmp(qstring_get_str(str), test_cases[i].decoded) == 0, + "%s != %s\n", qstring_get_str(str), test_cases[i].decoded); + + if (test_cases[i].skip == 0) { + str = qobject_to_json(obj); + fail_unless(strcmp(qstring_get_str(str),test_cases[i].encoded) == 0, + "%s != %s\n", qstring_get_str(str), + test_cases[i].encoded); + + qobject_decref(obj); + } + + QDECREF(str); + } +} +END_TEST + +START_TEST(simple_string) +{ + int i; + struct { + const char *encoded; + const char *decoded; + } test_cases[] = { + { "\"hello world\"", "hello world" }, + { "\"the quick brown fox jumped over the fence\"", + "the quick brown fox jumped over the fence" }, + {} + }; + + for (i = 0; test_cases[i].encoded; i++) { + QObject *obj; + QString *str; + + obj = qobject_from_json(test_cases[i].encoded); + + fail_unless(obj != NULL); + fail_unless(qobject_type(obj) == QTYPE_QSTRING); + + str = qobject_to_qstring(obj); + fail_unless(strcmp(qstring_get_str(str), test_cases[i].decoded) == 0); + + str = qobject_to_json(obj); + fail_unless(strcmp(qstring_get_str(str), test_cases[i].encoded) == 0); + + qobject_decref(obj); + + QDECREF(str); + } +} +END_TEST + +START_TEST(single_quote_string) +{ + int i; + struct { + const char *encoded; + const char *decoded; + } test_cases[] = { + { "'hello world'", "hello world" }, + { "'the quick brown fox \\' jumped over the fence'", + "the quick brown fox ' jumped over the fence" }, + {} + }; + + for (i = 0; test_cases[i].encoded; i++) { + QObject *obj; + QString *str; + + obj = qobject_from_json(test_cases[i].encoded); + + fail_unless(obj != NULL); + fail_unless(qobject_type(obj) == QTYPE_QSTRING); + + str = qobject_to_qstring(obj); + fail_unless(strcmp(qstring_get_str(str), test_cases[i].decoded) == 0); + + QDECREF(str); + } +} +END_TEST + +START_TEST(vararg_string) +{ + int i; + struct { + const char *decoded; + } test_cases[] = { + { "hello world" }, + { "the quick brown fox jumped over the fence" }, + {} + }; + + for (i = 0; test_cases[i].decoded; i++) { + QObject *obj; + QString *str; + + obj = qobject_from_jsonf("%s", test_cases[i].decoded); + + fail_unless(obj != NULL); + fail_unless(qobject_type(obj) == QTYPE_QSTRING); + + str = qobject_to_qstring(obj); + fail_unless(strcmp(qstring_get_str(str), test_cases[i].decoded) == 0); + + QDECREF(str); + } +} +END_TEST + +START_TEST(simple_number) +{ + int i; + struct { + const char *encoded; + int64_t decoded; + int skip; + } test_cases[] = { + { "0", 0 }, + { "1234", 1234 }, + { "1", 1 }, + { "-32", -32 }, + { "-0", 0, .skip = 1 }, + { }, + }; + + for (i = 0; test_cases[i].encoded; i++) { + QObject *obj; + QInt *qint; + + obj = qobject_from_json(test_cases[i].encoded); + fail_unless(obj != NULL); + fail_unless(qobject_type(obj) == QTYPE_QINT); + + qint = qobject_to_qint(obj); + fail_unless(qint_get_int(qint) == test_cases[i].decoded); + if (test_cases[i].skip == 0) { + QString *str; + + str = qobject_to_json(obj); + fail_unless(strcmp(qstring_get_str(str), test_cases[i].encoded) == 0); + QDECREF(str); + } + + QDECREF(qint); + } +} +END_TEST + +START_TEST(float_number) +{ + int i; + struct { + const char *encoded; + double decoded; + int skip; + } test_cases[] = { + { "32.43", 32.43 }, + { "0.222", 0.222 }, + { "-32.12313", -32.12313 }, + { "-32.20e-10", -32.20e-10, .skip = 1 }, + { }, + }; + + for (i = 0; test_cases[i].encoded; i++) { + QObject *obj; + QFloat *qfloat; + + obj = qobject_from_json(test_cases[i].encoded); + fail_unless(obj != NULL); + fail_unless(qobject_type(obj) == QTYPE_QFLOAT); + + qfloat = qobject_to_qfloat(obj); + fail_unless(qfloat_get_double(qfloat) == test_cases[i].decoded); + + if (test_cases[i].skip == 0) { + QString *str; + + str = qobject_to_json(obj); + fail_unless(strcmp(qstring_get_str(str), test_cases[i].encoded) == 0); + QDECREF(str); + } + + QDECREF(qfloat); + } +} +END_TEST + +START_TEST(vararg_number) +{ + QObject *obj; + QInt *qint; + QFloat *qfloat; + int value = 0x2342; + int64_t value64 = 0x2342342343LL; + double valuef = 2.323423423; + + obj = qobject_from_jsonf("%d", value); + fail_unless(obj != NULL); + fail_unless(qobject_type(obj) == QTYPE_QINT); + + qint = qobject_to_qint(obj); + fail_unless(qint_get_int(qint) == value); + + QDECREF(qint); + + obj = qobject_from_jsonf("%" PRId64, value64); + fail_unless(obj != NULL); + fail_unless(qobject_type(obj) == QTYPE_QINT); + + qint = qobject_to_qint(obj); + fail_unless(qint_get_int(qint) == value64); + + QDECREF(qint); + + obj = qobject_from_jsonf("%f", valuef); + fail_unless(obj != NULL); + fail_unless(qobject_type(obj) == QTYPE_QFLOAT); + + qfloat = qobject_to_qfloat(obj); + fail_unless(qfloat_get_double(qfloat) == valuef); + + QDECREF(qfloat); +} +END_TEST + +START_TEST(keyword_literal) +{ + QObject *obj; + QBool *qbool; + QString *str; + + obj = qobject_from_json("true"); + fail_unless(obj != NULL); + fail_unless(qobject_type(obj) == QTYPE_QBOOL); + + qbool = qobject_to_qbool(obj); + fail_unless(qbool_get_int(qbool) != 0); + + str = qobject_to_json(obj); + fail_unless(strcmp(qstring_get_str(str), "true") == 0); + QDECREF(str); + + QDECREF(qbool); + + obj = qobject_from_json("false"); + fail_unless(obj != NULL); + fail_unless(qobject_type(obj) == QTYPE_QBOOL); + + qbool = qobject_to_qbool(obj); + fail_unless(qbool_get_int(qbool) == 0); + + str = qobject_to_json(obj); + fail_unless(strcmp(qstring_get_str(str), "false") == 0); + QDECREF(str); + + QDECREF(qbool); + + obj = qobject_from_jsonf("%i", false); + fail_unless(obj != NULL); + fail_unless(qobject_type(obj) == QTYPE_QBOOL); + + qbool = qobject_to_qbool(obj); + fail_unless(qbool_get_int(qbool) == 0); + + QDECREF(qbool); + + obj = qobject_from_jsonf("%i", true); + fail_unless(obj != NULL); + fail_unless(qobject_type(obj) == QTYPE_QBOOL); + + qbool = qobject_to_qbool(obj); + fail_unless(qbool_get_int(qbool) != 0); + + QDECREF(qbool); +} +END_TEST + +typedef struct LiteralQDictEntry LiteralQDictEntry; +typedef struct LiteralQObject LiteralQObject; + +struct LiteralQObject +{ + int type; + union { + int64_t qint; + const char *qstr; + LiteralQDictEntry *qdict; + LiteralQObject *qlist; + } value; +}; + +struct LiteralQDictEntry +{ + const char *key; + LiteralQObject value; +}; + +#define QLIT_QINT(val) (LiteralQObject){.type = QTYPE_QINT, .value.qint = (val)} +#define QLIT_QSTR(val) (LiteralQObject){.type = QTYPE_QSTRING, .value.qstr = (val)} +#define QLIT_QDICT(val) (LiteralQObject){.type = QTYPE_QDICT, .value.qdict = (val)} +#define QLIT_QLIST(val) (LiteralQObject){.type = QTYPE_QLIST, .value.qlist = (val)} + +typedef struct QListCompareHelper +{ + int index; + LiteralQObject *objs; + int result; +} QListCompareHelper; + +static int compare_litqobj_to_qobj(LiteralQObject *lhs, QObject *rhs); + +static void compare_helper(QObject *obj, void *opaque) +{ + QListCompareHelper *helper = opaque; + + if (helper->result == 0) { + return; + } + + if (helper->objs[helper->index].type == QTYPE_NONE) { + helper->result = 0; + return; + } + + helper->result = compare_litqobj_to_qobj(&helper->objs[helper->index++], obj); +} + +static int compare_litqobj_to_qobj(LiteralQObject *lhs, QObject *rhs) +{ + if (lhs->type != qobject_type(rhs)) { + return 0; + } + + switch (lhs->type) { + case QTYPE_QINT: + return lhs->value.qint == qint_get_int(qobject_to_qint(rhs)); + case QTYPE_QSTRING: + return (strcmp(lhs->value.qstr, qstring_get_str(qobject_to_qstring(rhs))) == 0); + case QTYPE_QDICT: { + int i; + + for (i = 0; lhs->value.qdict[i].key; i++) { + QObject *obj = qdict_get(qobject_to_qdict(rhs), lhs->value.qdict[i].key); + + if (!compare_litqobj_to_qobj(&lhs->value.qdict[i].value, obj)) { + return 0; + } + } + + return 1; + } + case QTYPE_QLIST: { + QListCompareHelper helper; + + helper.index = 0; + helper.objs = lhs->value.qlist; + helper.result = 1; + + qlist_iter(qobject_to_qlist(rhs), compare_helper, &helper); + + return helper.result; + } + default: + break; + } + + return 0; +} + +START_TEST(simple_dict) +{ + int i; + struct { + const char *encoded; + LiteralQObject decoded; + } test_cases[] = { + { + .encoded = "{\"foo\": 42, \"bar\": \"hello world\"}", + .decoded = QLIT_QDICT(((LiteralQDictEntry[]){ + { "foo", QLIT_QINT(42) }, + { "bar", QLIT_QSTR("hello world") }, + { } + })), + }, { + .encoded = "{}", + .decoded = QLIT_QDICT(((LiteralQDictEntry[]){ + { } + })), + }, { + .encoded = "{\"foo\": 43}", + .decoded = QLIT_QDICT(((LiteralQDictEntry[]){ + { "foo", QLIT_QINT(43) }, + { } + })), + }, + { } + }; + + for (i = 0; test_cases[i].encoded; i++) { + QObject *obj; + QString *str; + + obj = qobject_from_json(test_cases[i].encoded); + fail_unless(obj != NULL); + fail_unless(qobject_type(obj) == QTYPE_QDICT); + + fail_unless(compare_litqobj_to_qobj(&test_cases[i].decoded, obj) == 1); + + str = qobject_to_json(obj); + qobject_decref(obj); + + obj = qobject_from_json(qstring_get_str(str)); + fail_unless(obj != NULL); + fail_unless(qobject_type(obj) == QTYPE_QDICT); + + fail_unless(compare_litqobj_to_qobj(&test_cases[i].decoded, obj) == 1); + qobject_decref(obj); + QDECREF(str); + } +} +END_TEST + +START_TEST(simple_list) +{ + int i; + struct { + const char *encoded; + LiteralQObject decoded; + } test_cases[] = { + { + .encoded = "[43,42]", + .decoded = QLIT_QLIST(((LiteralQObject[]){ + QLIT_QINT(43), + QLIT_QINT(42), + { } + })), + }, + { + .encoded = "[43]", + .decoded = QLIT_QLIST(((LiteralQObject[]){ + QLIT_QINT(43), + { } + })), + }, + { + .encoded = "[]", + .decoded = QLIT_QLIST(((LiteralQObject[]){ + { } + })), + }, + { + .encoded = "[{}]", + .decoded = QLIT_QLIST(((LiteralQObject[]){ + QLIT_QDICT(((LiteralQDictEntry[]){ + {}, + })), + {}, + })), + }, + { } + }; + + for (i = 0; test_cases[i].encoded; i++) { + QObject *obj; + QString *str; + + obj = qobject_from_json(test_cases[i].encoded); + fail_unless(obj != NULL); + fail_unless(qobject_type(obj) == QTYPE_QLIST); + + fail_unless(compare_litqobj_to_qobj(&test_cases[i].decoded, obj) == 1); + + str = qobject_to_json(obj); + qobject_decref(obj); + + obj = qobject_from_json(qstring_get_str(str)); + fail_unless(obj != NULL); + fail_unless(qobject_type(obj) == QTYPE_QLIST); + + fail_unless(compare_litqobj_to_qobj(&test_cases[i].decoded, obj) == 1); + qobject_decref(obj); + QDECREF(str); + } +} +END_TEST + +START_TEST(simple_whitespace) +{ + int i; + struct { + const char *encoded; + LiteralQObject decoded; + } test_cases[] = { + { + .encoded = " [ 43 , 42 ]", + .decoded = QLIT_QLIST(((LiteralQObject[]){ + QLIT_QINT(43), + QLIT_QINT(42), + { } + })), + }, + { + .encoded = " [ 43 , { 'h' : 'b' }, [ ], 42 ]", + .decoded = QLIT_QLIST(((LiteralQObject[]){ + QLIT_QINT(43), + QLIT_QDICT(((LiteralQDictEntry[]){ + { "h", QLIT_QSTR("b") }, + { }})), + QLIT_QLIST(((LiteralQObject[]){ + { }})), + QLIT_QINT(42), + { } + })), + }, + { + .encoded = " [ 43 , { 'h' : 'b' , 'a' : 32 }, [ ], 42 ]", + .decoded = QLIT_QLIST(((LiteralQObject[]){ + QLIT_QINT(43), + QLIT_QDICT(((LiteralQDictEntry[]){ + { "h", QLIT_QSTR("b") }, + { "a", QLIT_QINT(32) }, + { }})), + QLIT_QLIST(((LiteralQObject[]){ + { }})), + QLIT_QINT(42), + { } + })), + }, + { } + }; + + for (i = 0; test_cases[i].encoded; i++) { + QObject *obj; + QString *str; + + obj = qobject_from_json(test_cases[i].encoded); + fail_unless(obj != NULL); + fail_unless(qobject_type(obj) == QTYPE_QLIST); + + fail_unless(compare_litqobj_to_qobj(&test_cases[i].decoded, obj) == 1); + + str = qobject_to_json(obj); + qobject_decref(obj); + + obj = qobject_from_json(qstring_get_str(str)); + fail_unless(obj != NULL); + fail_unless(qobject_type(obj) == QTYPE_QLIST); + + fail_unless(compare_litqobj_to_qobj(&test_cases[i].decoded, obj) == 1); + + qobject_decref(obj); + QDECREF(str); + } +} +END_TEST + +START_TEST(simple_varargs) +{ + QObject *embedded_obj; + QObject *obj; + LiteralQObject decoded = QLIT_QLIST(((LiteralQObject[]){ + QLIT_QINT(1), + QLIT_QINT(2), + QLIT_QLIST(((LiteralQObject[]){ + QLIT_QINT(32), + QLIT_QINT(42), + {}})), + {}})); + + embedded_obj = qobject_from_json("[32, 42]"); + fail_unless(embedded_obj != NULL); + + obj = qobject_from_jsonf("[%d, 2, %p]", 1, embedded_obj); + fail_unless(obj != NULL); + + fail_unless(compare_litqobj_to_qobj(&decoded, obj) == 1); + + qobject_decref(obj); +} +END_TEST + +START_TEST(empty_input) +{ + const char *empty = ""; + + QObject *obj = qobject_from_json(empty); + fail_unless(obj == NULL); +} +END_TEST + +START_TEST(unterminated_string) +{ + QObject *obj = qobject_from_json("\"abc"); + fail_unless(obj == NULL); +} +END_TEST + +START_TEST(unterminated_sq_string) +{ + QObject *obj = qobject_from_json("'abc"); + fail_unless(obj == NULL); +} +END_TEST + +START_TEST(unterminated_escape) +{ + QObject *obj = qobject_from_json("\"abc\\\""); + fail_unless(obj == NULL); +} +END_TEST + +START_TEST(unterminated_array) +{ + QObject *obj = qobject_from_json("[32"); + fail_unless(obj == NULL); +} +END_TEST + +START_TEST(unterminated_array_comma) +{ + QObject *obj = qobject_from_json("[32,"); + fail_unless(obj == NULL); +} +END_TEST + +START_TEST(invalid_array_comma) +{ + QObject *obj = qobject_from_json("[32,}"); + fail_unless(obj == NULL); +} +END_TEST + +START_TEST(unterminated_dict) +{ + QObject *obj = qobject_from_json("{'abc':32"); + fail_unless(obj == NULL); +} +END_TEST + +START_TEST(unterminated_dict_comma) +{ + QObject *obj = qobject_from_json("{'abc':32,"); + fail_unless(obj == NULL); +} +END_TEST + +#if 0 +START_TEST(invalid_dict_comma) +{ + QObject *obj = qobject_from_json("{'abc':32,}"); + fail_unless(obj == NULL); +} +END_TEST + +START_TEST(unterminated_literal) +{ + QObject *obj = qobject_from_json("nul"); + fail_unless(obj == NULL); +} +END_TEST +#endif + +static Suite *qjson_suite(void) +{ + Suite *suite; + TCase *string_literals, *number_literals, *keyword_literals; + TCase *dicts, *lists, *whitespace, *varargs, *errors; + + string_literals = tcase_create("String Literals"); + tcase_add_test(string_literals, simple_string); + tcase_add_test(string_literals, escaped_string); + tcase_add_test(string_literals, single_quote_string); + tcase_add_test(string_literals, vararg_string); + + number_literals = tcase_create("Number Literals"); + tcase_add_test(number_literals, simple_number); + tcase_add_test(number_literals, float_number); + tcase_add_test(number_literals, vararg_number); + + keyword_literals = tcase_create("Keywords"); + tcase_add_test(keyword_literals, keyword_literal); + dicts = tcase_create("Objects"); + tcase_add_test(dicts, simple_dict); + lists = tcase_create("Lists"); + tcase_add_test(lists, simple_list); + + whitespace = tcase_create("Whitespace"); + tcase_add_test(whitespace, simple_whitespace); + + varargs = tcase_create("Varargs"); + tcase_add_test(varargs, simple_varargs); + + errors = tcase_create("Invalid JSON"); + tcase_add_test(errors, empty_input); + tcase_add_test(errors, unterminated_string); + tcase_add_test(errors, unterminated_escape); + tcase_add_test(errors, unterminated_sq_string); + tcase_add_test(errors, unterminated_array); + tcase_add_test(errors, unterminated_array_comma); + tcase_add_test(errors, invalid_array_comma); + tcase_add_test(errors, unterminated_dict); + tcase_add_test(errors, unterminated_dict_comma); +#if 0 + /* FIXME: this print parse error messages on stderr. */ + tcase_add_test(errors, invalid_dict_comma); + tcase_add_test(errors, unterminated_literal); +#endif + + suite = suite_create("QJSON test-suite"); + suite_add_tcase(suite, string_literals); + suite_add_tcase(suite, number_literals); + suite_add_tcase(suite, keyword_literals); + suite_add_tcase(suite, dicts); + suite_add_tcase(suite, lists); + suite_add_tcase(suite, whitespace); + suite_add_tcase(suite, varargs); + suite_add_tcase(suite, errors); + + return suite; +} + +int main(void) +{ + int nf; + Suite *s; + SRunner *sr; + + s = qjson_suite(); + sr = srunner_create(s); + + srunner_run_all(sr, CK_NORMAL); + nf = srunner_ntests_failed(sr); + srunner_free(sr); + + return (nf == 0) ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/check-qlist.c b/check-qlist.c new file mode 100644 index 0000000..58984cb --- /dev/null +++ b/check-qlist.c @@ -0,0 +1,153 @@ +/* + * QList unit-tests. + * + * Copyright (C) 2009 Red Hat Inc. + * + * Authors: + * Luiz Capitulino + * + * This work is licensed under the terms of the GNU LGPL, version 2.1 or later. + * See the COPYING.LIB file in the top-level directory. + */ +#include + +#include "qint.h" +#include "qlist.h" + +/* + * Public Interface test-cases + * + * (with some violations to access 'private' data) + */ + +START_TEST(qlist_new_test) +{ + QList *qlist; + + qlist = qlist_new(); + fail_unless(qlist != NULL); + fail_unless(qlist->base.refcnt == 1); + fail_unless(qobject_type(QOBJECT(qlist)) == QTYPE_QLIST); + + // destroy doesn't exist yet + qemu_free(qlist); +} +END_TEST + +START_TEST(qlist_append_test) +{ + QInt *qi; + QList *qlist; + QListEntry *entry; + + qi = qint_from_int(42); + + qlist = qlist_new(); + qlist_append(qlist, qi); + + entry = QTAILQ_FIRST(&qlist->head); + fail_unless(entry != NULL); + fail_unless(entry->value == QOBJECT(qi)); + + // destroy doesn't exist yet + QDECREF(qi); + qemu_free(entry); + qemu_free(qlist); +} +END_TEST + +START_TEST(qobject_to_qlist_test) +{ + QList *qlist; + + qlist = qlist_new(); + + fail_unless(qobject_to_qlist(QOBJECT(qlist)) == qlist); + + // destroy doesn't exist yet + qemu_free(qlist); +} +END_TEST + +START_TEST(qlist_destroy_test) +{ + int i; + QList *qlist; + + qlist = qlist_new(); + + for (i = 0; i < 42; i++) + qlist_append(qlist, qint_from_int(i)); + + QDECREF(qlist); +} +END_TEST + +static int iter_called; +static const int iter_max = 42; + +static void iter_func(QObject *obj, void *opaque) +{ + QInt *qi; + + fail_unless(opaque == NULL); + + qi = qobject_to_qint(obj); + fail_unless(qi != NULL); + fail_unless((qint_get_int(qi) >= 0) && (qint_get_int(qi) <= iter_max)); + + iter_called++; +} + +START_TEST(qlist_iter_test) +{ + int i; + QList *qlist; + + qlist = qlist_new(); + + for (i = 0; i < iter_max; i++) + qlist_append(qlist, qint_from_int(i)); + + iter_called = 0; + qlist_iter(qlist, iter_func, NULL); + + fail_unless(iter_called == iter_max); + + QDECREF(qlist); +} +END_TEST + +static Suite *QList_suite(void) +{ + Suite *s; + TCase *qlist_public_tcase; + + s = suite_create("QList suite"); + + qlist_public_tcase = tcase_create("Public Interface"); + suite_add_tcase(s, qlist_public_tcase); + tcase_add_test(qlist_public_tcase, qlist_new_test); + tcase_add_test(qlist_public_tcase, qlist_append_test); + tcase_add_test(qlist_public_tcase, qobject_to_qlist_test); + tcase_add_test(qlist_public_tcase, qlist_destroy_test); + tcase_add_test(qlist_public_tcase, qlist_iter_test); + + return s; +} + +int main(void) +{ + int nf; + Suite *s; + SRunner *sr; + + s = QList_suite(); + sr = srunner_create(s); + + srunner_run_all(sr, CK_NORMAL); + nf = srunner_ntests_failed(sr); + srunner_free(sr); + + return (nf == 0) ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/check-qstring.c b/check-qstring.c new file mode 100644 index 0000000..c9bafc2 --- /dev/null +++ b/check-qstring.c @@ -0,0 +1,134 @@ +/* + * QString unit-tests. + * + * Copyright (C) 2009 Red Hat Inc. + * + * Authors: + * Luiz Capitulino + * + * This work is licensed under the terms of the GNU LGPL, version 2.1 or later. + * See the COPYING.LIB file in the top-level directory. + */ +#include + +#include "qstring.h" +#include "qemu-common.h" + +/* + * Public Interface test-cases + * + * (with some violations to access 'private' data) + */ + +START_TEST(qstring_from_str_test) +{ + QString *qstring; + const char *str = "QEMU"; + + qstring = qstring_from_str(str); + fail_unless(qstring != NULL); + fail_unless(qstring->base.refcnt == 1); + fail_unless(strcmp(str, qstring->string) == 0); + fail_unless(qobject_type(QOBJECT(qstring)) == QTYPE_QSTRING); + + // destroy doesn't exit yet + qemu_free(qstring->string); + qemu_free(qstring); +} +END_TEST + +START_TEST(qstring_destroy_test) +{ + QString *qstring = qstring_from_str("destroy test"); + QDECREF(qstring); +} +END_TEST + +START_TEST(qstring_get_str_test) +{ + QString *qstring; + const char *ret_str; + const char *str = "QEMU/KVM"; + + qstring = qstring_from_str(str); + ret_str = qstring_get_str(qstring); + fail_unless(strcmp(ret_str, str) == 0); + + QDECREF(qstring); +} +END_TEST + +START_TEST(qstring_append_chr_test) +{ + int i; + QString *qstring; + const char *str = "qstring append char unit-test"; + + qstring = qstring_new(); + + for (i = 0; str[i]; i++) + qstring_append_chr(qstring, str[i]); + + fail_unless(strcmp(str, qstring_get_str(qstring)) == 0); + QDECREF(qstring); +} +END_TEST + +START_TEST(qstring_from_substr_test) +{ + QString *qs; + + qs = qstring_from_substr("virtualization", 3, 9); + fail_unless(qs != NULL); + fail_unless(strcmp(qstring_get_str(qs), "tualiza") == 0); + + QDECREF(qs); +} +END_TEST + + +START_TEST(qobject_to_qstring_test) +{ + QString *qstring; + + qstring = qstring_from_str("foo"); + fail_unless(qobject_to_qstring(QOBJECT(qstring)) == qstring); + + QDECREF(qstring); +} +END_TEST + +static Suite *qstring_suite(void) +{ + Suite *s; + TCase *qstring_public_tcase; + + s = suite_create("QString test-suite"); + + qstring_public_tcase = tcase_create("Public Interface"); + suite_add_tcase(s, qstring_public_tcase); + tcase_add_test(qstring_public_tcase, qstring_from_str_test); + tcase_add_test(qstring_public_tcase, qstring_destroy_test); + tcase_add_test(qstring_public_tcase, qstring_get_str_test); + tcase_add_test(qstring_public_tcase, qstring_append_chr_test); + tcase_add_test(qstring_public_tcase, qstring_from_substr_test); + tcase_add_test(qstring_public_tcase, qobject_to_qstring_test); + + return s; +} + +int main(void) +{ + int nf; + Suite *s; + SRunner *sr; + + s = qstring_suite(); + sr = srunner_create(s); + + srunner_run_all(sr, CK_NORMAL); + nf = srunner_ntests_failed(sr); + srunner_free(sr); + + return (nf == 0) ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/cmd.c b/cmd.c new file mode 100644 index 0000000..db2c9c4 --- /dev/null +++ b/cmd.c @@ -0,0 +1,610 @@ +/* + * Copyright (c) 2003-2005 Silicon Graphics, 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. + * + * This program is distributed in the hope that it would 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 . + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "cmd.h" +#include "qemu-aio.h" + +#define _(x) x /* not gettext support yet */ + +/* from libxcmd/command.c */ + +cmdinfo_t *cmdtab; +int ncmds; + +static argsfunc_t args_func; +static checkfunc_t check_func; +static int ncmdline; +static char **cmdline; + +static int +compare(const void *a, const void *b) +{ + return strcmp(((const cmdinfo_t *)a)->name, + ((const cmdinfo_t *)b)->name); +} + +void +add_command( + const cmdinfo_t *ci) +{ + cmdtab = realloc((void *)cmdtab, ++ncmds * sizeof(*cmdtab)); + cmdtab[ncmds - 1] = *ci; + qsort(cmdtab, ncmds, sizeof(*cmdtab), compare); +} + +static int +check_command( + const cmdinfo_t *ci) +{ + if (check_func) + return check_func(ci); + return 1; +} + +void +add_check_command( + checkfunc_t cf) +{ + check_func = cf; +} + +int +command_usage( + const cmdinfo_t *ci) +{ + printf("%s %s -- %s\n", ci->name, ci->args, ci->oneline); + return 0; +} + +int +command( + const cmdinfo_t *ct, + int argc, + char **argv) +{ + char *cmd = argv[0]; + + if (!check_command(ct)) + return 0; + + if (argc-1 < ct->argmin || (ct->argmax != -1 && argc-1 > ct->argmax)) { + if (ct->argmax == -1) + fprintf(stderr, + _("bad argument count %d to %s, expected at least %d arguments\n"), + argc-1, cmd, ct->argmin); + else if (ct->argmin == ct->argmax) + fprintf(stderr, + _("bad argument count %d to %s, expected %d arguments\n"), + argc-1, cmd, ct->argmin); + else + fprintf(stderr, + _("bad argument count %d to %s, expected between %d and %d arguments\n"), + argc-1, cmd, ct->argmin, ct->argmax); + return 0; + } + optind = 0; + return ct->cfunc(argc, argv); +} + +const cmdinfo_t * +find_command( + const char *cmd) +{ + cmdinfo_t *ct; + + for (ct = cmdtab; ct < &cmdtab[ncmds]; ct++) { + if (strcmp(ct->name, cmd) == 0 || + (ct->altname && strcmp(ct->altname, cmd) == 0)) + return (const cmdinfo_t *)ct; + } + return NULL; +} + +void +add_user_command(char *optarg) +{ + ncmdline++; + cmdline = realloc(cmdline, sizeof(char*) * (ncmdline)); + if (!cmdline) { + perror("realloc"); + exit(1); + } + cmdline[ncmdline-1] = optarg; +} + +static int +args_command( + int index) +{ + if (args_func) + return args_func(index); + return 0; +} + +void +add_args_command( + argsfunc_t af) +{ + args_func = af; +} + +static void prep_fetchline(void *opaque) +{ + int *fetchable = opaque; + + qemu_aio_set_fd_handler(STDIN_FILENO, NULL, NULL, NULL, NULL, NULL); + *fetchable= 1; +} + +static char *get_prompt(void); + +void +command_loop(void) +{ + int c, i, j = 0, done = 0, fetchable = 0, prompted = 0; + char *input; + char **v; + const cmdinfo_t *ct; + + for (i = 0; !done && i < ncmdline; i++) { + input = strdup(cmdline[i]); + if (!input) { + fprintf(stderr, + _("cannot strdup command '%s': %s\n"), + cmdline[i], strerror(errno)); + exit(1); + } + v = breakline(input, &c); + if (c) { + ct = find_command(v[0]); + if (ct) { + if (ct->flags & CMD_FLAG_GLOBAL) + done = command(ct, c, v); + else { + j = 0; + while (!done && (j = args_command(j))) + done = command(ct, c, v); + } + } else + fprintf(stderr, _("command \"%s\" not found\n"), + v[0]); + } + doneline(input, v); + } + if (cmdline) { + free(cmdline); + return; + } + + while (!done) { + if (!prompted) { + printf("%s", get_prompt()); + fflush(stdout); + qemu_aio_set_fd_handler(STDIN_FILENO, prep_fetchline, NULL, NULL, + NULL, &fetchable); + prompted = 1; + } + + qemu_aio_wait(); + + if (!fetchable) { + continue; + } + if ((input = fetchline()) == NULL) + break; + v = breakline(input, &c); + if (c) { + ct = find_command(v[0]); + if (ct) + done = command(ct, c, v); + else + fprintf(stderr, _("command \"%s\" not found\n"), + v[0]); + } + doneline(input, v); + + prompted = 0; + fetchable = 0; + } + qemu_aio_set_fd_handler(STDIN_FILENO, NULL, NULL, NULL, NULL, NULL); +} + +/* from libxcmd/input.c */ + +#if defined(ENABLE_READLINE) +# include +# include +#elif defined(ENABLE_EDITLINE) +# include +#endif + +static char * +get_prompt(void) +{ + static char prompt[FILENAME_MAX + 2 /*"> "*/ + 1 /*"\0"*/ ]; + + if (!prompt[0]) + snprintf(prompt, sizeof(prompt), "%s> ", progname); + return prompt; +} + +#if defined(ENABLE_READLINE) +char * +fetchline(void) +{ + char *line; + + line = readline(get_prompt()); + if (line && *line) + add_history(line); + return line; +} +#elif defined(ENABLE_EDITLINE) +static char *el_get_prompt(EditLine *e) { return get_prompt(); } +char * +fetchline(void) +{ + static EditLine *el; + static History *hist; + HistEvent hevent; + char *line; + int count; + + if (!el) { + hist = history_init(); + history(hist, &hevent, H_SETSIZE, 100); + el = el_init(progname, stdin, stdout, stderr); + el_source(el, NULL); + el_set(el, EL_SIGNAL, 1); + el_set(el, EL_PROMPT, el_get_prompt); + el_set(el, EL_HIST, history, (const char *)hist); + } + line = strdup(el_gets(el, &count)); + if (line) { + if (count > 0) + line[count-1] = '\0'; + if (*line) + history(hist, &hevent, H_ENTER, line); + } + return line; +} +#else +# define MAXREADLINESZ 1024 +char * +fetchline(void) +{ + char *p, *line = malloc(MAXREADLINESZ); + + if (!line) + return NULL; + if (!fgets(line, MAXREADLINESZ, stdin)) { + free(line); + return NULL; + } + p = line + strlen(line); + if (p != line && p[-1] == '\n') + p[-1] = '\0'; + return line; +} +#endif + +static char *qemu_strsep(char **input, const char *delim) +{ + char *result = *input; + if (result != NULL) { + char *p; + + for (p = result; *p != '\0'; p++) { + if (strchr(delim, *p)) { + break; + } + } + if (*p == '\0') { + *input = NULL; + } else { + *p = '\0'; + *input = p + 1; + } + } + return result; +} + +char ** +breakline( + char *input, + int *count) +{ + int c = 0; + char *p; + char **rval = calloc(sizeof(char *), 1); + + while (rval && (p = qemu_strsep(&input, " ")) != NULL) { + if (!*p) + continue; + c++; + rval = realloc(rval, sizeof(*rval) * (c + 1)); + if (!rval) { + c = 0; + break; + } + rval[c - 1] = p; + rval[c] = NULL; + } + *count = c; + return rval; +} + +void +doneline( + char *input, + char **vec) +{ + free(input); + free(vec); +} + +#define EXABYTES(x) ((long long)(x) << 60) +#define PETABYTES(x) ((long long)(x) << 50) +#define TERABYTES(x) ((long long)(x) << 40) +#define GIGABYTES(x) ((long long)(x) << 30) +#define MEGABYTES(x) ((long long)(x) << 20) +#define KILOBYTES(x) ((long long)(x) << 10) + +long long +cvtnum( + char *s) +{ + long long i; + char *sp; + int c; + + i = strtoll(s, &sp, 0); + if (i == 0 && sp == s) + return -1LL; + if (*sp == '\0') + return i; + + if (sp[1] != '\0') + return -1LL; + + c = tolower(*sp); + switch (c) { + default: + return i; + case 'k': + return KILOBYTES(i); + case 'm': + return MEGABYTES(i); + case 'g': + return GIGABYTES(i); + case 't': + return TERABYTES(i); + case 'p': + return PETABYTES(i); + case 'e': + return EXABYTES(i); + } + return -1LL; +} + +#define TO_EXABYTES(x) ((x) / EXABYTES(1)) +#define TO_PETABYTES(x) ((x) / PETABYTES(1)) +#define TO_TERABYTES(x) ((x) / TERABYTES(1)) +#define TO_GIGABYTES(x) ((x) / GIGABYTES(1)) +#define TO_MEGABYTES(x) ((x) / MEGABYTES(1)) +#define TO_KILOBYTES(x) ((x) / KILOBYTES(1)) + +void +cvtstr( + double value, + char *str, + size_t size) +{ + const char *fmt; + int precise; + + precise = ((double)value * 1000 == (double)(int)value * 1000); + + if (value >= EXABYTES(1)) { + fmt = precise ? "%.f EiB" : "%.3f EiB"; + snprintf(str, size, fmt, TO_EXABYTES(value)); + } else if (value >= PETABYTES(1)) { + fmt = precise ? "%.f PiB" : "%.3f PiB"; + snprintf(str, size, fmt, TO_PETABYTES(value)); + } else if (value >= TERABYTES(1)) { + fmt = precise ? "%.f TiB" : "%.3f TiB"; + snprintf(str, size, fmt, TO_TERABYTES(value)); + } else if (value >= GIGABYTES(1)) { + fmt = precise ? "%.f GiB" : "%.3f GiB"; + snprintf(str, size, fmt, TO_GIGABYTES(value)); + } else if (value >= MEGABYTES(1)) { + fmt = precise ? "%.f MiB" : "%.3f MiB"; + snprintf(str, size, fmt, TO_MEGABYTES(value)); + } else if (value >= KILOBYTES(1)) { + fmt = precise ? "%.f KiB" : "%.3f KiB"; + snprintf(str, size, fmt, TO_KILOBYTES(value)); + } else { + snprintf(str, size, "%f bytes", value); + } +} + +struct timeval +tsub(struct timeval t1, struct timeval t2) +{ + t1.tv_usec -= t2.tv_usec; + if (t1.tv_usec < 0) { + t1.tv_usec += 1000000; + t1.tv_sec--; + } + t1.tv_sec -= t2.tv_sec; + return t1; +} + +double +tdiv(double value, struct timeval tv) +{ + return value / ((double)tv.tv_sec + ((double)tv.tv_usec / 1000000.0)); +} + +#define HOURS(sec) ((sec) / (60 * 60)) +#define MINUTES(sec) (((sec) % (60 * 60)) / 60) +#define SECONDS(sec) ((sec) % 60) + +void +timestr( + struct timeval *tv, + char *ts, + size_t size, + int format) +{ + double usec = (double)tv->tv_usec / 1000000.0; + + if (format & TERSE_FIXED_TIME) { + if (!HOURS(tv->tv_sec)) { + snprintf(ts, size, "%u:%02u.%02u", + (unsigned int) MINUTES(tv->tv_sec), + (unsigned int) SECONDS(tv->tv_sec), + (unsigned int) usec * 100); + return; + } + format |= VERBOSE_FIXED_TIME; /* fallback if hours needed */ + } + + if ((format & VERBOSE_FIXED_TIME) || tv->tv_sec) { + snprintf(ts, size, "%u:%02u:%02u.%02u", + (unsigned int) HOURS(tv->tv_sec), + (unsigned int) MINUTES(tv->tv_sec), + (unsigned int) SECONDS(tv->tv_sec), + (unsigned int) usec * 100); + } else { + snprintf(ts, size, "0.%04u sec", (unsigned int) usec * 10000); + } +} + + +/* from libxcmd/quit.c */ + +static cmdinfo_t quit_cmd; + +/* ARGSUSED */ +static int +quit_f( + int argc, + char **argv) +{ + return 1; +} + +void +quit_init(void) +{ + quit_cmd.name = _("quit"); + quit_cmd.altname = _("q"); + quit_cmd.cfunc = quit_f; + quit_cmd.argmin = -1; + quit_cmd.argmax = -1; + quit_cmd.flags = CMD_FLAG_GLOBAL; + quit_cmd.oneline = _("exit the program"); + + add_command(&quit_cmd); +} + +/* from libxcmd/help.c */ + +static cmdinfo_t help_cmd; +static void help_onecmd(const char *cmd, const cmdinfo_t *ct); +static void help_oneline(const char *cmd, const cmdinfo_t *ct); + +static void +help_all(void) +{ + const cmdinfo_t *ct; + + for (ct = cmdtab; ct < &cmdtab[ncmds]; ct++) + help_oneline(ct->name, ct); + printf(_("\nUse 'help commandname' for extended help.\n")); +} + +static int +help_f( + int argc, + char **argv) +{ + const cmdinfo_t *ct; + + if (argc == 1) { + help_all(); + return 0; + } + ct = find_command(argv[1]); + if (ct == NULL) { + printf(_("command %s not found\n"), argv[1]); + return 0; + } + help_onecmd(argv[1], ct); + return 0; +} + +static void +help_onecmd( + const char *cmd, + const cmdinfo_t *ct) +{ + help_oneline(cmd, ct); + if (ct->help) + ct->help(); +} + +static void +help_oneline( + const char *cmd, + const cmdinfo_t *ct) +{ + if (cmd) + printf("%s ", cmd); + else { + printf("%s ", ct->name); + if (ct->altname) + printf("(or %s) ", ct->altname); + } + if (ct->args) + printf("%s ", ct->args); + printf("-- %s\n", ct->oneline); +} + +void +help_init(void) +{ + help_cmd.name = _("help"); + help_cmd.altname = _("?"); + help_cmd.cfunc = help_f; + help_cmd.argmin = 0; + help_cmd.argmax = 1; + help_cmd.flags = CMD_FLAG_GLOBAL; + help_cmd.args = _("[command]"); + help_cmd.oneline = _("help for one or all commands"); + + add_command(&help_cmd); +} diff --git a/cmd.h b/cmd.h new file mode 100644 index 0000000..b763b19 --- /dev/null +++ b/cmd.h @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2000-2005 Silicon Graphics, 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. + * + * This program is distributed in the hope that it would 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 . + */ +#ifndef __COMMAND_H__ +#define __COMMAND_H__ + +#define CMD_FLAG_GLOBAL ((int)0x80000000) /* don't iterate "args" */ + +typedef int (*cfunc_t)(int argc, char **argv); +typedef void (*helpfunc_t)(void); + +typedef struct cmdinfo { + const char *name; + const char *altname; + cfunc_t cfunc; + int argmin; + int argmax; + int canpush; + int flags; + const char *args; + const char *oneline; + helpfunc_t help; +} cmdinfo_t; + +extern cmdinfo_t *cmdtab; +extern int ncmds; + +void help_init(void); +void quit_init(void); + +typedef int (*argsfunc_t)(int index); +typedef int (*checkfunc_t)(const cmdinfo_t *ci); + +void add_command(const cmdinfo_t *ci); +void add_user_command(char *optarg); +void add_args_command(argsfunc_t af); +void add_check_command(checkfunc_t cf); + +const cmdinfo_t *find_command(const char *cmd); + +void command_loop(void); +int command_usage(const cmdinfo_t *ci); +int command(const cmdinfo_t *ci, int argc, char **argv); + +/* from input.h */ +char **breakline(char *input, int *count); +void doneline(char *input, char **vec); +char *fetchline(void); + +long long cvtnum(char *s); +void cvtstr(double value, char *str, size_t sz); + +struct timeval tsub(struct timeval t1, struct timeval t2); +double tdiv(double value, struct timeval tv); + +enum { + DEFAULT_TIME = 0x0, + TERSE_FIXED_TIME = 0x1, + VERBOSE_FIXED_TIME = 0x2 +}; + +void timestr(struct timeval *tv, char *str, size_t sz, int flags); + +extern char *progname; + +#endif /* __COMMAND_H__ */ diff --git a/compatfd.c b/compatfd.c new file mode 100644 index 0000000..a7cebc4 --- /dev/null +++ b/compatfd.c @@ -0,0 +1,117 @@ +/* + * signalfd/eventfd compatibility + * + * Copyright IBM, Corp. 2008 + * + * Authors: + * Anthony Liguori + * + * 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 "compatfd.h" + +#include +#include + +struct sigfd_compat_info +{ + sigset_t mask; + int fd; +}; + +static void *sigwait_compat(void *opaque) +{ + struct sigfd_compat_info *info = opaque; + int err; + sigset_t all; + + sigfillset(&all); + sigprocmask(SIG_BLOCK, &all, NULL); + + do { + siginfo_t siginfo; + + err = sigwaitinfo(&info->mask, &siginfo); + if (err == -1 && errno == EINTR) { + err = 0; + continue; + } + + if (err > 0) { + char buffer[128]; + size_t offset = 0; + + memcpy(buffer, &err, sizeof(err)); + while (offset < sizeof(buffer)) { + ssize_t len; + + len = write(info->fd, buffer + offset, + sizeof(buffer) - offset); + if (len == -1 && errno == EINTR) + continue; + + if (len <= 0) { + err = -1; + break; + } + + offset += len; + } + } + } while (err >= 0); + + return NULL; +} + +static int qemu_signalfd_compat(const sigset_t *mask) +{ + pthread_attr_t attr; + pthread_t tid; + struct sigfd_compat_info *info; + int fds[2]; + + info = malloc(sizeof(*info)); + if (info == NULL) { + errno = ENOMEM; + return -1; + } + + if (pipe(fds) == -1) { + free(info); + return -1; + } + + qemu_set_cloexec(fds[0]); + qemu_set_cloexec(fds[1]); + + memcpy(&info->mask, mask, sizeof(*mask)); + info->fd = fds[1]; + + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + + pthread_create(&tid, &attr, sigwait_compat, info); + + pthread_attr_destroy(&attr); + + return fds[0]; +} + +int qemu_signalfd(const sigset_t *mask) +{ +#if defined(CONFIG_SIGNALFD) + int ret; + + ret = syscall(SYS_signalfd, -1, mask, _NSIG / 8); + if (ret != -1) { + qemu_set_cloexec(ret); + return ret; + } +#endif + + return qemu_signalfd_compat(mask); +} diff --git a/compatfd.h b/compatfd.h new file mode 100644 index 0000000..fc37915 --- /dev/null +++ b/compatfd.h @@ -0,0 +1,43 @@ +/* + * signalfd/eventfd compatibility + * + * Copyright IBM, Corp. 2008 + * + * Authors: + * Anthony Liguori + * + * This work is licensed under the terms of the GNU GPL, version 2. See + * the COPYING file in the top-level directory. + * + */ + +#ifndef QEMU_COMPATFD_H +#define QEMU_COMPATFD_H + +#include + +struct qemu_signalfd_siginfo { + uint32_t ssi_signo; /* Signal number */ + int32_t ssi_errno; /* Error number (unused) */ + int32_t ssi_code; /* Signal code */ + uint32_t ssi_pid; /* PID of sender */ + uint32_t ssi_uid; /* Real UID of sender */ + int32_t ssi_fd; /* File descriptor (SIGIO) */ + uint32_t ssi_tid; /* Kernel timer ID (POSIX timers) */ + uint32_t ssi_band; /* Band event (SIGIO) */ + uint32_t ssi_overrun; /* POSIX timer overrun count */ + uint32_t ssi_trapno; /* Trap number that caused signal */ + int32_t ssi_status; /* Exit status or signal (SIGCHLD) */ + int32_t ssi_int; /* Integer sent by sigqueue(2) */ + uint64_t ssi_ptr; /* Pointer sent by sigqueue(2) */ + uint64_t ssi_utime; /* User CPU time consumed (SIGCHLD) */ + uint64_t ssi_stime; /* System CPU time consumed (SIGCHLD) */ + uint64_t ssi_addr; /* Address that generated signal + (for hardware-generated signals) */ + uint8_t pad[48]; /* Pad size to 128 bytes (allow for + additional fields in the future) */ +}; + +int qemu_signalfd(const sigset_t *mask); + +#endif diff --git a/config.h b/config.h new file mode 100644 index 0000000..e20f786 --- /dev/null +++ b/config.h @@ -0,0 +1,2 @@ +#include "config-host.h" +#include "config-target.h" diff --git a/configure b/configure new file mode 100644 index 0000000..f2551b3 --- /dev/null +++ b/configure @@ -0,0 +1,3294 @@ +#!/bin/sh +# +# qemu configure script (c) 2003 Fabrice Bellard +# +# set temporary file name +if test ! -z "$TMPDIR" ; then + TMPDIR1="${TMPDIR}" +elif test ! -z "$TEMPDIR" ; then + TMPDIR1="${TEMPDIR}" +else + TMPDIR1="/tmp" +fi + +TMPC="${TMPDIR1}/qemu-conf-${RANDOM}-$$-${RANDOM}.c" +TMPO="${TMPDIR1}/qemu-conf-${RANDOM}-$$-${RANDOM}.o" +TMPE="${TMPDIR1}/qemu-conf-${RANDOM}-$$-${RANDOM}.exe" + +# NB: do not call "exit" in the trap handler; this is buggy with some shells; +# see <1285349658-3122-1-git-send-email-loic.minier@linaro.org> +trap "rm -f $TMPC $TMPO $TMPE" EXIT INT QUIT TERM +rm -f config.log + +compile_object() { + echo $cc $QEMU_CFLAGS -c -o $TMPO $TMPC >> config.log + $cc $QEMU_CFLAGS -c -o $TMPO $TMPC >> config.log 2>&1 +} + +compile_prog() { + local_cflags="$1" + local_ldflags="$2" + echo $cc $QEMU_CFLAGS $local_cflags -o $TMPE $TMPC $LDFLAGS $local_ldflags >> config.log + $cc $QEMU_CFLAGS $local_cflags -o $TMPE $TMPC $LDFLAGS $local_ldflags >> config.log 2>&1 +} + +# symbolically link $1 to $2. Portable version of "ln -sf". +symlink() { + rm -f $2 + ln -s $1 $2 +} + +# check whether a command is available to this shell (may be either an +# executable or a builtin) +has() { + type "$1" >/dev/null 2>&1 +} + +# search for an executable in PATH +path_of() { + local_command="$1" + local_ifs="$IFS" + local_dir="" + + # pathname has a dir component? + if [ "${local_command#*/}" != "$local_command" ]; then + if [ -x "$local_command" ] && [ ! -d "$local_command" ]; then + echo "$local_command" + return 0 + fi + fi + if [ -z "$local_command" ]; then + return 1 + fi + + IFS=: + for local_dir in $PATH; do + if [ -x "$local_dir/$local_command" ] && [ ! -d "$local_dir/$local_command" ]; then + echo "$local_dir/$local_command" + IFS="${local_ifs:-$(printf ' \t\n')}" + return 0 + fi + done + # not found + IFS="${local_ifs:-$(printf ' \t\n')}" + return 1 +} + +# default parameters +source_path=`dirname "$0"` +cpu="" +interp_prefix="/usr/gnemul/qemu-%M" +static="no" +sparc_cpu="" +cross_prefix="" +audio_drv_list="" +audio_card_list="ac97 es1370 sb16 hda" +audio_possible_cards="ac97 es1370 sb16 cs4231a adlib gus hda" +block_drv_whitelist="" +host_cc="gcc" +helper_cflags="" +libs_softmmu="" +libs_tools="" +audio_pt_int="" +audio_win_int="" +cc_i386=i386-pc-linux-gnu-gcc + +target_list="" + +# Default value for a variable defining feature "foo". +# * foo="no" feature will only be used if --enable-foo arg is given +# * foo="" feature will be searched for, and if found, will be used +# unless --disable-foo is given +# * foo="yes" this value will only be set by --enable-foo flag. +# feature will searched for, +# if not found, configure exits with error +# +# Always add --enable-foo and --disable-foo command line args. +# Distributions want to ensure that several features are compiled in, and it +# is impossible without a --enable-foo that exits if a feature is not found. + +bluez="" +brlapi="" +curl="" +curses="" +docs="" +fdt="" +kvm="" +kvm_para="" +nptl="" +sdl="" +sparse="no" +uuid="" +vde="" +vnc_tls="" +vnc_sasl="" +vnc_jpeg="" +vnc_png="" +vnc_thread="no" +xen="" +linux_aio="" +attr="" +vhost_net="" +xfs="" + +gprof="no" +debug_tcg="no" +debug_mon="no" +debug="no" +strip_opt="yes" +bigendian="no" +mingw32="no" +EXESUF="" +prefix="/usr/local" +mandir="\${prefix}/share/man" +datadir="\${prefix}/share/qemu" +docdir="\${prefix}/share/doc/qemu" +bindir="\${prefix}/bin" +sysconfdir="\${prefix}/etc" +confsuffix="/qemu" +slirp="yes" +fmod_lib="" +fmod_inc="" +oss_lib="" +bsd="no" +linux="no" +solaris="no" +profiler="no" +cocoa="no" +softmmu="yes" +linux_user="no" +darwin_user="no" +bsd_user="no" +guest_base="" +uname_release="" +io_thread="no" +mixemu="no" +kerneldir="" +aix="no" +blobs="yes" +pkgversion="" +check_utests="no" +user_pie="no" +zero_malloc="" +trace_backend="nop" +trace_file="trace" +spice="" +rbd="" + +# parse CC options first +for opt do + optarg=`expr "x$opt" : 'x[^=]*=\(.*\)'` + case "$opt" in + --cross-prefix=*) cross_prefix="$optarg" + ;; + --cc=*) CC="$optarg" + ;; + --source-path=*) source_path="$optarg" + ;; + --cpu=*) cpu="$optarg" + ;; + --extra-cflags=*) QEMU_CFLAGS="$optarg $QEMU_CFLAGS" + ;; + --extra-ldflags=*) LDFLAGS="$optarg $LDFLAGS" + ;; + --sparc_cpu=*) + sparc_cpu="$optarg" + case $sparc_cpu in + v7|v8|v8plus|v8plusa) + cpu="sparc" + ;; + v9) + cpu="sparc64" + ;; + *) + echo "undefined SPARC architecture. Exiting"; + exit 1 + ;; + esac + ;; + esac +done +# OS specific +# Using uname is really, really broken. Once we have the right set of checks +# we can eliminate it's usage altogether + +cc="${cross_prefix}${CC-gcc}" +ar="${cross_prefix}${AR-ar}" +objcopy="${cross_prefix}${OBJCOPY-objcopy}" +ld="${cross_prefix}${LD-ld}" +strip="${cross_prefix}${STRIP-strip}" +windres="${cross_prefix}${WINDRES-windres}" +pkg_config="${cross_prefix}${PKG_CONFIG-pkg-config}" +sdl_config="${cross_prefix}${SDL_CONFIG-sdl-config}" + +# default flags for all hosts +QEMU_CFLAGS="-fno-strict-aliasing $QEMU_CFLAGS" +CFLAGS="-g $CFLAGS" +QEMU_CFLAGS="-Wall -Wundef -Wendif-labels -Wwrite-strings -Wmissing-prototypes $QEMU_CFLAGS" +QEMU_CFLAGS="-Wstrict-prototypes -Wredundant-decls $QEMU_CFLAGS" +QEMU_CFLAGS="-D_GNU_SOURCE -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE $QEMU_CFLAGS" +QEMU_CFLAGS="-D_FORTIFY_SOURCE=2 $QEMU_CFLAGS" +QEMU_INCLUDES="-I. -I\$(SRC_PATH)" +LDFLAGS="-g $LDFLAGS" + +# make source path absolute +source_path=`cd "$source_path"; pwd` + +check_define() { +cat > $TMPC <" +echo " --disable-spice disable spice" +echo " --enable-spice enable spice" +echo " --enable-rbd enable building the rados block device (rbd)" +echo "" +echo "NOTE: The object files are built at the place where configure is launched" +exit 1 +fi + +# check that the C compiler works. +cat > $TMPC < $TMPC << EOF +int main(void) { return 0; } +EOF +for flag in $gcc_flags; do + if compile_prog "-Werror $QEMU_CFLAGS" "-Werror $flag" ; then + QEMU_CFLAGS="$QEMU_CFLAGS $flag" + fi +done + +# +# Solaris specific configure tool chain decisions +# +if test "$solaris" = "yes" ; then + if has $install; then + : + else + echo "Solaris install program not found. Use --install=/usr/ucb/install or" + echo "install fileutils from www.blastwave.org using pkg-get -i fileutils" + echo "to get ginstall which is used by default (which lives in /opt/csw/bin)" + exit 1 + fi + if test "`path_of $install`" = "/usr/sbin/install" ; then + echo "Error: Solaris /usr/sbin/install is not an appropriate install program." + echo "try ginstall from the GNU fileutils available from www.blastwave.org" + echo "using pkg-get -i fileutils, or use --install=/usr/ucb/install" + exit 1 + fi + if has ar; then + : + else + echo "Error: No path includes ar" + if test -f /usr/ccs/bin/ar ; then + echo "Add /usr/ccs/bin to your path and rerun configure" + fi + exit 1 + fi +fi + + +if test -z "$target_list" ; then +# these targets are portable + if [ "$softmmu" = "yes" ] ; then + target_list="\ +i386-softmmu \ +x86_64-softmmu \ +arm-softmmu \ +cris-softmmu \ +m68k-softmmu \ +microblaze-softmmu \ +mips-softmmu \ +mipsel-softmmu \ +mips64-softmmu \ +mips64el-softmmu \ +ppc-softmmu \ +ppcemb-softmmu \ +ppc64-softmmu \ +sh4-softmmu \ +sh4eb-softmmu \ +sparc-softmmu \ +sparc64-softmmu \ +" + fi +# the following are Linux specific + if [ "$linux_user" = "yes" ] ; then + target_list="${target_list}\ +i386-linux-user \ +x86_64-linux-user \ +alpha-linux-user \ +arm-linux-user \ +armeb-linux-user \ +cris-linux-user \ +m68k-linux-user \ +microblaze-linux-user \ +mips-linux-user \ +mipsel-linux-user \ +ppc-linux-user \ +ppc64-linux-user \ +ppc64abi32-linux-user \ +sh4-linux-user \ +sh4eb-linux-user \ +sparc-linux-user \ +sparc64-linux-user \ +sparc32plus-linux-user \ +" + fi +# the following are Darwin specific + if [ "$darwin_user" = "yes" ] ; then + target_list="$target_list i386-darwin-user ppc-darwin-user " + fi +# the following are BSD specific + if [ "$bsd_user" = "yes" ] ; then + target_list="${target_list}\ +i386-bsd-user \ +x86_64-bsd-user \ +sparc-bsd-user \ +sparc64-bsd-user \ +" + fi +else + target_list=`echo "$target_list" | sed -e 's/,/ /g'` +fi +if test -z "$target_list" ; then + echo "No targets enabled" + exit 1 +fi +# see if system emulation was really requested +case " $target_list " in + *"-softmmu "*) softmmu=yes + ;; + *) softmmu=no + ;; +esac + +feature_not_found() { + feature=$1 + + echo "ERROR" + echo "ERROR: User requested feature $feature" + echo "ERROR: configure was not able to find it" + echo "ERROR" + exit 1; +} + +if test -z "$cross_prefix" ; then + +# --- +# big/little endian test +cat > $TMPC << EOF +#include +int main(int argc, char ** argv){ + volatile uint32_t i=0x01234567; + return (*((uint8_t*)(&i))) == 0x67; +} +EOF + +if compile_prog "" "" ; then +$TMPE && bigendian="yes" +else +echo big/little test failed +fi + +else + +# if cross compiling, cannot launch a program, so make a static guess +case "$cpu" in + armv4b|hppa|m68k|mips|mips64|ppc|ppc64|s390|s390x|sparc|sparc64) + bigendian=yes + ;; +esac + +fi + +# host long bits test, actually a pointer size test +cat > $TMPC << EOF +int sizeof_pointer_is_8[sizeof(void *) == 8 ? 1 : -1]; +EOF +if compile_object; then +hostlongbits=64 +else +hostlongbits=32 +fi + + +########################################## +# NPTL probe + +if test "$nptl" != "no" ; then + cat > $TMPC < +#include +void foo() +{ +#if !defined(CLONE_SETTLS) || !defined(FUTEX_WAIT) +#error bork +#endif +} +EOF + + if compile_object ; then + nptl=yes + else + if test "$nptl" = "yes" ; then + feature_not_found "nptl" + fi + nptl=no + fi +fi + +########################################## +# zlib check + +cat > $TMPC << EOF +#include +int main(void) { zlibVersion(); return 0; } +EOF +if compile_prog "" "-lz" ; then + : +else + echo + echo "Error: zlib check failed" + echo "Make sure to have the zlib libs and headers installed." + echo + exit 1 +fi + +########################################## +# xen probe + +if test "$xen" != "no" ; then + xen_libs="-lxenstore -lxenctrl -lxenguest" + cat > $TMPC < +#include +int main(void) { xs_daemon_open(); xc_interface_open(); return 0; } +EOF + if compile_prog "" "$xen_libs" ; then + xen=yes + libs_softmmu="$xen_libs $libs_softmmu" + else + if test "$xen" = "yes" ; then + feature_not_found "xen" + fi + xen=no + fi +fi + +########################################## +# pkg-config probe + +if ! has $pkg_config; then + echo warning: proceeding without "$pkg_config" >&2 + pkg_config=/bin/false +fi + +########################################## +# Sparse probe +if test "$sparse" != "no" ; then + if has cgcc; then + sparse=yes + else + if test "$sparse" = "yes" ; then + feature_not_found "sparse" + fi + sparse=no + fi +fi + +########################################## +# SDL probe + +# Look for sdl configuration program (pkg-config or sdl-config). Try +# sdl-config even without cross prefix, and favour pkg-config over sdl-config. +if test "`basename $sdl_config`" != sdl-config && ! has ${sdl_config}; then + sdl_config=sdl-config +fi + +if $pkg_config sdl --modversion >/dev/null 2>&1; then + sdlconfig="$pkg_config sdl" + _sdlversion=`$sdlconfig --modversion 2>/dev/null | sed 's/[^0-9]//g'` +elif has ${sdl_config}; then + sdlconfig="$sdl_config" + _sdlversion=`$sdlconfig --version | sed 's/[^0-9]//g'` +else + if test "$sdl" = "yes" ; then + feature_not_found "sdl" + fi + sdl=no +fi +if test -n "$cross_prefix" && test "`basename $sdlconfig`" = sdl-config; then + echo warning: using "\"$sdlconfig\"" to detect cross-compiled sdl >&2 +fi + +sdl_too_old=no +if test "$sdl" != "no" ; then + cat > $TMPC << EOF +#include +#undef main /* We don't want SDL to override our main() */ +int main( void ) { return SDL_Init (SDL_INIT_VIDEO); } +EOF + sdl_cflags=`$sdlconfig --cflags 2> /dev/null` + if test "$static" = "yes" ; then + sdl_libs=`$sdlconfig --static-libs 2>/dev/null` + else + sdl_libs=`$sdlconfig --libs 2> /dev/null` + fi + if compile_prog "$sdl_cflags" "$sdl_libs" ; then + if test "$_sdlversion" -lt 121 ; then + sdl_too_old=yes + else + if test "$cocoa" = "no" ; then + sdl=yes + fi + fi + + # static link with sdl ? (note: sdl.pc's --static --libs is broken) + if test "$sdl" = "yes" -a "$static" = "yes" ; then + if test $? = 0 && echo $sdl_libs | grep -- -laa > /dev/null; then + sdl_libs="$sdl_libs `aalib-config --static-libs 2>/dev/null`" + sdl_cflags="$sdl_cflags `aalib-config --cflags 2>/dev/null`" + fi + if compile_prog "$sdl_cflags" "$sdl_libs" ; then + : + else + sdl=no + fi + fi # static link + else # sdl not found + if test "$sdl" = "yes" ; then + feature_not_found "sdl" + fi + sdl=no + fi # sdl compile test +fi + +if test "$sdl" = "yes" ; then + cat > $TMPC < +#if defined(SDL_VIDEO_DRIVER_X11) +#include +#else +#error No x11 support +#endif +int main(void) { return 0; } +EOF + if compile_prog "$sdl_cflags" "$sdl_libs" ; then + sdl_libs="$sdl_libs -lX11" + fi + if test "$mingw32" = "yes" ; then + sdl_libs="`echo $sdl_libs | sed s/-mwindows//g` -mconsole" + fi + libs_softmmu="$sdl_libs $libs_softmmu" +fi + +########################################## +# VNC TLS detection +if test "$vnc_tls" != "no" ; then + cat > $TMPC < +int main(void) { gnutls_session_t s; gnutls_init(&s, GNUTLS_SERVER); return 0; } +EOF + vnc_tls_cflags=`$pkg_config --cflags gnutls 2> /dev/null` + vnc_tls_libs=`$pkg_config --libs gnutls 2> /dev/null` + if compile_prog "$vnc_tls_cflags" "$vnc_tls_libs" ; then + vnc_tls=yes + libs_softmmu="$vnc_tls_libs $libs_softmmu" + else + if test "$vnc_tls" = "yes" ; then + feature_not_found "vnc-tls" + fi + vnc_tls=no + fi +fi + +########################################## +# VNC SASL detection +if test "$vnc_sasl" != "no" ; then + cat > $TMPC < +#include +int main(void) { sasl_server_init(NULL, "qemu"); return 0; } +EOF + # Assuming Cyrus-SASL installed in /usr prefix + vnc_sasl_cflags="" + vnc_sasl_libs="-lsasl2" + if compile_prog "$vnc_sasl_cflags" "$vnc_sasl_libs" ; then + vnc_sasl=yes + libs_softmmu="$vnc_sasl_libs $libs_softmmu" + else + if test "$vnc_sasl" = "yes" ; then + feature_not_found "vnc-sasl" + fi + vnc_sasl=no + fi +fi + +########################################## +# VNC JPEG detection +if test "$vnc_jpeg" != "no" ; then +cat > $TMPC < +#include +int main(void) { struct jpeg_compress_struct s; jpeg_create_compress(&s); return 0; } +EOF + vnc_jpeg_cflags="" + vnc_jpeg_libs="-ljpeg" + if compile_prog "$vnc_jpeg_cflags" "$vnc_jpeg_libs" ; then + vnc_jpeg=yes + libs_softmmu="$vnc_jpeg_libs $libs_softmmu" + else + if test "$vnc_jpeg" = "yes" ; then + feature_not_found "vnc-jpeg" + fi + vnc_jpeg=no + fi +fi + +########################################## +# VNC PNG detection +if test "$vnc_png" != "no" ; then +cat > $TMPC < +#include +#include +int main(void) { + png_structp png_ptr; + png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + return 0; +} +EOF + vnc_png_cflags="" + vnc_png_libs="-lpng" + if compile_prog "$vnc_png_cflags" "$vnc_png_libs" ; then + vnc_png=yes + libs_softmmu="$vnc_png_libs $libs_softmmu" + else + if test "$vnc_png" = "yes" ; then + feature_not_found "vnc-png" + fi + vnc_png=no + fi +fi + +########################################## +# fnmatch() probe, used for ACL routines +fnmatch="no" +cat > $TMPC << EOF +#include +int main(void) +{ + fnmatch("foo", "foo", 0); + return 0; +} +EOF +if compile_prog "" "" ; then + fnmatch="yes" +fi + +########################################## +# uuid_generate() probe, used for vdi block driver +if test "$uuid" != "no" ; then + uuid_libs="-luuid" + cat > $TMPC << EOF +#include +int main(void) +{ + uuid_t my_uuid; + uuid_generate(my_uuid); + return 0; +} +EOF + if compile_prog "" "$uuid_libs" ; then + uuid="yes" + libs_softmmu="$uuid_libs $libs_softmmu" + libs_tools="$uuid_libs $libs_tools" + else + if test "$uuid" = "yes" ; then + feature_not_found "uuid" + fi + uuid=no + fi +fi + +########################################## +# xfsctl() probe, used for raw-posix +if test "$xfs" != "no" ; then + cat > $TMPC << EOF +#include +int main(void) +{ + xfsctl(NULL, 0, 0, NULL); + return 0; +} +EOF + if compile_prog "" "" ; then + xfs="yes" + else + if test "$xfs" = "yes" ; then + feature_not_found "xfs" + fi + xfs=no + fi +fi + +########################################## +# vde libraries probe +if test "$vde" != "no" ; then + vde_libs="-lvdeplug" + cat > $TMPC << EOF +#include +int main(void) +{ + struct vde_open_args a = {0, 0, 0}; + vde_open("", "", &a); + return 0; +} +EOF + if compile_prog "" "$vde_libs" ; then + vde=yes + libs_softmmu="$vde_libs $libs_softmmu" + libs_tools="$vde_libs $libs_tools" + else + if test "$vde" = "yes" ; then + feature_not_found "vde" + fi + vde=no + fi +fi + +########################################## +# Sound support libraries probe + +audio_drv_probe() +{ + drv=$1 + hdr=$2 + lib=$3 + exp=$4 + cfl=$5 + cat > $TMPC << EOF +#include <$hdr> +int main(void) { $exp } +EOF + if compile_prog "$cfl" "$lib" ; then + : + else + echo + echo "Error: $drv check failed" + echo "Make sure to have the $drv libs and headers installed." + echo + exit 1 + fi +} + +audio_drv_list=`echo "$audio_drv_list" | sed -e 's/,/ /g'` +for drv in $audio_drv_list; do + case $drv in + alsa) + audio_drv_probe $drv alsa/asoundlib.h -lasound \ + "snd_pcm_t **handle; return snd_pcm_close(*handle);" + libs_softmmu="-lasound $libs_softmmu" + ;; + + fmod) + if test -z $fmod_lib || test -z $fmod_inc; then + echo + echo "Error: You must specify path to FMOD library and headers" + echo "Example: --fmod-inc=/path/include/fmod --fmod-lib=/path/lib/libfmod-3.74.so" + echo + exit 1 + fi + audio_drv_probe $drv fmod.h $fmod_lib "return FSOUND_GetVersion();" "-I $fmod_inc" + libs_softmmu="$fmod_lib $libs_softmmu" + ;; + + esd) + audio_drv_probe $drv esd.h -lesd 'return esd_play_stream(0, 0, "", 0);' + libs_softmmu="-lesd $libs_softmmu" + audio_pt_int="yes" + ;; + + pa) + audio_drv_probe $drv pulse/simple.h "-lpulse-simple -lpulse" \ + "pa_simple *s = NULL; pa_simple_free(s); return 0;" + libs_softmmu="-lpulse -lpulse-simple $libs_softmmu" + audio_pt_int="yes" + ;; + + coreaudio) + libs_softmmu="-framework CoreAudio $libs_softmmu" + ;; + + dsound) + libs_softmmu="-lole32 -ldxguid $libs_softmmu" + audio_win_int="yes" + ;; + + oss) + libs_softmmu="$oss_lib $libs_softmmu" + ;; + + sdl|wav) + # XXX: Probes for CoreAudio, DirectSound, SDL(?) + ;; + + winwave) + libs_softmmu="-lwinmm $libs_softmmu" + audio_win_int="yes" + ;; + + *) + echo "$audio_possible_drivers" | grep -q "\<$drv\>" || { + echo + echo "Error: Unknown driver '$drv' selected" + echo "Possible drivers are: $audio_possible_drivers" + echo + exit 1 + } + ;; + esac +done + +########################################## +# BrlAPI probe + +if test "$brlapi" != "no" ; then + brlapi_libs="-lbrlapi" + cat > $TMPC << EOF +#include +#include +int main( void ) { return brlapi__openConnection (NULL, NULL, NULL); } +EOF + if compile_prog "" "$brlapi_libs" ; then + brlapi=yes + libs_softmmu="$brlapi_libs $libs_softmmu" + else + if test "$brlapi" = "yes" ; then + feature_not_found "brlapi" + fi + brlapi=no + fi +fi + +########################################## +# curses probe +curses_list="-lncurses -lcurses" + +if test "$curses" != "no" ; then + curses_found=no + cat > $TMPC << EOF +#include +#ifdef __OpenBSD__ +#define resize_term resizeterm +#endif +int main(void) { resize_term(0, 0); return curses_version(); } +EOF + for curses_lib in $curses_list; do + if compile_prog "" "$curses_lib" ; then + curses_found=yes + libs_softmmu="$curses_lib $libs_softmmu" + break + fi + done + if test "$curses_found" = "yes" ; then + curses=yes + else + if test "$curses" = "yes" ; then + feature_not_found "curses" + fi + curses=no + fi +fi + +########################################## +# curl probe + +if $pkg_config libcurl --modversion >/dev/null 2>&1; then + curlconfig="$pkg_config libcurl" +else + curlconfig=curl-config +fi + +if test "$curl" != "no" ; then + cat > $TMPC << EOF +#include +int main(void) { return curl_easy_init(); } +EOF + curl_cflags=`$curlconfig --cflags 2>/dev/null` + curl_libs=`$curlconfig --libs 2>/dev/null` + if compile_prog "$curl_cflags" "$curl_libs" ; then + curl=yes + libs_tools="$curl_libs $libs_tools" + libs_softmmu="$curl_libs $libs_softmmu" + else + if test "$curl" = "yes" ; then + feature_not_found "curl" + fi + curl=no + fi +fi # test "$curl" + +########################################## +# check framework probe + +if test "$check_utests" != "no" ; then + cat > $TMPC << EOF +#include +int main(void) { suite_create("qemu test"); return 0; } +EOF + check_libs=`$pkg_config --libs check` + if compile_prog "" $check_libs ; then + check_utests=yes + libs_tools="$check_libs $libs_tools" + else + if test "$check_utests" = "yes" ; then + feature_not_found "check" + fi + check_utests=no + fi +fi # test "$check_utests" + +########################################## +# bluez support probe +if test "$bluez" != "no" ; then + cat > $TMPC << EOF +#include +int main(void) { return bt_error(0); } +EOF + bluez_cflags=`$pkg_config --cflags bluez 2> /dev/null` + bluez_libs=`$pkg_config --libs bluez 2> /dev/null` + if compile_prog "$bluez_cflags" "$bluez_libs" ; then + bluez=yes + libs_softmmu="$bluez_libs $libs_softmmu" + else + if test "$bluez" = "yes" ; then + feature_not_found "bluez" + fi + bluez="no" + fi +fi + +########################################## +# kvm probe +if test "$kvm" != "no" ; then + cat > $TMPC < +#if !defined(KVM_API_VERSION) || KVM_API_VERSION < 12 || KVM_API_VERSION > 12 +#error Invalid KVM version +#endif +EOF + must_have_caps="KVM_CAP_USER_MEMORY \ + KVM_CAP_DESTROY_MEMORY_REGION_WORKS \ + KVM_CAP_COALESCED_MMIO \ + KVM_CAP_SYNC_MMU \ + " + if test \( "$cpu" = "i386" -o "$cpu" = "x86_64" \) ; then + must_have_caps="$caps \ + KVM_CAP_SET_TSS_ADDR \ + KVM_CAP_EXT_CPUID \ + KVM_CAP_CLOCKSOURCE \ + KVM_CAP_NOP_IO_DELAY \ + KVM_CAP_PV_MMU \ + KVM_CAP_MP_STATE \ + KVM_CAP_USER_NMI \ + " + fi + for c in $must_have_caps ; do + cat >> $TMPC <> $TMPC </dev/null` + fi + if compile_prog "$kvm_cflags" "" ; then + kvm=yes + cat > $TMPC < +int main(void) { return 0; } +EOF + if compile_prog "$kvm_cflags" "" ; then + kvm_para=yes + fi + else + if test "$kvm" = "yes" ; then + if has awk && has grep; then + kvmerr=`LANG=C $cc $QEMU_CFLAGS -o $TMPE $kvm_cflags $TMPC 2>&1 \ + | grep "error: " \ + | awk -F "error: " '{if (NR>1) printf(", "); printf("%s",$2);}'` + if test "$kvmerr" != "" ; then + echo -e "${kvmerr}\n\ +NOTE: To enable KVM support, update your kernel to 2.6.29+ or install \ +recent kvm-kmod from http://sourceforge.net/projects/kvm." + fi + fi + feature_not_found "kvm" + fi + kvm=no + fi +fi + +########################################## +# test for vhost net + +if test "$vhost_net" != "no"; then + if test "$kvm" != "no"; then + cat > $TMPC < + int main(void) { return 0; } +EOF + if compile_prog "$kvm_cflags" "" ; then + vhost_net=yes + else + if test "$vhost_net" = "yes" ; then + feature_not_found "vhost-net" + fi + vhost_net=no + fi + else + if test "$vhost_net" = "yes" ; then + echo "NOTE: vhost-net feature requires KVM (--enable-kvm)." + feature_not_found "vhost-net" + fi + vhost_net=no + fi +fi + +########################################## +# pthread probe +PTHREADLIBS_LIST="-lpthread -lpthreadGC2" + +pthread=no +cat > $TMPC << EOF +#include +int main(void) { pthread_create(0,0,0,0); return 0; } +EOF +if compile_prog "" "" ; then + pthread=yes +else + for pthread_lib in $PTHREADLIBS_LIST; do + if compile_prog "" "$pthread_lib" ; then + pthread=yes + LIBS="$pthread_lib $LIBS" + break + fi + done +fi + +if test "$mingw32" != yes -a "$pthread" = no; then + echo + echo "Error: pthread check failed" + echo "Make sure to have the pthread libs and headers installed." + echo + exit 1 +fi + +########################################## +# rbd probe +if test "$rbd" != "no" ; then + cat > $TMPC < +#include +int main(void) { rados_initialize(0, NULL); return 0; } +EOF + rbd_libs="-lrados" + if compile_prog "" "$rbd_libs" ; then + librados_too_old=no + cat > $TMPC < +#include +#ifndef CEPH_OSD_TMAP_SET +#error missing CEPH_OSD_TMAP_SET +#endif +int main(void) { + int (*func)(const rados_pool_t pool, uint64_t *snapid) = rados_selfmanaged_snap_create; + rados_initialize(0, NULL); + return 0; +} +EOF + if compile_prog "" "$rbd_libs" ; then + rbd=yes + libs_tools="$rbd_libs $libs_tools" + libs_softmmu="$rbd_libs $libs_softmmu" + else + rbd=no + librados_too_old=yes + fi + else + if test "$rbd" = "yes" ; then + feature_not_found "rados block device" + fi + rbd=no + fi + if test "$librados_too_old" = "yes" ; then + echo "-> Your librados version is too old - upgrade needed to have rbd support" + fi +fi + +########################################## +# linux-aio probe + +if test "$linux_aio" != "no" ; then + cat > $TMPC < +#include +#include +int main(void) { io_setup(0, NULL); io_set_eventfd(NULL, 0); eventfd(0, 0); return 0; } +EOF + if compile_prog "" "-laio" ; then + linux_aio=yes + libs_softmmu="$libs_softmmu -laio" + libs_tools="$libs_tools -laio" + else + if test "$linux_aio" = "yes" ; then + feature_not_found "linux AIO" + fi + linux_aio=no + fi +fi + +########################################## +# attr probe + +if test "$attr" != "no" ; then + cat > $TMPC < +#include +#include +int main(void) { getxattr(NULL, NULL, NULL, 0); setxattr(NULL, NULL, NULL, 0, 0); return 0; } +EOF + if compile_prog "" "-lattr" ; then + attr=yes + LIBS="-lattr $LIBS" + else + if test "$attr" = "yes" ; then + feature_not_found "ATTR" + fi + attr=no + fi +fi + +########################################## +# iovec probe +cat > $TMPC < +#include +#include +int main(void) { struct iovec iov; return 0; } +EOF +iovec=no +if compile_prog "" "" ; then + iovec=yes +fi + +########################################## +# preadv probe +cat > $TMPC < +#include +#include +int main(void) { preadv; } +EOF +preadv=no +if compile_prog "" "" ; then + preadv=yes +fi + +########################################## +# fdt probe +if test "$fdt" != "no" ; then + fdt_libs="-lfdt" + cat > $TMPC << EOF +int main(void) { return 0; } +EOF + if compile_prog "" "$fdt_libs" ; then + fdt=yes + libs_softmmu="$fdt_libs $libs_softmmu" + else + if test "$fdt" = "yes" ; then + feature_not_found "fdt" + fi + fdt=no + fi +fi + +# +# Check for xxxat() functions when we are building linux-user +# emulator. This is done because older glibc versions don't +# have syscall stubs for these implemented. +# +atfile=no +cat > $TMPC << EOF +#define _ATFILE_SOURCE +#include +#include +#include + +int +main(void) +{ + /* try to unlink nonexisting file */ + return (unlinkat(AT_FDCWD, "nonexistent_file", 0)); +} +EOF +if compile_prog "" "" ; then + atfile=yes +fi + +# Check for inotify functions when we are building linux-user +# emulator. This is done because older glibc versions don't +# have syscall stubs for these implemented. In that case we +# don't provide them even if kernel supports them. +# +inotify=no +cat > $TMPC << EOF +#include + +int +main(void) +{ + /* try to start inotify */ + return inotify_init(); +} +EOF +if compile_prog "" "" ; then + inotify=yes +fi + +inotify1=no +cat > $TMPC << EOF +#include + +int +main(void) +{ + /* try to start inotify */ + return inotify_init1(0); +} +EOF +if compile_prog "" "" ; then + inotify1=yes +fi + +# check if utimensat and futimens are supported +utimens=no +cat > $TMPC << EOF +#define _ATFILE_SOURCE +#include +#include + +int main(void) +{ + utimensat(AT_FDCWD, "foo", NULL, 0); + futimens(0, NULL); + return 0; +} +EOF +if compile_prog "" "" ; then + utimens=yes +fi + +# check if pipe2 is there +pipe2=no +cat > $TMPC << EOF +#include +#include + +int main(void) +{ + int pipefd[2]; + pipe2(pipefd, O_CLOEXEC); + return 0; +} +EOF +if compile_prog "" "" ; then + pipe2=yes +fi + +# check if accept4 is there +accept4=no +cat > $TMPC << EOF +#include +#include + +int main(void) +{ + accept4(0, NULL, NULL, SOCK_CLOEXEC); + return 0; +} +EOF +if compile_prog "" "" ; then + accept4=yes +fi + +# check if tee/splice is there. vmsplice was added same time. +splice=no +cat > $TMPC << EOF +#include +#include +#include + +int main(void) +{ + int len, fd; + len = tee(STDIN_FILENO, STDOUT_FILENO, INT_MAX, SPLICE_F_NONBLOCK); + splice(STDIN_FILENO, NULL, fd, NULL, len, SPLICE_F_MOVE); + return 0; +} +EOF +if compile_prog "" "" ; then + splice=yes +fi + +########################################## +# signalfd probe +signalfd="no" +cat > $TMPC << EOF +#define _GNU_SOURCE +#include +#include +#include +int main(void) { return syscall(SYS_signalfd, -1, NULL, _NSIG / 8); } +EOF + +if compile_prog "" "" ; then + signalfd=yes +fi + +# check if eventfd is supported +eventfd=no +cat > $TMPC << EOF +#include + +int main(void) +{ + int efd = eventfd(0, 0); + return 0; +} +EOF +if compile_prog "" "" ; then + eventfd=yes +fi + +# check for fallocate +fallocate=no +cat > $TMPC << EOF +#include + +int main(void) +{ + fallocate(0, 0, 0, 0); + return 0; +} +EOF +if compile_prog "$ARCH_CFLAGS" "" ; then + fallocate=yes +fi + +# check for sync_file_range +sync_file_range=no +cat > $TMPC << EOF +#include + +int main(void) +{ + sync_file_range(0, 0, 0, 0); + return 0; +} +EOF +if compile_prog "$ARCH_CFLAGS" "" ; then + sync_file_range=yes +fi + +# check for linux/fiemap.h and FS_IOC_FIEMAP +fiemap=no +cat > $TMPC << EOF +#include +#include +#include + +int main(void) +{ + ioctl(0, FS_IOC_FIEMAP, 0); + return 0; +} +EOF +if compile_prog "$ARCH_CFLAGS" "" ; then + fiemap=yes +fi + +# check for dup3 +dup3=no +cat > $TMPC << EOF +#include + +int main(void) +{ + dup3(0, 0, 0); + return 0; +} +EOF +if compile_prog "" "" ; then + dup3=yes +fi + +# Check if tools are available to build documentation. +if test "$docs" != "no" ; then + if has makeinfo && has pod2man; then + docs=yes + else + if test "$docs" = "yes" ; then + feature_not_found "docs" + fi + docs=no + fi +fi + +# Search for bswap_32 function +byteswap_h=no +cat > $TMPC << EOF +#include +int main(void) { return bswap_32(0); } +EOF +if compile_prog "" "" ; then + byteswap_h=yes +fi + +# Search for bswap_32 function +bswap_h=no +cat > $TMPC << EOF +#include +#include +#include +int main(void) { return bswap32(0); } +EOF +if compile_prog "" "" ; then + bswap_h=yes +fi + +########################################## +# Do we need librt +cat > $TMPC < +#include +int main(void) { clockid_t id; return clock_gettime(id, NULL); } +EOF + +if compile_prog "" "" ; then + : +elif compile_prog "" "-lrt" ; then + LIBS="-lrt $LIBS" +fi + +if test "$darwin" != "yes" -a "$mingw32" != "yes" -a "$solaris" != yes -a \ + "$aix" != "yes" -a "$haiku" != "yes" ; then + libs_softmmu="-lutil $libs_softmmu" +fi + +########################################## +# check if the compiler defines offsetof + +need_offsetof=yes +cat > $TMPC << EOF +#include +int main(void) { struct s { int f; }; return offsetof(struct s, f); } +EOF +if compile_prog "" "" ; then + need_offsetof=no +fi + +########################################## +# check if the compiler understands attribute warn_unused_result +# +# This could be smarter, but gcc -Werror does not error out even when warning +# about attribute warn_unused_result + +gcc_attribute_warn_unused_result=no +cat > $TMPC << EOF +#if defined(__GNUC__) && (__GNUC__ < 4) && defined(__GNUC_MINOR__) && (__GNUC__ < 4) +#error gcc 3.3 or older +#endif +int main(void) { return 0;} +EOF +if compile_prog "" ""; then + gcc_attribute_warn_unused_result=yes +fi + +# spice probe +if test "$spice" != "no" ; then + cat > $TMPC << EOF +#include +int main(void) { spice_server_new(); return 0; } +EOF + spice_cflags=$($pkg_config --cflags spice-protocol spice-server 2>/dev/null) + spice_libs=$($pkg_config --libs spice-protocol spice-server 2>/dev/null) + if $pkg_config --atleast-version=0.5.3 spice-server >/dev/null 2>&1 && \ + compile_prog "$spice_cflags" "$spice_libs" ; then + spice="yes" + libs_softmmu="$libs_softmmu $spice_libs" + QEMU_CFLAGS="$QEMU_CFLAGS $spice_cflags" + else + if test "$spice" = "yes" ; then + feature_not_found "spice" + fi + spice="no" + fi +fi + +########################################## + +########################################## +# check if we have fdatasync + +fdatasync=no +cat > $TMPC << EOF +#include +int main(void) { return fdatasync(0); } +EOF +if compile_prog "" "" ; then + fdatasync=yes +fi + +########################################## +# check if we have madvise + +madvise=no +cat > $TMPC << EOF +#include +#include +#include +int main(void) { return madvise(NULL, 0, MADV_DONTNEED); } +EOF +if compile_prog "" "" ; then + madvise=yes +fi + +########################################## +# check if we have posix_madvise + +posix_madvise=no +cat > $TMPC << EOF +#include +#include +int main(void) { return posix_madvise(NULL, 0, POSIX_MADV_DONTNEED); } +EOF +if compile_prog "" "" ; then + posix_madvise=yes +fi + +########################################## +# check if trace backend exists + +sh "$source_path/scripts/tracetool" "--$trace_backend" --check-backend > /dev/null 2> /dev/null +if test "$?" -ne 0 ; then + echo + echo "Error: invalid trace backend" + echo "Please choose a supported trace backend." + echo + exit 1 +fi + +########################################## +# For 'ust' backend, test if ust headers are present +if test "$trace_backend" = "ust"; then + cat > $TMPC << EOF +#include +#include +int main(void) { return 0; } +EOF + if compile_prog "" "" ; then + LIBS="-lust $LIBS" + else + echo + echo "Error: Trace backend 'ust' missing libust header files" + echo + exit 1 + fi +fi + +########################################## +# For 'dtrace' backend, test if 'dtrace' command is present +if test "$trace_backend" = "dtrace"; then + if ! has 'dtrace' ; then + echo + echo "Error: dtrace command is not found in PATH $PATH" + echo + exit 1 + fi + trace_backend_stap="no" + if has 'stap' ; then + trace_backend_stap="yes" + fi +fi + +########################################## +# End of CC checks +# After here, no more $cc or $ld runs + +if test "$debug" = "no" ; then + CFLAGS="-O2 $CFLAGS" +fi + +# Consult white-list to determine whether to enable werror +# by default. Only enable by default for git builds +z_version=`cut -f3 -d. $source_path/VERSION` + +if test -z "$werror" ; then + if test "$z_version" = "50" -a \ + "$linux" = "yes" ; then + werror="yes" + else + werror="no" + fi +fi + +# Disable zero malloc errors for official releases unless explicitly told to +# enable/disable +if test -z "$zero_malloc" ; then + if test "$z_version" = "50" ; then + zero_malloc="no" + else + zero_malloc="yes" + fi +fi + +if test "$werror" = "yes" ; then + QEMU_CFLAGS="-Werror $QEMU_CFLAGS" +fi + +if test "$solaris" = "no" ; then + if $ld --version 2>/dev/null | grep "GNU ld" >/dev/null 2>/dev/null ; then + LDFLAGS="-Wl,--warn-common $LDFLAGS" + fi +fi + +# Use ASLR, no-SEH and DEP if available +if test "$mingw32" = "yes" ; then + for flag in --dynamicbase --no-seh --nxcompat; do + if $ld --help 2>/dev/null | grep ".$flag" >/dev/null 2>/dev/null ; then + LDFLAGS="-Wl,$flag $LDFLAGS" + fi + done +fi + +confdir=$sysconfdir$confsuffix + +tools= +if test "$softmmu" = yes ; then + tools="qemu-img\$(EXESUF) qemu-io\$(EXESUF) $tools" + if [ "$linux" = "yes" -o "$bsd" = "yes" -o "$solaris" = "yes" ] ; then + tools="qemu-nbd\$(EXESUF) $tools" + if [ "$check_utests" = "yes" ]; then + tools="check-qint check-qstring check-qdict check-qlist $tools" + tools="check-qfloat check-qjson $tools" + fi + fi +fi + +# Mac OS X ships with a broken assembler +roms= +if test \( "$cpu" = "i386" -o "$cpu" = "x86_64" \) -a \ + "$targetos" != "Darwin" -a "$targetos" != "SunOS" -a \ + "$softmmu" = yes ; then + roms="optionrom" +fi + + +echo "Install prefix $prefix" +echo "BIOS directory `eval echo $datadir`" +echo "binary directory `eval echo $bindir`" +echo "config directory `eval echo $sysconfdir`" +if test "$mingw32" = "no" ; then +echo "Manual directory `eval echo $mandir`" +echo "ELF interp prefix $interp_prefix" +fi +echo "Source path $source_path" +echo "C compiler $cc" +echo "Host C compiler $host_cc" +echo "CFLAGS $CFLAGS" +echo "QEMU_CFLAGS $QEMU_CFLAGS" +echo "LDFLAGS $LDFLAGS" +echo "make $make" +echo "install $install" +echo "host CPU $cpu" +echo "host big endian $bigendian" +echo "target list $target_list" +echo "tcg debug enabled $debug_tcg" +echo "Mon debug enabled $debug_mon" +echo "gprof enabled $gprof" +echo "sparse enabled $sparse" +echo "strip binaries $strip_opt" +echo "profiler $profiler" +echo "static build $static" +echo "-Werror enabled $werror" +if test "$darwin" = "yes" ; then + echo "Cocoa support $cocoa" +fi +echo "SDL support $sdl" +echo "curses support $curses" +echo "curl support $curl" +echo "check support $check_utests" +echo "mingw32 support $mingw32" +echo "Audio drivers $audio_drv_list" +echo "Extra audio cards $audio_card_list" +echo "Block whitelist $block_drv_whitelist" +echo "Mixer emulation $mixemu" +echo "VNC TLS support $vnc_tls" +echo "VNC SASL support $vnc_sasl" +echo "VNC JPEG support $vnc_jpeg" +echo "VNC PNG support $vnc_png" +echo "VNC thread $vnc_thread" +if test -n "$sparc_cpu"; then + echo "Target Sparc Arch $sparc_cpu" +fi +echo "xen support $xen" +echo "brlapi support $brlapi" +echo "bluez support $bluez" +echo "Documentation $docs" +[ ! -z "$uname_release" ] && \ +echo "uname -r $uname_release" +echo "NPTL support $nptl" +echo "GUEST_BASE $guest_base" +echo "PIE user targets $user_pie" +echo "vde support $vde" +echo "IO thread $io_thread" +echo "Linux AIO support $linux_aio" +echo "ATTR/XATTR support $attr" +echo "Install blobs $blobs" +echo "KVM support $kvm" +echo "fdt support $fdt" +echo "preadv support $preadv" +echo "fdatasync $fdatasync" +echo "madvise $madvise" +echo "posix_madvise $posix_madvise" +echo "uuid support $uuid" +echo "vhost-net support $vhost_net" +echo "Trace backend $trace_backend" +echo "Trace output file $trace_file-" +echo "spice support $spice" +echo "rbd support $rbd" +echo "xfsctl support $xfs" + +if test $sdl_too_old = "yes"; then +echo "-> Your SDL version is too old - please upgrade to have SDL support" +fi + +config_host_mak="config-host.mak" +config_host_ld="config-host.ld" + +echo "# Automatically generated by configure - do not modify" > $config_host_mak +printf "# Configured with:" >> $config_host_mak +printf " '%s'" "$0" "$@" >> $config_host_mak +echo >> $config_host_mak + +echo all: >> $config_host_mak +echo "prefix=$prefix" >> $config_host_mak +echo "bindir=$bindir" >> $config_host_mak +echo "mandir=$mandir" >> $config_host_mak +echo "datadir=$datadir" >> $config_host_mak +echo "sysconfdir=$sysconfdir" >> $config_host_mak +echo "docdir=$docdir" >> $config_host_mak +echo "confdir=$confdir" >> $config_host_mak + +case "$cpu" in + i386|x86_64|alpha|cris|hppa|ia64|m68k|microblaze|mips|mips64|ppc|ppc64|s390|s390x|sparc|sparc64) + ARCH=$cpu + ;; + armv4b|armv4l) + ARCH=arm + ;; +esac +echo "ARCH=$ARCH" >> $config_host_mak +if test "$debug_tcg" = "yes" ; then + echo "CONFIG_DEBUG_TCG=y" >> $config_host_mak +fi +if test "$debug_mon" = "yes" ; then + echo "CONFIG_DEBUG_MONITOR=y" >> $config_host_mak +fi +if test "$debug" = "yes" ; then + echo "CONFIG_DEBUG_EXEC=y" >> $config_host_mak +fi +if test "$strip_opt" = "yes" ; then + echo "STRIP=${strip}" >> $config_host_mak +fi +if test "$bigendian" = "yes" ; then + echo "HOST_WORDS_BIGENDIAN=y" >> $config_host_mak +fi +echo "HOST_LONG_BITS=$hostlongbits" >> $config_host_mak +if test "$mingw32" = "yes" ; then + echo "CONFIG_WIN32=y" >> $config_host_mak + rc_version=`cat $source_path/VERSION` + version_major=${rc_version%%.*} + rc_version=${rc_version#*.} + version_minor=${rc_version%%.*} + rc_version=${rc_version#*.} + version_subminor=${rc_version%%.*} + version_micro=0 + echo "CONFIG_FILEVERSION=$version_major,$version_minor,$version_subminor,$version_micro" >> $config_host_mak + echo "CONFIG_PRODUCTVERSION=$version_major,$version_minor,$version_subminor,$version_micro" >> $config_host_mak +else + echo "CONFIG_POSIX=y" >> $config_host_mak +fi + +if test "$linux" = "yes" ; then + echo "CONFIG_LINUX=y" >> $config_host_mak +fi + +if test "$darwin" = "yes" ; then + echo "CONFIG_DARWIN=y" >> $config_host_mak +fi + +if test "$aix" = "yes" ; then + echo "CONFIG_AIX=y" >> $config_host_mak +fi + +if test "$solaris" = "yes" ; then + echo "CONFIG_SOLARIS=y" >> $config_host_mak + echo "CONFIG_SOLARIS_VERSION=$solarisrev" >> $config_host_mak + if test "$needs_libsunmath" = "yes" ; then + echo "CONFIG_NEEDS_LIBSUNMATH=y" >> $config_host_mak + fi +fi +if test "$haiku" = "yes" ; then + echo "CONFIG_HAIKU=y" >> $config_host_mak +fi +if test "$static" = "yes" ; then + echo "CONFIG_STATIC=y" >> $config_host_mak +fi +if test $profiler = "yes" ; then + echo "CONFIG_PROFILER=y" >> $config_host_mak +fi +if test "$slirp" = "yes" ; then + echo "CONFIG_SLIRP=y" >> $config_host_mak + QEMU_INCLUDES="-I\$(SRC_PATH)/slirp $QEMU_INCLUDES" +fi +if test "$vde" = "yes" ; then + echo "CONFIG_VDE=y" >> $config_host_mak +fi +for card in $audio_card_list; do + def=CONFIG_`echo $card | tr '[:lower:]' '[:upper:]'` + echo "$def=y" >> $config_host_mak +done +echo "CONFIG_AUDIO_DRIVERS=$audio_drv_list" >> $config_host_mak +for drv in $audio_drv_list; do + def=CONFIG_`echo $drv | tr '[:lower:]' '[:upper:]'` + echo "$def=y" >> $config_host_mak + if test "$drv" = "fmod"; then + echo "FMOD_CFLAGS=-I$fmod_inc" >> $config_host_mak + fi +done +if test "$audio_pt_int" = "yes" ; then + echo "CONFIG_AUDIO_PT_INT=y" >> $config_host_mak +fi +if test "$audio_win_int" = "yes" ; then + echo "CONFIG_AUDIO_WIN_INT=y" >> $config_host_mak +fi +echo "CONFIG_BDRV_WHITELIST=$block_drv_whitelist" >> $config_host_mak +if test "$mixemu" = "yes" ; then + echo "CONFIG_MIXEMU=y" >> $config_host_mak +fi +if test "$vnc_tls" = "yes" ; then + echo "CONFIG_VNC_TLS=y" >> $config_host_mak + echo "VNC_TLS_CFLAGS=$vnc_tls_cflags" >> $config_host_mak +fi +if test "$vnc_sasl" = "yes" ; then + echo "CONFIG_VNC_SASL=y" >> $config_host_mak + echo "VNC_SASL_CFLAGS=$vnc_sasl_cflags" >> $config_host_mak +fi +if test "$vnc_jpeg" != "no" ; then + echo "CONFIG_VNC_JPEG=y" >> $config_host_mak + echo "VNC_JPEG_CFLAGS=$vnc_jpeg_cflags" >> $config_host_mak +fi +if test "$vnc_png" != "no" ; then + echo "CONFIG_VNC_PNG=y" >> $config_host_mak + echo "VNC_PNG_CFLAGS=$vnc_png_cflags" >> $config_host_mak +fi +if test "$vnc_thread" != "no" ; then + echo "CONFIG_VNC_THREAD=y" >> $config_host_mak + echo "CONFIG_THREAD=y" >> $config_host_mak +fi +if test "$fnmatch" = "yes" ; then + echo "CONFIG_FNMATCH=y" >> $config_host_mak +fi +if test "$uuid" = "yes" ; then + echo "CONFIG_UUID=y" >> $config_host_mak +fi +if test "$xfs" = "yes" ; then + echo "CONFIG_XFS=y" >> $config_host_mak +fi +qemu_version=`head $source_path/VERSION` +echo "VERSION=$qemu_version" >>$config_host_mak +echo "PKGVERSION=$pkgversion" >>$config_host_mak +echo "SRC_PATH=$source_path" >> $config_host_mak +echo "TARGET_DIRS=$target_list" >> $config_host_mak +if [ "$docs" = "yes" ] ; then + echo "BUILD_DOCS=yes" >> $config_host_mak +fi +if test "$sdl" = "yes" ; then + echo "CONFIG_SDL=y" >> $config_host_mak + echo "SDL_CFLAGS=$sdl_cflags" >> $config_host_mak +fi +if test "$cocoa" = "yes" ; then + echo "CONFIG_COCOA=y" >> $config_host_mak +fi +if test "$curses" = "yes" ; then + echo "CONFIG_CURSES=y" >> $config_host_mak +fi +if test "$atfile" = "yes" ; then + echo "CONFIG_ATFILE=y" >> $config_host_mak +fi +if test "$utimens" = "yes" ; then + echo "CONFIG_UTIMENSAT=y" >> $config_host_mak +fi +if test "$pipe2" = "yes" ; then + echo "CONFIG_PIPE2=y" >> $config_host_mak +fi +if test "$accept4" = "yes" ; then + echo "CONFIG_ACCEPT4=y" >> $config_host_mak +fi +if test "$splice" = "yes" ; then + echo "CONFIG_SPLICE=y" >> $config_host_mak +fi +if test "$eventfd" = "yes" ; then + echo "CONFIG_EVENTFD=y" >> $config_host_mak +fi +if test "$fallocate" = "yes" ; then + echo "CONFIG_FALLOCATE=y" >> $config_host_mak +fi +if test "$sync_file_range" = "yes" ; then + echo "CONFIG_SYNC_FILE_RANGE=y" >> $config_host_mak +fi +if test "$fiemap" = "yes" ; then + echo "CONFIG_FIEMAP=y" >> $config_host_mak +fi +if test "$dup3" = "yes" ; then + echo "CONFIG_DUP3=y" >> $config_host_mak +fi +if test "$inotify" = "yes" ; then + echo "CONFIG_INOTIFY=y" >> $config_host_mak +fi +if test "$inotify1" = "yes" ; then + echo "CONFIG_INOTIFY1=y" >> $config_host_mak +fi +if test "$byteswap_h" = "yes" ; then + echo "CONFIG_BYTESWAP_H=y" >> $config_host_mak +fi +if test "$bswap_h" = "yes" ; then + echo "CONFIG_MACHINE_BSWAP_H=y" >> $config_host_mak +fi +if test "$curl" = "yes" ; then + echo "CONFIG_CURL=y" >> $config_host_mak + echo "CURL_CFLAGS=$curl_cflags" >> $config_host_mak +fi +if test "$brlapi" = "yes" ; then + echo "CONFIG_BRLAPI=y" >> $config_host_mak +fi +if test "$bluez" = "yes" ; then + echo "CONFIG_BLUEZ=y" >> $config_host_mak + echo "BLUEZ_CFLAGS=$bluez_cflags" >> $config_host_mak +fi +if test "$xen" = "yes" ; then + echo "CONFIG_XEN=y" >> $config_host_mak +fi +if test "$io_thread" = "yes" ; then + echo "CONFIG_IOTHREAD=y" >> $config_host_mak + echo "CONFIG_THREAD=y" >> $config_host_mak +fi +if test "$linux_aio" = "yes" ; then + echo "CONFIG_LINUX_AIO=y" >> $config_host_mak +fi +if test "$attr" = "yes" ; then + echo "CONFIG_ATTR=y" >> $config_host_mak +fi +if test "$linux" = "yes" ; then + if test "$attr" = "yes" ; then + echo "CONFIG_VIRTFS=y" >> $config_host_mak + fi +fi +if test "$blobs" = "yes" ; then + echo "INSTALL_BLOBS=yes" >> $config_host_mak +fi +if test "$iovec" = "yes" ; then + echo "CONFIG_IOVEC=y" >> $config_host_mak +fi +if test "$preadv" = "yes" ; then + echo "CONFIG_PREADV=y" >> $config_host_mak +fi +if test "$fdt" = "yes" ; then + echo "CONFIG_FDT=y" >> $config_host_mak +fi +if test "$signalfd" = "yes" ; then + echo "CONFIG_SIGNALFD=y" >> $config_host_mak +fi +if test "$need_offsetof" = "yes" ; then + echo "CONFIG_NEED_OFFSETOF=y" >> $config_host_mak +fi +if test "$gcc_attribute_warn_unused_result" = "yes" ; then + echo "CONFIG_GCC_ATTRIBUTE_WARN_UNUSED_RESULT=y" >> $config_host_mak +fi +if test "$fdatasync" = "yes" ; then + echo "CONFIG_FDATASYNC=y" >> $config_host_mak +fi +if test "$madvise" = "yes" ; then + echo "CONFIG_MADVISE=y" >> $config_host_mak +fi +if test "$posix_madvise" = "yes" ; then + echo "CONFIG_POSIX_MADVISE=y" >> $config_host_mak +fi + +if test "$spice" = "yes" ; then + echo "CONFIG_SPICE=y" >> $config_host_mak +fi + +# XXX: suppress that +if [ "$bsd" = "yes" ] ; then + echo "CONFIG_BSD=y" >> $config_host_mak +fi + +echo "CONFIG_UNAME_RELEASE=\"$uname_release\"" >> $config_host_mak + +if test "$zero_malloc" = "yes" ; then + echo "CONFIG_ZERO_MALLOC=y" >> $config_host_mak +fi +if test "$rbd" = "yes" ; then + echo "CONFIG_RBD=y" >> $config_host_mak +fi + +# USB host support +case "$usb" in +linux) + echo "HOST_USB=linux" >> $config_host_mak +;; +bsd) + echo "HOST_USB=bsd" >> $config_host_mak +;; +*) + echo "HOST_USB=stub" >> $config_host_mak +;; +esac + +echo "TRACE_BACKEND=$trace_backend" >> $config_host_mak +if test "$trace_backend" = "simple"; then + echo "CONFIG_SIMPLE_TRACE=y" >> $config_host_mak +fi +# Set the appropriate trace file. +if test "$trace_backend" = "simple"; then + trace_file="\"$trace_file-%u\"" +fi +if test "$trace_backend" = "dtrace" -a "$trace_backend_stap" = "yes" ; then + echo "CONFIG_SYSTEMTAP_TRACE=y" >> $config_host_mak +fi +echo "CONFIG_TRACE_FILE=$trace_file" >> $config_host_mak + +echo "TOOLS=$tools" >> $config_host_mak +echo "ROMS=$roms" >> $config_host_mak +echo "MAKE=$make" >> $config_host_mak +echo "INSTALL=$install" >> $config_host_mak +echo "INSTALL_DIR=$install -d -m0755 -p" >> $config_host_mak +echo "INSTALL_DATA=$install -m0644 -p" >> $config_host_mak +echo "INSTALL_PROG=$install -m0755 -p" >> $config_host_mak +echo "CC=$cc" >> $config_host_mak +echo "CC_I386=$cc_i386" >> $config_host_mak +echo "HOST_CC=$host_cc" >> $config_host_mak +echo "AR=$ar" >> $config_host_mak +echo "OBJCOPY=$objcopy" >> $config_host_mak +echo "LD=$ld" >> $config_host_mak +echo "WINDRES=$windres" >> $config_host_mak +echo "CFLAGS=$CFLAGS" >> $config_host_mak +echo "QEMU_CFLAGS=$QEMU_CFLAGS" >> $config_host_mak +echo "QEMU_INCLUDES=$QEMU_INCLUDES" >> $config_host_mak +if test "$sparse" = "yes" ; then + echo "CC := REAL_CC=\"\$(CC)\" cgcc" >> $config_host_mak + echo "HOST_CC := REAL_CC=\"\$(HOST_CC)\" cgcc" >> $config_host_mak + echo "QEMU_CFLAGS += -Wbitwise -Wno-transparent-union -Wno-old-initializer -Wno-non-pointer-null" >> $config_host_mak +fi +echo "HELPER_CFLAGS=$helper_cflags" >> $config_host_mak +echo "LDFLAGS=$LDFLAGS" >> $config_host_mak +echo "ARLIBS_BEGIN=$arlibs_begin" >> $config_host_mak +echo "ARLIBS_END=$arlibs_end" >> $config_host_mak +echo "LIBS+=$LIBS" >> $config_host_mak +echo "LIBS_TOOLS+=$libs_tools" >> $config_host_mak +echo "EXESUF=$EXESUF" >> $config_host_mak + +# generate list of library paths for linker script + +$ld --verbose -v 2> /dev/null | grep SEARCH_DIR > ${config_host_ld} + +if test -f ${config_host_ld}~ ; then + if cmp -s $config_host_ld ${config_host_ld}~ ; then + mv ${config_host_ld}~ $config_host_ld + else + rm ${config_host_ld}~ + fi +fi + +for d in libdis libdis-user; do + mkdir -p $d + symlink $source_path/Makefile.dis $d/Makefile + echo > $d/config.mak +done +if test "$static" = "no" -a "$user_pie" = "yes" ; then + echo "QEMU_CFLAGS+=-fpie" > libdis-user/config.mak +fi + +for target in $target_list; do +target_dir="$target" +config_target_mak=$target_dir/config-target.mak +target_arch2=`echo $target | cut -d '-' -f 1` +target_bigendian="no" + +case "$target_arch2" in + armeb|m68k|microblaze|mips|mipsn32|mips64|ppc|ppcemb|ppc64|ppc64abi32|s390x|sh4eb|sparc|sparc64|sparc32plus) + target_bigendian=yes + ;; +esac +target_softmmu="no" +target_user_only="no" +target_linux_user="no" +target_darwin_user="no" +target_bsd_user="no" +case "$target" in + ${target_arch2}-softmmu) + target_softmmu="yes" + ;; + ${target_arch2}-linux-user) + if test "$linux" != "yes" ; then + echo "ERROR: Target '$target' is only available on a Linux host" + exit 1 + fi + target_user_only="yes" + target_linux_user="yes" + ;; + ${target_arch2}-darwin-user) + if test "$darwin" != "yes" ; then + echo "ERROR: Target '$target' is only available on a Darwin host" + exit 1 + fi + target_user_only="yes" + target_darwin_user="yes" + ;; + ${target_arch2}-bsd-user) + if test "$bsd" != "yes" ; then + echo "ERROR: Target '$target' is only available on a BSD host" + exit 1 + fi + target_user_only="yes" + target_bsd_user="yes" + ;; + *) + echo "ERROR: Target '$target' not recognised" + exit 1 + ;; +esac + +mkdir -p $target_dir +mkdir -p $target_dir/fpu +mkdir -p $target_dir/tcg +mkdir -p $target_dir/ide +if test "$target" = "arm-linux-user" -o "$target" = "armeb-linux-user" -o "$target" = "arm-bsd-user" -o "$target" = "armeb-bsd-user" ; then + mkdir -p $target_dir/nwfpe +fi +symlink $source_path/Makefile.target $target_dir/Makefile + + +echo "# Automatically generated by configure - do not modify" > $config_target_mak + +bflt="no" +target_nptl="no" +interp_prefix1=`echo "$interp_prefix" | sed "s/%M/$target_arch2/g"` +echo "CONFIG_QEMU_INTERP_PREFIX=\"$interp_prefix1\"" >> $config_target_mak +gdb_xml_files="" + +TARGET_ARCH="$target_arch2" +TARGET_BASE_ARCH="" +TARGET_ABI_DIR="" + +case "$target_arch2" in + i386) + target_phys_bits=32 + ;; + x86_64) + TARGET_BASE_ARCH=i386 + target_phys_bits=64 + ;; + alpha) + target_phys_bits=64 + target_nptl="yes" + ;; + arm|armeb) + TARGET_ARCH=arm + bflt="yes" + target_nptl="yes" + gdb_xml_files="arm-core.xml arm-vfp.xml arm-vfp3.xml arm-neon.xml" + target_phys_bits=32 + ;; + cris) + target_nptl="yes" + target_phys_bits=32 + ;; + m68k) + bflt="yes" + gdb_xml_files="cf-core.xml cf-fp.xml" + target_phys_bits=32 + ;; + microblaze) + bflt="yes" + target_nptl="yes" + target_phys_bits=32 + ;; + mips|mipsel) + TARGET_ARCH=mips + echo "TARGET_ABI_MIPSO32=y" >> $config_target_mak + target_nptl="yes" + target_phys_bits=64 + ;; + mipsn32|mipsn32el) + TARGET_ARCH=mipsn32 + TARGET_BASE_ARCH=mips + echo "TARGET_ABI_MIPSN32=y" >> $config_target_mak + target_phys_bits=64 + ;; + mips64|mips64el) + TARGET_ARCH=mips64 + TARGET_BASE_ARCH=mips + echo "TARGET_ABI_MIPSN64=y" >> $config_target_mak + target_phys_bits=64 + ;; + ppc) + gdb_xml_files="power-core.xml power-fpu.xml power-altivec.xml power-spe.xml" + target_phys_bits=32 + target_nptl="yes" + ;; + ppcemb) + TARGET_BASE_ARCH=ppc + TARGET_ABI_DIR=ppc + gdb_xml_files="power-core.xml power-fpu.xml power-altivec.xml power-spe.xml" + target_phys_bits=64 + target_nptl="yes" + ;; + ppc64) + TARGET_BASE_ARCH=ppc + TARGET_ABI_DIR=ppc + gdb_xml_files="power64-core.xml power-fpu.xml power-altivec.xml power-spe.xml" + target_phys_bits=64 + ;; + ppc64abi32) + TARGET_ARCH=ppc64 + TARGET_BASE_ARCH=ppc + TARGET_ABI_DIR=ppc + echo "TARGET_ABI32=y" >> $config_target_mak + gdb_xml_files="power64-core.xml power-fpu.xml power-altivec.xml power-spe.xml" + target_phys_bits=64 + ;; + sh4|sh4eb) + TARGET_ARCH=sh4 + bflt="yes" + target_nptl="yes" + target_phys_bits=32 + ;; + sparc) + target_phys_bits=64 + ;; + sparc64) + TARGET_BASE_ARCH=sparc + target_phys_bits=64 + ;; + sparc32plus) + TARGET_ARCH=sparc64 + TARGET_BASE_ARCH=sparc + TARGET_ABI_DIR=sparc + echo "TARGET_ABI32=y" >> $config_target_mak + target_phys_bits=64 + ;; + s390x) + target_phys_bits=64 + ;; + *) + echo "Unsupported target CPU" + exit 1 + ;; +esac +echo "TARGET_ARCH=$TARGET_ARCH" >> $config_target_mak +target_arch_name="`echo $TARGET_ARCH | tr '[:lower:]' '[:upper:]'`" +echo "TARGET_$target_arch_name=y" >> $config_target_mak +echo "TARGET_ARCH2=$target_arch2" >> $config_target_mak +# TARGET_BASE_ARCH needs to be defined after TARGET_ARCH +if [ "$TARGET_BASE_ARCH" = "" ]; then + TARGET_BASE_ARCH=$TARGET_ARCH +fi +echo "TARGET_BASE_ARCH=$TARGET_BASE_ARCH" >> $config_target_mak +if [ "$TARGET_ABI_DIR" = "" ]; then + TARGET_ABI_DIR=$TARGET_ARCH +fi +echo "TARGET_ABI_DIR=$TARGET_ABI_DIR" >> $config_target_mak +case "$target_arch2" in + i386|x86_64) + if test "$xen" = "yes" -a "$target_softmmu" = "yes" ; then + echo "CONFIG_XEN=y" >> $config_target_mak + fi +esac +case "$target_arch2" in + i386|x86_64|ppcemb|ppc|ppc64|s390x) + # Make sure the target and host cpus are compatible + if test "$kvm" = "yes" -a "$target_softmmu" = "yes" -a \ + \( "$target_arch2" = "$cpu" -o \ + \( "$target_arch2" = "ppcemb" -a "$cpu" = "ppc" \) -o \ + \( "$target_arch2" = "ppc64" -a "$cpu" = "ppc" \) -o \ + \( "$target_arch2" = "x86_64" -a "$cpu" = "i386" \) -o \ + \( "$target_arch2" = "i386" -a "$cpu" = "x86_64" \) \) ; then + echo "CONFIG_KVM=y" >> $config_target_mak + echo "KVM_CFLAGS=$kvm_cflags" >> $config_target_mak + if test "$kvm_para" = "yes"; then + echo "CONFIG_KVM_PARA=y" >> $config_target_mak + fi + if test $vhost_net = "yes" ; then + echo "CONFIG_VHOST_NET=y" >> $config_target_mak + fi + fi +esac +if test "$target_bigendian" = "yes" ; then + echo "TARGET_WORDS_BIGENDIAN=y" >> $config_target_mak +fi +if test "$target_softmmu" = "yes" ; then + echo "TARGET_PHYS_ADDR_BITS=$target_phys_bits" >> $config_target_mak + echo "CONFIG_SOFTMMU=y" >> $config_target_mak + echo "LIBS+=$libs_softmmu" >> $config_target_mak + echo "HWDIR=../libhw$target_phys_bits" >> $config_target_mak + echo "subdir-$target: subdir-libhw$target_phys_bits" >> $config_host_mak +fi +if test "$target_user_only" = "yes" ; then + echo "CONFIG_USER_ONLY=y" >> $config_target_mak +fi +if test "$target_linux_user" = "yes" ; then + echo "CONFIG_LINUX_USER=y" >> $config_target_mak +fi +if test "$target_darwin_user" = "yes" ; then + echo "CONFIG_DARWIN_USER=y" >> $config_target_mak +fi +list="" +if test ! -z "$gdb_xml_files" ; then + for x in $gdb_xml_files; do + list="$list $source_path/gdb-xml/$x" + done + echo "TARGET_XML_FILES=$list" >> $config_target_mak +fi + +case "$target_arch2" in + i386|x86_64) + echo "CONFIG_NOSOFTFLOAT=y" >> $config_target_mak + ;; + *) + echo "CONFIG_SOFTFLOAT=y" >> $config_target_mak + ;; +esac + +if test "$target_user_only" = "yes" -a "$bflt" = "yes"; then + echo "TARGET_HAS_BFLT=y" >> $config_target_mak +fi +if test "$target_user_only" = "yes" \ + -a "$nptl" = "yes" -a "$target_nptl" = "yes"; then + echo "CONFIG_USE_NPTL=y" >> $config_target_mak +fi +if test "$target_user_only" = "yes" -a "$guest_base" = "yes"; then + echo "CONFIG_USE_GUEST_BASE=y" >> $config_target_mak +fi +if test "$target_bsd_user" = "yes" ; then + echo "CONFIG_BSD_USER=y" >> $config_target_mak +fi + +# generate QEMU_CFLAGS/LDFLAGS for targets + +cflags="" +includes="" +ldflags="" + +if test "$ARCH" = "sparc64" ; then + includes="-I\$(SRC_PATH)/tcg/sparc $includes" +elif test "$ARCH" = "s390x" ; then + includes="-I\$(SRC_PATH)/tcg/s390 $includes" +elif test "$ARCH" = "x86_64" ; then + includes="-I\$(SRC_PATH)/tcg/i386 $includes" +else + includes="-I\$(SRC_PATH)/tcg/\$(ARCH) $includes" +fi +includes="-I\$(SRC_PATH)/tcg $includes" +includes="-I\$(SRC_PATH)/fpu $includes" + +if test "$target_user_only" = "yes" ; then + libdis_config_mak=libdis-user/config.mak +else + libdis_config_mak=libdis/config.mak +fi + +for i in $ARCH $TARGET_BASE_ARCH ; do + case "$i" in + alpha) + echo "CONFIG_ALPHA_DIS=y" >> $config_target_mak + echo "CONFIG_ALPHA_DIS=y" >> $libdis_config_mak + ;; + arm) + echo "CONFIG_ARM_DIS=y" >> $config_target_mak + echo "CONFIG_ARM_DIS=y" >> $libdis_config_mak + ;; + cris) + echo "CONFIG_CRIS_DIS=y" >> $config_target_mak + echo "CONFIG_CRIS_DIS=y" >> $libdis_config_mak + ;; + hppa) + echo "CONFIG_HPPA_DIS=y" >> $config_target_mak + echo "CONFIG_HPPA_DIS=y" >> $libdis_config_mak + ;; + i386|x86_64) + echo "CONFIG_I386_DIS=y" >> $config_target_mak + echo "CONFIG_I386_DIS=y" >> $libdis_config_mak + ;; + ia64*) + echo "CONFIG_IA64_DIS=y" >> $config_target_mak + echo "CONFIG_IA64_DIS=y" >> $libdis_config_mak + ;; + m68k) + echo "CONFIG_M68K_DIS=y" >> $config_target_mak + echo "CONFIG_M68K_DIS=y" >> $libdis_config_mak + ;; + microblaze) + echo "CONFIG_MICROBLAZE_DIS=y" >> $config_target_mak + echo "CONFIG_MICROBLAZE_DIS=y" >> $libdis_config_mak + ;; + mips*) + echo "CONFIG_MIPS_DIS=y" >> $config_target_mak + echo "CONFIG_MIPS_DIS=y" >> $libdis_config_mak + ;; + ppc*) + echo "CONFIG_PPC_DIS=y" >> $config_target_mak + echo "CONFIG_PPC_DIS=y" >> $libdis_config_mak + ;; + s390*) + echo "CONFIG_S390_DIS=y" >> $config_target_mak + echo "CONFIG_S390_DIS=y" >> $libdis_config_mak + ;; + sh4) + echo "CONFIG_SH4_DIS=y" >> $config_target_mak + echo "CONFIG_SH4_DIS=y" >> $libdis_config_mak + ;; + sparc*) + echo "CONFIG_SPARC_DIS=y" >> $config_target_mak + echo "CONFIG_SPARC_DIS=y" >> $libdis_config_mak + ;; + esac +done + +case "$ARCH" in +alpha) + # Ensure there's only a single GP + cflags="-msmall-data $cflags" +;; +esac + +if test "$target_softmmu" = "yes" ; then + case "$TARGET_BASE_ARCH" in + arm) + cflags="-DHAS_AUDIO $cflags" + ;; + i386|mips|ppc) + cflags="-DHAS_AUDIO -DHAS_AUDIO_CHOICE $cflags" + ;; + esac +fi + +if test "$target_user_only" = "yes" -a "$static" = "no" -a \ + "$user_pie" = "yes" ; then + cflags="-fpie $cflags" + ldflags="-pie $ldflags" +fi + +if test "$target_softmmu" = "yes" -a \( \ + "$TARGET_ARCH" = "microblaze" -o \ + "$TARGET_ARCH" = "cris" \) ; then + echo "CONFIG_NEED_MMU=y" >> $config_target_mak +fi + +if test "$gprof" = "yes" ; then + echo "TARGET_GPROF=yes" >> $config_target_mak + if test "$target_linux_user" = "yes" ; then + cflags="-p $cflags" + ldflags="-p $ldflags" + fi + if test "$target_softmmu" = "yes" ; then + ldflags="-p $ldflags" + echo "GPROF_CFLAGS=-p" >> $config_target_mak + fi +fi + +linker_script="-Wl,-T../config-host.ld -Wl,-T,\$(SRC_PATH)/\$(ARCH).ld" +if test "$target_linux_user" = "yes" -o "$target_bsd_user" = "yes" ; then + case "$ARCH" in + sparc) + # -static is used to avoid g1/g3 usage by the dynamic linker + ldflags="$linker_script -static $ldflags" + ;; + alpha | s390x) + # The default placement of the application is fine. + ;; + *) + ldflags="$linker_script $ldflags" + ;; + esac +fi + +echo "LDFLAGS+=$ldflags" >> $config_target_mak +echo "QEMU_CFLAGS+=$cflags" >> $config_target_mak +echo "QEMU_INCLUDES+=$includes" >> $config_target_mak + +done # for target in $targets + +# build tree in object directory in case the source is not in the current directory +DIRS="tests tests/cris slirp audio block net pc-bios/optionrom" +DIRS="$DIRS roms/seabios roms/vgabios" +DIRS="$DIRS fsdev ui" +FILES="Makefile tests/Makefile" +FILES="$FILES tests/cris/Makefile tests/cris/.gdbinit" +FILES="$FILES pc-bios/optionrom/Makefile pc-bios/keymaps" +FILES="$FILES roms/seabios/Makefile roms/vgabios/Makefile" +for bios_file in $source_path/pc-bios/*.bin $source_path/pc-bios/*.dtb $source_path/pc-bios/openbios-*; do + FILES="$FILES pc-bios/`basename $bios_file`" +done +mkdir -p $DIRS +for f in $FILES ; do + test -e $f || symlink $source_path/$f $f +done + +# temporary config to build submodules +for rom in seabios vgabios ; do + config_mak=roms/$rom/config.mak + echo "# Automatically generated by configure - do not modify" > $config_mak + echo "SRC_PATH=$source_path/roms/$rom" >> $config_mak + echo "CC=$cc" >> $config_mak + echo "BCC=bcc" >> $config_mak + echo "CPP=${cross_prefix}cpp" >> $config_mak + echo "OBJCOPY=objcopy" >> $config_mak + echo "IASL=iasl" >> $config_mak + echo "LD=$ld" >> $config_mak +done + +for hwlib in 32 64; do + d=libhw$hwlib + mkdir -p $d + mkdir -p $d/ide + symlink $source_path/Makefile.hw $d/Makefile + echo "QEMU_CFLAGS+=-DTARGET_PHYS_ADDR_BITS=$hwlib" > $d/config.mak +done + +d=libuser +mkdir -p $d +symlink $source_path/Makefile.user $d/Makefile +if test "$static" = "no" -a "$user_pie" = "yes" ; then + echo "QEMU_CFLAGS+=-fpie" > $d/config.mak +fi + +if test "$docs" = "yes" ; then + mkdir -p QMP +fi diff --git a/console.c b/console.c new file mode 100644 index 0000000..57d6eb5 --- /dev/null +++ b/console.c @@ -0,0 +1,1707 @@ +/* + * QEMU graphical console + * + * 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 "qemu-common.h" +#include "console.h" +#include "qemu-timer.h" + +//#define DEBUG_CONSOLE +#define DEFAULT_BACKSCROLL 512 +#define MAX_CONSOLES 12 + +#define QEMU_RGBA(r, g, b, a) (((a) << 24) | ((r) << 16) | ((g) << 8) | (b)) +#define QEMU_RGB(r, g, b) QEMU_RGBA(r, g, b, 0xff) + +typedef struct TextAttributes { + uint8_t fgcol:4; + uint8_t bgcol:4; + uint8_t bold:1; + uint8_t uline:1; + uint8_t blink:1; + uint8_t invers:1; + uint8_t unvisible:1; +} TextAttributes; + +typedef struct TextCell { + uint8_t ch; + TextAttributes t_attrib; +} TextCell; + +#define MAX_ESC_PARAMS 3 + +enum TTYState { + TTY_STATE_NORM, + TTY_STATE_ESC, + TTY_STATE_CSI, +}; + +typedef struct QEMUFIFO { + uint8_t *buf; + int buf_size; + int count, wptr, rptr; +} QEMUFIFO; + +static int qemu_fifo_write(QEMUFIFO *f, const uint8_t *buf, int len1) +{ + int l, len; + + l = f->buf_size - f->count; + if (len1 > l) + len1 = l; + len = len1; + while (len > 0) { + l = f->buf_size - f->wptr; + if (l > len) + l = len; + memcpy(f->buf + f->wptr, buf, l); + f->wptr += l; + if (f->wptr >= f->buf_size) + f->wptr = 0; + buf += l; + len -= l; + } + f->count += len1; + return len1; +} + +static int qemu_fifo_read(QEMUFIFO *f, uint8_t *buf, int len1) +{ + int l, len; + + if (len1 > f->count) + len1 = f->count; + len = len1; + while (len > 0) { + l = f->buf_size - f->rptr; + if (l > len) + l = len; + memcpy(buf, f->buf + f->rptr, l); + f->rptr += l; + if (f->rptr >= f->buf_size) + f->rptr = 0; + buf += l; + len -= l; + } + f->count -= len1; + return len1; +} + +typedef enum { + GRAPHIC_CONSOLE, + TEXT_CONSOLE, + TEXT_CONSOLE_FIXED_SIZE +} console_type_t; + +/* ??? This is mis-named. + It is used for both text and graphical consoles. */ +struct TextConsole { + console_type_t console_type; + DisplayState *ds; + /* Graphic console state. */ + vga_hw_update_ptr hw_update; + vga_hw_invalidate_ptr hw_invalidate; + vga_hw_screen_dump_ptr hw_screen_dump; + vga_hw_text_update_ptr hw_text_update; + void *hw; + + int g_width, g_height; + int width; + int height; + int total_height; + int backscroll_height; + int x, y; + int x_saved, y_saved; + int y_displayed; + int y_base; + TextAttributes t_attrib_default; /* default text attributes */ + TextAttributes t_attrib; /* currently active text attributes */ + TextCell *cells; + int text_x[2], text_y[2], cursor_invalidate; + int echo; + + int update_x0; + int update_y0; + int update_x1; + int update_y1; + + enum TTYState state; + int esc_params[MAX_ESC_PARAMS]; + int nb_esc_params; + + CharDriverState *chr; + /* fifo for key pressed */ + QEMUFIFO out_fifo; + uint8_t out_fifo_buf[16]; + QEMUTimer *kbd_timer; +}; + +static DisplayState *display_state; +static TextConsole *active_console; +static TextConsole *consoles[MAX_CONSOLES]; +static int nb_consoles = 0; + +void vga_hw_update(void) +{ + if (active_console && active_console->hw_update) + active_console->hw_update(active_console->hw); +} + +void vga_hw_invalidate(void) +{ + if (active_console && active_console->hw_invalidate) + active_console->hw_invalidate(active_console->hw); +} + +void vga_hw_screen_dump(const char *filename) +{ + TextConsole *previous_active_console; + + previous_active_console = active_console; + active_console = consoles[0]; + /* There is currently no way of specifying which screen we want to dump, + so always dump the first one. */ + if (consoles[0]->hw_screen_dump) + consoles[0]->hw_screen_dump(consoles[0]->hw, filename); + active_console = previous_active_console; +} + +void vga_hw_text_update(console_ch_t *chardata) +{ + if (active_console && active_console->hw_text_update) + active_console->hw_text_update(active_console->hw, chardata); +} + +/* convert a RGBA color to a color index usable in graphic primitives */ +static unsigned int vga_get_color(DisplayState *ds, unsigned int rgba) +{ + unsigned int r, g, b, color; + + switch(ds_get_bits_per_pixel(ds)) { +#if 0 + case 8: + r = (rgba >> 16) & 0xff; + g = (rgba >> 8) & 0xff; + b = (rgba) & 0xff; + color = (rgb_to_index[r] * 6 * 6) + + (rgb_to_index[g] * 6) + + (rgb_to_index[b]); + break; +#endif + case 15: + r = (rgba >> 16) & 0xff; + g = (rgba >> 8) & 0xff; + b = (rgba) & 0xff; + color = ((r >> 3) << 10) | ((g >> 3) << 5) | (b >> 3); + break; + case 16: + r = (rgba >> 16) & 0xff; + g = (rgba >> 8) & 0xff; + b = (rgba) & 0xff; + color = ((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3); + break; + case 32: + default: + color = rgba; + break; + } + return color; +} + +static void vga_fill_rect (DisplayState *ds, + int posx, int posy, int width, int height, uint32_t color) +{ + uint8_t *d, *d1; + int x, y, bpp; + + bpp = (ds_get_bits_per_pixel(ds) + 7) >> 3; + d1 = ds_get_data(ds) + + ds_get_linesize(ds) * posy + bpp * posx; + for (y = 0; y < height; y++) { + d = d1; + switch(bpp) { + case 1: + for (x = 0; x < width; x++) { + *((uint8_t *)d) = color; + d++; + } + break; + case 2: + for (x = 0; x < width; x++) { + *((uint16_t *)d) = color; + d += 2; + } + break; + case 4: + for (x = 0; x < width; x++) { + *((uint32_t *)d) = color; + d += 4; + } + break; + } + d1 += ds_get_linesize(ds); + } +} + +/* copy from (xs, ys) to (xd, yd) a rectangle of size (w, h) */ +static void vga_bitblt(DisplayState *ds, int xs, int ys, int xd, int yd, int w, int h) +{ + const uint8_t *s; + uint8_t *d; + int wb, y, bpp; + + bpp = (ds_get_bits_per_pixel(ds) + 7) >> 3; + wb = w * bpp; + if (yd <= ys) { + s = ds_get_data(ds) + + ds_get_linesize(ds) * ys + bpp * xs; + d = ds_get_data(ds) + + ds_get_linesize(ds) * yd + bpp * xd; + for (y = 0; y < h; y++) { + memmove(d, s, wb); + d += ds_get_linesize(ds); + s += ds_get_linesize(ds); + } + } else { + s = ds_get_data(ds) + + ds_get_linesize(ds) * (ys + h - 1) + bpp * xs; + d = ds_get_data(ds) + + ds_get_linesize(ds) * (yd + h - 1) + bpp * xd; + for (y = 0; y < h; y++) { + memmove(d, s, wb); + d -= ds_get_linesize(ds); + s -= ds_get_linesize(ds); + } + } +} + +/***********************************************************/ +/* basic char display */ + +#define FONT_HEIGHT 16 +#define FONT_WIDTH 8 + +#include "vgafont.h" + +#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) x +#else +#define PAT(x) cbswap_32(x) +#endif + +static const uint32_t dmask16[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), +}; + +static const uint32_t dmask4[4] = { + PAT(0x00000000), + PAT(0x0000ffff), + PAT(0xffff0000), + PAT(0xffffffff), +}; + +static uint32_t color_table[2][8]; + +enum color_names { + COLOR_BLACK = 0, + COLOR_RED = 1, + COLOR_GREEN = 2, + COLOR_YELLOW = 3, + COLOR_BLUE = 4, + COLOR_MAGENTA = 5, + COLOR_CYAN = 6, + COLOR_WHITE = 7 +}; + +static const uint32_t color_table_rgb[2][8] = { + { /* dark */ + QEMU_RGB(0x00, 0x00, 0x00), /* black */ + QEMU_RGB(0xaa, 0x00, 0x00), /* red */ + QEMU_RGB(0x00, 0xaa, 0x00), /* green */ + QEMU_RGB(0xaa, 0xaa, 0x00), /* yellow */ + QEMU_RGB(0x00, 0x00, 0xaa), /* blue */ + QEMU_RGB(0xaa, 0x00, 0xaa), /* magenta */ + QEMU_RGB(0x00, 0xaa, 0xaa), /* cyan */ + QEMU_RGB(0xaa, 0xaa, 0xaa), /* white */ + }, + { /* bright */ + QEMU_RGB(0x00, 0x00, 0x00), /* black */ + QEMU_RGB(0xff, 0x00, 0x00), /* red */ + QEMU_RGB(0x00, 0xff, 0x00), /* green */ + QEMU_RGB(0xff, 0xff, 0x00), /* yellow */ + QEMU_RGB(0x00, 0x00, 0xff), /* blue */ + QEMU_RGB(0xff, 0x00, 0xff), /* magenta */ + QEMU_RGB(0x00, 0xff, 0xff), /* cyan */ + QEMU_RGB(0xff, 0xff, 0xff), /* white */ + } +}; + +static inline unsigned int col_expand(DisplayState *ds, unsigned int col) +{ + switch(ds_get_bits_per_pixel(ds)) { + case 8: + col |= col << 8; + col |= col << 16; + break; + case 15: + case 16: + col |= col << 16; + break; + default: + break; + } + + return col; +} +#ifdef DEBUG_CONSOLE +static void console_print_text_attributes(TextAttributes *t_attrib, char ch) +{ + if (t_attrib->bold) { + printf("b"); + } else { + printf(" "); + } + if (t_attrib->uline) { + printf("u"); + } else { + printf(" "); + } + if (t_attrib->blink) { + printf("l"); + } else { + printf(" "); + } + if (t_attrib->invers) { + printf("i"); + } else { + printf(" "); + } + if (t_attrib->unvisible) { + printf("n"); + } else { + printf(" "); + } + + printf(" fg: %d bg: %d ch:'%2X' '%c'\n", t_attrib->fgcol, t_attrib->bgcol, ch, ch); +} +#endif + +static void vga_putcharxy(DisplayState *ds, int x, int y, int ch, + TextAttributes *t_attrib) +{ + uint8_t *d; + const uint8_t *font_ptr; + unsigned int font_data, linesize, xorcol, bpp; + int i; + unsigned int fgcol, bgcol; + +#ifdef DEBUG_CONSOLE + printf("x: %2i y: %2i", x, y); + console_print_text_attributes(t_attrib, ch); +#endif + + if (t_attrib->invers) { + bgcol = color_table[t_attrib->bold][t_attrib->fgcol]; + fgcol = color_table[t_attrib->bold][t_attrib->bgcol]; + } else { + fgcol = color_table[t_attrib->bold][t_attrib->fgcol]; + bgcol = color_table[t_attrib->bold][t_attrib->bgcol]; + } + + bpp = (ds_get_bits_per_pixel(ds) + 7) >> 3; + d = ds_get_data(ds) + + ds_get_linesize(ds) * y * FONT_HEIGHT + bpp * x * FONT_WIDTH; + linesize = ds_get_linesize(ds); + font_ptr = vgafont16 + FONT_HEIGHT * ch; + xorcol = bgcol ^ fgcol; + switch(ds_get_bits_per_pixel(ds)) { + case 8: + for(i = 0; i < FONT_HEIGHT; i++) { + font_data = *font_ptr++; + if (t_attrib->uline + && ((i == FONT_HEIGHT - 2) || (i == FONT_HEIGHT - 3))) { + font_data = 0xFFFF; + } + ((uint32_t *)d)[0] = (dmask16[(font_data >> 4)] & xorcol) ^ bgcol; + ((uint32_t *)d)[1] = (dmask16[(font_data >> 0) & 0xf] & xorcol) ^ bgcol; + d += linesize; + } + break; + case 16: + case 15: + for(i = 0; i < FONT_HEIGHT; i++) { + font_data = *font_ptr++; + if (t_attrib->uline + && ((i == FONT_HEIGHT - 2) || (i == FONT_HEIGHT - 3))) { + font_data = 0xFFFF; + } + ((uint32_t *)d)[0] = (dmask4[(font_data >> 6)] & xorcol) ^ bgcol; + ((uint32_t *)d)[1] = (dmask4[(font_data >> 4) & 3] & xorcol) ^ bgcol; + ((uint32_t *)d)[2] = (dmask4[(font_data >> 2) & 3] & xorcol) ^ bgcol; + ((uint32_t *)d)[3] = (dmask4[(font_data >> 0) & 3] & xorcol) ^ bgcol; + d += linesize; + } + break; + case 32: + for(i = 0; i < FONT_HEIGHT; i++) { + font_data = *font_ptr++; + if (t_attrib->uline && ((i == FONT_HEIGHT - 2) || (i == FONT_HEIGHT - 3))) { + font_data = 0xFFFF; + } + ((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; + d += linesize; + } + break; + } +} + +static void text_console_resize(TextConsole *s) +{ + TextCell *cells, *c, *c1; + int w1, x, y, last_width; + + last_width = s->width; + s->width = s->g_width / FONT_WIDTH; + s->height = s->g_height / FONT_HEIGHT; + + w1 = last_width; + if (s->width < w1) + w1 = s->width; + + cells = qemu_malloc(s->width * s->total_height * sizeof(TextCell)); + for(y = 0; y < s->total_height; y++) { + c = &cells[y * s->width]; + if (w1 > 0) { + c1 = &s->cells[y * last_width]; + for(x = 0; x < w1; x++) { + *c++ = *c1++; + } + } + for(x = w1; x < s->width; x++) { + c->ch = ' '; + c->t_attrib = s->t_attrib_default; + c++; + } + } + qemu_free(s->cells); + s->cells = cells; +} + +static inline void text_update_xy(TextConsole *s, int x, int y) +{ + s->text_x[0] = MIN(s->text_x[0], x); + s->text_x[1] = MAX(s->text_x[1], x); + s->text_y[0] = MIN(s->text_y[0], y); + s->text_y[1] = MAX(s->text_y[1], y); +} + +static void invalidate_xy(TextConsole *s, int x, int y) +{ + if (s->update_x0 > x * FONT_WIDTH) + s->update_x0 = x * FONT_WIDTH; + if (s->update_y0 > y * FONT_HEIGHT) + s->update_y0 = y * FONT_HEIGHT; + if (s->update_x1 < (x + 1) * FONT_WIDTH) + s->update_x1 = (x + 1) * FONT_WIDTH; + if (s->update_y1 < (y + 1) * FONT_HEIGHT) + s->update_y1 = (y + 1) * FONT_HEIGHT; +} + +static void update_xy(TextConsole *s, int x, int y) +{ + TextCell *c; + int y1, y2; + + if (s == active_console) { + if (!ds_get_bits_per_pixel(s->ds)) { + text_update_xy(s, x, y); + return; + } + + y1 = (s->y_base + y) % s->total_height; + y2 = y1 - s->y_displayed; + if (y2 < 0) + y2 += s->total_height; + if (y2 < s->height) { + c = &s->cells[y1 * s->width + x]; + vga_putcharxy(s->ds, x, y2, c->ch, + &(c->t_attrib)); + invalidate_xy(s, x, y2); + } + } +} + +static void console_show_cursor(TextConsole *s, int show) +{ + TextCell *c; + int y, y1; + + if (s == active_console) { + int x = s->x; + + if (!ds_get_bits_per_pixel(s->ds)) { + s->cursor_invalidate = 1; + return; + } + + if (x >= s->width) { + x = s->width - 1; + } + y1 = (s->y_base + s->y) % s->total_height; + y = y1 - s->y_displayed; + if (y < 0) + y += s->total_height; + if (y < s->height) { + c = &s->cells[y1 * s->width + x]; + if (show) { + TextAttributes t_attrib = s->t_attrib_default; + t_attrib.invers = !(t_attrib.invers); /* invert fg and bg */ + vga_putcharxy(s->ds, x, y, c->ch, &t_attrib); + } else { + vga_putcharxy(s->ds, x, y, c->ch, &(c->t_attrib)); + } + invalidate_xy(s, x, y); + } + } +} + +static void console_refresh(TextConsole *s) +{ + TextCell *c; + int x, y, y1; + + if (s != active_console) + return; + if (!ds_get_bits_per_pixel(s->ds)) { + s->text_x[0] = 0; + s->text_y[0] = 0; + s->text_x[1] = s->width - 1; + s->text_y[1] = s->height - 1; + s->cursor_invalidate = 1; + return; + } + + vga_fill_rect(s->ds, 0, 0, ds_get_width(s->ds), ds_get_height(s->ds), + color_table[0][COLOR_BLACK]); + y1 = s->y_displayed; + for(y = 0; y < s->height; y++) { + c = s->cells + y1 * s->width; + for(x = 0; x < s->width; x++) { + vga_putcharxy(s->ds, x, y, c->ch, + &(c->t_attrib)); + c++; + } + if (++y1 == s->total_height) + y1 = 0; + } + console_show_cursor(s, 1); + dpy_update(s->ds, 0, 0, ds_get_width(s->ds), ds_get_height(s->ds)); +} + +static void console_scroll(int ydelta) +{ + TextConsole *s; + int i, y1; + + s = active_console; + if (!s || (s->console_type == GRAPHIC_CONSOLE)) + return; + + if (ydelta > 0) { + for(i = 0; i < ydelta; i++) { + if (s->y_displayed == s->y_base) + break; + if (++s->y_displayed == s->total_height) + s->y_displayed = 0; + } + } else { + ydelta = -ydelta; + i = s->backscroll_height; + if (i > s->total_height - s->height) + i = s->total_height - s->height; + y1 = s->y_base - i; + if (y1 < 0) + y1 += s->total_height; + for(i = 0; i < ydelta; i++) { + if (s->y_displayed == y1) + break; + if (--s->y_displayed < 0) + s->y_displayed = s->total_height - 1; + } + } + console_refresh(s); +} + +static void console_put_lf(TextConsole *s) +{ + TextCell *c; + int x, y1; + + s->y++; + if (s->y >= s->height) { + s->y = s->height - 1; + + if (s->y_displayed == s->y_base) { + if (++s->y_displayed == s->total_height) + s->y_displayed = 0; + } + if (++s->y_base == s->total_height) + s->y_base = 0; + if (s->backscroll_height < s->total_height) + s->backscroll_height++; + y1 = (s->y_base + s->height - 1) % s->total_height; + c = &s->cells[y1 * s->width]; + for(x = 0; x < s->width; x++) { + c->ch = ' '; + c->t_attrib = s->t_attrib_default; + c++; + } + if (s == active_console && s->y_displayed == s->y_base) { + if (!ds_get_bits_per_pixel(s->ds)) { + s->text_x[0] = 0; + s->text_y[0] = 0; + s->text_x[1] = s->width - 1; + s->text_y[1] = s->height - 1; + return; + } + + vga_bitblt(s->ds, 0, FONT_HEIGHT, 0, 0, + s->width * FONT_WIDTH, + (s->height - 1) * FONT_HEIGHT); + vga_fill_rect(s->ds, 0, (s->height - 1) * FONT_HEIGHT, + s->width * FONT_WIDTH, FONT_HEIGHT, + color_table[0][s->t_attrib_default.bgcol]); + s->update_x0 = 0; + s->update_y0 = 0; + s->update_x1 = s->width * FONT_WIDTH; + s->update_y1 = s->height * FONT_HEIGHT; + } + } +} + +/* Set console attributes depending on the current escape codes. + * NOTE: I know this code is not very efficient (checking every color for it + * self) but it is more readable and better maintainable. + */ +static void console_handle_escape(TextConsole *s) +{ + int i; + + for (i=0; inb_esc_params; i++) { + switch (s->esc_params[i]) { + case 0: /* reset all console attributes to default */ + s->t_attrib = s->t_attrib_default; + break; + case 1: + s->t_attrib.bold = 1; + break; + case 4: + s->t_attrib.uline = 1; + break; + case 5: + s->t_attrib.blink = 1; + break; + case 7: + s->t_attrib.invers = 1; + break; + case 8: + s->t_attrib.unvisible = 1; + break; + case 22: + s->t_attrib.bold = 0; + break; + case 24: + s->t_attrib.uline = 0; + break; + case 25: + s->t_attrib.blink = 0; + break; + case 27: + s->t_attrib.invers = 0; + break; + case 28: + s->t_attrib.unvisible = 0; + break; + /* set foreground color */ + case 30: + s->t_attrib.fgcol=COLOR_BLACK; + break; + case 31: + s->t_attrib.fgcol=COLOR_RED; + break; + case 32: + s->t_attrib.fgcol=COLOR_GREEN; + break; + case 33: + s->t_attrib.fgcol=COLOR_YELLOW; + break; + case 34: + s->t_attrib.fgcol=COLOR_BLUE; + break; + case 35: + s->t_attrib.fgcol=COLOR_MAGENTA; + break; + case 36: + s->t_attrib.fgcol=COLOR_CYAN; + break; + case 37: + s->t_attrib.fgcol=COLOR_WHITE; + break; + /* set background color */ + case 40: + s->t_attrib.bgcol=COLOR_BLACK; + break; + case 41: + s->t_attrib.bgcol=COLOR_RED; + break; + case 42: + s->t_attrib.bgcol=COLOR_GREEN; + break; + case 43: + s->t_attrib.bgcol=COLOR_YELLOW; + break; + case 44: + s->t_attrib.bgcol=COLOR_BLUE; + break; + case 45: + s->t_attrib.bgcol=COLOR_MAGENTA; + break; + case 46: + s->t_attrib.bgcol=COLOR_CYAN; + break; + case 47: + s->t_attrib.bgcol=COLOR_WHITE; + break; + } + } +} + +static void console_clear_xy(TextConsole *s, int x, int y) +{ + int y1 = (s->y_base + y) % s->total_height; + TextCell *c = &s->cells[y1 * s->width + x]; + c->ch = ' '; + c->t_attrib = s->t_attrib_default; + update_xy(s, x, y); +} + +static void console_putchar(TextConsole *s, int ch) +{ + TextCell *c; + int y1, i; + int x, y; + + switch(s->state) { + case TTY_STATE_NORM: + switch(ch) { + case '\r': /* carriage return */ + s->x = 0; + break; + case '\n': /* newline */ + console_put_lf(s); + break; + case '\b': /* backspace */ + if (s->x > 0) + s->x--; + break; + case '\t': /* tabspace */ + if (s->x + (8 - (s->x % 8)) > s->width) { + s->x = 0; + console_put_lf(s); + } else { + s->x = s->x + (8 - (s->x % 8)); + } + break; + case '\a': /* alert aka. bell */ + /* TODO: has to be implemented */ + break; + case 14: + /* SI (shift in), character set 0 (ignored) */ + break; + case 15: + /* SO (shift out), character set 1 (ignored) */ + break; + case 27: /* esc (introducing an escape sequence) */ + s->state = TTY_STATE_ESC; + break; + default: + if (s->x >= s->width) { + /* line wrap */ + s->x = 0; + console_put_lf(s); + } + y1 = (s->y_base + s->y) % s->total_height; + c = &s->cells[y1 * s->width + s->x]; + c->ch = ch; + c->t_attrib = s->t_attrib; + update_xy(s, s->x, s->y); + s->x++; + break; + } + break; + case TTY_STATE_ESC: /* check if it is a terminal escape sequence */ + if (ch == '[') { + for(i=0;iesc_params[i] = 0; + s->nb_esc_params = 0; + s->state = TTY_STATE_CSI; + } else { + s->state = TTY_STATE_NORM; + } + break; + case TTY_STATE_CSI: /* handle escape sequence parameters */ + if (ch >= '0' && ch <= '9') { + if (s->nb_esc_params < MAX_ESC_PARAMS) { + s->esc_params[s->nb_esc_params] = + s->esc_params[s->nb_esc_params] * 10 + ch - '0'; + } + } else { + s->nb_esc_params++; + if (ch == ';') + break; +#ifdef DEBUG_CONSOLE + fprintf(stderr, "escape sequence CSI%d;%d%c, %d parameters\n", + s->esc_params[0], s->esc_params[1], ch, s->nb_esc_params); +#endif + s->state = TTY_STATE_NORM; + switch(ch) { + case 'A': + /* move cursor up */ + if (s->esc_params[0] == 0) { + s->esc_params[0] = 1; + } + s->y -= s->esc_params[0]; + if (s->y < 0) { + s->y = 0; + } + break; + case 'B': + /* move cursor down */ + if (s->esc_params[0] == 0) { + s->esc_params[0] = 1; + } + s->y += s->esc_params[0]; + if (s->y >= s->height) { + s->y = s->height - 1; + } + break; + case 'C': + /* move cursor right */ + if (s->esc_params[0] == 0) { + s->esc_params[0] = 1; + } + s->x += s->esc_params[0]; + if (s->x >= s->width) { + s->x = s->width - 1; + } + break; + case 'D': + /* move cursor left */ + if (s->esc_params[0] == 0) { + s->esc_params[0] = 1; + } + s->x -= s->esc_params[0]; + if (s->x < 0) { + s->x = 0; + } + break; + case 'G': + /* move cursor to column */ + s->x = s->esc_params[0] - 1; + if (s->x < 0) { + s->x = 0; + } + break; + case 'f': + case 'H': + /* move cursor to row, column */ + s->x = s->esc_params[1] - 1; + if (s->x < 0) { + s->x = 0; + } + s->y = s->esc_params[0] - 1; + if (s->y < 0) { + s->y = 0; + } + break; + case 'J': + switch (s->esc_params[0]) { + case 0: + /* clear to end of screen */ + for (y = s->y; y < s->height; y++) { + for (x = 0; x < s->width; x++) { + if (y == s->y && x < s->x) { + continue; + } + console_clear_xy(s, x, y); + } + } + break; + case 1: + /* clear from beginning of screen */ + for (y = 0; y <= s->y; y++) { + for (x = 0; x < s->width; x++) { + if (y == s->y && x > s->x) { + break; + } + console_clear_xy(s, x, y); + } + } + break; + case 2: + /* clear entire screen */ + for (y = 0; y <= s->height; y++) { + for (x = 0; x < s->width; x++) { + console_clear_xy(s, x, y); + } + } + break; + } + case 'K': + switch (s->esc_params[0]) { + case 0: + /* clear to eol */ + for(x = s->x; x < s->width; x++) { + console_clear_xy(s, x, s->y); + } + break; + case 1: + /* clear from beginning of line */ + for (x = 0; x <= s->x; x++) { + console_clear_xy(s, x, s->y); + } + break; + case 2: + /* clear entire line */ + for(x = 0; x < s->width; x++) { + console_clear_xy(s, x, s->y); + } + break; + } + break; + case 'm': + console_handle_escape(s); + break; + case 'n': + /* report cursor position */ + /* TODO: send ESC[row;colR */ + break; + case 's': + /* save cursor position */ + s->x_saved = s->x; + s->y_saved = s->y; + break; + case 'u': + /* restore cursor position */ + s->x = s->x_saved; + s->y = s->y_saved; + break; + default: +#ifdef DEBUG_CONSOLE + fprintf(stderr, "unhandled escape character '%c'\n", ch); +#endif + break; + } + break; + } + } +} + +void console_select(unsigned int index) +{ + TextConsole *s; + + if (index >= MAX_CONSOLES) + return; + if (active_console) { + active_console->g_width = ds_get_width(active_console->ds); + active_console->g_height = ds_get_height(active_console->ds); + } + s = consoles[index]; + if (s) { + DisplayState *ds = s->ds; + active_console = s; + if (ds_get_bits_per_pixel(s->ds)) { + ds->surface = qemu_resize_displaysurface(ds, s->g_width, s->g_height); + } else { + s->ds->surface->width = s->width; + s->ds->surface->height = s->height; + } + dpy_resize(s->ds); + vga_hw_invalidate(); + } +} + +static int console_puts(CharDriverState *chr, const uint8_t *buf, int len) +{ + TextConsole *s = chr->opaque; + int i; + + s->update_x0 = s->width * FONT_WIDTH; + s->update_y0 = s->height * FONT_HEIGHT; + s->update_x1 = 0; + s->update_y1 = 0; + console_show_cursor(s, 0); + for(i = 0; i < len; i++) { + console_putchar(s, buf[i]); + } + console_show_cursor(s, 1); + if (ds_get_bits_per_pixel(s->ds) && s->update_x0 < s->update_x1) { + dpy_update(s->ds, s->update_x0, s->update_y0, + s->update_x1 - s->update_x0, + s->update_y1 - s->update_y0); + } + return len; +} + +static void console_send_event(CharDriverState *chr, int event) +{ + TextConsole *s = chr->opaque; + int i; + + if (event == CHR_EVENT_FOCUS) { + for(i = 0; i < nb_consoles; i++) { + if (consoles[i] == s) { + console_select(i); + break; + } + } + } +} + +static void kbd_send_chars(void *opaque) +{ + TextConsole *s = opaque; + int len; + uint8_t buf[16]; + + len = qemu_chr_can_read(s->chr); + if (len > s->out_fifo.count) + len = s->out_fifo.count; + if (len > 0) { + if (len > sizeof(buf)) + len = sizeof(buf); + qemu_fifo_read(&s->out_fifo, buf, len); + qemu_chr_read(s->chr, buf, len); + } + /* characters are pending: we send them a bit later (XXX: + horrible, should change char device API) */ + if (s->out_fifo.count > 0) { + qemu_mod_timer(s->kbd_timer, qemu_get_clock(rt_clock) + 1); + } +} + +/* called when an ascii key is pressed */ +void kbd_put_keysym(int keysym) +{ + TextConsole *s; + uint8_t buf[16], *q; + int c; + + s = active_console; + if (!s || (s->console_type == GRAPHIC_CONSOLE)) + return; + + switch(keysym) { + case QEMU_KEY_CTRL_UP: + console_scroll(-1); + break; + case QEMU_KEY_CTRL_DOWN: + console_scroll(1); + break; + case QEMU_KEY_CTRL_PAGEUP: + console_scroll(-10); + break; + case QEMU_KEY_CTRL_PAGEDOWN: + console_scroll(10); + break; + default: + /* convert the QEMU keysym to VT100 key string */ + q = buf; + if (keysym >= 0xe100 && keysym <= 0xe11f) { + *q++ = '\033'; + *q++ = '['; + c = keysym - 0xe100; + if (c >= 10) + *q++ = '0' + (c / 10); + *q++ = '0' + (c % 10); + *q++ = '~'; + } else if (keysym >= 0xe120 && keysym <= 0xe17f) { + *q++ = '\033'; + *q++ = '['; + *q++ = keysym & 0xff; + } else if (s->echo && (keysym == '\r' || keysym == '\n')) { + console_puts(s->chr, (const uint8_t *) "\r", 1); + *q++ = '\n'; + } else { + *q++ = keysym; + } + if (s->echo) { + console_puts(s->chr, buf, q - buf); + } + if (s->chr->chr_read) { + qemu_fifo_write(&s->out_fifo, buf, q - buf); + kbd_send_chars(s); + } + break; + } +} + +static void text_console_invalidate(void *opaque) +{ + TextConsole *s = (TextConsole *) opaque; + if (!ds_get_bits_per_pixel(s->ds) && s->console_type == TEXT_CONSOLE) { + s->g_width = ds_get_width(s->ds); + s->g_height = ds_get_height(s->ds); + text_console_resize(s); + } + console_refresh(s); +} + +static void text_console_update(void *opaque, console_ch_t *chardata) +{ + TextConsole *s = (TextConsole *) opaque; + int i, j, src; + + if (s->text_x[0] <= s->text_x[1]) { + src = (s->y_base + s->text_y[0]) * s->width; + chardata += s->text_y[0] * s->width; + for (i = s->text_y[0]; i <= s->text_y[1]; i ++) + for (j = 0; j < s->width; j ++, src ++) + console_write_ch(chardata ++, s->cells[src].ch | + (s->cells[src].t_attrib.fgcol << 12) | + (s->cells[src].t_attrib.bgcol << 8) | + (s->cells[src].t_attrib.bold << 21)); + dpy_update(s->ds, s->text_x[0], s->text_y[0], + s->text_x[1] - s->text_x[0], i - s->text_y[0]); + s->text_x[0] = s->width; + s->text_y[0] = s->height; + s->text_x[1] = 0; + s->text_y[1] = 0; + } + if (s->cursor_invalidate) { + dpy_cursor(s->ds, s->x, s->y); + s->cursor_invalidate = 0; + } +} + +static TextConsole *get_graphic_console(DisplayState *ds) +{ + int i; + TextConsole *s; + for (i = 0; i < nb_consoles; i++) { + s = consoles[i]; + if (s->console_type == GRAPHIC_CONSOLE && s->ds == ds) + return s; + } + return NULL; +} + +static TextConsole *new_console(DisplayState *ds, console_type_t console_type) +{ + TextConsole *s; + int i; + + if (nb_consoles >= MAX_CONSOLES) + return NULL; + s = qemu_mallocz(sizeof(TextConsole)); + if (!active_console || ((active_console->console_type != GRAPHIC_CONSOLE) && + (console_type == GRAPHIC_CONSOLE))) { + active_console = s; + } + s->ds = ds; + s->console_type = console_type; + if (console_type != GRAPHIC_CONSOLE) { + consoles[nb_consoles++] = s; + } else { + /* HACK: Put graphical consoles before text consoles. */ + for (i = nb_consoles; i > 0; i--) { + if (consoles[i - 1]->console_type == GRAPHIC_CONSOLE) + break; + consoles[i] = consoles[i - 1]; + } + consoles[i] = s; + nb_consoles++; + } + return s; +} + +static DisplaySurface* defaultallocator_create_displaysurface(int width, int height) +{ + DisplaySurface *surface = (DisplaySurface*) qemu_mallocz(sizeof(DisplaySurface)); + + surface->width = width; + surface->height = height; + surface->linesize = width * 4; + surface->pf = qemu_default_pixelformat(32); +#ifdef HOST_WORDS_BIGENDIAN + surface->flags = QEMU_ALLOCATED_FLAG | QEMU_BIG_ENDIAN_FLAG; +#else + surface->flags = QEMU_ALLOCATED_FLAG; +#endif + surface->data = (uint8_t*) qemu_mallocz(surface->linesize * surface->height); + + return surface; +} + +static DisplaySurface* defaultallocator_resize_displaysurface(DisplaySurface *surface, + int width, int height) +{ + surface->width = width; + surface->height = height; + surface->linesize = width * 4; + surface->pf = qemu_default_pixelformat(32); + if (surface->flags & QEMU_ALLOCATED_FLAG) + surface->data = (uint8_t*) qemu_realloc(surface->data, surface->linesize * surface->height); + else + surface->data = (uint8_t*) qemu_malloc(surface->linesize * surface->height); +#ifdef HOST_WORDS_BIGENDIAN + surface->flags = QEMU_ALLOCATED_FLAG | QEMU_BIG_ENDIAN_FLAG; +#else + surface->flags = QEMU_ALLOCATED_FLAG; +#endif + + return surface; +} + +DisplaySurface* qemu_create_displaysurface_from(int width, int height, int bpp, + int linesize, uint8_t *data) +{ + DisplaySurface *surface = (DisplaySurface*) qemu_mallocz(sizeof(DisplaySurface)); + + surface->width = width; + surface->height = height; + surface->linesize = linesize; + surface->pf = qemu_default_pixelformat(bpp); +#ifdef HOST_WORDS_BIGENDIAN + surface->flags = QEMU_BIG_ENDIAN_FLAG; +#endif + surface->data = data; + + return surface; +} + +static void defaultallocator_free_displaysurface(DisplaySurface *surface) +{ + if (surface == NULL) + return; + if (surface->flags & QEMU_ALLOCATED_FLAG) + qemu_free(surface->data); + qemu_free(surface); +} + +static struct DisplayAllocator default_allocator = { + defaultallocator_create_displaysurface, + defaultallocator_resize_displaysurface, + defaultallocator_free_displaysurface +}; + +static void dumb_display_init(void) +{ + DisplayState *ds = qemu_mallocz(sizeof(DisplayState)); + ds->allocator = &default_allocator; + ds->surface = qemu_create_displaysurface(ds, 640, 480); + register_displaystate(ds); +} + +/***********************************************************/ +/* register display */ + +void register_displaystate(DisplayState *ds) +{ + DisplayState **s; + s = &display_state; + while (*s != NULL) + s = &(*s)->next; + ds->next = NULL; + *s = ds; +} + +DisplayState *get_displaystate(void) +{ + if (!display_state) { + dumb_display_init (); + } + return display_state; +} + +DisplayAllocator *register_displayallocator(DisplayState *ds, DisplayAllocator *da) +{ + if(ds->allocator == &default_allocator) { + DisplaySurface *surf; + surf = da->create_displaysurface(ds_get_width(ds), ds_get_height(ds)); + defaultallocator_free_displaysurface(ds->surface); + ds->surface = surf; + ds->allocator = da; + } + return ds->allocator; +} + +DisplayState *graphic_console_init(vga_hw_update_ptr update, + vga_hw_invalidate_ptr invalidate, + vga_hw_screen_dump_ptr screen_dump, + vga_hw_text_update_ptr text_update, + void *opaque) +{ + TextConsole *s; + DisplayState *ds; + + ds = (DisplayState *) qemu_mallocz(sizeof(DisplayState)); + ds->allocator = &default_allocator; + ds->surface = qemu_create_displaysurface(ds, 640, 480); + + s = new_console(ds, GRAPHIC_CONSOLE); + if (s == NULL) { + qemu_free_displaysurface(ds); + qemu_free(ds); + return NULL; + } + s->hw_update = update; + s->hw_invalidate = invalidate; + s->hw_screen_dump = screen_dump; + s->hw_text_update = text_update; + s->hw = opaque; + + register_displaystate(ds); + return ds; +} + +int is_graphic_console(void) +{ + return active_console && active_console->console_type == GRAPHIC_CONSOLE; +} + +int is_fixedsize_console(void) +{ + return active_console && active_console->console_type != TEXT_CONSOLE; +} + +void console_color_init(DisplayState *ds) +{ + int i, j; + for (j = 0; j < 2; j++) { + for (i = 0; i < 8; i++) { + color_table[j][i] = col_expand(ds, + vga_get_color(ds, color_table_rgb[j][i])); + } + } +} + +static int n_text_consoles; +static CharDriverState *text_consoles[128]; + +static void text_console_set_echo(CharDriverState *chr, bool echo) +{ + TextConsole *s = chr->opaque; + + s->echo = echo; +} + +static void text_console_do_init(CharDriverState *chr, DisplayState *ds) +{ + TextConsole *s; + static int color_inited; + + s = chr->opaque; + + chr->chr_write = console_puts; + chr->chr_send_event = console_send_event; + + s->out_fifo.buf = s->out_fifo_buf; + s->out_fifo.buf_size = sizeof(s->out_fifo_buf); + s->kbd_timer = qemu_new_timer(rt_clock, kbd_send_chars, s); + s->ds = ds; + + if (!color_inited) { + color_inited = 1; + console_color_init(s->ds); + } + s->y_displayed = 0; + s->y_base = 0; + s->total_height = DEFAULT_BACKSCROLL; + s->x = 0; + s->y = 0; + if (s->console_type == TEXT_CONSOLE) { + s->g_width = ds_get_width(s->ds); + s->g_height = ds_get_height(s->ds); + } + + s->hw_invalidate = text_console_invalidate; + s->hw_text_update = text_console_update; + s->hw = s; + + /* Set text attribute defaults */ + s->t_attrib_default.bold = 0; + s->t_attrib_default.uline = 0; + s->t_attrib_default.blink = 0; + s->t_attrib_default.invers = 0; + s->t_attrib_default.unvisible = 0; + s->t_attrib_default.fgcol = COLOR_WHITE; + s->t_attrib_default.bgcol = COLOR_BLACK; + /* set current text attributes to default */ + s->t_attrib = s->t_attrib_default; + text_console_resize(s); + + if (chr->label) { + char msg[128]; + int len; + + s->t_attrib.bgcol = COLOR_BLUE; + len = snprintf(msg, sizeof(msg), "%s console\r\n", chr->label); + console_puts(chr, (uint8_t*)msg, len); + s->t_attrib = s->t_attrib_default; + } + + qemu_chr_generic_open(chr); + if (chr->init) + chr->init(chr); +} + +CharDriverState *text_console_init(QemuOpts *opts) +{ + CharDriverState *chr; + TextConsole *s; + unsigned width; + unsigned height; + + chr = qemu_mallocz(sizeof(CharDriverState)); + + if (n_text_consoles == 128) { + fprintf(stderr, "Too many text consoles\n"); + exit(1); + } + text_consoles[n_text_consoles] = chr; + n_text_consoles++; + + width = qemu_opt_get_number(opts, "width", 0); + if (width == 0) + width = qemu_opt_get_number(opts, "cols", 0) * FONT_WIDTH; + + height = qemu_opt_get_number(opts, "height", 0); + if (height == 0) + height = qemu_opt_get_number(opts, "rows", 0) * FONT_HEIGHT; + + if (width == 0 || height == 0) { + s = new_console(NULL, TEXT_CONSOLE); + } else { + s = new_console(NULL, TEXT_CONSOLE_FIXED_SIZE); + } + + if (!s) { + free(chr); + return NULL; + } + + s->chr = chr; + s->g_width = width; + s->g_height = height; + chr->opaque = s; + chr->chr_set_echo = text_console_set_echo; + return chr; +} + +void text_consoles_set_display(DisplayState *ds) +{ + int i; + + for (i = 0; i < n_text_consoles; i++) { + text_console_do_init(text_consoles[i], ds); + } + + n_text_consoles = 0; +} + +void qemu_console_resize(DisplayState *ds, int width, int height) +{ + TextConsole *s = get_graphic_console(ds); + if (!s) return; + + s->g_width = width; + s->g_height = height; + if (is_graphic_console()) { + ds->surface = qemu_resize_displaysurface(ds, width, height); + dpy_resize(ds); + } +} + +void qemu_console_copy(DisplayState *ds, int src_x, int src_y, + int dst_x, int dst_y, int w, int h) +{ + if (is_graphic_console()) { + dpy_copy(ds, src_x, src_y, dst_x, dst_y, w, h); + } +} + +PixelFormat qemu_different_endianness_pixelformat(int bpp) +{ + PixelFormat pf; + + memset(&pf, 0x00, sizeof(PixelFormat)); + + pf.bits_per_pixel = bpp; + pf.bytes_per_pixel = bpp / 8; + pf.depth = bpp == 32 ? 24 : bpp; + + switch (bpp) { + case 24: + pf.rmask = 0x000000FF; + pf.gmask = 0x0000FF00; + pf.bmask = 0x00FF0000; + pf.rmax = 255; + pf.gmax = 255; + pf.bmax = 255; + pf.rshift = 0; + pf.gshift = 8; + pf.bshift = 16; + pf.rbits = 8; + pf.gbits = 8; + pf.bbits = 8; + break; + case 32: + pf.rmask = 0x0000FF00; + pf.gmask = 0x00FF0000; + pf.bmask = 0xFF000000; + pf.amask = 0x00000000; + pf.amax = 255; + pf.rmax = 255; + pf.gmax = 255; + pf.bmax = 255; + pf.ashift = 0; + pf.rshift = 8; + pf.gshift = 16; + pf.bshift = 24; + pf.rbits = 8; + pf.gbits = 8; + pf.bbits = 8; + pf.abits = 8; + break; + default: + break; + } + return pf; +} + +PixelFormat qemu_default_pixelformat(int bpp) +{ + PixelFormat pf; + + memset(&pf, 0x00, sizeof(PixelFormat)); + + pf.bits_per_pixel = bpp; + pf.bytes_per_pixel = bpp / 8; + pf.depth = bpp == 32 ? 24 : bpp; + + switch (bpp) { + case 15: + pf.bits_per_pixel = 16; + pf.bytes_per_pixel = 2; + pf.rmask = 0x00007c00; + pf.gmask = 0x000003E0; + pf.bmask = 0x0000001F; + pf.rmax = 31; + pf.gmax = 31; + pf.bmax = 31; + pf.rshift = 10; + pf.gshift = 5; + pf.bshift = 0; + pf.rbits = 5; + pf.gbits = 5; + pf.bbits = 5; + break; + case 16: + pf.rmask = 0x0000F800; + pf.gmask = 0x000007E0; + pf.bmask = 0x0000001F; + pf.rmax = 31; + pf.gmax = 63; + pf.bmax = 31; + pf.rshift = 11; + pf.gshift = 5; + pf.bshift = 0; + pf.rbits = 5; + pf.gbits = 6; + pf.bbits = 5; + break; + case 24: + pf.rmask = 0x00FF0000; + pf.gmask = 0x0000FF00; + pf.bmask = 0x000000FF; + pf.rmax = 255; + pf.gmax = 255; + pf.bmax = 255; + pf.rshift = 16; + pf.gshift = 8; + pf.bshift = 0; + pf.rbits = 8; + pf.gbits = 8; + pf.bbits = 8; + case 32: + pf.rmask = 0x00FF0000; + pf.gmask = 0x0000FF00; + pf.bmask = 0x000000FF; + pf.amax = 255; + pf.rmax = 255; + pf.gmax = 255; + pf.bmax = 255; + pf.ashift = 24; + pf.rshift = 16; + pf.gshift = 8; + pf.bshift = 0; + pf.rbits = 8; + pf.gbits = 8; + pf.bbits = 8; + pf.abits = 8; + break; + default: + break; + } + return pf; +} diff --git a/console.h b/console.h new file mode 100644 index 0000000..f4e4741 --- /dev/null +++ b/console.h @@ -0,0 +1,381 @@ +#ifndef CONSOLE_H +#define CONSOLE_H + +#include "qemu-char.h" +#include "qdict.h" +#include "notify.h" + +/* keyboard/mouse support */ + +#define MOUSE_EVENT_LBUTTON 0x01 +#define MOUSE_EVENT_RBUTTON 0x02 +#define MOUSE_EVENT_MBUTTON 0x04 + +/* identical to the ps/2 keyboard bits */ +#define QEMU_SCROLL_LOCK_LED (1 << 0) +#define QEMU_NUM_LOCK_LED (1 << 1) +#define QEMU_CAPS_LOCK_LED (1 << 2) + +/* in ms */ +#define GUI_REFRESH_INTERVAL 30 + +typedef void QEMUPutKBDEvent(void *opaque, int keycode); +typedef void QEMUPutLEDEvent(void *opaque, int ledstate); +typedef void QEMUPutMouseEvent(void *opaque, int dx, int dy, int dz, int buttons_state); + +typedef struct QEMUPutMouseEntry { + QEMUPutMouseEvent *qemu_put_mouse_event; + void *qemu_put_mouse_event_opaque; + int qemu_put_mouse_event_absolute; + char *qemu_put_mouse_event_name; + + int index; + + /* used internally by qemu for handling mice */ + QTAILQ_ENTRY(QEMUPutMouseEntry) node; +} QEMUPutMouseEntry; + +typedef struct QEMUPutLEDEntry { + QEMUPutLEDEvent *put_led; + void *opaque; + QTAILQ_ENTRY(QEMUPutLEDEntry) next; +} QEMUPutLEDEntry; + +void qemu_add_kbd_event_handler(QEMUPutKBDEvent *func, void *opaque); +void qemu_remove_kbd_event_handler(void); +QEMUPutMouseEntry *qemu_add_mouse_event_handler(QEMUPutMouseEvent *func, + void *opaque, int absolute, + const char *name); +void qemu_remove_mouse_event_handler(QEMUPutMouseEntry *entry); +void qemu_activate_mouse_event_handler(QEMUPutMouseEntry *entry); + +QEMUPutLEDEntry *qemu_add_led_event_handler(QEMUPutLEDEvent *func, void *opaque); +void qemu_remove_led_event_handler(QEMUPutLEDEntry *entry); + +void kbd_put_keycode(int keycode); +void kbd_put_ledstate(int ledstate); +void kbd_mouse_event(int dx, int dy, int dz, int buttons_state); + +/* Does the current mouse generate absolute events */ +int kbd_mouse_is_absolute(void); +void qemu_add_mouse_mode_change_notifier(Notifier *notify); +void qemu_remove_mouse_mode_change_notifier(Notifier *notify); + +/* Of all the mice, is there one that generates absolute events */ +int kbd_mouse_has_absolute(void); + +struct MouseTransformInfo { + /* Touchscreen resolution */ + int x; + int y; + /* Calibration values as used/generated by tslib */ + int a[7]; +}; + +void do_info_mice_print(Monitor *mon, const QObject *data); +void do_info_mice(Monitor *mon, QObject **ret_data); +void do_mouse_set(Monitor *mon, const QDict *qdict); + +/* keysym is a unicode code except for special keys (see QEMU_KEY_xxx + constants) */ +#define QEMU_KEY_ESC1(c) ((c) | 0xe100) +#define QEMU_KEY_BACKSPACE 0x007f +#define QEMU_KEY_UP QEMU_KEY_ESC1('A') +#define QEMU_KEY_DOWN QEMU_KEY_ESC1('B') +#define QEMU_KEY_RIGHT QEMU_KEY_ESC1('C') +#define QEMU_KEY_LEFT QEMU_KEY_ESC1('D') +#define QEMU_KEY_HOME QEMU_KEY_ESC1(1) +#define QEMU_KEY_END QEMU_KEY_ESC1(4) +#define QEMU_KEY_PAGEUP QEMU_KEY_ESC1(5) +#define QEMU_KEY_PAGEDOWN QEMU_KEY_ESC1(6) +#define QEMU_KEY_DELETE QEMU_KEY_ESC1(3) + +#define QEMU_KEY_CTRL_UP 0xe400 +#define QEMU_KEY_CTRL_DOWN 0xe401 +#define QEMU_KEY_CTRL_LEFT 0xe402 +#define QEMU_KEY_CTRL_RIGHT 0xe403 +#define QEMU_KEY_CTRL_HOME 0xe404 +#define QEMU_KEY_CTRL_END 0xe405 +#define QEMU_KEY_CTRL_PAGEUP 0xe406 +#define QEMU_KEY_CTRL_PAGEDOWN 0xe407 + +void kbd_put_keysym(int keysym); + +/* consoles */ + +#define QEMU_BIG_ENDIAN_FLAG 0x01 +#define QEMU_ALLOCATED_FLAG 0x02 +#define QEMU_REALPIXELS_FLAG 0x04 + +struct PixelFormat { + uint8_t bits_per_pixel; + uint8_t bytes_per_pixel; + uint8_t depth; /* color depth in bits */ + uint32_t rmask, gmask, bmask, amask; + uint8_t rshift, gshift, bshift, ashift; + uint8_t rmax, gmax, bmax, amax; + uint8_t rbits, gbits, bbits, abits; +}; + +struct DisplaySurface { + uint8_t flags; + int width; + int height; + int linesize; /* bytes per line */ + uint8_t *data; + + struct PixelFormat pf; +}; + +/* cursor data format is 32bit RGBA */ +typedef struct QEMUCursor { + int width, height; + int hot_x, hot_y; + int refcount; + uint32_t data[]; +} QEMUCursor; + +QEMUCursor *cursor_alloc(int width, int height); +void cursor_get(QEMUCursor *c); +void cursor_put(QEMUCursor *c); +QEMUCursor *cursor_builtin_hidden(void); +QEMUCursor *cursor_builtin_left_ptr(void); +void cursor_print_ascii_art(QEMUCursor *c, const char *prefix); +int cursor_get_mono_bpl(QEMUCursor *c); +void cursor_set_mono(QEMUCursor *c, + uint32_t foreground, uint32_t background, uint8_t *image, + int transparent, uint8_t *mask); +void cursor_get_mono_image(QEMUCursor *c, int foreground, uint8_t *mask); +void cursor_get_mono_mask(QEMUCursor *c, int transparent, uint8_t *mask); + +struct DisplayChangeListener { + int idle; + uint64_t gui_timer_interval; + + void (*dpy_update)(struct DisplayState *s, int x, int y, int w, int h); + void (*dpy_resize)(struct DisplayState *s); + void (*dpy_setdata)(struct DisplayState *s); + void (*dpy_refresh)(struct DisplayState *s); + void (*dpy_copy)(struct DisplayState *s, int src_x, int src_y, + int dst_x, int dst_y, int w, int h); + void (*dpy_fill)(struct DisplayState *s, int x, int y, + int w, int h, uint32_t c); + void (*dpy_text_cursor)(struct DisplayState *s, int x, int y); + + struct DisplayChangeListener *next; +}; + +struct DisplayAllocator { + DisplaySurface* (*create_displaysurface)(int width, int height); + DisplaySurface* (*resize_displaysurface)(DisplaySurface *surface, int width, int height); + void (*free_displaysurface)(DisplaySurface *surface); +}; + +struct DisplayState { + struct DisplaySurface *surface; + void *opaque; + struct QEMUTimer *gui_timer; + + struct DisplayAllocator* allocator; + struct DisplayChangeListener* listeners; + + void (*mouse_set)(int x, int y, int on); + void (*cursor_define)(QEMUCursor *cursor); + + struct DisplayState *next; +}; + +void register_displaystate(DisplayState *ds); +DisplayState *get_displaystate(void); +DisplaySurface* qemu_create_displaysurface_from(int width, int height, int bpp, + int linesize, uint8_t *data); +PixelFormat qemu_different_endianness_pixelformat(int bpp); +PixelFormat qemu_default_pixelformat(int bpp); + +DisplayAllocator *register_displayallocator(DisplayState *ds, DisplayAllocator *da); + +static inline DisplaySurface* qemu_create_displaysurface(DisplayState *ds, int width, int height) +{ + return ds->allocator->create_displaysurface(width, height); +} + +static inline DisplaySurface* qemu_resize_displaysurface(DisplayState *ds, int width, int height) +{ + return ds->allocator->resize_displaysurface(ds->surface, width, height); +} + +static inline void qemu_free_displaysurface(DisplayState *ds) +{ + ds->allocator->free_displaysurface(ds->surface); +} + +static inline int is_surface_bgr(DisplaySurface *surface) +{ + if (surface->pf.bits_per_pixel == 32 && surface->pf.rshift == 0) + return 1; + else + return 0; +} + +static inline int is_buffer_shared(DisplaySurface *surface) +{ + return (!(surface->flags & QEMU_ALLOCATED_FLAG) && + !(surface->flags & QEMU_REALPIXELS_FLAG)); +} + +static inline void register_displaychangelistener(DisplayState *ds, DisplayChangeListener *dcl) +{ + dcl->next = ds->listeners; + ds->listeners = dcl; +} + +static inline void dpy_update(DisplayState *s, int x, int y, int w, int h) +{ + struct DisplayChangeListener *dcl = s->listeners; + while (dcl != NULL) { + dcl->dpy_update(s, x, y, w, h); + dcl = dcl->next; + } +} + +static inline void dpy_resize(DisplayState *s) +{ + struct DisplayChangeListener *dcl = s->listeners; + while (dcl != NULL) { + dcl->dpy_resize(s); + dcl = dcl->next; + } +} + +static inline void dpy_setdata(DisplayState *s) +{ + struct DisplayChangeListener *dcl = s->listeners; + while (dcl != NULL) { + if (dcl->dpy_setdata) dcl->dpy_setdata(s); + dcl = dcl->next; + } +} + +static inline void dpy_refresh(DisplayState *s) +{ + struct DisplayChangeListener *dcl = s->listeners; + while (dcl != NULL) { + if (dcl->dpy_refresh) dcl->dpy_refresh(s); + dcl = dcl->next; + } +} + +static inline void dpy_copy(struct DisplayState *s, int src_x, int src_y, + int dst_x, int dst_y, int w, int h) { + struct DisplayChangeListener *dcl = s->listeners; + while (dcl != NULL) { + if (dcl->dpy_copy) + dcl->dpy_copy(s, src_x, src_y, dst_x, dst_y, w, h); + else /* TODO */ + dcl->dpy_update(s, dst_x, dst_y, w, h); + dcl = dcl->next; + } +} + +static inline void dpy_fill(struct DisplayState *s, int x, int y, + int w, int h, uint32_t c) { + struct DisplayChangeListener *dcl = s->listeners; + while (dcl != NULL) { + if (dcl->dpy_fill) dcl->dpy_fill(s, x, y, w, h, c); + dcl = dcl->next; + } +} + +static inline void dpy_cursor(struct DisplayState *s, int x, int y) { + struct DisplayChangeListener *dcl = s->listeners; + while (dcl != NULL) { + if (dcl->dpy_text_cursor) dcl->dpy_text_cursor(s, x, y); + dcl = dcl->next; + } +} + +static inline int ds_get_linesize(DisplayState *ds) +{ + return ds->surface->linesize; +} + +static inline uint8_t* ds_get_data(DisplayState *ds) +{ + return ds->surface->data; +} + +static inline int ds_get_width(DisplayState *ds) +{ + return ds->surface->width; +} + +static inline int ds_get_height(DisplayState *ds) +{ + return ds->surface->height; +} + +static inline int ds_get_bits_per_pixel(DisplayState *ds) +{ + return ds->surface->pf.bits_per_pixel; +} + +static inline int ds_get_bytes_per_pixel(DisplayState *ds) +{ + return ds->surface->pf.bytes_per_pixel; +} + +typedef unsigned long console_ch_t; +static inline void console_write_ch(console_ch_t *dest, uint32_t ch) +{ + if (!(ch & 0xff)) + ch |= ' '; + *dest = ch; +} + +typedef void (*vga_hw_update_ptr)(void *); +typedef void (*vga_hw_invalidate_ptr)(void *); +typedef void (*vga_hw_screen_dump_ptr)(void *, const char *); +typedef void (*vga_hw_text_update_ptr)(void *, console_ch_t *); + +DisplayState *graphic_console_init(vga_hw_update_ptr update, + vga_hw_invalidate_ptr invalidate, + vga_hw_screen_dump_ptr screen_dump, + vga_hw_text_update_ptr text_update, + void *opaque); + +void vga_hw_update(void); +void vga_hw_invalidate(void); +void vga_hw_screen_dump(const char *filename); +void vga_hw_text_update(console_ch_t *chardata); + +int is_graphic_console(void); +int is_fixedsize_console(void); +CharDriverState *text_console_init(QemuOpts *opts); +void text_consoles_set_display(DisplayState *ds); +void console_select(unsigned int index); +void console_color_init(DisplayState *ds); +void qemu_console_resize(DisplayState *ds, int width, int height); +void qemu_console_copy(DisplayState *ds, int src_x, int src_y, + int dst_x, int dst_y, int w, int h); + +/* sdl.c */ +void sdl_display_init(DisplayState *ds, int full_screen, int no_frame); + +/* cocoa.m */ +void cocoa_display_init(DisplayState *ds, int full_screen); + +/* vnc.c */ +void vnc_display_init(DisplayState *ds); +void vnc_display_close(DisplayState *ds); +int vnc_display_open(DisplayState *ds, const char *display); +int vnc_display_password(DisplayState *ds, const char *password); +int vnc_display_disable_login(DisplayState *ds); +int vnc_display_pw_expire(DisplayState *ds, time_t expires); +void do_info_vnc_print(Monitor *mon, const QObject *data); +void do_info_vnc(Monitor *mon, QObject **ret_data); +char *vnc_display_local_addr(DisplayState *ds); + +/* curses.c */ +void curses_display_init(DisplayState *ds, int full_screen); + +#endif diff --git a/cpu-all.h b/cpu-all.h new file mode 100644 index 0000000..ffbd6a4 --- /dev/null +++ b/cpu-all.h @@ -0,0 +1,972 @@ +/* + * defines common to all virtual CPUs + * + * Copyright (c) 2003 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 . + */ +#ifndef CPU_ALL_H +#define CPU_ALL_H + +#include "qemu-common.h" +#include "cpu-common.h" + +/* some important defines: + * + * WORDS_ALIGNED : if defined, the host cpu can only make word aligned + * memory accesses. + * + * HOST_WORDS_BIGENDIAN : if defined, the host cpu is big endian and + * otherwise little endian. + * + * (TARGET_WORDS_ALIGNED : same for target cpu (not supported yet)) + * + * TARGET_WORDS_BIGENDIAN : same for target cpu + */ + +#include "softfloat.h" + +#if defined(HOST_WORDS_BIGENDIAN) != defined(TARGET_WORDS_BIGENDIAN) +#define BSWAP_NEEDED +#endif + +#ifdef BSWAP_NEEDED + +static inline uint16_t tswap16(uint16_t s) +{ + return bswap16(s); +} + +static inline uint32_t tswap32(uint32_t s) +{ + return bswap32(s); +} + +static inline uint64_t tswap64(uint64_t s) +{ + return bswap64(s); +} + +static inline void tswap16s(uint16_t *s) +{ + *s = bswap16(*s); +} + +static inline void tswap32s(uint32_t *s) +{ + *s = bswap32(*s); +} + +static inline void tswap64s(uint64_t *s) +{ + *s = bswap64(*s); +} + +#else + +static inline uint16_t tswap16(uint16_t s) +{ + return s; +} + +static inline uint32_t tswap32(uint32_t s) +{ + return s; +} + +static inline uint64_t tswap64(uint64_t s) +{ + return s; +} + +static inline void tswap16s(uint16_t *s) +{ +} + +static inline void tswap32s(uint32_t *s) +{ +} + +static inline void tswap64s(uint64_t *s) +{ +} + +#endif + +#if TARGET_LONG_SIZE == 4 +#define tswapl(s) tswap32(s) +#define tswapls(s) tswap32s((uint32_t *)(s)) +#define bswaptls(s) bswap32s(s) +#else +#define tswapl(s) tswap64(s) +#define tswapls(s) tswap64s((uint64_t *)(s)) +#define bswaptls(s) bswap64s(s) +#endif + +typedef union { + float32 f; + uint32_t l; +} CPU_FloatU; + +/* NOTE: arm FPA is horrible as double 32 bit words are stored in big + endian ! */ +typedef union { + float64 d; +#if defined(HOST_WORDS_BIGENDIAN) \ + || (defined(__arm__) && !defined(__VFP_FP__) && !defined(CONFIG_SOFTFLOAT)) + struct { + uint32_t upper; + uint32_t lower; + } l; +#else + struct { + uint32_t lower; + uint32_t upper; + } l; +#endif + uint64_t ll; +} CPU_DoubleU; + +#ifdef TARGET_SPARC +typedef union { + float128 q; +#if defined(HOST_WORDS_BIGENDIAN) \ + || (defined(__arm__) && !defined(__VFP_FP__) && !defined(CONFIG_SOFTFLOAT)) + struct { + uint32_t upmost; + uint32_t upper; + uint32_t lower; + uint32_t lowest; + } l; + struct { + uint64_t upper; + uint64_t lower; + } ll; +#else + struct { + uint32_t lowest; + uint32_t lower; + uint32_t upper; + uint32_t upmost; + } l; + struct { + uint64_t lower; + uint64_t upper; + } ll; +#endif +} CPU_QuadU; +#endif + +/* CPU memory access without any memory or io remapping */ + +/* + * the generic syntax for the memory accesses is: + * + * load: ld{type}{sign}{size}{endian}_{access_type}(ptr) + * + * store: st{type}{size}{endian}_{access_type}(ptr, val) + * + * type is: + * (empty): integer access + * f : float access + * + * sign is: + * (empty): for floats or 32 bit size + * u : unsigned + * s : signed + * + * size is: + * b: 8 bits + * w: 16 bits + * l: 32 bits + * q: 64 bits + * + * endian is: + * (empty): target cpu endianness or 8 bit access + * r : reversed target cpu endianness (not implemented yet) + * be : big endian (not implemented yet) + * le : little endian (not implemented yet) + * + * access_type is: + * raw : host memory access + * user : user mode access using soft MMU + * kernel : kernel mode access using soft MMU + */ +static inline int ldub_p(const void *ptr) +{ + return *(uint8_t *)ptr; +} + +static inline int ldsb_p(const void *ptr) +{ + return *(int8_t *)ptr; +} + +static inline void stb_p(void *ptr, int v) +{ + *(uint8_t *)ptr = v; +} + +/* NOTE: on arm, putting 2 in /proc/sys/debug/alignment so that the + kernel handles unaligned load/stores may give better results, but + it is a system wide setting : bad */ +#if defined(HOST_WORDS_BIGENDIAN) || defined(WORDS_ALIGNED) + +/* conservative code for little endian unaligned accesses */ +static inline int lduw_le_p(const void *ptr) +{ +#ifdef _ARCH_PPC + int val; + __asm__ __volatile__ ("lhbrx %0,0,%1" : "=r" (val) : "r" (ptr)); + return val; +#else + const uint8_t *p = ptr; + return p[0] | (p[1] << 8); +#endif +} + +static inline int ldsw_le_p(const void *ptr) +{ +#ifdef _ARCH_PPC + int val; + __asm__ __volatile__ ("lhbrx %0,0,%1" : "=r" (val) : "r" (ptr)); + return (int16_t)val; +#else + const uint8_t *p = ptr; + return (int16_t)(p[0] | (p[1] << 8)); +#endif +} + +static inline int ldl_le_p(const void *ptr) +{ +#ifdef _ARCH_PPC + int val; + __asm__ __volatile__ ("lwbrx %0,0,%1" : "=r" (val) : "r" (ptr)); + return val; +#else + const uint8_t *p = ptr; + return p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24); +#endif +} + +static inline uint64_t ldq_le_p(const void *ptr) +{ + const uint8_t *p = ptr; + uint32_t v1, v2; + v1 = ldl_le_p(p); + v2 = ldl_le_p(p + 4); + return v1 | ((uint64_t)v2 << 32); +} + +static inline void stw_le_p(void *ptr, int v) +{ +#ifdef _ARCH_PPC + __asm__ __volatile__ ("sthbrx %1,0,%2" : "=m" (*(uint16_t *)ptr) : "r" (v), "r" (ptr)); +#else + uint8_t *p = ptr; + p[0] = v; + p[1] = v >> 8; +#endif +} + +static inline void stl_le_p(void *ptr, int v) +{ +#ifdef _ARCH_PPC + __asm__ __volatile__ ("stwbrx %1,0,%2" : "=m" (*(uint32_t *)ptr) : "r" (v), "r" (ptr)); +#else + uint8_t *p = ptr; + p[0] = v; + p[1] = v >> 8; + p[2] = v >> 16; + p[3] = v >> 24; +#endif +} + +static inline void stq_le_p(void *ptr, uint64_t v) +{ + uint8_t *p = ptr; + stl_le_p(p, (uint32_t)v); + stl_le_p(p + 4, v >> 32); +} + +/* float access */ + +static inline float32 ldfl_le_p(const void *ptr) +{ + union { + float32 f; + uint32_t i; + } u; + u.i = ldl_le_p(ptr); + return u.f; +} + +static inline void stfl_le_p(void *ptr, float32 v) +{ + union { + float32 f; + uint32_t i; + } u; + u.f = v; + stl_le_p(ptr, u.i); +} + +static inline float64 ldfq_le_p(const void *ptr) +{ + CPU_DoubleU u; + u.l.lower = ldl_le_p(ptr); + u.l.upper = ldl_le_p(ptr + 4); + return u.d; +} + +static inline void stfq_le_p(void *ptr, float64 v) +{ + CPU_DoubleU u; + u.d = v; + stl_le_p(ptr, u.l.lower); + stl_le_p(ptr + 4, u.l.upper); +} + +#else + +static inline int lduw_le_p(const void *ptr) +{ + return *(uint16_t *)ptr; +} + +static inline int ldsw_le_p(const void *ptr) +{ + return *(int16_t *)ptr; +} + +static inline int ldl_le_p(const void *ptr) +{ + return *(uint32_t *)ptr; +} + +static inline uint64_t ldq_le_p(const void *ptr) +{ + return *(uint64_t *)ptr; +} + +static inline void stw_le_p(void *ptr, int v) +{ + *(uint16_t *)ptr = v; +} + +static inline void stl_le_p(void *ptr, int v) +{ + *(uint32_t *)ptr = v; +} + +static inline void stq_le_p(void *ptr, uint64_t v) +{ + *(uint64_t *)ptr = v; +} + +/* float access */ + +static inline float32 ldfl_le_p(const void *ptr) +{ + return *(float32 *)ptr; +} + +static inline float64 ldfq_le_p(const void *ptr) +{ + return *(float64 *)ptr; +} + +static inline void stfl_le_p(void *ptr, float32 v) +{ + *(float32 *)ptr = v; +} + +static inline void stfq_le_p(void *ptr, float64 v) +{ + *(float64 *)ptr = v; +} +#endif + +#if !defined(HOST_WORDS_BIGENDIAN) || defined(WORDS_ALIGNED) + +static inline int lduw_be_p(const void *ptr) +{ +#if defined(__i386__) + int val; + asm volatile ("movzwl %1, %0\n" + "xchgb %b0, %h0\n" + : "=q" (val) + : "m" (*(uint16_t *)ptr)); + return val; +#else + const uint8_t *b = ptr; + return ((b[0] << 8) | b[1]); +#endif +} + +static inline int ldsw_be_p(const void *ptr) +{ +#if defined(__i386__) + int val; + asm volatile ("movzwl %1, %0\n" + "xchgb %b0, %h0\n" + : "=q" (val) + : "m" (*(uint16_t *)ptr)); + return (int16_t)val; +#else + const uint8_t *b = ptr; + return (int16_t)((b[0] << 8) | b[1]); +#endif +} + +static inline int ldl_be_p(const void *ptr) +{ +#if defined(__i386__) || defined(__x86_64__) + int val; + asm volatile ("movl %1, %0\n" + "bswap %0\n" + : "=r" (val) + : "m" (*(uint32_t *)ptr)); + return val; +#else + const uint8_t *b = ptr; + return (b[0] << 24) | (b[1] << 16) | (b[2] << 8) | b[3]; +#endif +} + +static inline uint64_t ldq_be_p(const void *ptr) +{ + uint32_t a,b; + a = ldl_be_p(ptr); + b = ldl_be_p((uint8_t *)ptr + 4); + return (((uint64_t)a<<32)|b); +} + +static inline void stw_be_p(void *ptr, int v) +{ +#if defined(__i386__) + asm volatile ("xchgb %b0, %h0\n" + "movw %w0, %1\n" + : "=q" (v) + : "m" (*(uint16_t *)ptr), "0" (v)); +#else + uint8_t *d = (uint8_t *) ptr; + d[0] = v >> 8; + d[1] = v; +#endif +} + +static inline void stl_be_p(void *ptr, int v) +{ +#if defined(__i386__) || defined(__x86_64__) + asm volatile ("bswap %0\n" + "movl %0, %1\n" + : "=r" (v) + : "m" (*(uint32_t *)ptr), "0" (v)); +#else + uint8_t *d = (uint8_t *) ptr; + d[0] = v >> 24; + d[1] = v >> 16; + d[2] = v >> 8; + d[3] = v; +#endif +} + +static inline void stq_be_p(void *ptr, uint64_t v) +{ + stl_be_p(ptr, v >> 32); + stl_be_p((uint8_t *)ptr + 4, v); +} + +/* float access */ + +static inline float32 ldfl_be_p(const void *ptr) +{ + union { + float32 f; + uint32_t i; + } u; + u.i = ldl_be_p(ptr); + return u.f; +} + +static inline void stfl_be_p(void *ptr, float32 v) +{ + union { + float32 f; + uint32_t i; + } u; + u.f = v; + stl_be_p(ptr, u.i); +} + +static inline float64 ldfq_be_p(const void *ptr) +{ + CPU_DoubleU u; + u.l.upper = ldl_be_p(ptr); + u.l.lower = ldl_be_p((uint8_t *)ptr + 4); + return u.d; +} + +static inline void stfq_be_p(void *ptr, float64 v) +{ + CPU_DoubleU u; + u.d = v; + stl_be_p(ptr, u.l.upper); + stl_be_p((uint8_t *)ptr + 4, u.l.lower); +} + +#else + +static inline int lduw_be_p(const void *ptr) +{ + return *(uint16_t *)ptr; +} + +static inline int ldsw_be_p(const void *ptr) +{ + return *(int16_t *)ptr; +} + +static inline int ldl_be_p(const void *ptr) +{ + return *(uint32_t *)ptr; +} + +static inline uint64_t ldq_be_p(const void *ptr) +{ + return *(uint64_t *)ptr; +} + +static inline void stw_be_p(void *ptr, int v) +{ + *(uint16_t *)ptr = v; +} + +static inline void stl_be_p(void *ptr, int v) +{ + *(uint32_t *)ptr = v; +} + +static inline void stq_be_p(void *ptr, uint64_t v) +{ + *(uint64_t *)ptr = v; +} + +/* float access */ + +static inline float32 ldfl_be_p(const void *ptr) +{ + return *(float32 *)ptr; +} + +static inline float64 ldfq_be_p(const void *ptr) +{ + return *(float64 *)ptr; +} + +static inline void stfl_be_p(void *ptr, float32 v) +{ + *(float32 *)ptr = v; +} + +static inline void stfq_be_p(void *ptr, float64 v) +{ + *(float64 *)ptr = v; +} + +#endif + +/* target CPU memory access functions */ +#if defined(TARGET_WORDS_BIGENDIAN) +#define lduw_p(p) lduw_be_p(p) +#define ldsw_p(p) ldsw_be_p(p) +#define ldl_p(p) ldl_be_p(p) +#define ldq_p(p) ldq_be_p(p) +#define ldfl_p(p) ldfl_be_p(p) +#define ldfq_p(p) ldfq_be_p(p) +#define stw_p(p, v) stw_be_p(p, v) +#define stl_p(p, v) stl_be_p(p, v) +#define stq_p(p, v) stq_be_p(p, v) +#define stfl_p(p, v) stfl_be_p(p, v) +#define stfq_p(p, v) stfq_be_p(p, v) +#else +#define lduw_p(p) lduw_le_p(p) +#define ldsw_p(p) ldsw_le_p(p) +#define ldl_p(p) ldl_le_p(p) +#define ldq_p(p) ldq_le_p(p) +#define ldfl_p(p) ldfl_le_p(p) +#define ldfq_p(p) ldfq_le_p(p) +#define stw_p(p, v) stw_le_p(p, v) +#define stl_p(p, v) stl_le_p(p, v) +#define stq_p(p, v) stq_le_p(p, v) +#define stfl_p(p, v) stfl_le_p(p, v) +#define stfq_p(p, v) stfq_le_p(p, v) +#endif + +/* MMU memory access macros */ + +#if defined(CONFIG_USER_ONLY) +#include +#include "qemu-types.h" + +/* On some host systems the guest address space is reserved on the host. + * This allows the guest address space to be offset to a convenient location. + */ +#if defined(CONFIG_USE_GUEST_BASE) +extern unsigned long guest_base; +extern int have_guest_base; +extern unsigned long reserved_va; +#define GUEST_BASE guest_base +#define RESERVED_VA reserved_va +#else +#define GUEST_BASE 0ul +#define RESERVED_VA 0ul +#endif + +/* All direct uses of g2h and h2g need to go away for usermode softmmu. */ +#define g2h(x) ((void *)((unsigned long)(x) + GUEST_BASE)) + +#if HOST_LONG_BITS <= TARGET_VIRT_ADDR_SPACE_BITS +#define h2g_valid(x) 1 +#else +#define h2g_valid(x) ({ \ + unsigned long __guest = (unsigned long)(x) - GUEST_BASE; \ + __guest < (1ul << TARGET_VIRT_ADDR_SPACE_BITS); \ +}) +#endif + +#define h2g(x) ({ \ + unsigned long __ret = (unsigned long)(x) - GUEST_BASE; \ + /* Check if given address fits target address space */ \ + assert(h2g_valid(x)); \ + (abi_ulong)__ret; \ +}) + +#define saddr(x) g2h(x) +#define laddr(x) g2h(x) + +#else /* !CONFIG_USER_ONLY */ +/* NOTE: we use double casts if pointers and target_ulong have + different sizes */ +#define saddr(x) (uint8_t *)(long)(x) +#define laddr(x) (uint8_t *)(long)(x) +#endif + +#define ldub_raw(p) ldub_p(laddr((p))) +#define ldsb_raw(p) ldsb_p(laddr((p))) +#define lduw_raw(p) lduw_p(laddr((p))) +#define ldsw_raw(p) ldsw_p(laddr((p))) +#define ldl_raw(p) ldl_p(laddr((p))) +#define ldq_raw(p) ldq_p(laddr((p))) +#define ldfl_raw(p) ldfl_p(laddr((p))) +#define ldfq_raw(p) ldfq_p(laddr((p))) +#define stb_raw(p, v) stb_p(saddr((p)), v) +#define stw_raw(p, v) stw_p(saddr((p)), v) +#define stl_raw(p, v) stl_p(saddr((p)), v) +#define stq_raw(p, v) stq_p(saddr((p)), v) +#define stfl_raw(p, v) stfl_p(saddr((p)), v) +#define stfq_raw(p, v) stfq_p(saddr((p)), v) + + +#if defined(CONFIG_USER_ONLY) + +/* if user mode, no other memory access functions */ +#define ldub(p) ldub_raw(p) +#define ldsb(p) ldsb_raw(p) +#define lduw(p) lduw_raw(p) +#define ldsw(p) ldsw_raw(p) +#define ldl(p) ldl_raw(p) +#define ldq(p) ldq_raw(p) +#define ldfl(p) ldfl_raw(p) +#define ldfq(p) ldfq_raw(p) +#define stb(p, v) stb_raw(p, v) +#define stw(p, v) stw_raw(p, v) +#define stl(p, v) stl_raw(p, v) +#define stq(p, v) stq_raw(p, v) +#define stfl(p, v) stfl_raw(p, v) +#define stfq(p, v) stfq_raw(p, v) + +#define ldub_code(p) ldub_raw(p) +#define ldsb_code(p) ldsb_raw(p) +#define lduw_code(p) lduw_raw(p) +#define ldsw_code(p) ldsw_raw(p) +#define ldl_code(p) ldl_raw(p) +#define ldq_code(p) ldq_raw(p) + +#define ldub_kernel(p) ldub_raw(p) +#define ldsb_kernel(p) ldsb_raw(p) +#define lduw_kernel(p) lduw_raw(p) +#define ldsw_kernel(p) ldsw_raw(p) +#define ldl_kernel(p) ldl_raw(p) +#define ldq_kernel(p) ldq_raw(p) +#define ldfl_kernel(p) ldfl_raw(p) +#define ldfq_kernel(p) ldfq_raw(p) +#define stb_kernel(p, v) stb_raw(p, v) +#define stw_kernel(p, v) stw_raw(p, v) +#define stl_kernel(p, v) stl_raw(p, v) +#define stq_kernel(p, v) stq_raw(p, v) +#define stfl_kernel(p, v) stfl_raw(p, v) +#define stfq_kernel(p, vt) stfq_raw(p, v) + +#endif /* defined(CONFIG_USER_ONLY) */ + +/* page related stuff */ + +#define TARGET_PAGE_SIZE (1 << TARGET_PAGE_BITS) +#define TARGET_PAGE_MASK ~(TARGET_PAGE_SIZE - 1) +#define TARGET_PAGE_ALIGN(addr) (((addr) + TARGET_PAGE_SIZE - 1) & TARGET_PAGE_MASK) + +/* ??? These should be the larger of unsigned long and target_ulong. */ +extern unsigned long qemu_real_host_page_size; +extern unsigned long qemu_host_page_bits; +extern unsigned long qemu_host_page_size; +extern unsigned long qemu_host_page_mask; + +#define HOST_PAGE_ALIGN(addr) (((addr) + qemu_host_page_size - 1) & qemu_host_page_mask) + +/* same as PROT_xxx */ +#define PAGE_READ 0x0001 +#define PAGE_WRITE 0x0002 +#define PAGE_EXEC 0x0004 +#define PAGE_BITS (PAGE_READ | PAGE_WRITE | PAGE_EXEC) +#define PAGE_VALID 0x0008 +/* original state of the write flag (used when tracking self-modifying + code */ +#define PAGE_WRITE_ORG 0x0010 +#if defined(CONFIG_BSD) && defined(CONFIG_USER_ONLY) +/* FIXME: Code that sets/uses this is broken and needs to go away. */ +#define PAGE_RESERVED 0x0020 +#endif + +#if defined(CONFIG_USER_ONLY) +void page_dump(FILE *f); + +typedef int (*walk_memory_regions_fn)(void *, abi_ulong, + abi_ulong, unsigned long); +int walk_memory_regions(void *, walk_memory_regions_fn); + +int page_get_flags(target_ulong address); +void page_set_flags(target_ulong start, target_ulong end, int flags); +int page_check_range(target_ulong start, target_ulong len, int flags); +#endif + +CPUState *cpu_copy(CPUState *env); +CPUState *qemu_get_cpu(int cpu); + +#define CPU_DUMP_CODE 0x00010000 + +void cpu_dump_state(CPUState *env, FILE *f, fprintf_function cpu_fprintf, + int flags); +void cpu_dump_statistics(CPUState *env, FILE *f, fprintf_function cpu_fprintf, + int flags); + +void QEMU_NORETURN cpu_abort(CPUState *env, const char *fmt, ...) + GCC_FMT_ATTR(2, 3); +extern CPUState *first_cpu; +extern CPUState *cpu_single_env; + +#define CPU_INTERRUPT_HARD 0x02 /* hardware interrupt pending */ +#define CPU_INTERRUPT_EXITTB 0x04 /* exit the current TB (use for x86 a20 case) */ +#define CPU_INTERRUPT_TIMER 0x08 /* internal timer exception pending */ +#define CPU_INTERRUPT_FIQ 0x10 /* Fast interrupt pending. */ +#define CPU_INTERRUPT_HALT 0x20 /* CPU halt wanted */ +#define CPU_INTERRUPT_SMI 0x40 /* (x86 only) SMI interrupt pending */ +#define CPU_INTERRUPT_DEBUG 0x80 /* Debug event occured. */ +#define CPU_INTERRUPT_VIRQ 0x100 /* virtual interrupt pending. */ +#define CPU_INTERRUPT_NMI 0x200 /* NMI pending. */ +#define CPU_INTERRUPT_INIT 0x400 /* INIT pending. */ +#define CPU_INTERRUPT_SIPI 0x800 /* SIPI pending. */ +#define CPU_INTERRUPT_MCE 0x1000 /* (x86 only) MCE pending. */ + +void cpu_interrupt(CPUState *s, int mask); +void cpu_reset_interrupt(CPUState *env, int mask); + +void cpu_exit(CPUState *s); + +int qemu_cpu_has_work(CPUState *env); + +/* Breakpoint/watchpoint flags */ +#define BP_MEM_READ 0x01 +#define BP_MEM_WRITE 0x02 +#define BP_MEM_ACCESS (BP_MEM_READ | BP_MEM_WRITE) +#define BP_STOP_BEFORE_ACCESS 0x04 +#define BP_WATCHPOINT_HIT 0x08 +#define BP_GDB 0x10 +#define BP_CPU 0x20 + +int cpu_breakpoint_insert(CPUState *env, target_ulong pc, int flags, + CPUBreakpoint **breakpoint); +int cpu_breakpoint_remove(CPUState *env, target_ulong pc, int flags); +void cpu_breakpoint_remove_by_ref(CPUState *env, CPUBreakpoint *breakpoint); +void cpu_breakpoint_remove_all(CPUState *env, int mask); +int cpu_watchpoint_insert(CPUState *env, target_ulong addr, target_ulong len, + int flags, CPUWatchpoint **watchpoint); +int cpu_watchpoint_remove(CPUState *env, target_ulong addr, + target_ulong len, int flags); +void cpu_watchpoint_remove_by_ref(CPUState *env, CPUWatchpoint *watchpoint); +void cpu_watchpoint_remove_all(CPUState *env, int mask); + +#define SSTEP_ENABLE 0x1 /* Enable simulated HW single stepping */ +#define SSTEP_NOIRQ 0x2 /* Do not use IRQ while single stepping */ +#define SSTEP_NOTIMER 0x4 /* Do not Timers while single stepping */ + +void cpu_single_step(CPUState *env, int enabled); +void cpu_reset(CPUState *s); +int cpu_is_stopped(CPUState *env); +void run_on_cpu(CPUState *env, void (*func)(void *data), void *data); + +#define CPU_LOG_TB_OUT_ASM (1 << 0) +#define CPU_LOG_TB_IN_ASM (1 << 1) +#define CPU_LOG_TB_OP (1 << 2) +#define CPU_LOG_TB_OP_OPT (1 << 3) +#define CPU_LOG_INT (1 << 4) +#define CPU_LOG_EXEC (1 << 5) +#define CPU_LOG_PCALL (1 << 6) +#define CPU_LOG_IOPORT (1 << 7) +#define CPU_LOG_TB_CPU (1 << 8) +#define CPU_LOG_RESET (1 << 9) + +/* define log items */ +typedef struct CPULogItem { + int mask; + const char *name; + const char *help; +} CPULogItem; + +extern const CPULogItem cpu_log_items[]; + +void cpu_set_log(int log_flags); +void cpu_set_log_filename(const char *filename); +int cpu_str_to_log_mask(const char *str); + +#if !defined(CONFIG_USER_ONLY) + +/* Return the physical page corresponding to a virtual one. Use it + only for debugging because no protection checks are done. Return -1 + if no page found. */ +target_phys_addr_t cpu_get_phys_page_debug(CPUState *env, target_ulong addr); + +/* memory API */ + +extern int phys_ram_fd; +extern ram_addr_t ram_size; + +typedef struct RAMBlock { + uint8_t *host; + ram_addr_t offset; + ram_addr_t length; + char idstr[256]; + QLIST_ENTRY(RAMBlock) next; +#if defined(__linux__) && !defined(TARGET_S390X) + int fd; +#endif +} RAMBlock; + +typedef struct RAMList { + uint8_t *phys_dirty; + QLIST_HEAD(ram, RAMBlock) blocks; +} RAMList; +extern RAMList ram_list; + +extern const char *mem_path; +extern int mem_prealloc; + +/* physical memory access */ + +/* MMIO pages are identified by a combination of an IO device index and + 3 flags. The ROMD code stores the page ram offset in iotlb entry, + so only a limited number of ids are avaiable. */ + +#define IO_MEM_NB_ENTRIES (1 << (TARGET_PAGE_BITS - IO_MEM_SHIFT)) + +/* Flags stored in the low bits of the TLB virtual address. These are + defined so that fast path ram access is all zeros. */ +/* Zero if TLB entry is valid. */ +#define TLB_INVALID_MASK (1 << 3) +/* Set if TLB entry references a clean RAM page. The iotlb entry will + contain the page physical address. */ +#define TLB_NOTDIRTY (1 << 4) +/* Set if TLB entry is an IO callback. */ +#define TLB_MMIO (1 << 5) + +#define VGA_DIRTY_FLAG 0x01 +#define CODE_DIRTY_FLAG 0x02 +#define MIGRATION_DIRTY_FLAG 0x08 + +/* read dirty bit (return 0 or 1) */ +static inline int cpu_physical_memory_is_dirty(ram_addr_t addr) +{ + return ram_list.phys_dirty[addr >> TARGET_PAGE_BITS] == 0xff; +} + +static inline int cpu_physical_memory_get_dirty_flags(ram_addr_t addr) +{ + return ram_list.phys_dirty[addr >> TARGET_PAGE_BITS]; +} + +static inline int cpu_physical_memory_get_dirty(ram_addr_t addr, + int dirty_flags) +{ + return ram_list.phys_dirty[addr >> TARGET_PAGE_BITS] & dirty_flags; +} + +static inline void cpu_physical_memory_set_dirty(ram_addr_t addr) +{ + ram_list.phys_dirty[addr >> TARGET_PAGE_BITS] = 0xff; +} + +static inline int cpu_physical_memory_set_dirty_flags(ram_addr_t addr, + int dirty_flags) +{ + return ram_list.phys_dirty[addr >> TARGET_PAGE_BITS] |= dirty_flags; +} + +static inline void cpu_physical_memory_mask_dirty_range(ram_addr_t start, + int length, + int dirty_flags) +{ + int i, mask, len; + uint8_t *p; + + len = length >> TARGET_PAGE_BITS; + mask = ~dirty_flags; + p = ram_list.phys_dirty + (start >> TARGET_PAGE_BITS); + for (i = 0; i < len; i++) { + p[i] &= mask; + } +} + +void cpu_physical_memory_reset_dirty(ram_addr_t start, ram_addr_t end, + int dirty_flags); +void cpu_tlb_update_dirty(CPUState *env); + +int cpu_physical_memory_set_dirty_tracking(int enable); + +int cpu_physical_memory_get_dirty_tracking(void); + +int cpu_physical_sync_dirty_bitmap(target_phys_addr_t start_addr, + target_phys_addr_t end_addr); + +void dump_exec_info(FILE *f, fprintf_function cpu_fprintf); +#endif /* !CONFIG_USER_ONLY */ + +int cpu_memory_rw_debug(CPUState *env, target_ulong addr, + uint8_t *buf, int len, int is_write); + +void cpu_inject_x86_mce(CPUState *cenv, int bank, uint64_t status, + uint64_t mcg_status, uint64_t addr, uint64_t misc, + int broadcast); + +#endif /* CPU_ALL_H */ diff --git a/cpu-common.h b/cpu-common.h new file mode 100644 index 0000000..6d4a898 --- /dev/null +++ b/cpu-common.h @@ -0,0 +1,143 @@ +#ifndef CPU_COMMON_H +#define CPU_COMMON_H 1 + +/* CPU interfaces that are target indpendent. */ + +#if defined(__arm__) || defined(__sparc__) || defined(__mips__) || defined(__hppa__) || defined(__ia64__) +#define WORDS_ALIGNED +#endif + +#ifdef TARGET_PHYS_ADDR_BITS +#include "targphys.h" +#endif + +#ifndef NEED_CPU_H +#include "poison.h" +#endif + +#include "bswap.h" +#include "qemu-queue.h" + +#if !defined(CONFIG_USER_ONLY) + +enum device_endian { + DEVICE_NATIVE_ENDIAN, + DEVICE_BIG_ENDIAN, + DEVICE_LITTLE_ENDIAN, +}; + +/* address in the RAM (different from a physical address) */ +typedef unsigned long ram_addr_t; + +/* memory API */ + +typedef void CPUWriteMemoryFunc(void *opaque, target_phys_addr_t addr, uint32_t value); +typedef uint32_t CPUReadMemoryFunc(void *opaque, target_phys_addr_t addr); + +void cpu_register_physical_memory_offset(target_phys_addr_t start_addr, + ram_addr_t size, + ram_addr_t phys_offset, + ram_addr_t region_offset); +static inline void cpu_register_physical_memory(target_phys_addr_t start_addr, + ram_addr_t size, + ram_addr_t phys_offset) +{ + cpu_register_physical_memory_offset(start_addr, size, phys_offset, 0); +} + +ram_addr_t cpu_get_physical_page_desc(target_phys_addr_t addr); +ram_addr_t qemu_ram_alloc_from_ptr(DeviceState *dev, const char *name, + ram_addr_t size, void *host); +ram_addr_t qemu_ram_alloc(DeviceState *dev, const char *name, ram_addr_t size); +void qemu_ram_free(ram_addr_t addr); +/* This should only be used for ram local to a device. */ +void *qemu_get_ram_ptr(ram_addr_t addr); +/* Same but slower, to use for migration, where the order of + * RAMBlocks must not change. */ +void *qemu_safe_ram_ptr(ram_addr_t addr); +/* This should not be used by devices. */ +int qemu_ram_addr_from_host(void *ptr, ram_addr_t *ram_addr); +ram_addr_t qemu_ram_addr_from_host_nofail(void *ptr); + +int cpu_register_io_memory(CPUReadMemoryFunc * const *mem_read, + CPUWriteMemoryFunc * const *mem_write, + void *opaque, enum device_endian endian); +void cpu_unregister_io_memory(int table_address); + +void cpu_physical_memory_rw(target_phys_addr_t addr, uint8_t *buf, + int len, int is_write); +static inline void cpu_physical_memory_read(target_phys_addr_t addr, + uint8_t *buf, int len) +{ + cpu_physical_memory_rw(addr, buf, len, 0); +} +static inline void cpu_physical_memory_write(target_phys_addr_t addr, + const uint8_t *buf, int len) +{ + cpu_physical_memory_rw(addr, (uint8_t *)buf, len, 1); +} +void *cpu_physical_memory_map(target_phys_addr_t addr, + target_phys_addr_t *plen, + int is_write); +void cpu_physical_memory_unmap(void *buffer, target_phys_addr_t len, + int is_write, target_phys_addr_t access_len); +void *cpu_register_map_client(void *opaque, void (*callback)(void *opaque)); +void cpu_unregister_map_client(void *cookie); + +struct CPUPhysMemoryClient; +typedef struct CPUPhysMemoryClient CPUPhysMemoryClient; +struct CPUPhysMemoryClient { + void (*set_memory)(struct CPUPhysMemoryClient *client, + target_phys_addr_t start_addr, + ram_addr_t size, + ram_addr_t phys_offset); + int (*sync_dirty_bitmap)(struct CPUPhysMemoryClient *client, + target_phys_addr_t start_addr, + target_phys_addr_t end_addr); + int (*migration_log)(struct CPUPhysMemoryClient *client, + int enable); + QLIST_ENTRY(CPUPhysMemoryClient) list; +}; + +void cpu_register_phys_memory_client(CPUPhysMemoryClient *); +void cpu_unregister_phys_memory_client(CPUPhysMemoryClient *); + +/* Coalesced MMIO regions are areas where write operations can be reordered. + * This usually implies that write operations are side-effect free. This allows + * batching which can make a major impact on performance when using + * virtualization. + */ +void qemu_register_coalesced_mmio(target_phys_addr_t addr, ram_addr_t size); + +void qemu_unregister_coalesced_mmio(target_phys_addr_t addr, ram_addr_t size); + +void qemu_flush_coalesced_mmio_buffer(void); + +uint32_t ldub_phys(target_phys_addr_t addr); +uint32_t lduw_phys(target_phys_addr_t addr); +uint32_t ldl_phys(target_phys_addr_t addr); +uint64_t ldq_phys(target_phys_addr_t addr); +void stl_phys_notdirty(target_phys_addr_t addr, uint32_t val); +void stq_phys_notdirty(target_phys_addr_t addr, uint64_t val); +void stb_phys(target_phys_addr_t addr, uint32_t val); +void stw_phys(target_phys_addr_t addr, uint32_t val); +void stl_phys(target_phys_addr_t addr, uint32_t val); +void stq_phys(target_phys_addr_t addr, uint64_t val); + +void cpu_physical_memory_write_rom(target_phys_addr_t addr, + const uint8_t *buf, int len); + +#define IO_MEM_SHIFT 3 + +#define IO_MEM_RAM (0 << IO_MEM_SHIFT) /* hardcoded offset */ +#define IO_MEM_ROM (1 << IO_MEM_SHIFT) /* hardcoded offset */ +#define IO_MEM_UNASSIGNED (2 << IO_MEM_SHIFT) +#define IO_MEM_NOTDIRTY (3 << IO_MEM_SHIFT) + +/* Acts like a ROM when read and like a device when written. */ +#define IO_MEM_ROMD (1) +#define IO_MEM_SUBPAGE (2) + +#endif + +#endif /* !CPU_COMMON_H */ diff --git a/cpu-defs.h b/cpu-defs.h new file mode 100644 index 0000000..8d4bf86 --- /dev/null +++ b/cpu-defs.h @@ -0,0 +1,215 @@ +/* + * common defines for all CPUs + * + * Copyright (c) 2003 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 . + */ +#ifndef CPU_DEFS_H +#define CPU_DEFS_H + +#ifndef NEED_CPU_H +#error cpu.h included from common code +#endif + +#include "config.h" +#include +#include +#include +#include "osdep.h" +#include "qemu-queue.h" +#include "targphys.h" + +#ifndef TARGET_LONG_BITS +#error TARGET_LONG_BITS must be defined before including this header +#endif + +#define TARGET_LONG_SIZE (TARGET_LONG_BITS / 8) + +/* target_ulong is the type of a virtual address */ +#if TARGET_LONG_SIZE == 4 +typedef int32_t target_long; +typedef uint32_t target_ulong; +#define TARGET_FMT_lx "%08x" +#define TARGET_FMT_ld "%d" +#define TARGET_FMT_lu "%u" +#elif TARGET_LONG_SIZE == 8 +typedef int64_t target_long; +typedef uint64_t target_ulong; +#define TARGET_FMT_lx "%016" PRIx64 +#define TARGET_FMT_ld "%" PRId64 +#define TARGET_FMT_lu "%" PRIu64 +#else +#error TARGET_LONG_SIZE undefined +#endif + +#define HOST_LONG_SIZE (HOST_LONG_BITS / 8) + +#define EXCP_INTERRUPT 0x10000 /* async interruption */ +#define EXCP_HLT 0x10001 /* hlt instruction reached */ +#define EXCP_DEBUG 0x10002 /* cpu stopped after a breakpoint or singlestep */ +#define EXCP_HALTED 0x10003 /* cpu is halted (waiting for external event) */ + +#define TB_JMP_CACHE_BITS 12 +#define TB_JMP_CACHE_SIZE (1 << TB_JMP_CACHE_BITS) + +/* Only the bottom TB_JMP_PAGE_BITS of the jump cache hash bits vary for + addresses on the same page. The top bits are the same. This allows + TLB invalidation to quickly clear a subset of the hash table. */ +#define TB_JMP_PAGE_BITS (TB_JMP_CACHE_BITS / 2) +#define TB_JMP_PAGE_SIZE (1 << TB_JMP_PAGE_BITS) +#define TB_JMP_ADDR_MASK (TB_JMP_PAGE_SIZE - 1) +#define TB_JMP_PAGE_MASK (TB_JMP_CACHE_SIZE - TB_JMP_PAGE_SIZE) + +#if !defined(CONFIG_USER_ONLY) +#define CPU_TLB_BITS 8 +#define CPU_TLB_SIZE (1 << CPU_TLB_BITS) + +#if HOST_LONG_BITS == 32 && TARGET_LONG_BITS == 32 +#define CPU_TLB_ENTRY_BITS 4 +#else +#define CPU_TLB_ENTRY_BITS 5 +#endif + +typedef struct CPUTLBEntry { + /* bit TARGET_LONG_BITS to TARGET_PAGE_BITS : virtual address + bit TARGET_PAGE_BITS-1..4 : Nonzero for accesses that should not + go directly to ram. + bit 3 : indicates that the entry is invalid + bit 2..0 : zero + */ + target_ulong addr_read; + target_ulong addr_write; + target_ulong addr_code; + /* Addend to virtual address to get host address. IO accesses + use the corresponding iotlb value. */ + unsigned long addend; + /* padding to get a power of two size */ + uint8_t dummy[(1 << CPU_TLB_ENTRY_BITS) - + (sizeof(target_ulong) * 3 + + ((-sizeof(target_ulong) * 3) & (sizeof(unsigned long) - 1)) + + sizeof(unsigned long))]; +} CPUTLBEntry; + +extern int CPUTLBEntry_wrong_size[sizeof(CPUTLBEntry) == (1 << CPU_TLB_ENTRY_BITS) ? 1 : -1]; + +#define CPU_COMMON_TLB \ + /* The meaning of the MMU modes is defined in the target code. */ \ + CPUTLBEntry tlb_table[NB_MMU_MODES][CPU_TLB_SIZE]; \ + target_phys_addr_t iotlb[NB_MMU_MODES][CPU_TLB_SIZE]; \ + target_ulong tlb_flush_addr; \ + target_ulong tlb_flush_mask; + +#else + +#define CPU_COMMON_TLB + +#endif + + +#ifdef HOST_WORDS_BIGENDIAN +typedef struct icount_decr_u16 { + uint16_t high; + uint16_t low; +} icount_decr_u16; +#else +typedef struct icount_decr_u16 { + uint16_t low; + uint16_t high; +} icount_decr_u16; +#endif + +struct kvm_run; +struct KVMState; +struct qemu_work_item; + +typedef struct CPUBreakpoint { + target_ulong pc; + int flags; /* BP_* */ + QTAILQ_ENTRY(CPUBreakpoint) entry; +} CPUBreakpoint; + +typedef struct CPUWatchpoint { + target_ulong vaddr; + target_ulong len_mask; + int flags; /* BP_* */ + QTAILQ_ENTRY(CPUWatchpoint) entry; +} CPUWatchpoint; + +#define CPU_TEMP_BUF_NLONGS 128 +#define CPU_COMMON \ + struct TranslationBlock *current_tb; /* currently executing TB */ \ + /* soft mmu support */ \ + /* in order to avoid passing too many arguments to the MMIO \ + helpers, we store some rarely used information in the CPU \ + context) */ \ + unsigned long mem_io_pc; /* host pc at which the memory was \ + accessed */ \ + target_ulong mem_io_vaddr; /* target virtual addr at which the \ + memory was accessed */ \ + uint32_t halted; /* Nonzero if the CPU is in suspend state */ \ + uint32_t interrupt_request; \ + volatile sig_atomic_t exit_request; \ + CPU_COMMON_TLB \ + struct TranslationBlock *tb_jmp_cache[TB_JMP_CACHE_SIZE]; \ + /* buffer for temporaries in the code generator */ \ + long temp_buf[CPU_TEMP_BUF_NLONGS]; \ + \ + int64_t icount_extra; /* Instructions until next timer event. */ \ + /* Number of cycles left, with interrupt flag in high bit. \ + This allows a single read-compare-cbranch-write sequence to test \ + for both decrementer underflow and exceptions. */ \ + union { \ + uint32_t u32; \ + icount_decr_u16 u16; \ + } icount_decr; \ + uint32_t can_do_io; /* nonzero if memory mapped IO is safe. */ \ + \ + /* from this point: preserved by CPU reset */ \ + /* ice debug support */ \ + QTAILQ_HEAD(breakpoints_head, CPUBreakpoint) breakpoints; \ + int singlestep_enabled; \ + \ + QTAILQ_HEAD(watchpoints_head, CPUWatchpoint) watchpoints; \ + CPUWatchpoint *watchpoint_hit; \ + \ + struct GDBRegisterState *gdb_regs; \ + \ + /* Core interrupt code */ \ + jmp_buf jmp_env; \ + int exception_index; \ + \ + CPUState *next_cpu; /* next CPU sharing TB cache */ \ + int cpu_index; /* CPU index (informative) */ \ + uint32_t host_tid; /* host thread ID */ \ + int numa_node; /* NUMA node this cpu is belonging to */ \ + int nr_cores; /* number of cores within this CPU package */ \ + int nr_threads;/* number of threads within this CPU */ \ + int running; /* Nonzero if cpu is currently running(usermode). */ \ + /* user data */ \ + void *opaque; \ + \ + uint32_t created; \ + uint32_t stop; /* Stop request */ \ + uint32_t stopped; /* Artificially stopped */ \ + struct QemuThread *thread; \ + struct QemuCond *halt_cond; \ + struct qemu_work_item *queued_work_first, *queued_work_last; \ + const char *cpu_model_str; \ + struct KVMState *kvm_state; \ + struct kvm_run *kvm_run; \ + int kvm_fd; \ + int kvm_vcpu_dirty; + +#endif diff --git a/cpu-exec.c b/cpu-exec.c new file mode 100644 index 0000000..8c9fb8b --- /dev/null +++ b/cpu-exec.c @@ -0,0 +1,1271 @@ +/* + * i386 emulator main execution loop + * + * Copyright (c) 2003-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 . + */ +#include "config.h" +#include "exec.h" +#include "disas.h" +#include "tcg.h" +#include "kvm.h" +#include "qemu-barrier.h" + +#if !defined(CONFIG_SOFTMMU) +#undef EAX +#undef ECX +#undef EDX +#undef EBX +#undef ESP +#undef EBP +#undef ESI +#undef EDI +#undef EIP +#include +#ifdef __linux__ +#include +#endif +#endif + +#if defined(__sparc__) && !defined(CONFIG_SOLARIS) +// Work around ugly bugs in glibc that mangle global register contents +#undef env +#define env cpu_single_env +#endif + +int tb_invalidated_flag; + +//#define CONFIG_DEBUG_EXEC +//#define DEBUG_SIGNAL + +int qemu_cpu_has_work(CPUState *env) +{ + return cpu_has_work(env); +} + +void cpu_loop_exit(void) +{ + env->current_tb = NULL; + longjmp(env->jmp_env, 1); +} + +/* exit the current TB from a signal handler. The host registers are + restored in a state compatible with the CPU emulator + */ +void cpu_resume_from_signal(CPUState *env1, void *puc) +{ +#if !defined(CONFIG_SOFTMMU) +#ifdef __linux__ + struct ucontext *uc = puc; +#elif defined(__OpenBSD__) + struct sigcontext *uc = puc; +#endif +#endif + + env = env1; + + /* XXX: restore cpu registers saved in host registers */ + +#if !defined(CONFIG_SOFTMMU) + if (puc) { + /* XXX: use siglongjmp ? */ +#ifdef __linux__ +#ifdef __ia64 + sigprocmask(SIG_SETMASK, (sigset_t *)&uc->uc_sigmask, NULL); +#else + sigprocmask(SIG_SETMASK, &uc->uc_sigmask, NULL); +#endif +#elif defined(__OpenBSD__) + sigprocmask(SIG_SETMASK, &uc->sc_mask, NULL); +#endif + } +#endif + env->exception_index = -1; + longjmp(env->jmp_env, 1); +} + +/* Execute the code without caching the generated code. An interpreter + could be used if available. */ +static void cpu_exec_nocache(int max_cycles, TranslationBlock *orig_tb) +{ + unsigned long next_tb; + TranslationBlock *tb; + + /* Should never happen. + We only end up here when an existing TB is too long. */ + if (max_cycles > CF_COUNT_MASK) + max_cycles = CF_COUNT_MASK; + + tb = tb_gen_code(env, orig_tb->pc, orig_tb->cs_base, orig_tb->flags, + max_cycles); + env->current_tb = tb; + /* execute the generated code */ + next_tb = tcg_qemu_tb_exec(tb->tc_ptr); + env->current_tb = NULL; + + if ((next_tb & 3) == 2) { + /* Restore PC. This may happen if async event occurs before + the TB starts executing. */ + cpu_pc_from_tb(env, tb); + } + tb_phys_invalidate(tb, -1); + tb_free(tb); +} + +static TranslationBlock *tb_find_slow(target_ulong pc, + target_ulong cs_base, + uint64_t flags) +{ + TranslationBlock *tb, **ptb1; + unsigned int h; + tb_page_addr_t phys_pc, phys_page1, phys_page2; + target_ulong virt_page2; + + tb_invalidated_flag = 0; + + /* find translated block using physical mappings */ + phys_pc = get_page_addr_code(env, pc); + phys_page1 = phys_pc & TARGET_PAGE_MASK; + phys_page2 = -1; + h = tb_phys_hash_func(phys_pc); + ptb1 = &tb_phys_hash[h]; + for(;;) { + tb = *ptb1; + if (!tb) + goto not_found; + if (tb->pc == pc && + tb->page_addr[0] == phys_page1 && + tb->cs_base == cs_base && + tb->flags == flags) { + /* check next page if needed */ + if (tb->page_addr[1] != -1) { + virt_page2 = (pc & TARGET_PAGE_MASK) + + TARGET_PAGE_SIZE; + phys_page2 = get_page_addr_code(env, virt_page2); + if (tb->page_addr[1] == phys_page2) + goto found; + } else { + goto found; + } + } + ptb1 = &tb->phys_hash_next; + } + not_found: + /* if no translated code available, then translate it now */ + tb = tb_gen_code(env, pc, cs_base, flags, 0); + + found: + /* Move the last found TB to the head of the list */ + if (likely(*ptb1)) { + *ptb1 = tb->phys_hash_next; + tb->phys_hash_next = tb_phys_hash[h]; + tb_phys_hash[h] = tb; + } + /* we add the TB in the virtual pc hash table */ + env->tb_jmp_cache[tb_jmp_cache_hash_func(pc)] = tb; + return tb; +} + +static inline TranslationBlock *tb_find_fast(void) +{ + TranslationBlock *tb; + target_ulong cs_base, pc; + int flags; + + /* we record a subset of the CPU state. It will + always be the same before a given translated block + is executed. */ + cpu_get_tb_cpu_state(env, &pc, &cs_base, &flags); + tb = env->tb_jmp_cache[tb_jmp_cache_hash_func(pc)]; + if (unlikely(!tb || tb->pc != pc || tb->cs_base != cs_base || + tb->flags != flags)) { + tb = tb_find_slow(pc, cs_base, flags); + } + return tb; +} + +static CPUDebugExcpHandler *debug_excp_handler; + +CPUDebugExcpHandler *cpu_set_debug_excp_handler(CPUDebugExcpHandler *handler) +{ + CPUDebugExcpHandler *old_handler = debug_excp_handler; + + debug_excp_handler = handler; + return old_handler; +} + +static void cpu_handle_debug_exception(CPUState *env) +{ + CPUWatchpoint *wp; + + if (!env->watchpoint_hit) + QTAILQ_FOREACH(wp, &env->watchpoints, entry) + wp->flags &= ~BP_WATCHPOINT_HIT; + + if (debug_excp_handler) + debug_excp_handler(env); +} + +/* main execution loop */ + +volatile sig_atomic_t exit_request; + +int cpu_exec(CPUState *env1) +{ + volatile host_reg_t saved_env_reg; + int ret, interrupt_request; + TranslationBlock *tb; + uint8_t *tc_ptr; + unsigned long next_tb; + + if (cpu_halted(env1) == EXCP_HALTED) + return EXCP_HALTED; + + cpu_single_env = env1; + + /* the access to env below is actually saving the global register's + value, so that files not including target-xyz/exec.h are free to + use it. */ + QEMU_BUILD_BUG_ON (sizeof (saved_env_reg) != sizeof (env)); + saved_env_reg = (host_reg_t) env; + barrier(); + env = env1; + + if (unlikely(exit_request)) { + env->exit_request = 1; + } + +#if defined(TARGET_I386) + if (!kvm_enabled()) { + /* put eflags in CPU temporary format */ + CC_SRC = env->eflags & (CC_O | CC_S | CC_Z | CC_A | CC_P | CC_C); + DF = 1 - (2 * ((env->eflags >> 10) & 1)); + CC_OP = CC_OP_EFLAGS; + env->eflags &= ~(DF_MASK | CC_O | CC_S | CC_Z | CC_A | CC_P | CC_C); + } +#elif defined(TARGET_SPARC) +#elif defined(TARGET_M68K) + env->cc_op = CC_OP_FLAGS; + env->cc_dest = env->sr & 0xf; + env->cc_x = (env->sr >> 4) & 1; +#elif defined(TARGET_ALPHA) +#elif defined(TARGET_ARM) +#elif defined(TARGET_PPC) +#elif defined(TARGET_MICROBLAZE) +#elif defined(TARGET_MIPS) +#elif defined(TARGET_SH4) +#elif defined(TARGET_CRIS) +#elif defined(TARGET_S390X) + /* XXXXX */ +#else +#error unsupported target CPU +#endif + env->exception_index = -1; + + /* prepare setjmp context for exception handling */ + for(;;) { + if (setjmp(env->jmp_env) == 0) { +#if defined(__sparc__) && !defined(CONFIG_SOLARIS) +#undef env + env = cpu_single_env; +#define env cpu_single_env +#endif + /* if an exception is pending, we execute it here */ + if (env->exception_index >= 0) { + if (env->exception_index >= EXCP_INTERRUPT) { + /* exit request from the cpu execution loop */ + ret = env->exception_index; + if (ret == EXCP_DEBUG) + cpu_handle_debug_exception(env); + break; + } else { +#if defined(CONFIG_USER_ONLY) + /* if user mode only, we simulate a fake exception + which will be handled outside the cpu execution + loop */ +#if defined(TARGET_I386) + do_interrupt_user(env->exception_index, + env->exception_is_int, + env->error_code, + env->exception_next_eip); + /* successfully delivered */ + env->old_exception = -1; +#endif + ret = env->exception_index; + break; +#else +#if defined(TARGET_I386) + /* simulate a real cpu exception. On i386, it can + trigger new exceptions, but we do not handle + double or triple faults yet. */ + do_interrupt(env->exception_index, + env->exception_is_int, + env->error_code, + env->exception_next_eip, 0); + /* successfully delivered */ + env->old_exception = -1; +#elif defined(TARGET_PPC) + do_interrupt(env); +#elif defined(TARGET_MICROBLAZE) + do_interrupt(env); +#elif defined(TARGET_MIPS) + do_interrupt(env); +#elif defined(TARGET_SPARC) + do_interrupt(env); +#elif defined(TARGET_ARM) + do_interrupt(env); +#elif defined(TARGET_SH4) + do_interrupt(env); +#elif defined(TARGET_ALPHA) + do_interrupt(env); +#elif defined(TARGET_CRIS) + do_interrupt(env); +#elif defined(TARGET_M68K) + do_interrupt(0); +#endif + env->exception_index = -1; +#endif + } + } + + if (kvm_enabled()) { + kvm_cpu_exec(env); + longjmp(env->jmp_env, 1); + } + + next_tb = 0; /* force lookup of first TB */ + for(;;) { + interrupt_request = env->interrupt_request; + if (unlikely(interrupt_request)) { + if (unlikely(env->singlestep_enabled & SSTEP_NOIRQ)) { + /* Mask out external interrupts for this step. */ + interrupt_request &= ~(CPU_INTERRUPT_HARD | + CPU_INTERRUPT_FIQ | + CPU_INTERRUPT_SMI | + CPU_INTERRUPT_NMI); + } + if (interrupt_request & CPU_INTERRUPT_DEBUG) { + env->interrupt_request &= ~CPU_INTERRUPT_DEBUG; + env->exception_index = EXCP_DEBUG; + cpu_loop_exit(); + } +#if defined(TARGET_ARM) || defined(TARGET_SPARC) || defined(TARGET_MIPS) || \ + defined(TARGET_PPC) || defined(TARGET_ALPHA) || defined(TARGET_CRIS) || \ + defined(TARGET_MICROBLAZE) + if (interrupt_request & CPU_INTERRUPT_HALT) { + env->interrupt_request &= ~CPU_INTERRUPT_HALT; + env->halted = 1; + env->exception_index = EXCP_HLT; + cpu_loop_exit(); + } +#endif +#if defined(TARGET_I386) + if (interrupt_request & CPU_INTERRUPT_INIT) { + svm_check_intercept(SVM_EXIT_INIT); + do_cpu_init(env); + env->exception_index = EXCP_HALTED; + cpu_loop_exit(); + } else if (interrupt_request & CPU_INTERRUPT_SIPI) { + do_cpu_sipi(env); + } else if (env->hflags2 & HF2_GIF_MASK) { + if ((interrupt_request & CPU_INTERRUPT_SMI) && + !(env->hflags & HF_SMM_MASK)) { + svm_check_intercept(SVM_EXIT_SMI); + env->interrupt_request &= ~CPU_INTERRUPT_SMI; + do_smm_enter(); + next_tb = 0; + } else if ((interrupt_request & CPU_INTERRUPT_NMI) && + !(env->hflags2 & HF2_NMI_MASK)) { + env->interrupt_request &= ~CPU_INTERRUPT_NMI; + env->hflags2 |= HF2_NMI_MASK; + do_interrupt(EXCP02_NMI, 0, 0, 0, 1); + next_tb = 0; + } else if (interrupt_request & CPU_INTERRUPT_MCE) { + env->interrupt_request &= ~CPU_INTERRUPT_MCE; + do_interrupt(EXCP12_MCHK, 0, 0, 0, 0); + next_tb = 0; + } else if ((interrupt_request & CPU_INTERRUPT_HARD) && + (((env->hflags2 & HF2_VINTR_MASK) && + (env->hflags2 & HF2_HIF_MASK)) || + (!(env->hflags2 & HF2_VINTR_MASK) && + (env->eflags & IF_MASK && + !(env->hflags & HF_INHIBIT_IRQ_MASK))))) { + int intno; + svm_check_intercept(SVM_EXIT_INTR); + env->interrupt_request &= ~(CPU_INTERRUPT_HARD | CPU_INTERRUPT_VIRQ); + intno = cpu_get_pic_interrupt(env); + qemu_log_mask(CPU_LOG_TB_IN_ASM, "Servicing hardware INT=0x%02x\n", intno); +#if defined(__sparc__) && !defined(CONFIG_SOLARIS) +#undef env + env = cpu_single_env; +#define env cpu_single_env +#endif + do_interrupt(intno, 0, 0, 0, 1); + /* ensure that no TB jump will be modified as + the program flow was changed */ + next_tb = 0; +#if !defined(CONFIG_USER_ONLY) + } else if ((interrupt_request & CPU_INTERRUPT_VIRQ) && + (env->eflags & IF_MASK) && + !(env->hflags & HF_INHIBIT_IRQ_MASK)) { + int intno; + /* FIXME: this should respect TPR */ + svm_check_intercept(SVM_EXIT_VINTR); + intno = ldl_phys(env->vm_vmcb + offsetof(struct vmcb, control.int_vector)); + qemu_log_mask(CPU_LOG_TB_IN_ASM, "Servicing virtual hardware INT=0x%02x\n", intno); + do_interrupt(intno, 0, 0, 0, 1); + env->interrupt_request &= ~CPU_INTERRUPT_VIRQ; + next_tb = 0; +#endif + } + } +#elif defined(TARGET_PPC) +#if 0 + if ((interrupt_request & CPU_INTERRUPT_RESET)) { + cpu_reset(env); + } +#endif + if (interrupt_request & CPU_INTERRUPT_HARD) { + ppc_hw_interrupt(env); + if (env->pending_interrupts == 0) + env->interrupt_request &= ~CPU_INTERRUPT_HARD; + next_tb = 0; + } +#elif defined(TARGET_MICROBLAZE) + if ((interrupt_request & CPU_INTERRUPT_HARD) + && (env->sregs[SR_MSR] & MSR_IE) + && !(env->sregs[SR_MSR] & (MSR_EIP | MSR_BIP)) + && !(env->iflags & (D_FLAG | IMM_FLAG))) { + env->exception_index = EXCP_IRQ; + do_interrupt(env); + next_tb = 0; + } +#elif defined(TARGET_MIPS) + if ((interrupt_request & CPU_INTERRUPT_HARD) && + cpu_mips_hw_interrupts_pending(env)) { + /* Raise it */ + env->exception_index = EXCP_EXT_INTERRUPT; + env->error_code = 0; + do_interrupt(env); + next_tb = 0; + } +#elif defined(TARGET_SPARC) + if (interrupt_request & CPU_INTERRUPT_HARD) { + if (cpu_interrupts_enabled(env) && + env->interrupt_index > 0) { + int pil = env->interrupt_index & 0xf; + int type = env->interrupt_index & 0xf0; + + if (((type == TT_EXTINT) && + cpu_pil_allowed(env, pil)) || + type != TT_EXTINT) { + env->exception_index = env->interrupt_index; + do_interrupt(env); + next_tb = 0; + } + } + } else if (interrupt_request & CPU_INTERRUPT_TIMER) { + //do_interrupt(0, 0, 0, 0, 0); + env->interrupt_request &= ~CPU_INTERRUPT_TIMER; + } +#elif defined(TARGET_ARM) + if (interrupt_request & CPU_INTERRUPT_FIQ + && !(env->uncached_cpsr & CPSR_F)) { + env->exception_index = EXCP_FIQ; + do_interrupt(env); + next_tb = 0; + } + /* ARMv7-M interrupt return works by loading a magic value + into the PC. On real hardware the load causes the + return to occur. The qemu implementation performs the + jump normally, then does the exception return when the + CPU tries to execute code at the magic address. + This will cause the magic PC value to be pushed to + the stack if an interrupt occured at the wrong time. + We avoid this by disabling interrupts when + pc contains a magic address. */ + if (interrupt_request & CPU_INTERRUPT_HARD + && ((IS_M(env) && env->regs[15] < 0xfffffff0) + || !(env->uncached_cpsr & CPSR_I))) { + env->exception_index = EXCP_IRQ; + do_interrupt(env); + next_tb = 0; + } +#elif defined(TARGET_SH4) + if (interrupt_request & CPU_INTERRUPT_HARD) { + do_interrupt(env); + next_tb = 0; + } +#elif defined(TARGET_ALPHA) + if (interrupt_request & CPU_INTERRUPT_HARD) { + do_interrupt(env); + next_tb = 0; + } +#elif defined(TARGET_CRIS) + if (interrupt_request & CPU_INTERRUPT_HARD + && (env->pregs[PR_CCS] & I_FLAG) + && !env->locked_irq) { + env->exception_index = EXCP_IRQ; + do_interrupt(env); + next_tb = 0; + } + if (interrupt_request & CPU_INTERRUPT_NMI + && (env->pregs[PR_CCS] & M_FLAG)) { + env->exception_index = EXCP_NMI; + do_interrupt(env); + next_tb = 0; + } +#elif defined(TARGET_M68K) + if (interrupt_request & CPU_INTERRUPT_HARD + && ((env->sr & SR_I) >> SR_I_SHIFT) + < env->pending_level) { + /* Real hardware gets the interrupt vector via an + IACK cycle at this point. Current emulated + hardware doesn't rely on this, so we + provide/save the vector when the interrupt is + first signalled. */ + env->exception_index = env->pending_vector; + do_interrupt(1); + next_tb = 0; + } +#endif + /* Don't use the cached interupt_request value, + do_interrupt may have updated the EXITTB flag. */ + if (env->interrupt_request & CPU_INTERRUPT_EXITTB) { + env->interrupt_request &= ~CPU_INTERRUPT_EXITTB; + /* ensure that no TB jump will be modified as + the program flow was changed */ + next_tb = 0; + } + } + if (unlikely(env->exit_request)) { + env->exit_request = 0; + env->exception_index = EXCP_INTERRUPT; + cpu_loop_exit(); + } +#if defined(DEBUG_DISAS) || defined(CONFIG_DEBUG_EXEC) + if (qemu_loglevel_mask(CPU_LOG_TB_CPU)) { + /* restore flags in standard format */ +#if defined(TARGET_I386) + env->eflags = env->eflags | helper_cc_compute_all(CC_OP) | (DF & DF_MASK); + log_cpu_state(env, X86_DUMP_CCOP); + env->eflags &= ~(DF_MASK | CC_O | CC_S | CC_Z | CC_A | CC_P | CC_C); +#elif defined(TARGET_M68K) + cpu_m68k_flush_flags(env, env->cc_op); + env->cc_op = CC_OP_FLAGS; + env->sr = (env->sr & 0xffe0) + | env->cc_dest | (env->cc_x << 4); + log_cpu_state(env, 0); +#else + log_cpu_state(env, 0); +#endif + } +#endif /* DEBUG_DISAS || CONFIG_DEBUG_EXEC */ + spin_lock(&tb_lock); + tb = tb_find_fast(); + /* Note: we do it here to avoid a gcc bug on Mac OS X when + doing it in tb_find_slow */ + if (tb_invalidated_flag) { + /* as some TB could have been invalidated because + of memory exceptions while generating the code, we + must recompute the hash index here */ + next_tb = 0; + tb_invalidated_flag = 0; + } +#ifdef CONFIG_DEBUG_EXEC + qemu_log_mask(CPU_LOG_EXEC, "Trace 0x%08lx [" TARGET_FMT_lx "] %s\n", + (long)tb->tc_ptr, tb->pc, + lookup_symbol(tb->pc)); +#endif + /* see if we can patch the calling TB. When the TB + spans two pages, we cannot safely do a direct + jump. */ + if (next_tb != 0 && tb->page_addr[1] == -1) { + tb_add_jump((TranslationBlock *)(next_tb & ~3), next_tb & 3, tb); + } + spin_unlock(&tb_lock); + + /* cpu_interrupt might be called while translating the + TB, but before it is linked into a potentially + infinite loop and becomes env->current_tb. Avoid + starting execution if there is a pending interrupt. */ + env->current_tb = tb; + barrier(); + if (likely(!env->exit_request)) { + tc_ptr = tb->tc_ptr; + /* execute the generated code */ +#if defined(__sparc__) && !defined(CONFIG_SOLARIS) +#undef env + env = cpu_single_env; +#define env cpu_single_env +#endif + next_tb = tcg_qemu_tb_exec(tc_ptr); + if ((next_tb & 3) == 2) { + /* Instruction counter expired. */ + int insns_left; + tb = (TranslationBlock *)(long)(next_tb & ~3); + /* Restore PC. */ + cpu_pc_from_tb(env, tb); + insns_left = env->icount_decr.u32; + if (env->icount_extra && insns_left >= 0) { + /* Refill decrementer and continue execution. */ + env->icount_extra += insns_left; + if (env->icount_extra > 0xffff) { + insns_left = 0xffff; + } else { + insns_left = env->icount_extra; + } + env->icount_extra -= insns_left; + env->icount_decr.u16.low = insns_left; + } else { + if (insns_left > 0) { + /* Execute remaining instructions. */ + cpu_exec_nocache(insns_left, tb); + } + env->exception_index = EXCP_INTERRUPT; + next_tb = 0; + cpu_loop_exit(); + } + } + } + env->current_tb = NULL; + /* reset soft MMU for next block (it can currently + only be set by a memory fault) */ + } /* for(;;) */ + } + } /* for(;;) */ + + +#if defined(TARGET_I386) + /* restore flags in standard format */ + env->eflags = env->eflags | helper_cc_compute_all(CC_OP) | (DF & DF_MASK); +#elif defined(TARGET_ARM) + /* XXX: Save/restore host fpu exception state?. */ +#elif defined(TARGET_SPARC) +#elif defined(TARGET_PPC) +#elif defined(TARGET_M68K) + cpu_m68k_flush_flags(env, env->cc_op); + env->cc_op = CC_OP_FLAGS; + env->sr = (env->sr & 0xffe0) + | env->cc_dest | (env->cc_x << 4); +#elif defined(TARGET_MICROBLAZE) +#elif defined(TARGET_MIPS) +#elif defined(TARGET_SH4) +#elif defined(TARGET_ALPHA) +#elif defined(TARGET_CRIS) +#elif defined(TARGET_S390X) + /* XXXXX */ +#else +#error unsupported target CPU +#endif + + /* restore global registers */ + barrier(); + env = (void *) saved_env_reg; + + /* fail safe : never use cpu_single_env outside cpu_exec() */ + cpu_single_env = NULL; + return ret; +} + +/* must only be called from the generated code as an exception can be + generated */ +void tb_invalidate_page_range(target_ulong start, target_ulong end) +{ + /* XXX: cannot enable it yet because it yields to MMU exception + where NIP != read address on PowerPC */ +#if 0 + target_ulong phys_addr; + phys_addr = get_phys_addr_code(env, start); + tb_invalidate_phys_page_range(phys_addr, phys_addr + end - start, 0); +#endif +} + +#if defined(TARGET_I386) && defined(CONFIG_USER_ONLY) + +void cpu_x86_load_seg(CPUX86State *s, int seg_reg, int selector) +{ + CPUX86State *saved_env; + + saved_env = env; + env = s; + if (!(env->cr[0] & CR0_PE_MASK) || (env->eflags & VM_MASK)) { + selector &= 0xffff; + cpu_x86_load_seg_cache(env, seg_reg, selector, + (selector << 4), 0xffff, 0); + } else { + helper_load_seg(seg_reg, selector); + } + env = saved_env; +} + +void cpu_x86_fsave(CPUX86State *s, target_ulong ptr, int data32) +{ + CPUX86State *saved_env; + + saved_env = env; + env = s; + + helper_fsave(ptr, data32); + + env = saved_env; +} + +void cpu_x86_frstor(CPUX86State *s, target_ulong ptr, int data32) +{ + CPUX86State *saved_env; + + saved_env = env; + env = s; + + helper_frstor(ptr, data32); + + env = saved_env; +} + +#endif /* TARGET_I386 */ + +#if !defined(CONFIG_SOFTMMU) + +#if defined(TARGET_I386) +#define EXCEPTION_ACTION raise_exception_err(env->exception_index, env->error_code) +#else +#define EXCEPTION_ACTION cpu_loop_exit() +#endif + +/* 'pc' is the host PC at which the exception was raised. 'address' is + the effective address of the memory exception. 'is_write' is 1 if a + write caused the exception and otherwise 0'. 'old_set' is the + signal set which should be restored */ +static inline int handle_cpu_signal(unsigned long pc, unsigned long address, + int is_write, sigset_t *old_set, + void *puc) +{ + TranslationBlock *tb; + int ret; + + if (cpu_single_env) + env = cpu_single_env; /* XXX: find a correct solution for multithread */ +#if defined(DEBUG_SIGNAL) + qemu_printf("qemu: SIGSEGV pc=0x%08lx address=%08lx w=%d oldset=0x%08lx\n", + pc, address, is_write, *(unsigned long *)old_set); +#endif + /* XXX: locking issue */ + if (is_write && page_unprotect(h2g(address), pc, puc)) { + return 1; + } + + /* see if it is an MMU fault */ + ret = cpu_handle_mmu_fault(env, address, is_write, MMU_USER_IDX, 0); + if (ret < 0) + return 0; /* not an MMU fault */ + if (ret == 0) + return 1; /* the MMU fault was handled without causing real CPU fault */ + /* now we have a real cpu fault */ + tb = tb_find_pc(pc); + if (tb) { + /* the PC is inside the translated code. It means that we have + a virtual CPU fault */ + cpu_restore_state(tb, env, pc, puc); + } + + /* we restore the process signal mask as the sigreturn should + do it (XXX: use sigsetjmp) */ + sigprocmask(SIG_SETMASK, old_set, NULL); + EXCEPTION_ACTION; + + /* never comes here */ + return 1; +} + +#if defined(__i386__) + +#if defined(__APPLE__) +# include + +# define EIP_sig(context) (*((unsigned long*)&(context)->uc_mcontext->ss.eip)) +# define TRAP_sig(context) ((context)->uc_mcontext->es.trapno) +# define ERROR_sig(context) ((context)->uc_mcontext->es.err) +# define MASK_sig(context) ((context)->uc_sigmask) +#elif defined (__NetBSD__) +# include + +# define EIP_sig(context) ((context)->uc_mcontext.__gregs[_REG_EIP]) +# define TRAP_sig(context) ((context)->uc_mcontext.__gregs[_REG_TRAPNO]) +# define ERROR_sig(context) ((context)->uc_mcontext.__gregs[_REG_ERR]) +# define MASK_sig(context) ((context)->uc_sigmask) +#elif defined (__FreeBSD__) || defined(__DragonFly__) +# include + +# define EIP_sig(context) (*((unsigned long*)&(context)->uc_mcontext.mc_eip)) +# define TRAP_sig(context) ((context)->uc_mcontext.mc_trapno) +# define ERROR_sig(context) ((context)->uc_mcontext.mc_err) +# define MASK_sig(context) ((context)->uc_sigmask) +#elif defined(__OpenBSD__) +# define EIP_sig(context) ((context)->sc_eip) +# define TRAP_sig(context) ((context)->sc_trapno) +# define ERROR_sig(context) ((context)->sc_err) +# define MASK_sig(context) ((context)->sc_mask) +#else +# define EIP_sig(context) ((context)->uc_mcontext.gregs[REG_EIP]) +# define TRAP_sig(context) ((context)->uc_mcontext.gregs[REG_TRAPNO]) +# define ERROR_sig(context) ((context)->uc_mcontext.gregs[REG_ERR]) +# define MASK_sig(context) ((context)->uc_sigmask) +#endif + +int cpu_signal_handler(int host_signum, void *pinfo, + void *puc) +{ + siginfo_t *info = pinfo; +#if defined(__NetBSD__) || defined (__FreeBSD__) || defined(__DragonFly__) + ucontext_t *uc = puc; +#elif defined(__OpenBSD__) + struct sigcontext *uc = puc; +#else + struct ucontext *uc = puc; +#endif + unsigned long pc; + int trapno; + +#ifndef REG_EIP +/* for glibc 2.1 */ +#define REG_EIP EIP +#define REG_ERR ERR +#define REG_TRAPNO TRAPNO +#endif + pc = EIP_sig(uc); + trapno = TRAP_sig(uc); + return handle_cpu_signal(pc, (unsigned long)info->si_addr, + trapno == 0xe ? + (ERROR_sig(uc) >> 1) & 1 : 0, + &MASK_sig(uc), puc); +} + +#elif defined(__x86_64__) + +#ifdef __NetBSD__ +#define PC_sig(context) _UC_MACHINE_PC(context) +#define TRAP_sig(context) ((context)->uc_mcontext.__gregs[_REG_TRAPNO]) +#define ERROR_sig(context) ((context)->uc_mcontext.__gregs[_REG_ERR]) +#define MASK_sig(context) ((context)->uc_sigmask) +#elif defined(__OpenBSD__) +#define PC_sig(context) ((context)->sc_rip) +#define TRAP_sig(context) ((context)->sc_trapno) +#define ERROR_sig(context) ((context)->sc_err) +#define MASK_sig(context) ((context)->sc_mask) +#elif defined (__FreeBSD__) || defined(__DragonFly__) +#include + +#define PC_sig(context) (*((unsigned long*)&(context)->uc_mcontext.mc_rip)) +#define TRAP_sig(context) ((context)->uc_mcontext.mc_trapno) +#define ERROR_sig(context) ((context)->uc_mcontext.mc_err) +#define MASK_sig(context) ((context)->uc_sigmask) +#else +#define PC_sig(context) ((context)->uc_mcontext.gregs[REG_RIP]) +#define TRAP_sig(context) ((context)->uc_mcontext.gregs[REG_TRAPNO]) +#define ERROR_sig(context) ((context)->uc_mcontext.gregs[REG_ERR]) +#define MASK_sig(context) ((context)->uc_sigmask) +#endif + +int cpu_signal_handler(int host_signum, void *pinfo, + void *puc) +{ + siginfo_t *info = pinfo; + unsigned long pc; +#if defined(__NetBSD__) || defined (__FreeBSD__) || defined(__DragonFly__) + ucontext_t *uc = puc; +#elif defined(__OpenBSD__) + struct sigcontext *uc = puc; +#else + struct ucontext *uc = puc; +#endif + + pc = PC_sig(uc); + return handle_cpu_signal(pc, (unsigned long)info->si_addr, + TRAP_sig(uc) == 0xe ? + (ERROR_sig(uc) >> 1) & 1 : 0, + &MASK_sig(uc), puc); +} + +#elif defined(_ARCH_PPC) + +/*********************************************************************** + * signal context platform-specific definitions + * From Wine + */ +#ifdef linux +/* All Registers access - only for local access */ +# define REG_sig(reg_name, context) ((context)->uc_mcontext.regs->reg_name) +/* Gpr Registers access */ +# define GPR_sig(reg_num, context) REG_sig(gpr[reg_num], context) +# define IAR_sig(context) REG_sig(nip, context) /* Program counter */ +# define MSR_sig(context) REG_sig(msr, context) /* Machine State Register (Supervisor) */ +# define CTR_sig(context) REG_sig(ctr, context) /* Count register */ +# define XER_sig(context) REG_sig(xer, context) /* User's integer exception register */ +# define LR_sig(context) REG_sig(link, context) /* Link register */ +# define CR_sig(context) REG_sig(ccr, context) /* Condition register */ +/* Float Registers access */ +# define FLOAT_sig(reg_num, context) (((double*)((char*)((context)->uc_mcontext.regs+48*4)))[reg_num]) +# define FPSCR_sig(context) (*(int*)((char*)((context)->uc_mcontext.regs+(48+32*2)*4))) +/* Exception Registers access */ +# define DAR_sig(context) REG_sig(dar, context) +# define DSISR_sig(context) REG_sig(dsisr, context) +# define TRAP_sig(context) REG_sig(trap, context) +#endif /* linux */ + +#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) +#include +# define IAR_sig(context) ((context)->uc_mcontext.mc_srr0) +# define MSR_sig(context) ((context)->uc_mcontext.mc_srr1) +# define CTR_sig(context) ((context)->uc_mcontext.mc_ctr) +# define XER_sig(context) ((context)->uc_mcontext.mc_xer) +# define LR_sig(context) ((context)->uc_mcontext.mc_lr) +# define CR_sig(context) ((context)->uc_mcontext.mc_cr) +/* Exception Registers access */ +# define DAR_sig(context) ((context)->uc_mcontext.mc_dar) +# define DSISR_sig(context) ((context)->uc_mcontext.mc_dsisr) +# define TRAP_sig(context) ((context)->uc_mcontext.mc_exc) +#endif /* __FreeBSD__|| __FreeBSD_kernel__ */ + +#ifdef __APPLE__ +# include +typedef struct ucontext SIGCONTEXT; +/* All Registers access - only for local access */ +# define REG_sig(reg_name, context) ((context)->uc_mcontext->ss.reg_name) +# define FLOATREG_sig(reg_name, context) ((context)->uc_mcontext->fs.reg_name) +# define EXCEPREG_sig(reg_name, context) ((context)->uc_mcontext->es.reg_name) +# define VECREG_sig(reg_name, context) ((context)->uc_mcontext->vs.reg_name) +/* Gpr Registers access */ +# define GPR_sig(reg_num, context) REG_sig(r##reg_num, context) +# define IAR_sig(context) REG_sig(srr0, context) /* Program counter */ +# define MSR_sig(context) REG_sig(srr1, context) /* Machine State Register (Supervisor) */ +# define CTR_sig(context) REG_sig(ctr, context) +# define XER_sig(context) REG_sig(xer, context) /* Link register */ +# define LR_sig(context) REG_sig(lr, context) /* User's integer exception register */ +# define CR_sig(context) REG_sig(cr, context) /* Condition register */ +/* Float Registers access */ +# define FLOAT_sig(reg_num, context) FLOATREG_sig(fpregs[reg_num], context) +# define FPSCR_sig(context) ((double)FLOATREG_sig(fpscr, context)) +/* Exception Registers access */ +# define DAR_sig(context) EXCEPREG_sig(dar, context) /* Fault registers for coredump */ +# define DSISR_sig(context) EXCEPREG_sig(dsisr, context) +# define TRAP_sig(context) EXCEPREG_sig(exception, context) /* number of powerpc exception taken */ +#endif /* __APPLE__ */ + +int cpu_signal_handler(int host_signum, void *pinfo, + void *puc) +{ + siginfo_t *info = pinfo; +#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) + ucontext_t *uc = puc; +#else + struct ucontext *uc = puc; +#endif + unsigned long pc; + int is_write; + + pc = IAR_sig(uc); + is_write = 0; +#if 0 + /* ppc 4xx case */ + if (DSISR_sig(uc) & 0x00800000) + is_write = 1; +#else + if (TRAP_sig(uc) != 0x400 && (DSISR_sig(uc) & 0x02000000)) + is_write = 1; +#endif + return handle_cpu_signal(pc, (unsigned long)info->si_addr, + is_write, &uc->uc_sigmask, puc); +} + +#elif defined(__alpha__) + +int cpu_signal_handler(int host_signum, void *pinfo, + void *puc) +{ + siginfo_t *info = pinfo; + struct ucontext *uc = puc; + uint32_t *pc = uc->uc_mcontext.sc_pc; + uint32_t insn = *pc; + int is_write = 0; + + /* XXX: need kernel patch to get write flag faster */ + switch (insn >> 26) { + case 0x0d: // stw + case 0x0e: // stb + case 0x0f: // stq_u + case 0x24: // stf + case 0x25: // stg + case 0x26: // sts + case 0x27: // stt + case 0x2c: // stl + case 0x2d: // stq + case 0x2e: // stl_c + case 0x2f: // stq_c + is_write = 1; + } + + return handle_cpu_signal(pc, (unsigned long)info->si_addr, + is_write, &uc->uc_sigmask, puc); +} +#elif defined(__sparc__) + +int cpu_signal_handler(int host_signum, void *pinfo, + void *puc) +{ + siginfo_t *info = pinfo; + int is_write; + uint32_t insn; +#if !defined(__arch64__) || defined(CONFIG_SOLARIS) + uint32_t *regs = (uint32_t *)(info + 1); + void *sigmask = (regs + 20); + /* XXX: is there a standard glibc define ? */ + unsigned long pc = regs[1]; +#else +#ifdef __linux__ + struct sigcontext *sc = puc; + unsigned long pc = sc->sigc_regs.tpc; + void *sigmask = (void *)sc->sigc_mask; +#elif defined(__OpenBSD__) + struct sigcontext *uc = puc; + unsigned long pc = uc->sc_pc; + void *sigmask = (void *)(long)uc->sc_mask; +#endif +#endif + + /* XXX: need kernel patch to get write flag faster */ + is_write = 0; + insn = *(uint32_t *)pc; + if ((insn >> 30) == 3) { + switch((insn >> 19) & 0x3f) { + case 0x05: // stb + case 0x15: // stba + case 0x06: // sth + case 0x16: // stha + case 0x04: // st + case 0x14: // sta + case 0x07: // std + case 0x17: // stda + case 0x0e: // stx + case 0x1e: // stxa + case 0x24: // stf + case 0x34: // stfa + case 0x27: // stdf + case 0x37: // stdfa + case 0x26: // stqf + case 0x36: // stqfa + case 0x25: // stfsr + case 0x3c: // casa + case 0x3e: // casxa + is_write = 1; + break; + } + } + return handle_cpu_signal(pc, (unsigned long)info->si_addr, + is_write, sigmask, NULL); +} + +#elif defined(__arm__) + +int cpu_signal_handler(int host_signum, void *pinfo, + void *puc) +{ + siginfo_t *info = pinfo; + struct ucontext *uc = puc; + unsigned long pc; + int is_write; + +#if (__GLIBC__ < 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ <= 3)) + pc = uc->uc_mcontext.gregs[R15]; +#else + pc = uc->uc_mcontext.arm_pc; +#endif + /* XXX: compute is_write */ + is_write = 0; + return handle_cpu_signal(pc, (unsigned long)info->si_addr, + is_write, + &uc->uc_sigmask, puc); +} + +#elif defined(__mc68000) + +int cpu_signal_handler(int host_signum, void *pinfo, + void *puc) +{ + siginfo_t *info = pinfo; + struct ucontext *uc = puc; + unsigned long pc; + int is_write; + + pc = uc->uc_mcontext.gregs[16]; + /* XXX: compute is_write */ + is_write = 0; + return handle_cpu_signal(pc, (unsigned long)info->si_addr, + is_write, + &uc->uc_sigmask, puc); +} + +#elif defined(__ia64) + +#ifndef __ISR_VALID + /* This ought to be in ... */ +# define __ISR_VALID 1 +#endif + +int cpu_signal_handler(int host_signum, void *pinfo, void *puc) +{ + siginfo_t *info = pinfo; + struct ucontext *uc = puc; + unsigned long ip; + int is_write = 0; + + ip = uc->uc_mcontext.sc_ip; + switch (host_signum) { + case SIGILL: + case SIGFPE: + case SIGSEGV: + case SIGBUS: + case SIGTRAP: + if (info->si_code && (info->si_segvflags & __ISR_VALID)) + /* ISR.W (write-access) is bit 33: */ + is_write = (info->si_isr >> 33) & 1; + break; + + default: + break; + } + return handle_cpu_signal(ip, (unsigned long)info->si_addr, + is_write, + (sigset_t *)&uc->uc_sigmask, puc); +} + +#elif defined(__s390__) + +int cpu_signal_handler(int host_signum, void *pinfo, + void *puc) +{ + siginfo_t *info = pinfo; + struct ucontext *uc = puc; + unsigned long pc; + uint16_t *pinsn; + int is_write = 0; + + pc = uc->uc_mcontext.psw.addr; + + /* ??? On linux, the non-rt signal handler has 4 (!) arguments instead + of the normal 2 arguments. The 3rd argument contains the "int_code" + from the hardware which does in fact contain the is_write value. + The rt signal handler, as far as I can tell, does not give this value + at all. Not that we could get to it from here even if it were. */ + /* ??? This is not even close to complete, since it ignores all + of the read-modify-write instructions. */ + pinsn = (uint16_t *)pc; + switch (pinsn[0] >> 8) { + case 0x50: /* ST */ + case 0x42: /* STC */ + case 0x40: /* STH */ + is_write = 1; + break; + case 0xc4: /* RIL format insns */ + switch (pinsn[0] & 0xf) { + case 0xf: /* STRL */ + case 0xb: /* STGRL */ + case 0x7: /* STHRL */ + is_write = 1; + } + break; + case 0xe3: /* RXY format insns */ + switch (pinsn[2] & 0xff) { + case 0x50: /* STY */ + case 0x24: /* STG */ + case 0x72: /* STCY */ + case 0x70: /* STHY */ + case 0x8e: /* STPQ */ + case 0x3f: /* STRVH */ + case 0x3e: /* STRV */ + case 0x2f: /* STRVG */ + is_write = 1; + } + break; + } + return handle_cpu_signal(pc, (unsigned long)info->si_addr, + is_write, &uc->uc_sigmask, puc); +} + +#elif defined(__mips__) + +int cpu_signal_handler(int host_signum, void *pinfo, + void *puc) +{ + siginfo_t *info = pinfo; + struct ucontext *uc = puc; + greg_t pc = uc->uc_mcontext.pc; + int is_write; + + /* XXX: compute is_write */ + is_write = 0; + return handle_cpu_signal(pc, (unsigned long)info->si_addr, + is_write, &uc->uc_sigmask, puc); +} + +#elif defined(__hppa__) + +int cpu_signal_handler(int host_signum, void *pinfo, + void *puc) +{ + struct siginfo *info = pinfo; + struct ucontext *uc = puc; + unsigned long pc = uc->uc_mcontext.sc_iaoq[0]; + uint32_t insn = *(uint32_t *)pc; + int is_write = 0; + + /* XXX: need kernel patch to get write flag faster. */ + switch (insn >> 26) { + case 0x1a: /* STW */ + case 0x19: /* STH */ + case 0x18: /* STB */ + case 0x1b: /* STWM */ + is_write = 1; + break; + + case 0x09: /* CSTWX, FSTWX, FSTWS */ + case 0x0b: /* CSTDX, FSTDX, FSTDS */ + /* Distinguish from coprocessor load ... */ + is_write = (insn >> 9) & 1; + break; + + case 0x03: + switch ((insn >> 6) & 15) { + case 0xa: /* STWS */ + case 0x9: /* STHS */ + case 0x8: /* STBS */ + case 0xe: /* STWAS */ + case 0xc: /* STBYS */ + is_write = 1; + } + break; + } + + return handle_cpu_signal(pc, (unsigned long)info->si_addr, + is_write, &uc->uc_sigmask, puc); +} + +#else + +#error host CPU specific signal handler needed + +#endif + +#endif /* !defined(CONFIG_SOFTMMU) */ diff --git a/cpus.c b/cpus.c new file mode 100644 index 0000000..4c9928e --- /dev/null +++ b/cpus.c @@ -0,0 +1,989 @@ +/* + * QEMU System Emulator + * + * Copyright (c) 2003-2008 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. + */ + +/* Needed early for CONFIG_BSD etc. */ +#include "config-host.h" + +#include "monitor.h" +#include "sysemu.h" +#include "gdbstub.h" +#include "dma.h" +#include "kvm.h" +#include "exec-all.h" + +#include "cpus.h" +#include "compatfd.h" +#ifdef CONFIG_LINUX +#include +#endif + +#ifdef SIGRTMIN +#define SIG_IPI (SIGRTMIN+4) +#else +#define SIG_IPI SIGUSR1 +#endif + +#ifndef PR_MCE_KILL +#define PR_MCE_KILL 33 +#endif + +static CPUState *next_cpu; + +/***********************************************************/ +void hw_error(const char *fmt, ...) +{ + va_list ap; + CPUState *env; + + va_start(ap, fmt); + fprintf(stderr, "qemu: hardware error: "); + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + for(env = first_cpu; env != NULL; env = env->next_cpu) { + fprintf(stderr, "CPU #%d:\n", env->cpu_index); +#ifdef TARGET_I386 + cpu_dump_state(env, stderr, fprintf, X86_DUMP_FPU); +#else + cpu_dump_state(env, stderr, fprintf, 0); +#endif + } + va_end(ap); + abort(); +} + +void cpu_synchronize_all_states(void) +{ + CPUState *cpu; + + for (cpu = first_cpu; cpu; cpu = cpu->next_cpu) { + cpu_synchronize_state(cpu); + } +} + +void cpu_synchronize_all_post_reset(void) +{ + CPUState *cpu; + + for (cpu = first_cpu; cpu; cpu = cpu->next_cpu) { + cpu_synchronize_post_reset(cpu); + } +} + +void cpu_synchronize_all_post_init(void) +{ + CPUState *cpu; + + for (cpu = first_cpu; cpu; cpu = cpu->next_cpu) { + cpu_synchronize_post_init(cpu); + } +} + +int cpu_is_stopped(CPUState *env) +{ + return !vm_running || env->stopped; +} + +static void do_vm_stop(int reason) +{ + if (vm_running) { + cpu_disable_ticks(); + vm_running = 0; + pause_all_vcpus(); + vm_state_notify(0, reason); + qemu_aio_flush(); + bdrv_flush_all(); + monitor_protocol_event(QEVENT_STOP, NULL); + } +} + +static int cpu_can_run(CPUState *env) +{ + if (env->stop) + return 0; + if (env->stopped || !vm_running) + return 0; + return 1; +} + +static int cpu_has_work(CPUState *env) +{ + if (env->stop) + return 1; + if (env->queued_work_first) + return 1; + if (env->stopped || !vm_running) + return 0; + if (!env->halted) + return 1; + if (qemu_cpu_has_work(env)) + return 1; + return 0; +} + +static int any_cpu_has_work(void) +{ + CPUState *env; + + for (env = first_cpu; env != NULL; env = env->next_cpu) + if (cpu_has_work(env)) + return 1; + return 0; +} + +static void cpu_debug_handler(CPUState *env) +{ + gdb_set_stop_cpu(env); + debug_requested = EXCP_DEBUG; + vm_stop(EXCP_DEBUG); +} + +#ifndef _WIN32 +static int io_thread_fd = -1; + +static void qemu_event_increment(void) +{ + /* Write 8 bytes to be compatible with eventfd. */ + static const uint64_t val = 1; + ssize_t ret; + + if (io_thread_fd == -1) + return; + + do { + ret = write(io_thread_fd, &val, sizeof(val)); + } while (ret < 0 && errno == EINTR); + + /* EAGAIN is fine, a read must be pending. */ + if (ret < 0 && errno != EAGAIN) { + fprintf(stderr, "qemu_event_increment: write() filed: %s\n", + strerror(errno)); + exit (1); + } +} + +static void qemu_event_read(void *opaque) +{ + int fd = (unsigned long)opaque; + ssize_t len; + char buffer[512]; + + /* Drain the notify pipe. For eventfd, only 8 bytes will be read. */ + do { + len = read(fd, buffer, sizeof(buffer)); + } while ((len == -1 && errno == EINTR) || len == sizeof(buffer)); +} + +static int qemu_event_init(void) +{ + int err; + int fds[2]; + + err = qemu_eventfd(fds); + if (err == -1) + return -errno; + + err = fcntl_setfl(fds[0], O_NONBLOCK); + if (err < 0) + goto fail; + + err = fcntl_setfl(fds[1], O_NONBLOCK); + if (err < 0) + goto fail; + + qemu_set_fd_handler2(fds[0], NULL, qemu_event_read, NULL, + (void *)(unsigned long)fds[0]); + + io_thread_fd = fds[1]; + return 0; + +fail: + close(fds[0]); + close(fds[1]); + return err; +} +#else +HANDLE qemu_event_handle; + +static void dummy_event_handler(void *opaque) +{ +} + +static int qemu_event_init(void) +{ + qemu_event_handle = CreateEvent(NULL, FALSE, FALSE, NULL); + if (!qemu_event_handle) { + fprintf(stderr, "Failed CreateEvent: %ld\n", GetLastError()); + return -1; + } + qemu_add_wait_object(qemu_event_handle, dummy_event_handler, NULL); + return 0; +} + +static void qemu_event_increment(void) +{ + if (!SetEvent(qemu_event_handle)) { + fprintf(stderr, "qemu_event_increment: SetEvent failed: %ld\n", + GetLastError()); + exit (1); + } +} +#endif + +#ifndef CONFIG_IOTHREAD +int qemu_init_main_loop(void) +{ + cpu_set_debug_excp_handler(cpu_debug_handler); + + return qemu_event_init(); +} + +void qemu_main_loop_start(void) +{ +} + +void qemu_init_vcpu(void *_env) +{ + CPUState *env = _env; + + env->nr_cores = smp_cores; + env->nr_threads = smp_threads; + if (kvm_enabled()) + kvm_init_vcpu(env); + return; +} + +int qemu_cpu_self(void *env) +{ + return 1; +} + +void run_on_cpu(CPUState *env, void (*func)(void *data), void *data) +{ + func(data); +} + +void resume_all_vcpus(void) +{ +} + +void pause_all_vcpus(void) +{ +} + +void qemu_cpu_kick(void *env) +{ + return; +} + +void qemu_notify_event(void) +{ + CPUState *env = cpu_single_env; + + qemu_event_increment (); + if (env) { + cpu_exit(env); + } + if (next_cpu && env != next_cpu) { + cpu_exit(next_cpu); + } +} + +void qemu_mutex_lock_iothread(void) {} +void qemu_mutex_unlock_iothread(void) {} + +void vm_stop(int reason) +{ + do_vm_stop(reason); +} + +#else /* CONFIG_IOTHREAD */ + +#include "qemu-thread.h" + +QemuMutex qemu_global_mutex; +static QemuMutex qemu_fair_mutex; + +static QemuThread io_thread; + +static QemuThread *tcg_cpu_thread; +static QemuCond *tcg_halt_cond; + +static int qemu_system_ready; +/* cpu creation */ +static QemuCond qemu_cpu_cond; +/* system init */ +static QemuCond qemu_system_cond; +static QemuCond qemu_pause_cond; +static QemuCond qemu_work_cond; + +static void tcg_init_ipi(void); +static void kvm_init_ipi(CPUState *env); +static sigset_t block_io_signals(void); + +/* If we have signalfd, we mask out the signals we want to handle and then + * use signalfd to listen for them. We rely on whatever the current signal + * handler is to dispatch the signals when we receive them. + */ +static void sigfd_handler(void *opaque) +{ + int fd = (unsigned long) opaque; + struct qemu_signalfd_siginfo info; + struct sigaction action; + ssize_t len; + + while (1) { + do { + len = read(fd, &info, sizeof(info)); + } while (len == -1 && errno == EINTR); + + if (len == -1 && errno == EAGAIN) { + break; + } + + if (len != sizeof(info)) { + printf("read from sigfd returned %zd: %m\n", len); + return; + } + + sigaction(info.ssi_signo, NULL, &action); + if ((action.sa_flags & SA_SIGINFO) && action.sa_sigaction) { + action.sa_sigaction(info.ssi_signo, + (siginfo_t *)&info, NULL); + } else if (action.sa_handler) { + action.sa_handler(info.ssi_signo); + } + } +} + +static int qemu_signalfd_init(sigset_t mask) +{ + int sigfd; + + sigfd = qemu_signalfd(&mask); + if (sigfd == -1) { + fprintf(stderr, "failed to create signalfd\n"); + return -errno; + } + + fcntl_setfl(sigfd, O_NONBLOCK); + + qemu_set_fd_handler2(sigfd, NULL, sigfd_handler, NULL, + (void *)(unsigned long) sigfd); + + return 0; +} + +int qemu_init_main_loop(void) +{ + int ret; + sigset_t blocked_signals; + + cpu_set_debug_excp_handler(cpu_debug_handler); + + blocked_signals = block_io_signals(); + + ret = qemu_signalfd_init(blocked_signals); + if (ret) + return ret; + + /* Note eventfd must be drained before signalfd handlers run */ + ret = qemu_event_init(); + if (ret) + return ret; + + qemu_cond_init(&qemu_pause_cond); + qemu_cond_init(&qemu_system_cond); + qemu_mutex_init(&qemu_fair_mutex); + qemu_mutex_init(&qemu_global_mutex); + qemu_mutex_lock(&qemu_global_mutex); + + qemu_thread_self(&io_thread); + + return 0; +} + +void qemu_main_loop_start(void) +{ + qemu_system_ready = 1; + qemu_cond_broadcast(&qemu_system_cond); +} + +void run_on_cpu(CPUState *env, void (*func)(void *data), void *data) +{ + struct qemu_work_item wi; + + if (qemu_cpu_self(env)) { + func(data); + return; + } + + wi.func = func; + wi.data = data; + if (!env->queued_work_first) + env->queued_work_first = &wi; + else + env->queued_work_last->next = &wi; + env->queued_work_last = &wi; + wi.next = NULL; + wi.done = false; + + qemu_cpu_kick(env); + while (!wi.done) { + CPUState *self_env = cpu_single_env; + + qemu_cond_wait(&qemu_work_cond, &qemu_global_mutex); + cpu_single_env = self_env; + } +} + +static void flush_queued_work(CPUState *env) +{ + struct qemu_work_item *wi; + + if (!env->queued_work_first) + return; + + while ((wi = env->queued_work_first)) { + env->queued_work_first = wi->next; + wi->func(wi->data); + wi->done = true; + } + env->queued_work_last = NULL; + qemu_cond_broadcast(&qemu_work_cond); +} + +static void qemu_wait_io_event_common(CPUState *env) +{ + if (env->stop) { + env->stop = 0; + env->stopped = 1; + qemu_cond_signal(&qemu_pause_cond); + } + flush_queued_work(env); +} + +static void qemu_tcg_wait_io_event(void) +{ + CPUState *env; + + while (!any_cpu_has_work()) + qemu_cond_timedwait(tcg_halt_cond, &qemu_global_mutex, 1000); + + qemu_mutex_unlock(&qemu_global_mutex); + + /* + * Users of qemu_global_mutex can be starved, having no chance + * to acquire it since this path will get to it first. + * So use another lock to provide fairness. + */ + qemu_mutex_lock(&qemu_fair_mutex); + qemu_mutex_unlock(&qemu_fair_mutex); + + qemu_mutex_lock(&qemu_global_mutex); + + for (env = first_cpu; env != NULL; env = env->next_cpu) { + qemu_wait_io_event_common(env); + } +} + +static void sigbus_reraise(void) +{ + sigset_t set; + struct sigaction action; + + memset(&action, 0, sizeof(action)); + action.sa_handler = SIG_DFL; + if (!sigaction(SIGBUS, &action, NULL)) { + raise(SIGBUS); + sigemptyset(&set); + sigaddset(&set, SIGBUS); + sigprocmask(SIG_UNBLOCK, &set, NULL); + } + perror("Failed to re-raise SIGBUS!\n"); + abort(); +} + +static void sigbus_handler(int n, struct qemu_signalfd_siginfo *siginfo, + void *ctx) +{ +#if defined(TARGET_I386) + if (kvm_on_sigbus(siginfo->ssi_code, (void *)(intptr_t)siginfo->ssi_addr)) +#endif + sigbus_reraise(); +} + +static void qemu_kvm_eat_signal(CPUState *env, int timeout) +{ + struct timespec ts; + int r, e; + siginfo_t siginfo; + sigset_t waitset; + sigset_t chkset; + + ts.tv_sec = timeout / 1000; + ts.tv_nsec = (timeout % 1000) * 1000000; + + sigemptyset(&waitset); + sigaddset(&waitset, SIG_IPI); + sigaddset(&waitset, SIGBUS); + + do { + qemu_mutex_unlock(&qemu_global_mutex); + + r = sigtimedwait(&waitset, &siginfo, &ts); + e = errno; + + qemu_mutex_lock(&qemu_global_mutex); + + if (r == -1 && !(e == EAGAIN || e == EINTR)) { + fprintf(stderr, "sigtimedwait: %s\n", strerror(e)); + exit(1); + } + + switch (r) { + case SIGBUS: +#ifdef TARGET_I386 + if (kvm_on_sigbus_vcpu(env, siginfo.si_code, siginfo.si_addr)) +#endif + sigbus_reraise(); + break; + default: + break; + } + + r = sigpending(&chkset); + if (r == -1) { + fprintf(stderr, "sigpending: %s\n", strerror(e)); + exit(1); + } + } while (sigismember(&chkset, SIG_IPI) || sigismember(&chkset, SIGBUS)); +} + +static void qemu_kvm_wait_io_event(CPUState *env) +{ + while (!cpu_has_work(env)) + qemu_cond_timedwait(env->halt_cond, &qemu_global_mutex, 1000); + + qemu_kvm_eat_signal(env, 0); + qemu_wait_io_event_common(env); +} + +static int qemu_cpu_exec(CPUState *env); + +static void *kvm_cpu_thread_fn(void *arg) +{ + CPUState *env = arg; + + qemu_mutex_lock(&qemu_global_mutex); + qemu_thread_self(env->thread); + if (kvm_enabled()) + kvm_init_vcpu(env); + + kvm_init_ipi(env); + + /* signal CPU creation */ + env->created = 1; + qemu_cond_signal(&qemu_cpu_cond); + + /* and wait for machine initialization */ + while (!qemu_system_ready) + qemu_cond_timedwait(&qemu_system_cond, &qemu_global_mutex, 100); + + while (1) { + if (cpu_can_run(env)) + qemu_cpu_exec(env); + qemu_kvm_wait_io_event(env); + } + + return NULL; +} + +static void *tcg_cpu_thread_fn(void *arg) +{ + CPUState *env = arg; + + tcg_init_ipi(); + qemu_thread_self(env->thread); + + /* signal CPU creation */ + qemu_mutex_lock(&qemu_global_mutex); + for (env = first_cpu; env != NULL; env = env->next_cpu) + env->created = 1; + qemu_cond_signal(&qemu_cpu_cond); + + /* and wait for machine initialization */ + while (!qemu_system_ready) + qemu_cond_timedwait(&qemu_system_cond, &qemu_global_mutex, 100); + + while (1) { + cpu_exec_all(); + qemu_tcg_wait_io_event(); + } + + return NULL; +} + +void qemu_cpu_kick(void *_env) +{ + CPUState *env = _env; + qemu_cond_broadcast(env->halt_cond); + qemu_thread_signal(env->thread, SIG_IPI); +} + +int qemu_cpu_self(void *_env) +{ + CPUState *env = _env; + QemuThread this; + + qemu_thread_self(&this); + + return qemu_thread_equal(&this, env->thread); +} + +static void cpu_signal(int sig) +{ + if (cpu_single_env) + cpu_exit(cpu_single_env); + exit_request = 1; +} + +static void tcg_init_ipi(void) +{ + sigset_t set; + struct sigaction sigact; + + memset(&sigact, 0, sizeof(sigact)); + sigact.sa_handler = cpu_signal; + sigaction(SIG_IPI, &sigact, NULL); + + sigemptyset(&set); + sigaddset(&set, SIG_IPI); + pthread_sigmask(SIG_UNBLOCK, &set, NULL); +} + +static void dummy_signal(int sig) +{ +} + +static void kvm_init_ipi(CPUState *env) +{ + int r; + sigset_t set; + struct sigaction sigact; + + memset(&sigact, 0, sizeof(sigact)); + sigact.sa_handler = dummy_signal; + sigaction(SIG_IPI, &sigact, NULL); + + pthread_sigmask(SIG_BLOCK, NULL, &set); + sigdelset(&set, SIG_IPI); + sigdelset(&set, SIGBUS); + r = kvm_set_signal_mask(env, &set); + if (r) { + fprintf(stderr, "kvm_set_signal_mask: %s\n", strerror(r)); + exit(1); + } +} + +static sigset_t block_io_signals(void) +{ + sigset_t set; + struct sigaction action; + + /* SIGUSR2 used by posix-aio-compat.c */ + sigemptyset(&set); + sigaddset(&set, SIGUSR2); + pthread_sigmask(SIG_UNBLOCK, &set, NULL); + + sigemptyset(&set); + sigaddset(&set, SIGIO); + sigaddset(&set, SIGALRM); + sigaddset(&set, SIG_IPI); + sigaddset(&set, SIGBUS); + pthread_sigmask(SIG_BLOCK, &set, NULL); + + memset(&action, 0, sizeof(action)); + action.sa_flags = SA_SIGINFO; + action.sa_sigaction = (void (*)(int, siginfo_t*, void*))sigbus_handler; + sigaction(SIGBUS, &action, NULL); + prctl(PR_MCE_KILL, 1, 1, 0, 0); + + return set; +} + +void qemu_mutex_lock_iothread(void) +{ + if (kvm_enabled()) { + qemu_mutex_lock(&qemu_global_mutex); + } else { + qemu_mutex_lock(&qemu_fair_mutex); + if (qemu_mutex_trylock(&qemu_global_mutex)) { + qemu_thread_signal(tcg_cpu_thread, SIG_IPI); + qemu_mutex_lock(&qemu_global_mutex); + } + qemu_mutex_unlock(&qemu_fair_mutex); + } +} + +void qemu_mutex_unlock_iothread(void) +{ + qemu_mutex_unlock(&qemu_global_mutex); +} + +static int all_vcpus_paused(void) +{ + CPUState *penv = first_cpu; + + while (penv) { + if (!penv->stopped) + return 0; + penv = (CPUState *)penv->next_cpu; + } + + return 1; +} + +void pause_all_vcpus(void) +{ + CPUState *penv = first_cpu; + + while (penv) { + penv->stop = 1; + qemu_cpu_kick(penv); + penv = (CPUState *)penv->next_cpu; + } + + while (!all_vcpus_paused()) { + qemu_cond_timedwait(&qemu_pause_cond, &qemu_global_mutex, 100); + penv = first_cpu; + while (penv) { + qemu_cpu_kick(penv); + penv = (CPUState *)penv->next_cpu; + } + } +} + +void resume_all_vcpus(void) +{ + CPUState *penv = first_cpu; + + while (penv) { + penv->stop = 0; + penv->stopped = 0; + qemu_cpu_kick(penv); + penv = (CPUState *)penv->next_cpu; + } +} + +static void tcg_init_vcpu(void *_env) +{ + CPUState *env = _env; + /* share a single thread for all cpus with TCG */ + if (!tcg_cpu_thread) { + env->thread = qemu_mallocz(sizeof(QemuThread)); + env->halt_cond = qemu_mallocz(sizeof(QemuCond)); + qemu_cond_init(env->halt_cond); + qemu_thread_create(env->thread, tcg_cpu_thread_fn, env); + while (env->created == 0) + qemu_cond_timedwait(&qemu_cpu_cond, &qemu_global_mutex, 100); + tcg_cpu_thread = env->thread; + tcg_halt_cond = env->halt_cond; + } else { + env->thread = tcg_cpu_thread; + env->halt_cond = tcg_halt_cond; + } +} + +static void kvm_start_vcpu(CPUState *env) +{ + env->thread = qemu_mallocz(sizeof(QemuThread)); + env->halt_cond = qemu_mallocz(sizeof(QemuCond)); + qemu_cond_init(env->halt_cond); + qemu_thread_create(env->thread, kvm_cpu_thread_fn, env); + while (env->created == 0) + qemu_cond_timedwait(&qemu_cpu_cond, &qemu_global_mutex, 100); +} + +void qemu_init_vcpu(void *_env) +{ + CPUState *env = _env; + + env->nr_cores = smp_cores; + env->nr_threads = smp_threads; + if (kvm_enabled()) + kvm_start_vcpu(env); + else + tcg_init_vcpu(env); +} + +void qemu_notify_event(void) +{ + qemu_event_increment(); +} + +static void qemu_system_vmstop_request(int reason) +{ + vmstop_requested = reason; + qemu_notify_event(); +} + +void vm_stop(int reason) +{ + QemuThread me; + qemu_thread_self(&me); + + if (!qemu_thread_equal(&me, &io_thread)) { + qemu_system_vmstop_request(reason); + /* + * FIXME: should not return to device code in case + * vm_stop() has been requested. + */ + if (cpu_single_env) { + cpu_exit(cpu_single_env); + cpu_single_env->stop = 1; + } + return; + } + do_vm_stop(reason); +} + +#endif + +static int qemu_cpu_exec(CPUState *env) +{ + int ret; +#ifdef CONFIG_PROFILER + int64_t ti; +#endif + +#ifdef CONFIG_PROFILER + ti = profile_getclock(); +#endif + if (use_icount) { + int64_t count; + int decr; + qemu_icount -= (env->icount_decr.u16.low + env->icount_extra); + env->icount_decr.u16.low = 0; + env->icount_extra = 0; + count = qemu_icount_round (qemu_next_deadline()); + qemu_icount += count; + decr = (count > 0xffff) ? 0xffff : count; + count -= decr; + env->icount_decr.u16.low = decr; + env->icount_extra = count; + } + ret = cpu_exec(env); +#ifdef CONFIG_PROFILER + qemu_time += profile_getclock() - ti; +#endif + if (use_icount) { + /* Fold pending instructions back into the + instruction counter, and clear the interrupt flag. */ + qemu_icount -= (env->icount_decr.u16.low + + env->icount_extra); + env->icount_decr.u32 = 0; + env->icount_extra = 0; + } + return ret; +} + +bool cpu_exec_all(void) +{ + if (next_cpu == NULL) + next_cpu = first_cpu; + for (; next_cpu != NULL && !exit_request; next_cpu = next_cpu->next_cpu) { + CPUState *env = next_cpu; + + qemu_clock_enable(vm_clock, + (env->singlestep_enabled & SSTEP_NOTIMER) == 0); + + if (qemu_alarm_pending()) + break; + if (cpu_can_run(env)) { + if (qemu_cpu_exec(env) == EXCP_DEBUG) { + break; + } + } else if (env->stop) { + break; + } + } + exit_request = 0; + return any_cpu_has_work(); +} + +void set_numa_modes(void) +{ + CPUState *env; + int i; + + for (env = first_cpu; env != NULL; env = env->next_cpu) { + for (i = 0; i < nb_numa_nodes; i++) { + if (node_cpumask[i] & (1 << env->cpu_index)) { + env->numa_node = i; + } + } + } +} + +void set_cpu_log(const char *optarg) +{ + int mask; + const CPULogItem *item; + + mask = cpu_str_to_log_mask(optarg); + if (!mask) { + printf("Log items (comma separated):\n"); + for (item = cpu_log_items; item->mask != 0; item++) { + printf("%-10s %s\n", item->name, item->help); + } + exit(1); + } + cpu_set_log(mask); +} + +/* Return the virtual CPU time, based on the instruction counter. */ +int64_t cpu_get_icount(void) +{ + int64_t icount; + CPUState *env = cpu_single_env;; + + icount = qemu_icount; + if (env) { + if (!can_do_io(env)) { + fprintf(stderr, "Bad clock read\n"); + } + icount -= (env->icount_decr.u16.low + env->icount_extra); + } + return qemu_icount_bias + (icount << icount_time_shift); +} + +void list_cpus(FILE *f, fprintf_function cpu_fprintf, const char *optarg) +{ + /* XXX: implement xxx_cpu_list for targets that still miss it */ +#if defined(cpu_list_id) + cpu_list_id(f, cpu_fprintf, optarg); +#elif defined(cpu_list) + cpu_list(f, cpu_fprintf); /* deprecated */ +#endif +} diff --git a/cpus.h b/cpus.h new file mode 100644 index 0000000..bf4d9bb --- /dev/null +++ b/cpus.h @@ -0,0 +1,21 @@ +#ifndef QEMU_CPUS_H +#define QEMU_CPUS_H + +/* cpus.c */ +int qemu_init_main_loop(void); +void qemu_main_loop_start(void); +void resume_all_vcpus(void); +void pause_all_vcpus(void); + +/* vl.c */ +extern int smp_cores; +extern int smp_threads; +extern int debug_requested; +extern int vmstop_requested; +void vm_state_notify(int running, int reason); +bool cpu_exec_all(void); +void set_numa_modes(void); +void set_cpu_log(const char *optarg); +void list_cpus(FILE *f, fprintf_function cpu_fprintf, const char *optarg); + +#endif diff --git a/cris-dis.c b/cris-dis.c new file mode 100644 index 0000000..5fa67d9 --- /dev/null +++ b/cris-dis.c @@ -0,0 +1,2893 @@ +/* Disassembler code for CRIS. + Copyright 2000, 2001, 2002, 2004, 2005, 2006 Free Software Foundation, Inc. + Contributed by Axis Communications AB, Lund, Sweden. + Written by Hans-Peter Nilsson. + + This file is part of the GNU binutils and GDB, the GNU debugger. + + 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. + + 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 . */ + +#include "qemu-common.h" +#include "dis-asm.h" +//#include "sysdep.h" +#include "target-cris/opcode-cris.h" +//#include "libiberty.h" + +#define CONST_STRNEQ(STR1,STR2) (strncmp ((STR1), (STR2), sizeof (STR2) - 1) == 0) + +/* cris-opc.c -- Table of opcodes for the CRIS processor. + Copyright 2000, 2001, 2004 Free Software Foundation, Inc. + Contributed by Axis Communications AB, Lund, Sweden. + Originally written for GAS 1.38.1 by Mikael Asker. + Reorganized by Hans-Peter Nilsson. + +This file is part of GAS, GDB and the GNU binutils. + +GAS, GDB, and GNU binutils 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. + +GAS, GDB, and GNU binutils are distributed in the hope that they 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 . */ + +#ifndef NULL +#define NULL (0) +#endif + +/* This table isn't used for CRISv32 and the size of immediate operands. */ +const struct cris_spec_reg +cris_spec_regs[] = +{ + {"bz", 0, 1, cris_ver_v32p, NULL}, + {"p0", 0, 1, 0, NULL}, + {"vr", 1, 1, 0, NULL}, + {"p1", 1, 1, 0, NULL}, + {"pid", 2, 1, cris_ver_v32p, NULL}, + {"p2", 2, 1, cris_ver_v32p, NULL}, + {"p2", 2, 1, cris_ver_warning, NULL}, + {"srs", 3, 1, cris_ver_v32p, NULL}, + {"p3", 3, 1, cris_ver_v32p, NULL}, + {"p3", 3, 1, cris_ver_warning, NULL}, + {"wz", 4, 2, cris_ver_v32p, NULL}, + {"p4", 4, 2, 0, NULL}, + {"ccr", 5, 2, cris_ver_v0_10, NULL}, + {"exs", 5, 4, cris_ver_v32p, NULL}, + {"p5", 5, 2, cris_ver_v0_10, NULL}, + {"p5", 5, 4, cris_ver_v32p, NULL}, + {"dcr0",6, 2, cris_ver_v0_3, NULL}, + {"eda", 6, 4, cris_ver_v32p, NULL}, + {"p6", 6, 2, cris_ver_v0_3, NULL}, + {"p6", 6, 4, cris_ver_v32p, NULL}, + {"dcr1/mof", 7, 4, cris_ver_v10p, + "Register `dcr1/mof' with ambiguous size specified. Guessing 4 bytes"}, + {"dcr1/mof", 7, 2, cris_ver_v0_3, + "Register `dcr1/mof' with ambiguous size specified. Guessing 2 bytes"}, + {"mof", 7, 4, cris_ver_v10p, NULL}, + {"dcr1",7, 2, cris_ver_v0_3, NULL}, + {"p7", 7, 4, cris_ver_v10p, NULL}, + {"p7", 7, 2, cris_ver_v0_3, NULL}, + {"dz", 8, 4, cris_ver_v32p, NULL}, + {"p8", 8, 4, 0, NULL}, + {"ibr", 9, 4, cris_ver_v0_10, NULL}, + {"ebp", 9, 4, cris_ver_v32p, NULL}, + {"p9", 9, 4, 0, NULL}, + {"irp", 10, 4, cris_ver_v0_10, NULL}, + {"erp", 10, 4, cris_ver_v32p, NULL}, + {"p10", 10, 4, 0, NULL}, + {"srp", 11, 4, 0, NULL}, + {"p11", 11, 4, 0, NULL}, + /* For disassembly use only. Accept at assembly with a warning. */ + {"bar/dtp0", 12, 4, cris_ver_warning, + "Ambiguous register `bar/dtp0' specified"}, + {"nrp", 12, 4, cris_ver_v32p, NULL}, + {"bar", 12, 4, cris_ver_v8_10, NULL}, + {"dtp0",12, 4, cris_ver_v0_3, NULL}, + {"p12", 12, 4, 0, NULL}, + /* For disassembly use only. Accept at assembly with a warning. */ + {"dccr/dtp1",13, 4, cris_ver_warning, + "Ambiguous register `dccr/dtp1' specified"}, + {"ccs", 13, 4, cris_ver_v32p, NULL}, + {"dccr",13, 4, cris_ver_v8_10, NULL}, + {"dtp1",13, 4, cris_ver_v0_3, NULL}, + {"p13", 13, 4, 0, NULL}, + {"brp", 14, 4, cris_ver_v3_10, NULL}, + {"usp", 14, 4, cris_ver_v32p, NULL}, + {"p14", 14, 4, cris_ver_v3p, NULL}, + {"usp", 15, 4, cris_ver_v10, NULL}, + {"spc", 15, 4, cris_ver_v32p, NULL}, + {"p15", 15, 4, cris_ver_v10p, NULL}, + {NULL, 0, 0, cris_ver_version_all, NULL} +}; + +/* Add version specifiers to this table when necessary. + The (now) regular coding of register names suggests a simpler + implementation. */ +const struct cris_support_reg cris_support_regs[] = +{ + {"s0", 0}, + {"s1", 1}, + {"s2", 2}, + {"s3", 3}, + {"s4", 4}, + {"s5", 5}, + {"s6", 6}, + {"s7", 7}, + {"s8", 8}, + {"s9", 9}, + {"s10", 10}, + {"s11", 11}, + {"s12", 12}, + {"s13", 13}, + {"s14", 14}, + {"s15", 15}, + {NULL, 0} +}; + +/* All CRIS opcodes are 16 bits. + + - The match component is a mask saying which bits must match a + particular opcode in order for an instruction to be an instance + of that opcode. + + - The args component is a string containing characters symbolically + matching the operands of an instruction. Used for both assembly + and disassembly. + + Operand-matching characters: + [ ] , space + Verbatim. + A The string "ACR" (case-insensitive). + B Not really an operand. It causes a "BDAP -size,SP" prefix to be + output for the PUSH alias-instructions and recognizes a push- + prefix at disassembly. This letter isn't recognized for v32. + Must be followed by a R or P letter. + ! Non-match pattern, will not match if there's a prefix insn. + b Non-matching operand, used for branches with 16-bit + displacement. Only recognized by the disassembler. + c 5-bit unsigned immediate in bits <4:0>. + C 4-bit unsigned immediate in bits <3:0>. + d At assembly, optionally (as in put other cases before this one) + ".d" or ".D" at the start of the operands, followed by one space + character. At disassembly, nothing. + D General register in bits <15:12> and <3:0>. + f List of flags in bits <15:12> and <3:0>. + i 6-bit signed immediate in bits <5:0>. + I 6-bit unsigned immediate in bits <5:0>. + M Size modifier (B, W or D) for CLEAR instructions. + m Size modifier (B, W or D) in bits <5:4> + N A 32-bit dword, like in the difference between s and y. + This has no effect on bits in the opcode. Can also be expressed + as "[pc+]" in input. + n As N, but PC-relative (to the start of the instruction). + o [-128..127] word offset in bits <7:1> and <0>. Used by 8-bit + branch instructions. + O [-128..127] offset in bits <7:0>. Also matches a comma and a + general register after the expression, in bits <15:12>. Used + only for the BDAP prefix insn (in v32 the ADDOQ insn; same opcode). + P Special register in bits <15:12>. + p Indicates that the insn is a prefix insn. Must be first + character. + Q As O, but don't relax; force an 8-bit offset. + R General register in bits <15:12>. + r General register in bits <3:0>. + S Source operand in bit <10> and a prefix; a 3-operand prefix + without side-effect. + s Source operand in bits <10> and <3:0>, optionally with a + side-effect prefix, except [pc] (the name, not R15 as in ACR) + isn't allowed for v32 and higher. + T Support register in bits <15:12>. + u 4-bit (PC-relative) unsigned immediate word offset in bits <3:0>. + U Relaxes to either u or n, instruction is assumed LAPCQ or LAPC. + Not recognized at disassembly. + x Register-dot-modifier, for example "r5.w" in bits <15:12> and <5:4>. + y Like 's' but do not allow an integer at assembly. + Y The difference s-y; only an integer is allowed. + z Size modifier (B or W) in bit <4>. */ + + +/* Please note the order of the opcodes in this table is significant. + The assembler requires that all instances of the same mnemonic must + be consecutive. If they aren't, the assembler might not recognize + them, or may indicate an internal error. + + The disassembler should not normally care about the order of the + opcodes, but will prefer an earlier alternative if the "match-score" + (see cris-dis.c) is computed as equal. + + It should not be significant for proper execution that this table is + in alphabetical order, but please follow that convention for an easy + overview. */ + +const struct cris_opcode +cris_opcodes[] = +{ + {"abs", 0x06B0, 0x0940, "r,R", 0, SIZE_NONE, 0, + cris_abs_op}, + + {"add", 0x0600, 0x09c0, "m r,R", 0, SIZE_NONE, 0, + cris_reg_mode_add_sub_cmp_and_or_move_op}, + + {"add", 0x0A00, 0x01c0, "m s,R", 0, SIZE_FIELD, 0, + cris_none_reg_mode_add_sub_cmp_and_or_move_op}, + + {"add", 0x0A00, 0x01c0, "m S,D", 0, SIZE_NONE, + cris_ver_v0_10, + cris_none_reg_mode_add_sub_cmp_and_or_move_op}, + + {"add", 0x0a00, 0x05c0, "m S,R,r", 0, SIZE_NONE, + cris_ver_v0_10, + cris_three_operand_add_sub_cmp_and_or_op}, + + {"add", 0x0A00, 0x01c0, "m s,R", 0, SIZE_FIELD, + cris_ver_v32p, + cris_none_reg_mode_add_sub_cmp_and_or_move_op}, + + {"addc", 0x0570, 0x0A80, "r,R", 0, SIZE_FIX_32, + cris_ver_v32p, + cris_not_implemented_op}, + + {"addc", 0x09A0, 0x0250, "s,R", 0, SIZE_FIX_32, + cris_ver_v32p, + cris_not_implemented_op}, + + {"addi", 0x0540, 0x0A80, "x,r,A", 0, SIZE_NONE, + cris_ver_v32p, + cris_addi_op}, + + {"addi", 0x0500, 0x0Ac0, "x,r", 0, SIZE_NONE, 0, + cris_addi_op}, + + /* This collates after "addo", but we want to disassemble as "addoq", + not "addo". */ + {"addoq", 0x0100, 0x0E00, "Q,A", 0, SIZE_NONE, + cris_ver_v32p, + cris_not_implemented_op}, + + {"addo", 0x0940, 0x0280, "m s,R,A", 0, SIZE_FIELD_SIGNED, + cris_ver_v32p, + cris_not_implemented_op}, + + /* This must be located after the insn above, lest we misinterpret + "addo.b -1,r0,acr" as "addo .b-1,r0,acr". FIXME: Sounds like a + parser bug. */ + {"addo", 0x0100, 0x0E00, "O,A", 0, SIZE_NONE, + cris_ver_v32p, + cris_not_implemented_op}, + + {"addq", 0x0200, 0x0Dc0, "I,R", 0, SIZE_NONE, 0, + cris_quick_mode_add_sub_op}, + + {"adds", 0x0420, 0x0Bc0, "z r,R", 0, SIZE_NONE, 0, + cris_reg_mode_add_sub_cmp_and_or_move_op}, + + /* FIXME: SIZE_FIELD_SIGNED and all necessary changes. */ + {"adds", 0x0820, 0x03c0, "z s,R", 0, SIZE_FIELD, 0, + cris_none_reg_mode_add_sub_cmp_and_or_move_op}, + + {"adds", 0x0820, 0x03c0, "z S,D", 0, SIZE_NONE, + cris_ver_v0_10, + cris_none_reg_mode_add_sub_cmp_and_or_move_op}, + + {"adds", 0x0820, 0x07c0, "z S,R,r", 0, SIZE_NONE, + cris_ver_v0_10, + cris_three_operand_add_sub_cmp_and_or_op}, + + {"addu", 0x0400, 0x0be0, "z r,R", 0, SIZE_NONE, 0, + cris_reg_mode_add_sub_cmp_and_or_move_op}, + + /* FIXME: SIZE_FIELD_UNSIGNED and all necessary changes. */ + {"addu", 0x0800, 0x03e0, "z s,R", 0, SIZE_FIELD, 0, + cris_none_reg_mode_add_sub_cmp_and_or_move_op}, + + {"addu", 0x0800, 0x03e0, "z S,D", 0, SIZE_NONE, + cris_ver_v0_10, + cris_none_reg_mode_add_sub_cmp_and_or_move_op}, + + {"addu", 0x0800, 0x07e0, "z S,R,r", 0, SIZE_NONE, + cris_ver_v0_10, + cris_three_operand_add_sub_cmp_and_or_op}, + + {"and", 0x0700, 0x08C0, "m r,R", 0, SIZE_NONE, 0, + cris_reg_mode_add_sub_cmp_and_or_move_op}, + + {"and", 0x0B00, 0x00C0, "m s,R", 0, SIZE_FIELD, 0, + cris_none_reg_mode_add_sub_cmp_and_or_move_op}, + + {"and", 0x0B00, 0x00C0, "m S,D", 0, SIZE_NONE, + cris_ver_v0_10, + cris_none_reg_mode_add_sub_cmp_and_or_move_op}, + + {"and", 0x0B00, 0x04C0, "m S,R,r", 0, SIZE_NONE, + cris_ver_v0_10, + cris_three_operand_add_sub_cmp_and_or_op}, + + {"andq", 0x0300, 0x0CC0, "i,R", 0, SIZE_NONE, 0, + cris_quick_mode_and_cmp_move_or_op}, + + {"asr", 0x0780, 0x0840, "m r,R", 0, SIZE_NONE, 0, + cris_asr_op}, + + {"asrq", 0x03a0, 0x0c40, "c,R", 0, SIZE_NONE, 0, + cris_asrq_op}, + + {"ax", 0x15B0, 0xEA4F, "", 0, SIZE_NONE, 0, + cris_ax_ei_setf_op}, + + /* FIXME: Should use branch #defines. */ + {"b", 0x0dff, 0x0200, "b", 1, SIZE_NONE, 0, + cris_sixteen_bit_offset_branch_op}, + + {"ba", + BA_QUICK_OPCODE, + 0x0F00+(0xF-CC_A)*0x1000, "o", 1, SIZE_NONE, 0, + cris_eight_bit_offset_branch_op}, + + /* Needs to come after the usual "ba o", which might be relaxed to + this one. */ + {"ba", BA_DWORD_OPCODE, + 0xffff & (~BA_DWORD_OPCODE), "n", 0, SIZE_FIX_32, + cris_ver_v32p, + cris_none_reg_mode_jump_op}, + + {"bas", 0x0EBF, 0x0140, "n,P", 0, SIZE_FIX_32, + cris_ver_v32p, + cris_none_reg_mode_jump_op}, + + {"basc", 0x0EFF, 0x0100, "n,P", 0, SIZE_FIX_32, + cris_ver_v32p, + cris_none_reg_mode_jump_op}, + + {"bcc", + BRANCH_QUICK_OPCODE+CC_CC*0x1000, + 0x0f00+(0xF-CC_CC)*0x1000, "o", 1, SIZE_NONE, 0, + cris_eight_bit_offset_branch_op}, + + {"bcs", + BRANCH_QUICK_OPCODE+CC_CS*0x1000, + 0x0f00+(0xF-CC_CS)*0x1000, "o", 1, SIZE_NONE, 0, + cris_eight_bit_offset_branch_op}, + + {"bdap", + BDAP_INDIR_OPCODE, BDAP_INDIR_Z_BITS, "pm s,R", 0, SIZE_FIELD_SIGNED, + cris_ver_v0_10, + cris_bdap_prefix}, + + {"bdap", + BDAP_QUICK_OPCODE, BDAP_QUICK_Z_BITS, "pO", 0, SIZE_NONE, + cris_ver_v0_10, + cris_quick_mode_bdap_prefix}, + + {"beq", + BRANCH_QUICK_OPCODE+CC_EQ*0x1000, + 0x0f00+(0xF-CC_EQ)*0x1000, "o", 1, SIZE_NONE, 0, + cris_eight_bit_offset_branch_op}, + + /* This is deliberately put before "bext" to trump it, even though not + in alphabetical order, since we don't do excluding version checks + for v0..v10. */ + {"bwf", + BRANCH_QUICK_OPCODE+CC_EXT*0x1000, + 0x0f00+(0xF-CC_EXT)*0x1000, "o", 1, SIZE_NONE, + cris_ver_v10, + cris_eight_bit_offset_branch_op}, + + {"bext", + BRANCH_QUICK_OPCODE+CC_EXT*0x1000, + 0x0f00+(0xF-CC_EXT)*0x1000, "o", 1, SIZE_NONE, + cris_ver_v0_3, + cris_eight_bit_offset_branch_op}, + + {"bge", + BRANCH_QUICK_OPCODE+CC_GE*0x1000, + 0x0f00+(0xF-CC_GE)*0x1000, "o", 1, SIZE_NONE, 0, + cris_eight_bit_offset_branch_op}, + + {"bgt", + BRANCH_QUICK_OPCODE+CC_GT*0x1000, + 0x0f00+(0xF-CC_GT)*0x1000, "o", 1, SIZE_NONE, 0, + cris_eight_bit_offset_branch_op}, + + {"bhi", + BRANCH_QUICK_OPCODE+CC_HI*0x1000, + 0x0f00+(0xF-CC_HI)*0x1000, "o", 1, SIZE_NONE, 0, + cris_eight_bit_offset_branch_op}, + + {"bhs", + BRANCH_QUICK_OPCODE+CC_HS*0x1000, + 0x0f00+(0xF-CC_HS)*0x1000, "o", 1, SIZE_NONE, 0, + cris_eight_bit_offset_branch_op}, + + {"biap", BIAP_OPCODE, BIAP_Z_BITS, "pm r,R", 0, SIZE_NONE, + cris_ver_v0_10, + cris_biap_prefix}, + + {"ble", + BRANCH_QUICK_OPCODE+CC_LE*0x1000, + 0x0f00+(0xF-CC_LE)*0x1000, "o", 1, SIZE_NONE, 0, + cris_eight_bit_offset_branch_op}, + + {"blo", + BRANCH_QUICK_OPCODE+CC_LO*0x1000, + 0x0f00+(0xF-CC_LO)*0x1000, "o", 1, SIZE_NONE, 0, + cris_eight_bit_offset_branch_op}, + + {"bls", + BRANCH_QUICK_OPCODE+CC_LS*0x1000, + 0x0f00+(0xF-CC_LS)*0x1000, "o", 1, SIZE_NONE, 0, + cris_eight_bit_offset_branch_op}, + + {"blt", + BRANCH_QUICK_OPCODE+CC_LT*0x1000, + 0x0f00+(0xF-CC_LT)*0x1000, "o", 1, SIZE_NONE, 0, + cris_eight_bit_offset_branch_op}, + + {"bmi", + BRANCH_QUICK_OPCODE+CC_MI*0x1000, + 0x0f00+(0xF-CC_MI)*0x1000, "o", 1, SIZE_NONE, 0, + cris_eight_bit_offset_branch_op}, + + {"bmod", 0x0ab0, 0x0140, "s,R", 0, SIZE_FIX_32, + cris_ver_sim_v0_10, + cris_not_implemented_op}, + + {"bmod", 0x0ab0, 0x0140, "S,D", 0, SIZE_NONE, + cris_ver_sim_v0_10, + cris_not_implemented_op}, + + {"bmod", 0x0ab0, 0x0540, "S,R,r", 0, SIZE_NONE, + cris_ver_sim_v0_10, + cris_not_implemented_op}, + + {"bne", + BRANCH_QUICK_OPCODE+CC_NE*0x1000, + 0x0f00+(0xF-CC_NE)*0x1000, "o", 1, SIZE_NONE, 0, + cris_eight_bit_offset_branch_op}, + + {"bound", 0x05c0, 0x0A00, "m r,R", 0, SIZE_NONE, 0, + cris_two_operand_bound_op}, + /* FIXME: SIZE_FIELD_UNSIGNED and all necessary changes. */ + {"bound", 0x09c0, 0x0200, "m s,R", 0, SIZE_FIELD, + cris_ver_v0_10, + cris_two_operand_bound_op}, + /* FIXME: SIZE_FIELD_UNSIGNED and all necessary changes. */ + {"bound", 0x0dcf, 0x0200, "m Y,R", 0, SIZE_FIELD, 0, + cris_two_operand_bound_op}, + {"bound", 0x09c0, 0x0200, "m S,D", 0, SIZE_NONE, + cris_ver_v0_10, + cris_two_operand_bound_op}, + {"bound", 0x09c0, 0x0600, "m S,R,r", 0, SIZE_NONE, + cris_ver_v0_10, + cris_three_operand_bound_op}, + + {"bpl", + BRANCH_QUICK_OPCODE+CC_PL*0x1000, + 0x0f00+(0xF-CC_PL)*0x1000, "o", 1, SIZE_NONE, 0, + cris_eight_bit_offset_branch_op}, + + {"break", 0xe930, 0x16c0, "C", 0, SIZE_NONE, + cris_ver_v3p, + cris_break_op}, + + {"bsb", + BRANCH_QUICK_OPCODE+CC_EXT*0x1000, + 0x0f00+(0xF-CC_EXT)*0x1000, "o", 1, SIZE_NONE, + cris_ver_v32p, + cris_eight_bit_offset_branch_op}, + + {"bsr", 0xBEBF, 0x4140, "n", 0, SIZE_FIX_32, + cris_ver_v32p, + cris_none_reg_mode_jump_op}, + + {"bsrc", 0xBEFF, 0x4100, "n", 0, SIZE_FIX_32, + cris_ver_v32p, + cris_none_reg_mode_jump_op}, + + {"bstore", 0x0af0, 0x0100, "s,R", 0, SIZE_FIX_32, + cris_ver_warning, + cris_not_implemented_op}, + + {"bstore", 0x0af0, 0x0100, "S,D", 0, SIZE_NONE, + cris_ver_warning, + cris_not_implemented_op}, + + {"bstore", 0x0af0, 0x0500, "S,R,r", 0, SIZE_NONE, + cris_ver_warning, + cris_not_implemented_op}, + + {"btst", 0x04F0, 0x0B00, "r,R", 0, SIZE_NONE, 0, + cris_btst_nop_op}, + {"btstq", 0x0380, 0x0C60, "c,R", 0, SIZE_NONE, 0, + cris_btst_nop_op}, + + {"bvc", + BRANCH_QUICK_OPCODE+CC_VC*0x1000, + 0x0f00+(0xF-CC_VC)*0x1000, "o", 1, SIZE_NONE, 0, + cris_eight_bit_offset_branch_op}, + + {"bvs", + BRANCH_QUICK_OPCODE+CC_VS*0x1000, + 0x0f00+(0xF-CC_VS)*0x1000, "o", 1, SIZE_NONE, 0, + cris_eight_bit_offset_branch_op}, + + {"clear", 0x0670, 0x3980, "M r", 0, SIZE_NONE, 0, + cris_reg_mode_clear_op}, + + {"clear", 0x0A70, 0x3180, "M y", 0, SIZE_NONE, 0, + cris_none_reg_mode_clear_test_op}, + + {"clear", 0x0A70, 0x3180, "M S", 0, SIZE_NONE, + cris_ver_v0_10, + cris_none_reg_mode_clear_test_op}, + + {"clearf", 0x05F0, 0x0A00, "f", 0, SIZE_NONE, 0, + cris_clearf_di_op}, + + {"cmp", 0x06C0, 0x0900, "m r,R", 0, SIZE_NONE, 0, + cris_reg_mode_add_sub_cmp_and_or_move_op}, + + {"cmp", 0x0Ac0, 0x0100, "m s,R", 0, SIZE_FIELD, 0, + cris_none_reg_mode_add_sub_cmp_and_or_move_op}, + + {"cmp", 0x0Ac0, 0x0100, "m S,D", 0, SIZE_NONE, + cris_ver_v0_10, + cris_none_reg_mode_add_sub_cmp_and_or_move_op}, + + {"cmpq", 0x02C0, 0x0D00, "i,R", 0, SIZE_NONE, 0, + cris_quick_mode_and_cmp_move_or_op}, + + /* FIXME: SIZE_FIELD_SIGNED and all necessary changes. */ + {"cmps", 0x08e0, 0x0300, "z s,R", 0, SIZE_FIELD, 0, + cris_none_reg_mode_add_sub_cmp_and_or_move_op}, + + {"cmps", 0x08e0, 0x0300, "z S,D", 0, SIZE_NONE, + cris_ver_v0_10, + cris_none_reg_mode_add_sub_cmp_and_or_move_op}, + + /* FIXME: SIZE_FIELD_UNSIGNED and all necessary changes. */ + {"cmpu", 0x08c0, 0x0320, "z s,R" , 0, SIZE_FIELD, 0, + cris_none_reg_mode_add_sub_cmp_and_or_move_op}, + + {"cmpu", 0x08c0, 0x0320, "z S,D", 0, SIZE_NONE, + cris_ver_v0_10, + cris_none_reg_mode_add_sub_cmp_and_or_move_op}, + + {"di", 0x25F0, 0xDA0F, "", 0, SIZE_NONE, 0, + cris_clearf_di_op}, + + {"dip", DIP_OPCODE, DIP_Z_BITS, "ps", 0, SIZE_FIX_32, + cris_ver_v0_10, + cris_dip_prefix}, + + {"div", 0x0980, 0x0640, "m R,r", 0, SIZE_FIELD, 0, + cris_not_implemented_op}, + + {"dstep", 0x06f0, 0x0900, "r,R", 0, SIZE_NONE, 0, + cris_dstep_logshift_mstep_neg_not_op}, + + {"ei", 0x25B0, 0xDA4F, "", 0, SIZE_NONE, 0, + cris_ax_ei_setf_op}, + + {"fidxd", 0x0ab0, 0xf540, "[r]", 0, SIZE_NONE, + cris_ver_v32p, + cris_not_implemented_op}, + + {"fidxi", 0x0d30, 0xF2C0, "[r]", 0, SIZE_NONE, + cris_ver_v32p, + cris_not_implemented_op}, + + {"ftagd", 0x1AB0, 0xE540, "[r]", 0, SIZE_NONE, + cris_ver_v32p, + cris_not_implemented_op}, + + {"ftagi", 0x1D30, 0xE2C0, "[r]", 0, SIZE_NONE, + cris_ver_v32p, + cris_not_implemented_op}, + + {"halt", 0xF930, 0x06CF, "", 0, SIZE_NONE, + cris_ver_v32p, + cris_not_implemented_op}, + + {"jas", 0x09B0, 0x0640, "r,P", 0, SIZE_NONE, + cris_ver_v32p, + cris_reg_mode_jump_op}, + + {"jas", 0x0DBF, 0x0240, "N,P", 0, SIZE_FIX_32, + cris_ver_v32p, + cris_reg_mode_jump_op}, + + {"jasc", 0x0B30, 0x04C0, "r,P", 0, SIZE_NONE, + cris_ver_v32p, + cris_reg_mode_jump_op}, + + {"jasc", 0x0F3F, 0x00C0, "N,P", 0, SIZE_FIX_32, + cris_ver_v32p, + cris_reg_mode_jump_op}, + + {"jbrc", 0x69b0, 0x9640, "r", 0, SIZE_NONE, + cris_ver_v8_10, + cris_reg_mode_jump_op}, + + {"jbrc", 0x6930, 0x92c0, "s", 0, SIZE_FIX_32, + cris_ver_v8_10, + cris_none_reg_mode_jump_op}, + + {"jbrc", 0x6930, 0x92c0, "S", 0, SIZE_NONE, + cris_ver_v8_10, + cris_none_reg_mode_jump_op}, + + {"jir", 0xA9b0, 0x5640, "r", 0, SIZE_NONE, + cris_ver_v8_10, + cris_reg_mode_jump_op}, + + {"jir", 0xA930, 0x52c0, "s", 0, SIZE_FIX_32, + cris_ver_v8_10, + cris_none_reg_mode_jump_op}, + + {"jir", 0xA930, 0x52c0, "S", 0, SIZE_NONE, + cris_ver_v8_10, + cris_none_reg_mode_jump_op}, + + {"jirc", 0x29b0, 0xd640, "r", 0, SIZE_NONE, + cris_ver_v8_10, + cris_reg_mode_jump_op}, + + {"jirc", 0x2930, 0xd2c0, "s", 0, SIZE_FIX_32, + cris_ver_v8_10, + cris_none_reg_mode_jump_op}, + + {"jirc", 0x2930, 0xd2c0, "S", 0, SIZE_NONE, + cris_ver_v8_10, + cris_none_reg_mode_jump_op}, + + {"jsr", 0xB9b0, 0x4640, "r", 0, SIZE_NONE, 0, + cris_reg_mode_jump_op}, + + {"jsr", 0xB930, 0x42c0, "s", 0, SIZE_FIX_32, + cris_ver_v0_10, + cris_none_reg_mode_jump_op}, + + {"jsr", 0xBDBF, 0x4240, "N", 0, SIZE_FIX_32, + cris_ver_v32p, + cris_none_reg_mode_jump_op}, + + {"jsr", 0xB930, 0x42c0, "S", 0, SIZE_NONE, + cris_ver_v0_10, + cris_none_reg_mode_jump_op}, + + {"jsrc", 0x39b0, 0xc640, "r", 0, SIZE_NONE, + cris_ver_v8_10, + cris_reg_mode_jump_op}, + + {"jsrc", 0x3930, 0xc2c0, "s", 0, SIZE_FIX_32, + cris_ver_v8_10, + cris_none_reg_mode_jump_op}, + + {"jsrc", 0x3930, 0xc2c0, "S", 0, SIZE_NONE, + cris_ver_v8_10, + cris_none_reg_mode_jump_op}, + + {"jsrc", 0xBB30, 0x44C0, "r", 0, SIZE_NONE, + cris_ver_v32p, + cris_reg_mode_jump_op}, + + {"jsrc", 0xBF3F, 0x40C0, "N", 0, SIZE_FIX_32, + cris_ver_v32p, + cris_reg_mode_jump_op}, + + {"jump", 0x09b0, 0xF640, "r", 0, SIZE_NONE, 0, + cris_reg_mode_jump_op}, + + {"jump", + JUMP_INDIR_OPCODE, JUMP_INDIR_Z_BITS, "s", 0, SIZE_FIX_32, + cris_ver_v0_10, + cris_none_reg_mode_jump_op}, + + {"jump", + JUMP_INDIR_OPCODE, JUMP_INDIR_Z_BITS, "S", 0, SIZE_NONE, + cris_ver_v0_10, + cris_none_reg_mode_jump_op}, + + {"jump", 0x09F0, 0x060F, "P", 0, SIZE_NONE, + cris_ver_v32p, + cris_none_reg_mode_jump_op}, + + {"jump", + JUMP_PC_INCR_OPCODE_V32, + (0xffff & ~JUMP_PC_INCR_OPCODE_V32), "N", 0, SIZE_FIX_32, + cris_ver_v32p, + cris_none_reg_mode_jump_op}, + + {"jmpu", 0x8930, 0x72c0, "s", 0, SIZE_FIX_32, + cris_ver_v10, + cris_none_reg_mode_jump_op}, + + {"jmpu", 0x8930, 0x72c0, "S", 0, SIZE_NONE, + cris_ver_v10, + cris_none_reg_mode_jump_op}, + + {"lapc", 0x0970, 0x0680, "U,R", 0, SIZE_NONE, + cris_ver_v32p, + cris_not_implemented_op}, + + {"lapc", 0x0D7F, 0x0280, "dn,R", 0, SIZE_FIX_32, + cris_ver_v32p, + cris_not_implemented_op}, + + {"lapcq", 0x0970, 0x0680, "u,R", 0, SIZE_NONE, + cris_ver_v32p, + cris_addi_op}, + + {"lsl", 0x04C0, 0x0B00, "m r,R", 0, SIZE_NONE, 0, + cris_dstep_logshift_mstep_neg_not_op}, + + {"lslq", 0x03c0, 0x0C20, "c,R", 0, SIZE_NONE, 0, + cris_dstep_logshift_mstep_neg_not_op}, + + {"lsr", 0x07C0, 0x0800, "m r,R", 0, SIZE_NONE, 0, + cris_dstep_logshift_mstep_neg_not_op}, + + {"lsrq", 0x03e0, 0x0C00, "c,R", 0, SIZE_NONE, 0, + cris_dstep_logshift_mstep_neg_not_op}, + + {"lz", 0x0730, 0x08C0, "r,R", 0, SIZE_NONE, + cris_ver_v3p, + cris_not_implemented_op}, + + {"mcp", 0x07f0, 0x0800, "P,r", 0, SIZE_NONE, + cris_ver_v32p, + cris_not_implemented_op}, + + {"move", 0x0640, 0x0980, "m r,R", 0, SIZE_NONE, 0, + cris_reg_mode_add_sub_cmp_and_or_move_op}, + + {"move", 0x0A40, 0x0180, "m s,R", 0, SIZE_FIELD, 0, + cris_none_reg_mode_add_sub_cmp_and_or_move_op}, + + {"move", 0x0A40, 0x0180, "m S,D", 0, SIZE_NONE, + cris_ver_v0_10, + cris_none_reg_mode_add_sub_cmp_and_or_move_op}, + + {"move", 0x0630, 0x09c0, "r,P", 0, SIZE_NONE, 0, + cris_move_to_preg_op}, + + {"move", 0x0670, 0x0980, "P,r", 0, SIZE_NONE, 0, + cris_reg_mode_move_from_preg_op}, + + {"move", 0x0BC0, 0x0000, "m R,y", 0, SIZE_FIELD, 0, + cris_none_reg_mode_add_sub_cmp_and_or_move_op}, + + {"move", 0x0BC0, 0x0000, "m D,S", 0, SIZE_NONE, + cris_ver_v0_10, + cris_none_reg_mode_add_sub_cmp_and_or_move_op}, + + {"move", + MOVE_M_TO_PREG_OPCODE, MOVE_M_TO_PREG_ZBITS, + "s,P", 0, SIZE_SPEC_REG, 0, + cris_move_to_preg_op}, + + {"move", 0x0A30, 0x01c0, "S,P", 0, SIZE_NONE, + cris_ver_v0_10, + cris_move_to_preg_op}, + + {"move", 0x0A70, 0x0180, "P,y", 0, SIZE_SPEC_REG, 0, + cris_none_reg_mode_move_from_preg_op}, + + {"move", 0x0A70, 0x0180, "P,S", 0, SIZE_NONE, + cris_ver_v0_10, + cris_none_reg_mode_move_from_preg_op}, + + {"move", 0x0B70, 0x0480, "r,T", 0, SIZE_NONE, + cris_ver_v32p, + cris_not_implemented_op}, + + {"move", 0x0F70, 0x0080, "T,r", 0, SIZE_NONE, + cris_ver_v32p, + cris_not_implemented_op}, + + {"movem", 0x0BF0, 0x0000, "R,y", 0, SIZE_FIX_32, 0, + cris_move_reg_to_mem_movem_op}, + + {"movem", 0x0BF0, 0x0000, "D,S", 0, SIZE_NONE, + cris_ver_v0_10, + cris_move_reg_to_mem_movem_op}, + + {"movem", 0x0BB0, 0x0040, "s,R", 0, SIZE_FIX_32, 0, + cris_move_mem_to_reg_movem_op}, + + {"movem", 0x0BB0, 0x0040, "S,D", 0, SIZE_NONE, + cris_ver_v0_10, + cris_move_mem_to_reg_movem_op}, + + {"moveq", 0x0240, 0x0D80, "i,R", 0, SIZE_NONE, 0, + cris_quick_mode_and_cmp_move_or_op}, + + {"movs", 0x0460, 0x0B80, "z r,R", 0, SIZE_NONE, 0, + cris_reg_mode_add_sub_cmp_and_or_move_op}, + + /* FIXME: SIZE_FIELD_SIGNED and all necessary changes. */ + {"movs", 0x0860, 0x0380, "z s,R", 0, SIZE_FIELD, 0, + cris_none_reg_mode_add_sub_cmp_and_or_move_op}, + + {"movs", 0x0860, 0x0380, "z S,D", 0, SIZE_NONE, + cris_ver_v0_10, + cris_none_reg_mode_add_sub_cmp_and_or_move_op}, + + {"movu", 0x0440, 0x0Ba0, "z r,R", 0, SIZE_NONE, 0, + cris_reg_mode_add_sub_cmp_and_or_move_op}, + + /* FIXME: SIZE_FIELD_UNSIGNED and all necessary changes. */ + {"movu", 0x0840, 0x03a0, "z s,R", 0, SIZE_FIELD, 0, + cris_none_reg_mode_add_sub_cmp_and_or_move_op}, + + {"movu", 0x0840, 0x03a0, "z S,D", 0, SIZE_NONE, + cris_ver_v0_10, + cris_none_reg_mode_add_sub_cmp_and_or_move_op}, + + {"mstep", 0x07f0, 0x0800, "r,R", 0, SIZE_NONE, + cris_ver_v0_10, + cris_dstep_logshift_mstep_neg_not_op}, + + {"muls", 0x0d00, 0x02c0, "m r,R", 0, SIZE_NONE, + cris_ver_v10p, + cris_muls_op}, + + {"mulu", 0x0900, 0x06c0, "m r,R", 0, SIZE_NONE, + cris_ver_v10p, + cris_mulu_op}, + + {"neg", 0x0580, 0x0A40, "m r,R", 0, SIZE_NONE, 0, + cris_dstep_logshift_mstep_neg_not_op}, + + {"nop", NOP_OPCODE, NOP_Z_BITS, "", 0, SIZE_NONE, + cris_ver_v0_10, + cris_btst_nop_op}, + + {"nop", NOP_OPCODE_V32, NOP_Z_BITS_V32, "", 0, SIZE_NONE, + cris_ver_v32p, + cris_btst_nop_op}, + + {"not", 0x8770, 0x7880, "r", 0, SIZE_NONE, 0, + cris_dstep_logshift_mstep_neg_not_op}, + + {"or", 0x0740, 0x0880, "m r,R", 0, SIZE_NONE, 0, + cris_reg_mode_add_sub_cmp_and_or_move_op}, + + {"or", 0x0B40, 0x0080, "m s,R", 0, SIZE_FIELD, 0, + cris_none_reg_mode_add_sub_cmp_and_or_move_op}, + + {"or", 0x0B40, 0x0080, "m S,D", 0, SIZE_NONE, + cris_ver_v0_10, + cris_none_reg_mode_add_sub_cmp_and_or_move_op}, + + {"or", 0x0B40, 0x0480, "m S,R,r", 0, SIZE_NONE, + cris_ver_v0_10, + cris_three_operand_add_sub_cmp_and_or_op}, + + {"orq", 0x0340, 0x0C80, "i,R", 0, SIZE_NONE, 0, + cris_quick_mode_and_cmp_move_or_op}, + + {"pop", 0x0E6E, 0x0191, "!R", 0, SIZE_NONE, + cris_ver_v0_10, + cris_none_reg_mode_add_sub_cmp_and_or_move_op}, + + {"pop", 0x0e3e, 0x01c1, "!P", 0, SIZE_NONE, + cris_ver_v0_10, + cris_none_reg_mode_move_from_preg_op}, + + {"push", 0x0FEE, 0x0011, "BR", 0, SIZE_NONE, + cris_ver_v0_10, + cris_none_reg_mode_add_sub_cmp_and_or_move_op}, + + {"push", 0x0E7E, 0x0181, "BP", 0, SIZE_NONE, + cris_ver_v0_10, + cris_move_to_preg_op}, + + {"rbf", 0x3b30, 0xc0c0, "y", 0, SIZE_NONE, + cris_ver_v10, + cris_not_implemented_op}, + + {"rbf", 0x3b30, 0xc0c0, "S", 0, SIZE_NONE, + cris_ver_v10, + cris_not_implemented_op}, + + {"rfe", 0x2930, 0xD6CF, "", 0, SIZE_NONE, + cris_ver_v32p, + cris_not_implemented_op}, + + {"rfg", 0x4930, 0xB6CF, "", 0, SIZE_NONE, + cris_ver_v32p, + cris_not_implemented_op}, + + {"rfn", 0x5930, 0xA6CF, "", 0, SIZE_NONE, + cris_ver_v32p, + cris_not_implemented_op}, + + {"ret", 0xB67F, 0x4980, "", 1, SIZE_NONE, + cris_ver_v0_10, + cris_reg_mode_move_from_preg_op}, + + {"ret", 0xB9F0, 0x460F, "", 1, SIZE_NONE, + cris_ver_v32p, + cris_reg_mode_move_from_preg_op}, + + {"retb", 0xe67f, 0x1980, "", 1, SIZE_NONE, + cris_ver_v0_10, + cris_reg_mode_move_from_preg_op}, + + {"rete", 0xA9F0, 0x560F, "", 1, SIZE_NONE, + cris_ver_v32p, + cris_reg_mode_move_from_preg_op}, + + {"reti", 0xA67F, 0x5980, "", 1, SIZE_NONE, + cris_ver_v0_10, + cris_reg_mode_move_from_preg_op}, + + {"retn", 0xC9F0, 0x360F, "", 1, SIZE_NONE, + cris_ver_v32p, + cris_reg_mode_move_from_preg_op}, + + {"sbfs", 0x3b70, 0xc080, "y", 0, SIZE_NONE, + cris_ver_v10, + cris_not_implemented_op}, + + {"sbfs", 0x3b70, 0xc080, "S", 0, SIZE_NONE, + cris_ver_v10, + cris_not_implemented_op}, + + {"sa", + 0x0530+CC_A*0x1000, + 0x0AC0+(0xf-CC_A)*0x1000, "r", 0, SIZE_NONE, 0, + cris_scc_op}, + + {"ssb", + 0x0530+CC_EXT*0x1000, + 0x0AC0+(0xf-CC_EXT)*0x1000, "r", 0, SIZE_NONE, + cris_ver_v32p, + cris_scc_op}, + + {"scc", + 0x0530+CC_CC*0x1000, + 0x0AC0+(0xf-CC_CC)*0x1000, "r", 0, SIZE_NONE, 0, + cris_scc_op}, + + {"scs", + 0x0530+CC_CS*0x1000, + 0x0AC0+(0xf-CC_CS)*0x1000, "r", 0, SIZE_NONE, 0, + cris_scc_op}, + + {"seq", + 0x0530+CC_EQ*0x1000, + 0x0AC0+(0xf-CC_EQ)*0x1000, "r", 0, SIZE_NONE, 0, + cris_scc_op}, + + {"setf", 0x05b0, 0x0A40, "f", 0, SIZE_NONE, 0, + cris_ax_ei_setf_op}, + + {"sfe", 0x3930, 0xC6CF, "", 0, SIZE_NONE, + cris_ver_v32p, + cris_not_implemented_op}, + + /* Need to have "swf" in front of "sext" so it is the one displayed in + disassembly. */ + {"swf", + 0x0530+CC_EXT*0x1000, + 0x0AC0+(0xf-CC_EXT)*0x1000, "r", 0, SIZE_NONE, + cris_ver_v10, + cris_scc_op}, + + {"sext", + 0x0530+CC_EXT*0x1000, + 0x0AC0+(0xf-CC_EXT)*0x1000, "r", 0, SIZE_NONE, + cris_ver_v0_3, + cris_scc_op}, + + {"sge", + 0x0530+CC_GE*0x1000, + 0x0AC0+(0xf-CC_GE)*0x1000, "r", 0, SIZE_NONE, 0, + cris_scc_op}, + + {"sgt", + 0x0530+CC_GT*0x1000, + 0x0AC0+(0xf-CC_GT)*0x1000, "r", 0, SIZE_NONE, 0, + cris_scc_op}, + + {"shi", + 0x0530+CC_HI*0x1000, + 0x0AC0+(0xf-CC_HI)*0x1000, "r", 0, SIZE_NONE, 0, + cris_scc_op}, + + {"shs", + 0x0530+CC_HS*0x1000, + 0x0AC0+(0xf-CC_HS)*0x1000, "r", 0, SIZE_NONE, 0, + cris_scc_op}, + + {"sle", + 0x0530+CC_LE*0x1000, + 0x0AC0+(0xf-CC_LE)*0x1000, "r", 0, SIZE_NONE, 0, + cris_scc_op}, + + {"slo", + 0x0530+CC_LO*0x1000, + 0x0AC0+(0xf-CC_LO)*0x1000, "r", 0, SIZE_NONE, 0, + cris_scc_op}, + + {"sls", + 0x0530+CC_LS*0x1000, + 0x0AC0+(0xf-CC_LS)*0x1000, "r", 0, SIZE_NONE, 0, + cris_scc_op}, + + {"slt", + 0x0530+CC_LT*0x1000, + 0x0AC0+(0xf-CC_LT)*0x1000, "r", 0, SIZE_NONE, 0, + cris_scc_op}, + + {"smi", + 0x0530+CC_MI*0x1000, + 0x0AC0+(0xf-CC_MI)*0x1000, "r", 0, SIZE_NONE, 0, + cris_scc_op}, + + {"sne", + 0x0530+CC_NE*0x1000, + 0x0AC0+(0xf-CC_NE)*0x1000, "r", 0, SIZE_NONE, 0, + cris_scc_op}, + + {"spl", + 0x0530+CC_PL*0x1000, + 0x0AC0+(0xf-CC_PL)*0x1000, "r", 0, SIZE_NONE, 0, + cris_scc_op}, + + {"sub", 0x0680, 0x0940, "m r,R", 0, SIZE_NONE, 0, + cris_reg_mode_add_sub_cmp_and_or_move_op}, + + {"sub", 0x0a80, 0x0140, "m s,R", 0, SIZE_FIELD, 0, + cris_none_reg_mode_add_sub_cmp_and_or_move_op}, + + {"sub", 0x0a80, 0x0140, "m S,D", 0, SIZE_NONE, + cris_ver_v0_10, + cris_none_reg_mode_add_sub_cmp_and_or_move_op}, + + {"sub", 0x0a80, 0x0540, "m S,R,r", 0, SIZE_NONE, + cris_ver_v0_10, + cris_three_operand_add_sub_cmp_and_or_op}, + + {"subq", 0x0280, 0x0d40, "I,R", 0, SIZE_NONE, 0, + cris_quick_mode_add_sub_op}, + + {"subs", 0x04a0, 0x0b40, "z r,R", 0, SIZE_NONE, 0, + cris_reg_mode_add_sub_cmp_and_or_move_op}, + + /* FIXME: SIZE_FIELD_SIGNED and all necessary changes. */ + {"subs", 0x08a0, 0x0340, "z s,R", 0, SIZE_FIELD, 0, + cris_none_reg_mode_add_sub_cmp_and_or_move_op}, + + {"subs", 0x08a0, 0x0340, "z S,D", 0, SIZE_NONE, + cris_ver_v0_10, + cris_none_reg_mode_add_sub_cmp_and_or_move_op}, + + {"subs", 0x08a0, 0x0740, "z S,R,r", 0, SIZE_NONE, + cris_ver_v0_10, + cris_three_operand_add_sub_cmp_and_or_op}, + + {"subu", 0x0480, 0x0b60, "z r,R", 0, SIZE_NONE, 0, + cris_reg_mode_add_sub_cmp_and_or_move_op}, + + /* FIXME: SIZE_FIELD_UNSIGNED and all necessary changes. */ + {"subu", 0x0880, 0x0360, "z s,R", 0, SIZE_FIELD, 0, + cris_none_reg_mode_add_sub_cmp_and_or_move_op}, + + {"subu", 0x0880, 0x0360, "z S,D", 0, SIZE_NONE, + cris_ver_v0_10, + cris_none_reg_mode_add_sub_cmp_and_or_move_op}, + + {"subu", 0x0880, 0x0760, "z S,R,r", 0, SIZE_NONE, + cris_ver_v0_10, + cris_three_operand_add_sub_cmp_and_or_op}, + + {"svc", + 0x0530+CC_VC*0x1000, + 0x0AC0+(0xf-CC_VC)*0x1000, "r", 0, SIZE_NONE, 0, + cris_scc_op}, + + {"svs", + 0x0530+CC_VS*0x1000, + 0x0AC0+(0xf-CC_VS)*0x1000, "r", 0, SIZE_NONE, 0, + cris_scc_op}, + + /* The insn "swapn" is the same as "not" and will be disassembled as + such, but the swap* family of mnmonics are generally v8-and-higher + only, so count it in. */ + {"swapn", 0x8770, 0x7880, "r", 0, SIZE_NONE, + cris_ver_v8p, + cris_not_implemented_op}, + + {"swapw", 0x4770, 0xb880, "r", 0, SIZE_NONE, + cris_ver_v8p, + cris_not_implemented_op}, + + {"swapnw", 0xc770, 0x3880, "r", 0, SIZE_NONE, + cris_ver_v8p, + cris_not_implemented_op}, + + {"swapb", 0x2770, 0xd880, "r", 0, SIZE_NONE, + cris_ver_v8p, + cris_not_implemented_op}, + + {"swapnb", 0xA770, 0x5880, "r", 0, SIZE_NONE, + cris_ver_v8p, + cris_not_implemented_op}, + + {"swapwb", 0x6770, 0x9880, "r", 0, SIZE_NONE, + cris_ver_v8p, + cris_not_implemented_op}, + + {"swapnwb", 0xE770, 0x1880, "r", 0, SIZE_NONE, + cris_ver_v8p, + cris_not_implemented_op}, + + {"swapr", 0x1770, 0xe880, "r", 0, SIZE_NONE, + cris_ver_v8p, + cris_not_implemented_op}, + + {"swapnr", 0x9770, 0x6880, "r", 0, SIZE_NONE, + cris_ver_v8p, + cris_not_implemented_op}, + + {"swapwr", 0x5770, 0xa880, "r", 0, SIZE_NONE, + cris_ver_v8p, + cris_not_implemented_op}, + + {"swapnwr", 0xd770, 0x2880, "r", 0, SIZE_NONE, + cris_ver_v8p, + cris_not_implemented_op}, + + {"swapbr", 0x3770, 0xc880, "r", 0, SIZE_NONE, + cris_ver_v8p, + cris_not_implemented_op}, + + {"swapnbr", 0xb770, 0x4880, "r", 0, SIZE_NONE, + cris_ver_v8p, + cris_not_implemented_op}, + + {"swapwbr", 0x7770, 0x8880, "r", 0, SIZE_NONE, + cris_ver_v8p, + cris_not_implemented_op}, + + {"swapnwbr", 0xf770, 0x0880, "r", 0, SIZE_NONE, + cris_ver_v8p, + cris_not_implemented_op}, + + {"test", 0x0640, 0x0980, "m D", 0, SIZE_NONE, + cris_ver_v0_10, + cris_reg_mode_test_op}, + + {"test", 0x0b80, 0xf040, "m y", 0, SIZE_FIELD, 0, + cris_none_reg_mode_clear_test_op}, + + {"test", 0x0b80, 0xf040, "m S", 0, SIZE_NONE, + cris_ver_v0_10, + cris_none_reg_mode_clear_test_op}, + + {"xor", 0x07B0, 0x0840, "r,R", 0, SIZE_NONE, 0, + cris_xor_op}, + + {NULL, 0, 0, NULL, 0, 0, 0, cris_not_implemented_op} +}; + +/* Condition-names, indexed by the CC_* numbers as found in cris.h. */ +const char * const +cris_cc_strings[] = +{ + "hs", + "lo", + "ne", + "eq", + "vc", + "vs", + "pl", + "mi", + "ls", + "hi", + "ge", + "lt", + "gt", + "le", + "a", + /* This is a placeholder. In v0, this would be "ext". In v32, this + is "sb". See cris_conds15. */ + "wf" +}; + +/* Different names and semantics for condition 1111 (0xf). */ +const struct cris_cond15 cris_cond15s[] = +{ + /* FIXME: In what version did condition "ext" disappear? */ + {"ext", cris_ver_v0_3}, + {"wf", cris_ver_v10}, + {"sb", cris_ver_v32p}, + {NULL, 0} +}; + + +/* + * Local variables: + * eval: (c-set-style "gnu") + * indent-tabs-mode: t + * End: + */ + + +/* No instruction will be disassembled longer than this. In theory, and + in silicon, address prefixes can be cascaded. In practice, cascading + is not used by GCC, and not supported by the assembler. */ +#ifndef MAX_BYTES_PER_CRIS_INSN +#define MAX_BYTES_PER_CRIS_INSN 8 +#endif + +/* Whether or not to decode prefixes, folding it into the following + instruction. FIXME: Make this optional later. */ +#ifndef PARSE_PREFIX +#define PARSE_PREFIX 1 +#endif + +/* Sometimes we prefix all registers with this character. */ +#define REGISTER_PREFIX_CHAR '$' + +/* Whether or not to trace the following sequence: + sub* X,r%d + bound* Y,r%d + adds.w [pc+r%d.w],pc + + This is the assembly form of a switch-statement in C. + The "sub is optional. If there is none, then X will be zero. + X is the value of the first case, + Y is the number of cases (including default). + + This results in case offsets printed on the form: + case N: -> case_address + where N is an estimation on the corresponding 'case' operand in C, + and case_address is where execution of that case continues after the + sequence presented above. + + The old style of output was to print the offsets as instructions, + which made it hard to follow "case"-constructs in the disassembly, + and caused a lot of annoying warnings about undefined instructions. + + FIXME: Make this optional later. */ +#ifndef TRACE_CASE +#define TRACE_CASE (disdata->trace_case) +#endif + +enum cris_disass_family + { cris_dis_v0_v10, cris_dis_common_v10_v32, cris_dis_v32 }; + +/* Stored in the disasm_info->private_data member. */ +struct cris_disasm_data +{ + /* Whether to print something less confusing if we find something + matching a switch-construct. */ + bfd_boolean trace_case; + + /* Whether this code is flagged as crisv32. FIXME: Should be an enum + that includes "compatible". */ + enum cris_disass_family distype; +}; + +/* Value of first element in switch. */ +static long case_offset = 0; + +/* How many more case-offsets to print. */ +static long case_offset_counter = 0; + +/* Number of case offsets. */ +static long no_of_case_offsets = 0; + +/* Candidate for next case_offset. */ +static long last_immediate = 0; + +static int cris_constraint + (const char *, unsigned, unsigned, struct cris_disasm_data *); + +/* Parse disassembler options and store state in info. FIXME: For the + time being, we abuse static variables. */ + +static bfd_boolean +cris_parse_disassembler_options (disassemble_info *info, + enum cris_disass_family distype) +{ + struct cris_disasm_data *disdata; + + info->private_data = calloc (1, sizeof (struct cris_disasm_data)); + disdata = (struct cris_disasm_data *) info->private_data; + if (disdata == NULL) + return false; + + /* Default true. */ + disdata->trace_case + = (info->disassembler_options == NULL + || (strcmp (info->disassembler_options, "nocase") != 0)); + + disdata->distype = distype; + return true; +} + +static const struct cris_spec_reg * +spec_reg_info (unsigned int sreg, enum cris_disass_family distype) +{ + int i; + + for (i = 0; cris_spec_regs[i].name != NULL; i++) + { + if (cris_spec_regs[i].number == sreg) + { + if (distype == cris_dis_v32) + switch (cris_spec_regs[i].applicable_version) + { + case cris_ver_warning: + case cris_ver_version_all: + case cris_ver_v3p: + case cris_ver_v8p: + case cris_ver_v10p: + case cris_ver_v32p: + /* No ambiguous sizes or register names with CRISv32. */ + if (cris_spec_regs[i].warning == NULL) + return &cris_spec_regs[i]; + default: + ; + } + else if (cris_spec_regs[i].applicable_version != cris_ver_v32p) + return &cris_spec_regs[i]; + } + } + + return NULL; +} + +/* Return the number of bits in the argument. */ + +static int +number_of_bits (unsigned int val) +{ + int bits; + + for (bits = 0; val != 0; val &= val - 1) + bits++; + + return bits; +} + +/* Get an entry in the opcode-table. */ + +static const struct cris_opcode * +get_opcode_entry (unsigned int insn, + unsigned int prefix_insn, + struct cris_disasm_data *disdata) +{ + /* For non-prefixed insns, we keep a table of pointers, indexed by the + insn code. Each entry is initialized when found to be NULL. */ + static const struct cris_opcode **opc_table = NULL; + + const struct cris_opcode *max_matchedp = NULL; + const struct cris_opcode **prefix_opc_table = NULL; + + /* We hold a table for each prefix that need to be handled differently. */ + static const struct cris_opcode **dip_prefixes = NULL; + static const struct cris_opcode **bdapq_m1_prefixes = NULL; + static const struct cris_opcode **bdapq_m2_prefixes = NULL; + static const struct cris_opcode **bdapq_m4_prefixes = NULL; + static const struct cris_opcode **rest_prefixes = NULL; + + /* Allocate and clear the opcode-table. */ + if (opc_table == NULL) + { + opc_table = qemu_malloc (65536 * sizeof (opc_table[0])); + + memset (opc_table, 0, 65536 * sizeof (const struct cris_opcode *)); + + dip_prefixes + = qemu_malloc (65536 * sizeof (const struct cris_opcode **)); + + memset (dip_prefixes, 0, 65536 * sizeof (dip_prefixes[0])); + + bdapq_m1_prefixes + = qemu_malloc (65536 * sizeof (const struct cris_opcode **)); + + memset (bdapq_m1_prefixes, 0, 65536 * sizeof (bdapq_m1_prefixes[0])); + + bdapq_m2_prefixes + = qemu_malloc (65536 * sizeof (const struct cris_opcode **)); + + memset (bdapq_m2_prefixes, 0, 65536 * sizeof (bdapq_m2_prefixes[0])); + + bdapq_m4_prefixes + = qemu_malloc (65536 * sizeof (const struct cris_opcode **)); + + memset (bdapq_m4_prefixes, 0, 65536 * sizeof (bdapq_m4_prefixes[0])); + + rest_prefixes + = qemu_malloc (65536 * sizeof (const struct cris_opcode **)); + + memset (rest_prefixes, 0, 65536 * sizeof (rest_prefixes[0])); + } + + /* Get the right table if this is a prefix. + This code is connected to cris_constraints in that it knows what + prefixes play a role in recognition of patterns; the necessary + state is reflected by which table is used. If constraints + involving match or non-match of prefix insns are changed, then this + probably needs changing too. */ + if (prefix_insn != NO_CRIS_PREFIX) + { + const struct cris_opcode *popcodep + = (opc_table[prefix_insn] != NULL + ? opc_table[prefix_insn] + : get_opcode_entry (prefix_insn, NO_CRIS_PREFIX, disdata)); + + if (popcodep == NULL) + return NULL; + + if (popcodep->match == BDAP_QUICK_OPCODE) + { + /* Since some offsets are recognized with "push" macros, we + have to have different tables for them. */ + int offset = (prefix_insn & 255); + + if (offset > 127) + offset -= 256; + + switch (offset) + { + case -4: + prefix_opc_table = bdapq_m4_prefixes; + break; + + case -2: + prefix_opc_table = bdapq_m2_prefixes; + break; + + case -1: + prefix_opc_table = bdapq_m1_prefixes; + break; + + default: + prefix_opc_table = rest_prefixes; + break; + } + } + else if (popcodep->match == DIP_OPCODE) + /* We don't allow postincrement when the prefix is DIP, so use a + different table for DIP. */ + prefix_opc_table = dip_prefixes; + else + prefix_opc_table = rest_prefixes; + } + + if (prefix_insn != NO_CRIS_PREFIX + && prefix_opc_table[insn] != NULL) + max_matchedp = prefix_opc_table[insn]; + else if (prefix_insn == NO_CRIS_PREFIX && opc_table[insn] != NULL) + max_matchedp = opc_table[insn]; + else + { + const struct cris_opcode *opcodep; + int max_level_of_match = -1; + + for (opcodep = cris_opcodes; + opcodep->name != NULL; + opcodep++) + { + int level_of_match; + + if (disdata->distype == cris_dis_v32) + { + switch (opcodep->applicable_version) + { + case cris_ver_version_all: + break; + + case cris_ver_v0_3: + case cris_ver_v0_10: + case cris_ver_v3_10: + case cris_ver_sim_v0_10: + case cris_ver_v8_10: + case cris_ver_v10: + case cris_ver_warning: + continue; + + case cris_ver_v3p: + case cris_ver_v8p: + case cris_ver_v10p: + case cris_ver_v32p: + break; + + case cris_ver_v8: + abort (); + default: + abort (); + } + } + else + { + switch (opcodep->applicable_version) + { + case cris_ver_version_all: + case cris_ver_v0_3: + case cris_ver_v3p: + case cris_ver_v0_10: + case cris_ver_v8p: + case cris_ver_v8_10: + case cris_ver_v10: + case cris_ver_sim_v0_10: + case cris_ver_v10p: + case cris_ver_warning: + break; + + case cris_ver_v32p: + continue; + + case cris_ver_v8: + abort (); + default: + abort (); + } + } + + /* We give a double lead for bits matching the template in + cris_opcodes. Not even, because then "move p8,r10" would + be given 2 bits lead over "clear.d r10". When there's a + tie, the first entry in the table wins. This is + deliberate, to avoid a more complicated recognition + formula. */ + if ((opcodep->match & insn) == opcodep->match + && (opcodep->lose & insn) == 0 + && ((level_of_match + = cris_constraint (opcodep->args, + insn, + prefix_insn, + disdata)) + >= 0) + && ((level_of_match + += 2 * number_of_bits (opcodep->match + | opcodep->lose)) + > max_level_of_match)) + { + max_matchedp = opcodep; + max_level_of_match = level_of_match; + + /* If there was a full match, never mind looking + further. */ + if (level_of_match >= 2 * 16) + break; + } + } + /* Fill in the new entry. + + If there are changes to the opcode-table involving prefixes, and + disassembly then does not work correctly, try removing the + else-clause below that fills in the prefix-table. If that + helps, you need to change the prefix_opc_table setting above, or + something related. */ + if (prefix_insn == NO_CRIS_PREFIX) + opc_table[insn] = max_matchedp; + else + prefix_opc_table[insn] = max_matchedp; + } + + return max_matchedp; +} + +/* Return -1 if the constraints of a bitwise-matched instruction say + that there is no match. Otherwise return a nonnegative number + indicating the confidence in the match (higher is better). */ + +static int +cris_constraint (const char *cs, + unsigned int insn, + unsigned int prefix_insn, + struct cris_disasm_data *disdata) +{ + int retval = 0; + int tmp; + int prefix_ok = 0; + const char *s; + + for (s = cs; *s; s++) + switch (*s) + { + case '!': + /* Do not recognize "pop" if there's a prefix and then only for + v0..v10. */ + if (prefix_insn != NO_CRIS_PREFIX + || disdata->distype != cris_dis_v0_v10) + return -1; + break; + + case 'U': + /* Not recognized at disassembly. */ + return -1; + + case 'M': + /* Size modifier for "clear", i.e. special register 0, 4 or 8. + Check that it is one of them. Only special register 12 could + be mismatched, but checking for matches is more logical than + checking for mismatches when there are only a few cases. */ + tmp = ((insn >> 12) & 0xf); + if (tmp != 0 && tmp != 4 && tmp != 8) + return -1; + break; + + case 'm': + if ((insn & 0x30) == 0x30) + return -1; + break; + + case 'S': + /* A prefix operand without side-effect. */ + if (prefix_insn != NO_CRIS_PREFIX && (insn & 0x400) == 0) + { + prefix_ok = 1; + break; + } + else + return -1; + + case 's': + case 'y': + case 'Y': + /* If this is a prefixed insn with postincrement (side-effect), + the prefix must not be DIP. */ + if (prefix_insn != NO_CRIS_PREFIX) + { + if (insn & 0x400) + { + const struct cris_opcode *prefix_opcodep + = get_opcode_entry (prefix_insn, NO_CRIS_PREFIX, disdata); + + if (prefix_opcodep->match == DIP_OPCODE) + return -1; + } + + prefix_ok = 1; + } + break; + + case 'B': + /* If we don't fall through, then the prefix is ok. */ + prefix_ok = 1; + + /* A "push" prefix. Check for valid "push" size. + In case of special register, it may be != 4. */ + if (prefix_insn != NO_CRIS_PREFIX) + { + /* Match the prefix insn to BDAPQ. */ + const struct cris_opcode *prefix_opcodep + = get_opcode_entry (prefix_insn, NO_CRIS_PREFIX, disdata); + + if (prefix_opcodep->match == BDAP_QUICK_OPCODE) + { + int pushsize = (prefix_insn & 255); + + if (pushsize > 127) + pushsize -= 256; + + if (s[1] == 'P') + { + unsigned int spec_reg = (insn >> 12) & 15; + const struct cris_spec_reg *sregp + = spec_reg_info (spec_reg, disdata->distype); + + /* For a special-register, the "prefix size" must + match the size of the register. */ + if (sregp && sregp->reg_size == (unsigned int) -pushsize) + break; + } + else if (s[1] == 'R') + { + if ((insn & 0x30) == 0x20 && pushsize == -4) + break; + } + /* FIXME: Should abort here; next constraint letter + *must* be 'P' or 'R'. */ + } + } + return -1; + + case 'D': + retval = (((insn >> 12) & 15) == (insn & 15)); + if (!retval) + return -1; + else + retval += 4; + break; + + case 'P': + { + const struct cris_spec_reg *sregp + = spec_reg_info ((insn >> 12) & 15, disdata->distype); + + /* Since we match four bits, we will give a value of 4-1 = 3 + in a match. If there is a corresponding exact match of a + special register in another pattern, it will get a value of + 4, which will be higher. This should be correct in that an + exact pattern would match better than a general pattern. + + Note that there is a reason for not returning zero; the + pattern for "clear" is partly matched in the bit-pattern + (the two lower bits must be zero), while the bit-pattern + for a move from a special register is matched in the + register constraint. */ + + if (sregp != NULL) + { + retval += 3; + break; + } + else + return -1; + } + } + + if (prefix_insn != NO_CRIS_PREFIX && ! prefix_ok) + return -1; + + return retval; +} + +/* Format number as hex with a leading "0x" into outbuffer. */ + +static char * +format_hex (unsigned long number, + char *outbuffer, + struct cris_disasm_data *disdata) +{ + /* Truncate negative numbers on >32-bit hosts. */ + number &= 0xffffffff; + + sprintf (outbuffer, "0x%lx", number); + + /* Save this value for the "case" support. */ + if (TRACE_CASE) + last_immediate = number; + + return outbuffer + strlen (outbuffer); +} + +/* Format number as decimal into outbuffer. Parameter signedp says + whether the number should be formatted as signed (!= 0) or + unsigned (== 0). */ + +static char * +format_dec (long number, char *outbuffer, int signedp) +{ + last_immediate = number; + sprintf (outbuffer, signedp ? "%ld" : "%lu", number); + + return outbuffer + strlen (outbuffer); +} + +/* Format the name of the general register regno into outbuffer. */ + +static char * +format_reg (struct cris_disasm_data *disdata, + int regno, + char *outbuffer_start, + bfd_boolean with_reg_prefix) +{ + char *outbuffer = outbuffer_start; + + if (with_reg_prefix) + *outbuffer++ = REGISTER_PREFIX_CHAR; + + switch (regno) + { + case 15: + /* For v32, there is no context in which we output PC. */ + if (disdata->distype == cris_dis_v32) + strcpy (outbuffer, "acr"); + else + strcpy (outbuffer, "pc"); + break; + + case 14: + strcpy (outbuffer, "sp"); + break; + + default: + sprintf (outbuffer, "r%d", regno); + break; + } + + return outbuffer_start + strlen (outbuffer_start); +} + +/* Format the name of a support register into outbuffer. */ + +static char * +format_sup_reg (unsigned int regno, + char *outbuffer_start, + bfd_boolean with_reg_prefix) +{ + char *outbuffer = outbuffer_start; + int i; + + if (with_reg_prefix) + *outbuffer++ = REGISTER_PREFIX_CHAR; + + for (i = 0; cris_support_regs[i].name != NULL; i++) + if (cris_support_regs[i].number == regno) + { + sprintf (outbuffer, "%s", cris_support_regs[i].name); + return outbuffer_start + strlen (outbuffer_start); + } + + /* There's supposed to be register names covering all numbers, though + some may be generic names. */ + sprintf (outbuffer, "format_sup_reg-BUG"); + return outbuffer_start + strlen (outbuffer_start); +} + +/* Return the length of an instruction. */ + +static unsigned +bytes_to_skip (unsigned int insn, + const struct cris_opcode *matchedp, + enum cris_disass_family distype, + const struct cris_opcode *prefix_matchedp) +{ + /* Each insn is a word plus "immediate" operands. */ + unsigned to_skip = 2; + const char *template = matchedp->args; + const char *s; + + for (s = template; *s; s++) + if ((*s == 's' || *s == 'N' || *s == 'Y') + && (insn & 0x400) && (insn & 15) == 15 + && prefix_matchedp == NULL) + { + /* Immediate via [pc+], so we have to check the size of the + operand. */ + int mode_size = 1 << ((insn >> 4) & (*template == 'z' ? 1 : 3)); + + if (matchedp->imm_oprnd_size == SIZE_FIX_32) + to_skip += 4; + else if (matchedp->imm_oprnd_size == SIZE_SPEC_REG) + { + const struct cris_spec_reg *sregp + = spec_reg_info ((insn >> 12) & 15, distype); + + /* FIXME: Improve error handling; should have been caught + earlier. */ + if (sregp == NULL) + return 2; + + /* PC is incremented by two, not one, for a byte. Except on + CRISv32, where constants are always DWORD-size for + special registers. */ + to_skip += + distype == cris_dis_v32 ? 4 : (sregp->reg_size + 1) & ~1; + } + else + to_skip += (mode_size + 1) & ~1; + } + else if (*s == 'n') + to_skip += 4; + else if (*s == 'b') + to_skip += 2; + + return to_skip; +} + +/* Print condition code flags. */ + +static char * +print_flags (struct cris_disasm_data *disdata, unsigned int insn, char *cp) +{ + /* Use the v8 (Etrax 100) flag definitions for disassembly. + The differences with v0 (Etrax 1..4) vs. Svinto are: + v0 'd' <=> v8 'm' + v0 'e' <=> v8 'b'. + FIXME: Emit v0..v3 flag names somehow. */ + static const char v8_fnames[] = "cvznxibm"; + static const char v32_fnames[] = "cvznxiup"; + const char *fnames + = disdata->distype == cris_dis_v32 ? v32_fnames : v8_fnames; + + unsigned char flagbits = (((insn >> 8) & 0xf0) | (insn & 15)); + int i; + + for (i = 0; i < 8; i++) + if (flagbits & (1 << i)) + *cp++ = fnames[i]; + + return cp; +} + +/* Print out an insn with its operands, and update the info->insn_type + fields. The prefix_opcodep and the rest hold a prefix insn that is + supposed to be output as an address mode. */ + +static void +print_with_operands (const struct cris_opcode *opcodep, + unsigned int insn, + unsigned char *buffer, + bfd_vma addr, + disassemble_info *info, + /* If a prefix insn was before this insn (and is supposed + to be output as an address), here is a description of + it. */ + const struct cris_opcode *prefix_opcodep, + unsigned int prefix_insn, + unsigned char *prefix_buffer, + bfd_boolean with_reg_prefix) +{ + /* Get a buffer of somewhat reasonable size where we store + intermediate parts of the insn. */ + char temp[sizeof (".d [$r13=$r12-2147483648],$r10") * 2]; + char *tp = temp; + static const char mode_char[] = "bwd?"; + const char *s; + const char *cs; + struct cris_disasm_data *disdata + = (struct cris_disasm_data *) info->private_data; + + /* Print out the name first thing we do. */ + (*info->fprintf_func) (info->stream, "%s", opcodep->name); + + cs = opcodep->args; + s = cs; + + /* Ignore any prefix indicator. */ + if (*s == 'p') + s++; + + if (*s == 'm' || *s == 'M' || *s == 'z') + { + *tp++ = '.'; + + /* Get the size-letter. */ + *tp++ = *s == 'M' + ? (insn & 0x8000 ? 'd' + : insn & 0x4000 ? 'w' : 'b') + : mode_char[(insn >> 4) & (*s == 'z' ? 1 : 3)]; + + /* Ignore the size and the space character that follows. */ + s += 2; + } + + /* Add a space if this isn't a long-branch, because for those will add + the condition part of the name later. */ + if (opcodep->match != (BRANCH_PC_LOW + BRANCH_INCR_HIGH * 256)) + *tp++ = ' '; + + /* Fill in the insn-type if deducible from the name (and there's no + better way). */ + if (opcodep->name[0] == 'j') + { + if (CONST_STRNEQ (opcodep->name, "jsr")) + /* It's "jsr" or "jsrc". */ + info->insn_type = dis_jsr; + else + /* Any other jump-type insn is considered a branch. */ + info->insn_type = dis_branch; + } + + /* We might know some more fields right now. */ + info->branch_delay_insns = opcodep->delayed; + + /* Handle operands. */ + for (; *s; s++) + { + switch (*s) + { + case 'T': + tp = format_sup_reg ((insn >> 12) & 15, tp, with_reg_prefix); + break; + + case 'A': + if (with_reg_prefix) + *tp++ = REGISTER_PREFIX_CHAR; + *tp++ = 'a'; + *tp++ = 'c'; + *tp++ = 'r'; + break; + + case '[': + case ']': + case ',': + *tp++ = *s; + break; + + case '!': + /* Ignore at this point; used at earlier stages to avoid + recognition if there's a prefix at something that in other + ways looks like a "pop". */ + break; + + case 'd': + /* Ignore. This is an optional ".d " on the large one of + relaxable insns. */ + break; + + case 'B': + /* This was the prefix that made this a "push". We've already + handled it by recognizing it, so signal that the prefix is + handled by setting it to NULL. */ + prefix_opcodep = NULL; + break; + + case 'D': + case 'r': + tp = format_reg (disdata, insn & 15, tp, with_reg_prefix); + break; + + case 'R': + tp = format_reg (disdata, (insn >> 12) & 15, tp, with_reg_prefix); + break; + + case 'n': + { + /* Like N but pc-relative to the start of the insn. */ + unsigned long number + = (buffer[2] + buffer[3] * 256 + buffer[4] * 65536 + + buffer[5] * 0x1000000 + addr); + + /* Finish off and output previous formatted bytes. */ + *tp = 0; + if (temp[0]) + (*info->fprintf_func) (info->stream, "%s", temp); + tp = temp; + + (*info->print_address_func) ((bfd_vma) number, info); + } + break; + + case 'u': + { + /* Like n but the offset is bits <3:0> in the instruction. */ + unsigned long number = (buffer[0] & 0xf) * 2 + addr; + + /* Finish off and output previous formatted bytes. */ + *tp = 0; + if (temp[0]) + (*info->fprintf_func) (info->stream, "%s", temp); + tp = temp; + + (*info->print_address_func) ((bfd_vma) number, info); + } + break; + + case 'N': + case 'y': + case 'Y': + case 'S': + case 's': + /* Any "normal" memory operand. */ + if ((insn & 0x400) && (insn & 15) == 15 && prefix_opcodep == NULL) + { + /* We're looking at [pc+], i.e. we need to output an immediate + number, where the size can depend on different things. */ + long number; + int signedp + = ((*cs == 'z' && (insn & 0x20)) + || opcodep->match == BDAP_QUICK_OPCODE); + int nbytes; + + if (opcodep->imm_oprnd_size == SIZE_FIX_32) + nbytes = 4; + else if (opcodep->imm_oprnd_size == SIZE_SPEC_REG) + { + const struct cris_spec_reg *sregp + = spec_reg_info ((insn >> 12) & 15, disdata->distype); + + /* A NULL return should have been as a non-match earlier, + so catch it as an internal error in the error-case + below. */ + if (sregp == NULL) + /* Whatever non-valid size. */ + nbytes = 42; + else + /* PC is always incremented by a multiple of two. + For CRISv32, immediates are always 4 bytes for + special registers. */ + nbytes = disdata->distype == cris_dis_v32 + ? 4 : (sregp->reg_size + 1) & ~1; + } + else + { + int mode_size = 1 << ((insn >> 4) & (*cs == 'z' ? 1 : 3)); + + if (mode_size == 1) + nbytes = 2; + else + nbytes = mode_size; + } + + switch (nbytes) + { + case 1: + number = buffer[2]; + if (signedp && number > 127) + number -= 256; + break; + + case 2: + number = buffer[2] + buffer[3] * 256; + if (signedp && number > 32767) + number -= 65536; + break; + + case 4: + number + = buffer[2] + buffer[3] * 256 + buffer[4] * 65536 + + buffer[5] * 0x1000000; + break; + + default: + strcpy (tp, "bug"); + tp += 3; + number = 42; + } + + if ((*cs == 'z' && (insn & 0x20)) + || (opcodep->match == BDAP_QUICK_OPCODE + && (nbytes <= 2 || buffer[1 + nbytes] == 0))) + tp = format_dec (number, tp, signedp); + else + { + unsigned int highbyte = (number >> 24) & 0xff; + + /* Either output this as an address or as a number. If it's + a dword with the same high-byte as the address of the + insn, assume it's an address, and also if it's a non-zero + non-0xff high-byte. If this is a jsr or a jump, then + it's definitely an address. */ + if (nbytes == 4 + && (highbyte == ((addr >> 24) & 0xff) + || (highbyte != 0 && highbyte != 0xff) + || info->insn_type == dis_branch + || info->insn_type == dis_jsr)) + { + /* Finish off and output previous formatted bytes. */ + *tp = 0; + tp = temp; + if (temp[0]) + (*info->fprintf_func) (info->stream, "%s", temp); + + (*info->print_address_func) ((bfd_vma) number, info); + + info->target = number; + } + else + tp = format_hex (number, tp, disdata); + } + } + else + { + /* Not an immediate number. Then this is a (possibly + prefixed) memory operand. */ + if (info->insn_type != dis_nonbranch) + { + int mode_size + = 1 << ((insn >> 4) + & (opcodep->args[0] == 'z' ? 1 : 3)); + int size; + info->insn_type = dis_dref; + info->flags |= CRIS_DIS_FLAG_MEMREF; + + if (opcodep->imm_oprnd_size == SIZE_FIX_32) + size = 4; + else if (opcodep->imm_oprnd_size == SIZE_SPEC_REG) + { + const struct cris_spec_reg *sregp + = spec_reg_info ((insn >> 12) & 15, disdata->distype); + + /* FIXME: Improve error handling; should have been caught + earlier. */ + if (sregp == NULL) + size = 4; + else + size = sregp->reg_size; + } + else + size = mode_size; + + info->data_size = size; + } + + *tp++ = '['; + + if (prefix_opcodep + /* We don't match dip with a postincremented field + as a side-effect address mode. */ + && ((insn & 0x400) == 0 + || prefix_opcodep->match != DIP_OPCODE)) + { + if (insn & 0x400) + { + tp = format_reg (disdata, insn & 15, tp, with_reg_prefix); + *tp++ = '='; + } + + + /* We mainly ignore the prefix format string when the + address-mode syntax is output. */ + switch (prefix_opcodep->match) + { + case DIP_OPCODE: + /* It's [r], [r+] or [pc+]. */ + if ((prefix_insn & 0x400) && (prefix_insn & 15) == 15) + { + /* It's [pc+]. This cannot possibly be anything + but an address. */ + unsigned long number + = prefix_buffer[2] + prefix_buffer[3] * 256 + + prefix_buffer[4] * 65536 + + prefix_buffer[5] * 0x1000000; + + info->target = (bfd_vma) number; + + /* Finish off and output previous formatted + data. */ + *tp = 0; + tp = temp; + if (temp[0]) + (*info->fprintf_func) (info->stream, "%s", temp); + + (*info->print_address_func) ((bfd_vma) number, info); + } + else + { + /* For a memref in an address, we use target2. + In this case, target is zero. */ + info->flags + |= (CRIS_DIS_FLAG_MEM_TARGET2_IS_REG + | CRIS_DIS_FLAG_MEM_TARGET2_MEM); + + info->target2 = prefix_insn & 15; + + *tp++ = '['; + tp = format_reg (disdata, prefix_insn & 15, tp, + with_reg_prefix); + if (prefix_insn & 0x400) + *tp++ = '+'; + *tp++ = ']'; + } + break; + + case BDAP_QUICK_OPCODE: + { + int number; + + number = prefix_buffer[0]; + if (number > 127) + number -= 256; + + /* Output "reg+num" or, if num < 0, "reg-num". */ + tp = format_reg (disdata, (prefix_insn >> 12) & 15, tp, + with_reg_prefix); + if (number >= 0) + *tp++ = '+'; + tp = format_dec (number, tp, 1); + + info->flags |= CRIS_DIS_FLAG_MEM_TARGET_IS_REG; + info->target = (prefix_insn >> 12) & 15; + info->target2 = (bfd_vma) number; + break; + } + + case BIAP_OPCODE: + /* Output "r+R.m". */ + tp = format_reg (disdata, prefix_insn & 15, tp, + with_reg_prefix); + *tp++ = '+'; + tp = format_reg (disdata, (prefix_insn >> 12) & 15, tp, + with_reg_prefix); + *tp++ = '.'; + *tp++ = mode_char[(prefix_insn >> 4) & 3]; + + info->flags + |= (CRIS_DIS_FLAG_MEM_TARGET2_IS_REG + | CRIS_DIS_FLAG_MEM_TARGET_IS_REG + + | ((prefix_insn & 0x8000) + ? CRIS_DIS_FLAG_MEM_TARGET2_MULT4 + : ((prefix_insn & 0x8000) + ? CRIS_DIS_FLAG_MEM_TARGET2_MULT2 : 0))); + + /* Is it the casejump? It's a "adds.w [pc+r%d.w],pc". */ + if (insn == 0xf83f && (prefix_insn & ~0xf000) == 0x55f) + /* Then start interpreting data as offsets. */ + case_offset_counter = no_of_case_offsets; + break; + + case BDAP_INDIR_OPCODE: + /* Output "r+s.m", or, if "s" is [pc+], "r+s" or + "r-s". */ + tp = format_reg (disdata, (prefix_insn >> 12) & 15, tp, + with_reg_prefix); + + if ((prefix_insn & 0x400) && (prefix_insn & 15) == 15) + { + long number; + unsigned int nbytes; + + /* It's a value. Get its size. */ + int mode_size = 1 << ((prefix_insn >> 4) & 3); + + if (mode_size == 1) + nbytes = 2; + else + nbytes = mode_size; + + switch (nbytes) + { + case 1: + number = prefix_buffer[2]; + if (number > 127) + number -= 256; + break; + + case 2: + number = prefix_buffer[2] + prefix_buffer[3] * 256; + if (number > 32767) + number -= 65536; + break; + + case 4: + number + = prefix_buffer[2] + prefix_buffer[3] * 256 + + prefix_buffer[4] * 65536 + + prefix_buffer[5] * 0x1000000; + break; + + default: + strcpy (tp, "bug"); + tp += 3; + number = 42; + } + + info->flags |= CRIS_DIS_FLAG_MEM_TARGET_IS_REG; + info->target2 = (bfd_vma) number; + + /* If the size is dword, then assume it's an + address. */ + if (nbytes == 4) + { + /* Finish off and output previous formatted + bytes. */ + *tp++ = '+'; + *tp = 0; + tp = temp; + (*info->fprintf_func) (info->stream, "%s", temp); + + (*info->print_address_func) ((bfd_vma) number, info); + } + else + { + if (number >= 0) + *tp++ = '+'; + tp = format_dec (number, tp, 1); + } + } + else + { + /* Output "r+[R].m" or "r+[R+].m". */ + *tp++ = '+'; + *tp++ = '['; + tp = format_reg (disdata, prefix_insn & 15, tp, + with_reg_prefix); + if (prefix_insn & 0x400) + *tp++ = '+'; + *tp++ = ']'; + *tp++ = '.'; + *tp++ = mode_char[(prefix_insn >> 4) & 3]; + + info->flags + |= (CRIS_DIS_FLAG_MEM_TARGET2_IS_REG + | CRIS_DIS_FLAG_MEM_TARGET2_MEM + | CRIS_DIS_FLAG_MEM_TARGET_IS_REG + + | (((prefix_insn >> 4) == 2) + ? 0 + : (((prefix_insn >> 4) & 3) == 1 + ? CRIS_DIS_FLAG_MEM_TARGET2_MEM_WORD + : CRIS_DIS_FLAG_MEM_TARGET2_MEM_BYTE))); + } + break; + + default: + (*info->fprintf_func) (info->stream, "?prefix-bug"); + } + + /* To mark that the prefix is used, reset it. */ + prefix_opcodep = NULL; + } + else + { + tp = format_reg (disdata, insn & 15, tp, with_reg_prefix); + + info->flags |= CRIS_DIS_FLAG_MEM_TARGET_IS_REG; + info->target = insn & 15; + + if (insn & 0x400) + *tp++ = '+'; + } + *tp++ = ']'; + } + break; + + case 'x': + tp = format_reg (disdata, (insn >> 12) & 15, tp, with_reg_prefix); + *tp++ = '.'; + *tp++ = mode_char[(insn >> 4) & 3]; + break; + + case 'I': + tp = format_dec (insn & 63, tp, 0); + break; + + case 'b': + { + int where = buffer[2] + buffer[3] * 256; + + if (where > 32767) + where -= 65536; + + where += addr + ((disdata->distype == cris_dis_v32) ? 0 : 4); + + if (insn == BA_PC_INCR_OPCODE) + info->insn_type = dis_branch; + else + info->insn_type = dis_condbranch; + + info->target = (bfd_vma) where; + + *tp = 0; + tp = temp; + (*info->fprintf_func) (info->stream, "%s%s ", + temp, cris_cc_strings[insn >> 12]); + + (*info->print_address_func) ((bfd_vma) where, info); + } + break; + + case 'c': + tp = format_dec (insn & 31, tp, 0); + break; + + case 'C': + tp = format_dec (insn & 15, tp, 0); + break; + + case 'o': + { + long offset = insn & 0xfe; + bfd_vma target; + + if (insn & 1) + offset |= ~0xff; + + if (opcodep->match == BA_QUICK_OPCODE) + info->insn_type = dis_branch; + else + info->insn_type = dis_condbranch; + + target = addr + ((disdata->distype == cris_dis_v32) ? 0 : 2) + offset; + info->target = target; + *tp = 0; + tp = temp; + (*info->fprintf_func) (info->stream, "%s", temp); + (*info->print_address_func) (target, info); + } + break; + + case 'Q': + case 'O': + { + long number = buffer[0]; + + if (number > 127) + number = number - 256; + + tp = format_dec (number, tp, 1); + *tp++ = ','; + tp = format_reg (disdata, (insn >> 12) & 15, tp, with_reg_prefix); + } + break; + + case 'f': + tp = print_flags (disdata, insn, tp); + break; + + case 'i': + tp = format_dec ((insn & 32) ? (insn & 31) | ~31L : insn & 31, tp, 1); + break; + + case 'P': + { + const struct cris_spec_reg *sregp + = spec_reg_info ((insn >> 12) & 15, disdata->distype); + + if (sregp->name == NULL) + /* Should have been caught as a non-match eariler. */ + *tp++ = '?'; + else + { + if (with_reg_prefix) + *tp++ = REGISTER_PREFIX_CHAR; + strcpy (tp, sregp->name); + tp += strlen (tp); + } + } + break; + + default: + strcpy (tp, "???"); + tp += 3; + } + } + + *tp = 0; + + if (prefix_opcodep) + (*info->fprintf_func) (info->stream, " (OOPS unused prefix \"%s: %s\")", + prefix_opcodep->name, prefix_opcodep->args); + + (*info->fprintf_func) (info->stream, "%s", temp); + + /* Get info for matching case-tables, if we don't have any active. + We assume that the last constant seen is used; either in the insn + itself or in a "move.d const,rN, sub.d rN,rM"-like sequence. */ + if (TRACE_CASE && case_offset_counter == 0) + { + if (CONST_STRNEQ (opcodep->name, "sub")) + case_offset = last_immediate; + + /* It could also be an "add", if there are negative case-values. */ + else if (CONST_STRNEQ (opcodep->name, "add")) + /* The first case is the negated operand to the add. */ + case_offset = -last_immediate; + + /* A bound insn will tell us the number of cases. */ + else if (CONST_STRNEQ (opcodep->name, "bound")) + no_of_case_offsets = last_immediate + 1; + + /* A jump or jsr or branch breaks the chain of insns for a + case-table, so assume default first-case again. */ + else if (info->insn_type == dis_jsr + || info->insn_type == dis_branch + || info->insn_type == dis_condbranch) + case_offset = 0; + } +} + + +/* Print the CRIS instruction at address memaddr on stream. Returns + length of the instruction, in bytes. Prefix register names with `$' if + WITH_REG_PREFIX. */ + +static int +print_insn_cris_generic (bfd_vma memaddr, + disassemble_info *info, + bfd_boolean with_reg_prefix) +{ + int nbytes; + unsigned int insn; + const struct cris_opcode *matchedp; + int advance = 0; + struct cris_disasm_data *disdata + = (struct cris_disasm_data *) info->private_data; + + /* No instruction will be disassembled as longer than this number of + bytes; stacked prefixes will not be expanded. */ + unsigned char buffer[MAX_BYTES_PER_CRIS_INSN]; + unsigned char *bufp; + int status = 0; + bfd_vma addr; + + /* There will be an "out of range" error after the last instruction. + Reading pairs of bytes in decreasing number, we hope that we will get + at least the amount that we will consume. + + If we can't get any data, or we do not get enough data, we print + the error message. */ + + nbytes = info->buffer_length; + if (nbytes > MAX_BYTES_PER_CRIS_INSN) + nbytes = MAX_BYTES_PER_CRIS_INSN; + status = (*info->read_memory_func) (memaddr, buffer, nbytes, info); + + /* If we did not get all we asked for, then clear the rest. + Hopefully this makes a reproducible result in case of errors. */ + if (nbytes != MAX_BYTES_PER_CRIS_INSN) + memset (buffer + nbytes, 0, MAX_BYTES_PER_CRIS_INSN - nbytes); + + addr = memaddr; + bufp = buffer; + + /* Set some defaults for the insn info. */ + info->insn_info_valid = 1; + info->branch_delay_insns = 0; + info->data_size = 0; + info->insn_type = dis_nonbranch; + info->flags = 0; + info->target = 0; + info->target2 = 0; + + /* If we got any data, disassemble it. */ + if (nbytes != 0) + { + matchedp = NULL; + + insn = bufp[0] + bufp[1] * 256; + + /* If we're in a case-table, don't disassemble the offsets. */ + if (TRACE_CASE && case_offset_counter != 0) + { + info->insn_type = dis_noninsn; + advance += 2; + + /* If to print data as offsets, then shortcut here. */ + (*info->fprintf_func) (info->stream, "case %ld%s: -> ", + case_offset + no_of_case_offsets + - case_offset_counter, + case_offset_counter == 1 ? "/default" : + ""); + + (*info->print_address_func) ((bfd_vma) + ((short) (insn) + + (long) (addr + - (no_of_case_offsets + - case_offset_counter) + * 2)), info); + case_offset_counter--; + + /* The default case start (without a "sub" or "add") must be + zero. */ + if (case_offset_counter == 0) + case_offset = 0; + } + else if (insn == 0) + { + /* We're often called to disassemble zeroes. While this is a + valid "bcc .+2" insn, it is also useless enough and enough + of a nuiscance that we will just output "bcc .+2" for it + and signal it as a noninsn. */ + (*info->fprintf_func) (info->stream, + disdata->distype == cris_dis_v32 + ? "bcc ." : "bcc .+2"); + info->insn_type = dis_noninsn; + advance += 2; + } + else + { + const struct cris_opcode *prefix_opcodep = NULL; + unsigned char *prefix_buffer = bufp; + unsigned int prefix_insn = insn; + int prefix_size = 0; + + matchedp = get_opcode_entry (insn, NO_CRIS_PREFIX, disdata); + + /* Check if we're supposed to write out prefixes as address + modes and if this was a prefix. */ + if (matchedp != NULL && PARSE_PREFIX && matchedp->args[0] == 'p') + { + /* If it's a prefix, put it into the prefix vars and get the + main insn. */ + prefix_size = bytes_to_skip (prefix_insn, matchedp, + disdata->distype, NULL); + prefix_opcodep = matchedp; + + insn = bufp[prefix_size] + bufp[prefix_size + 1] * 256; + matchedp = get_opcode_entry (insn, prefix_insn, disdata); + + if (matchedp != NULL) + { + addr += prefix_size; + bufp += prefix_size; + advance += prefix_size; + } + else + { + /* The "main" insn wasn't valid, at least not when + prefixed. Put back things enough to output the + prefix insn only, as a normal insn. */ + matchedp = prefix_opcodep; + insn = prefix_insn; + prefix_opcodep = NULL; + } + } + + if (matchedp == NULL) + { + (*info->fprintf_func) (info->stream, "??0x%x", insn); + advance += 2; + + info->insn_type = dis_noninsn; + } + else + { + advance + += bytes_to_skip (insn, matchedp, disdata->distype, + prefix_opcodep); + + /* The info_type and assorted fields will be set according + to the operands. */ + print_with_operands (matchedp, insn, bufp, addr, info, + prefix_opcodep, prefix_insn, + prefix_buffer, with_reg_prefix); + } + } + } + else + info->insn_type = dis_noninsn; + + /* If we read less than MAX_BYTES_PER_CRIS_INSN, i.e. we got an error + status when reading that much, and the insn decoding indicated a + length exceeding what we read, there is an error. */ + if (status != 0 && (nbytes == 0 || advance > nbytes)) + { + (*info->memory_error_func) (status, memaddr, info); + return -1; + } + + /* Max supported insn size with one folded prefix insn. */ + info->bytes_per_line = MAX_BYTES_PER_CRIS_INSN; + + /* I would like to set this to a fixed value larger than the actual + number of bytes to print in order to avoid spaces between bytes, + but objdump.c (2.9.1) does not like that, so we print 16-bit + chunks, which is the next choice. */ + info->bytes_per_chunk = 2; + + /* Printing bytes in order of increasing addresses makes sense, + especially on a little-endian target. + This is completely the opposite of what you think; setting this to + BFD_ENDIAN_LITTLE will print bytes in order N..0 rather than the 0..N + we want. */ + info->display_endian = BFD_ENDIAN_BIG; + + return advance; +} + +/* Disassemble, prefixing register names with `$'. CRIS v0..v10. */ +static int +print_insn_cris_with_register_prefix (bfd_vma vma, + disassemble_info *info) +{ + if (info->private_data == NULL + && !cris_parse_disassembler_options (info, cris_dis_v0_v10)) + return -1; + return print_insn_cris_generic (vma, info, true); +} +/* Disassemble, prefixing register names with `$'. CRIS v32. */ + +static int +print_insn_crisv32_with_register_prefix (bfd_vma vma, + disassemble_info *info) +{ + if (info->private_data == NULL + && !cris_parse_disassembler_options (info, cris_dis_v32)) + return -1; + return print_insn_cris_generic (vma, info, true); +} + +#if 0 +/* Disassemble, prefixing register names with `$'. + Common v10 and v32 subset. */ + +static int +print_insn_crisv10_v32_with_register_prefix (bfd_vma vma, + disassemble_info *info) +{ + if (info->private_data == NULL + && !cris_parse_disassembler_options (info, cris_dis_common_v10_v32)) + return -1; + return print_insn_cris_generic (vma, info, true); +} + +/* Disassemble, no prefixes on register names. CRIS v0..v10. */ + +static int +print_insn_cris_without_register_prefix (bfd_vma vma, + disassemble_info *info) +{ + if (info->private_data == NULL + && !cris_parse_disassembler_options (info, cris_dis_v0_v10)) + return -1; + return print_insn_cris_generic (vma, info, false); +} + +/* Disassemble, no prefixes on register names. CRIS v32. */ + +static int +print_insn_crisv32_without_register_prefix (bfd_vma vma, + disassemble_info *info) +{ + if (info->private_data == NULL + && !cris_parse_disassembler_options (info, cris_dis_v32)) + return -1; + return print_insn_cris_generic (vma, info, false); +} + +/* Disassemble, no prefixes on register names. + Common v10 and v32 subset. */ + +static int +print_insn_crisv10_v32_without_register_prefix (bfd_vma vma, + disassemble_info *info) +{ + if (info->private_data == NULL + && !cris_parse_disassembler_options (info, cris_dis_common_v10_v32)) + return -1; + return print_insn_cris_generic (vma, info, false); +} +#endif + +int +print_insn_crisv10 (bfd_vma vma, + disassemble_info *info) +{ + return print_insn_cris_with_register_prefix(vma, info); +} + +int +print_insn_crisv32 (bfd_vma vma, + disassemble_info *info) +{ + return print_insn_crisv32_with_register_prefix(vma, info); +} + +/* Return a disassembler-function that prints registers with a `$' prefix, + or one that prints registers without a prefix. + FIXME: We should improve the solution to avoid the multitude of + functions seen above. */ +#if 0 +disassembler_ftype +cris_get_disassembler (bfd *abfd) +{ + /* If there's no bfd in sight, we return what is valid as input in all + contexts if fed back to the assembler: disassembly *with* register + prefix. Unfortunately this will be totally wrong for v32. */ + if (abfd == NULL) + return print_insn_cris_with_register_prefix; + + if (bfd_get_symbol_leading_char (abfd) == 0) + { + if (bfd_get_mach (abfd) == bfd_mach_cris_v32) + return print_insn_crisv32_with_register_prefix; + if (bfd_get_mach (abfd) == bfd_mach_cris_v10_v32) + return print_insn_crisv10_v32_with_register_prefix; + + /* We default to v10. This may be specifically specified in the + bfd mach, but is also the default setting. */ + return print_insn_cris_with_register_prefix; + } + + if (bfd_get_mach (abfd) == bfd_mach_cris_v32) + return print_insn_crisv32_without_register_prefix; + if (bfd_get_mach (abfd) == bfd_mach_cris_v10_v32) + return print_insn_crisv10_v32_without_register_prefix; + return print_insn_cris_without_register_prefix; +} +#endif +/* Local variables: + eval: (c-set-style "gnu") + indent-tabs-mode: t + End: */ diff --git a/cursor.c b/cursor.c new file mode 100644 index 0000000..dfb9eef --- /dev/null +++ b/cursor.c @@ -0,0 +1,210 @@ +#include "qemu-common.h" +#include "console.h" + +#include "cursor_hidden.xpm" +#include "cursor_left_ptr.xpm" + +/* for creating built-in cursors */ +static QEMUCursor *cursor_parse_xpm(const char *xpm[]) +{ + QEMUCursor *c; + uint32_t ctab[128]; + unsigned int width, height, colors, chars; + unsigned int line = 0, i, r, g, b, x, y, pixel; + char name[16]; + uint8_t idx; + + /* parse header line: width, height, #colors, #chars */ + if (sscanf(xpm[line], "%d %d %d %d", &width, &height, &colors, &chars) != 4) { + fprintf(stderr, "%s: header parse error: \"%s\"\n", + __FUNCTION__, xpm[line]); + return NULL; + } + if (chars != 1) { + fprintf(stderr, "%s: chars != 1 not supported\n", __FUNCTION__); + return NULL; + } + line++; + + /* parse color table */ + for (i = 0; i < colors; i++, line++) { + if (sscanf(xpm[line], "%c c %15s", &idx, name) == 2) { + if (sscanf(name, "#%02x%02x%02x", &r, &g, &b) == 3) { + ctab[idx] = (0xff << 24) | (b << 16) | (g << 8) | r; + continue; + } + if (strcmp(name, "None") == 0) { + ctab[idx] = 0x00000000; + continue; + } + } + fprintf(stderr, "%s: color parse error: \"%s\"\n", + __FUNCTION__, xpm[line]); + return NULL; + } + + /* parse pixel data */ + c = cursor_alloc(width, height); + for (pixel = 0, y = 0; y < height; y++, line++) { + for (x = 0; x < height; x++, pixel++) { + idx = xpm[line][x]; + c->data[pixel] = ctab[idx]; + } + } + return c; +} + +/* nice for debugging */ +void cursor_print_ascii_art(QEMUCursor *c, const char *prefix) +{ + uint32_t *data = c->data; + int x,y; + + for (y = 0; y < c->height; y++) { + fprintf(stderr, "%s: %2d: |", prefix, y); + for (x = 0; x < c->width; x++, data++) { + if ((*data & 0xff000000) != 0xff000000) { + fprintf(stderr, " "); /* transparent */ + } else if ((*data & 0x00ffffff) == 0x00ffffff) { + fprintf(stderr, "."); /* white */ + } else if ((*data & 0x00ffffff) == 0x00000000) { + fprintf(stderr, "X"); /* black */ + } else { + fprintf(stderr, "o"); /* other */ + } + } + fprintf(stderr, "|\n"); + } +} + +QEMUCursor *cursor_builtin_hidden(void) +{ + QEMUCursor *c; + + c = cursor_parse_xpm(cursor_hidden_xpm); + return c; +} + +QEMUCursor *cursor_builtin_left_ptr(void) +{ + QEMUCursor *c; + + c = cursor_parse_xpm(cursor_left_ptr_xpm); + return c; +} + +QEMUCursor *cursor_alloc(int width, int height) +{ + QEMUCursor *c; + int datasize = width * height * sizeof(uint32_t); + + c = qemu_mallocz(sizeof(QEMUCursor) + datasize); + c->width = width; + c->height = height; + c->refcount = 1; + return c; +} + +void cursor_get(QEMUCursor *c) +{ + c->refcount++; +} + +void cursor_put(QEMUCursor *c) +{ + if (c == NULL) + return; + c->refcount--; + if (c->refcount) + return; + qemu_free(c); +} + +int cursor_get_mono_bpl(QEMUCursor *c) +{ + return (c->width + 7) / 8; +} + +void cursor_set_mono(QEMUCursor *c, + uint32_t foreground, uint32_t background, uint8_t *image, + int transparent, uint8_t *mask) +{ + uint32_t *data = c->data; + uint8_t bit; + int x,y,bpl; + + bpl = cursor_get_mono_bpl(c); + for (y = 0; y < c->height; y++) { + bit = 0x80; + for (x = 0; x < c->width; x++, data++) { + if (transparent && mask[x/8] & bit) { + *data = 0x00000000; + } else if (!transparent && !(mask[x/8] & bit)) { + *data = 0x00000000; + } else if (image[x/8] & bit) { + *data = 0xff000000 | foreground; + } else { + *data = 0xff000000 | background; + } + bit >>= 1; + if (bit == 0) { + bit = 0x80; + } + } + mask += bpl; + image += bpl; + } +} + +void cursor_get_mono_image(QEMUCursor *c, int foreground, uint8_t *image) +{ + uint32_t *data = c->data; + uint8_t bit; + int x,y,bpl; + + bpl = cursor_get_mono_bpl(c); + memset(image, 0, bpl * c->height); + for (y = 0; y < c->height; y++) { + bit = 0x80; + for (x = 0; x < c->width; x++, data++) { + if (((*data & 0xff000000) == 0xff000000) && + ((*data & 0x00ffffff) == foreground)) { + image[x/8] |= bit; + } + bit >>= 1; + if (bit == 0) { + bit = 0x80; + } + } + image += bpl; + } +} + +void cursor_get_mono_mask(QEMUCursor *c, int transparent, uint8_t *mask) +{ + uint32_t *data = c->data; + uint8_t bit; + int x,y,bpl; + + bpl = cursor_get_mono_bpl(c); + memset(mask, 0, bpl * c->height); + for (y = 0; y < c->height; y++) { + bit = 0x80; + for (x = 0; x < c->width; x++, data++) { + if ((*data & 0xff000000) != 0xff000000) { + if (transparent != 0) { + mask[x/8] |= bit; + } + } else { + if (transparent == 0) { + mask[x/8] |= bit; + } + } + bit >>= 1; + if (bit == 0) { + bit = 0x80; + } + } + mask += bpl; + } +} diff --git a/cursor_hidden.xpm b/cursor_hidden.xpm new file mode 100644 index 0000000..354e7a9 --- /dev/null +++ b/cursor_hidden.xpm @@ -0,0 +1,37 @@ +/* XPM */ +static const char *cursor_hidden_xpm[] = { + "32 32 1 1", + " c None", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", +}; diff --git a/cursor_left_ptr.xpm b/cursor_left_ptr.xpm new file mode 100644 index 0000000..6c9ada9 --- /dev/null +++ b/cursor_left_ptr.xpm @@ -0,0 +1,39 @@ +/* XPM */ +static const char *cursor_left_ptr_xpm[] = { + "32 32 3 1", + "X c #000000", + ". c #ffffff", + " c None", + "X ", + "XX ", + "X.X ", + "X..X ", + "X...X ", + "X....X ", + "X.....X ", + "X......X ", + "X.......X ", + "X........X ", + "X.....XXXXX ", + "X..X..X ", + "X.X X..X ", + "XX X..X ", + "X X..X ", + " X..X ", + " X..X ", + " X..X ", + " XX ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", + " ", +}; diff --git a/cutils.c b/cutils.c new file mode 100644 index 0000000..f9a7e36 --- /dev/null +++ b/cutils.c @@ -0,0 +1,411 @@ +/* + * Simple C functions to supplement the C library + * + * 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 "qemu-common.h" +#include "host-utils.h" +#include + +void pstrcpy(char *buf, int buf_size, const char *str) +{ + int c; + char *q = buf; + + if (buf_size <= 0) + return; + + for(;;) { + c = *str++; + if (c == 0 || q >= buf + buf_size - 1) + break; + *q++ = c; + } + *q = '\0'; +} + +/* strcat and truncate. */ +char *pstrcat(char *buf, int buf_size, const char *s) +{ + int len; + len = strlen(buf); + if (len < buf_size) + pstrcpy(buf + len, buf_size - len, s); + return buf; +} + +int strstart(const char *str, const char *val, const char **ptr) +{ + const char *p, *q; + p = str; + q = val; + while (*q != '\0') { + if (*p != *q) + return 0; + p++; + q++; + } + if (ptr) + *ptr = p; + return 1; +} + +int stristart(const char *str, const char *val, const char **ptr) +{ + const char *p, *q; + p = str; + q = val; + while (*q != '\0') { + if (qemu_toupper(*p) != qemu_toupper(*q)) + return 0; + p++; + q++; + } + if (ptr) + *ptr = p; + return 1; +} + +/* XXX: use host strnlen if available ? */ +int qemu_strnlen(const char *s, int max_len) +{ + int i; + + for(i = 0; i < max_len; i++) { + if (s[i] == '\0') { + break; + } + } + return i; +} + +time_t mktimegm(struct tm *tm) +{ + time_t t; + int y = tm->tm_year + 1900, m = tm->tm_mon + 1, d = tm->tm_mday; + if (m < 3) { + m += 12; + y--; + } + t = 86400 * (d + (153 * m - 457) / 5 + 365 * y + y / 4 - y / 100 + + y / 400 - 719469); + t += 3600 * tm->tm_hour + 60 * tm->tm_min + tm->tm_sec; + return t; +} + +int qemu_fls(int i) +{ + return 32 - clz32(i); +} + +/* + * Make sure data goes on disk, but if possible do not bother to + * write out the inode just for timestamp updates. + * + * Unfortunately even in 2009 many operating systems do not support + * fdatasync and have to fall back to fsync. + */ +int qemu_fdatasync(int fd) +{ +#ifdef CONFIG_FDATASYNC + return fdatasync(fd); +#else + return fsync(fd); +#endif +} + +/* io vectors */ + +void qemu_iovec_init(QEMUIOVector *qiov, int alloc_hint) +{ + qiov->iov = qemu_malloc(alloc_hint * sizeof(struct iovec)); + qiov->niov = 0; + qiov->nalloc = alloc_hint; + qiov->size = 0; +} + +void qemu_iovec_init_external(QEMUIOVector *qiov, struct iovec *iov, int niov) +{ + int i; + + qiov->iov = iov; + qiov->niov = niov; + qiov->nalloc = -1; + qiov->size = 0; + for (i = 0; i < niov; i++) + qiov->size += iov[i].iov_len; +} + +void qemu_iovec_add(QEMUIOVector *qiov, void *base, size_t len) +{ + assert(qiov->nalloc != -1); + + if (qiov->niov == qiov->nalloc) { + qiov->nalloc = 2 * qiov->nalloc + 1; + qiov->iov = qemu_realloc(qiov->iov, qiov->nalloc * sizeof(struct iovec)); + } + qiov->iov[qiov->niov].iov_base = base; + qiov->iov[qiov->niov].iov_len = len; + qiov->size += len; + ++qiov->niov; +} + +/* + * Copies iovecs from src to the end of dst. It starts copying after skipping + * the given number of bytes in src and copies until src is completely copied + * or the total size of the copied iovec reaches size.The size of the last + * copied iovec is changed in order to fit the specified total size if it isn't + * a perfect fit already. + */ +void qemu_iovec_copy(QEMUIOVector *dst, QEMUIOVector *src, uint64_t skip, + size_t size) +{ + int i; + size_t done; + void *iov_base; + uint64_t iov_len; + + assert(dst->nalloc != -1); + + done = 0; + for (i = 0; (i < src->niov) && (done != size); i++) { + if (skip >= src->iov[i].iov_len) { + /* Skip the whole iov */ + skip -= src->iov[i].iov_len; + continue; + } else { + /* Skip only part (or nothing) of the iov */ + iov_base = (uint8_t*) src->iov[i].iov_base + skip; + iov_len = src->iov[i].iov_len - skip; + skip = 0; + } + + if (done + iov_len > size) { + qemu_iovec_add(dst, iov_base, size - done); + break; + } else { + qemu_iovec_add(dst, iov_base, iov_len); + } + done += iov_len; + } +} + +void qemu_iovec_concat(QEMUIOVector *dst, QEMUIOVector *src, size_t size) +{ + qemu_iovec_copy(dst, src, 0, size); +} + +void qemu_iovec_destroy(QEMUIOVector *qiov) +{ + assert(qiov->nalloc != -1); + + qemu_free(qiov->iov); +} + +void qemu_iovec_reset(QEMUIOVector *qiov) +{ + assert(qiov->nalloc != -1); + + qiov->niov = 0; + qiov->size = 0; +} + +void qemu_iovec_to_buffer(QEMUIOVector *qiov, void *buf) +{ + uint8_t *p = (uint8_t *)buf; + int i; + + for (i = 0; i < qiov->niov; ++i) { + memcpy(p, qiov->iov[i].iov_base, qiov->iov[i].iov_len); + p += qiov->iov[i].iov_len; + } +} + +void qemu_iovec_from_buffer(QEMUIOVector *qiov, const void *buf, size_t count) +{ + const uint8_t *p = (const uint8_t *)buf; + size_t copy; + int i; + + for (i = 0; i < qiov->niov && count; ++i) { + copy = count; + if (copy > qiov->iov[i].iov_len) + copy = qiov->iov[i].iov_len; + memcpy(qiov->iov[i].iov_base, p, copy); + p += copy; + count -= copy; + } +} + +void qemu_iovec_memset(QEMUIOVector *qiov, int c, size_t count) +{ + size_t n; + int i; + + for (i = 0; i < qiov->niov && count; ++i) { + n = MIN(count, qiov->iov[i].iov_len); + memset(qiov->iov[i].iov_base, c, n); + count -= n; + } +} + +void qemu_iovec_memset_skip(QEMUIOVector *qiov, int c, size_t count, + size_t skip) +{ + int i; + size_t done; + void *iov_base; + uint64_t iov_len; + + done = 0; + for (i = 0; (i < qiov->niov) && (done != count); i++) { + if (skip >= qiov->iov[i].iov_len) { + /* Skip the whole iov */ + skip -= qiov->iov[i].iov_len; + continue; + } else { + /* Skip only part (or nothing) of the iov */ + iov_base = (uint8_t*) qiov->iov[i].iov_base + skip; + iov_len = qiov->iov[i].iov_len - skip; + skip = 0; + } + + if (done + iov_len > count) { + memset(iov_base, c, count - done); + break; + } else { + memset(iov_base, c, iov_len); + } + done += iov_len; + } +} + +#ifndef _WIN32 +/* Sets a specific flag */ +int fcntl_setfl(int fd, int flag) +{ + int flags; + + flags = fcntl(fd, F_GETFL); + if (flags == -1) + return -errno; + + if (fcntl(fd, F_SETFL, flags | flag) == -1) + return -errno; + + return 0; +} +#endif + +/* + * Convert string to bytes, allowing either B/b for bytes, K/k for KB, + * M/m for MB, G/g for GB or T/t for TB. Default without any postfix + * is MB. End pointer will be returned in *end, if not NULL. A valid + * value must be terminated by whitespace, ',' or '\0'. Return -1 on + * error. + */ +int64_t strtosz_suffix(const char *nptr, char **end, const char default_suffix) +{ + int64_t retval = -1; + char *endptr; + unsigned char c, d; + int mul_required = 0; + double val, mul, integral, fraction; + + errno = 0; + val = strtod(nptr, &endptr); + if (isnan(val) || endptr == nptr || errno != 0) { + goto fail; + } + fraction = modf(val, &integral); + if (fraction != 0) { + mul_required = 1; + } + /* + * Any whitespace character is fine for terminating the number, + * in addition we accept ',' to handle strings where the size is + * part of a multi token argument. + */ + c = *endptr; + d = c; + if (qemu_isspace(c) || c == '\0' || c == ',') { + c = 0; + if (default_suffix) { + d = default_suffix; + } else { + d = c; + } + } + switch (qemu_toupper(d)) { + case STRTOSZ_DEFSUFFIX_B: + mul = 1; + if (mul_required) { + goto fail; + } + break; + case STRTOSZ_DEFSUFFIX_KB: + mul = 1 << 10; + break; + case 0: + if (mul_required) { + goto fail; + } + case STRTOSZ_DEFSUFFIX_MB: + mul = 1ULL << 20; + break; + case STRTOSZ_DEFSUFFIX_GB: + mul = 1ULL << 30; + break; + case STRTOSZ_DEFSUFFIX_TB: + mul = 1ULL << 40; + break; + default: + goto fail; + } + /* + * If not terminated by whitespace, ',', or \0, increment endptr + * to point to next character, then check that we are terminated + * by an appropriate separating character, ie. whitespace, ',', or + * \0. If not, we are seeing trailing garbage, thus fail. + */ + if (c != 0) { + endptr++; + if (!qemu_isspace(*endptr) && *endptr != ',' && *endptr != 0) { + goto fail; + } + } + if ((val * mul >= INT64_MAX) || val < 0) { + goto fail; + } + retval = val * mul; + +fail: + if (end) { + *end = endptr; + } + + return retval; +} + +int64_t strtosz(const char *nptr, char **end) +{ + return strtosz_suffix(nptr, end, STRTOSZ_DEFSUFFIX_MB); +} diff --git a/darwin-user/commpage.c b/darwin-user/commpage.c new file mode 100644 index 0000000..f6aa71e --- /dev/null +++ b/darwin-user/commpage.c @@ -0,0 +1,357 @@ + /* + * Commpage syscalls + * + * Copyright (c) 2006 Pierre d'Herbemont + * + * 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 . + */ +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "qemu.h" + +//#define DEBUG_COMMPAGE + +#ifdef DEBUG_COMMPAGE +# define DPRINTF(...) do { qemu_log(__VA_ARGS__); printf(__VA_ARGS__); } while(0) +#else +# define DPRINTF(...) do { qemu_log(__VA_ARGS__); } while(0) +#endif + +/******************************************************************** + * Commpage definitions + */ +#ifdef TARGET_I386 +/* Reserve space for the commpage see xnu/osfmk/i386/cpu_capabilities.h */ +# define COMMPAGE_START (-16 * 4096) /* base address is -20 * 4096 */ +# define COMMPAGE_SIZE (0x1240) /* _COMM_PAGE_AREA_LENGTH is 19 * 4096 */ +#elif defined(TARGET_PPC) +/* Reserve space for the commpage see xnu/osfmk/ppc/cpu_capabilities.h */ +# define COMMPAGE_START (-8*4096) +# define COMMPAGE_SIZE (2*4096) /* its _COMM_PAGE_AREA_USED but _COMM_PAGE_AREA_LENGTH is 7*4096 */ +#endif + +void do_compare_and_swap32(void *cpu_env, int num); +void do_compare_and_swap64(void *cpu_env, int num); +void do_add_atomic_word32(void *cpu_env, int num); +void do_cgettimeofday(void *cpu_env, int num, uint32_t arg1); +void do_nanotime(void *cpu_env, int num); + +void unimpl_commpage(void *cpu_env, int num); + +typedef void (*commpage_8args_function_t)(uint32_t arg1, uint32_t arg2, uint32_t arg3, + uint32_t arg4, uint32_t arg5, uint32_t arg6, uint32_t arg7, + uint32_t arg8); +typedef void (*commpage_indirect_function_t)(void *cpu_env, int num, uint32_t arg1, + uint32_t arg2, uint32_t arg3, uint32_t arg4, uint32_t arg5, + uint32_t arg6, uint32_t arg7, uint32_t arg8); + +#define HAS_PTR 0x10 +#define NO_PTR 0x20 +#define CALL_DIRECT 0x1 +#define CALL_INDIRECT 0x2 + +#define COMMPAGE_ENTRY(name, nargs, offset, func, options) \ + { #name, offset, nargs, options, (commpage_8args_function_t)func } + +struct commpage_entry { + char * name; + int offset; + int nargs; + char options; + commpage_8args_function_t function; +}; + +static inline int commpage_code_num(struct commpage_entry *entry) +{ + if((entry->options & HAS_PTR)) + return entry->offset + 4; + else + return entry->offset; +} + +static inline int commpage_is_indirect(struct commpage_entry *entry) +{ + return !(entry->options & CALL_DIRECT); +} + +/******************************************************************** + * Commpage entry + */ +static struct commpage_entry commpage_entries[] = +{ + COMMPAGE_ENTRY(compare_and_swap32, 0, 0x080, do_compare_and_swap32, CALL_INDIRECT | HAS_PTR), + COMMPAGE_ENTRY(compare_and_swap64, 0, 0x0c0, do_compare_and_swap64, CALL_INDIRECT | HAS_PTR), + COMMPAGE_ENTRY(enqueue, 0, 0x100, unimpl_commpage, CALL_INDIRECT), + COMMPAGE_ENTRY(dequeue, 0, 0x140, unimpl_commpage, CALL_INDIRECT), + COMMPAGE_ENTRY(memory_barrier, 0, 0x180, unimpl_commpage, CALL_INDIRECT), + COMMPAGE_ENTRY(add_atomic_word32, 0, 0x1a0, do_add_atomic_word32, CALL_INDIRECT | HAS_PTR), + COMMPAGE_ENTRY(add_atomic_word64, 0, 0x1c0, unimpl_commpage, CALL_INDIRECT | HAS_PTR), + + COMMPAGE_ENTRY(mach_absolute_time, 0, 0x200, unimpl_commpage, CALL_INDIRECT), + COMMPAGE_ENTRY(spinlock_try, 1, 0x220, unimpl_commpage, CALL_INDIRECT), + COMMPAGE_ENTRY(spinlock_lock, 1, 0x260, OSSpinLockLock, CALL_DIRECT), + COMMPAGE_ENTRY(spinlock_unlock, 1, 0x2a0, OSSpinLockUnlock, CALL_DIRECT), + COMMPAGE_ENTRY(pthread_getspecific, 0, 0x2c0, unimpl_commpage, CALL_INDIRECT), + COMMPAGE_ENTRY(gettimeofday, 1, 0x2e0, do_cgettimeofday, CALL_INDIRECT), + COMMPAGE_ENTRY(sys_dcache_flush, 0, 0x4e0, unimpl_commpage, CALL_INDIRECT), + COMMPAGE_ENTRY(sys_icache_invalidate, 0, 0x520, unimpl_commpage, CALL_INDIRECT), + COMMPAGE_ENTRY(pthread_self, 0, 0x580, unimpl_commpage, CALL_INDIRECT), + + COMMPAGE_ENTRY(relinquish, 0, 0x5c0, unimpl_commpage, CALL_INDIRECT), + +#ifdef TARGET_I386 + COMMPAGE_ENTRY(bts, 0, 0x5e0, unimpl_commpage, CALL_INDIRECT), + COMMPAGE_ENTRY(btc, 0, 0x5f0, unimpl_commpage, CALL_INDIRECT), +#endif + + COMMPAGE_ENTRY(bzero, 2, 0x600, bzero, CALL_DIRECT), + COMMPAGE_ENTRY(bcopy, 3, 0x780, bcopy, CALL_DIRECT), + COMMPAGE_ENTRY(memcpy, 3, 0x7a0, memcpy, CALL_DIRECT), + +#ifdef TARGET_I386 + COMMPAGE_ENTRY(old_nanotime, 0, 0xf80, do_nanotime, CALL_INDIRECT), + COMMPAGE_ENTRY(memset_pattern, 0, 0xf80, unimpl_commpage, CALL_INDIRECT), + COMMPAGE_ENTRY(long_copy, 0, 0x1200, unimpl_commpage, CALL_INDIRECT), + + COMMPAGE_ENTRY(sysintegrity, 0, 0x1600, unimpl_commpage, CALL_INDIRECT), + + COMMPAGE_ENTRY(nanotime, 0, 0x1700, do_nanotime, CALL_INDIRECT), +#elif TARGET_PPC + COMMPAGE_ENTRY(compare_and_swap32b, 0, 0xf80, unimpl_commpage, CALL_INDIRECT), + COMMPAGE_ENTRY(compare_and_swap64b, 0, 0xfc0, unimpl_commpage, CALL_INDIRECT), + COMMPAGE_ENTRY(memset_pattern, 0, 0x1000, unimpl_commpage, CALL_INDIRECT), + COMMPAGE_ENTRY(bigcopy, 0, 0x1140, unimpl_commpage, CALL_INDIRECT), +#endif +}; + + +/******************************************************************** + * Commpage backdoor + */ +static inline void print_commpage_entry(struct commpage_entry entry) +{ + printf("@0x%x %s\n", entry.offset, entry.name); +} + +static inline void install_commpage_backdoor_for_entry(struct commpage_entry entry) +{ +#ifdef TARGET_I386 + char * commpage = (char*)(COMMPAGE_START+entry.offset); + int c = 0; + if(entry.options & HAS_PTR) + { + commpage[c++] = (COMMPAGE_START+entry.offset+4) & 0xff; + commpage[c++] = ((COMMPAGE_START+entry.offset+4) >> 8) & 0xff; + commpage[c++] = ((COMMPAGE_START+entry.offset+4) >> 16) & 0xff; + commpage[c++] = ((COMMPAGE_START+entry.offset+4) >> 24) & 0xff; + } + commpage[c++] = 0xcd; + commpage[c++] = 0x79; /* int 0x79 */ + commpage[c++] = 0xc3; /* ret */ +#else + qerror("can't install the commpage on this arch\n"); +#endif +} + +/******************************************************************** + * Commpage initialization + */ +void commpage_init(void) +{ +#if (defined(__i386__) ^ defined(TARGET_I386)) || (defined(_ARCH_PPC) ^ defined(TARGET_PPC)) + int i; + void * commpage = (void *)target_mmap( COMMPAGE_START, COMMPAGE_SIZE, + PROT_WRITE | PROT_READ, MAP_ANONYMOUS | MAP_FIXED, -1, 0); + if((int)commpage != COMMPAGE_START) + qerror("can't allocate the commpage\n"); + + bzero(commpage, COMMPAGE_SIZE); + + /* XXX: commpage data not handled */ + + for(i = 0; i < ARRAY_SIZE(commpage_entries); i++) + install_commpage_backdoor_for_entry(commpage_entries[i]); +#else + /* simply map our pages so they can be executed + XXX: we don't really want to do that since in the ppc on ppc situation we may + not able to run commpages host optimized instructions (like G5's on a G5), + hence this is sometimes a broken fix. */ + page_set_flags(COMMPAGE_START, COMMPAGE_START+COMMPAGE_SIZE, PROT_EXEC | PROT_READ | PAGE_VALID); +#endif +} + +/******************************************************************** + * Commpage implementation + */ +void do_compare_and_swap32(void *cpu_env, int num) +{ +#ifdef TARGET_I386 + uint32_t old = ((CPUX86State*)cpu_env)->regs[R_EAX]; + uint32_t *value = (uint32_t*)((CPUX86State*)cpu_env)->regs[R_ECX]; + DPRINTF("commpage: compare_and_swap32(%x,new,%p)\n", old, value); + + if(value && old == tswap32(*value)) + { + uint32_t new = ((CPUX86State*)cpu_env)->regs[R_EDX]; + *value = tswap32(new); + /* set zf flag */ + ((CPUX86State*)cpu_env)->eflags |= 0x40; + } + else + { + ((CPUX86State*)cpu_env)->regs[R_EAX] = tswap32(*value); + /* unset zf flag */ + ((CPUX86State*)cpu_env)->eflags &= ~0x40; + } +#else + qerror("do_compare_and_swap32 unimplemented"); +#endif +} + +void do_compare_and_swap64(void *cpu_env, int num) +{ +#ifdef TARGET_I386 + /* OSAtomicCompareAndSwap64 is not available on non 64 bits ppc, here is a raw implementation */ + uint64_t old, new, swapped_val; + uint64_t *value = (uint64_t*)((CPUX86State*)cpu_env)->regs[R_ESI]; + old = (uint64_t)((uint64_t)((CPUX86State*)cpu_env)->regs[R_EDX]) << 32 | (uint64_t)((CPUX86State*)cpu_env)->regs[R_EAX]; + + DPRINTF("commpage: compare_and_swap64(%" PRIx64 ",new,%p)\n", old, value); + swapped_val = tswap64(*value); + + if(old == swapped_val) + { + new = (uint64_t)((uint64_t)((CPUX86State*)cpu_env)->regs[R_ECX]) << 32 | (uint64_t)((CPUX86State*)cpu_env)->regs[R_EBX]; + *value = tswap64(new); + /* set zf flag */ + ((CPUX86State*)cpu_env)->eflags |= 0x40; + } + else + { + ((CPUX86State*)cpu_env)->regs[R_EAX] = (uint32_t)(swapped_val); + ((CPUX86State*)cpu_env)->regs[R_EDX] = (uint32_t)(swapped_val >> 32); + /* unset zf flag */ + ((CPUX86State*)cpu_env)->eflags &= ~0x40; + } +#else + qerror("do_compare_and_swap64 unimplemented"); +#endif +} + +void do_add_atomic_word32(void *cpu_env, int num) +{ +#ifdef TARGET_I386 + uint32_t amt = ((CPUX86State*)cpu_env)->regs[R_EAX]; + uint32_t *value = (uint32_t*)((CPUX86State*)cpu_env)->regs[R_EDX]; + uint32_t swapped_value = tswap32(*value); + + DPRINTF("commpage: add_atomic_word32(%x,%p)\n", amt, value); + + /* old value in EAX */ + ((CPUX86State*)cpu_env)->regs[R_EAX] = swapped_value; + *value = tswap32(swapped_value + amt); +#else + qerror("do_add_atomic_word32 unimplemented"); +#endif +} + +void do_cgettimeofday(void *cpu_env, int num, uint32_t arg1) +{ +#ifdef TARGET_I386 + extern int __commpage_gettimeofday(struct timeval *); + DPRINTF("commpage: gettimeofday(0x%x)\n", arg1); + struct timeval *time = (struct timeval *)arg1; + int ret = __commpage_gettimeofday(time); + tswap32s((uint32_t*)&time->tv_sec); + tswap32s((uint32_t*)&time->tv_usec); + ((CPUX86State*)cpu_env)->regs[R_EAX] = ret; /* Success */ +#else + qerror("do_gettimeofday unimplemented"); +#endif +} + +void do_nanotime(void *cpu_env, int num) +{ +#ifdef TARGET_I386 + uint64_t t = mach_absolute_time(); + ((CPUX86State*)cpu_env)->regs[R_EAX] = (int)(t & 0xffffffff); + ((CPUX86State*)cpu_env)->regs[R_EDX] = (int)((t >> 32) & 0xffffffff); +#else + qerror("do_nanotime unimplemented"); +#endif +} + +void unimpl_commpage(void *cpu_env, int num) +{ + qerror("qemu: commpage function 0x%x not implemented\n", num); +} + +/******************************************************************** + * do_commpage - called by the main cpu loop + */ +void +do_commpage(void *cpu_env, int num, uint32_t arg1, uint32_t arg2, uint32_t arg3, + uint32_t arg4, uint32_t arg5, uint32_t arg6, uint32_t arg7, + uint32_t arg8) +{ + int i, found = 0; + + arg1 = tswap32(arg1); + arg2 = tswap32(arg2); + arg3 = tswap32(arg3); + arg4 = tswap32(arg4); + arg5 = tswap32(arg5); + arg6 = tswap32(arg6); + arg7 = tswap32(arg7); + arg8 = tswap32(arg8); + + num = num-COMMPAGE_START-2; + + for(i = 0; i < ARRAY_SIZE(commpage_entries); i++) { + if( num == commpage_code_num(&commpage_entries[i]) ) + { + DPRINTF("commpage: %s %s\n", commpage_entries[i].name, commpage_is_indirect(&commpage_entries[i]) ? "[indirect]" : "[direct]"); + found = 1; + if(commpage_is_indirect(&commpage_entries[i])) + { + commpage_indirect_function_t function = (commpage_indirect_function_t)commpage_entries[i].function; + function(cpu_env, num, arg1, arg2, arg3, + arg4, arg5, arg6, arg7, arg8); + } + else + { + commpage_entries[i].function(arg1, arg2, arg3, + arg4, arg5, arg6, arg7, arg8); + } + break; + } + } + + if(!found) + { + gemu_log("qemu: commpage function 0x%x not defined\n", num); + gdb_handlesig (cpu_env, SIGTRAP); + exit(-1); + } +} diff --git a/darwin-user/ioctls.h b/darwin-user/ioctls.h new file mode 100644 index 0000000..dc73af2 --- /dev/null +++ b/darwin-user/ioctls.h @@ -0,0 +1,4 @@ + /* emulated ioctl list */ + + IOCTL(TIOCGETA, IOC_R, MK_PTR(MK_STRUCT(STRUCT_termios))) + IOCTL(TIOCSETA, IOC_W, MK_PTR(MK_STRUCT(STRUCT_termios))) diff --git a/darwin-user/ioctls_types.h b/darwin-user/ioctls_types.h new file mode 100644 index 0000000..014561a --- /dev/null +++ b/darwin-user/ioctls_types.h @@ -0,0 +1 @@ +STRUCT(termios, TYPE_INT, TYPE_INT, TYPE_INT, TYPE_INT, MK_ARRAY(TYPE_CHAR, 20), TYPE_INT, TYPE_INT) diff --git a/darwin-user/machload.c b/darwin-user/machload.c new file mode 100644 index 0000000..3bc3b65 --- /dev/null +++ b/darwin-user/machload.c @@ -0,0 +1,902 @@ +/* + * Mach-O object file loading + * + * Copyright (c) 2006 Pierre d'Herbemont + * + * 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 . + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "qemu.h" +#include "disas.h" + +#include +#include +#include +#include +#include + +//#define DEBUG_MACHLOAD + +#ifdef DEBUG_MACHLOAD +# define DPRINTF(...) do { qemu_log(__VA_ARGS__); printf(__VA_ARGS__); } while(0) +#else +# define DPRINTF(...) do { qemu_log(__VA_ARGS__); } while(0) +#endif + +# define check_mach_header(x) (x.magic == MH_CIGAM) + +extern const char *interp_prefix; + +/* we don't have a good implementation for this */ +#define DONT_USE_DYLD_SHARED_MAP + +/* Pass extra arg to DYLD for debug */ +//#define ACTIVATE_DYLD_TRACE + +//#define OVERRIDE_DYLINKER + +#ifdef OVERRIDE_DYLINKER +# ifdef TARGET_I386 +# define DYLINKER_NAME "/Users/steg/qemu/tests/i386-darwin-env/usr/lib/dyld" +# else +# define DYLINKER_NAME "/usr/lib/dyld" +# endif +#endif + +/* XXX: in an include */ +struct nlist_extended +{ + union { + char *n_name; + long n_strx; + } n_un; + unsigned char n_type; + unsigned char n_sect; + short st_desc; + unsigned long st_value; + unsigned long st_size; +}; + +/* Print symbols in gdb */ +void *macho_text_sect = 0; +int macho_offset = 0; + +int load_object(const char *filename, struct target_pt_regs * regs, void ** mh); + +#ifdef TARGET_I386 +typedef struct mach_i386_thread_state { + unsigned int eax; + unsigned int ebx; + unsigned int ecx; + unsigned int edx; + unsigned int edi; + unsigned int esi; + unsigned int ebp; + unsigned int esp; + unsigned int ss; + unsigned int eflags; + unsigned int eip; + unsigned int cs; + unsigned int ds; + unsigned int es; + unsigned int fs; + unsigned int gs; +} mach_i386_thread_state_t; + +void bswap_i386_thread_state(struct mach_i386_thread_state *ts) +{ + bswap32s((uint32_t*)&ts->eax); + bswap32s((uint32_t*)&ts->ebx); + bswap32s((uint32_t*)&ts->ecx); + bswap32s((uint32_t*)&ts->edx); + bswap32s((uint32_t*)&ts->edi); + bswap32s((uint32_t*)&ts->esi); + bswap32s((uint32_t*)&ts->ebp); + bswap32s((uint32_t*)&ts->esp); + bswap32s((uint32_t*)&ts->ss); + bswap32s((uint32_t*)&ts->eflags); + bswap32s((uint32_t*)&ts->eip); + bswap32s((uint32_t*)&ts->cs); + bswap32s((uint32_t*)&ts->ds); + bswap32s((uint32_t*)&ts->es); + bswap32s((uint32_t*)&ts->fs); + bswap32s((uint32_t*)&ts->gs); +} +#define target_thread_state mach_i386_thread_state +#define TARGET_CPU_TYPE CPU_TYPE_I386 +#define TARGET_CPU_NAME "i386" +#endif + +#ifdef TARGET_PPC +struct mach_ppc_thread_state { + unsigned int srr0; /* Instruction address register (PC) */ + unsigned int srr1; /* Machine state register (supervisor) */ + unsigned int r0; + unsigned int r1; + unsigned int r2; + unsigned int r3; + unsigned int r4; + unsigned int r5; + unsigned int r6; + unsigned int r7; + unsigned int r8; + unsigned int r9; + unsigned int r10; + unsigned int r11; + unsigned int r12; + unsigned int r13; + unsigned int r14; + unsigned int r15; + unsigned int r16; + unsigned int r17; + unsigned int r18; + unsigned int r19; + unsigned int r20; + unsigned int r21; + unsigned int r22; + unsigned int r23; + unsigned int r24; + unsigned int r25; + unsigned int r26; + unsigned int r27; + unsigned int r28; + unsigned int r29; + unsigned int r30; + unsigned int r31; + + unsigned int cr; /* Condition register */ + unsigned int xer; /* User's integer exception register */ + unsigned int lr; /* Link register */ + unsigned int ctr; /* Count register */ + unsigned int mq; /* MQ register (601 only) */ + + unsigned int vrsave; /* Vector Save Register */ +}; + +void bswap_ppc_thread_state(struct mach_ppc_thread_state *ts) +{ + bswap32s((uint32_t*)&ts->srr0); + bswap32s((uint32_t*)&ts->srr1); + bswap32s((uint32_t*)&ts->r0); + bswap32s((uint32_t*)&ts->r1); + bswap32s((uint32_t*)&ts->r2); + bswap32s((uint32_t*)&ts->r3); + bswap32s((uint32_t*)&ts->r4); + bswap32s((uint32_t*)&ts->r5); + bswap32s((uint32_t*)&ts->r6); + bswap32s((uint32_t*)&ts->r7); + bswap32s((uint32_t*)&ts->r8); + bswap32s((uint32_t*)&ts->r9); + bswap32s((uint32_t*)&ts->r10); + bswap32s((uint32_t*)&ts->r11); + bswap32s((uint32_t*)&ts->r12); + bswap32s((uint32_t*)&ts->r13); + bswap32s((uint32_t*)&ts->r14); + bswap32s((uint32_t*)&ts->r15); + bswap32s((uint32_t*)&ts->r16); + bswap32s((uint32_t*)&ts->r17); + bswap32s((uint32_t*)&ts->r18); + bswap32s((uint32_t*)&ts->r19); + bswap32s((uint32_t*)&ts->r20); + bswap32s((uint32_t*)&ts->r21); + bswap32s((uint32_t*)&ts->r22); + bswap32s((uint32_t*)&ts->r23); + bswap32s((uint32_t*)&ts->r24); + bswap32s((uint32_t*)&ts->r25); + bswap32s((uint32_t*)&ts->r26); + bswap32s((uint32_t*)&ts->r27); + bswap32s((uint32_t*)&ts->r28); + bswap32s((uint32_t*)&ts->r29); + bswap32s((uint32_t*)&ts->r30); + bswap32s((uint32_t*)&ts->r31); + + bswap32s((uint32_t*)&ts->cr); + bswap32s((uint32_t*)&ts->xer); + bswap32s((uint32_t*)&ts->lr); + bswap32s((uint32_t*)&ts->ctr); + bswap32s((uint32_t*)&ts->mq); + + bswap32s((uint32_t*)&ts->vrsave); +} + +#define target_thread_state mach_ppc_thread_state +#define TARGET_CPU_TYPE CPU_TYPE_POWERPC +#define TARGET_CPU_NAME "PowerPC" +#endif + +struct target_thread_command { + unsigned long cmd; /* LC_THREAD or LC_UNIXTHREAD */ + unsigned long cmdsize; /* total size of this command */ + unsigned long flavor; /* flavor of thread state */ + unsigned long count; /* count of longs in thread state */ + struct target_thread_state state; /* thread state for this flavor */ +}; + +void bswap_tc(struct target_thread_command *tc) +{ + bswap32s((uint32_t*)(&tc->flavor)); + bswap32s((uint32_t*)&tc->count); +#if defined(TARGET_I386) + bswap_i386_thread_state(&tc->state); +#elif defined(TARGET_PPC) + bswap_ppc_thread_state(&tc->state); +#else +# error unknown TARGET_CPU_TYPE +#endif +} + +void bswap_mh(struct mach_header *mh) +{ + bswap32s((uint32_t*)(&mh->magic)); + bswap32s((uint32_t*)&mh->cputype); + bswap32s((uint32_t*)&mh->cpusubtype); + bswap32s((uint32_t*)&mh->filetype); + bswap32s((uint32_t*)&mh->ncmds); + bswap32s((uint32_t*)&mh->sizeofcmds); + bswap32s((uint32_t*)&mh->flags); +} + +void bswap_lc(struct load_command *lc) +{ + bswap32s((uint32_t*)&lc->cmd); + bswap32s((uint32_t*)&lc->cmdsize); +} + + +void bswap_fh(struct fat_header *fh) +{ + bswap32s((uint32_t*)&fh->magic); + bswap32s((uint32_t*)&fh->nfat_arch); +} + +void bswap_fa(struct fat_arch *fa) +{ + bswap32s((uint32_t*)&fa->cputype); + bswap32s((uint32_t*)&fa->cpusubtype); + bswap32s((uint32_t*)&fa->offset); + bswap32s((uint32_t*)&fa->size); + bswap32s((uint32_t*)&fa->align); +} + +void bswap_segcmd(struct segment_command *sc) +{ + bswap32s((uint32_t*)&sc->vmaddr); + bswap32s((uint32_t*)&sc->vmsize); + bswap32s((uint32_t*)&sc->fileoff); + bswap32s((uint32_t*)&sc->filesize); + bswap32s((uint32_t*)&sc->maxprot); + bswap32s((uint32_t*)&sc->initprot); + bswap32s((uint32_t*)&sc->nsects); + bswap32s((uint32_t*)&sc->flags); +} + +void bswap_symtabcmd(struct symtab_command *stc) +{ + bswap32s((uint32_t*)&stc->cmd); + bswap32s((uint32_t*)&stc->cmdsize); + bswap32s((uint32_t*)&stc->symoff); + bswap32s((uint32_t*)&stc->nsyms); + bswap32s((uint32_t*)&stc->stroff); + bswap32s((uint32_t*)&stc->strsize); +} + +void bswap_sym(struct nlist *n) +{ + bswap32s((uint32_t*)&n->n_un.n_strx); + bswap16s((uint16_t*)&n->n_desc); + bswap32s((uint32_t*)&n->n_value); +} + +int load_thread(struct mach_header *mh, struct target_thread_command *tc, struct target_pt_regs * regs, int fd, int mh_pos, int need_bswap) +{ + int entry; + if(need_bswap) + bswap_tc(tc); +#if defined(TARGET_I386) + entry = tc->state.eip; + DPRINTF(" eax 0x%.8x\n ebx 0x%.8x\n ecx 0x%.8x\n edx 0x%.8x\n edi 0x%.8x\n esi 0x%.8x\n ebp 0x%.8x\n esp 0x%.8x\n ss 0x%.8x\n eflags 0x%.8x\n eip 0x%.8x\n cs 0x%.8x\n ds 0x%.8x\n es 0x%.8x\n fs 0x%.8x\n gs 0x%.8x\n", + tc->state.eax, tc->state.ebx, tc->state.ecx, tc->state.edx, tc->state.edi, tc->state.esi, tc->state.ebp, + tc->state.esp, tc->state.ss, tc->state.eflags, tc->state.eip, tc->state.cs, tc->state.ds, tc->state.es, + tc->state.fs, tc->state.gs ); +#define reg_copy(reg) regs->reg = tc->state.reg + if(regs) + { + reg_copy(eax); + reg_copy(ebx); + reg_copy(ecx); + reg_copy(edx); + + reg_copy(edi); + reg_copy(esi); + + reg_copy(ebp); + reg_copy(esp); + + reg_copy(eflags); + reg_copy(eip); + /* + reg_copy(ss); + reg_copy(cs); + reg_copy(ds); + reg_copy(es); + reg_copy(fs); + reg_copy(gs);*/ + } +#undef reg_copy +#elif defined(TARGET_PPC) + entry = tc->state.srr0; +#endif + DPRINTF("load_thread: entry 0x%x\n", entry); + return entry; +} + +int load_dylinker(struct mach_header *mh, struct dylinker_command *dc, int fd, int mh_pos, int need_bswap) +{ + int size; + char * dylinker_name; + size = dc->cmdsize - sizeof(struct dylinker_command); + + if(need_bswap) + dylinker_name = (char*)(bswap_32(dc->name.offset)+(int)dc); + else + dylinker_name = (char*)((dc->name.offset)+(int)dc); + +#ifdef OVERRIDE_DYLINKER + dylinker_name = DYLINKER_NAME; +#else + if(asprintf(&dylinker_name, "%s%s", interp_prefix, dylinker_name) == -1) + qerror("can't allocate the new dylinker name\n"); +#endif + + DPRINTF("dylinker_name %s\n", dylinker_name); + return load_object(dylinker_name, NULL, NULL); +} + +int load_segment(struct mach_header *mh, struct segment_command *sc, int fd, int mh_pos, int need_bswap, int fixed, int slide) +{ + unsigned long addr = sc->vmaddr; + unsigned long size = sc->filesize; + unsigned long error = 0; + + if(need_bswap) + bswap_segcmd(sc); + + if(sc->vmaddr == 0) + { + DPRINTF("load_segment: sc->vmaddr == 0 returning\n"); + return -1; + } + + if (strcmp(sc->segname, "__PAGEZERO") == 0) + { + DPRINTF("load_segment: __PAGEZERO returning\n"); + return -1; + } + + /* Right now mmap memory */ + /* XXX: should check to see that the space is free, because MAP_FIXED is dangerous */ + DPRINTF("load_segment: mmaping %s to 0x%x-(0x%x|0x%x) + 0x%x\n", sc->segname, sc->vmaddr, sc->filesize, sc->vmsize, slide); + + if(sc->filesize > 0) + { + int opt = 0; + + if(fixed) + opt |= MAP_FIXED; + + DPRINTF("sc->vmaddr 0x%x slide 0x%x add 0x%x\n", slide, sc->vmaddr, sc->vmaddr+slide); + + addr = target_mmap(sc->vmaddr+slide, sc->filesize, sc->initprot, opt, fd, mh_pos + sc->fileoff); + + if(addr==-1) + qerror("load_segment: can't mmap at 0x%x\n", sc->vmaddr+slide); + + error = addr-sc->vmaddr; + } + else + { + addr = sc->vmaddr+slide; + error = slide; + } + + if(sc->vmsize > sc->filesize) + { + addr += sc->filesize; + size = sc->vmsize-sc->filesize; + addr = target_mmap(addr, size, sc->initprot, MAP_ANONYMOUS | MAP_FIXED, -1, 0); + if(addr==-1) + qerror("load_segment: can't mmap at 0x%x\n", sc->vmaddr+slide); + } + + return error; +} + +void *load_data(int fd, long offset, unsigned int size) +{ + char *data; + + data = malloc(size); + if (!data) + return NULL; + lseek(fd, offset, SEEK_SET); + if (read(fd, data, size) != size) { + free(data); + return NULL; + } + return data; +} + +/* load a mach-o object file */ +int load_object(const char *filename, struct target_pt_regs * regs, void ** mh) +{ + int need_bswap = 0; + int entry_point = 0; + int dyld_entry_point = 0; + int slide, mmapfixed; + int fd; + struct load_command *lcmds, *lc; + int is_fat = 0; + unsigned int i, magic; + int mach_hdr_pos = 0; + struct mach_header mach_hdr; + + /* for symbol lookup whith -d flag. */ + struct symtab_command * symtabcmd = 0; + struct nlist_extended *symtab, *sym; + struct nlist *symtab_std, *syment; + char *strtab; + + fd = open(filename, O_RDONLY); + if (fd < 0) + qerror("can't open file '%s'", filename); + + /* Read magic header. */ + if (read(fd, &magic, sizeof (magic)) != sizeof (magic)) + qerror("unable to read Magic of '%s'", filename); + + /* Check Mach identification. */ + if(magic == MH_MAGIC) + { + is_fat = 0; + need_bswap = 0; + } else if (magic == MH_CIGAM) + { + is_fat = 0; + need_bswap = 1; + } else if (magic == FAT_MAGIC) + { + is_fat = 1; + need_bswap = 0; + } else if (magic == FAT_CIGAM) + { + is_fat = 1; + need_bswap = 1; + } + else + qerror("Not a Mach-O file.", filename); + + DPRINTF("loading %s %s...\n", filename, is_fat ? "[FAT]": "[REGULAR]"); + if(is_fat) + { + int found = 0; + struct fat_header fh; + struct fat_arch *fa; + + lseek(fd, 0, SEEK_SET); + + /* Read Fat header. */ + if (read(fd, &fh, sizeof (fh)) != sizeof (fh)) + qerror("unable to read file header"); + + if(need_bswap) + bswap_fh(&fh); + + /* Read Fat Arch. */ + fa = malloc(sizeof(struct fat_arch)*fh.nfat_arch); + + if (read(fd, fa, sizeof(struct fat_arch)*fh.nfat_arch) != sizeof(struct fat_arch)*fh.nfat_arch) + qerror("unable to read file header"); + + for( i = 0; i < fh.nfat_arch; i++, fa++) + { + if(need_bswap) + bswap_fa(fa); + if(fa->cputype == TARGET_CPU_TYPE) + { + mach_hdr_pos = fa->offset; + lseek(fd, mach_hdr_pos, SEEK_SET); + + /* Read Mach header. */ + + if (read(fd, &mach_hdr, sizeof(struct mach_header)) != sizeof (struct mach_header)) + qerror("unable to read file header"); + + if(mach_hdr.magic == MH_MAGIC) + need_bswap = 0; + else if (mach_hdr.magic == MH_CIGAM) + need_bswap = 1; + else + qerror("Invalid mach header in Fat Mach-O File"); + found = 1; + break; + } + } + if(!found) + qerror("%s: No %s CPU found in FAT Header", filename, TARGET_CPU_NAME); + } + else + { + lseek(fd, 0, SEEK_SET); + /* Read Mach header */ + if (read(fd, &mach_hdr, sizeof (mach_hdr)) != sizeof (mach_hdr)) + qerror("%s: unable to read file header", filename); + } + + if(need_bswap) + bswap_mh(&mach_hdr); + + if ((mach_hdr.cputype) != TARGET_CPU_TYPE) + qerror("%s: Unsupported CPU 0x%x (only 0x%x(%s) supported)", filename, mach_hdr.cputype, TARGET_CPU_TYPE, TARGET_CPU_NAME); + + + switch(mach_hdr.filetype) + { + case MH_EXECUTE: break; + case MH_FVMLIB: + case MH_DYLIB: + case MH_DYLINKER: break; + default: + qerror("%s: Unsupported Mach type (0x%x)", filename, mach_hdr.filetype); + } + + /* read segment headers */ + lcmds = malloc(mach_hdr.sizeofcmds); + + if(read(fd, lcmds, mach_hdr.sizeofcmds) != mach_hdr.sizeofcmds) + qerror("%s: unable to read load_command", filename); + slide = 0; + mmapfixed = 0; + for(i=0, lc = lcmds; i < (mach_hdr.ncmds) ; i++) + { + + if(need_bswap) + bswap_lc(lc); + switch(lc->cmd) + { + case LC_SEGMENT: + /* The main_exe can't be relocated */ + if(mach_hdr.filetype == MH_EXECUTE) + mmapfixed = 1; + + slide = load_segment(&mach_hdr, (struct segment_command*)lc, fd, mach_hdr_pos, need_bswap, mmapfixed, slide); + + /* other segment must be mapped according to slide exactly, if load_segment did something */ + if(slide != -1) + mmapfixed = 1; + else + slide = 0; /* load_segment didn't map the segment */ + + if(mach_hdr.filetype == MH_EXECUTE && slide != 0) + qerror("%s: Warning executable can't be mapped at the right address (offset: 0x%x)\n", filename, slide); + + if(strcmp(((struct segment_command*)(lc))->segname, "__TEXT") == 0) + { + /* Text section */ + if(mach_hdr.filetype == MH_EXECUTE) + { + /* return the mach_header */ + *mh = (void*)(((struct segment_command*)(lc))->vmaddr + slide); + } + else + { + /* it is dyld save the section for gdb, we will be interested in dyld symbol + while debuging */ + macho_text_sect = (void*)(((struct segment_command*)(lc))->vmaddr + slide); + macho_offset = slide; + } + } + break; + case LC_LOAD_DYLINKER: + dyld_entry_point = load_dylinker( &mach_hdr, (struct dylinker_command*)lc, fd, mach_hdr_pos, need_bswap ); + break; + case LC_LOAD_DYLIB: + /* dyld will do that for us */ + break; + case LC_THREAD: + case LC_UNIXTHREAD: + { + struct target_pt_regs * _regs; + if(mach_hdr.filetype == MH_DYLINKER) + _regs = regs; + else + _regs = 0; + entry_point = load_thread( &mach_hdr, (struct target_thread_command*)lc, _regs, fd, mach_hdr_pos, need_bswap ); + } + break; + case LC_SYMTAB: + /* Save the symtab and strtab */ + symtabcmd = (struct symtab_command *)lc; + break; + case LC_ID_DYLINKER: + case LC_ID_DYLIB: + case LC_UUID: + case LC_DYSYMTAB: + case LC_TWOLEVEL_HINTS: + case LC_PREBIND_CKSUM: + case LC_SUB_LIBRARY: + break; + default: fprintf(stderr, "warning: unkown command 0x%x in '%s'\n", lc->cmd, filename); + } + lc = (struct load_command*)((int)(lc)+(lc->cmdsize)); + } + + if(symtabcmd) + { + if(need_bswap) + bswap_symtabcmd(symtabcmd); + + symtab_std = load_data(fd, symtabcmd->symoff+mach_hdr_pos, symtabcmd->nsyms * sizeof(struct nlist)); + strtab = load_data(fd, symtabcmd->stroff+mach_hdr_pos, symtabcmd->strsize); + + symtab = malloc(sizeof(struct nlist_extended) * symtabcmd->nsyms); + + if(need_bswap) + { + for(i = 0, syment = symtab_std; i < symtabcmd->nsyms; i++, syment++) + bswap_sym(syment); + } + + for(i = 0, sym = symtab, syment = symtab_std; i < symtabcmd->nsyms; i++, sym++, syment++) + { + struct nlist *sym_follow, *sym_next = 0; + unsigned int j; + memset(sym, 0, sizeof(*sym)); + + sym->n_type = syment->n_type; + if ( syment->n_type & N_STAB ) /* Debug symbols are skipped */ + continue; + + memcpy(sym, syment, sizeof(*syment)); + + /* Find the following symbol in order to get the current symbol size */ + for(j = 0, sym_follow = symtab_std; j < symtabcmd->nsyms; j++, sym_follow++) { + if ( sym_follow->n_type & N_STAB || !(sym_follow->n_value > sym->st_value)) + continue; + if(!sym_next) { + sym_next = sym_follow; + continue; + } + if(!(sym_next->n_value > sym_follow->n_value)) + continue; + sym_next = sym_follow; + } + if(sym_next) + sym->st_size = sym_next->n_value - sym->st_value; + else + sym->st_size = 10; /* XXX: text_sec_hdr->size + text_sec_hdr->offset - sym->st_value; */ + + sym->st_value += slide; + } + + free((void*)symtab_std); + + { + DPRINTF("saving symtab of %s (%d symbol(s))\n", filename, symtabcmd->nsyms); + struct syminfo *s; + s = malloc(sizeof(*s)); + s->disas_symtab = symtab; + s->disas_strtab = strtab; + s->disas_num_syms = symtabcmd->nsyms; + s->next = syminfos; + syminfos = s; + } + } + close(fd); + if(mach_hdr.filetype == MH_EXECUTE && dyld_entry_point) + return dyld_entry_point; + else + return entry_point+slide; +} + +extern unsigned long stack_size; + +unsigned long setup_arg_pages(void * mh, char ** argv, char ** env) +{ + unsigned long stack_base, error, size; + int i; + int * stack; + int argc, envc; + + /* Create enough stack to hold everything. If we don't use + * it for args, we'll use it for something else... + */ + size = stack_size; + + error = target_mmap(0, + size + qemu_host_page_size, + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, + -1, 0); + if (error == -1) + qerror("stk mmap"); + + /* we reserve one extra page at the top of the stack as guard */ + target_mprotect(error + size, qemu_host_page_size, PROT_NONE); + + stack_base = error + size; + stack = (void*)stack_base; +/* + * | STRING AREA | + * +-------------+ + * | 0 | +* +-------------+ + * | apple[n] | + * +-------------+ + * : + * +-------------+ + * | apple[0] | + * +-------------+ + * | 0 | + * +-------------+ + * | env[n] | + * +-------------+ + * : + * : + * +-------------+ + * | env[0] | + * +-------------+ + * | 0 | + * +-------------+ + * | arg[argc-1] | + * +-------------+ + * : + * : + * +-------------+ + * | arg[0] | + * +-------------+ + * | argc | + * +-------------+ + * sp-> | mh | address of where the a.out's file offset 0 is in memory + * +-------------+ +*/ + /* Construct the stack Stack grows down */ + stack--; + + /* XXX: string should go up there */ + + *stack = 0; + stack--; + + /* Push the absolute path of our executable */ + DPRINTF("pushing apple %s (0x%x)\n", (char*)argv[0], (int)argv[0]); + stl(stack, (int) argv[0]); + + stack--; + + stl(stack, 0); + stack--; + + /* Get envc */ + for(envc = 0; env[envc]; envc++); + + for(i = envc-1; i >= 0; i--) + { + DPRINTF("pushing env %s (0x%x)\n", (char*)env[i], (int)env[i]); + stl(stack, (int)env[i]); + stack--; + + /* XXX: remove that when string will be on top of the stack */ + page_set_flags((int)env[i], (int)(env[i]+strlen(env[i])), PROT_READ | PAGE_VALID); + } + + /* Add on the stack the interp_prefix choosen if so */ + if(interp_prefix[0]) + { + char *dyld_root; + asprintf(&dyld_root, "DYLD_ROOT_PATH=%s", interp_prefix); + page_set_flags((int)dyld_root, (int)(dyld_root+strlen(interp_prefix)+1), PROT_READ | PAGE_VALID); + + stl(stack, (int)dyld_root); + stack--; + } + +#ifdef DONT_USE_DYLD_SHARED_MAP + { + char *shared_map_mode; + asprintf(&shared_map_mode, "DYLD_SHARED_REGION=avoid"); + page_set_flags((int)shared_map_mode, (int)(shared_map_mode+strlen(shared_map_mode)+1), PROT_READ | PAGE_VALID); + + stl(stack, (int)shared_map_mode); + stack--; + } +#endif + +#ifdef ACTIVATE_DYLD_TRACE + char * extra_env_static[] = {"DYLD_DEBUG_TRACE=yes", + "DYLD_PREBIND_DEBUG=3", "DYLD_UNKNOW_TRACE=yes", + "DYLD_PRINT_INITIALIZERS=yes", + "DYLD_PRINT_SEGMENTS=yes", "DYLD_PRINT_REBASINGS=yes", "DYLD_PRINT_BINDINGS=yes", "DYLD_PRINT_INITIALIZERS=yes", "DYLD_PRINT_WARNINGS=yes" }; + + char ** extra_env = malloc(sizeof(extra_env_static)); + bcopy(extra_env_static, extra_env, sizeof(extra_env_static)); + page_set_flags((int)extra_env, (int)((void*)extra_env+sizeof(extra_env_static)), PROT_READ | PAGE_VALID); + + for(i = 0; i<9; i++) + { + DPRINTF("pushing (extra) env %s (0x%x)\n", (char*)extra_env[i], (int)extra_env[i]); + stl(stack, (int) extra_env[i]); + stack--; + } +#endif + + stl(stack, 0); + stack--; + + /* Get argc */ + for(argc = 0; argv[argc]; argc++); + + for(i = argc-1; i >= 0; i--) + { + DPRINTF("pushing arg %s (0x%x)\n", (char*)argv[i], (int)argv[i]); + stl(stack, (int) argv[i]); + stack--; + + /* XXX: remove that when string will be on top of the stack */ + page_set_flags((int)argv[i], (int)(argv[i]+strlen(argv[i])), PROT_READ | PAGE_VALID); + } + + DPRINTF("pushing argc %d \n", argc); + stl(stack, argc); + stack--; + + DPRINTF("pushing mh 0x%x \n", (int)mh); + stl(stack, (int) mh); + + /* Stack points on the mh */ + return (unsigned long)stack; +} + +int mach_exec(const char * filename, char ** argv, char ** envp, + struct target_pt_regs * regs) +{ + int entrypoint, stack; + void * mh; /* the Mach Header that will be used by dyld */ + + DPRINTF("mach_exec at 0x%x\n", (int)mach_exec); + + entrypoint = load_object(filename, regs, &mh); + stack = setup_arg_pages(mh, argv, envp); +#if defined(TARGET_I386) + regs->eip = entrypoint; + regs->esp = stack; +#elif defined(TARGET_PPC) + regs->nip = entrypoint; + regs->gpr[1] = stack; +#endif + DPRINTF("mach_exec returns eip set to 0x%x esp 0x%x mh 0x%x\n", entrypoint, stack, (int)mh); + + if(!entrypoint) + qerror("%s: no entry point!\n", filename); + + return 0; +} diff --git a/darwin-user/main.c b/darwin-user/main.c new file mode 100644 index 0000000..175e12f --- /dev/null +++ b/darwin-user/main.c @@ -0,0 +1,1015 @@ +/* + * qemu user main + * + * Copyright (c) 2003 Fabrice Bellard + * Copyright (c) 2006 Pierre d'Herbemont + * + * 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 . + */ +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "qemu.h" + +#define DEBUG_LOGFILE "/tmp/qemu.log" + +#ifdef __APPLE__ +#include +# define environ (*_NSGetEnviron()) +#endif + +#include +#include + +int singlestep; + +const char *interp_prefix = ""; + +asm(".zerofill __STD_PROG_ZONE, __STD_PROG_ZONE, __std_prog_zone, 0x0dfff000"); + +/* XXX: on x86 MAP_GROWSDOWN only works if ESP <= address + 32, so + we allocate a bigger stack. Need a better solution, for example + by remapping the process stack directly at the right place */ +unsigned long stack_size = 512 * 1024; + +void qerror(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + fprintf(stderr, "\n"); + exit(1); +} + +void gemu_log(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); +} + +int cpu_get_pic_interrupt(CPUState *env) +{ + return -1; +} +#ifdef TARGET_PPC + +static inline uint64_t cpu_ppc_get_tb (CPUState *env) +{ + /* TO FIX */ + return 0; +} + +uint64_t cpu_ppc_load_tbl (CPUState *env) +{ + return cpu_ppc_get_tb(env); +} + +uint32_t cpu_ppc_load_tbu (CPUState *env) +{ + return cpu_ppc_get_tb(env) >> 32; +} + +uint64_t cpu_ppc_load_atbl (CPUState *env) +{ + return cpu_ppc_get_tb(env); +} + +uint32_t cpu_ppc_load_atbu (CPUState *env) +{ + return cpu_ppc_get_tb(env) >> 32; +} + +uint32_t cpu_ppc601_load_rtcu (CPUState *env) +{ + cpu_ppc_load_tbu(env); +} + +uint32_t cpu_ppc601_load_rtcl (CPUState *env) +{ + return cpu_ppc_load_tbl(env) & 0x3FFFFF80; +} + +/* XXX: to be fixed */ +int ppc_dcr_read (ppc_dcr_t *dcr_env, int dcrn, uint32_t *valp) +{ + return -1; +} + +int ppc_dcr_write (ppc_dcr_t *dcr_env, int dcrn, uint32_t val) +{ + return -1; +} + +#define EXCP_DUMP(env, fmt, ...) \ +do { \ + fprintf(stderr, fmt , ## __VA_ARGS__); \ + cpu_dump_state(env, stderr, fprintf, 0); \ + qemu_log(fmt, ## __VA_ARGS__); \ + log_cpu_state(env, 0); \ +} while (0) + +void cpu_loop(CPUPPCState *env) +{ + int trapnr; + uint32_t ret; + target_siginfo_t info; + + for(;;) { + trapnr = cpu_ppc_exec(env); + switch(trapnr) { + case POWERPC_EXCP_NONE: + /* Just go on */ + break; + case POWERPC_EXCP_CRITICAL: /* Critical input */ + cpu_abort(env, "Critical interrupt while in user mode. " + "Aborting\n"); + break; + case POWERPC_EXCP_MCHECK: /* Machine check exception */ + cpu_abort(env, "Machine check exception while in user mode. " + "Aborting\n"); + break; + case POWERPC_EXCP_DSI: /* Data storage exception */ +#ifndef DAR +/* To deal with multiple qemu header version as host for the darwin-user code */ +# define DAR SPR_DAR +#endif + EXCP_DUMP(env, "Invalid data memory access: 0x" TARGET_FMT_lx "\n", + env->spr[SPR_DAR]); + /* Handle this via the gdb */ + gdb_handlesig (env, SIGSEGV); + + info.si_addr = (void*)env->nip; + queue_signal(info.si_signo, &info); + break; + case POWERPC_EXCP_ISI: /* Instruction storage exception */ + EXCP_DUMP(env, "Invalid instruction fetch: 0x\n" TARGET_FMT_lx "\n", + env->spr[SPR_DAR]); + /* Handle this via the gdb */ + gdb_handlesig (env, SIGSEGV); + + info.si_addr = (void*)(env->nip - 4); + queue_signal(info.si_signo, &info); + break; + case POWERPC_EXCP_EXTERNAL: /* External input */ + cpu_abort(env, "External interrupt while in user mode. " + "Aborting\n"); + break; + case POWERPC_EXCP_ALIGN: /* Alignment exception */ + EXCP_DUMP(env, "Unaligned memory access\n"); + info.si_errno = 0; + info.si_code = BUS_ADRALN; + info.si_addr = (void*)(env->nip - 4); + queue_signal(info.si_signo, &info); + break; + case POWERPC_EXCP_PROGRAM: /* Program exception */ + /* XXX: check this */ + switch (env->error_code & ~0xF) { + case POWERPC_EXCP_FP: + EXCP_DUMP(env, "Floating point program exception\n"); + /* Set FX */ + info.si_signo = SIGFPE; + info.si_errno = 0; + switch (env->error_code & 0xF) { + case POWERPC_EXCP_FP_OX: + info.si_code = FPE_FLTOVF; + break; + case POWERPC_EXCP_FP_UX: + info.si_code = FPE_FLTUND; + break; + case POWERPC_EXCP_FP_ZX: + case POWERPC_EXCP_FP_VXZDZ: + info.si_code = FPE_FLTDIV; + break; + case POWERPC_EXCP_FP_XX: + info.si_code = FPE_FLTRES; + break; + case POWERPC_EXCP_FP_VXSOFT: + info.si_code = FPE_FLTINV; + break; + case POWERPC_EXCP_FP_VXSNAN: + case POWERPC_EXCP_FP_VXISI: + case POWERPC_EXCP_FP_VXIDI: + case POWERPC_EXCP_FP_VXIMZ: + case POWERPC_EXCP_FP_VXVC: + case POWERPC_EXCP_FP_VXSQRT: + case POWERPC_EXCP_FP_VXCVI: + info.si_code = FPE_FLTSUB; + break; + default: + EXCP_DUMP(env, "Unknown floating point exception (%02x)\n", + env->error_code); + break; + } + break; + case POWERPC_EXCP_INVAL: + EXCP_DUMP(env, "Invalid instruction\n"); + info.si_signo = SIGILL; + info.si_errno = 0; + switch (env->error_code & 0xF) { + case POWERPC_EXCP_INVAL_INVAL: + info.si_code = ILL_ILLOPC; + break; + case POWERPC_EXCP_INVAL_LSWX: + info.si_code = ILL_ILLOPN; + break; + case POWERPC_EXCP_INVAL_SPR: + info.si_code = ILL_PRVREG; + break; + case POWERPC_EXCP_INVAL_FP: + info.si_code = ILL_COPROC; + break; + default: + EXCP_DUMP(env, "Unknown invalid operation (%02x)\n", + env->error_code & 0xF); + info.si_code = ILL_ILLADR; + break; + } + /* Handle this via the gdb */ + gdb_handlesig (env, SIGSEGV); + break; + case POWERPC_EXCP_PRIV: + EXCP_DUMP(env, "Privilege violation\n"); + info.si_signo = SIGILL; + info.si_errno = 0; + switch (env->error_code & 0xF) { + case POWERPC_EXCP_PRIV_OPC: + info.si_code = ILL_PRVOPC; + break; + case POWERPC_EXCP_PRIV_REG: + info.si_code = ILL_PRVREG; + break; + default: + EXCP_DUMP(env, "Unknown privilege violation (%02x)\n", + env->error_code & 0xF); + info.si_code = ILL_PRVOPC; + break; + } + break; + case POWERPC_EXCP_TRAP: + cpu_abort(env, "Tried to call a TRAP\n"); + break; + default: + /* Should not happen ! */ + cpu_abort(env, "Unknown program exception (%02x)\n", + env->error_code); + break; + } + info.si_addr = (void*)(env->nip - 4); + queue_signal(info.si_signo, &info); + break; + case POWERPC_EXCP_FPU: /* Floating-point unavailable exception */ + EXCP_DUMP(env, "No floating point allowed\n"); + info.si_signo = SIGILL; + info.si_errno = 0; + info.si_code = ILL_COPROC; + info.si_addr = (void*)(env->nip - 4); + queue_signal(info.si_signo, &info); + break; + case POWERPC_EXCP_SYSCALL: /* System call exception */ + cpu_abort(env, "Syscall exception while in user mode. " + "Aborting\n"); + break; + case POWERPC_EXCP_APU: /* Auxiliary processor unavailable */ + EXCP_DUMP(env, "No APU instruction allowed\n"); + info.si_signo = SIGILL; + info.si_errno = 0; + info.si_code = ILL_COPROC; + info.si_addr = (void*)(env->nip - 4); + queue_signal(info.si_signo, &info); + break; + case POWERPC_EXCP_DECR: /* Decrementer exception */ + cpu_abort(env, "Decrementer interrupt while in user mode. " + "Aborting\n"); + break; + case POWERPC_EXCP_FIT: /* Fixed-interval timer interrupt */ + cpu_abort(env, "Fix interval timer interrupt while in user mode. " + "Aborting\n"); + break; + case POWERPC_EXCP_WDT: /* Watchdog timer interrupt */ + cpu_abort(env, "Watchdog timer interrupt while in user mode. " + "Aborting\n"); + break; + case POWERPC_EXCP_DTLB: /* Data TLB error */ + cpu_abort(env, "Data TLB exception while in user mode. " + "Aborting\n"); + break; + case POWERPC_EXCP_ITLB: /* Instruction TLB error */ + cpu_abort(env, "Instruction TLB exception while in user mode. " + "Aborting\n"); + break; + case POWERPC_EXCP_DEBUG: /* Debug interrupt */ + gdb_handlesig (env, SIGTRAP); + break; + case POWERPC_EXCP_SPEU: /* SPE/embedded floating-point unavail. */ + EXCP_DUMP(env, "No SPE/floating-point instruction allowed\n"); + info.si_signo = SIGILL; + info.si_errno = 0; + info.si_code = ILL_COPROC; + info.si_addr = (void*)(env->nip - 4); + queue_signal(info.si_signo, &info); + break; + case POWERPC_EXCP_EFPDI: /* Embedded floating-point data IRQ */ + cpu_abort(env, "Embedded floating-point data IRQ not handled\n"); + break; + case POWERPC_EXCP_EFPRI: /* Embedded floating-point round IRQ */ + cpu_abort(env, "Embedded floating-point round IRQ not handled\n"); + break; + case POWERPC_EXCP_EPERFM: /* Embedded performance monitor IRQ */ + cpu_abort(env, "Performance monitor exception not handled\n"); + break; + case POWERPC_EXCP_DOORI: /* Embedded doorbell interrupt */ + cpu_abort(env, "Doorbell interrupt while in user mode. " + "Aborting\n"); + break; + case POWERPC_EXCP_DOORCI: /* Embedded doorbell critical interrupt */ + cpu_abort(env, "Doorbell critical interrupt while in user mode. " + "Aborting\n"); + break; + case POWERPC_EXCP_RESET: /* System reset exception */ + cpu_abort(env, "Reset interrupt while in user mode. " + "Aborting\n"); + break; + case POWERPC_EXCP_DSEG: /* Data segment exception */ + cpu_abort(env, "Data segment exception while in user mode. " + "Aborting\n"); + break; + case POWERPC_EXCP_ISEG: /* Instruction segment exception */ + cpu_abort(env, "Instruction segment exception " + "while in user mode. Aborting\n"); + break; + case POWERPC_EXCP_HDECR: /* Hypervisor decrementer exception */ + cpu_abort(env, "Hypervisor decrementer interrupt " + "while in user mode. Aborting\n"); + break; + case POWERPC_EXCP_TRACE: /* Trace exception */ + /* Nothing to do: + * we use this exception to emulate step-by-step execution mode. + */ + break; + case POWERPC_EXCP_HDSI: /* Hypervisor data storage exception */ + cpu_abort(env, "Hypervisor data storage exception " + "while in user mode. Aborting\n"); + break; + case POWERPC_EXCP_HISI: /* Hypervisor instruction storage excp */ + cpu_abort(env, "Hypervisor instruction storage exception " + "while in user mode. Aborting\n"); + break; + case POWERPC_EXCP_HDSEG: /* Hypervisor data segment exception */ + cpu_abort(env, "Hypervisor data segment exception " + "while in user mode. Aborting\n"); + break; + case POWERPC_EXCP_HISEG: /* Hypervisor instruction segment excp */ + cpu_abort(env, "Hypervisor instruction segment exception " + "while in user mode. Aborting\n"); + break; + case POWERPC_EXCP_VPU: /* Vector unavailable exception */ + EXCP_DUMP(env, "No Altivec instructions allowed\n"); + info.si_signo = SIGILL; + info.si_errno = 0; + info.si_code = ILL_COPROC; + info.si_addr = (void*)(env->nip - 4); + queue_signal(info.si_signo, &info); + break; + case POWERPC_EXCP_PIT: /* Programmable interval timer IRQ */ + cpu_abort(env, "Programable interval timer interrupt " + "while in user mode. Aborting\n"); + break; + case POWERPC_EXCP_IO: /* IO error exception */ + cpu_abort(env, "IO error exception while in user mode. " + "Aborting\n"); + break; + case POWERPC_EXCP_RUNM: /* Run mode exception */ + cpu_abort(env, "Run mode exception while in user mode. " + "Aborting\n"); + break; + case POWERPC_EXCP_EMUL: /* Emulation trap exception */ + cpu_abort(env, "Emulation trap exception not handled\n"); + break; + case POWERPC_EXCP_IFTLB: /* Instruction fetch TLB error */ + cpu_abort(env, "Instruction fetch TLB exception " + "while in user-mode. Aborting"); + break; + case POWERPC_EXCP_DLTLB: /* Data load TLB miss */ + cpu_abort(env, "Data load TLB exception while in user-mode. " + "Aborting"); + break; + case POWERPC_EXCP_DSTLB: /* Data store TLB miss */ + cpu_abort(env, "Data store TLB exception while in user-mode. " + "Aborting"); + break; + case POWERPC_EXCP_FPA: /* Floating-point assist exception */ + cpu_abort(env, "Floating-point assist exception not handled\n"); + break; + case POWERPC_EXCP_IABR: /* Instruction address breakpoint */ + cpu_abort(env, "Instruction address breakpoint exception " + "not handled\n"); + break; + case POWERPC_EXCP_SMI: /* System management interrupt */ + cpu_abort(env, "System management interrupt while in user mode. " + "Aborting\n"); + break; + case POWERPC_EXCP_THERM: /* Thermal interrupt */ + cpu_abort(env, "Thermal interrupt interrupt while in user mode. " + "Aborting\n"); + break; + case POWERPC_EXCP_PERFM: /* Embedded performance monitor IRQ */ + cpu_abort(env, "Performance monitor exception not handled\n"); + break; + case POWERPC_EXCP_VPUA: /* Vector assist exception */ + cpu_abort(env, "Vector assist exception not handled\n"); + break; + case POWERPC_EXCP_SOFTP: /* Soft patch exception */ + cpu_abort(env, "Soft patch exception not handled\n"); + break; + case POWERPC_EXCP_MAINT: /* Maintenance exception */ + cpu_abort(env, "Maintenance exception while in user mode. " + "Aborting\n"); + break; + case POWERPC_EXCP_STOP: /* stop translation */ + /* We did invalidate the instruction cache. Go on */ + break; + case POWERPC_EXCP_BRANCH: /* branch instruction: */ + /* We just stopped because of a branch. Go on */ + break; + case POWERPC_EXCP_SYSCALL_USER: + /* system call in user-mode emulation */ + /* system call */ + if(((int)env->gpr[0]) <= SYS_MAXSYSCALL && ((int)env->gpr[0])>0) + ret = do_unix_syscall(env, env->gpr[0]/*, env->gpr[3], env->gpr[4], + env->gpr[5], env->gpr[6], env->gpr[7], + env->gpr[8], env->gpr[9], env->gpr[10]*/); + else if(((int)env->gpr[0])<0) + ret = do_mach_syscall(env, env->gpr[0], env->gpr[3], env->gpr[4], + env->gpr[5], env->gpr[6], env->gpr[7], + env->gpr[8], env->gpr[9], env->gpr[10]); + else + ret = do_thread_syscall(env, env->gpr[0], env->gpr[3], env->gpr[4], + env->gpr[5], env->gpr[6], env->gpr[7], + env->gpr[8], env->gpr[9], env->gpr[10]); + + /* Unix syscall error signaling */ + if(((int)env->gpr[0]) <= SYS_MAXSYSCALL && ((int)env->gpr[0])>0) + { + if( (int)ret < 0 ) + env->nip += 0; + else + env->nip += 4; + } + + /* Return value */ + env->gpr[3] = ret; + break; + case EXCP_INTERRUPT: + /* just indicate that signals should be handled asap */ + break; + default: + cpu_abort(env, "Unknown exception 0x%d. Aborting\n", trapnr); + break; + } + process_pending_signals(env); + } +} +#endif + + +#ifdef TARGET_I386 + +/***********************************************************/ +/* CPUX86 core interface */ + +uint64_t cpu_get_tsc(CPUX86State *env) +{ + return cpu_get_real_ticks(); +} + +void +write_dt(void *ptr, unsigned long addr, unsigned long limit, + int flags) +{ + unsigned int e1, e2; + e1 = (addr << 16) | (limit & 0xffff); + e2 = ((addr >> 16) & 0xff) | (addr & 0xff000000) | (limit & 0x000f0000); + e2 |= flags; + stl((uint8_t *)ptr, e1); + stl((uint8_t *)ptr + 4, e2); +} + +static void set_gate(void *ptr, unsigned int type, unsigned int dpl, + unsigned long addr, unsigned int sel) +{ + unsigned int e1, e2; + e1 = (addr & 0xffff) | (sel << 16); + e2 = (addr & 0xffff0000) | 0x8000 | (dpl << 13) | (type << 8); + stl((uint8_t *)ptr, e1); + stl((uint8_t *)ptr + 4, e2); +} + +#define GDT_TABLE_SIZE 14 +#define LDT_TABLE_SIZE 15 +#define IDT_TABLE_SIZE 256 +#define TSS_SIZE 104 +uint64_t gdt_table[GDT_TABLE_SIZE]; +uint64_t ldt_table[LDT_TABLE_SIZE]; +uint64_t idt_table[IDT_TABLE_SIZE]; +uint32_t tss[TSS_SIZE]; + +/* only dpl matters as we do only user space emulation */ +static void set_idt(int n, unsigned int dpl) +{ + set_gate(idt_table + n, 0, dpl, 0, 0); +} + +/* ABI convention: after a syscall if there was an error the CF flag is set */ +static inline void set_error(CPUX86State *env, int ret) +{ + if(ret<0) + env->eflags = env->eflags | 0x1; + else + env->eflags &= ~0x1; + env->regs[R_EAX] = ret; +} + +void cpu_loop(CPUX86State *env) +{ + int trapnr; + int ret; + uint8_t *pc; + target_siginfo_t info; + + for(;;) { + trapnr = cpu_x86_exec(env); + uint32_t *params = (uint32_t *)env->regs[R_ESP]; + switch(trapnr) { + case 0x79: /* Our commpage hack back door exit is here */ + do_commpage(env, env->eip, *(params + 1), *(params + 2), + *(params + 3), *(params + 4), + *(params + 5), *(params + 6), + *(params + 7), *(params + 8)); + break; + case 0x81: /* mach syscall */ + { + ret = do_mach_syscall(env, env->regs[R_EAX], + *(params + 1), *(params + 2), + *(params + 3), *(params + 4), + *(params + 5), *(params + 6), + *(params + 7), *(params + 8)); + set_error(env, ret); + break; + } + case 0x90: /* unix backdoor */ + { + /* after sysenter, stack is in R_ECX, new eip in R_EDX (sysexit will flip them back)*/ + int saved_stack = env->regs[R_ESP]; + env->regs[R_ESP] = env->regs[R_ECX]; + + ret = do_unix_syscall(env, env->regs[R_EAX]); + + env->regs[R_ECX] = env->regs[R_ESP]; + env->regs[R_ESP] = saved_stack; + + set_error(env, ret); + break; + } + case 0x80: /* unix syscall */ + { + ret = do_unix_syscall(env, env->regs[R_EAX]/*, + *(params + 1), *(params + 2), + *(params + 3), *(params + 4), + *(params + 5), *(params + 6), + *(params + 7), *(params + 8)*/); + set_error(env, ret); + break; + } + case 0x82: /* thread syscall */ + { + ret = do_thread_syscall(env, env->regs[R_EAX], + *(params + 1), *(params + 2), + *(params + 3), *(params + 4), + *(params + 5), *(params + 6), + *(params + 7), *(params + 8)); + set_error(env, ret); + break; + } + case EXCP0B_NOSEG: + case EXCP0C_STACK: + info.si_signo = SIGBUS; + info.si_errno = 0; + info.si_code = BUS_NOOP; + info.si_addr = 0; + gdb_handlesig (env, SIGBUS); + queue_signal(info.si_signo, &info); + break; + case EXCP0D_GPF: + info.si_signo = SIGSEGV; + info.si_errno = 0; + info.si_code = SEGV_NOOP; + info.si_addr = 0; + gdb_handlesig (env, SIGSEGV); + queue_signal(info.si_signo, &info); + break; + case EXCP0E_PAGE: + info.si_signo = SIGSEGV; + info.si_errno = 0; + if (!(env->error_code & 1)) + info.si_code = SEGV_MAPERR; + else + info.si_code = SEGV_ACCERR; + info.si_addr = (void*)env->cr[2]; + gdb_handlesig (env, SIGSEGV); + queue_signal(info.si_signo, &info); + break; + case EXCP00_DIVZ: + /* division by zero */ + info.si_signo = SIGFPE; + info.si_errno = 0; + info.si_code = FPE_INTDIV; + info.si_addr = (void*)env->eip; + gdb_handlesig (env, SIGFPE); + queue_signal(info.si_signo, &info); + break; + case EXCP01_SSTP: + case EXCP03_INT3: + info.si_signo = SIGTRAP; + info.si_errno = 0; + info.si_code = TRAP_BRKPT; + info.si_addr = (void*)env->eip; + gdb_handlesig (env, SIGTRAP); + queue_signal(info.si_signo, &info); + break; + case EXCP04_INTO: + case EXCP05_BOUND: + info.si_signo = SIGSEGV; + info.si_errno = 0; + info.si_code = SEGV_NOOP; + info.si_addr = 0; + gdb_handlesig (env, SIGSEGV); + queue_signal(info.si_signo, &info); + break; + case EXCP06_ILLOP: + info.si_signo = SIGILL; + info.si_errno = 0; + info.si_code = ILL_ILLOPN; + info.si_addr = (void*)env->eip; + gdb_handlesig (env, SIGILL); + queue_signal(info.si_signo, &info); + break; + case EXCP_INTERRUPT: + /* just indicate that signals should be handled asap */ + break; + case EXCP_DEBUG: + { + int sig; + + sig = gdb_handlesig (env, SIGTRAP); + if (sig) + { + info.si_signo = sig; + info.si_errno = 0; + info.si_code = TRAP_BRKPT; + queue_signal(info.si_signo, &info); + } + } + break; + default: + pc = (void*)(env->segs[R_CS].base + env->eip); + fprintf(stderr, "qemu: 0x%08lx: unhandled CPU exception 0x%x - aborting\n", + (long)pc, trapnr); + abort(); + } + process_pending_signals(env); + } +} +#endif + +static void usage(void) +{ + printf("qemu-" TARGET_ARCH " version " QEMU_VERSION ", Copyright (c) 2003-2004 Fabrice Bellard\n" + "usage: qemu-" TARGET_ARCH " [-h] [-d opts] [-L path] [-s size] program [arguments...]\n" + "Darwin CPU emulator (compiled for %s emulation)\n" + "\n" + "-h print this help\n" + "-L path set the %s library path (default='%s')\n" + "-s size set the stack size in bytes (default=%ld)\n" + "\n" + "debug options:\n" + "-d options activate log (logfile='%s')\n" + "-g wait for gdb on port 1234\n" + "-p pagesize set the host page size to 'pagesize'\n", + "-singlestep always run in singlestep mode\n" + TARGET_ARCH, + TARGET_ARCH, + interp_prefix, + stack_size, + DEBUG_LOGFILE); + exit(1); +} + +/* XXX: currently only used for async signals (see signal.c) */ +CPUState *global_env; +/* used only if single thread */ +CPUState *cpu_single_env = NULL; + +/* used to free thread contexts */ +TaskState *first_task_state; + +int main(int argc, char **argv) +{ + const char *filename; + struct target_pt_regs regs1, *regs = ®s1; + TaskState ts1, *ts = &ts1; + CPUState *env; + int optind; + short use_gdbstub = 0; + const char *r; + const char *cpu_model; + + if (argc <= 1) + usage(); + + /* init debug */ + cpu_set_log_filename(DEBUG_LOGFILE); + + optind = 1; + for(;;) { + if (optind >= argc) + break; + r = argv[optind]; + if (r[0] != '-') + break; + optind++; + r++; + if (!strcmp(r, "-")) { + break; + } else if (!strcmp(r, "d")) { + int mask; + CPULogItem *item; + + if (optind >= argc) + break; + + r = argv[optind++]; + mask = cpu_str_to_log_mask(r); + if (!mask) { + printf("Log items (comma separated):\n"); + for(item = cpu_log_items; item->mask != 0; item++) { + printf("%-10s %s\n", item->name, item->help); + } + exit(1); + } + cpu_set_log(mask); + } else if (!strcmp(r, "s")) { + r = argv[optind++]; + stack_size = strtol(r, (char **)&r, 0); + if (stack_size <= 0) + usage(); + if (*r == 'M') + stack_size *= 1024 * 1024; + else if (*r == 'k' || *r == 'K') + stack_size *= 1024; + } else if (!strcmp(r, "L")) { + interp_prefix = argv[optind++]; + } else if (!strcmp(r, "p")) { + qemu_host_page_size = atoi(argv[optind++]); + if (qemu_host_page_size == 0 || + (qemu_host_page_size & (qemu_host_page_size - 1)) != 0) { + fprintf(stderr, "page size must be a power of two\n"); + exit(1); + } + } else + if (!strcmp(r, "g")) { + use_gdbstub = 1; + } else if (!strcmp(r, "cpu")) { + cpu_model = argv[optind++]; + if (strcmp(cpu_model, "?") == 0) { +/* XXX: implement xxx_cpu_list for targets that still miss it */ +#if defined(cpu_list) + cpu_list(stdout, &fprintf); +#endif + exit(1); + } + } else if (!strcmp(r, "singlestep")) { + singlestep = 1; + } else + { + usage(); + } + } + if (optind >= argc) + usage(); + filename = argv[optind]; + + /* Zero out regs */ + memset(regs, 0, sizeof(struct target_pt_regs)); + + if (cpu_model == NULL) { +#if defined(TARGET_I386) +#ifdef TARGET_X86_64 + cpu_model = "qemu64"; +#else + cpu_model = "qemu32"; +#endif +#elif defined(TARGET_PPC) +#ifdef TARGET_PPC64 + cpu_model = "970"; +#else + cpu_model = "750"; +#endif +#else +#error unsupported CPU +#endif + } + + cpu_exec_init_all(0); + /* NOTE: we need to init the CPU at this stage to get + qemu_host_page_size */ + env = cpu_init(cpu_model); + cpu_reset(env); + + printf("Starting %s with qemu\n----------------\n", filename); + + commpage_init(); + + if (mach_exec(filename, argv+optind, environ, regs) != 0) { + printf("Error loading %s\n", filename); + _exit(1); + } + + syscall_init(); + signal_init(); + global_env = env; + + /* build Task State */ + memset(ts, 0, sizeof(TaskState)); + env->opaque = ts; + ts->used = 1; + +#if defined(TARGET_I386) + cpu_x86_set_cpl(env, 3); + + env->cr[0] = CR0_PG_MASK | CR0_WP_MASK | CR0_PE_MASK; + env->hflags |= HF_PE_MASK; + + if (env->cpuid_features & CPUID_SSE) { + env->cr[4] |= CR4_OSFXSR_MASK; + env->hflags |= HF_OSFXSR_MASK; + } + + /* flags setup : we activate the IRQs by default as in user mode */ + env->eflags |= IF_MASK; + + /* darwin register setup */ + env->regs[R_EAX] = regs->eax; + env->regs[R_EBX] = regs->ebx; + env->regs[R_ECX] = regs->ecx; + env->regs[R_EDX] = regs->edx; + env->regs[R_ESI] = regs->esi; + env->regs[R_EDI] = regs->edi; + env->regs[R_EBP] = regs->ebp; + env->regs[R_ESP] = regs->esp; + env->eip = regs->eip; + + /* Darwin LDT setup */ + /* 2 - User code segment + 3 - User data segment + 4 - User cthread */ + bzero(ldt_table, LDT_TABLE_SIZE * sizeof(ldt_table[0])); + env->ldt.base = (uint32_t) ldt_table; + env->ldt.limit = sizeof(ldt_table) - 1; + + write_dt(ldt_table + 2, 0, 0xfffff, + DESC_G_MASK | DESC_B_MASK | DESC_P_MASK | DESC_S_MASK | + (3 << DESC_DPL_SHIFT) | (0xa << DESC_TYPE_SHIFT)); + write_dt(ldt_table + 3, 0, 0xfffff, + DESC_G_MASK | DESC_B_MASK | DESC_P_MASK | DESC_S_MASK | + (3 << DESC_DPL_SHIFT) | (0x2 << DESC_TYPE_SHIFT)); + write_dt(ldt_table + 4, 0, 0xfffff, + DESC_G_MASK | DESC_B_MASK | DESC_P_MASK | DESC_S_MASK | + (3 << DESC_DPL_SHIFT) | (0x2 << DESC_TYPE_SHIFT)); + + /* Darwin GDT setup. + * has changed a lot between old Darwin/x86 (pre-Mac Intel) and Mac OS X/x86, + now everything is done via int 0x81(mach) int 0x82 (thread) and sysenter/sysexit(unix) */ + bzero(gdt_table, sizeof(gdt_table)); + env->gdt.base = (uint32_t)gdt_table; + env->gdt.limit = sizeof(gdt_table) - 1; + + /* Set up a back door to handle sysenter syscalls (unix) */ + char * syscallbackdoor = malloc(64); + page_set_flags((int)syscallbackdoor, (int)syscallbackdoor + 64, PROT_EXEC | PROT_READ | PAGE_VALID); + + int i = 0; + syscallbackdoor[i++] = 0xcd; + syscallbackdoor[i++] = 0x90; /* int 0x90 */ + syscallbackdoor[i++] = 0x0F; + syscallbackdoor[i++] = 0x35; /* sysexit */ + + /* Darwin sysenter/sysexit setup */ + env->sysenter_cs = 0x1; //XXX + env->sysenter_eip = (int)syscallbackdoor; + env->sysenter_esp = (int)malloc(64); + + /* Darwin TSS setup + This must match up with GDT[4] */ + env->tr.base = (uint32_t) tss; + env->tr.limit = sizeof(tss) - 1; + env->tr.flags = DESC_P_MASK | (0x9 << DESC_TYPE_SHIFT); + stw(tss + 2, 0x10); // ss0 = 0x10 = GDT[2] = Kernel Data Segment + + /* Darwin interrupt setup */ + bzero(idt_table, sizeof(idt_table)); + env->idt.base = (uint32_t) idt_table; + env->idt.limit = sizeof(idt_table) - 1; + set_idt(0, 0); + set_idt(1, 0); + set_idt(2, 0); + set_idt(3, 3); + set_idt(4, 3); + set_idt(5, 3); + set_idt(6, 0); + set_idt(7, 0); + set_idt(8, 0); + set_idt(9, 0); + set_idt(10, 0); + set_idt(11, 0); + set_idt(12, 0); + set_idt(13, 0); + set_idt(14, 0); + set_idt(15, 0); + set_idt(16, 0); + set_idt(17, 0); + set_idt(18, 0); + set_idt(19, 0); + /* Syscalls are done via + int 0x80 (unix) (rarely used) + int 0x81 (mach) + int 0x82 (thread) + int 0x83 (diag) (not handled here) + sysenter/sysexit (unix) -> we redirect that to int 0x90 */ + set_idt(0x79, 3); /* Commpage hack, here is our backdoor interrupt */ + set_idt(0x80, 3); /* Unix Syscall */ + set_idt(0x81, 3); /* Mach Syscalls */ + set_idt(0x82, 3); /* thread Syscalls */ + + set_idt(0x90, 3); /* qemu-darwin-user's Unix syscalls backdoor */ + + + cpu_x86_load_seg(env, R_CS, __USER_CS); + cpu_x86_load_seg(env, R_DS, __USER_DS); + cpu_x86_load_seg(env, R_ES, __USER_DS); + cpu_x86_load_seg(env, R_SS, __USER_DS); + cpu_x86_load_seg(env, R_FS, __USER_DS); + cpu_x86_load_seg(env, R_GS, __USER_DS); + +#elif defined(TARGET_PPC) + { + int i; + +#if defined(TARGET_PPC64) +#if defined(TARGET_ABI32) + env->msr &= ~((target_ulong)1 << MSR_SF); +#else + env->msr |= (target_ulong)1 << MSR_SF; +#endif +#endif + env->nip = regs->nip; + for(i = 0; i < 32; i++) { + env->gpr[i] = regs->gpr[i]; + } + } +#else +#error unsupported target CPU +#endif + + if (use_gdbstub) { + printf("Waiting for gdb Connection on port 1234...\n"); + gdbserver_start (1234); + gdb_handlesig(env, 0); + } + + cpu_loop(env); + /* never exits */ + return 0; +} diff --git a/darwin-user/mmap.c b/darwin-user/mmap.c new file mode 100644 index 0000000..d840b28 --- /dev/null +++ b/darwin-user/mmap.c @@ -0,0 +1,409 @@ +/* + * mmap support for qemu + * + * Copyright (c) 2003 Fabrice Bellard + * + * 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 . + */ +#include +#include +#include +#include +#include +#include +#include + +#include "qemu.h" + +//#define DEBUG_MMAP + +/* NOTE: all the constants are the HOST ones */ +int target_mprotect(unsigned long start, unsigned long len, int prot) +{ + unsigned long end, host_start, host_end, addr; + int prot1, ret; + +#ifdef DEBUG_MMAP + printf("mprotect: start=0x%lx len=0x%lx prot=%c%c%c\n", start, len, + prot & PROT_READ ? 'r' : '-', + prot & PROT_WRITE ? 'w' : '-', + prot & PROT_EXEC ? 'x' : '-'); +#endif + + if ((start & ~TARGET_PAGE_MASK) != 0) + return -EINVAL; + len = TARGET_PAGE_ALIGN(len); + end = start + len; + if (end < start) + return -EINVAL; + if (prot & ~(PROT_READ | PROT_WRITE | PROT_EXEC)) + return -EINVAL; + if (len == 0) + return 0; + + host_start = start & qemu_host_page_mask; + host_end = HOST_PAGE_ALIGN(end); + if (start > host_start) { + /* handle host page containing start */ + prot1 = prot; + for(addr = host_start; addr < start; addr += TARGET_PAGE_SIZE) { + prot1 |= page_get_flags(addr); + } + if (host_end == host_start + qemu_host_page_size) { + for(addr = end; addr < host_end; addr += TARGET_PAGE_SIZE) { + prot1 |= page_get_flags(addr); + } + end = host_end; + } + ret = mprotect((void *)host_start, qemu_host_page_size, prot1 & PAGE_BITS); + if (ret != 0) + return ret; + host_start += qemu_host_page_size; + } + if (end < host_end) { + prot1 = prot; + for(addr = end; addr < host_end; addr += TARGET_PAGE_SIZE) { + prot1 |= page_get_flags(addr); + } + ret = mprotect((void *)(host_end - qemu_host_page_size), qemu_host_page_size, + prot1 & PAGE_BITS); + if (ret != 0) + return ret; + host_end -= qemu_host_page_size; + } + + /* handle the pages in the middle */ + if (host_start < host_end) { + ret = mprotect((void *)host_start, host_end - host_start, prot); + if (ret != 0) + return ret; + } + page_set_flags(start, start + len, prot | PAGE_VALID); + return 0; +} + +/* map an incomplete host page */ +int mmap_frag(unsigned long host_start, + unsigned long start, unsigned long end, + int prot, int flags, int fd, unsigned long offset) +{ + unsigned long host_end, ret, addr; + int prot1, prot_new; + + host_end = host_start + qemu_host_page_size; + + /* get the protection of the target pages outside the mapping */ + prot1 = 0; + for(addr = host_start; addr < host_end; addr++) { + if (addr < start || addr >= end) + prot1 |= page_get_flags(addr); + } + + if (prot1 == 0) { + /* no page was there, so we allocate one */ + ret = (long)mmap((void *)host_start, qemu_host_page_size, prot, + flags | MAP_ANONYMOUS, -1, 0); + if (ret == -1) + return ret; + } + prot1 &= PAGE_BITS; + + prot_new = prot | prot1; + if (!(flags & MAP_ANONYMOUS)) { + /* msync() won't work here, so we return an error if write is + possible while it is a shared mapping */ +#ifndef __APPLE__ + if ((flags & MAP_TYPE) == MAP_SHARED && +#else + if ((flags & MAP_SHARED) && +#endif + (prot & PROT_WRITE)) + return -1; + + /* adjust protection to be able to read */ + if (!(prot1 & PROT_WRITE)) + mprotect((void *)host_start, qemu_host_page_size, prot1 | PROT_WRITE); + + /* read the corresponding file data */ + pread(fd, (void *)start, end - start, offset); + + /* put final protection */ + if (prot_new != (prot1 | PROT_WRITE)) + mprotect((void *)host_start, qemu_host_page_size, prot_new); + } else { + /* just update the protection */ + if (prot_new != prot1) { + mprotect((void *)host_start, qemu_host_page_size, prot_new); + } + } + return 0; +} + +/* NOTE: all the constants are the HOST ones */ +long target_mmap(unsigned long start, unsigned long len, int prot, + int flags, int fd, unsigned long offset) +{ + unsigned long ret, end, host_start, host_end, retaddr, host_offset, host_len; +#if defined(__alpha__) || defined(__sparc__) || defined(__x86_64__) + static unsigned long last_start = 0x40000000; +#endif + +#ifdef DEBUG_MMAP + { + printf("mmap: start=0x%lx len=0x%lx prot=%c%c%c flags=", + start, len, + prot & PROT_READ ? 'r' : '-', + prot & PROT_WRITE ? 'w' : '-', + prot & PROT_EXEC ? 'x' : '-'); + if (flags & MAP_FIXED) + printf("MAP_FIXED "); + if (flags & MAP_ANONYMOUS) + printf("MAP_ANON "); +#ifndef MAP_TYPE +# define MAP_TYPE 0x3 +#endif + switch(flags & MAP_TYPE) { + case MAP_PRIVATE: + printf("MAP_PRIVATE "); + break; + case MAP_SHARED: + printf("MAP_SHARED "); + break; + default: + printf("[MAP_TYPE=0x%x] ", flags & MAP_TYPE); + break; + } + printf("fd=%d offset=%lx\n", fd, offset); + } +#endif + + if (offset & ~TARGET_PAGE_MASK) + return -EINVAL; + + len = TARGET_PAGE_ALIGN(len); + if (len == 0) + return start; + host_start = start & qemu_host_page_mask; + + if (!(flags & MAP_FIXED)) { +#if defined(__alpha__) || defined(__sparc__) || defined(__x86_64__) + /* tell the kernel to search at the same place as i386 */ + if (host_start == 0) { + host_start = last_start; + last_start += HOST_PAGE_ALIGN(len); + } +#endif + if (qemu_host_page_size != qemu_real_host_page_size) { + /* NOTE: this code is only for debugging with '-p' option */ + /* reserve a memory area */ + host_len = HOST_PAGE_ALIGN(len) + qemu_host_page_size - TARGET_PAGE_SIZE; + host_start = (long)mmap((void *)host_start, host_len, PROT_NONE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (host_start == -1) + return host_start; + host_end = host_start + host_len; + start = HOST_PAGE_ALIGN(host_start); + end = start + HOST_PAGE_ALIGN(len); + if (start > host_start) + munmap((void *)host_start, start - host_start); + if (end < host_end) + munmap((void *)end, host_end - end); + /* use it as a fixed mapping */ + flags |= MAP_FIXED; + } else { + /* if not fixed, no need to do anything */ + host_offset = offset & qemu_host_page_mask; + host_len = len + offset - host_offset; + start = (long)mmap((void *)host_start, host_len, + prot, flags, fd, host_offset); + if (start == -1) + return start; + /* update start so that it points to the file position at 'offset' */ + if (!(flags & MAP_ANONYMOUS)) + start += offset - host_offset; + goto the_end1; + } + } + + if (start & ~TARGET_PAGE_MASK) + return -EINVAL; + end = start + len; + host_end = HOST_PAGE_ALIGN(end); + + /* worst case: we cannot map the file because the offset is not + aligned, so we read it */ + if (!(flags & MAP_ANONYMOUS) && + (offset & ~qemu_host_page_mask) != (start & ~qemu_host_page_mask)) { + /* msync() won't work here, so we return an error if write is + possible while it is a shared mapping */ +#ifndef __APPLE__ + if ((flags & MAP_TYPE) == MAP_SHARED && +#else + if ((flags & MAP_SHARED) && +#endif + (prot & PROT_WRITE)) + return -EINVAL; + retaddr = target_mmap(start, len, prot | PROT_WRITE, + MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS, + -1, 0); + if (retaddr == -1) + return retaddr; + pread(fd, (void *)start, len, offset); + if (!(prot & PROT_WRITE)) { + ret = target_mprotect(start, len, prot); + if (ret != 0) + return ret; + } + goto the_end; + } + + /* handle the start of the mapping */ + if (start > host_start) { + if (host_end == host_start + qemu_host_page_size) { + /* one single host page */ + ret = mmap_frag(host_start, start, end, + prot, flags, fd, offset); + if (ret == -1) + return ret; + goto the_end1; + } + ret = mmap_frag(host_start, start, host_start + qemu_host_page_size, + prot, flags, fd, offset); + if (ret == -1) + return ret; + host_start += qemu_host_page_size; + } + /* handle the end of the mapping */ + if (end < host_end) { + ret = mmap_frag(host_end - qemu_host_page_size, + host_end - qemu_host_page_size, host_end, + prot, flags, fd, + offset + host_end - qemu_host_page_size - start); + if (ret == -1) + return ret; + host_end -= qemu_host_page_size; + } + + /* map the middle (easier) */ + if (host_start < host_end) { + unsigned long offset1; + if (flags & MAP_ANONYMOUS) + offset1 = 0; + else + offset1 = offset + host_start - start; + ret = (long)mmap((void *)host_start, host_end - host_start, + prot, flags, fd, offset1); + if (ret == -1) + return ret; + } + the_end1: + page_set_flags(start, start + len, prot | PAGE_VALID); + the_end: +#ifdef DEBUG_MMAP + printf("target_mmap: ret=0x%lx\n", (long)start); + page_dump(stdout); + printf("\n"); +#endif + return start; +} + +int target_munmap(unsigned long start, unsigned long len) +{ + unsigned long end, host_start, host_end, addr; + int prot, ret; + +#ifdef DEBUG_MMAP + printf("munmap: start=0x%lx len=0x%lx\n", start, len); +#endif + if (start & ~TARGET_PAGE_MASK) + return -EINVAL; + len = TARGET_PAGE_ALIGN(len); + if (len == 0) + return -EINVAL; + end = start + len; + host_start = start & qemu_host_page_mask; + host_end = HOST_PAGE_ALIGN(end); + + if (start > host_start) { + /* handle host page containing start */ + prot = 0; + for(addr = host_start; addr < start; addr += TARGET_PAGE_SIZE) { + prot |= page_get_flags(addr); + } + if (host_end == host_start + qemu_host_page_size) { + for(addr = end; addr < host_end; addr += TARGET_PAGE_SIZE) { + prot |= page_get_flags(addr); + } + end = host_end; + } + if (prot != 0) + host_start += qemu_host_page_size; + } + if (end < host_end) { + prot = 0; + for(addr = end; addr < host_end; addr += TARGET_PAGE_SIZE) { + prot |= page_get_flags(addr); + } + if (prot != 0) + host_end -= qemu_host_page_size; + } + + /* unmap what we can */ + if (host_start < host_end) { + ret = munmap((void *)host_start, host_end - host_start); + if (ret != 0) + return ret; + } + + page_set_flags(start, start + len, 0); + return 0; +} + +/* XXX: currently, we only handle MAP_ANONYMOUS and not MAP_FIXED + blocks which have been allocated starting on a host page */ +long target_mremap(unsigned long old_addr, unsigned long old_size, + unsigned long new_size, unsigned long flags, + unsigned long new_addr) +{ +#ifndef __APPLE__ + /* XXX: use 5 args syscall */ + new_addr = (long)mremap((void *)old_addr, old_size, new_size, flags); + if (new_addr == -1) + return new_addr; + prot = page_get_flags(old_addr); + page_set_flags(old_addr, old_addr + old_size, 0); + page_set_flags(new_addr, new_addr + new_size, prot | PAGE_VALID); + return new_addr; +#else + qerror("target_mremap: unsupported\n"); +#endif + +} + +int target_msync(unsigned long start, unsigned long len, int flags) +{ + unsigned long end; + + if (start & ~TARGET_PAGE_MASK) + return -EINVAL; + len = TARGET_PAGE_ALIGN(len); + end = start + len; + if (end < start) + return -EINVAL; + if (end == start) + return 0; + + start &= qemu_host_page_mask; + return msync((void *)start, end - start, flags); +} diff --git a/darwin-user/qemu.h b/darwin-user/qemu.h new file mode 100644 index 0000000..b6d3e6c --- /dev/null +++ b/darwin-user/qemu.h @@ -0,0 +1,178 @@ +#ifndef GEMU_H +#define GEMU_H + +#include +#include + +#include "cpu.h" + +#include "thunk.h" + +#include "gdbstub.h" + +typedef siginfo_t target_siginfo_t; +#define target_sigaction sigaction +#ifdef TARGET_I386 +struct target_pt_regs { + long ebx; + long ecx; + long edx; + long esi; + long edi; + long ebp; + long eax; + int xds; + int xes; + long orig_eax; + long eip; + int xcs; + long eflags; + long esp; + int xss; +}; +struct target_sigcontext { + int sc_onstack; + int sc_mask; + int sc_eax; + int sc_ebx; + int sc_ecx; + int sc_edx; + int sc_edi; + int sc_esi; + int sc_ebp; + int sc_esp; + int sc_ss; + int sc_eflags; + int sc_eip; + int sc_cs; + int sc_ds; + int sc_es; + int sc_fs; + int sc_gs; +}; + +#define __USER_CS (0x17) +#define __USER_DS (0x1F) + +#elif defined(TARGET_PPC) +struct target_pt_regs { + unsigned long gpr[32]; + unsigned long nip; + unsigned long msr; + unsigned long orig_gpr3; /* Used for restarting system calls */ + unsigned long ctr; + unsigned long link; + unsigned long xer; + unsigned long ccr; + unsigned long mq; /* 601 only (not used at present) */ + /* Used on APUS to hold IPL value. */ + unsigned long trap; /* Reason for being here */ + unsigned long dar; /* Fault registers */ + unsigned long dsisr; + unsigned long result; /* Result of a system call */ +}; + +struct target_sigcontext { + int sc_onstack; /* sigstack state to restore */ + int sc_mask; /* signal mask to restore */ + int sc_ir; /* pc */ + int sc_psw; /* processor status word */ + int sc_sp; /* stack pointer if sc_regs == NULL */ + void *sc_regs; /* (kernel private) saved state */ +}; + +#endif + +typedef struct TaskState { + struct TaskState *next; + int used; /* non zero if used */ + uint8_t stack[0]; +} __attribute__((aligned(16))) TaskState; + +void syscall_init(void); +long do_mach_syscall(void *cpu_env, int num, uint32_t arg1, uint32_t arg2, uint32_t arg3, + uint32_t arg4, uint32_t arg5, uint32_t arg6, uint32_t arg7, uint32_t arg8); +long do_thread_syscall(void *cpu_env, int num, uint32_t arg1, uint32_t arg2, uint32_t arg3, + uint32_t arg4, uint32_t arg5, uint32_t arg6, uint32_t arg7, uint32_t arg8); +long do_unix_syscall(void *cpu_env, int num); +int do_sigaction(int sig, const struct sigaction *act, + struct sigaction *oact); +int do_sigaltstack(const struct sigaltstack *ss, struct sigaltstack *oss); + +void gemu_log(const char *fmt, ...) GCC_FMT_ATTR(1, 2); +void qerror(const char *fmt, ...) GCC_FMT_ATTR(1, 2); + +void write_dt(void *ptr, unsigned long addr, unsigned long limit, int flags); + +extern CPUState *global_env; +void cpu_loop(CPUState *env); +void init_paths(const char *prefix); +const char *path(const char *pathname); + +#include "qemu-log.h" + +/* commpage.c */ +void commpage_init(void); +void do_commpage(void *cpu_env, int num, uint32_t arg1, uint32_t arg2, uint32_t arg3, + uint32_t arg4, uint32_t arg5, uint32_t arg6, uint32_t arg7, uint32_t arg8); + +/* signal.c */ +void process_pending_signals(void *cpu_env); +void signal_init(void); +int queue_signal(int sig, target_siginfo_t *info); +void host_to_target_siginfo(target_siginfo_t *tinfo, const siginfo_t *info); +void target_to_host_siginfo(siginfo_t *info, const target_siginfo_t *tinfo); +long do_sigreturn(CPUState *env, int num); + +/* machload.c */ +int mach_exec(const char * filename, char ** argv, char ** envp, + struct target_pt_regs * regs); + +/* mmap.c */ +int target_mprotect(unsigned long start, unsigned long len, int prot); +long target_mmap(unsigned long start, unsigned long len, int prot, + int flags, int fd, unsigned long offset); +int target_munmap(unsigned long start, unsigned long len); +long target_mremap(unsigned long old_addr, unsigned long old_size, + unsigned long new_size, unsigned long flags, + unsigned long new_addr); +int target_msync(unsigned long start, unsigned long len, int flags); + +/* user access */ + +/* XXX: todo protect every memory access */ +#define lock_user(x,y,z) (void*)(x) +#define unlock_user(x,y,z) + +/* Mac OS X ABI arguments processing */ +#ifdef TARGET_I386 +static inline uint32_t get_int_arg(int *i, CPUX86State *cpu_env) +{ + uint32_t *args = (uint32_t*)(cpu_env->regs[R_ESP] + 4 + *i); + *i+=4; + return tswap32(*args); +} +static inline uint64_t get_int64_arg(int *i, CPUX86State *cpu_env) +{ + uint64_t *args = (uint64_t*)(cpu_env->regs[R_ESP] + 4 + *i); + *i+=8; + return tswap64(*args); +} +#elif defined(TARGET_PPC) +static inline uint32_t get_int_arg(int *i, CPUPPCState *cpu_env) +{ + /* XXX: won't work when args goes on stack after gpr10 */ + uint32_t args = (uint32_t)(cpu_env->gpr[3+(*i & 0xff)/4]); + *i+=4; + return tswap32(args); +} +static inline uint64_t get_int64_arg(int *i, CPUPPCState *cpu_env) +{ + /* XXX: won't work when args goes on stack after gpr10 */ + uint64_t args = (uint64_t)(cpu_env->fpr[1+(*i >> 8)/8]); + *i+=(8 << 8) + 8; + return tswap64(args); +} +#endif + +#endif diff --git a/darwin-user/signal.c b/darwin-user/signal.c new file mode 100644 index 0000000..4862018 --- /dev/null +++ b/darwin-user/signal.c @@ -0,0 +1,457 @@ +/* + * Emulation of Linux signals + * + * Copyright (c) 2003 Fabrice Bellard + * + * 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 . + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __ia64__ +#undef uc_mcontext +#undef uc_sigmask +#undef uc_stack +#undef uc_link +#endif + +#include + +#include "qemu.h" +#include "qemu-common.h" + +#define DEBUG_SIGNAL + +#define MAX_SIGQUEUE_SIZE 1024 + +struct sigqueue { + struct sigqueue *next; + target_siginfo_t info; +}; + +struct emulated_sigaction { + struct target_sigaction sa; + int pending; /* true if signal is pending */ + struct sigqueue *first; + struct sigqueue info; /* in order to always have memory for the + first signal, we put it here */ +}; + +static struct sigaltstack target_sigaltstack_used = { + 0, 0, SA_DISABLE +}; + +static struct emulated_sigaction sigact_table[NSIG]; +static struct sigqueue sigqueue_table[MAX_SIGQUEUE_SIZE]; /* siginfo queue */ +static struct sigqueue *first_free; /* first free siginfo queue entry */ +static int signal_pending; /* non zero if a signal may be pending */ + +static void host_signal_handler(int host_signum, siginfo_t *info, + void *puc); + + +static inline int host_to_target_signal(int sig) +{ + return sig; +} + +static inline int target_to_host_signal(int sig) +{ + return sig; +} + +/* siginfo conversion */ + + + +void host_to_target_siginfo(target_siginfo_t *tinfo, const siginfo_t *info) +{ + +} + +void target_to_host_siginfo(siginfo_t *info, const target_siginfo_t *tinfo) +{ + +} + +void signal_init(void) +{ + struct sigaction act; + int i; + + /* set all host signal handlers. ALL signals are blocked during + the handlers to serialize them. */ + sigfillset(&act.sa_mask); + act.sa_flags = SA_SIGINFO; + act.sa_sigaction = host_signal_handler; + for(i = 1; i < NSIG; i++) { + sigaction(i, &act, NULL); + } + + memset(sigact_table, 0, sizeof(sigact_table)); + + first_free = &sigqueue_table[0]; + for(i = 0; i < MAX_SIGQUEUE_SIZE - 1; i++) + sigqueue_table[i].next = &sigqueue_table[i + 1]; + sigqueue_table[MAX_SIGQUEUE_SIZE - 1].next = NULL; +} + +/* signal queue handling */ + +static inline struct sigqueue *alloc_sigqueue(void) +{ + struct sigqueue *q = first_free; + if (!q) + return NULL; + first_free = q->next; + return q; +} + +static inline void free_sigqueue(struct sigqueue *q) +{ + q->next = first_free; + first_free = q; +} + +/* abort execution with signal */ +void QEMU_NORETURN force_sig(int sig) +{ + int host_sig; + host_sig = target_to_host_signal(sig); + fprintf(stderr, "qemu: uncaught target signal %d (%s) - exiting\n", + sig, strsignal(host_sig)); + _exit(-host_sig); +} + +/* queue a signal so that it will be send to the virtual CPU as soon + as possible */ +int queue_signal(int sig, target_siginfo_t *info) +{ + struct emulated_sigaction *k; + struct sigqueue *q, **pq; + target_ulong handler; + +#if defined(DEBUG_SIGNAL) + fprintf(stderr, "queue_signal: sig=%d\n", + sig); +#endif + k = &sigact_table[sig - 1]; + handler = (target_ulong)k->sa.sa_handler; + if (handler == SIG_DFL) { + /* default handler : ignore some signal. The other are fatal */ + if (sig != SIGCHLD && + sig != SIGURG && + sig != SIGWINCH) { + force_sig(sig); + } else { + return 0; /* indicate ignored */ + } + } else if (handler == host_to_target_signal(SIG_IGN)) { + /* ignore signal */ + return 0; + } else if (handler == host_to_target_signal(SIG_ERR)) { + force_sig(sig); + } else { + pq = &k->first; + if (!k->pending) { + /* first signal */ + q = &k->info; + } else { + q = alloc_sigqueue(); + if (!q) + return -EAGAIN; + while (*pq != NULL) + pq = &(*pq)->next; + } + *pq = q; + q->info = *info; + q->next = NULL; + k->pending = 1; + /* signal that a new signal is pending */ + signal_pending = 1; + return 1; /* indicates that the signal was queued */ + } +} + +static void host_signal_handler(int host_signum, siginfo_t *info, + void *puc) +{ + int sig; + target_siginfo_t tinfo; + + /* the CPU emulator uses some host signals to detect exceptions, + we we forward to it some signals */ + if (host_signum == SIGSEGV || host_signum == SIGBUS) { + if (cpu_signal_handler(host_signum, (void*)info, puc)) + return; + } + + /* get target signal number */ + sig = host_to_target_signal(host_signum); + if (sig < 1 || sig > NSIG) + return; + +#if defined(DEBUG_SIGNAL) + fprintf(stderr, "qemu: got signal %d\n", sig); +#endif + if (queue_signal(sig, &tinfo) == 1) { + /* interrupt the virtual CPU as soon as possible */ + cpu_exit(global_env); + } +} + +int do_sigaltstack(const struct sigaltstack *ss, struct sigaltstack *oss) +{ + /* XXX: test errors */ + if(oss) + { + oss->ss_sp = tswap32(target_sigaltstack_used.ss_sp); + oss->ss_size = tswap32(target_sigaltstack_used.ss_size); + oss->ss_flags = tswap32(target_sigaltstack_used.ss_flags); + } + if(ss) + { + target_sigaltstack_used.ss_sp = tswap32(ss->ss_sp); + target_sigaltstack_used.ss_size = tswap32(ss->ss_size); + target_sigaltstack_used.ss_flags = tswap32(ss->ss_flags); + } + return 0; +} + +int do_sigaction(int sig, const struct sigaction *act, + struct sigaction *oact) +{ + struct emulated_sigaction *k; + struct sigaction act1; + int host_sig; + + if (sig < 1 || sig > NSIG) + return -EINVAL; + + k = &sigact_table[sig - 1]; +#if defined(DEBUG_SIGNAL) + fprintf(stderr, "sigaction 1 sig=%d act=0x%08x, oact=0x%08x\n", + sig, (int)act, (int)oact); +#endif + if (oact) { +#if defined(DEBUG_SIGNAL) + fprintf(stderr, "sigaction 1 sig=%d act=0x%08x, oact=0x%08x\n", + sig, (int)act, (int)oact); +#endif + + oact->sa_handler = tswapl(k->sa.sa_handler); + oact->sa_flags = tswapl(k->sa.sa_flags); + oact->sa_mask = tswapl(k->sa.sa_mask); + } + if (act) { +#if defined(DEBUG_SIGNAL) + fprintf(stderr, "sigaction handler 0x%x flag 0x%x mask 0x%x\n", + act->sa_handler, act->sa_flags, act->sa_mask); +#endif + + k->sa.sa_handler = tswapl(act->sa_handler); + k->sa.sa_flags = tswapl(act->sa_flags); + k->sa.sa_mask = tswapl(act->sa_mask); + /* we update the host signal state */ + host_sig = target_to_host_signal(sig); + if (host_sig != SIGSEGV && host_sig != SIGBUS) { +#if defined(DEBUG_SIGNAL) + fprintf(stderr, "sigaction handler going to call sigaction\n", + act->sa_handler, act->sa_flags, act->sa_mask); +#endif + + sigfillset(&act1.sa_mask); + act1.sa_flags = SA_SIGINFO; + if (k->sa.sa_flags & SA_RESTART) + act1.sa_flags |= SA_RESTART; + /* NOTE: it is important to update the host kernel signal + ignore state to avoid getting unexpected interrupted + syscalls */ + if (k->sa.sa_handler == SIG_IGN) { + act1.sa_sigaction = (void *)SIG_IGN; + } else if (k->sa.sa_handler == SIG_DFL) { + act1.sa_sigaction = (void *)SIG_DFL; + } else { + act1.sa_sigaction = host_signal_handler; + } + sigaction(host_sig, &act1, NULL); + } + } + return 0; +} + + +#ifdef TARGET_I386 + +static inline void * +get_sigframe(struct emulated_sigaction *ka, CPUX86State *env, size_t frame_size) +{ + /* XXX Fix that */ + if(target_sigaltstack_used.ss_flags & SA_DISABLE) + { + int esp; + /* Default to using normal stack */ + esp = env->regs[R_ESP]; + + return (void *)((esp - frame_size) & -8ul); + } + else + { + return target_sigaltstack_used.ss_sp; + } +} + +static void setup_frame(int sig, struct emulated_sigaction *ka, + void *set, CPUState *env) +{ + void *frame; + int i, err = 0; + + fprintf(stderr, "setup_frame %d\n", sig); + frame = get_sigframe(ka, env, sizeof(*frame)); + + /* Set up registers for signal handler */ + env->regs[R_ESP] = (unsigned long) frame; + env->eip = (unsigned long) ka->sa.sa_handler; + + env->eflags &= ~TF_MASK; + + return; + +give_sigsegv: + if (sig == SIGSEGV) + ka->sa.sa_handler = SIG_DFL; + force_sig(SIGSEGV /* , current */); +} + +long do_sigreturn(CPUState *env, int num) +{ + int i = 0; + struct target_sigcontext *scp = get_int_arg(&i, env); + /* XXX Get current signal number */ + /* XXX Adjust accordin to sc_onstack, sc_mask */ + if(tswapl(scp->sc_onstack) & 0x1) + target_sigaltstack_used.ss_flags |= ~SA_DISABLE; + else + target_sigaltstack_used.ss_flags &= SA_DISABLE; + int set = tswapl(scp->sc_eax); + sigprocmask(SIG_SETMASK, &set, NULL); + + fprintf(stderr, "do_sigreturn: partially implemented %x EAX:%x EBX:%x\n", scp->sc_mask, tswapl(scp->sc_eax), tswapl(scp->sc_ebx)); + fprintf(stderr, "ECX:%x EDX:%x EDI:%x\n", scp->sc_ecx, tswapl(scp->sc_edx), tswapl(scp->sc_edi)); + fprintf(stderr, "EIP:%x\n", tswapl(scp->sc_eip)); + + env->regs[R_EAX] = tswapl(scp->sc_eax); + env->regs[R_EBX] = tswapl(scp->sc_ebx); + env->regs[R_ECX] = tswapl(scp->sc_ecx); + env->regs[R_EDX] = tswapl(scp->sc_edx); + env->regs[R_EDI] = tswapl(scp->sc_edi); + env->regs[R_ESI] = tswapl(scp->sc_esi); + env->regs[R_EBP] = tswapl(scp->sc_ebp); + env->regs[R_ESP] = tswapl(scp->sc_esp); + env->segs[R_SS].selector = (void*)tswapl(scp->sc_ss); + env->eflags = tswapl(scp->sc_eflags); + env->eip = tswapl(scp->sc_eip); + env->segs[R_CS].selector = (void*)tswapl(scp->sc_cs); + env->segs[R_DS].selector = (void*)tswapl(scp->sc_ds); + env->segs[R_ES].selector = (void*)tswapl(scp->sc_es); + env->segs[R_FS].selector = (void*)tswapl(scp->sc_fs); + env->segs[R_GS].selector = (void*)tswapl(scp->sc_gs); + + /* Again, because our caller's caller will reset EAX */ + return env->regs[R_EAX]; +} + +#else + +static void setup_frame(int sig, struct emulated_sigaction *ka, + void *set, CPUState *env) +{ + fprintf(stderr, "setup_frame: not implemented\n"); +} + +long do_sigreturn(CPUState *env, int num) +{ + int i = 0; + struct target_sigcontext *scp = get_int_arg(&i, env); + fprintf(stderr, "do_sigreturn: not implemented\n"); + return -ENOSYS; +} + +#endif + +void process_pending_signals(void *cpu_env) +{ + struct emulated_sigaction *k; + struct sigqueue *q; + target_ulong handler; + int sig; + + if (!signal_pending) + return; + + k = sigact_table; + + for(sig = 1; sig <= NSIG; sig++) { + if (k->pending) + goto handle_signal; + k++; + } + + /* if no signal is pending, just return */ + signal_pending = 0; + return; +handle_signal: + #ifdef DEBUG_SIGNAL + fprintf(stderr, "qemu: process signal %d\n", sig); + #endif + /* dequeue signal */ + q = k->first; + k->first = q->next; + if (!k->first) + k->pending = 0; + + sig = gdb_handlesig (cpu_env, sig); + if (!sig) { + fprintf (stderr, "Lost signal\n"); + abort(); + } + + handler = k->sa.sa_handler; + if (handler == SIG_DFL) { + /* default handler : ignore some signal. The other are fatal */ + if (sig != SIGCHLD && + sig != SIGURG && + sig != SIGWINCH) { + force_sig(sig); + } + } else if (handler == SIG_IGN) { + /* ignore sig */ + } else if (handler == SIG_ERR) { + force_sig(sig); + } else { + + setup_frame(sig, k, 0, cpu_env); + if (k->sa.sa_flags & SA_RESETHAND) + k->sa.sa_handler = SIG_DFL; + } + if (q != &k->info) + free_sigqueue(q); +} diff --git a/darwin-user/syscall.c b/darwin-user/syscall.c new file mode 100644 index 0000000..060acc8 --- /dev/null +++ b/darwin-user/syscall.c @@ -0,0 +1,1566 @@ +/* + * Darwin syscalls + * + * Copyright (c) 2003 Fabrice Bellard + * Copyright (c) 2006 Pierre d'Herbemont + * + * 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 . + */ +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include +#include + +#include + +#include "qemu.h" + +//#define DEBUG_SYSCALL + +#ifdef DEBUG_SYSCALL +# define DEBUG_FORCE_ENABLE_LOCAL() int __DEBUG_qemu_user_force_enable = 1 +# define DEBUG_BEGIN_ENABLE __DEBUG_qemu_user_force_enable = 1; +# define DEBUG_END_ENABLE __DEBUG_qemu_user_force_enable = 0; + +# define DEBUG_DISABLE_ALL() static int __DEBUG_qemu_user_force_enable = 0 +# define DEBUG_ENABLE_ALL() static int __DEBUG_qemu_user_force_enable = 1 + DEBUG_ENABLE_ALL(); + +# define DPRINTF(...) do { qemu_log(__VA_ARGS__); \ + if(__DEBUG_qemu_user_force_enable) fprintf(stderr, __VA_ARGS__); \ + } while(0) +#else +# define DEBUG_FORCE_ENABLE_LOCAL() +# define DEBUG_BEGIN_ENABLE +# define DEBUG_END_ENABLE + +# define DPRINTF(...) do { qemu_log(__VA_ARGS__); } while(0) +#endif + +enum { + bswap_out = 0, + bswap_in = 1 +}; + +extern const char *interp_prefix; + +static inline long get_errno(long ret) +{ + if (ret == -1) + return -errno; + else + return ret; +} + +static inline int is_error(long ret) +{ + return (unsigned long)ret >= (unsigned long)(-4096); +} + +/* ------------------------------------------------------------ + Mach syscall handling +*/ + +void static inline print_description_msg_header(mach_msg_header_t *hdr) +{ + char *name = NULL; + int i; + struct { int number; char *name; } msg_name[] = + { + /* see http://fxr.watson.org/fxr/source/compat/mach/mach_namemap.c?v=NETBSD */ + { 200, "host_info" }, + { 202, "host_page_size" }, + { 206, "host_get_clock_service" }, + { 206, "host_get_clock_service" }, + { 206, "host_get_clock_service" }, + { 306, "host_get_clock_service" }, + { 3204, "mach_port_allocate" }, + { 3206, "mach_port_deallocate" }, + { 3404, "mach_ports_lookup" }, + { 3409, "mach_task_get_special_port" }, + { 3414, "mach_task_get_exception_ports" }, + { 3418, "mach_semaphore_create" }, + { 3504, "mach_semaphore_create" }, + { 3509, "mach_semaphore_create" }, + { 3518, "semaphore_create" }, + { 3616, "thread_policy" }, + { 3801, "vm_allocate" }, + { 3802, "vm_deallocate" }, + { 3802, "vm_deallocate" }, + { 3803, "vm_protect" }, + { 3812, "vm_map" }, + { 4241776, "lu_message_send_id" }, /* lookupd */ + { 4241876, "lu_message_reply_id" }, /* lookupd */ + }; + + for(i = 0; i < ARRAY_SIZE(msg_name); i++) { + if(msg_name[i].number == hdr->msgh_id) + { + name = msg_name[i].name; + break; + } + } + if(!name) + DPRINTF("unknown mach msg %d 0x%x\n", hdr->msgh_id, hdr->msgh_id); + else + DPRINTF("%s\n", name); +#if 0 + DPRINTF("Bits: %8x\n", hdr->msgh_bits); + DPRINTF("Size: %8x\n", hdr->msgh_size); + DPRINTF("Rmte: %8x\n", hdr->msgh_remote_port); + DPRINTF("Locl: %8x\n", hdr->msgh_local_port); + DPRINTF("Rsrv: %8x\n", hdr->msgh_reserved); + + DPRINTF("Id : %8x\n", hdr->msgh_id); + + NDR_record_t *ndr = (NDR_record_t *)(hdr + 1); + DPRINTF("hdr = %p, sizeof(hdr) = %x, NDR = %p\n", hdr, (unsigned int)sizeof(mach_msg_header_t), ndr); + DPRINTF("%d %d %d %d %d %d %d %d\n", + ndr->mig_vers, ndr->if_vers, ndr->reserved1, ndr->mig_encoding, + ndr->int_rep, ndr->char_rep, ndr->float_rep, ndr->reserved2); +#endif +} + +static inline void print_mach_msg_return(mach_msg_return_t ret) +{ + int i, found = 0; +#define MACH_MSG_RET(msg) { msg, #msg } + struct { int code; char *name; } msg_name[] = + { + /* ref: http://darwinsource.opendarwin.org/10.4.2/xnu-792.2.4/osfmk/man/mach_msg.html */ + /* send message */ + MACH_MSG_RET(MACH_SEND_MSG_TOO_SMALL), + MACH_MSG_RET(MACH_SEND_NO_BUFFER), + MACH_MSG_RET(MACH_SEND_INVALID_DATA), + MACH_MSG_RET(MACH_SEND_INVALID_HEADER), + MACH_MSG_RET(MACH_SEND_INVALID_DEST), + MACH_MSG_RET(MACH_SEND_INVALID_NOTIFY), + MACH_MSG_RET(MACH_SEND_INVALID_REPLY), + MACH_MSG_RET(MACH_SEND_INVALID_TRAILER), + MACH_MSG_RET(MACH_SEND_INVALID_MEMORY), + MACH_MSG_RET(MACH_SEND_INVALID_RIGHT), + MACH_MSG_RET(MACH_SEND_INVALID_TYPE), + MACH_MSG_RET(MACH_SEND_INTERRUPTED), + MACH_MSG_RET(MACH_SEND_TIMED_OUT), + + MACH_MSG_RET(MACH_RCV_BODY_ERROR), + MACH_MSG_RET(MACH_RCV_HEADER_ERROR), + + MACH_MSG_RET(MACH_RCV_IN_SET), + MACH_MSG_RET(MACH_RCV_INTERRUPTED), + + MACH_MSG_RET(MACH_RCV_INVALID_DATA), + MACH_MSG_RET(MACH_RCV_INVALID_NAME), + MACH_MSG_RET(MACH_RCV_INVALID_NOTIFY), + MACH_MSG_RET(MACH_RCV_INVALID_TRAILER), + MACH_MSG_RET(MACH_RCV_INVALID_TYPE), + + MACH_MSG_RET(MACH_RCV_PORT_CHANGED), + MACH_MSG_RET(MACH_RCV_PORT_DIED), + + MACH_MSG_RET(MACH_RCV_SCATTER_SMALL), + MACH_MSG_RET(MACH_RCV_TIMED_OUT), + MACH_MSG_RET(MACH_RCV_TOO_LARGE) + }; +#undef MACH_MSG_RET + + if( ret == MACH_MSG_SUCCESS) + DPRINTF("MACH_MSG_SUCCESS\n"); + else + { + for( i = 0; i < ARRAY_SIZE(msg_name); i++) { + if(msg_name[i].code == ret) { + DPRINTF("%s\n", msg_name[i].name); + found = 1; + break; + } + } + if(!found) + qerror("unknow mach message ret code %d\n", ret); + } +} + +static inline void swap_mach_msg_header(mach_msg_header_t *hdr) +{ + hdr->msgh_bits = tswap32(hdr->msgh_bits); + hdr->msgh_size = tswap32(hdr->msgh_size); + hdr->msgh_remote_port = tswap32(hdr->msgh_remote_port); + hdr->msgh_local_port = tswap32(hdr->msgh_local_port); + hdr->msgh_reserved = tswap32(hdr->msgh_reserved); + hdr->msgh_id = tswap32(hdr->msgh_id); +} + +struct complex_msg { + mach_msg_header_t hdr; + mach_msg_body_t body; +}; + +static inline void swap_mach_msg_body(struct complex_msg *complex_msg, int bswap) +{ + mach_msg_port_descriptor_t *descr = (mach_msg_port_descriptor_t *)(complex_msg+1); + int i,j; + + if(bswap == bswap_in) + tswap32s(&complex_msg->body.msgh_descriptor_count); + + DPRINTF("body.msgh_descriptor_count %d\n", complex_msg->body.msgh_descriptor_count); + + for(i = 0; i < complex_msg->body.msgh_descriptor_count; i++) { + switch(descr->type) + { + case MACH_MSG_PORT_DESCRIPTOR: + tswap32s(&descr->name); + descr++; + break; + case MACH_MSG_OOL_DESCRIPTOR: + { + mach_msg_ool_descriptor_t *ool = (void *)descr; + tswap32s((uint32_t *)&ool->address); + tswap32s(&ool->size); + + descr = (mach_msg_port_descriptor_t *)(ool+1); + break; + } + case MACH_MSG_OOL_PORTS_DESCRIPTOR: + { + mach_msg_ool_ports_descriptor_t *ool_ports = (void *)descr; + mach_port_name_t * port_names; + + if(bswap == bswap_in) + { + tswap32s((uint32_t *)&ool_ports->address); + tswap32s(&ool_ports->count); + } + + port_names = ool_ports->address; + + for(j = 0; j < ool_ports->count; j++) + tswap32s(&port_names[j]); + + if(bswap == bswap_out) + { + tswap32s((uint32_t *)&ool_ports->address); + tswap32s(&ool_ports->count); + } + + descr = (mach_msg_port_descriptor_t *)(ool_ports+1); + break; + } + default: qerror("unknow mach msg descriptor type %x\n", descr->type); + } + } + if(bswap == bswap_out) + tswap32s(&complex_msg->body.msgh_descriptor_count); +} + +static inline void swap_mach_msg(mach_msg_header_t *hdr, int bswap) +{ + if (bswap == bswap_out && hdr->msgh_bits & MACH_MSGH_BITS_COMPLEX) + swap_mach_msg_body((struct complex_msg *)hdr, bswap); + + swap_mach_msg_header(hdr); + + if (bswap == bswap_in && hdr->msgh_bits & MACH_MSGH_BITS_COMPLEX) + swap_mach_msg_body((struct complex_msg *)hdr, bswap); +} + +static inline uint32_t target_mach_msg_trap( + mach_msg_header_t *hdr, uint32_t options, uint32_t send_size, + uint32_t rcv_size, uint32_t rcv_name, uint32_t time_out, uint32_t notify) +{ + extern int mach_msg_trap(mach_msg_header_t *, mach_msg_option_t, + mach_msg_size_t, mach_msg_size_t, mach_port_t, + mach_msg_timeout_t, mach_port_t); + mach_msg_audit_trailer_t *trailer; + mach_msg_id_t msg_id; + uint32_t ret = 0; + int i; + + swap_mach_msg(hdr, bswap_in); + + msg_id = hdr->msgh_id; + + print_description_msg_header(hdr); + + ret = mach_msg_trap(hdr, options, send_size, rcv_size, rcv_name, time_out, notify); + + print_mach_msg_return(ret); + + if( (options & MACH_RCV_MSG) && (REQUESTED_TRAILER_SIZE(options) > 0) ) + { + /* XXX: the kernel always return the full trailer with MACH_SEND_MSG, so we should + probably always bswap it */ + /* warning: according to Mac OS X Internals (the book) msg_size might be expressed in + natural_t units but according to xnu/osfmk/mach/message.h: "The size of + the message must be specified in bytes" */ + trailer = (mach_msg_audit_trailer_t *)((uint8_t *)hdr + hdr->msgh_size); + /* XXX: Should probably do that based on the option asked by the sender, but dealing + with kernel answer seems more sound */ + switch(trailer->msgh_trailer_size) + { + case sizeof(mach_msg_audit_trailer_t): + for(i = 0; i < 8; i++) + tswap32s(&trailer->msgh_audit.val[i]); + /* Fall in mach_msg_security_trailer_t case */ + case sizeof(mach_msg_security_trailer_t): + tswap32s(&trailer->msgh_sender.val[0]); + tswap32s(&trailer->msgh_sender.val[1]); + /* Fall in mach_msg_seqno_trailer_t case */ + case sizeof(mach_msg_seqno_trailer_t): + tswap32s(&trailer->msgh_seqno); + /* Fall in mach_msg_trailer_t case */ + case sizeof(mach_msg_trailer_t): + tswap32s(&trailer->msgh_trailer_type); + tswap32s(&trailer->msgh_trailer_size); + break; + case 0: + /* Safer not to byteswap, but probably wrong */ + break; + default: + qerror("unknow trailer type given its size %d\n", trailer->msgh_trailer_size); + break; + } + } + + /* Special message handling */ + switch (msg_id) { + case 200: /* host_info */ + { + mig_reply_error_t *err = (mig_reply_error_t *)hdr; + struct { + uint32_t unknow1; + uint32_t max_cpus; + uint32_t avail_cpus; + uint32_t memory_size; + uint32_t cpu_type; + uint32_t cpu_subtype; + } *data = (void *)(err+1); + + DPRINTF("maxcpu = 0x%x\n", data->max_cpus); + DPRINTF("numcpu = 0x%x\n", data->avail_cpus); + DPRINTF("memsize = 0x%x\n", data->memory_size); + +#if defined(TARGET_I386) + data->cpu_type = CPU_TYPE_I386; + DPRINTF("cpu_type changed to 0x%x(i386)\n", data->cpu_type); + data->cpu_subtype = CPU_SUBTYPE_PENT; + DPRINTF("cpu_subtype changed to 0x%x(i386_pent)\n", data->cpu_subtype); +#elif defined(TARGET_PPC) + data->cpu_type = CPU_TYPE_POWERPC; + DPRINTF("cpu_type changed to 0x%x(ppc)\n", data->cpu_type); + data->cpu_subtype = CPU_SUBTYPE_POWERPC_750; + DPRINTF("cpu_subtype changed to 0x%x(ppc_all)\n", data->cpu_subtype); +#else +# error target not supported +#endif + break; + } + case 202: /* host_page_size */ + { + mig_reply_error_t *err = (mig_reply_error_t *)hdr; + uint32_t *pagesize = (uint32_t *)(err+1); + + DPRINTF("pagesize = %d\n", *pagesize); + break; + } + default: break; + } + + swap_mach_msg(hdr, bswap_out); + + return ret; +} + +long do_mach_syscall(void *cpu_env, int num, uint32_t arg1, uint32_t arg2, uint32_t arg3, + uint32_t arg4, uint32_t arg5, uint32_t arg6, uint32_t arg7, + uint32_t arg8) +{ + extern uint32_t mach_reply_port(void); + + long ret = 0; + + arg1 = tswap32(arg1); + arg2 = tswap32(arg2); + arg3 = tswap32(arg3); + arg4 = tswap32(arg4); + arg5 = tswap32(arg5); + arg6 = tswap32(arg6); + arg7 = tswap32(arg7); + arg8 = tswap32(arg8); + + DPRINTF("mach syscall %d : " , num); + + switch(num) { + /* see xnu/osfmk/mach/syscall_sw.h */ + case -26: + DPRINTF("mach_reply_port()\n"); + ret = mach_reply_port(); + break; + case -27: + DPRINTF("mach_thread_self()\n"); + ret = mach_thread_self(); + break; + case -28: + DPRINTF("mach_task_self()\n"); + ret = mach_task_self(); + break; + case -29: + DPRINTF("mach_host_self()\n"); + ret = mach_host_self(); + break; + case -31: + DPRINTF("mach_msg_trap(0x%x, 0x%x, 0x%x, 0x%x, 0x%x, 0x%x, 0x%x)\n", + arg1, arg2, arg3, arg4, arg5, arg6, arg7); + ret = target_mach_msg_trap((mach_msg_header_t *)arg1, arg2, arg3, arg4, arg5, arg6, arg7); + break; +/* may need more translation if target arch is different from host */ +#if (defined(TARGET_I386) && defined(__i386__)) || (defined(TARGET_PPC) && defined(__ppc__)) + case -33: + DPRINTF("semaphore_signal_trap(0x%x)\n", arg1); + ret = semaphore_signal_trap(arg1); + break; + case -34: + DPRINTF("semaphore_signal_all_trap(0x%x)\n", arg1); + ret = semaphore_signal_all_trap(arg1); + break; + case -35: + DPRINTF("semaphore_signal_thread_trap(0x%x)\n", arg1, arg2); + ret = semaphore_signal_thread_trap(arg1,arg2); + break; +#endif + case -36: + DPRINTF("semaphore_wait_trap(0x%x)\n", arg1); + extern int semaphore_wait_trap(int); // XXX: is there any header for that? + ret = semaphore_wait_trap(arg1); + break; +/* may need more translation if target arch is different from host */ +#if (defined(TARGET_I386) && defined(__i386__)) || (defined(TARGET_PPC) && defined(__ppc__)) + case -37: + DPRINTF("semaphore_wait_signal_trap(0x%x, 0x%x)\n", arg1, arg2); + ret = semaphore_wait_signal_trap(arg1,arg2); + break; +#endif + case -43: + DPRINTF("map_fd(0x%x, 0x%x, 0x%x, 0x%x, 0x%x)\n", + arg1, arg2, arg3, arg4, arg5); + ret = map_fd(arg1, arg2, (void*)arg3, arg4, arg5); + tswap32s((uint32_t*)arg3); + break; +/* may need more translation if target arch is different from host */ +#if (defined(TARGET_I386) && defined(__i386__)) || (defined(TARGET_PPC) && defined(__ppc__)) + case -61: + DPRINTF("syscall_thread_switch(0x%x, 0x%x, 0x%x)\n", + arg1, arg2, arg3); + ret = syscall_thread_switch(arg1, arg2, arg3); // just a hint to the scheduler; can drop? + break; +#endif + case -89: + DPRINTF("mach_timebase_info(0x%x)\n", arg1); + struct mach_timebase_info info; + ret = mach_timebase_info(&info); + if(!is_error(ret)) + { + struct mach_timebase_info *outInfo = (void*)arg1; + outInfo->numer = tswap32(info.numer); + outInfo->denom = tswap32(info.denom); + } + break; + case -90: + DPRINTF("mach_wait_until()\n"); + extern int mach_wait_until(uint64_t); // XXX: is there any header for that? + ret = mach_wait_until(((uint64_t)arg2<<32) | (uint64_t)arg1); + break; + case -91: + DPRINTF("mk_timer_create()\n"); + extern int mk_timer_create(); // XXX: is there any header for that? + ret = mk_timer_create(); + break; + case -92: + DPRINTF("mk_timer_destroy()\n"); + extern int mk_timer_destroy(int); // XXX: is there any header for that? + ret = mk_timer_destroy(arg1); + break; + case -93: + DPRINTF("mk_timer_create()\n"); + extern int mk_timer_arm(int, uint64_t); // XXX: is there any header for that? + ret = mk_timer_arm(arg1, ((uint64_t)arg3<<32) | (uint64_t)arg2); + break; + case -94: + DPRINTF("mk_timer_cancel()\n"); + extern int mk_timer_cancel(int, uint64_t *); // XXX: is there any header for that? + ret = mk_timer_cancel(arg1, (uint64_t *)arg2); + if((!is_error(ret)) && arg2) + tswap64s((uint64_t *)arg2); + break; + default: + gemu_log("qemu: Unsupported mach syscall: %d(0x%x)\n", num, num); + gdb_handlesig (cpu_env, SIGTRAP); + exit(0); + break; + } + return ret; +} + +/* ------------------------------------------------------------ + thread type syscall handling +*/ +long do_thread_syscall(void *cpu_env, int num, uint32_t arg1, uint32_t arg2, uint32_t arg3, + uint32_t arg4, uint32_t arg5, uint32_t arg6, uint32_t arg7, + uint32_t arg8) +{ + extern uint32_t cthread_set_self(uint32_t); + extern uint32_t processor_facilities_used(void); + long ret = 0; + + arg1 = tswap32(arg1); + arg2 = tswap32(arg2); + arg3 = tswap32(arg3); + arg4 = tswap32(arg4); + arg5 = tswap32(arg5); + arg6 = tswap32(arg6); + arg7 = tswap32(arg7); + arg8 = tswap32(arg8); + + DPRINTF("thread syscall %d : " , num); + + switch(num) { +#ifdef TARGET_I386 + case 0x3: +#endif + case 0x7FF1: /* cthread_set_self */ + DPRINTF("cthread_set_self(0x%x)\n", (unsigned int)arg1); + ret = cthread_set_self(arg1); +#ifdef TARGET_I386 + /* we need to update the LDT with the address of the thread */ + write_dt((void *)(((CPUX86State *) cpu_env)->ldt.base + (4 * sizeof(uint64_t))), arg1, 1, + DESC_G_MASK | DESC_B_MASK | DESC_P_MASK | DESC_S_MASK | + (3 << DESC_DPL_SHIFT) | (0x2 << DESC_TYPE_SHIFT)); + /* New i386 convention, %gs should be set to our this LDT entry */ + cpu_x86_load_seg(cpu_env, R_GS, 0x27); + /* Old i386 convention, the kernel returns the selector for the cthread (pre-10.4.8?)*/ + ret = 0x27; +#endif + break; + case 0x7FF2: /* Called the super-fast pthread_self handler by the apple guys */ + DPRINTF("pthread_self()\n"); + ret = (uint32_t)pthread_self(); + break; + case 0x7FF3: + DPRINTF("processor_facilities_used()\n"); +#ifdef __i386__ + qerror("processor_facilities_used: not implemented!\n"); +#else + ret = (uint32_t)processor_facilities_used(); +#endif + break; + default: + gemu_log("qemu: Unsupported thread syscall: %d(0x%x)\n", num, num); + gdb_handlesig (cpu_env, SIGTRAP); + exit(0); + break; + } + return ret; +} + +/* ------------------------------------------------------------ + ioctl handling +*/ +static inline void byteswap_termios(struct termios *t) +{ + tswap32s((uint32_t*)&t->c_iflag); + tswap32s((uint32_t*)&t->c_oflag); + tswap32s((uint32_t*)&t->c_cflag); + tswap32s((uint32_t*)&t->c_lflag); + /* 20 (char) bytes then */ + tswap32s((uint32_t*)&t->c_ispeed); + tswap32s((uint32_t*)&t->c_ospeed); +} + +static inline void byteswap_winsize(struct winsize *w) +{ + tswap16s(&w->ws_row); + tswap16s(&w->ws_col); + tswap16s(&w->ws_xpixel); + tswap16s(&w->ws_ypixel); +} + +#define STRUCT(name, ...) STRUCT_ ## name, +#define STRUCT_SPECIAL(name) STRUCT_ ## name, +enum { +#include "ioctls_types.h" +}; +#undef STRUCT +#undef STRUCT_SPECIAL + +#define STRUCT(name, ...) const argtype struct_ ## name ## _def[] = { __VA_ARGS__, TYPE_NULL }; +#define STRUCT_SPECIAL(name) +#include "ioctls_types.h" +#undef STRUCT +#undef STRUCT_SPECIAL + +typedef struct IOCTLEntry { + unsigned int target_cmd; + unsigned int host_cmd; + const char *name; + int access; + const argtype arg_type[5]; +} IOCTLEntry; + +#define IOC_R 0x0001 +#define IOC_W 0x0002 +#define IOC_RW (IOC_R | IOC_W) + +#define MAX_STRUCT_SIZE 4096 + +static IOCTLEntry ioctl_entries[] = { +#define IOCTL(cmd, access, ...) \ + { cmd, cmd, #cmd, access, { __VA_ARGS__ } }, +#include "ioctls.h" + { 0, 0, }, +}; + +/* ??? Implement proper locking for ioctls. */ +static long do_ioctl(long fd, long cmd, long arg) +{ + const IOCTLEntry *ie; + const argtype *arg_type; + int ret; + uint8_t buf_temp[MAX_STRUCT_SIZE]; + int target_size; + void *argptr; + + ie = ioctl_entries; + for(;;) { + if (ie->target_cmd == 0) { + gemu_log("Unsupported ioctl: cmd=0x%04lx\n", cmd); + return -ENOSYS; + } + if (ie->target_cmd == cmd) + break; + ie++; + } + arg_type = ie->arg_type; +#if defined(DEBUG) + gemu_log("ioctl: cmd=0x%04lx (%s)\n", cmd, ie->name); +#endif + switch(arg_type[0]) { + case TYPE_NULL: + /* no argument */ + ret = get_errno(ioctl(fd, ie->host_cmd)); + break; + case TYPE_PTRVOID: + case TYPE_INT: + /* int argment */ + ret = get_errno(ioctl(fd, ie->host_cmd, arg)); + break; + case TYPE_PTR: + arg_type++; + target_size = thunk_type_size(arg_type, 0); + switch(ie->access) { + case IOC_R: + ret = get_errno(ioctl(fd, ie->host_cmd, buf_temp)); + if (!is_error(ret)) { + argptr = lock_user(arg, target_size, 0); + thunk_convert(argptr, buf_temp, arg_type, THUNK_TARGET); + unlock_user(argptr, arg, target_size); + } + break; + case IOC_W: + argptr = lock_user(arg, target_size, 1); + thunk_convert(buf_temp, argptr, arg_type, THUNK_HOST); + unlock_user(argptr, arg, 0); + ret = get_errno(ioctl(fd, ie->host_cmd, buf_temp)); + break; + default: + case IOC_RW: + argptr = lock_user(arg, target_size, 1); + thunk_convert(buf_temp, argptr, arg_type, THUNK_HOST); + unlock_user(argptr, arg, 0); + ret = get_errno(ioctl(fd, ie->host_cmd, buf_temp)); + if (!is_error(ret)) { + argptr = lock_user(arg, target_size, 0); + thunk_convert(argptr, buf_temp, arg_type, THUNK_TARGET); + unlock_user(argptr, arg, target_size); + } + break; + } + break; + default: + gemu_log("Unsupported ioctl type: cmd=0x%04lx type=%d\n", cmd, arg_type[0]); + ret = -ENOSYS; + break; + } + return ret; +} + +/* ------------------------------------------------------------ + Unix syscall handling +*/ + +static inline void byteswap_attrlist(struct attrlist *a) +{ + tswap16s(&a->bitmapcount); + tswap16s(&a->reserved); + tswap32s(&a->commonattr); + tswap32s(&a->volattr); + tswap32s(&a->dirattr); + tswap32s(&a->fileattr); + tswap32s(&a->forkattr); +} + +struct attrbuf_header { + unsigned long length; +}; + +static inline void byteswap_attrbuf(struct attrbuf_header *attrbuf, struct attrlist *attrlist) +{ + DPRINTF("attrBuf.lenght %lx\n", attrbuf->length); +} + +static inline void byteswap_statfs(struct statfs *s) +{ + tswap16s((uint16_t*)&s->f_otype); + tswap16s((uint16_t*)&s->f_oflags); + tswap32s((uint32_t*)&s->f_bsize); + tswap32s((uint32_t*)&s->f_iosize); + tswap32s((uint32_t*)&s->f_blocks); + tswap32s((uint32_t*)&s->f_bfree); + tswap32s((uint32_t*)&s->f_bavail); + tswap32s((uint32_t*)&s->f_files); + tswap32s((uint32_t*)&s->f_ffree); + tswap32s((uint32_t*)&s->f_fsid.val[0]); + tswap32s((uint32_t*)&s->f_fsid.val[1]); + tswap16s((uint16_t*)&s->f_reserved1); + tswap16s((uint16_t*)&s->f_type); + tswap32s((uint32_t*)&s->f_flags); +} + +static inline void byteswap_stat(struct stat *s) +{ + tswap32s((uint32_t*)&s->st_dev); + tswap32s(&s->st_ino); + tswap16s(&s->st_mode); + tswap16s(&s->st_nlink); + tswap32s(&s->st_uid); + tswap32s(&s->st_gid); + tswap32s((uint32_t*)&s->st_rdev); + tswap32s((uint32_t*)&s->st_atimespec.tv_sec); + tswap32s((uint32_t*)&s->st_atimespec.tv_nsec); + tswap32s((uint32_t*)&s->st_mtimespec.tv_sec); + tswap32s((uint32_t*)&s->st_mtimespec.tv_nsec); + tswap32s((uint32_t*)&s->st_ctimespec.tv_sec); + tswap32s((uint32_t*)&s->st_ctimespec.tv_nsec); + tswap64s((uint64_t*)&s->st_size); + tswap64s((uint64_t*)&s->st_blocks); + tswap32s((uint32_t*)&s->st_blksize); + tswap32s(&s->st_flags); + tswap32s(&s->st_gen); +} + +static inline void byteswap_dirents(struct dirent *d, int bytes) +{ + char *b; + for( b = (char*)d; (int)b < (int)d+bytes; ) + { + unsigned short s = ((struct dirent *)b)->d_reclen; + tswap32s(&((struct dirent *)b)->d_ino); + tswap16s(&((struct dirent *)b)->d_reclen); + if(s<=0) + break; + b += s; + } +} + +static inline void byteswap_iovec(struct iovec *v, int n) +{ + int i; + for(i = 0; i < n; i++) + { + tswap32s((uint32_t*)&v[i].iov_base); + tswap32s((uint32_t*)&v[i].iov_len); + } +} + +static inline void byteswap_timeval(struct timeval *t) +{ + tswap32s((uint32_t*)&t->tv_sec); + tswap32s((uint32_t*)&t->tv_usec); +} + +long do_unix_syscall_indirect(void *cpu_env, int num); +long do_sync(void); +long do_exit(uint32_t arg1); +long do_getlogin(char *out, uint32_t size); +long do_open(char * arg1, uint32_t arg2, uint32_t arg3); +long do_getfsstat(struct statfs * arg1, uint32_t arg2, uint32_t arg3); +long do_sigprocmask(uint32_t arg1, uint32_t * arg2, uint32_t * arg3); +long do_execve(char* arg1, char ** arg2, char ** arg3); +long do_getgroups(uint32_t arg1, gid_t * arg2); +long do_gettimeofday(struct timeval * arg1, void * arg2); +long do_readv(uint32_t arg1, struct iovec * arg2, uint32_t arg3); +long do_writev(uint32_t arg1, struct iovec * arg2, uint32_t arg3); +long do_utimes(char * arg1, struct timeval * arg2); +long do_futimes(uint32_t arg1, struct timeval * arg2); +long do_statfs(char * arg1, struct statfs * arg2); +long do_fstatfs(uint32_t arg1, struct statfs * arg2); +long do_stat(char * arg1, struct stat * arg2); +long do_fstat(uint32_t arg1, struct stat * arg2); +long do_lstat(char * arg1, struct stat * arg2); +long do_getdirentries(uint32_t arg1, void* arg2, uint32_t arg3, void* arg4); +long do_lseek(void *cpu_env, int num); +long do___sysctl(int * name, uint32_t namelen, void * oldp, size_t * oldlenp, void * newp, size_t newlen /* ignored */); +long do_getattrlist(void * arg1, void * arg2, void * arg3, uint32_t arg4, uint32_t arg5); +long do_getdirentriesattr(uint32_t arg1, void * arg2, void * arg3, size_t arg4, void * arg5, void * arg6, void* arg7, uint32_t arg8); +long do_fcntl(int fd, int cmd, int arg); + +long no_syscall(void *cpu_env, int num); + +long do_pread(uint32_t arg1, void * arg2, size_t arg3, off_t arg4) +{ + DPRINTF("0x%x, %p, 0x%lx, 0x%" PRIx64 "\n", arg1, arg2, arg3, arg4); + long ret = pread(arg1, arg2, arg3, arg4); + return ret; +} + +long do_read(int d, void *buf, size_t nbytes) +{ + DPRINTF("0x%x, %p, 0x%lx\n", d, buf, nbytes); + long ret = get_errno(read(d, buf, nbytes)); + if(!is_error(ret)) + DPRINTF("%x\n", *(uint32_t*)buf); + return ret; +} + +long unimpl_unix_syscall(void *cpu_env, int num); + +typedef long (*syscall_function_t)(void *cpu_env, int num); + + +/* define a table that will handle the syscall number->function association */ +#define VOID void +#define INT (uint32_t)get_int_arg(&i, cpu_env) +#define INT64 (uint64_t)get_int64_arg(&i, cpu_env) +#define UINT (unsigned int)INT +#define PTR (void*)INT + +#define SIZE INT +#define OFFSET INT64 + +#define WRAPPER_CALL_DIRECT_0(function, args) long __qemu_##function(void *cpu_env) { return (long)function(); } +#define WRAPPER_CALL_DIRECT_1(function, _arg1) long __qemu_##function(void *cpu_env) { int i = 0; typeof(_arg1) arg1 = _arg1; return (long)function(arg1); } +#define WRAPPER_CALL_DIRECT_2(function, _arg1, _arg2) long __qemu_##function(void *cpu_env) { int i = 0; typeof(_arg1) arg1 = _arg1; typeof(_arg2) arg2 = _arg2; return (long)function(arg1, arg2); } +#define WRAPPER_CALL_DIRECT_3(function, _arg1, _arg2, _arg3) long __qemu_##function(void *cpu_env) { int i = 0; typeof(_arg1) arg1 = _arg1; typeof(_arg2) arg2 = _arg2; typeof(_arg3) arg3 = _arg3; return (long)function(arg1, arg2, arg3); } +#define WRAPPER_CALL_DIRECT_4(function, _arg1, _arg2, _arg3, _arg4) long __qemu_##function(void *cpu_env) { int i = 0; typeof(_arg1) arg1 = _arg1; typeof(_arg2) arg2 = _arg2; typeof(_arg3) arg3 = _arg3; typeof(_arg4) arg4 = _arg4; return (long)function(arg1, arg2, arg3, arg4); } +#define WRAPPER_CALL_DIRECT_5(function, _arg1, _arg2, _arg3, _arg4, _arg5) long __qemu_##function(void *cpu_env) { int i = 0; typeof(_arg1) arg1 = _arg1; typeof(_arg2) arg2 = _arg2; typeof(_arg3) arg3 = _arg3; typeof(_arg4) arg4 = _arg4; typeof(_arg5) arg5 = _arg5; return (long)function(arg1, arg2, arg3, arg4, arg5); } +#define WRAPPER_CALL_DIRECT_6(function, _arg1, _arg2, _arg3, _arg4, _arg5, _arg6) long __qemu_##function(void *cpu_env) { int i = 0; typeof(_arg1) arg1 = _arg1; typeof(_arg2) arg2 = _arg2; typeof(_arg3) arg3 = _arg3; typeof(_arg4) arg4 = _arg4; typeof(_arg5) arg5 = _arg5; typeof(_arg6) arg6 = _arg6; return (long)function(arg1, arg2, arg3, arg4, arg5, arg6); } +#define WRAPPER_CALL_DIRECT_7(function, _arg1, _arg2, _arg3, _arg4, _arg5, _arg6, _arg7) long __qemu_##function(void *cpu_env) { int i = 0; typeof(_arg1) arg1 = _arg1; typeof(_arg2) arg2 = _arg2; typeof(_arg3) arg3 = _arg3; typeof(_arg4) arg4 = _arg4; typeof(_arg5) arg5 = _arg5; typeof(_arg6) arg6 = _arg6; typeof(_arg7) arg7 = _arg7; return (long)function(arg1, arg2, arg3, arg4, arg5, arg6, arg7); } +#define WRAPPER_CALL_DIRECT_8(function, _arg1, _arg2, _arg3, _arg4, _arg5, _arg6, _arg7, _arg8) long __qemu_##function(void *cpu_env) { int i = 0; typeof(_arg1) arg1 = _arg1; typeof(_arg2) arg2 = _arg2; typeof(_arg3) arg3 = _arg3; typeof(_arg4) arg4 = _arg4; typeof(_arg5) arg5 = _arg5; typeof(_arg6) arg6 = _arg6; typeof(_arg7) arg7 = _arg7; typeof(_arg8) arg8 = _arg8; return (long)function(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); } +#define WRAPPER_CALL_DIRECT(function, nargs, ...) WRAPPER_CALL_DIRECT_##nargs(function, __VA_ARGS__) +#define WRAPPER_CALL_NOERRNO(function, nargs, ...) WRAPPER_CALL_DIRECT(function, nargs, __VA_ARGS__) +#define WRAPPER_CALL_INDIRECT(function, nargs, ...) +#define ENTRY(name, number, function, nargs, call_type, ...) WRAPPER_##call_type(function, nargs, __VA_ARGS__) + +#include "syscalls.h" + +#undef ENTRY +#undef WRAPPER_CALL_DIRECT +#undef WRAPPER_CALL_NOERRNO +#undef WRAPPER_CALL_INDIRECT +#undef OFFSET +#undef SIZE +#undef INT +#undef PTR +#undef INT64 + +#define _ENTRY(name, number, function, nargs, call_type) [number] = {\ + name, \ + number, \ + (syscall_function_t)function, \ + nargs, \ + call_type \ + }, + +#define ENTRY_CALL_DIRECT(name, number, function, nargs, call_type) _ENTRY(name, number, __qemu_##function, nargs, call_type) +#define ENTRY_CALL_NOERRNO(name, number, function, nargs, call_type) ENTRY_CALL_DIRECT(name, number, function, nargs, call_type) +#define ENTRY_CALL_INDIRECT(name, number, function, nargs, call_type) _ENTRY(name, number, function, nargs, call_type) +#define ENTRY(name, number, function, nargs, call_type, ...) ENTRY_##call_type(name, number, function, nargs, call_type) + +#define CALL_DIRECT 1 +#define CALL_INDIRECT 2 +#define CALL_NOERRNO (CALL_DIRECT | 4 /* = 5 */) + +struct unix_syscall { + char * name; + int number; + syscall_function_t function; + int nargs; + int call_type; +} unix_syscall_table[SYS_MAXSYSCALL] = { +#include "syscalls.h" +}; + +#undef ENTRY +#undef _ENTRY +#undef ENTRY_CALL_DIRECT +#undef ENTRY_CALL_INDIRECT +#undef ENTRY_CALL_NOERRNO + +/* Actual syscalls implementation */ + +long do_unix_syscall_indirect(void *cpu_env, int num) +{ + long ret; + int new_num; + int i = 0; + + new_num = get_int_arg(&i, cpu_env); +#ifdef TARGET_I386 + ((CPUX86State*)cpu_env)->regs[R_ESP] += 4; + /* XXX: not necessary */ + ((CPUX86State*)cpu_env)->regs[R_EAX] = new_num; +#elif TARGET_PPC + { + int i; + uint32_t **regs = ((CPUPPCState*)cpu_env)->gpr; + for(i = 3; i < 11; i++) + *regs[i] = *regs[i+1]; + /* XXX: not necessary */ + *regs[0] = new_num; + } +#endif + ret = do_unix_syscall(cpu_env, new_num); +#ifdef TARGET_I386 + ((CPUX86State*)cpu_env)->regs[R_ESP] -= 4; + /* XXX: not necessary */ + ((CPUX86State*)cpu_env)->regs[R_EAX] = num; +#elif TARGET_PPC + { + int i; + /* XXX: not really needed those regs are volatile accross calls */ + uint32_t **regs = ((CPUPPCState*)cpu_env)->gpr; + for(i = 11; i > 3; i--) + *regs[i] = *regs[i-1]; + regs[3] = new_num; + *regs[0] = num; + } +#endif + return ret; +} + +long do_exit(uint32_t arg1) +{ + exit(arg1); + /* not reached */ + return -1; +} + +long do_sync(void) +{ + sync(); + return 0; +} + +long do_getlogin(char *out, uint32_t size) +{ + char *login = getlogin(); + if(!login) + return -1; + memcpy(out, login, size); + return 0; +} +long do_open(char * arg1, uint32_t arg2, uint32_t arg3) +{ + /* XXX: don't let the %s stay in there */ + DPRINTF("open(%s, 0x%x, 0x%x)\n", arg1, arg2, arg3); + return get_errno(open(arg1, arg2, arg3)); +} + +long do_getfsstat(struct statfs * arg1, uint32_t arg2, uint32_t arg3) +{ + long ret; + DPRINTF("getfsstat(%p, 0x%x, 0x%x)\n", arg1, arg2, arg3); + ret = get_errno(getfsstat(arg1, arg2, arg3)); + if((!is_error(ret)) && arg1) + byteswap_statfs(arg1); + return ret; +} + +long do_sigprocmask(uint32_t arg1, uint32_t * arg2, uint32_t * arg3) +{ + long ret; + DPRINTF("sigprocmask(%d, %p, %p)\n", arg1, arg2, arg3); + gemu_log("XXX: sigprocmask not tested (%d, %p, %p)\n", arg1, arg2, arg3); + if(arg2) + tswap32s(arg2); + ret = get_errno(sigprocmask(arg1, (void *)arg2, (void *)arg3)); + if((!is_error(ret)) && arg3) + tswap32s(arg3); + if(arg2) + tswap32s(arg2); + return ret; +} + +long do_execve(char* arg1, char ** arg2, char ** arg3) +{ + long ret; + char **argv = arg2; + char **envp = arg3; + int argc; + int envc; + + /* XXX: don't let the %s stay in here */ + DPRINTF("execve(%s, %p, %p)\n", arg1, arg2, arg3); + + for(argc = 0; argv[argc]; argc++); + for(envc = 0; envp[envc]; envc++); + + argv = (char**)malloc(sizeof(char*)*argc); + envp = (char**)malloc(sizeof(char*)*envc); + + for(; argc >= 0; argc--) + argv[argc] = (char*)tswap32((uint32_t)(arg2)[argc]); + + for(; envc >= 0; envc--) + envp[envc] = (char*)tswap32((uint32_t)(arg3)[envc]); + + ret = get_errno(execve(arg1, argv, envp)); + free(argv); + free(envp); + return ret; +} + +long do_getgroups(uint32_t arg1, gid_t * arg2) +{ + long ret; + int i; + DPRINTF("getgroups(0x%x, %p)\n", arg1, arg2); + ret = get_errno(getgroups(arg1, arg2)); + if(ret > 0) + for(i = 0; i < arg1; i++) + tswap32s(&arg2[i]); + return ret; +} + +long do_gettimeofday(struct timeval * arg1, void * arg2) +{ + long ret; + DPRINTF("gettimeofday(%p, %p)\n", + arg1, arg2); + ret = get_errno(gettimeofday(arg1, arg2)); + if(!is_error(ret)) + { + /* timezone no longer used according to the manpage, so don't bother with it */ + byteswap_timeval(arg1); + } + return ret; +} + +long do_readv(uint32_t arg1, struct iovec * arg2, uint32_t arg3) +{ + long ret; + DPRINTF("readv(0x%x, %p, 0x%x)\n", arg1, arg2, arg3); + if(arg2) + byteswap_iovec(arg2, arg3); + ret = get_errno(readv(arg1, arg2, arg3)); + if((!is_error(ret)) && arg2) + byteswap_iovec(arg2, arg3); + return ret; +} + +long do_writev(uint32_t arg1, struct iovec * arg2, uint32_t arg3) +{ + long ret; + DPRINTF("writev(0x%x, %p, 0x%x)\n", arg1, arg2, arg3); + if(arg2) + byteswap_iovec(arg2, arg3); + ret = get_errno(writev(arg1, arg2, arg3)); + if((!is_error(ret)) && arg2) + byteswap_iovec(arg2, arg3); + return ret; +} + +long do_utimes(char * arg1, struct timeval * arg2) +{ + DPRINTF("utimes(%p, %p)\n", arg1, arg2); + if(arg2) + { + byteswap_timeval(arg2); + byteswap_timeval(arg2+1); + } + return get_errno(utimes(arg1, arg2)); +} + +long do_futimes(uint32_t arg1, struct timeval * arg2) +{ + DPRINTF("futimes(0x%x, %p)\n", arg1, arg2); + if(arg2) + { + byteswap_timeval(arg2); + byteswap_timeval(arg2+1); + } + return get_errno(futimes(arg1, arg2)); +} + +long do_statfs(char * arg1, struct statfs * arg2) +{ + long ret; + DPRINTF("statfs(%p, %p)\n", arg1, arg2); + ret = get_errno(statfs(arg1, arg2)); + if(!is_error(ret)) + byteswap_statfs(arg2); + return ret; +} + +long do_fstatfs(uint32_t arg1, struct statfs* arg2) +{ + long ret; + DPRINTF("fstatfs(0x%x, %p)\n", + arg1, arg2); + ret = get_errno(fstatfs(arg1, arg2)); + if(!is_error(ret)) + byteswap_statfs(arg2); + + return ret; +} + +long do_stat(char * arg1, struct stat * arg2) +{ + long ret; + /* XXX: don't let the %s stay in there */ + DPRINTF("stat(%s, %p)\n", arg1, arg2); + ret = get_errno(stat(arg1, arg2)); + if(!is_error(ret)) + byteswap_stat(arg2); + return ret; +} + +long do_fstat(uint32_t arg1, struct stat * arg2) +{ + long ret; + DPRINTF("fstat(0x%x, %p)\n", arg1, arg2); + ret = get_errno(fstat(arg1, arg2)); + if(!is_error(ret)) + byteswap_stat(arg2); + return ret; +} + +long do_lstat(char * arg1, struct stat * arg2) +{ + long ret; + /* XXX: don't let the %s stay in there */ + DPRINTF("lstat(%s, %p)\n", (const char *)arg1, arg2); + ret = get_errno(lstat(arg1, arg2)); + if(!is_error(ret)) + byteswap_stat(arg2); + return ret; +} + +long do_getdirentries(uint32_t arg1, void* arg2, uint32_t arg3, void* arg4) +{ + long ret; + DPRINTF("getdirentries(0x%x, %p, 0x%x, %p)\n", arg1, arg2, arg3, arg4); + if(arg4) + tswap32s((uint32_t *)arg4); + ret = get_errno(getdirentries(arg1, arg2, arg3, arg4)); + if(arg4) + tswap32s((uint32_t *)arg4); + if(!is_error(ret)) + byteswap_dirents(arg2, ret); + return ret; +} + +long do_lseek(void *cpu_env, int num) +{ + long ret; + int i = 0; + uint32_t arg1 = get_int_arg(&i, cpu_env); + uint64_t offset = get_int64_arg(&i, cpu_env); + uint32_t arg3 = get_int_arg(&i, cpu_env); + uint64_t r = lseek(arg1, offset, arg3); +#ifdef TARGET_I386 + /* lowest word in eax, highest in edx */ + ret = r & 0xffffffff; /* will be set to eax after do_unix_syscall exit */ + ((CPUX86State *)cpu_env)->regs[R_EDX] = (uint32_t)((r >> 32) & 0xffffffff) ; +#elif defined TARGET_PPC + ret = r & 0xffffffff; /* will be set to r3 after do_unix_syscall exit */ + ((CPUPPCState *)cpu_env)->gpr[4] = (uint32_t)((r >> 32) & 0xffffffff) ; +#else + qerror("64 bit ret value on your arch?"); +#endif + return get_errno(ret); +} + +void no_swap(void * oldp, int size) +{ +} + +void sysctl_tswap32s(void * oldp, int size) +{ + tswap32s(oldp); +} + +void bswap_oid(uint32_t * oldp, int size) +{ + int count = size / sizeof(int); + int i = 0; + do { tswap32s(oldp + i); } while (++i < count); +} + +void sysctl_usrstack(uint32_t * oldp, int size) +{ + DPRINTF("sysctl_usrstack: 0x%x\n", *oldp); + tswap32s(oldp); +} + +void sysctl_ncpu(uint32_t * ncpu, int size) +{ + *ncpu = 0x1; + DPRINTF("sysctl_ncpu: 0x%x\n", *ncpu); + tswap32s(ncpu); +} + +void sysctl_exec(char * exec, int size) +{ + DPRINTF("sysctl_exec: %s\n", exec); +} + +void sysctl_translate(char * exec, int size) +{ + DPRINTF("sysctl_translate: %s\n", exec); +} + +struct sysctl_dir { + int num; + const char * name; + void (*swap_func)(void *, int); + struct sysctl_dir *childs; +}; + +#define ENTRYD(num, name, childs) { num, name, NULL, childs } +#define ENTRYE(num, name, func) { num, name, (void (*)(void *, int))func, NULL } +struct sysctl_dir sysctls_unspec[] = { + ENTRYE(3, "oip", bswap_oid), + { 0, NULL, NULL, NULL } +}; + +struct sysctl_dir sysctls_kern[] = { + ENTRYE(KERN_TRANSLATE, "translate", sysctl_translate), /* 44 */ + ENTRYE(KERN_EXEC, "exec", sysctl_exec), /* 45 */ + ENTRYE(KERN_USRSTACK32, "KERN_USRSTACK32", sysctl_usrstack), /* 35 */ + ENTRYE(KERN_SHREG_PRIVATIZABLE, "KERN_SHREG_PRIVATIZABLE", sysctl_tswap32s), /* 54 */ + { 0, NULL, NULL, NULL } +}; + +struct sysctl_dir sysctls_hw[] = { + ENTRYE(HW_NCPU, "ncpud", sysctl_tswap32s), + ENTRYE(104, "104", no_swap), + ENTRYE(105, "105", no_swap), + { 0, NULL, NULL, NULL } +}; + +struct sysctl_dir sysctls[] = { + ENTRYD(CTL_UNSPEC, "unspec", sysctls_unspec), + ENTRYD(CTL_KERN, "kern", sysctls_kern), + ENTRYD(CTL_HW, "hw", sysctls_hw ), + { 0, NULL, NULL, NULL } +}; + +#undef ENTRYE +#undef ENTRYD + +static inline struct sysctl_dir * get_sysctl_entry_for_mib(int mib, struct sysctl_dir * sysctl_elmt) +{ + if(!sysctl_elmt) + return NULL; + for(; sysctl_elmt->name != NULL ; sysctl_elmt++) { + if(sysctl_elmt->num == mib) + return sysctl_elmt; + } + return NULL; +} + +static inline long bswap_syctl(int * mib, int count, void *buf, int size) +{ + int i; + struct sysctl_dir * sysctl = sysctls; + struct sysctl_dir * ret = NULL; + + for(i = 0; i < count; i++) { + + if(!(ret = sysctl = get_sysctl_entry_for_mib(mib[i], sysctl))) { + gemu_log("bswap_syctl: can't find mib %d\n", mib[i]); + return -ENOTDIR; + } + if(!(sysctl = sysctl->childs)) + break; + } + + if(ret->childs) + qerror("we shouldn't have a directory element\n"); + + ret->swap_func(buf, size); + return 0; +} + +static inline void print_syctl(int * mib, int count) +{ + int i; + struct sysctl_dir * sysctl = sysctls; + struct sysctl_dir * ret = NULL; + + for(i = 0; i < count; i++) { + if(!(ret = sysctl = get_sysctl_entry_for_mib(mib[i], sysctl))){ + gemu_log("print_syctl: can't find mib %d\n", mib[i]); + return; + } + DPRINTF("%s.", sysctl->name); + if(!(sysctl = sysctl->childs)) + break; + } + DPRINTF("\n"); +} + +long do___sysctl(int * name, uint32_t namelen, void * oldp, size_t * oldlenp, void * newp, size_t newlen /* ignored */) +{ + long ret = 0; + int i; + DPRINTF("sysctl(%p, 0x%x, %p, %p, %p, 0x%lx)\n", + name, namelen, oldp, oldlenp, newp, newlen); + if(name) { + i = 0; + do { tswap32s( name + i); } while (++i < namelen); + print_syctl(name, namelen); + //bswap_syctl(name, namelen, newp, newlen); + tswap32s((uint32_t*)oldlenp); + } + + if(name) /* Sometimes sysctl is called with no arg1, ignore */ + ret = get_errno(sysctl(name, namelen, oldp, oldlenp, newp, newlen)); + +#if defined(TARGET_I386) ^ defined(__i386__) || defined(TARGET_PPC) ^ defined(__ppc__) + if (!is_error(ret) && bswap_syctl(name, namelen, oldp, *oldlenp) != 0) { + return -ENOTDIR; + } +#endif + + if(name) { + //bswap_syctl(name, namelen, newp, newlen); + tswap32s((uint32_t*)oldlenp); + + i = 0; + do { tswap32s( name + i); } while (++i < namelen); + } + return ret; +} + +long do_getattrlist(void * arg1, void * arg2, void * arg3, uint32_t arg4, uint32_t arg5) +{ + struct attrlist * attrlist = (void *)arg2; + long ret; + +#if defined(TARGET_I386) ^ defined(__i386__) || defined(TARGET_PPC) ^ defined(__ppc__) + gemu_log("SYS_getdirentriesattr unimplemented\n"); + return -ENOTSUP; +#endif + /* XXX: don't let the %s stay in there */ + DPRINTF("getattrlist(%s, %p, %p, 0x%x, 0x%x)\n", + (char *)arg1, arg2, arg3, arg4, arg5); + + if(arg2) /* XXX: We should handle that in a copy especially + if the structure is not writable */ + byteswap_attrlist(attrlist); + + ret = get_errno(getattrlist((const char* )arg1, attrlist, (void *)arg3, arg4, arg5)); + + if(!is_error(ret)) + { + byteswap_attrbuf((void *)arg3, attrlist); + byteswap_attrlist(attrlist); + } + return ret; +} + +long do_getdirentriesattr(uint32_t arg1, void * arg2, void * arg3, size_t arg4, void * arg5, void * arg6, void* arg7, uint32_t arg8) +{ + DPRINTF("getdirentriesattr(0x%x, %p, %p, 0x%lx, %p, %p, %p, 0x%x)\n", + arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); +#if defined(TARGET_I386) ^ defined(__i386__) || defined(TARGET_PPC) ^ defined(__ppc__) + qerror("SYS_getdirentriesattr unimplemented\n"); +#endif + + return get_errno(getdirentriesattr( arg1, (struct attrlist * )arg2, (void *)arg3, arg4, + (unsigned long *)arg5, (unsigned long *)arg6, + (unsigned long *)arg7, arg8)); +} + +static inline void bswap_flock(struct flock *f) +{ + tswap64s(&f->l_start); + tswap64s(&f->l_len); + tswap32s(&f->l_pid); + tswap16s(&f->l_type); + tswap16s(&f->l_whence); +} + +static inline void bswap_fstore(struct fstore *f) +{ + tswap32s(&f->fst_flags); + tswap32s(&f->fst_posmode); + tswap64s(&f->fst_offset); + tswap64s(&f->fst_length); + tswap64s(&f->fst_bytesalloc); +} + +static inline void bswap_radvisory(struct radvisory *f) +{ + tswap64s(&f->ra_offset); + tswap32s(&f->ra_count); +} + +static inline void bswap_fbootstraptransfer(struct fbootstraptransfer *f) +{ + tswap64s(&f->fbt_offset); + tswap32s((uint32_t*)&f->fbt_length); + tswap32s((uint32_t*)&f->fbt_buffer); /* XXX: this is a ptr */ +} + +static inline void bswap_log2phys(struct log2phys *f) +{ + tswap32s(&f->l2p_flags); + tswap64s(&f->l2p_contigbytes); + tswap64s(&f->l2p_devoffset); +} + +static inline void bswap_fcntl_arg(int cmd, void * arg) +{ + switch(cmd) + { + case F_DUPFD: + case F_GETFD: + case F_SETFD: + case F_GETFL: + case F_SETFL: + case F_GETOWN: + case F_SETOWN: + case F_SETSIZE: + case F_RDAHEAD: + case F_FULLFSYNC: + break; + case F_GETLK: + case F_SETLK: + case F_SETLKW: + bswap_flock(arg); + break; + case F_PREALLOCATE: + bswap_fstore(arg); + break; + case F_RDADVISE: + bswap_radvisory(arg); + break; + case F_READBOOTSTRAP: + case F_WRITEBOOTSTRAP: + bswap_fbootstraptransfer(arg); + break; + case F_LOG2PHYS: + bswap_log2phys(arg); + break; + default: + gemu_log("unknow cmd in fcntl\n"); + } +} + +long do_fcntl(int fd, int cmd, int arg) +{ + long ret; + bswap_fcntl_arg(cmd, (void *)arg); + ret = get_errno(fcntl(fd, cmd, arg)); + if(!is_error(ret)) + bswap_fcntl_arg(cmd, (void *)arg); + return ret; +} + +long no_syscall(void *cpu_env, int num) +{ + /* XXX: We should probably fordward it to the host kernel */ + qerror("no unix syscall %d\n", num); + /* not reached */ + return -1; +} + +long unimpl_unix_syscall(void *cpu_env, int num) +{ + if( (num < 0) || (num > SYS_MAXSYSCALL-1) ) + qerror("unix syscall %d is out of unix syscall bounds (0-%d) " , num, SYS_MAXSYSCALL-1); + + gemu_log("qemu: Unsupported unix syscall %s %d\n", unix_syscall_table[num].name , num); + gdb_handlesig (cpu_env, SIGTRAP); + exit(-1); +} + +long do_unix_syscall(void *cpu_env, int num) +{ + long ret = 0; + + DPRINTF("unix syscall %d: " , num); + + if( (num < 0) || (num > SYS_MAXSYSCALL-1) ) + qerror("unix syscall %d is out of unix syscall bounds (0-%d) " , num, SYS_MAXSYSCALL-1); + + DPRINTF("%s [%s]", unix_syscall_table[num].name, unix_syscall_table[num].call_type & CALL_DIRECT ? "direct" : "indirect" ); + ret = unix_syscall_table[num].function(cpu_env, num); + + if(!(unix_syscall_table[num].call_type & CALL_NOERRNO)) + ret = get_errno(ret); + + DPRINTF("[returned 0x%x(%d)]\n", (int)ret, (int)ret); + return ret; +} + +/* ------------------------------------------------------------ + syscall_init +*/ +void syscall_init(void) +{ + /* Nothing yet */ +} diff --git a/darwin-user/syscalls.h b/darwin-user/syscalls.h new file mode 100644 index 0000000..34d95da --- /dev/null +++ b/darwin-user/syscalls.h @@ -0,0 +1,384 @@ +/* generated from xnu/bsd/kern/syscalls.master */ + + ENTRY("syscall", SYS_syscall, do_unix_syscall_indirect, 0, CALL_INDIRECT, VOID) /* 0 indirect syscall */ + ENTRY("exit", SYS_exit, do_exit, 1, CALL_DIRECT, INT) /* 1 */ + ENTRY("fork", SYS_fork, fork, 0, CALL_NOERRNO, VOID) /* 2 */ + ENTRY("read", SYS_read, do_read, 3, CALL_DIRECT, INT, PTR, SIZE) /* 3 */ + ENTRY("write", SYS_write, write, 3, CALL_DIRECT, INT, PTR, SIZE) /* 4 */ + ENTRY("open", SYS_open, do_open, 3, CALL_DIRECT, PTR, INT, INT) /* 5 */ + ENTRY("close", SYS_close, close, 1, CALL_DIRECT, INT) /* 6 */ + ENTRY("wait4", SYS_wait4, wait4, 4, CALL_DIRECT, INT, PTR, INT, PTR) /* 7 */ + ENTRY("", 8, no_syscall, 0, CALL_INDIRECT, VOID) /* 8 old creat */ + ENTRY("link", SYS_link, link, 2, CALL_DIRECT, PTR, PTR) /* 9 */ + ENTRY("unlink", SYS_unlink, unlink, 1, CALL_DIRECT, PTR) /* 10 */ + ENTRY("", 11, no_syscall, 0, CALL_INDIRECT, VOID) /* 11 old execv */ + ENTRY("chdir", SYS_chdir, chdir, 1, CALL_DIRECT, PTR) /* 12 */ + ENTRY("fchdir", SYS_fchdir, fchdir, 1, CALL_DIRECT, INT) /* 13 */ + ENTRY("mknod", SYS_mknod, mknod, 3, CALL_DIRECT, PTR, INT, INT) /* 14 */ + ENTRY("chmod", SYS_chmod, chmod, 2, CALL_DIRECT, PTR, INT) /* 15 */ + ENTRY("chown", SYS_chown, chown, 3, CALL_DIRECT, PTR, INT, INT) /* 16 */ + ENTRY("obreak", SYS_obreak, no_syscall, 1, CALL_INDIRECT, VOID) /* 17 old break */ + ENTRY("ogetfsstat", 18, unimpl_unix_syscall, 3, CALL_INDIRECT, PTR, INT, INT) /* 18 */ + ENTRY("", 19, no_syscall, 0, CALL_INDIRECT, VOID) /* 19 old lseek */ + ENTRY("getpid", SYS_getpid, getpid, 0, CALL_NOERRNO, VOID) /* 20 */ + ENTRY("", 21, no_syscall, 0, CALL_INDIRECT, VOID) /* 21 old mount */ + ENTRY("", 22, no_syscall, 0, CALL_INDIRECT, VOID) /* 22 old umount */ + ENTRY("setuid", SYS_setuid, setuid, 1, CALL_DIRECT, INT) /* 23 */ + ENTRY("getuid", SYS_getuid, getuid, 0, CALL_NOERRNO, VOID) /* 24 */ + ENTRY("geteuid", SYS_geteuid, geteuid, 0, CALL_NOERRNO, VOID) /* 25 */ + ENTRY("ptrace", SYS_ptrace, ptrace, 4, CALL_DIRECT, INT, INT, PTR, INT) /* 26 */ + ENTRY("recvmsg", SYS_recvmsg, recvmsg, 3, CALL_DIRECT, INT, PTR, INT) /* 27 */ + ENTRY("sendmsg", SYS_sendmsg, sendmsg, 3, CALL_DIRECT, INT, PTR, INT) /* 28 */ + ENTRY("recvfrom", SYS_recvfrom, recvfrom, 6, CALL_DIRECT, INT, PTR, INT, INT, PTR, PTR) /* 29 */ + ENTRY("accept", SYS_accept, accept, 3, CALL_DIRECT, INT, PTR, PTR) /* 30 */ + ENTRY("getpeername", SYS_getpeername, getpeername, 3, CALL_DIRECT, INT, PTR, PTR) /* 31 */ + ENTRY("getsockname", SYS_getsockname, getsockname, 3, CALL_DIRECT, INT, PTR, PTR) /* 32 */ + ENTRY("access", SYS_access, access, 2, CALL_DIRECT, PTR, INT) /* 33 */ + ENTRY("chflags", SYS_chflags, chflags, 2, CALL_DIRECT, PTR, INT) /* 34 */ + ENTRY("fchflags", SYS_fchflags, fchflags, 2, CALL_DIRECT, INT, INT) /* 35 */ + ENTRY("sync", SYS_sync, do_sync, 0, CALL_INDIRECT, VOID) /* 36 */ + ENTRY("kill", SYS_kill, kill, 2, CALL_DIRECT, INT, INT) /* 37 */ + ENTRY("", 38, no_syscall, 0, CALL_INDIRECT, VOID) /* 38 old stat */ + ENTRY("getppid", SYS_getppid, getppid, 0, CALL_DIRECT, VOID) /* 39 */ + ENTRY("", 40, no_syscall, 0, CALL_INDIRECT, VOID) /* 40 old lstat */ + ENTRY("dup", SYS_dup, dup, 1, CALL_DIRECT, INT) /* 41 */ + ENTRY("pipe", SYS_pipe, pipe, 0, CALL_INDIRECT, PTR) /* 42 */ + ENTRY("getegid", SYS_getegid, getegid, 0, CALL_NOERRNO, VOID) /* 43 */ + ENTRY("profil", SYS_profil, profil, 4, CALL_DIRECT, PTR, SIZE, INT, INT) /* 44 */ + ENTRY("ktrace", SYS_ktrace, no_syscall, 4, CALL_INDIRECT, VOID) /* 45 */ + ENTRY("sigaction", SYS_sigaction, do_sigaction, 3, CALL_DIRECT, INT, PTR, PTR) /* 46 */ + ENTRY("getgid", SYS_getgid, getgid, 0, CALL_NOERRNO, VOID) /* 47 */ + ENTRY("sigprocmask", SYS_sigprocmask, do_sigprocmask, 3, CALL_DIRECT, INT, PTR, PTR) /* 48 */ + ENTRY("getlogin", SYS_getlogin, do_getlogin, 2, CALL_DIRECT, PTR, UINT) /* 49 XXX */ + ENTRY("setlogin", SYS_setlogin, setlogin, 1, CALL_DIRECT, PTR) /* 50 */ + ENTRY("acct", SYS_acct, acct, 1, CALL_DIRECT, PTR) /* 51 */ + ENTRY("sigpending", SYS_sigpending, sigpending, 1, CALL_DIRECT, PTR) /* 52 */ + ENTRY("sigaltstack", SYS_sigaltstack, do_sigaltstack, 2, CALL_DIRECT, PTR, PTR) /* 53 */ + ENTRY("ioctl", SYS_ioctl, do_ioctl, 3, CALL_DIRECT, INT, INT, INT) /* 54 */ + ENTRY("reboot", SYS_reboot, unimpl_unix_syscall, 2, CALL_INDIRECT, INT, PTR) /* 55 */ + ENTRY("revoke", SYS_revoke, revoke, 1, CALL_DIRECT, PTR) /* 56 */ + ENTRY("symlink", SYS_symlink, symlink, 2, CALL_DIRECT, PTR, PTR) /* 57 */ + ENTRY("readlink", SYS_readlink, readlink, 3, CALL_DIRECT, PTR, PTR, INT) /* 58 */ + ENTRY("execve", SYS_execve, do_execve, 3, CALL_DIRECT, PTR, PTR, PTR) /* 59 */ + ENTRY("umask", SYS_umask, umask, 1, CALL_DIRECT, INT) /* 60 */ + ENTRY("chroot", SYS_chroot, chroot, 1, CALL_DIRECT, PTR) /* 61 */ + ENTRY("", 62, no_syscall, 0, CALL_INDIRECT, VOID) /* 62 old fstat */ + ENTRY("", 63, no_syscall, 0, CALL_INDIRECT, VOID) /* 63 used internally , reserved */ + ENTRY("", 64, no_syscall, 0, CALL_INDIRECT, VOID) /* 64 old getpagesize */ + ENTRY("msync", SYS_msync, target_msync, 3, CALL_DIRECT, UINT /*PTR*/, SIZE, INT) /* 65 */ + ENTRY("vfork", SYS_vfork, vfork, 0, CALL_DIRECT, VOID) /* 66 */ + ENTRY("", 67, no_syscall, 0, CALL_INDIRECT, VOID) /* 67 old vread */ + ENTRY("", 68, no_syscall, 0, CALL_INDIRECT, VOID) /* 68 old vwrite */ + ENTRY("sbrk", SYS_sbrk, sbrk, 1, CALL_DIRECT, INT) /* 69 */ + ENTRY("sstk", SYS_sstk, no_syscall, 1, CALL_INDIRECT, VOID) /* 70 */ + ENTRY("", 71, no_syscall, 0, CALL_INDIRECT, VOID) /* 71 old mmap */ + ENTRY("ovadvise", SYS_ovadvise, no_syscall, 0, CALL_INDIRECT, VOID) /* 72 old vadvise */ + ENTRY("munmap", SYS_munmap, target_munmap, 2, CALL_DIRECT, UINT /* PTR */, SIZE) /* 73 */ + ENTRY("mprotect", SYS_mprotect, mprotect, 3, CALL_DIRECT, PTR, SIZE, INT) /* 74 */ + ENTRY("madvise", SYS_madvise, madvise, 3, CALL_DIRECT, PTR, SIZE, INT) /* 75 */ + ENTRY("", 76, no_syscall, 0, CALL_INDIRECT, VOID) /* 76 old vhangup */ + ENTRY("", 77, no_syscall, 0, CALL_INDIRECT, VOID) /* 77 old vlimit */ + ENTRY("mincore", SYS_mincore, mincore, 3, CALL_DIRECT, PTR, SIZE, PTR) /* 78 */ + ENTRY("getgroups", SYS_getgroups, do_getgroups, 2, CALL_DIRECT, UINT, PTR) /* 79 */ + ENTRY("setgroups", SYS_setgroups, setgroups, 2, CALL_DIRECT, UINT, PTR) /* 80 */ + ENTRY("getpgrp", SYS_getpgrp, getpgrp, 0, CALL_DIRECT, VOID) /* 81 */ + ENTRY("setpgid", SYS_setpgid, setpgid, 2, CALL_DIRECT, INT, INT) /* 82 */ + ENTRY("setitimer", SYS_setitimer, setitimer, 3, CALL_DIRECT, INT, PTR, PTR) /* 83 */ + ENTRY("", 84, no_syscall, 0, CALL_INDIRECT, VOID) /* 84 old wait */ + ENTRY("swapon", SYS_swapon, unimpl_unix_syscall, 0, CALL_INDIRECT, VOID) /* 85 */ + ENTRY("getitimer", SYS_getitimer, getitimer, 2, CALL_DIRECT, INT, PTR) /* 86 */ + ENTRY("", 87, no_syscall, 0, CALL_INDIRECT, VOID) /* 87 old gethostname */ + ENTRY("", 88, no_syscall, 0, CALL_INDIRECT, VOID) /* 88 old sethostname */ + ENTRY("getdtablesize", SYS_getdtablesize, getdtablesize, 0, CALL_DIRECT, VOID) /* 89 */ + ENTRY("dup2", SYS_dup2, dup2, 2, CALL_DIRECT, INT, INT) /* 90 */ + ENTRY("", 91, no_syscall, 0, CALL_INDIRECT, VOID) /* 91 old getdopt */ + ENTRY("fcntl", SYS_fcntl, do_fcntl, 3, CALL_DIRECT, INT, INT, INT) /* 92 */ + ENTRY("select", SYS_select, select, 5, CALL_DIRECT, INT, PTR, PTR, PTR, PTR) /* 93 */ + ENTRY("", 94, no_syscall, 0, CALL_INDIRECT, VOID) /* 94 old setdopt */ + ENTRY("fsync", SYS_fsync, fsync, 1, CALL_DIRECT, INT) /* 95 */ + ENTRY("setpriority", SYS_setpriority, setpriority, 3, CALL_DIRECT, INT, INT, INT) /* 96 */ + ENTRY("socket", SYS_socket, socket, 3, CALL_DIRECT, INT, INT, INT) /* 97 */ + ENTRY("connect", SYS_connect, connect, 3, CALL_DIRECT, INT, PTR, INT) /* 98 */ + ENTRY("", 99, no_syscall, 0, CALL_INDIRECT, VOID) /* 99 old accept */ + ENTRY("getpriority", SYS_getpriority, getpriority, 2, CALL_DIRECT, INT, INT) /* 100 */ + ENTRY("", 101, no_syscall, 0, CALL_INDIRECT, VOID) /* 101 old send */ + ENTRY("", 102, no_syscall, 0, CALL_INDIRECT, VOID) /* 102 old recv */ + ENTRY("", 103, no_syscall, 0, CALL_INDIRECT, VOID) /* 103 old sigreturn */ + ENTRY("bind", SYS_bind, bind, 3, CALL_DIRECT, INT, PTR, INT) /* 104 */ + ENTRY("setsockopt", SYS_setsockopt, setsockopt, 5, CALL_DIRECT, INT, INT, INT, PTR, INT) /* 105 */ + ENTRY("listen", SYS_listen, listen, 2, CALL_DIRECT, INT, INT) /* 106 */ + ENTRY("", 107, no_syscall, 0, CALL_INDIRECT, VOID) /* 107 old vtimes */ + ENTRY("", 108, no_syscall, 0, CALL_INDIRECT, VOID) /* 108 old sigvec */ + ENTRY("", 109, no_syscall, 0, CALL_INDIRECT, VOID) /* 109 old sigblock */ + ENTRY("", 110, no_syscall, 0, CALL_INDIRECT, VOID) /* 110 old sigsetmask */ + ENTRY("sigsuspend", SYS_sigsuspend, unimpl_unix_syscall, 1, CALL_INDIRECT, INT) /* 111 */ + ENTRY("", 112, no_syscall, 0, CALL_INDIRECT, VOID) /* 112 old sigstack */ + ENTRY("", 113, no_syscall, 0, CALL_INDIRECT, VOID) /* 113 old recvmsg */ + ENTRY("", 114, no_syscall, 0, CALL_INDIRECT, VOID) /* 114 old sendmsg */ + ENTRY("", 115, no_syscall, 0, CALL_INDIRECT, VOID) /* 115 old vtrace */ + ENTRY("gettimeofday", SYS_gettimeofday, do_gettimeofday, 2, CALL_DIRECT, PTR, PTR) /* 116 */ + ENTRY("getrusage", SYS_getrusage, getrusage, 2, CALL_DIRECT, INT, PTR) /* 117 */ + ENTRY("getsockopt", SYS_getsockopt, getsockopt, 5, CALL_DIRECT, INT, INT, INT, PTR, PTR) /* 118 */ + ENTRY("", 119, no_syscall, 0, CALL_INDIRECT, VOID) /* 119 old resuba */ + ENTRY("readv", SYS_readv, do_readv, 3, CALL_DIRECT, INT, PTR, UINT) /* 120 */ + ENTRY("writev", SYS_writev, do_writev, 3, CALL_DIRECT, INT, PTR, UINT) /* 121 */ + ENTRY("settimeofday", SYS_settimeofday, settimeofday, 2, CALL_DIRECT, PTR, PTR) /* 122 */ + ENTRY("fchown", SYS_fchown, fchown, 3, CALL_DIRECT, INT, INT, INT) /* 123 */ + ENTRY("fchmod", SYS_fchmod, fchmod, 2, CALL_DIRECT, INT, INT) /* 124 */ + ENTRY("", 125, no_syscall, 0, CALL_INDIRECT, VOID) /* 125 old recvfrom */ + ENTRY("", 126, no_syscall, 0, CALL_INDIRECT, VOID) /* 126 old setreuid */ + ENTRY("", 127, no_syscall, 0, CALL_INDIRECT, VOID) /* 127 old setregid */ + ENTRY("rename", SYS_rename, rename, 2, CALL_DIRECT, PTR, PTR) /* 128 */ + ENTRY("", 129, no_syscall, 0, CALL_INDIRECT, VOID) /* 129 old truncate */ + ENTRY("", 130, no_syscall, 0, CALL_INDIRECT, VOID) /* 130 old ftruncate */ + ENTRY("flock", SYS_flock, flock, 2, CALL_DIRECT, INT, INT) /* 131 */ + ENTRY("mkfifo", SYS_mkfifo, mkfifo, 2, CALL_DIRECT, PTR, INT) /* 132 */ + ENTRY("sendto", SYS_sendto, sendto, 6, CALL_DIRECT, INT, PTR, SIZE, INT, PTR, INT) /* 133 */ + ENTRY("shutdown", SYS_shutdown, shutdown, 2, CALL_DIRECT, INT, INT) /* 134 */ + ENTRY("socketpair", SYS_socketpair, socketpair, 4, CALL_DIRECT, INT, INT, INT, PTR) /* 135 */ + ENTRY("mkdir", SYS_mkdir, mkdir, 2, CALL_DIRECT, PTR, INT) /* 136 */ + ENTRY("rmdir", SYS_rmdir, rmdir, 1, CALL_DIRECT, PTR) /* 137 */ + ENTRY("utimes", SYS_utimes, do_utimes, 2, CALL_DIRECT, PTR, PTR) /* 138 */ + ENTRY("futimes", SYS_futimes, do_futimes, 2, CALL_DIRECT, INT, PTR) /* 139 */ + ENTRY("adjtime", SYS_adjtime, adjtime, 2, CALL_DIRECT, PTR, PTR) /* 140 */ + ENTRY("", 141, no_syscall, 0, CALL_INDIRECT, VOID) /* 141 old getpeername */ + ENTRY("", 142, no_syscall, 0, CALL_INDIRECT, VOID) /* 142 old gethostid */ + ENTRY("", 143, no_syscall, 0, CALL_INDIRECT, VOID) /* 143 old sethostid */ + ENTRY("", 144, no_syscall, 0, CALL_INDIRECT, VOID) /* 144 old getrlimit */ + ENTRY("", 145, no_syscall, 0, CALL_INDIRECT, VOID) /* 145 old setrlimit */ + ENTRY("", 146, no_syscall, 0, CALL_INDIRECT, VOID) /* 146 old killpg */ + ENTRY("setsid", SYS_setsid, setsid, 0, CALL_DIRECT, VOID) /* 147 */ + ENTRY("", 148, no_syscall, 0, CALL_INDIRECT, VOID) /* 148 old setquota */ + ENTRY("", 149, no_syscall, 0, CALL_INDIRECT, VOID) /* 149 old qquota */ + ENTRY("", 150, no_syscall, 0, CALL_INDIRECT, VOID) /* 150 old getsockname */ + ENTRY("getpgid", SYS_getpgid, getpgid, 1, CALL_DIRECT, INT) /* 151 */ + ENTRY("setprivexec", SYS_setprivexec, no_syscall, 1, CALL_INDIRECT, VOID) /* 152 */ + ENTRY("pread", SYS_pread, do_pread, 4, CALL_DIRECT, INT, PTR, SIZE, OFFSET) /* 153 */ + ENTRY("pwrite", SYS_pwrite, pwrite, 4, CALL_DIRECT, INT, PTR, SIZE, OFFSET) /* 154 */ +#ifdef SYS_nfssvc + ENTRY("nfssvc", SYS_nfssvc, nfssvc, 2, CALL_DIRECT, INT, PTR) /* 155 */ +#else + ENTRY("nfssvc", 155, no_syscall, 2, CALL_INDIRECT, VOID) /* 155 */ +#endif + ENTRY("", 155, no_syscall, 0, CALL_INDIRECT, VOID) /* 155 */ + ENTRY("", 156, no_syscall, 0, CALL_INDIRECT, VOID) /* 156 old getdirentries */ + ENTRY("statfs", SYS_statfs, do_statfs, 2, CALL_DIRECT, PTR, PTR) /* 157 */ + ENTRY("fstatfs", SYS_fstatfs, do_fstatfs, 2, CALL_DIRECT, INT, PTR) /* 158 */ + ENTRY("unmount", SYS_unmount, unmount, 2, CALL_DIRECT, PTR, INT) /* 159 */ + ENTRY("", 160, no_syscall, 0, CALL_INDIRECT, VOID) /* 160 old async_daemon */ + ENTRY("", 161, no_syscall, 0, CALL_INDIRECT, VOID) /* 161 */ + ENTRY("", 162, no_syscall, 0, CALL_INDIRECT, VOID) /* 162 old getdomainname */ + ENTRY("", 163, no_syscall, 0, CALL_INDIRECT, VOID) /* 163 old setdomainname */ + ENTRY("", 164, no_syscall, 0, CALL_INDIRECT, VOID) /* 164 */ + ENTRY("quotactl", SYS_quotactl, no_syscall, 4, CALL_INDIRECT, VOID) /* 165 */ + ENTRY("", 166, no_syscall, 0, CALL_INDIRECT, VOID) /* 166 old exportfs */ + ENTRY("mount", SYS_mount, mount, 4, CALL_DIRECT, PTR, PTR, INT, PTR) /* 167 */ + ENTRY("", 168, no_syscall, 0, CALL_INDIRECT, VOID) /* 168 old ustat */ + ENTRY("", 169, no_syscall, 0, CALL_INDIRECT, VOID) /* 169 */ + ENTRY("table", SYS_table, no_syscall, 0, CALL_INDIRECT, VOID) /* 170 old table */ + ENTRY("", 171, no_syscall, 0, CALL_INDIRECT, VOID) /* 171 old wait3 */ + ENTRY("", 172, no_syscall, 0, CALL_INDIRECT, VOID) /* 172 old rpause */ + ENTRY("waitid", SYS_waitid, unimpl_unix_syscall, 4, CALL_INDIRECT, VOID) /* 173 */ + ENTRY("", 174, no_syscall, 0, CALL_INDIRECT, VOID) /* 174 old getdents */ + ENTRY("", 175, no_syscall, 0, CALL_INDIRECT, VOID) /* 175 old gc_control */ + ENTRY("add_profil", SYS_add_profil, add_profil, 4, CALL_DIRECT, PTR, SIZE, UINT, UINT) /* 176 */ + ENTRY("", 177, no_syscall, 0, CALL_INDIRECT, VOID) /* 177 */ + ENTRY("", 178, no_syscall, 0, CALL_INDIRECT, VOID) /* 178 */ + ENTRY("", 179, no_syscall, 0, CALL_INDIRECT, VOID) /* 179 */ + ENTRY("kdebug_trace", SYS_kdebug_trace, no_syscall, 6, CALL_INDIRECT, VOID) /* 180 */ + ENTRY("setgid", SYS_setgid, setgid, 1, CALL_DIRECT, INT) /* 181 */ + ENTRY("setegid", SYS_setegid, setegid, 1, CALL_DIRECT, INT) /* 182 */ + ENTRY("seteuid", SYS_seteuid, seteuid, 1, CALL_DIRECT, INT) /* 183 */ + ENTRY("sigreturn", SYS_sigreturn, do_sigreturn, 2, CALL_INDIRECT, PTR, INT) /* 184 */ + ENTRY("chud", SYS_chud, unimpl_unix_syscall, 6, CALL_INDIRECT, VOID) /* 185 */ + ENTRY("", 186, no_syscall, 0, CALL_INDIRECT, VOID) /* 186 */ + ENTRY("", 187, no_syscall, 0, CALL_INDIRECT, VOID) /* 187 */ + ENTRY("stat", SYS_stat, do_stat, 2, CALL_DIRECT, PTR, PTR) /* 188 */ + ENTRY("fstat", SYS_fstat, do_fstat, 2, CALL_DIRECT, INT, PTR) /* 189 */ + ENTRY("lstat", SYS_lstat, do_lstat, 2, CALL_DIRECT, PTR, PTR) /* 190 */ + ENTRY("pathconf", SYS_pathconf, pathconf, 2, CALL_DIRECT, PTR, INT) /* 191 */ + ENTRY("fpathconf", SYS_fpathconf, fpathconf, 2, CALL_DIRECT, INT, INT) /* 192 */ + ENTRY("getfsstat", SYS_getfsstat, do_getfsstat, 3, CALL_DIRECT, PTR, INT, INT) /* 193 */ + ENTRY("", 193, no_syscall, 0, CALL_INDIRECT, VOID) /* 193 */ + ENTRY("getrlimit", SYS_getrlimit, getrlimit, 2, CALL_DIRECT, UINT, PTR) /* 194 */ + ENTRY("setrlimit", SYS_setrlimit, setrlimit, 2, CALL_DIRECT, UINT, PTR) /* 195 */ + ENTRY("getdirentries", SYS_getdirentries, do_getdirentries, 4, CALL_DIRECT, INT, PTR, UINT, PTR) /* 196 */ + ENTRY("mmap", SYS_mmap, target_mmap, 6, CALL_DIRECT, UINT /*PTR*/, SIZE, INT, INT, INT, OFFSET) /* 197 */ + ENTRY("", 198, no_syscall, 0, CALL_INDIRECT, VOID) /* 198 __syscall */ + ENTRY("lseek", SYS_lseek, do_lseek, 3, CALL_INDIRECT, INT, OFFSET, INT) /* 199 */ + ENTRY("truncate", SYS_truncate, truncate, 2, CALL_DIRECT, PTR, OFFSET) /* 200 */ + ENTRY("ftruncate", SYS_ftruncate, ftruncate, 2, CALL_DIRECT, INT, OFFSET) /* 201 */ + ENTRY("__sysctl", SYS___sysctl, do___sysctl, 6, CALL_DIRECT, PTR, INT, PTR, PTR, PTR, SIZE) /* 202 */ + ENTRY("mlock", SYS_mlock, mlock, 2, CALL_DIRECT, PTR, SIZE) /* 203 */ + ENTRY("munlock", SYS_munlock, munlock, 2, CALL_DIRECT, PTR, SIZE) /* 204 */ + ENTRY("undelete", SYS_undelete, undelete, 1, CALL_DIRECT, PTR) /* 205 */ + ENTRY("ATsocket", SYS_ATsocket, no_syscall, 1, CALL_INDIRECT, VOID) /* 206 */ + ENTRY("ATgetmsg", SYS_ATgetmsg, no_syscall, 4, CALL_INDIRECT, VOID) /* 207 */ + ENTRY("ATputmsg", SYS_ATputmsg, no_syscall, 4, CALL_INDIRECT, VOID) /* 208 */ + ENTRY("ATPsndreq", SYS_ATPsndreq, no_syscall, 4, CALL_INDIRECT, VOID) /* 209 */ + ENTRY("ATPsndrsp", SYS_ATPsndrsp, no_syscall, 4, CALL_INDIRECT, VOID) /* 210 */ + ENTRY("ATPgetreq", SYS_ATPgetreq, no_syscall, 3, CALL_INDIRECT, VOID) /* 211 */ + ENTRY("ATPgetrsp", SYS_ATPgetrsp, no_syscall, 2, CALL_INDIRECT, VOID) /* 212 */ + ENTRY("", 213, no_syscall, 0, CALL_INDIRECT, VOID) /* 213 Reserved for AppleTalk */ + ENTRY("kqueue_from_portset_np", SYS_kqueue_from_portset_np, no_syscall, 1, CALL_INDIRECT, VOID) /* 214 */ + ENTRY("kqueue_portset_np", SYS_kqueue_portset_np, no_syscall, 1, CALL_INDIRECT, VOID) /* 215 */ + ENTRY("mkcomplex", SYS_mkcomplex, no_syscall, 3, CALL_INDIRECT, VOID) /* 216 soon to be obsolete */ + ENTRY("statv", SYS_statv, no_syscall, 2, CALL_INDIRECT, VOID) /* 217 soon to be obsolete */ + ENTRY("lstatv", SYS_lstatv, no_syscall, 2, CALL_INDIRECT, VOID) /* 218 soon to be obsolete */ + ENTRY("fstatv", SYS_fstatv, no_syscall, 2, CALL_INDIRECT, VOID) /* 219 soon to be obsolete */ + ENTRY("getattrlist", SYS_getattrlist, do_getattrlist, 5, CALL_DIRECT, PTR, PTR, PTR, SIZE, UINT) /* 220 */ + ENTRY("setattrlist", SYS_setattrlist, unimpl_unix_syscall, 5, CALL_INDIRECT, VOID) /* 221 */ + ENTRY("getdirentriesattr", SYS_getdirentriesattr, do_getdirentriesattr, 8, CALL_DIRECT, INT, PTR, PTR, SIZE, PTR, PTR, PTR, UINT) /* 222 */ + ENTRY("exchangedata", SYS_exchangedata, exchangedata, 3, CALL_DIRECT, PTR, PTR, UINT) /* 223 */ + ENTRY("checkuseraccess", SYS_checkuseraccess, checkuseraccess, 6, CALL_DIRECT, PTR, INT, PTR, INT, INT, UINT) /* 224 */ + ENTRY("", 224, no_syscall, 0, CALL_INDIRECT, VOID) /* 224 HFS checkuseraccess check access to a file */ + ENTRY("searchfs", SYS_searchfs, searchfs, 6, CALL_DIRECT, PTR, PTR, PTR, UINT, UINT, PTR) /* 225 */ + ENTRY("delete", SYS_delete, no_syscall, 1, CALL_INDIRECT, VOID) /* 226 private delete ( Carbon semantics ) */ + ENTRY("copyfile", SYS_copyfile, no_syscall, 4, CALL_INDIRECT, VOID) /* 227 */ + ENTRY("", 228, no_syscall, 0, CALL_INDIRECT, VOID) /* 228 */ + ENTRY("", 229, no_syscall, 0, CALL_INDIRECT, VOID) /* 229 */ + ENTRY("poll", SYS_poll, no_syscall, 3, CALL_INDIRECT, VOID) /* 230 */ + ENTRY("watchevent", SYS_watchevent, no_syscall, 2, CALL_INDIRECT, VOID) /* 231 */ + ENTRY("waitevent", SYS_waitevent, no_syscall, 2, CALL_INDIRECT, VOID) /* 232 */ + ENTRY("modwatch", SYS_modwatch, no_syscall, 2, CALL_INDIRECT, VOID) /* 233 */ + ENTRY("getxattr", SYS_getxattr, no_syscall, 6, CALL_INDIRECT, VOID) /* 234 */ + ENTRY("fgetxattr", SYS_fgetxattr, no_syscall, 6, CALL_INDIRECT, VOID) /* 235 */ + ENTRY("setxattr", SYS_setxattr, no_syscall, 6, CALL_INDIRECT, VOID) /* 236 */ + ENTRY("fsetxattr", SYS_fsetxattr, no_syscall, 6, CALL_INDIRECT, VOID) /* 237 */ + ENTRY("removexattr", SYS_removexattr, no_syscall, 3, CALL_INDIRECT, VOID) /* 238 */ + ENTRY("fremovexattr", SYS_fremovexattr, no_syscall, 3, CALL_INDIRECT, VOID) /* 239 */ + ENTRY("listxattr", SYS_listxattr, listxattr, 4, CALL_INDIRECT, VOID) /* 240 */ + ENTRY("flistxattr", SYS_flistxattr, no_syscall, 4, CALL_INDIRECT, VOID) /* 241 */ + ENTRY("fsctl", SYS_fsctl, fsctl, 4, CALL_DIRECT, PTR, UINT, PTR, UINT) /* 242 */ + ENTRY("initgroups", SYS_initgroups, unimpl_unix_syscall, 3, CALL_INDIRECT, UINT, PTR, INT) /* 243 */ + ENTRY("", 244, no_syscall, 0, CALL_INDIRECT, VOID) /* 244 */ + ENTRY("", 245, no_syscall, 0, CALL_INDIRECT, VOID) /* 245 */ + ENTRY("", 246, no_syscall, 0, CALL_INDIRECT, VOID) /* 246 */ +#ifdef SYS_nfsclnt + ENTRY("nfsclnt", SYS_nfsclnt, nfsclnt, 2, CALL_DIRECT, INT, PTR) /* 247 */ +#else + ENTRY("nfsclnt", 247, no_syscall, 2, CALL_INDIRECT, VOID) /* 247 */ +#endif + ENTRY("", 247, no_syscall, 0, CALL_INDIRECT, VOID) /* 247 */ + ENTRY("", 248, no_syscall, 0, CALL_INDIRECT, VOID) /* 248 */ + ENTRY("", 249, no_syscall, 0, CALL_INDIRECT, VOID) /* 249 */ + ENTRY("minherit", SYS_minherit, minherit, 3, CALL_DIRECT, PTR, INT, INT) /* 250 */ + ENTRY("semsys", SYS_semsys, unimpl_unix_syscall, 5, CALL_INDIRECT, VOID) /* 251 */ + ENTRY("msgsys", SYS_msgsys, unimpl_unix_syscall, 5, CALL_INDIRECT, VOID) /* 252 */ + ENTRY("shmsys", SYS_shmsys, unimpl_unix_syscall, 4, CALL_INDIRECT, VOID) /* 253 */ + ENTRY("semctl", SYS_semctl, unimpl_unix_syscall, 4, CALL_INDIRECT, VOID) /* 254 */ + ENTRY("semget", SYS_semget, unimpl_unix_syscall, 3, CALL_INDIRECT, VOID) /* 255 */ + ENTRY("semop", SYS_semop, unimpl_unix_syscall, 3, CALL_INDIRECT, VOID) /* 256 */ + ENTRY("", 257, no_syscall, 0, CALL_INDIRECT, VOID) /* 257 */ + ENTRY("msgctl", SYS_msgctl, unimpl_unix_syscall, 3, CALL_INDIRECT, VOID) /* 258 */ + ENTRY("msgget", SYS_msgget, unimpl_unix_syscall, 2, CALL_INDIRECT, VOID) /* 259 */ + ENTRY("msgsnd", SYS_msgsnd, unimpl_unix_syscall, 4, CALL_INDIRECT, VOID) /* 260 */ + ENTRY("msgrcv", SYS_msgrcv, unimpl_unix_syscall, 5, CALL_INDIRECT, VOID) /* 261 */ + ENTRY("shmat", SYS_shmat, unimpl_unix_syscall, 3, CALL_INDIRECT, VOID) /* 262 */ + ENTRY("shmctl", SYS_shmctl, unimpl_unix_syscall, 3, CALL_INDIRECT, VOID) /* 263 */ + ENTRY("shmdt", SYS_shmdt, unimpl_unix_syscall, 1, CALL_INDIRECT, VOID) /* 264 */ + ENTRY("shmget", SYS_shmget, unimpl_unix_syscall, 3, CALL_INDIRECT, VOID) /* 265 */ + ENTRY("shm_open", SYS_shm_open, shm_open, 3, CALL_DIRECT, PTR, INT, INT) /* 266 */ + ENTRY("shm_unlink", SYS_shm_unlink, shm_unlink, 1, CALL_DIRECT, PTR) /* 267 */ + ENTRY("sem_open", SYS_sem_open, unimpl_unix_syscall, 4, CALL_INDIRECT, VOID) /* 268 */ + ENTRY("sem_close", SYS_sem_close, unimpl_unix_syscall, 1, CALL_INDIRECT, VOID) /* 269 */ + ENTRY("sem_unlink", SYS_sem_unlink, unimpl_unix_syscall, 1, CALL_INDIRECT, VOID) /* 270 */ + ENTRY("sem_wait", SYS_sem_wait, unimpl_unix_syscall, 1, CALL_INDIRECT, VOID) /* 271 */ + ENTRY("sem_trywait", SYS_sem_trywait, unimpl_unix_syscall, 1, CALL_INDIRECT, VOID) /* 272 */ + ENTRY("sem_post", SYS_sem_post, unimpl_unix_syscall, 1, CALL_INDIRECT, VOID) /* 273 */ + ENTRY("sem_getvalue", SYS_sem_getvalue, unimpl_unix_syscall, 2, CALL_INDIRECT, VOID) /* 274 */ + ENTRY("sem_init", SYS_sem_init, unimpl_unix_syscall, 3, CALL_INDIRECT, VOID) /* 275 */ + ENTRY("sem_destroy", SYS_sem_destroy, unimpl_unix_syscall, 1, CALL_INDIRECT, VOID) /* 276 */ + ENTRY("open_extended", SYS_open_extended, unimpl_unix_syscall, 6, CALL_INDIRECT, VOID) /* 277 */ + ENTRY("umask_extended", SYS_umask_extended, unimpl_unix_syscall, 2, CALL_INDIRECT, VOID) /* 278 */ + ENTRY("stat_extended", SYS_stat_extended, unimpl_unix_syscall, 4, CALL_INDIRECT, VOID) /* 279 */ + ENTRY("lstat_extended", SYS_lstat_extended, unimpl_unix_syscall, 4, CALL_INDIRECT, VOID) /* 280 */ + ENTRY("fstat_extended", SYS_fstat_extended, unimpl_unix_syscall, 4, CALL_INDIRECT, VOID) /* 281 */ + ENTRY("chmod_extended", SYS_chmod_extended, unimpl_unix_syscall, 5, CALL_INDIRECT, VOID) /* 282 */ + ENTRY("fchmod_extended", SYS_fchmod_extended, unimpl_unix_syscall, 5, CALL_INDIRECT, VOID) /* 283 */ + ENTRY("access_extended", SYS_access_extended, unimpl_unix_syscall, 4, CALL_INDIRECT, VOID) /* 284 */ + ENTRY("settid", SYS_settid, unimpl_unix_syscall, 2, CALL_INDIRECT, VOID) /* 285 */ + ENTRY("gettid", SYS_gettid, unimpl_unix_syscall, 2, CALL_INDIRECT, VOID) /* 286 */ + ENTRY("setsgroups", SYS_setsgroups, unimpl_unix_syscall, 2, CALL_INDIRECT, VOID) /* 287 */ + ENTRY("getsgroups", SYS_getsgroups, unimpl_unix_syscall, 2, CALL_INDIRECT, VOID) /* 288 */ + ENTRY("setwgroups", SYS_setwgroups, unimpl_unix_syscall, 2, CALL_INDIRECT, VOID) /* 289 */ + ENTRY("getwgroups", SYS_getwgroups, unimpl_unix_syscall, 2, CALL_INDIRECT, VOID) /* 290 */ + ENTRY("mkfifo_extended", SYS_mkfifo_extended, unimpl_unix_syscall, 5, CALL_INDIRECT, VOID) /* 291 */ + ENTRY("mkdir_extended", SYS_mkdir_extended, unimpl_unix_syscall, 5, CALL_INDIRECT, VOID) /* 292 */ + ENTRY("identitysvc", SYS_identitysvc, unimpl_unix_syscall, 2, CALL_INDIRECT, VOID) /* 293 */ + ENTRY("", 294, no_syscall, 0, CALL_INDIRECT, VOID) /* 294 */ + ENTRY("", 295, no_syscall, 0, CALL_INDIRECT, VOID) /* 295 */ + ENTRY("load_shared_file", SYS_load_shared_file, unimpl_unix_syscall, 7, CALL_INDIRECT, VOID) /* 296 */ + ENTRY("reset_shared_file", SYS_reset_shared_file, unimpl_unix_syscall, 3, CALL_INDIRECT, VOID) /* 297 */ + ENTRY("new_system_shared_regions", SYS_new_system_shared_regions, unimpl_unix_syscall, 0, CALL_INDIRECT, VOID) /* 298 */ + ENTRY("shared_region_map_file_np", SYS_shared_region_map_file_np, unimpl_unix_syscall, 4, CALL_INDIRECT, VOID) /* 299 */ + ENTRY("shared_region_make_private_np", SYS_shared_region_make_private_np, unimpl_unix_syscall, 2, CALL_INDIRECT, VOID) /* 300 */ + ENTRY("", 301, no_syscall, 0, CALL_INDIRECT, VOID) /* 301 */ + ENTRY("", 302, no_syscall, 0, CALL_INDIRECT, VOID) /* 302 */ + ENTRY("", 303, no_syscall, 0, CALL_INDIRECT, VOID) /* 303 */ + ENTRY("", 304, no_syscall, 0, CALL_INDIRECT, VOID) /* 304 */ + ENTRY("", 305, no_syscall, 0, CALL_INDIRECT, VOID) /* 305 */ + ENTRY("", 306, no_syscall, 0, CALL_INDIRECT, VOID) /* 306 */ + ENTRY("", 307, no_syscall, 0, CALL_INDIRECT, VOID) /* 307 */ + ENTRY("", 308, no_syscall, 0, CALL_INDIRECT, VOID) /* 308 */ + ENTRY("", 309, no_syscall, 0, CALL_INDIRECT, VOID) /* 309 */ + ENTRY("getsid", SYS_getsid, getsid, 1, CALL_DIRECT, INT) /* 310 */ + ENTRY("settid_with_pid", SYS_settid_with_pid, unimpl_unix_syscall, 2, CALL_INDIRECT, VOID) /* 311 */ + ENTRY("", 312, no_syscall, 0, CALL_INDIRECT, VOID) /* 312 */ + ENTRY("aio_fsync", SYS_aio_fsync, unimpl_unix_syscall, 2, CALL_INDIRECT, VOID) /* 313 */ + ENTRY("aio_return", SYS_aio_return, unimpl_unix_syscall, 1, CALL_INDIRECT, VOID) /* 314 */ + ENTRY("aio_suspend", SYS_aio_suspend, unimpl_unix_syscall, 3, CALL_INDIRECT, VOID) /* 315 */ + ENTRY("aio_cancel", SYS_aio_cancel, unimpl_unix_syscall, 2, CALL_INDIRECT, VOID) /* 316 */ + ENTRY("aio_error", SYS_aio_error, unimpl_unix_syscall, 1, CALL_INDIRECT, VOID) /* 317 */ + ENTRY("aio_read", SYS_aio_read, unimpl_unix_syscall, 1, CALL_INDIRECT, VOID) /* 318 */ + ENTRY("aio_write", SYS_aio_write, unimpl_unix_syscall, 1, CALL_INDIRECT, VOID) /* 319 */ + ENTRY("lio_listio", SYS_lio_listio, unimpl_unix_syscall, 4, CALL_INDIRECT, VOID) /* 320 */ + ENTRY("", 321, no_syscall, 0, CALL_INDIRECT, VOID) /* 321 */ + ENTRY("", 322, no_syscall, 0, CALL_INDIRECT, VOID) /* 322 */ + ENTRY("", 323, no_syscall, 0, CALL_INDIRECT, VOID) /* 323 */ + ENTRY("mlockall", SYS_mlockall, mlockall, 1, CALL_DIRECT, INT) /* 324 */ + ENTRY("munlockall", SYS_munlockall, unimpl_unix_syscall, 1, CALL_INDIRECT, VOID) /* 325 */ + ENTRY("", 326, no_syscall, 0, CALL_INDIRECT, VOID) /* 326 */ + ENTRY("issetugid", SYS_issetugid, issetugid, 0, CALL_DIRECT, VOID) /* 327 */ + ENTRY("__pthread_kill", SYS___pthread_kill, unimpl_unix_syscall, 2, CALL_INDIRECT, VOID) /* 328 */ + ENTRY("pthread_sigmask", SYS_pthread_sigmask, pthread_sigmask, 3, CALL_DIRECT, INT, PTR, PTR) /* 329 */ + ENTRY("sigwait", SYS_sigwait, sigwait, 2, CALL_DIRECT, PTR, PTR) /* 330 */ + ENTRY("__disable_threadsignal", SYS___disable_threadsignal, unimpl_unix_syscall, 1, CALL_INDIRECT, VOID) /* 331 */ + ENTRY("__pthread_markcancel", SYS___pthread_markcancel, unimpl_unix_syscall, 1, CALL_INDIRECT, VOID) /* 332 */ + ENTRY("__pthread_canceled", SYS___pthread_canceled, unimpl_unix_syscall, 1, CALL_INDIRECT, VOID) /* 333 */ + ENTRY("__semwait_signal", SYS___semwait_signal, unimpl_unix_syscall, 6, CALL_INDIRECT, VOID) /* 334 */ + ENTRY("utrace", SYS_utrace, unimpl_unix_syscall, 2, CALL_INDIRECT, VOID) /* 335 */ + ENTRY("proc_info", SYS_proc_info, unimpl_unix_syscall, 6, CALL_INDIRECT, VOID) /* 336 */ + ENTRY("", 337, no_syscall, 0, CALL_INDIRECT, VOID) /* 337 */ + ENTRY("", 338, no_syscall, 0, CALL_INDIRECT, VOID) /* 338 */ + ENTRY("", 339, no_syscall, 0, CALL_INDIRECT, VOID) /* 339 */ + ENTRY("", 340, no_syscall, 0, CALL_INDIRECT, VOID) /* 340 */ + ENTRY("", 341, no_syscall, 0, CALL_INDIRECT, VOID) /* 341 */ + ENTRY("", 342, no_syscall, 0, CALL_INDIRECT, VOID) /* 342 */ + ENTRY("", 343, no_syscall, 0, CALL_INDIRECT, VOID) /* 343 */ + ENTRY("", 344, no_syscall, 0, CALL_INDIRECT, VOID) /* 344 */ + ENTRY("", 345, no_syscall, 0, CALL_INDIRECT, VOID) /* 345 */ + ENTRY("", 346, no_syscall, 0, CALL_INDIRECT, VOID) /* 346 */ + ENTRY("", 347, no_syscall, 0, CALL_INDIRECT, VOID) /* 347 */ + ENTRY("", 348, no_syscall, 0, CALL_INDIRECT, VOID) /* 348 */ + ENTRY("", 349, no_syscall, 0, CALL_INDIRECT, VOID) /* 349 */ + ENTRY("audit", SYS_audit, audit, 2, CALL_DIRECT, PTR, INT) /* 350 */ + ENTRY("auditon", SYS_auditon, auditon, 3, CALL_DIRECT, INT, PTR, INT) /* 351 */ + ENTRY("", 352, no_syscall, 0, CALL_INDIRECT, VOID) /* 352 */ + ENTRY("getauid", SYS_getauid, getauid, 1, CALL_DIRECT, PTR) /* 353 */ + ENTRY("setauid", SYS_setauid, setauid, 1, CALL_DIRECT, PTR) /* 354 */ + ENTRY("getaudit", SYS_getaudit, getaudit, 1, CALL_DIRECT, PTR) /* 355 */ + ENTRY("setaudit", SYS_setaudit, setaudit, 1, CALL_DIRECT, PTR) /* 356 */ + ENTRY("getaudit_addr", SYS_getaudit_addr, getaudit_addr, 2, CALL_DIRECT, PTR, INT) /* 357 */ + ENTRY("setaudit_addr", SYS_setaudit_addr, setaudit_addr, 2, CALL_DIRECT, PTR, INT) /* 358 */ + ENTRY("auditctl", SYS_auditctl, auditctl, 1, CALL_DIRECT, PTR) /* 359 */ + ENTRY("", 360, no_syscall, 0, CALL_INDIRECT, VOID) /* 360 */ + ENTRY("", 361, no_syscall, 0, CALL_INDIRECT, VOID) /* 361 */ + ENTRY("kqueue", SYS_kqueue, kqueue, 0, CALL_DIRECT, VOID) /* 362 */ + ENTRY("kevent", SYS_kevent, kevent, 6, CALL_DIRECT, INT, PTR, INT, PTR, INT, PTR) /* 363 */ + ENTRY("lchown", SYS_lchown, lchown, 3, CALL_DIRECT, PTR, INT , INT) /* 364 */ + ENTRY("stack_snapshot", SYS_stack_snapshot, unimpl_unix_syscall, 4, CALL_INDIRECT, VOID) /* 365 */ + ENTRY("", 366, no_syscall, 0, CALL_INDIRECT, VOID) /* 366 */ + ENTRY("", 367, no_syscall, 0, CALL_INDIRECT, VOID) /* 367 */ + ENTRY("", 368, no_syscall, 0, CALL_INDIRECT, VOID) /* 368 */ + ENTRY("", 369, no_syscall, 0, CALL_INDIRECT, VOID) /* 369 */ diff --git a/def-helper.h b/def-helper.h new file mode 100644 index 0000000..8a822c7 --- /dev/null +++ b/def-helper.h @@ -0,0 +1,240 @@ +/* Helper file for declaring TCG helper functions. + Should be included at the start and end of target-foo/helper.h. + + Targets should use DEF_HELPER_N and DEF_HELPER_FLAGS_N to declare helper + functions. Names should be specified without the helper_ prefix, and + the return and argument types specified. 3 basic types are understood + (i32, i64 and ptr). Additional aliases are provided for convenience and + to match the types used by the C helper implementation. + + The target helper.h should be included in all files that use/define + helper functions. THis will ensure that function prototypes are + consistent. In addition it should be included an extra two times for + helper.c, defining: + GEN_HELPER 1 to produce op generation functions (gen_helper_*) + GEN_HELPER 2 to do runtime registration helper functions. + */ + +#ifndef DEF_HELPER_H +#define DEF_HELPER_H 1 + +#define HELPER(name) glue(helper_, name) + +#define GET_TCGV_i32 GET_TCGV_I32 +#define GET_TCGV_i64 GET_TCGV_I64 +#define GET_TCGV_ptr GET_TCGV_PTR + +/* Some types that make sense in C, but not for TCG. */ +#define dh_alias_i32 i32 +#define dh_alias_s32 i32 +#define dh_alias_int i32 +#define dh_alias_i64 i64 +#define dh_alias_s64 i64 +#define dh_alias_f32 i32 +#define dh_alias_f64 i64 +#if TARGET_LONG_BITS == 32 +#define dh_alias_tl i32 +#else +#define dh_alias_tl i64 +#endif +#define dh_alias_ptr ptr +#define dh_alias_void void +#define dh_alias_env ptr +#define dh_alias(t) glue(dh_alias_, t) + +#define dh_ctype_i32 uint32_t +#define dh_ctype_s32 int32_t +#define dh_ctype_int int +#define dh_ctype_i64 uint64_t +#define dh_ctype_s64 int64_t +#define dh_ctype_f32 float32 +#define dh_ctype_f64 float64 +#define dh_ctype_tl target_ulong +#define dh_ctype_ptr void * +#define dh_ctype_void void +#define dh_ctype_env CPUState * +#define dh_ctype(t) dh_ctype_##t + +/* We can't use glue() here because it falls foul of C preprocessor + recursive expansion rules. */ +#define dh_retvar_decl0_void void +#define dh_retvar_decl0_i32 TCGv_i32 retval +#define dh_retvar_decl0_i64 TCGv_i64 retval +#define dh_retvar_decl0_ptr TCGv_ptr retval +#define dh_retvar_decl0(t) glue(dh_retvar_decl0_, dh_alias(t)) + +#define dh_retvar_decl_void +#define dh_retvar_decl_i32 TCGv_i32 retval, +#define dh_retvar_decl_i64 TCGv_i64 retval, +#define dh_retvar_decl_ptr TCGv_ptr retval, +#define dh_retvar_decl(t) glue(dh_retvar_decl_, dh_alias(t)) + +#define dh_retvar_void TCG_CALL_DUMMY_ARG +#define dh_retvar_i32 GET_TCGV_i32(retval) +#define dh_retvar_i64 GET_TCGV_i64(retval) +#define dh_retvar_ptr GET_TCGV_ptr(retval) +#define dh_retvar(t) glue(dh_retvar_, dh_alias(t)) + +#define dh_is_64bit_void 0 +#define dh_is_64bit_i32 0 +#define dh_is_64bit_i64 1 +#define dh_is_64bit_ptr (TCG_TARGET_REG_BITS == 64) +#define dh_is_64bit(t) glue(dh_is_64bit_, dh_alias(t)) + +#define dh_is_signed_void 0 +#define dh_is_signed_i32 0 +#define dh_is_signed_s32 1 +#define dh_is_signed_i64 0 +#define dh_is_signed_s64 1 +#define dh_is_signed_f32 0 +#define dh_is_signed_f64 0 +#define dh_is_signed_tl 0 +#define dh_is_signed_int 1 +/* ??? This is highly specific to the host cpu. There are even special + extension instructions that may be required, e.g. ia64's addp4. But + for now we don't support any 64-bit targets with 32-bit pointers. */ +#define dh_is_signed_ptr 0 +#define dh_is_signed_env dh_is_signed_ptr +#define dh_is_signed(t) dh_is_signed_##t + +#define dh_sizemask(t, n) \ + sizemask |= dh_is_64bit(t) << (n*2); \ + sizemask |= dh_is_signed(t) << (n*2+1) + +#define dh_arg(t, n) \ + args[n - 1] = glue(GET_TCGV_, dh_alias(t))(glue(arg, n)); \ + dh_sizemask(t, n) + +#define dh_arg_decl(t, n) glue(TCGv_, dh_alias(t)) glue(arg, n) + + +#define DEF_HELPER_0(name, ret) \ + DEF_HELPER_FLAGS_0(name, 0, ret) +#define DEF_HELPER_1(name, ret, t1) \ + DEF_HELPER_FLAGS_1(name, 0, ret, t1) +#define DEF_HELPER_2(name, ret, t1, t2) \ + DEF_HELPER_FLAGS_2(name, 0, ret, t1, t2) +#define DEF_HELPER_3(name, ret, t1, t2, t3) \ + DEF_HELPER_FLAGS_3(name, 0, ret, t1, t2, t3) +#define DEF_HELPER_4(name, ret, t1, t2, t3, t4) \ + DEF_HELPER_FLAGS_4(name, 0, ret, t1, t2, t3, t4) + +#endif /* DEF_HELPER_H */ + +#ifndef GEN_HELPER +/* Function prototypes. */ + +#define DEF_HELPER_FLAGS_0(name, flags, ret) \ +dh_ctype(ret) HELPER(name) (void); + +#define DEF_HELPER_FLAGS_1(name, flags, ret, t1) \ +dh_ctype(ret) HELPER(name) (dh_ctype(t1)); + +#define DEF_HELPER_FLAGS_2(name, flags, ret, t1, t2) \ +dh_ctype(ret) HELPER(name) (dh_ctype(t1), dh_ctype(t2)); + +#define DEF_HELPER_FLAGS_3(name, flags, ret, t1, t2, t3) \ +dh_ctype(ret) HELPER(name) (dh_ctype(t1), dh_ctype(t2), dh_ctype(t3)); + +#define DEF_HELPER_FLAGS_4(name, flags, ret, t1, t2, t3, t4) \ +dh_ctype(ret) HELPER(name) (dh_ctype(t1), dh_ctype(t2), dh_ctype(t3), \ + dh_ctype(t4)); + +#undef GEN_HELPER +#define GEN_HELPER -1 + +#elif GEN_HELPER == 1 +/* Gen functions. */ + +#define DEF_HELPER_FLAGS_0(name, flags, ret) \ +static inline void glue(gen_helper_, name)(dh_retvar_decl0(ret)) \ +{ \ + int sizemask; \ + sizemask = dh_is_64bit(ret); \ + tcg_gen_helperN(HELPER(name), flags, sizemask, dh_retvar(ret), 0, NULL); \ +} + +#define DEF_HELPER_FLAGS_1(name, flags, ret, t1) \ +static inline void glue(gen_helper_, name)(dh_retvar_decl(ret) dh_arg_decl(t1, 1)) \ +{ \ + TCGArg args[1]; \ + int sizemask = 0; \ + dh_sizemask(ret, 0); \ + dh_arg(t1, 1); \ + tcg_gen_helperN(HELPER(name), flags, sizemask, dh_retvar(ret), 1, args); \ +} + +#define DEF_HELPER_FLAGS_2(name, flags, ret, t1, t2) \ +static inline void glue(gen_helper_, name)(dh_retvar_decl(ret) dh_arg_decl(t1, 1), \ + dh_arg_decl(t2, 2)) \ +{ \ + TCGArg args[2]; \ + int sizemask = 0; \ + dh_sizemask(ret, 0); \ + dh_arg(t1, 1); \ + dh_arg(t2, 2); \ + tcg_gen_helperN(HELPER(name), flags, sizemask, dh_retvar(ret), 2, args); \ +} + +#define DEF_HELPER_FLAGS_3(name, flags, ret, t1, t2, t3) \ +static inline void glue(gen_helper_, name)(dh_retvar_decl(ret) dh_arg_decl(t1, 1), \ + dh_arg_decl(t2, 2), dh_arg_decl(t3, 3)) \ +{ \ + TCGArg args[3]; \ + int sizemask = 0; \ + dh_sizemask(ret, 0); \ + dh_arg(t1, 1); \ + dh_arg(t2, 2); \ + dh_arg(t3, 3); \ + tcg_gen_helperN(HELPER(name), flags, sizemask, dh_retvar(ret), 3, args); \ +} + +#define DEF_HELPER_FLAGS_4(name, flags, ret, t1, t2, t3, t4) \ +static inline void glue(gen_helper_, name)(dh_retvar_decl(ret) dh_arg_decl(t1, 1), \ + dh_arg_decl(t2, 2), dh_arg_decl(t3, 3), dh_arg_decl(t4, 4)) \ +{ \ + TCGArg args[4]; \ + int sizemask = 0; \ + dh_sizemask(ret, 0); \ + dh_arg(t1, 1); \ + dh_arg(t2, 2); \ + dh_arg(t3, 3); \ + dh_arg(t4, 4); \ + tcg_gen_helperN(HELPER(name), flags, sizemask, dh_retvar(ret), 4, args); \ +} + +#undef GEN_HELPER +#define GEN_HELPER -1 + +#elif GEN_HELPER == 2 +/* Register helpers. */ + +#define DEF_HELPER_FLAGS_0(name, flags, ret) \ +tcg_register_helper(HELPER(name), #name); + +#define DEF_HELPER_FLAGS_1(name, flags, ret, t1) \ +DEF_HELPER_FLAGS_0(name, flags, ret) + +#define DEF_HELPER_FLAGS_2(name, flags, ret, t1, t2) \ +DEF_HELPER_FLAGS_0(name, flags, ret) + +#define DEF_HELPER_FLAGS_3(name, flags, ret, t1, t2, t3) \ +DEF_HELPER_FLAGS_0(name, flags, ret) + +#define DEF_HELPER_FLAGS_4(name, flags, ret, t1, t2, t3, t4) \ +DEF_HELPER_FLAGS_0(name, flags, ret) + +#undef GEN_HELPER +#define GEN_HELPER -1 + +#elif GEN_HELPER == -1 +/* Undefine macros. */ + +#undef DEF_HELPER_FLAGS_0 +#undef DEF_HELPER_FLAGS_1 +#undef DEF_HELPER_FLAGS_2 +#undef DEF_HELPER_FLAGS_3 +#undef DEF_HELPER_FLAGS_4 +#undef GEN_HELPER + +#endif diff --git a/default-configs/alpha-linux-user.mak b/default-configs/alpha-linux-user.mak new file mode 100644 index 0000000..7956e29 --- /dev/null +++ b/default-configs/alpha-linux-user.mak @@ -0,0 +1 @@ +# Default configuration for alpha-linux-user diff --git a/default-configs/arm-linux-user.mak b/default-configs/arm-linux-user.mak new file mode 100644 index 0000000..46d4aa2 --- /dev/null +++ b/default-configs/arm-linux-user.mak @@ -0,0 +1,3 @@ +# Default configuration for arm-linux-user + +CONFIG_GDBSTUB_XML=y diff --git a/default-configs/arm-softmmu.mak b/default-configs/arm-softmmu.mak new file mode 100644 index 0000000..8d1174f --- /dev/null +++ b/default-configs/arm-softmmu.mak @@ -0,0 +1,28 @@ +# Default configuration for arm-softmmu + +include pci.mak +CONFIG_GDBSTUB_XML=y +CONFIG_ISA_MMIO=y +CONFIG_NAND=y +CONFIG_ECC=y +CONFIG_SERIAL=y +CONFIG_PTIMER=y +CONFIG_SD=y +CONFIG_MAX7310=y +CONFIG_WM8750=y +CONFIG_TWL92230=y +CONFIG_TSC2005=y +CONFIG_LM832X=y +CONFIG_TMP105=y +CONFIG_STELLARIS_INPUT=y +CONFIG_SSD0303=y +CONFIG_SSD0323=y +CONFIG_ADS7846=y +CONFIG_MAX111X=y +CONFIG_SSI=y +CONFIG_SSI_SD=y +CONFIG_LAN9118=y +CONFIG_SMC91C111=y +CONFIG_DS1338=y +CONFIG_PFLASH_CFI01=y +CONFIG_PFLASH_CFI02=y diff --git a/default-configs/armeb-linux-user.mak b/default-configs/armeb-linux-user.mak new file mode 100644 index 0000000..41d0cc4 --- /dev/null +++ b/default-configs/armeb-linux-user.mak @@ -0,0 +1,3 @@ +# Default configuration for armeb-linux-user + +CONFIG_GDBSTUB_XML=y diff --git a/default-configs/cris-linux-user.mak b/default-configs/cris-linux-user.mak new file mode 100644 index 0000000..e3aec7b --- /dev/null +++ b/default-configs/cris-linux-user.mak @@ -0,0 +1 @@ +# Default configuration for cris-linux-user diff --git a/default-configs/cris-softmmu.mak b/default-configs/cris-softmmu.mak new file mode 100644 index 0000000..1a479cd --- /dev/null +++ b/default-configs/cris-softmmu.mak @@ -0,0 +1,5 @@ +# Default configuration for cris-softmmu + +CONFIG_NAND=y +CONFIG_PTIMER=y +CONFIG_PFLASH_CFI02=y diff --git a/default-configs/i386-bsd-user.mak b/default-configs/i386-bsd-user.mak new file mode 100644 index 0000000..af1b31a --- /dev/null +++ b/default-configs/i386-bsd-user.mak @@ -0,0 +1 @@ +# Default configuration for i386-bsd-user diff --git a/default-configs/i386-darwin-user.mak b/default-configs/i386-darwin-user.mak new file mode 100644 index 0000000..19afd3d --- /dev/null +++ b/default-configs/i386-darwin-user.mak @@ -0,0 +1 @@ +# Default configuration for i386-darwin-user diff --git a/default-configs/i386-linux-user.mak b/default-configs/i386-linux-user.mak new file mode 100644 index 0000000..8657e68 --- /dev/null +++ b/default-configs/i386-linux-user.mak @@ -0,0 +1 @@ +# Default configuration for i386-linux-user diff --git a/default-configs/i386-softmmu.mak b/default-configs/i386-softmmu.mak new file mode 100644 index 0000000..323fafb --- /dev/null +++ b/default-configs/i386-softmmu.mak @@ -0,0 +1,20 @@ +# Default configuration for i386-softmmu + +include pci.mak +CONFIG_VGA_PCI=y +CONFIG_VGA_ISA=y +CONFIG_VMWARE_VGA=y +CONFIG_SERIAL=y +CONFIG_PARALLEL=y +CONFIG_I8254=y +CONFIG_PCSPK=y +CONFIG_PCKBD=y +CONFIG_FDC=y +CONFIG_ACPI=y +CONFIG_APM=y +CONFIG_DMA=y +CONFIG_IDE_ISA=y +CONFIG_IDE_PIIX=y +CONFIG_NE2000_ISA=y +CONFIG_PIIX_PCI=y +CONFIG_SOUND=y diff --git a/default-configs/m68k-linux-user.mak b/default-configs/m68k-linux-user.mak new file mode 100644 index 0000000..f3487aa --- /dev/null +++ b/default-configs/m68k-linux-user.mak @@ -0,0 +1,3 @@ +# Default configuration for m68k-linux-user + +CONFIG_GDBSTUB_XML=y diff --git a/default-configs/m68k-softmmu.mak b/default-configs/m68k-softmmu.mak new file mode 100644 index 0000000..3e2ec37 --- /dev/null +++ b/default-configs/m68k-softmmu.mak @@ -0,0 +1,5 @@ +# Default configuration for m68k-softmmu + +include pci.mak +CONFIG_GDBSTUB_XML=y +CONFIG_PTIMER=y diff --git a/default-configs/microblaze-linux-user.mak b/default-configs/microblaze-linux-user.mak new file mode 100644 index 0000000..566fdc0 --- /dev/null +++ b/default-configs/microblaze-linux-user.mak @@ -0,0 +1 @@ +# Default configuration for microblaze-linux-user diff --git a/default-configs/microblaze-softmmu.mak b/default-configs/microblaze-softmmu.mak new file mode 100644 index 0000000..4399b8b3 --- /dev/null +++ b/default-configs/microblaze-softmmu.mak @@ -0,0 +1,4 @@ +# Default configuration for microblaze-softmmu + +CONFIG_PTIMER=y +CONFIG_PFLASH_CFI01=y diff --git a/default-configs/mips-linux-user.mak b/default-configs/mips-linux-user.mak new file mode 100644 index 0000000..31df570 --- /dev/null +++ b/default-configs/mips-linux-user.mak @@ -0,0 +1 @@ +# Default configuration for mips-linux-user diff --git a/default-configs/mips-softmmu.mak b/default-configs/mips-softmmu.mak new file mode 100644 index 0000000..f524971 --- /dev/null +++ b/default-configs/mips-softmmu.mak @@ -0,0 +1,28 @@ +# Default configuration for mips-softmmu + +include pci.mak +CONFIG_ISA_MMIO=y +CONFIG_ESP=y +CONFIG_VGA_PCI=y +CONFIG_VGA_ISA=y +CONFIG_VGA_ISA_MM=y +CONFIG_VMWARE_VGA=y +CONFIG_SERIAL=y +CONFIG_PARALLEL=y +CONFIG_I8254=y +CONFIG_PCSPK=y +CONFIG_PCKBD=y +CONFIG_FDC=y +CONFIG_ACPI=y +CONFIG_APM=y +CONFIG_DMA=y +CONFIG_PIIX4=y +CONFIG_IDE_ISA=y +CONFIG_IDE_PIIX=y +CONFIG_NE2000_ISA=y +CONFIG_SOUND=y +CONFIG_RC4030=y +CONFIG_DP8393X=y +CONFIG_DS1225Y=y +CONFIG_MIPSNET=y +CONFIG_PFLASH_CFI01=y diff --git a/default-configs/mips64-softmmu.mak b/default-configs/mips64-softmmu.mak new file mode 100644 index 0000000..aeab6b2 --- /dev/null +++ b/default-configs/mips64-softmmu.mak @@ -0,0 +1,28 @@ +# Default configuration for mips64-softmmu + +include pci.mak +CONFIG_ISA_MMIO=y +CONFIG_ESP=y +CONFIG_VGA_PCI=y +CONFIG_VGA_ISA=y +CONFIG_VGA_ISA_MM=y +CONFIG_VMWARE_VGA=y +CONFIG_SERIAL=y +CONFIG_PARALLEL=y +CONFIG_I8254=y +CONFIG_PCSPK=y +CONFIG_PCKBD=y +CONFIG_FDC=y +CONFIG_ACPI=y +CONFIG_APM=y +CONFIG_DMA=y +CONFIG_PIIX4=y +CONFIG_IDE_ISA=y +CONFIG_IDE_PIIX=y +CONFIG_NE2000_ISA=y +CONFIG_SOUND=y +CONFIG_RC4030=y +CONFIG_DP8393X=y +CONFIG_DS1225Y=y +CONFIG_MIPSNET=y +CONFIG_PFLASH_CFI01=y diff --git a/default-configs/mips64el-softmmu.mak b/default-configs/mips64el-softmmu.mak new file mode 100644 index 0000000..8e6511c --- /dev/null +++ b/default-configs/mips64el-softmmu.mak @@ -0,0 +1,30 @@ +# Default configuration for mips64el-softmmu + +include pci.mak +CONFIG_ISA_MMIO=y +CONFIG_ESP=y +CONFIG_VGA_PCI=y +CONFIG_VGA_ISA=y +CONFIG_VGA_ISA_MM=y +CONFIG_VMWARE_VGA=y +CONFIG_SERIAL=y +CONFIG_PARALLEL=y +CONFIG_I8254=y +CONFIG_PCSPK=y +CONFIG_PCKBD=y +CONFIG_FDC=y +CONFIG_ACPI=y +CONFIG_APM=y +CONFIG_DMA=y +CONFIG_PIIX4=y +CONFIG_IDE_ISA=y +CONFIG_IDE_PIIX=y +CONFIG_IDE_VIA=y +CONFIG_NE2000_ISA=y +CONFIG_SOUND=y +CONFIG_RC4030=y +CONFIG_DP8393X=y +CONFIG_DS1225Y=y +CONFIG_MIPSNET=y +CONFIG_PFLASH_CFI01=y +CONFIG_FULONG=y diff --git a/default-configs/mipsel-linux-user.mak b/default-configs/mipsel-linux-user.mak new file mode 100644 index 0000000..4d0e4af --- /dev/null +++ b/default-configs/mipsel-linux-user.mak @@ -0,0 +1 @@ +# Default configuration for mipsel-linux-user diff --git a/default-configs/mipsel-softmmu.mak b/default-configs/mipsel-softmmu.mak new file mode 100644 index 0000000..a05ac25 --- /dev/null +++ b/default-configs/mipsel-softmmu.mak @@ -0,0 +1,28 @@ +# Default configuration for mipsel-softmmu + +include pci.mak +CONFIG_ISA_MMIO=y +CONFIG_ESP=y +CONFIG_VGA_PCI=y +CONFIG_VGA_ISA=y +CONFIG_VGA_ISA_MM=y +CONFIG_VMWARE_VGA=y +CONFIG_SERIAL=y +CONFIG_PARALLEL=y +CONFIG_I8254=y +CONFIG_PCSPK=y +CONFIG_PCKBD=y +CONFIG_FDC=y +CONFIG_ACPI=y +CONFIG_APM=y +CONFIG_DMA=y +CONFIG_PIIX4=y +CONFIG_IDE_ISA=y +CONFIG_IDE_PIIX=y +CONFIG_NE2000_ISA=y +CONFIG_SOUND=y +CONFIG_RC4030=y +CONFIG_DP8393X=y +CONFIG_DS1225Y=y +CONFIG_MIPSNET=y +CONFIG_PFLASH_CFI01=y diff --git a/default-configs/pci.mak b/default-configs/pci.mak new file mode 100644 index 0000000..0471efb --- /dev/null +++ b/default-configs/pci.mak @@ -0,0 +1,16 @@ +CONFIG_PCI=y +CONFIG_VIRTIO_PCI=y +CONFIG_VIRTIO=y +CONFIG_USB_UHCI=y +CONFIG_USB_OHCI=y +CONFIG_NE2000_PCI=y +CONFIG_EEPRO100_PCI=y +CONFIG_PCNET_PCI=y +CONFIG_PCNET_COMMON=y +CONFIG_LSI_SCSI_PCI=y +CONFIG_RTL8139_PCI=y +CONFIG_E1000_PCI=y +CONFIG_IDE_CORE=y +CONFIG_IDE_QDEV=y +CONFIG_IDE_PCI=y +CONFIG_AHCI=y diff --git a/default-configs/ppc-darwin-user.mak b/default-configs/ppc-darwin-user.mak new file mode 100644 index 0000000..153ed12 --- /dev/null +++ b/default-configs/ppc-darwin-user.mak @@ -0,0 +1,3 @@ +# Default configuration for ppc-darwin-user + +CONFIG_GDBSTUB_XML=y diff --git a/default-configs/ppc-linux-user.mak b/default-configs/ppc-linux-user.mak new file mode 100644 index 0000000..681a945 --- /dev/null +++ b/default-configs/ppc-linux-user.mak @@ -0,0 +1,3 @@ +# Default configuration for ppc-linux-user + +CONFIG_GDBSTUB_XML=y diff --git a/default-configs/ppc-softmmu.mak b/default-configs/ppc-softmmu.mak new file mode 100644 index 0000000..4563742 --- /dev/null +++ b/default-configs/ppc-softmmu.mak @@ -0,0 +1,33 @@ +# Default configuration for ppc-softmmu + +include pci.mak +CONFIG_GDBSTUB_XML=y +CONFIG_ISA_MMIO=y +CONFIG_ESCC=y +CONFIG_M48T59=y +CONFIG_VGA_PCI=y +CONFIG_SERIAL=y +CONFIG_I8254=y +CONFIG_PCKBD=y +CONFIG_FDC=y +CONFIG_DMA=y +CONFIG_OPENPIC=y +CONFIG_PREP_PCI=y +CONFIG_MACIO=y +CONFIG_CUDA=y +CONFIG_ADB=y +CONFIG_MAC_NVRAM=y +CONFIG_MAC_DBDMA=y +CONFIG_HEATHROW_PIC=y +CONFIG_GRACKLE_PCI=y +CONFIG_UNIN_PCI=y +CONFIG_DEC_PCI=y +CONFIG_PPCE500_PCI=y +CONFIG_IDE_ISA=y +CONFIG_IDE_CMD646=y +CONFIG_IDE_MACIO=y +CONFIG_NE2000_ISA=y +CONFIG_SOUND=y +CONFIG_PFLASH_CFI01=y +CONFIG_PFLASH_CFI02=y +CONFIG_PTIMER=y diff --git a/default-configs/ppc64-linux-user.mak b/default-configs/ppc64-linux-user.mak new file mode 100644 index 0000000..089c08f --- /dev/null +++ b/default-configs/ppc64-linux-user.mak @@ -0,0 +1,3 @@ +# Default configuration for ppc64-linux-user + +CONFIG_GDBSTUB_XML=y diff --git a/default-configs/ppc64-softmmu.mak b/default-configs/ppc64-softmmu.mak new file mode 100644 index 0000000..d5073b3 --- /dev/null +++ b/default-configs/ppc64-softmmu.mak @@ -0,0 +1,33 @@ +# Default configuration for ppc64-softmmu + +include pci.mak +CONFIG_GDBSTUB_XML=y +CONFIG_ISA_MMIO=y +CONFIG_ESCC=y +CONFIG_M48T59=y +CONFIG_VGA_PCI=y +CONFIG_SERIAL=y +CONFIG_I8254=y +CONFIG_PCKBD=y +CONFIG_FDC=y +CONFIG_DMA=y +CONFIG_OPENPIC=y +CONFIG_PREP_PCI=y +CONFIG_MACIO=y +CONFIG_CUDA=y +CONFIG_ADB=y +CONFIG_MAC_NVRAM=y +CONFIG_MAC_DBDMA=y +CONFIG_HEATHROW_PIC=y +CONFIG_GRACKLE_PCI=y +CONFIG_UNIN_PCI=y +CONFIG_DEC_PCI=y +CONFIG_PPCE500_PCI=y +CONFIG_IDE_ISA=y +CONFIG_IDE_CMD646=y +CONFIG_IDE_MACIO=y +CONFIG_NE2000_ISA=y +CONFIG_SOUND=y +CONFIG_PFLASH_CFI01=y +CONFIG_PFLASH_CFI02=y +CONFIG_PTIMER=y diff --git a/default-configs/ppc64abi32-linux-user.mak b/default-configs/ppc64abi32-linux-user.mak new file mode 100644 index 0000000..f038ffd --- /dev/null +++ b/default-configs/ppc64abi32-linux-user.mak @@ -0,0 +1,3 @@ +# Default configuration for ppc64abi32-linux-user + +CONFIG_GDBSTUB_XML=y diff --git a/default-configs/ppcemb-softmmu.mak b/default-configs/ppcemb-softmmu.mak new file mode 100644 index 0000000..9f0730c --- /dev/null +++ b/default-configs/ppcemb-softmmu.mak @@ -0,0 +1,33 @@ +# Default configuration for ppcemb-softmmu + +include pci.mak +CONFIG_GDBSTUB_XML=y +CONFIG_ISA_MMIO=y +CONFIG_ESCC=y +CONFIG_M48T59=y +CONFIG_VGA_PCI=y +CONFIG_SERIAL=y +CONFIG_I8254=y +CONFIG_PCKBD=y +CONFIG_FDC=y +CONFIG_DMA=y +CONFIG_OPENPIC=y +CONFIG_PREP_PCI=y +CONFIG_MACIO=y +CONFIG_CUDA=y +CONFIG_ADB=y +CONFIG_MAC_NVRAM=y +CONFIG_MAC_DBDMA=y +CONFIG_HEATHROW_PIC=y +CONFIG_GRACKLE_PCI=y +CONFIG_UNIN_PCI=y +CONFIG_DEC_PCI=y +CONFIG_PPCE500_PCI=y +CONFIG_IDE_ISA=y +CONFIG_IDE_CMD646=y +CONFIG_IDE_MACIO=y +CONFIG_NE2000_ISA=y +CONFIG_SOUND=y +CONFIG_PFLASH_CFI01=y +CONFIG_PFLASH_CFI02=y +CONFIG_PTIMER=y diff --git a/default-configs/s390x-softmmu.mak b/default-configs/s390x-softmmu.mak new file mode 100644 index 0000000..3005729 --- /dev/null +++ b/default-configs/s390x-softmmu.mak @@ -0,0 +1 @@ +CONFIG_VIRTIO=y diff --git a/default-configs/sh4-linux-user.mak b/default-configs/sh4-linux-user.mak new file mode 100644 index 0000000..a469e19 --- /dev/null +++ b/default-configs/sh4-linux-user.mak @@ -0,0 +1 @@ +# Default configuration for sh4-linux-user diff --git a/default-configs/sh4-softmmu.mak b/default-configs/sh4-softmmu.mak new file mode 100644 index 0000000..5c69acc --- /dev/null +++ b/default-configs/sh4-softmmu.mak @@ -0,0 +1,7 @@ +# Default configuration for sh4-softmmu + +include pci.mak +CONFIG_SERIAL=y +CONFIG_PTIMER=y +CONFIG_PFLASH_CFI02=y +CONFIG_ISA_MMIO=y diff --git a/default-configs/sh4eb-linux-user.mak b/default-configs/sh4eb-linux-user.mak new file mode 100644 index 0000000..be08ca1 --- /dev/null +++ b/default-configs/sh4eb-linux-user.mak @@ -0,0 +1 @@ +# Default configuration for sh4eb-linux-user diff --git a/default-configs/sh4eb-softmmu.mak b/default-configs/sh4eb-softmmu.mak new file mode 100644 index 0000000..7cdc122 --- /dev/null +++ b/default-configs/sh4eb-softmmu.mak @@ -0,0 +1,7 @@ +# Default configuration for sh4eb-softmmu + +include pci.mak +CONFIG_SERIAL=y +CONFIG_PTIMER=y +CONFIG_PFLASH_CFI02=y +CONFIG_ISA_MMIO=y diff --git a/default-configs/sparc-bsd-user.mak b/default-configs/sparc-bsd-user.mak new file mode 100644 index 0000000..21e0950 --- /dev/null +++ b/default-configs/sparc-bsd-user.mak @@ -0,0 +1 @@ +# Default configuration for sparc-bsd-user diff --git a/default-configs/sparc-linux-user.mak b/default-configs/sparc-linux-user.mak new file mode 100644 index 0000000..9c716d1 --- /dev/null +++ b/default-configs/sparc-linux-user.mak @@ -0,0 +1 @@ +# Default configuration for sparc-linux-user diff --git a/default-configs/sparc-softmmu.mak b/default-configs/sparc-softmmu.mak new file mode 100644 index 0000000..b0310c5 --- /dev/null +++ b/default-configs/sparc-softmmu.mak @@ -0,0 +1,10 @@ +# Default configuration for sparc-softmmu + +CONFIG_ECC=y +CONFIG_ESP=y +CONFIG_ESCC=y +CONFIG_M48T59=y +CONFIG_PTIMER=y +CONFIG_FDC=y +CONFIG_EMPTY_SLOT=y +CONFIG_PCNET_COMMON=y diff --git a/default-configs/sparc32plus-linux-user.mak b/default-configs/sparc32plus-linux-user.mak new file mode 100644 index 0000000..432e880 --- /dev/null +++ b/default-configs/sparc32plus-linux-user.mak @@ -0,0 +1 @@ +# Default configuration for sparc32plus-linux-user diff --git a/default-configs/sparc64-bsd-user.mak b/default-configs/sparc64-bsd-user.mak new file mode 100644 index 0000000..b8b9eea --- /dev/null +++ b/default-configs/sparc64-bsd-user.mak @@ -0,0 +1 @@ +# Default configuration for sparc64-bsd-user diff --git a/default-configs/sparc64-linux-user.mak b/default-configs/sparc64-linux-user.mak new file mode 100644 index 0000000..bf1bdd6 --- /dev/null +++ b/default-configs/sparc64-linux-user.mak @@ -0,0 +1 @@ +# Default configuration for sparc64-linux-user diff --git a/default-configs/sparc64-softmmu.mak b/default-configs/sparc64-softmmu.mak new file mode 100644 index 0000000..d8f17e7 --- /dev/null +++ b/default-configs/sparc64-softmmu.mak @@ -0,0 +1,13 @@ +# Default configuration for sparc64-softmmu + +include pci.mak +CONFIG_ISA_MMIO=y +CONFIG_M48T59=y +CONFIG_PTIMER=y +CONFIG_VGA_PCI=y +CONFIG_SERIAL=y +CONFIG_PARALLEL=y +CONFIG_PCKBD=y +CONFIG_FDC=y +CONFIG_IDE_ISA=y +CONFIG_IDE_CMD646=y diff --git a/default-configs/x86_64-bsd-user.mak b/default-configs/x86_64-bsd-user.mak new file mode 100644 index 0000000..73e5d34 --- /dev/null +++ b/default-configs/x86_64-bsd-user.mak @@ -0,0 +1 @@ +# Default configuration for x86_64-bsd-user diff --git a/default-configs/x86_64-linux-user.mak b/default-configs/x86_64-linux-user.mak new file mode 100644 index 0000000..bec1d9e --- /dev/null +++ b/default-configs/x86_64-linux-user.mak @@ -0,0 +1 @@ +# Default configuration for x86_64-linux-user diff --git a/default-configs/x86_64-softmmu.mak b/default-configs/x86_64-softmmu.mak new file mode 100644 index 0000000..eff26d2 --- /dev/null +++ b/default-configs/x86_64-softmmu.mak @@ -0,0 +1,20 @@ +# Default configuration for x86_64-softmmu + +include pci.mak +CONFIG_VGA_PCI=y +CONFIG_VGA_ISA=y +CONFIG_VMWARE_VGA=y +CONFIG_SERIAL=y +CONFIG_PARALLEL=y +CONFIG_I8254=y +CONFIG_PCSPK=y +CONFIG_PCKBD=y +CONFIG_FDC=y +CONFIG_ACPI=y +CONFIG_APM=y +CONFIG_DMA=y +CONFIG_IDE_ISA=y +CONFIG_IDE_PIIX=y +CONFIG_NE2000_ISA=y +CONFIG_PIIX_PCI=y +CONFIG_SOUND=y diff --git a/device_tree.c b/device_tree.c new file mode 100644 index 0000000..426a631 --- /dev/null +++ b/device_tree.c @@ -0,0 +1,110 @@ +/* + * Functions to help device tree manipulation using libfdt. + * It also provides functions to read entries from device tree proc + * interface. + * + * Copyright 2008 IBM Corporation. + * Authors: Jerone Young + * Hollis Blanchard + * + * This work is licensed under the GNU GPL license version 2 or later. + * + */ + +#include +#include +#include +#include +#include +#include + +#include "config.h" +#include "qemu-common.h" +#include "sysemu.h" +#include "device_tree.h" +#include "hw/loader.h" + +#include + +void *load_device_tree(const char *filename_path, int *sizep) +{ + int dt_size; + int dt_file_load_size; + int ret; + void *fdt = NULL; + + *sizep = 0; + dt_size = get_image_size(filename_path); + if (dt_size < 0) { + printf("Unable to get size of device tree file '%s'\n", + filename_path); + goto fail; + } + + /* Expand to 2x size to give enough room for manipulation. */ + dt_size *= 2; + /* First allocate space in qemu for device tree */ + fdt = qemu_mallocz(dt_size); + + dt_file_load_size = load_image(filename_path, fdt); + if (dt_file_load_size < 0) { + printf("Unable to open device tree file '%s'\n", + filename_path); + goto fail; + } + + ret = fdt_open_into(fdt, fdt, dt_size); + if (ret) { + printf("Unable to copy device tree in memory\n"); + goto fail; + } + + /* Check sanity of device tree */ + if (fdt_check_header(fdt)) { + printf ("Device tree file loaded into memory is invalid: %s\n", + filename_path); + goto fail; + } + *sizep = dt_size; + return fdt; + +fail: + qemu_free(fdt); + return NULL; +} + +int qemu_devtree_setprop(void *fdt, const char *node_path, + const char *property, uint32_t *val_array, int size) +{ + int offset; + + offset = fdt_path_offset(fdt, node_path); + if (offset < 0) + return offset; + + return fdt_setprop(fdt, offset, property, val_array, size); +} + +int qemu_devtree_setprop_cell(void *fdt, const char *node_path, + const char *property, uint32_t val) +{ + int offset; + + offset = fdt_path_offset(fdt, node_path); + if (offset < 0) + return offset; + + return fdt_setprop_cell(fdt, offset, property, val); +} + +int qemu_devtree_setprop_string(void *fdt, const char *node_path, + const char *property, const char *string) +{ + int offset; + + offset = fdt_path_offset(fdt, node_path); + if (offset < 0) + return offset; + + return fdt_setprop_string(fdt, offset, property, string); +} diff --git a/device_tree.h b/device_tree.h new file mode 100644 index 0000000..f05c4e7 --- /dev/null +++ b/device_tree.h @@ -0,0 +1,26 @@ +/* + * Header with function prototypes to help device tree manipulation using + * libfdt. It also provides functions to read entries from device tree proc + * interface. + * + * Copyright 2008 IBM Corporation. + * Authors: Jerone Young + * Hollis Blanchard + * + * This work is licensed under the GNU GPL license version 2 or later. + * + */ + +#ifndef __DEVICE_TREE_H__ +#define __DEVICE_TREE_H__ + +void *load_device_tree(const char *filename_path, int *sizep); + +int qemu_devtree_setprop(void *fdt, const char *node_path, + const char *property, uint32_t *val_array, int size); +int qemu_devtree_setprop_cell(void *fdt, const char *node_path, + const char *property, uint32_t val); +int qemu_devtree_setprop_string(void *fdt, const char *node_path, + const char *property, const char *string); + +#endif /* __DEVICE_TREE_H__ */ diff --git a/dis-asm.h b/dis-asm.h new file mode 100644 index 0000000..296537a --- /dev/null +++ b/dis-asm.h @@ -0,0 +1,476 @@ +/* Interface between the opcode library and its callers. + Written by Cygnus Support, 1993. + + The opcode library (libopcodes.a) provides instruction decoders for + a large variety of instruction sets, callable with an identical + interface, for making instruction-processing programs more independent + of the instruction set being processed. */ + +#ifndef DIS_ASM_H +#define DIS_ASM_H + +#include "qemu-common.h" + +typedef void *PTR; +typedef uint64_t bfd_vma; +typedef int64_t bfd_signed_vma; +typedef uint8_t bfd_byte; +#define sprintf_vma(s,x) sprintf (s, "%0" PRIx64, x) +#define snprintf_vma(s,ss,x) snprintf (s, ss, "%0" PRIx64, x) + +#define BFD64 + +enum bfd_flavour { + bfd_target_unknown_flavour, + bfd_target_aout_flavour, + bfd_target_coff_flavour, + bfd_target_ecoff_flavour, + bfd_target_elf_flavour, + bfd_target_ieee_flavour, + bfd_target_nlm_flavour, + bfd_target_oasys_flavour, + bfd_target_tekhex_flavour, + bfd_target_srec_flavour, + bfd_target_ihex_flavour, + bfd_target_som_flavour, + bfd_target_os9k_flavour, + bfd_target_versados_flavour, + bfd_target_msdos_flavour, + bfd_target_evax_flavour +}; + +enum bfd_endian { BFD_ENDIAN_BIG, BFD_ENDIAN_LITTLE, BFD_ENDIAN_UNKNOWN }; + +enum bfd_architecture +{ + bfd_arch_unknown, /* File arch not known */ + bfd_arch_obscure, /* Arch known, not one of these */ + bfd_arch_m68k, /* Motorola 68xxx */ +#define bfd_mach_m68000 1 +#define bfd_mach_m68008 2 +#define bfd_mach_m68010 3 +#define bfd_mach_m68020 4 +#define bfd_mach_m68030 5 +#define bfd_mach_m68040 6 +#define bfd_mach_m68060 7 +#define bfd_mach_cpu32 8 +#define bfd_mach_mcf5200 9 +#define bfd_mach_mcf5206e 10 +#define bfd_mach_mcf5307 11 +#define bfd_mach_mcf5407 12 +#define bfd_mach_mcf528x 13 +#define bfd_mach_mcfv4e 14 +#define bfd_mach_mcf521x 15 +#define bfd_mach_mcf5249 16 +#define bfd_mach_mcf547x 17 +#define bfd_mach_mcf548x 18 + bfd_arch_vax, /* DEC Vax */ + bfd_arch_i960, /* Intel 960 */ + /* The order of the following is important. + lower number indicates a machine type that + only accepts a subset of the instructions + available to machines with higher numbers. + The exception is the "ca", which is + incompatible with all other machines except + "core". */ + +#define bfd_mach_i960_core 1 +#define bfd_mach_i960_ka_sa 2 +#define bfd_mach_i960_kb_sb 3 +#define bfd_mach_i960_mc 4 +#define bfd_mach_i960_xa 5 +#define bfd_mach_i960_ca 6 +#define bfd_mach_i960_jx 7 +#define bfd_mach_i960_hx 8 + + bfd_arch_a29k, /* AMD 29000 */ + bfd_arch_sparc, /* SPARC */ +#define bfd_mach_sparc 1 +/* The difference between v8plus and v9 is that v9 is a true 64 bit env. */ +#define bfd_mach_sparc_sparclet 2 +#define bfd_mach_sparc_sparclite 3 +#define bfd_mach_sparc_v8plus 4 +#define bfd_mach_sparc_v8plusa 5 /* with ultrasparc add'ns. */ +#define bfd_mach_sparc_sparclite_le 6 +#define bfd_mach_sparc_v9 7 +#define bfd_mach_sparc_v9a 8 /* with ultrasparc add'ns. */ +#define bfd_mach_sparc_v8plusb 9 /* with cheetah add'ns. */ +#define bfd_mach_sparc_v9b 10 /* with cheetah add'ns. */ +/* Nonzero if MACH has the v9 instruction set. */ +#define bfd_mach_sparc_v9_p(mach) \ + ((mach) >= bfd_mach_sparc_v8plus && (mach) <= bfd_mach_sparc_v9b \ + && (mach) != bfd_mach_sparc_sparclite_le) + bfd_arch_mips, /* MIPS Rxxxx */ +#define bfd_mach_mips3000 3000 +#define bfd_mach_mips3900 3900 +#define bfd_mach_mips4000 4000 +#define bfd_mach_mips4010 4010 +#define bfd_mach_mips4100 4100 +#define bfd_mach_mips4300 4300 +#define bfd_mach_mips4400 4400 +#define bfd_mach_mips4600 4600 +#define bfd_mach_mips4650 4650 +#define bfd_mach_mips5000 5000 +#define bfd_mach_mips6000 6000 +#define bfd_mach_mips8000 8000 +#define bfd_mach_mips10000 10000 +#define bfd_mach_mips16 16 + bfd_arch_i386, /* Intel 386 */ +#define bfd_mach_i386_i386 0 +#define bfd_mach_i386_i8086 1 +#define bfd_mach_i386_i386_intel_syntax 2 +#define bfd_mach_x86_64 3 +#define bfd_mach_x86_64_intel_syntax 4 + bfd_arch_we32k, /* AT&T WE32xxx */ + bfd_arch_tahoe, /* CCI/Harris Tahoe */ + bfd_arch_i860, /* Intel 860 */ + bfd_arch_romp, /* IBM ROMP PC/RT */ + bfd_arch_alliant, /* Alliant */ + bfd_arch_convex, /* Convex */ + bfd_arch_m88k, /* Motorola 88xxx */ + bfd_arch_pyramid, /* Pyramid Technology */ + bfd_arch_h8300, /* Hitachi H8/300 */ +#define bfd_mach_h8300 1 +#define bfd_mach_h8300h 2 +#define bfd_mach_h8300s 3 + bfd_arch_powerpc, /* PowerPC */ +#define bfd_mach_ppc 0 +#define bfd_mach_ppc64 1 +#define bfd_mach_ppc_403 403 +#define bfd_mach_ppc_403gc 4030 +#define bfd_mach_ppc_e500 500 +#define bfd_mach_ppc_505 505 +#define bfd_mach_ppc_601 601 +#define bfd_mach_ppc_602 602 +#define bfd_mach_ppc_603 603 +#define bfd_mach_ppc_ec603e 6031 +#define bfd_mach_ppc_604 604 +#define bfd_mach_ppc_620 620 +#define bfd_mach_ppc_630 630 +#define bfd_mach_ppc_750 750 +#define bfd_mach_ppc_860 860 +#define bfd_mach_ppc_a35 35 +#define bfd_mach_ppc_rs64ii 642 +#define bfd_mach_ppc_rs64iii 643 +#define bfd_mach_ppc_7400 7400 + bfd_arch_rs6000, /* IBM RS/6000 */ + bfd_arch_hppa, /* HP PA RISC */ +#define bfd_mach_hppa10 10 +#define bfd_mach_hppa11 11 +#define bfd_mach_hppa20 20 +#define bfd_mach_hppa20w 25 + bfd_arch_d10v, /* Mitsubishi D10V */ + bfd_arch_z8k, /* Zilog Z8000 */ +#define bfd_mach_z8001 1 +#define bfd_mach_z8002 2 + bfd_arch_h8500, /* Hitachi H8/500 */ + bfd_arch_sh, /* Hitachi SH */ +#define bfd_mach_sh 1 +#define bfd_mach_sh2 0x20 +#define bfd_mach_sh_dsp 0x2d +#define bfd_mach_sh2a 0x2a +#define bfd_mach_sh2a_nofpu 0x2b +#define bfd_mach_sh2e 0x2e +#define bfd_mach_sh3 0x30 +#define bfd_mach_sh3_nommu 0x31 +#define bfd_mach_sh3_dsp 0x3d +#define bfd_mach_sh3e 0x3e +#define bfd_mach_sh4 0x40 +#define bfd_mach_sh4_nofpu 0x41 +#define bfd_mach_sh4_nommu_nofpu 0x42 +#define bfd_mach_sh4a 0x4a +#define bfd_mach_sh4a_nofpu 0x4b +#define bfd_mach_sh4al_dsp 0x4d +#define bfd_mach_sh5 0x50 + bfd_arch_alpha, /* Dec Alpha */ +#define bfd_mach_alpha 1 + bfd_arch_arm, /* Advanced Risc Machines ARM */ +#define bfd_mach_arm_unknown 0 +#define bfd_mach_arm_2 1 +#define bfd_mach_arm_2a 2 +#define bfd_mach_arm_3 3 +#define bfd_mach_arm_3M 4 +#define bfd_mach_arm_4 5 +#define bfd_mach_arm_4T 6 +#define bfd_mach_arm_5 7 +#define bfd_mach_arm_5T 8 +#define bfd_mach_arm_5TE 9 +#define bfd_mach_arm_XScale 10 +#define bfd_mach_arm_ep9312 11 +#define bfd_mach_arm_iWMMXt 12 +#define bfd_mach_arm_iWMMXt2 13 + bfd_arch_ns32k, /* National Semiconductors ns32000 */ + bfd_arch_w65, /* WDC 65816 */ + bfd_arch_tic30, /* Texas Instruments TMS320C30 */ + bfd_arch_v850, /* NEC V850 */ +#define bfd_mach_v850 0 + bfd_arch_arc, /* Argonaut RISC Core */ +#define bfd_mach_arc_base 0 + bfd_arch_m32r, /* Mitsubishi M32R/D */ +#define bfd_mach_m32r 0 /* backwards compatibility */ + bfd_arch_mn10200, /* Matsushita MN10200 */ + bfd_arch_mn10300, /* Matsushita MN10300 */ + bfd_arch_cris, /* Axis CRIS */ +#define bfd_mach_cris_v0_v10 255 +#define bfd_mach_cris_v32 32 +#define bfd_mach_cris_v10_v32 1032 + bfd_arch_microblaze, /* Xilinx MicroBlaze. */ + bfd_arch_ia64, /* HP/Intel ia64 */ +#define bfd_mach_ia64_elf64 64 +#define bfd_mach_ia64_elf32 32 + bfd_arch_last + }; +#define bfd_mach_s390_31 31 +#define bfd_mach_s390_64 64 + +typedef struct symbol_cache_entry +{ + const char *name; + union + { + PTR p; + bfd_vma i; + } udata; +} asymbol; + +enum dis_insn_type { + dis_noninsn, /* Not a valid instruction */ + dis_nonbranch, /* Not a branch instruction */ + dis_branch, /* Unconditional branch */ + dis_condbranch, /* Conditional branch */ + dis_jsr, /* Jump to subroutine */ + dis_condjsr, /* Conditional jump to subroutine */ + dis_dref, /* Data reference instruction */ + dis_dref2 /* Two data references in instruction */ +}; + +/* This struct is passed into the instruction decoding routine, + and is passed back out into each callback. The various fields are used + for conveying information from your main routine into your callbacks, + for passing information into the instruction decoders (such as the + addresses of the callback functions), or for passing information + back from the instruction decoders to their callers. + + It must be initialized before it is first passed; this can be done + by hand, or using one of the initialization macros below. */ + +typedef struct disassemble_info { + fprintf_function fprintf_func; + FILE *stream; + PTR application_data; + + /* Target description. We could replace this with a pointer to the bfd, + but that would require one. There currently isn't any such requirement + so to avoid introducing one we record these explicitly. */ + /* The bfd_flavour. This can be bfd_target_unknown_flavour. */ + enum bfd_flavour flavour; + /* The bfd_arch value. */ + enum bfd_architecture arch; + /* The bfd_mach value. */ + unsigned long mach; + /* Endianness (for bi-endian cpus). Mono-endian cpus can ignore this. */ + enum bfd_endian endian; + + /* An array of pointers to symbols either at the location being disassembled + or at the start of the function being disassembled. The array is sorted + so that the first symbol is intended to be the one used. The others are + present for any misc. purposes. This is not set reliably, but if it is + not NULL, it is correct. */ + asymbol **symbols; + /* Number of symbols in array. */ + int num_symbols; + + /* For use by the disassembler. + The top 16 bits are reserved for public use (and are documented here). + The bottom 16 bits are for the internal use of the disassembler. */ + unsigned long flags; +#define INSN_HAS_RELOC 0x80000000 + PTR private_data; + + /* Function used to get bytes to disassemble. MEMADDR is the + address of the stuff to be disassembled, MYADDR is the address to + put the bytes in, and LENGTH is the number of bytes to read. + INFO is a pointer to this struct. + Returns an errno value or 0 for success. */ + int (*read_memory_func) + (bfd_vma memaddr, bfd_byte *myaddr, int length, + struct disassemble_info *info); + + /* Function which should be called if we get an error that we can't + recover from. STATUS is the errno value from read_memory_func and + MEMADDR is the address that we were trying to read. INFO is a + pointer to this struct. */ + void (*memory_error_func) + (int status, bfd_vma memaddr, struct disassemble_info *info); + + /* Function called to print ADDR. */ + void (*print_address_func) + (bfd_vma addr, struct disassemble_info *info); + + /* Function called to determine if there is a symbol at the given ADDR. + If there is, the function returns 1, otherwise it returns 0. + This is used by ports which support an overlay manager where + the overlay number is held in the top part of an address. In + some circumstances we want to include the overlay number in the + address, (normally because there is a symbol associated with + that address), but sometimes we want to mask out the overlay bits. */ + int (* symbol_at_address_func) + (bfd_vma addr, struct disassemble_info * info); + + /* These are for buffer_read_memory. */ + bfd_byte *buffer; + bfd_vma buffer_vma; + int buffer_length; + + /* This variable may be set by the instruction decoder. It suggests + the number of bytes objdump should display on a single line. If + the instruction decoder sets this, it should always set it to + the same value in order to get reasonable looking output. */ + int bytes_per_line; + + /* the next two variables control the way objdump displays the raw data */ + /* For example, if bytes_per_line is 8 and bytes_per_chunk is 4, the */ + /* output will look like this: + 00: 00000000 00000000 + with the chunks displayed according to "display_endian". */ + int bytes_per_chunk; + enum bfd_endian display_endian; + + /* Results from instruction decoders. Not all decoders yet support + this information. This info is set each time an instruction is + decoded, and is only valid for the last such instruction. + + To determine whether this decoder supports this information, set + insn_info_valid to 0, decode an instruction, then check it. */ + + char insn_info_valid; /* Branch info has been set. */ + char branch_delay_insns; /* How many sequential insn's will run before + a branch takes effect. (0 = normal) */ + char data_size; /* Size of data reference in insn, in bytes */ + enum dis_insn_type insn_type; /* Type of instruction */ + bfd_vma target; /* Target address of branch or dref, if known; + zero if unknown. */ + bfd_vma target2; /* Second target address for dref2 */ + + /* Command line options specific to the target disassembler. */ + char * disassembler_options; + +} disassemble_info; + + +/* Standard disassemblers. Disassemble one instruction at the given + target address. Return number of bytes processed. */ +typedef int (*disassembler_ftype) (bfd_vma, disassemble_info *); + +int print_insn_big_mips (bfd_vma, disassemble_info*); +int print_insn_little_mips (bfd_vma, disassemble_info*); +int print_insn_i386 (bfd_vma, disassemble_info*); +int print_insn_m68k (bfd_vma, disassemble_info*); +int print_insn_z8001 (bfd_vma, disassemble_info*); +int print_insn_z8002 (bfd_vma, disassemble_info*); +int print_insn_h8300 (bfd_vma, disassemble_info*); +int print_insn_h8300h (bfd_vma, disassemble_info*); +int print_insn_h8300s (bfd_vma, disassemble_info*); +int print_insn_h8500 (bfd_vma, disassemble_info*); +int print_insn_alpha (bfd_vma, disassemble_info*); +disassembler_ftype arc_get_disassembler (int, int); +int print_insn_arm (bfd_vma, disassemble_info*); +int print_insn_sparc (bfd_vma, disassemble_info*); +int print_insn_big_a29k (bfd_vma, disassemble_info*); +int print_insn_little_a29k (bfd_vma, disassemble_info*); +int print_insn_i960 (bfd_vma, disassemble_info*); +int print_insn_sh (bfd_vma, disassemble_info*); +int print_insn_shl (bfd_vma, disassemble_info*); +int print_insn_hppa (bfd_vma, disassemble_info*); +int print_insn_m32r (bfd_vma, disassemble_info*); +int print_insn_m88k (bfd_vma, disassemble_info*); +int print_insn_mn10200 (bfd_vma, disassemble_info*); +int print_insn_mn10300 (bfd_vma, disassemble_info*); +int print_insn_ns32k (bfd_vma, disassemble_info*); +int print_insn_big_powerpc (bfd_vma, disassemble_info*); +int print_insn_little_powerpc (bfd_vma, disassemble_info*); +int print_insn_rs6000 (bfd_vma, disassemble_info*); +int print_insn_w65 (bfd_vma, disassemble_info*); +int print_insn_d10v (bfd_vma, disassemble_info*); +int print_insn_v850 (bfd_vma, disassemble_info*); +int print_insn_tic30 (bfd_vma, disassemble_info*); +int print_insn_ppc (bfd_vma, disassemble_info*); +int print_insn_s390 (bfd_vma, disassemble_info*); +int print_insn_crisv32 (bfd_vma, disassemble_info*); +int print_insn_crisv10 (bfd_vma, disassemble_info*); +int print_insn_microblaze (bfd_vma, disassemble_info*); +int print_insn_ia64 (bfd_vma, disassemble_info*); + +#if 0 +/* Fetch the disassembler for a given BFD, if that support is available. */ +disassembler_ftype disassembler(bfd *); +#endif + + +/* This block of definitions is for particular callers who read instructions + into a buffer before calling the instruction decoder. */ + +/* Here is a function which callers may wish to use for read_memory_func. + It gets bytes from a buffer. */ +int buffer_read_memory(bfd_vma, bfd_byte *, int, struct disassemble_info *); + +/* This function goes with buffer_read_memory. + It prints a message using info->fprintf_func and info->stream. */ +void perror_memory(int, bfd_vma, struct disassemble_info *); + + +/* Just print the address in hex. This is included for completeness even + though both GDB and objdump provide their own (to print symbolic + addresses). */ +void generic_print_address(bfd_vma, struct disassemble_info *); + +/* Always true. */ +int generic_symbol_at_address(bfd_vma, struct disassemble_info *); + +/* Macro to initialize a disassemble_info struct. This should be called + by all applications creating such a struct. */ +#define INIT_DISASSEMBLE_INFO(INFO, STREAM, FPRINTF_FUNC) \ + (INFO).flavour = bfd_target_unknown_flavour, \ + (INFO).arch = bfd_arch_unknown, \ + (INFO).mach = 0, \ + (INFO).endian = BFD_ENDIAN_UNKNOWN, \ + INIT_DISASSEMBLE_INFO_NO_ARCH(INFO, STREAM, FPRINTF_FUNC) + +/* Call this macro to initialize only the internal variables for the + disassembler. Architecture dependent things such as byte order, or machine + variant are not touched by this macro. This makes things much easier for + GDB which must initialize these things separately. */ + +#define INIT_DISASSEMBLE_INFO_NO_ARCH(INFO, STREAM, FPRINTF_FUNC) \ + (INFO).fprintf_func = (FPRINTF_FUNC), \ + (INFO).stream = (STREAM), \ + (INFO).symbols = NULL, \ + (INFO).num_symbols = 0, \ + (INFO).private_data = NULL, \ + (INFO).buffer = NULL, \ + (INFO).buffer_vma = 0, \ + (INFO).buffer_length = 0, \ + (INFO).read_memory_func = buffer_read_memory, \ + (INFO).memory_error_func = perror_memory, \ + (INFO).print_address_func = generic_print_address, \ + (INFO).symbol_at_address_func = generic_symbol_at_address, \ + (INFO).flags = 0, \ + (INFO).bytes_per_line = 0, \ + (INFO).bytes_per_chunk = 0, \ + (INFO).display_endian = BFD_ENDIAN_UNKNOWN, \ + (INFO).disassembler_options = NULL, \ + (INFO).insn_info_valid = 0 + +#define _(x) x +#define ATTRIBUTE_UNUSED __attribute__((unused)) + +/* from libbfd */ + +bfd_vma bfd_getl64 (const bfd_byte *addr); +bfd_vma bfd_getl32 (const bfd_byte *addr); +bfd_vma bfd_getb32 (const bfd_byte *addr); +bfd_vma bfd_getl16 (const bfd_byte *addr); +bfd_vma bfd_getb16 (const bfd_byte *addr); +typedef bool bfd_boolean; + +#endif /* ! defined (DIS_ASM_H) */ diff --git a/disas.c b/disas.c new file mode 100644 index 0000000..c76f36f --- /dev/null +++ b/disas.c @@ -0,0 +1,432 @@ +/* General "disassemble this chunk" code. Used for debugging. */ +#include "config.h" +#include "dis-asm.h" +#include "elf.h" +#include + +#include "cpu.h" +#include "exec-all.h" +#include "disas.h" + +/* Filled in by elfload.c. Simplistic, but will do for now. */ +struct syminfo *syminfos = NULL; + +/* Get LENGTH bytes from info's buffer, at target address memaddr. + Transfer them to myaddr. */ +int +buffer_read_memory(bfd_vma memaddr, bfd_byte *myaddr, int length, + struct disassemble_info *info) +{ + if (memaddr < info->buffer_vma + || memaddr + length > info->buffer_vma + info->buffer_length) + /* Out of bounds. Use EIO because GDB uses it. */ + return EIO; + memcpy (myaddr, info->buffer + (memaddr - info->buffer_vma), length); + return 0; +} + +/* Get LENGTH bytes from info's buffer, at target address memaddr. + Transfer them to myaddr. */ +static int +target_read_memory (bfd_vma memaddr, + bfd_byte *myaddr, + int length, + struct disassemble_info *info) +{ + cpu_memory_rw_debug(cpu_single_env, memaddr, myaddr, length, 0); + return 0; +} + +/* Print an error message. We can assume that this is in response to + an error return from buffer_read_memory. */ +void +perror_memory (int status, bfd_vma memaddr, struct disassemble_info *info) +{ + if (status != EIO) + /* Can't happen. */ + (*info->fprintf_func) (info->stream, "Unknown error %d\n", status); + else + /* Actually, address between memaddr and memaddr + len was + out of bounds. */ + (*info->fprintf_func) (info->stream, + "Address 0x%" PRIx64 " is out of bounds.\n", memaddr); +} + +/* This could be in a separate file, to save miniscule amounts of space + in statically linked executables. */ + +/* Just print the address is hex. This is included for completeness even + though both GDB and objdump provide their own (to print symbolic + addresses). */ + +void +generic_print_address (bfd_vma addr, struct disassemble_info *info) +{ + (*info->fprintf_func) (info->stream, "0x%" PRIx64, addr); +} + +/* Just return the given address. */ + +int +generic_symbol_at_address (bfd_vma addr, struct disassemble_info *info) +{ + return 1; +} + +bfd_vma bfd_getl64 (const bfd_byte *addr) +{ + unsigned long long v; + + v = (unsigned long long) addr[0]; + v |= (unsigned long long) addr[1] << 8; + v |= (unsigned long long) addr[2] << 16; + v |= (unsigned long long) addr[3] << 24; + v |= (unsigned long long) addr[4] << 32; + v |= (unsigned long long) addr[5] << 40; + v |= (unsigned long long) addr[6] << 48; + v |= (unsigned long long) addr[7] << 56; + return (bfd_vma) v; +} + +bfd_vma bfd_getl32 (const bfd_byte *addr) +{ + unsigned long v; + + v = (unsigned long) addr[0]; + v |= (unsigned long) addr[1] << 8; + v |= (unsigned long) addr[2] << 16; + v |= (unsigned long) addr[3] << 24; + return (bfd_vma) v; +} + +bfd_vma bfd_getb32 (const bfd_byte *addr) +{ + unsigned long v; + + v = (unsigned long) addr[0] << 24; + v |= (unsigned long) addr[1] << 16; + v |= (unsigned long) addr[2] << 8; + v |= (unsigned long) addr[3]; + return (bfd_vma) v; +} + +bfd_vma bfd_getl16 (const bfd_byte *addr) +{ + unsigned long v; + + v = (unsigned long) addr[0]; + v |= (unsigned long) addr[1] << 8; + return (bfd_vma) v; +} + +bfd_vma bfd_getb16 (const bfd_byte *addr) +{ + unsigned long v; + + v = (unsigned long) addr[0] << 24; + v |= (unsigned long) addr[1] << 16; + return (bfd_vma) v; +} + +#ifdef TARGET_ARM +static int +print_insn_thumb1(bfd_vma pc, disassemble_info *info) +{ + return print_insn_arm(pc | 1, info); +} +#endif + +/* Disassemble this for me please... (debugging). 'flags' has the following + values: + i386 - nonzero means 16 bit code + arm - nonzero means thumb code + ppc - nonzero means little endian + other targets - unused + */ +void target_disas(FILE *out, target_ulong code, target_ulong size, int flags) +{ + target_ulong pc; + int count; + struct disassemble_info disasm_info; + int (*print_insn)(bfd_vma pc, disassemble_info *info); + + INIT_DISASSEMBLE_INFO(disasm_info, out, fprintf); + + disasm_info.read_memory_func = target_read_memory; + disasm_info.buffer_vma = code; + disasm_info.buffer_length = size; + +#ifdef TARGET_WORDS_BIGENDIAN + disasm_info.endian = BFD_ENDIAN_BIG; +#else + disasm_info.endian = BFD_ENDIAN_LITTLE; +#endif +#if defined(TARGET_I386) + if (flags == 2) + disasm_info.mach = bfd_mach_x86_64; + else if (flags == 1) + disasm_info.mach = bfd_mach_i386_i8086; + else + disasm_info.mach = bfd_mach_i386_i386; + print_insn = print_insn_i386; +#elif defined(TARGET_ARM) + if (flags) + print_insn = print_insn_thumb1; + else + print_insn = print_insn_arm; +#elif defined(TARGET_SPARC) + print_insn = print_insn_sparc; +#ifdef TARGET_SPARC64 + disasm_info.mach = bfd_mach_sparc_v9b; +#endif +#elif defined(TARGET_PPC) + if (flags >> 16) + disasm_info.endian = BFD_ENDIAN_LITTLE; + if (flags & 0xFFFF) { + /* If we have a precise definitions of the instructions set, use it */ + disasm_info.mach = flags & 0xFFFF; + } else { +#ifdef TARGET_PPC64 + disasm_info.mach = bfd_mach_ppc64; +#else + disasm_info.mach = bfd_mach_ppc; +#endif + } + print_insn = print_insn_ppc; +#elif defined(TARGET_M68K) + print_insn = print_insn_m68k; +#elif defined(TARGET_MIPS) +#ifdef TARGET_WORDS_BIGENDIAN + print_insn = print_insn_big_mips; +#else + print_insn = print_insn_little_mips; +#endif +#elif defined(TARGET_SH4) + disasm_info.mach = bfd_mach_sh4; + print_insn = print_insn_sh; +#elif defined(TARGET_ALPHA) + disasm_info.mach = bfd_mach_alpha; + print_insn = print_insn_alpha; +#elif defined(TARGET_CRIS) + if (flags != 32) { + disasm_info.mach = bfd_mach_cris_v0_v10; + print_insn = print_insn_crisv10; + } else { + disasm_info.mach = bfd_mach_cris_v32; + print_insn = print_insn_crisv32; + } +#elif defined(TARGET_MICROBLAZE) + disasm_info.mach = bfd_arch_microblaze; + print_insn = print_insn_microblaze; +#else + fprintf(out, "0x" TARGET_FMT_lx + ": Asm output not supported on this arch\n", code); + return; +#endif + + for (pc = code; size > 0; pc += count, size -= count) { + fprintf(out, "0x" TARGET_FMT_lx ": ", pc); + count = print_insn(pc, &disasm_info); +#if 0 + { + int i; + uint8_t b; + fprintf(out, " {"); + for(i = 0; i < count; i++) { + target_read_memory(pc + i, &b, 1, &disasm_info); + fprintf(out, " %02x", b); + } + fprintf(out, " }"); + } +#endif + fprintf(out, "\n"); + if (count < 0) + break; + if (size < count) { + fprintf(out, + "Disassembler disagrees with translator over instruction " + "decoding\n" + "Please report this to qemu-devel@nongnu.org\n"); + break; + } + } +} + +/* Disassemble this for me please... (debugging). */ +void disas(FILE *out, void *code, unsigned long size) +{ + unsigned long pc; + int count; + struct disassemble_info disasm_info; + int (*print_insn)(bfd_vma pc, disassemble_info *info); + + INIT_DISASSEMBLE_INFO(disasm_info, out, fprintf); + + disasm_info.buffer = code; + disasm_info.buffer_vma = (unsigned long)code; + disasm_info.buffer_length = size; + +#ifdef HOST_WORDS_BIGENDIAN + disasm_info.endian = BFD_ENDIAN_BIG; +#else + disasm_info.endian = BFD_ENDIAN_LITTLE; +#endif +#if defined(__i386__) + disasm_info.mach = bfd_mach_i386_i386; + print_insn = print_insn_i386; +#elif defined(__x86_64__) + disasm_info.mach = bfd_mach_x86_64; + print_insn = print_insn_i386; +#elif defined(_ARCH_PPC) + print_insn = print_insn_ppc; +#elif defined(__alpha__) + print_insn = print_insn_alpha; +#elif defined(__sparc__) + print_insn = print_insn_sparc; +#if defined(__sparc_v8plus__) || defined(__sparc_v8plusa__) || defined(__sparc_v9__) + disasm_info.mach = bfd_mach_sparc_v9b; +#endif +#elif defined(__arm__) + print_insn = print_insn_arm; +#elif defined(__MIPSEB__) + print_insn = print_insn_big_mips; +#elif defined(__MIPSEL__) + print_insn = print_insn_little_mips; +#elif defined(__m68k__) + print_insn = print_insn_m68k; +#elif defined(__s390__) + print_insn = print_insn_s390; +#elif defined(__hppa__) + print_insn = print_insn_hppa; +#elif defined(__ia64__) + print_insn = print_insn_ia64; +#else + fprintf(out, "0x%lx: Asm output not supported on this arch\n", + (long) code); + return; +#endif + for (pc = (unsigned long)code; size > 0; pc += count, size -= count) { + fprintf(out, "0x%08lx: ", pc); + count = print_insn(pc, &disasm_info); + fprintf(out, "\n"); + if (count < 0) + break; + } +} + +/* Look up symbol for debugging purpose. Returns "" if unknown. */ +const char *lookup_symbol(target_ulong orig_addr) +{ + const char *symbol = ""; + struct syminfo *s; + + for (s = syminfos; s; s = s->next) { + symbol = s->lookup_symbol(s, orig_addr); + if (symbol[0] != '\0') { + break; + } + } + + return symbol; +} + +#if !defined(CONFIG_USER_ONLY) + +#include "monitor.h" + +static int monitor_disas_is_physical; +static CPUState *monitor_disas_env; + +static int +monitor_read_memory (bfd_vma memaddr, bfd_byte *myaddr, int length, + struct disassemble_info *info) +{ + if (monitor_disas_is_physical) { + cpu_physical_memory_rw(memaddr, myaddr, length, 0); + } else { + cpu_memory_rw_debug(monitor_disas_env, memaddr,myaddr, length, 0); + } + return 0; +} + +static int GCC_FMT_ATTR(2, 3) +monitor_fprintf(FILE *stream, const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + monitor_vprintf((Monitor *)stream, fmt, ap); + va_end(ap); + return 0; +} + +void monitor_disas(Monitor *mon, CPUState *env, + target_ulong pc, int nb_insn, int is_physical, int flags) +{ + int count, i; + struct disassemble_info disasm_info; + int (*print_insn)(bfd_vma pc, disassemble_info *info); + + INIT_DISASSEMBLE_INFO(disasm_info, (FILE *)mon, monitor_fprintf); + + monitor_disas_env = env; + monitor_disas_is_physical = is_physical; + disasm_info.read_memory_func = monitor_read_memory; + + disasm_info.buffer_vma = pc; + +#ifdef TARGET_WORDS_BIGENDIAN + disasm_info.endian = BFD_ENDIAN_BIG; +#else + disasm_info.endian = BFD_ENDIAN_LITTLE; +#endif +#if defined(TARGET_I386) + if (flags == 2) + disasm_info.mach = bfd_mach_x86_64; + else if (flags == 1) + disasm_info.mach = bfd_mach_i386_i8086; + else + disasm_info.mach = bfd_mach_i386_i386; + print_insn = print_insn_i386; +#elif defined(TARGET_ARM) + print_insn = print_insn_arm; +#elif defined(TARGET_ALPHA) + print_insn = print_insn_alpha; +#elif defined(TARGET_SPARC) + print_insn = print_insn_sparc; +#ifdef TARGET_SPARC64 + disasm_info.mach = bfd_mach_sparc_v9b; +#endif +#elif defined(TARGET_PPC) +#ifdef TARGET_PPC64 + disasm_info.mach = bfd_mach_ppc64; +#else + disasm_info.mach = bfd_mach_ppc; +#endif + print_insn = print_insn_ppc; +#elif defined(TARGET_M68K) + print_insn = print_insn_m68k; +#elif defined(TARGET_MIPS) +#ifdef TARGET_WORDS_BIGENDIAN + print_insn = print_insn_big_mips; +#else + print_insn = print_insn_little_mips; +#endif +#elif defined(TARGET_SH4) + disasm_info.mach = bfd_mach_sh4; + print_insn = print_insn_sh; +#else + monitor_printf(mon, "0x" TARGET_FMT_lx + ": Asm output not supported on this arch\n", pc); + return; +#endif + + for(i = 0; i < nb_insn; i++) { + monitor_printf(mon, "0x" TARGET_FMT_lx ": ", pc); + count = print_insn(pc, &disasm_info); + monitor_printf(mon, "\n"); + if (count < 0) + break; + pc += count; + } +} +#endif diff --git a/disas.h b/disas.h new file mode 100644 index 0000000..f9287f7 --- /dev/null +++ b/disas.h @@ -0,0 +1,42 @@ +#ifndef _QEMU_DISAS_H +#define _QEMU_DISAS_H + +#include "qemu-common.h" + +#ifdef NEED_CPU_H +/* Disassemble this for me please... (debugging). */ +void disas(FILE *out, void *code, unsigned long size); +void target_disas(FILE *out, target_ulong code, target_ulong size, int flags); + +void monitor_disas(Monitor *mon, CPUState *env, + target_ulong pc, int nb_insn, int is_physical, int flags); + +/* Look up symbol for debugging purpose. Returns "" if unknown. */ +const char *lookup_symbol(target_ulong orig_addr); +#endif + +struct syminfo; +struct elf32_sym; +struct elf64_sym; + +#if defined(CONFIG_USER_ONLY) +typedef const char *(*lookup_symbol_t)(struct syminfo *s, target_ulong orig_addr); +#else +typedef const char *(*lookup_symbol_t)(struct syminfo *s, target_phys_addr_t orig_addr); +#endif + +struct syminfo { + lookup_symbol_t lookup_symbol; + unsigned int disas_num_syms; + union { + struct elf32_sym *elf32; + struct elf64_sym *elf64; + } disas_symtab; + const char *disas_strtab; + struct syminfo *next; +}; + +/* Filled in by elfload.c. Simplistic, but will do for now. */ +extern struct syminfo *syminfos; + +#endif /* _QEMU_DISAS_H */ diff --git a/dma-helpers.c b/dma-helpers.c new file mode 100644 index 0000000..712ed89 --- /dev/null +++ b/dma-helpers.c @@ -0,0 +1,184 @@ +/* + * DMA helper functions + * + * Copyright (c) 2009 Red Hat + * + * This work is licensed under the terms of the GNU General Public License + * (GNU GPL), version 2 or later. + */ + +#include "dma.h" +#include "block_int.h" + +void qemu_sglist_init(QEMUSGList *qsg, int alloc_hint) +{ + qsg->sg = qemu_malloc(alloc_hint * sizeof(ScatterGatherEntry)); + qsg->nsg = 0; + qsg->nalloc = alloc_hint; + qsg->size = 0; +} + +void qemu_sglist_add(QEMUSGList *qsg, target_phys_addr_t base, + target_phys_addr_t len) +{ + if (qsg->nsg == qsg->nalloc) { + qsg->nalloc = 2 * qsg->nalloc + 1; + qsg->sg = qemu_realloc(qsg->sg, qsg->nalloc * sizeof(ScatterGatherEntry)); + } + qsg->sg[qsg->nsg].base = base; + qsg->sg[qsg->nsg].len = len; + qsg->size += len; + ++qsg->nsg; +} + +void qemu_sglist_destroy(QEMUSGList *qsg) +{ + qemu_free(qsg->sg); +} + +typedef struct { + BlockDriverAIOCB common; + BlockDriverState *bs; + BlockDriverAIOCB *acb; + QEMUSGList *sg; + uint64_t sector_num; + int is_write; + int sg_cur_index; + target_phys_addr_t sg_cur_byte; + QEMUIOVector iov; + QEMUBH *bh; +} DMAAIOCB; + +static void dma_bdrv_cb(void *opaque, int ret); + +static void reschedule_dma(void *opaque) +{ + DMAAIOCB *dbs = (DMAAIOCB *)opaque; + + qemu_bh_delete(dbs->bh); + dbs->bh = NULL; + dma_bdrv_cb(opaque, 0); +} + +static void continue_after_map_failure(void *opaque) +{ + DMAAIOCB *dbs = (DMAAIOCB *)opaque; + + dbs->bh = qemu_bh_new(reschedule_dma, dbs); + qemu_bh_schedule(dbs->bh); +} + +static void dma_bdrv_unmap(DMAAIOCB *dbs) +{ + int i; + + for (i = 0; i < dbs->iov.niov; ++i) { + cpu_physical_memory_unmap(dbs->iov.iov[i].iov_base, + dbs->iov.iov[i].iov_len, !dbs->is_write, + dbs->iov.iov[i].iov_len); + } +} + +static void dma_bdrv_cb(void *opaque, int ret) +{ + DMAAIOCB *dbs = (DMAAIOCB *)opaque; + target_phys_addr_t cur_addr, cur_len; + void *mem; + + dbs->acb = NULL; + dbs->sector_num += dbs->iov.size / 512; + dma_bdrv_unmap(dbs); + qemu_iovec_reset(&dbs->iov); + + if (dbs->sg_cur_index == dbs->sg->nsg || ret < 0) { + dbs->common.cb(dbs->common.opaque, ret); + qemu_iovec_destroy(&dbs->iov); + qemu_aio_release(dbs); + return; + } + + while (dbs->sg_cur_index < dbs->sg->nsg) { + cur_addr = dbs->sg->sg[dbs->sg_cur_index].base + dbs->sg_cur_byte; + cur_len = dbs->sg->sg[dbs->sg_cur_index].len - dbs->sg_cur_byte; + mem = cpu_physical_memory_map(cur_addr, &cur_len, !dbs->is_write); + if (!mem) + break; + qemu_iovec_add(&dbs->iov, mem, cur_len); + dbs->sg_cur_byte += cur_len; + if (dbs->sg_cur_byte == dbs->sg->sg[dbs->sg_cur_index].len) { + dbs->sg_cur_byte = 0; + ++dbs->sg_cur_index; + } + } + + if (dbs->iov.size == 0) { + cpu_register_map_client(dbs, continue_after_map_failure); + return; + } + + if (dbs->is_write) { + dbs->acb = bdrv_aio_writev(dbs->bs, dbs->sector_num, &dbs->iov, + dbs->iov.size / 512, dma_bdrv_cb, dbs); + } else { + dbs->acb = bdrv_aio_readv(dbs->bs, dbs->sector_num, &dbs->iov, + dbs->iov.size / 512, dma_bdrv_cb, dbs); + } + if (!dbs->acb) { + dma_bdrv_unmap(dbs); + qemu_iovec_destroy(&dbs->iov); + return; + } +} + +static void dma_aio_cancel(BlockDriverAIOCB *acb) +{ + DMAAIOCB *dbs = container_of(acb, DMAAIOCB, common); + + if (dbs->acb) { + bdrv_aio_cancel(dbs->acb); + } +} + +static AIOPool dma_aio_pool = { + .aiocb_size = sizeof(DMAAIOCB), + .cancel = dma_aio_cancel, +}; + +static BlockDriverAIOCB *dma_bdrv_io( + BlockDriverState *bs, QEMUSGList *sg, uint64_t sector_num, + BlockDriverCompletionFunc *cb, void *opaque, + int is_write) +{ + DMAAIOCB *dbs = qemu_aio_get(&dma_aio_pool, bs, cb, opaque); + + dbs->acb = NULL; + dbs->bs = bs; + dbs->sg = sg; + dbs->sector_num = sector_num; + dbs->sg_cur_index = 0; + dbs->sg_cur_byte = 0; + dbs->is_write = is_write; + dbs->bh = NULL; + qemu_iovec_init(&dbs->iov, sg->nsg); + dma_bdrv_cb(dbs, 0); + if (!dbs->acb) { + qemu_aio_release(dbs); + return NULL; + } + return &dbs->common; +} + + +BlockDriverAIOCB *dma_bdrv_read(BlockDriverState *bs, + QEMUSGList *sg, uint64_t sector, + void (*cb)(void *opaque, int ret), void *opaque) +{ + return dma_bdrv_io(bs, sg, sector, cb, opaque, 0); +} + +BlockDriverAIOCB *dma_bdrv_write(BlockDriverState *bs, + QEMUSGList *sg, uint64_t sector, + void (*cb)(void *opaque, int ret), void *opaque) +{ + return dma_bdrv_io(bs, sg, sector, cb, opaque, 1); +} diff --git a/dma.h b/dma.h new file mode 100644 index 0000000..f3bb275 --- /dev/null +++ b/dma.h @@ -0,0 +1,41 @@ +/* + * DMA helper functions + * + * Copyright (c) 2009 Red Hat + * + * This work is licensed under the terms of the GNU General Public License + * (GNU GPL), version 2 or later. + */ + +#ifndef DMA_H +#define DMA_H + +#include +//#include "cpu.h" +#include "hw/hw.h" +#include "block.h" + +typedef struct { + target_phys_addr_t base; + target_phys_addr_t len; +} ScatterGatherEntry; + +typedef struct { + ScatterGatherEntry *sg; + int nsg; + int nalloc; + target_phys_addr_t size; +} QEMUSGList; + +void qemu_sglist_init(QEMUSGList *qsg, int alloc_hint); +void qemu_sglist_add(QEMUSGList *qsg, target_phys_addr_t base, + target_phys_addr_t len); +void qemu_sglist_destroy(QEMUSGList *qsg); + +BlockDriverAIOCB *dma_bdrv_read(BlockDriverState *bs, + QEMUSGList *sg, uint64_t sector, + BlockDriverCompletionFunc *cb, void *opaque); +BlockDriverAIOCB *dma_bdrv_write(BlockDriverState *bs, + QEMUSGList *sg, uint64_t sector, + BlockDriverCompletionFunc *cb, void *opaque); +#endif diff --git a/docs/blkverify.txt b/docs/blkverify.txt new file mode 100644 index 0000000..d556dc4 --- /dev/null +++ b/docs/blkverify.txt @@ -0,0 +1,69 @@ += Block driver correctness testing with blkverify = + +== Introduction == + +This document describes how to use the blkverify protocol to test that a block +driver is operating correctly. + +It is difficult to test and debug block drivers against real guests. Often +processes inside the guest will crash because corrupt sectors were read as part +of the executable. Other times obscure errors are raised by a program inside +the guest. These issues are extremely hard to trace back to bugs in the block +driver. + +Blkverify solves this problem by catching data corruption inside QEMU the first +time bad data is read and reporting the disk sector that is corrupted. + +== How it works == + +The blkverify protocol has two child block devices, the "test" device and the +"raw" device. Read/write operations are mirrored to both devices so their +state should always be in sync. + +The "raw" device is a raw image, a flat file, that has identical starting +contents to the "test" image. The idea is that the "raw" device will handle +read/write operations correctly and not corrupt data. It can be used as a +reference for comparison against the "test" device. + +After a mirrored read operation completes, blkverify will compare the data and +raise an error if it is not identical. This makes it possible to catch the +first instance where corrupt data is read. + +== Example == + +Imagine raw.img has 0xcd repeated throughout its first sector: + + $ ./qemu-io -c 'read -v 0 512' raw.img + 00000000: cd cd cd cd cd cd cd cd cd cd cd cd cd cd cd cd ................ + 00000010: cd cd cd cd cd cd cd cd cd cd cd cd cd cd cd cd ................ + [...] + 000001e0: cd cd cd cd cd cd cd cd cd cd cd cd cd cd cd cd ................ + 000001f0: cd cd cd cd cd cd cd cd cd cd cd cd cd cd cd cd ................ + read 512/512 bytes at offset 0 + 512.000000 bytes, 1 ops; 0.0000 sec (97.656 MiB/sec and 200000.0000 ops/sec) + +And test.img is corrupt, its first sector is zeroed when it shouldn't be: + + $ ./qemu-io -c 'read -v 0 512' test.img + 00000000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ + 00000010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ + [...] + 000001e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ + 000001f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ + read 512/512 bytes at offset 0 + 512.000000 bytes, 1 ops; 0.0000 sec (81.380 MiB/sec and 166666.6667 ops/sec) + +This error is caught by blkverify: + + $ ./qemu-io -c 'read 0 512' blkverify:a.img:b.img + blkverify: read sector_num=0 nb_sectors=4 contents mismatch in sector 0 + +A more realistic scenario is verifying the installation of a guest OS: + + $ ./qemu-img create raw.img 16G + $ ./qemu-img create -f qcow2 test.qcow2 16G + $ x86_64-softmmu/qemu-system-x86_64 -cdrom debian.iso \ + -drive file=blkverify:raw.img:test.qcow2 + +If the installation is aborted when blkverify detects corruption, use qemu-io +to explore the contents of the disk image at the sector in question. diff --git a/docs/bootindex.txt b/docs/bootindex.txt new file mode 100644 index 0000000..16083b3 --- /dev/null +++ b/docs/bootindex.txt @@ -0,0 +1,43 @@ += Bootindex propery = + +Block and net devices have bootindex property. This property is used to +determine the order in which firmware will consider devices for booting +the guest OS. If the bootindex property is not set for a device, it gets +lowest boot priority. There is no particular order in which devices with +unset bootindex property will be considered for booting, but they will +still be bootable. + +== Example == + +Lets assume we have QEMU machine with two NICs (virtio, e1000) and two +disks (IDE, virtio): + +qemu -drive file=disk1.img,if=none,id=disk1 + -device ide-drive,drive=disk1,bootindex=4 + -drive file=disk2.img,if=none,id=disk2 + -device virtio-blk-pci,drive=disk2,bootindex=3 + -netdev type=user,id=net0 -device virtio-net-pci,netdev=net0,bootindex=2 + -netdev type=user,id=net1 -device e1000,netdev=net1,bootindex=1 + +Given the command above, firmware should try to boot from the e1000 NIC +first. If this fails, it should try the virtio NIC next, if this fails +too, it should try the virtio disk, and then the IDE disk. + +== Limitations == + +1. Some firmware has limitations on which devices can be considered for +booting. For instance, the PC BIOS boot specification allows only one +disk to be bootable. If boot from disk fails for some reason, the BIOS +won't retry booting from other disk. It still can try to boot from +floppy or net, though. + +2. Sometimes, firmware cannot map the device path QEMU wants firmware to +boot from to a boot method. It doesn't happen for devices the firmware +can natively boot from, but if firmware relies on an option ROM for +booting, and the same option ROM is used for booting from more then one +device, the firmware may not be able to ask the option ROM to boot from +a particular device reliably. For instance with PC BIOS, if a SCSI HBA +has three bootable devices target1, target3, target5 connected to it, +the option ROM will have a boot method for each of them, but it is not +possible to map from boot method back to a specific target. This is a +shortcoming of PC BIOS boot specification. diff --git a/docs/migration.txt b/docs/migration.txt new file mode 100644 index 0000000..4848c1e --- /dev/null +++ b/docs/migration.txt @@ -0,0 +1,303 @@ += Migration = + +QEMU has code to load/save the state of the guest that it is running. +These are two complementary operations. Saving the state just does +that, saves the state for each device that the guest is running. +Restoring a guest is just the opposite operation: we need to load the +state of each device. + +For this to work, QEMU has to be launched with the same arguments the +two times. I.e. it can only restore the state in one guest that has +the same devices that the one it was saved (this last requirement can +be relaxed a bit, but for now we can consider that configuration has +to be exactly the same). + +Once that we are able to save/restore a guest, a new functionality is +requested: migration. This means that QEMU is able to start in one +machine and being "migrated" to another machine. I.e. being moved to +another machine. + +Next was the "live migration" functionality. This is important +because some guests run with a lot of state (specially RAM), and it +can take a while to move all state from one machine to another. Live +migration allows the guest to continue running while the state is +transferred. Only while the last part of the state is transferred has +the guest to be stopped. Typically the time that the guest is +unresponsive during live migration is the low hundred of milliseconds +(notice that this depends on a lot of things). + +=== Types of migration === + +Now that we have talked about live migration, there are several ways +to do migration: + +- tcp migration: do the migration using tcp sockets +- unix migration: do the migration using unix sockets +- exec migration: do the migration using the stdin/stdout through a process. +- fd migration: do the migration using an file descriptor that is + passed to QEMU. QEMU doesn't care how this file descriptor is opened. + +All these four migration protocols use the same infrastructure to +save/restore state devices. This infrastructure is shared with the +savevm/loadvm functionality. + +=== State Live Migration == + +This is used for RAM and block devices. It is not yet ported to vmstate. + + +=== What is the common infrastructure === + +QEMU uses a QEMUFile abstraction to be able to do migration. Any type +of migration that wants to use QEMU infrastructure has to create a +QEMUFile with: + +QEMUFile *qemu_fopen_ops(void *opaque, + QEMUFilePutBufferFunc *put_buffer, + QEMUFileGetBufferFunc *get_buffer, + QEMUFileCloseFunc *close, + QEMUFileRateLimit *rate_limit, + QEMUFileSetRateLimit *set_rate_limit, + QEMUFileGetRateLimit *get_rate_limit); + +The functions have the following functionality: + +This function writes a chunk of data to a file at the given position. +The pos argument can be ignored if the file is only used for +streaming. The handler should try to write all of the data it can. + +typedef int (QEMUFilePutBufferFunc)(void *opaque, const uint8_t *buf, + int64_t pos, int size); + +Read a chunk of data from a file at the given position. The pos argument +can be ignored if the file is only be used for streaming. The number of +bytes actually read should be returned. + +typedef int (QEMUFileGetBufferFunc)(void *opaque, uint8_t *buf, + int64_t pos, int size); + +Close a file and return an error code. + +typedef int (QEMUFileCloseFunc)(void *opaque); + +Called to determine if the file has exceeded its bandwidth allocation. The +bandwidth capping is a soft limit, not a hard limit. + +typedef int (QEMUFileRateLimit)(void *opaque); + +Called to change the current bandwidth allocation. This function must return +the new actual bandwidth. It should be new_rate if everything goes OK, and +the old rate otherwise. + +typedef size_t (QEMUFileSetRateLimit)(void *opaque, size_t new_rate); +typedef size_t (QEMUFileGetRateLimit)(void *opaque); + +You can use any internal state that you need using the opaque void * +pointer that is passed to all functions. + +The rate limiting functions are used to limit the bandwidth used by +QEMU migration. + +The important functions for us are put_buffer()/get_buffer() that +allow to write/read a buffer into the QEMUFile. + +=== How to save the state of one device == + +The state of a device is saved using intermediate buffers. There are +some helper functions to assist this saving. + +There is a new concept that we have to explain here: device state +version. When we migrate a device, we save/load the state as a series +of fields. Some times, due to bugs or new functionality, we need to +change the state to store more/different information. We use the +version to identify each time that we do a change. Each version is +associated with a series of fields saved. The save_state always saves +the state as the newer version. But load_state sometimes is able to +load state from an older version. + + === Legacy way === + +This way is going to disappear as soon as all current users are ported to VMSTATE. + +Each device has to register two functions, one to save the state and +another to load the state back. + +int register_savevm(DeviceState *dev, + const char *idstr, + int instance_id, + int version_id, + SaveStateHandler *save_state, + LoadStateHandler *load_state, + void *opaque); + +typedef void SaveStateHandler(QEMUFile *f, void *opaque); +typedef int LoadStateHandler(QEMUFile *f, void *opaque, int version_id); + +The important functions for the device state format are the save_state +and load_state. Notice that load_state receives a version_id +parameter to know what state format is receiving. save_state doesn't +have a version_id parameter because it always uses the latest version. + +=== VMState === + +The legacy way of saving/loading state of the device had the problem +that we have to maintain two functions in sync. If we did one change +in one of them and not in the other, we would get a failed migration. + +VMState changed the way that state is saved/loaded. Instead of using +a function to save the state and another to load it, it was changed to +a declarative way of what the state consisted of. Now VMState is able +to interpret that definition to be able to load/save the state. As +the state is declared only once, it can't go out of sync in the +save/load functions. + +An example (from hw/pckbd.c) + +static const VMStateDescription vmstate_kbd = { + .name = "pckbd", + .version_id = 3, + .minimum_version_id = 3, + .minimum_version_id_old = 3, + .fields = (VMStateField []) { + VMSTATE_UINT8(write_cmd, KBDState), + VMSTATE_UINT8(status, KBDState), + VMSTATE_UINT8(mode, KBDState), + VMSTATE_UINT8(pending, KBDState), + VMSTATE_END_OF_LIST() + } +}; + +We are declaring the state with name "pckbd". +The version_id is 3, and the fields are 4 uint8_t in a KBDState structure. +We registered this with: + + vmstate_register(NULL, 0, &vmstate_kbd, s); + +Note: talk about how vmstate <-> qdev interact, and what the instance ids mean. + +You can search for VMSTATE_* macros for lots of types used in QEMU in +hw/hw.h. + +=== More about versions == + +You can see that there are several version fields: + +- version_id: the maximum version_id supported by VMState for that device. +- minimum_version_id: the minimum version_id that VMState is able to understand + for that device. +- minimum_version_id_old: For devices that were not able to port to vmstate, we can + assign a function that knows how to read this old state. + +So, VMState is able to read versions from minimum_version_id to +version_id. And the function load_state_old() is able to load state +from minimum_version_id_old to minimum_version_id. This function is +deprecated and will be removed when no more users are left. + +=== Massaging functions === + +Sometimes, it is not enough to be able to save the state directly +from one structure, we need to fill the correct values there. One +example is when we are using kvm. Before saving the cpu state, we +need to ask kvm to copy to QEMU the state that it is using. And the +opposite when we are loading the state, we need a way to tell kvm to +load the state for the cpu that we have just loaded from the QEMUFile. + +The functions to do that are inside a vmstate definition, and are called: + +- int (*pre_load)(void *opaque); + + This function is called before we load the state of one device. + +- int (*post_load)(void *opaque, int version_id); + + This function is called after we load the state of one device. + +- void (*pre_save)(void *opaque); + + This function is called before we save the state of one device. + +Example: You can look at hpet.c, that uses the three function to + massage the state that is transferred. + +=== Subsections === + +The use of version_id allows to be able to migrate from older versions +to newer versions of a device. But not the other way around. This +makes very complicated to fix bugs in stable branches. If we need to +add anything to the state to fix a bug, we have to disable migration +to older versions that don't have that bug-fix (i.e. a new field). + +But sometimes, that bug-fix is only needed sometimes, not always. For +instance, if the device is in the middle of a DMA operation, it is +using a specific functionality, .... + +It is impossible to create a way to make migration from any version to +any other version to work. But we can do better than only allowing +migration from older versions no newer ones. For that fields that are +only needed sometimes, we add the idea of subsections. A subsection +is "like" a device vmstate, but with a particularity, it has a Boolean +function that tells if that values are needed to be sent or not. If +this functions returns false, the subsection is not sent. + +On the receiving side, if we found a subsection for a device that we +don't understand, we just fail the migration. If we understand all +the subsections, then we load the state with success. + +One important note is that the post_load() function is called "after" +loading all subsections, because a newer subsection could change same +value that it uses. + +Example: + +static bool ide_drive_pio_state_needed(void *opaque) +{ + IDEState *s = opaque; + + return (s->status & DRQ_STAT) != 0; +} + +const VMStateDescription vmstate_ide_drive_pio_state = { + .name = "ide_drive/pio_state", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .pre_save = ide_drive_pio_pre_save, + .post_load = ide_drive_pio_post_load, + .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, + .minimum_version_id_old = 0, + .post_load = ide_drive_post_load, + .fields = (VMStateField []) { + .... several fields .... + VMSTATE_END_OF_LIST() + }, + .subsections = (VMStateSubsection []) { + { + .vmsd = &vmstate_ide_drive_pio_state, + .needed = ide_drive_pio_state_needed, + }, { + /* empty */ + } + } +}; + +Here we have a subsection for the pio state. We only need to +save/send this state when we are in the middle of a pio operation +(that is what ide_drive_pio_state_needed() checks). If DRQ_STAT is +not enabled, the values on that fields are garbage and don't need to +be sent. diff --git a/docs/qdev-device-use.txt b/docs/qdev-device-use.txt new file mode 100644 index 0000000..4bb2be8 --- /dev/null +++ b/docs/qdev-device-use.txt @@ -0,0 +1,368 @@ += How to convert to -device & friends = + +=== Specifying Bus and Address on Bus === + +In qdev, each device has a parent bus. Some devices provide one or +more buses for children. You can specify a device's parent bus with +-device parameter bus. + +A device typically has a device address on its parent bus. For buses +where this address can be configured, devices provide a bus-specific +property. These are + + bus property name value format + PCI addr %x.%x (dev.fn, .fn optional) + I2C address %u + SCSI scsi-id %u + +Example: device i440FX-pcihost is on the root bus, and provides a PCI +bus named pci.0. To put a FOO device into its slot 4, use -device +FOO,bus=/i440FX-pcihost/pci.0,addr=4. The abbreviated form bus=pci.0 +also works as long as the bus name is unique. + +Note: the USB device address can't be controlled at this time. + +=== Block Devices === + +A QEMU block device (drive) has a host and a guest part. + +In the general case, the guest device is connected to a controller +device. For instance, the IDE controller provides two IDE buses, each +of which can have up to two ide-drive devices, and each ide-drive +device is a guest part, and is connected to a host part. + +Except we sometimes lump controller, bus(es) and drive device(s) all +together into a single device. For instance, the ISA floppy +controller is connected to up to two host drives. + +The old ways to define block devices define host and guest part +together. Sometimes, they can even define a controller device in +addition to the block device. + +The new way keeps the parts separate: you create the host part with +-drive, and guest device(s) with -device. + +The various old ways to define drives all boil down to the common form + + -drive if=TYPE,index=IDX,bus=BUS,unit=UNIT,HOST-OPTS... + +TYPE, BUS and UNIT identify the controller device, which of its buses +to use, and the drive's address on that bus. Details depend on TYPE. +IDX is an alternative way to specify BUS and UNIT. + +In the new way, this becomes something like + + -drive if=none,id=DRIVE-ID,HOST-OPTS... + -device DEVNAME,drive=DRIVE-ID,DEV-OPTS... + +The -device argument differs in detail for each kind of drive: + +* if=ide + + -device ide-drive,drive=DRIVE-ID,bus=IDE-BUS,unit=UNIT + + where IDE-BUS identifies an IDE bus, normally either ide.0 or ide.1, + and UNIT is either 0 or 1. + + Bug: new way does not work for ide.1 unit 0 (in old terms: index=2) + unless you disable the default CD-ROM with -nodefaults. + +* if=scsi + + The old way implicitly creates SCSI controllers as needed. The new + way makes that explicit: + + -device lsi53c895a,id=ID + + As for all PCI devices, you can add bus=PCI-BUS,addr=DEVFN to + control the PCI device address. + + This SCSI controller a single SCSI bus, named ID.0. Put a disk on + it: + + -device scsi-disk,drive=DRIVE-ID,bus=ID.0,scsi-id=SCSI-ID,removable=RMB + + The (optional) removable parameter lets you override the SCSI INQUIRY + removable (RMB) bit for non CD-ROM devices. It is ignored for CD-ROM devices + which are always removable. RMB is "on" or "off". + +* if=floppy + + -global isa-fdc,driveA=DRIVE-ID,driveB=DRIVE-ID + + This is -global instead of -device, because the floppy controller is + created automatically, and we want to configure that one, not create + a second one (which isn't possible anyway). + + Omitting a drive parameter makes that drive empty. + + Bug: driveA works only if you disable the default floppy drive with + -nodefaults. + +* if=virtio + + -device virtio-blk-pci,drive=DRIVE-ID,class=C,vectors=V,ioeventfd=IOEVENTFD + + This lets you control PCI device class and MSI-X vectors. + + IOEVENTFD controls whether or not ioeventfd is used for virtqueue notify. It + can be set to on (default) or off. + + As for all PCI devices, you can add bus=PCI-BUS,addr=DEVFN to + control the PCI device address. + +* if=pflash, if=mtd, if=sd, if=xen are not yet available with -device + +For USB devices, the old way is actually different: + + -usbdevice disk:format=FMT:FILENAME + +Provides much less control than -drive's HOST-OPTS... The new way +fixes that: + + -device usb-storage,drive=DRIVE-ID,removable=RMB + +The removable parameter gives control over the SCSI INQUIRY removable (RMB) +bit. USB thumbdrives usually set removable=on, while USB hard disks set +removable=off. See the if=scsi description above for details on the removable +parameter, which applies only to scsi-disk devices and not to scsi-generic. + +=== Character Devices === + +A QEMU character device has a host and a guest part. + +The old ways to define character devices define host and guest part +together. + +The new way keeps the parts separate: you create the host part with +-chardev, and the guest device with -device. + +The various old ways to define a character device are all of the +general form + + -FOO FOO-OPTS...,LEGACY-CHARDEV + +where FOO-OPTS... is specific to -FOO, and the host part +LEGACY-CHARDEV is the same everywhere. + +In the new way, this becomes + + -chardev HOST-OPTS...,id=CHR-ID + -device DEVNAME,chardev=CHR-ID,DEV-OPTS... + +The appropriate DEVNAME depends on the machine type. For type "pc": + +* -serial becomes -device isa-serial,iobase=IOADDR,irq=IRQ,index=IDX + + This lets you control I/O ports and IRQs. + +* -parallel becomes -device isa-parallel,iobase=IOADDR,irq=IRQ,index=IDX + + This lets you control I/O ports and IRQs. + +* -usbdevice serial:vendorid=VID,productid=PRID becomes + -device usb-serial,vendorid=VID,productid=PRID + +* -usbdevice braille doesn't support LEGACY-CHARDEV syntax. It always + uses "braille". With -device, this useful default is gone, so you + have to use something like + + -device usb-braille,chardev=braille,vendorid=VID,productid=PRID + -chardev braille,id=braille + +* -virtioconsole is still being worked on + +LEGACY-CHARDEV translates to -chardev HOST-OPTS... as follows: + +* null becomes -chardev null + +* pty, msmouse, braille, stdio likewise + +* vc:WIDTHxHEIGHT becomes -chardev vc,width=WIDTH,height=HEIGHT + +* vc:CxC becomes -chardev vc,cols=,rows= + +* con: becomes -chardev console + +* COM becomes -chardev serial,path= + +* file:FNAME becomes -chardev file,path=FNAME + +* pipe:FNAME becomes -chardev pipe,path=FNAME + +* tcp:HOST:PORT,OPTS... becomes -chardev socket,host=HOST,port=PORT,OPTS... + +* telnet:HOST:PORT,OPTS... becomes + -chardev socket,host=HOST,port=PORT,OPTS...,telnet=on + +* udp:HOST:PORT@LOCALADDR:LOCALPORT becomes + -chardev udp,host=HOST,port=PORT,localaddr=LOCALADDR,localport=LOCALPORT + +* unix:FNAME becomes -chardev socket,path=FNAME + +* /dev/parportN becomes -chardev parport,file=/dev/parportN + +* /dev/ppiN likewise + +* Any other /dev/FNAME becomes -chardev tty,path=/dev/FNAME + +* mon:LEGACY-CHARDEV is special: it multiplexes the monitor onto the + character device defined by LEGACY-CHARDEV. -chardev provides more + general multiplexing instead: you can connect up to four users to a + single host part. You need to pass mux=on to -chardev to enable + switching the input focus. + +QEMU uses LEGACY-CHARDEV syntax not just to set up guest devices, but +also in various other places such as -monitor or -net +user,guestfwd=... You can use chardev:CHR-ID in place of +LEGACY-CHARDEV to refer to a host part defined with -chardev. + +=== Network Devices === + +A QEMU network device (NIC) has a host and a guest part. + +The old ways to define NICs define host and guest part together. It +looks like this: + + -net nic,vlan=VLAN,macaddr=MACADDR,model=MODEL,name=ID,addr=STR,vectors=V + +Except for USB it looks like this: + + -usbdevice net:vlan=VLAN,macaddr=MACADDR,name=ID,addr=STR,vectors=V + +The new way keeps the parts separate: you create the host part with +-netdev, and the guest device with -device, like this: + + -netdev type=TYPE,id=NET-ID + -device DEVNAME,netdev=NET-ID,mac=MACADDR,DEV-OPTS... + +Unlike the old way, this creates just a network device, not a VLAN. +If you really want a VLAN, create it the usual way, then create the +guest device like this: + + -device DEVNAME,vlan=VLAN,mac=MACADDR,DEV-OPTS... + +DEVNAME equals MODEL, except for virtio you have to name the virtio +device appropriate for the bus (virtio-net-pci for PCI), and for USB +NIC you have to use usb-net. + +The old name=ID parameter becomes the usual id=ID with -device. + +For PCI devices, you can add bus=PCI-BUS,addr=DEVFN to control the PCI +device address, as usual. The old -net nic provides parameter addr +for that, it is silently ignored when the NIC is not a PCI device. + +For virtio-net-pci, you can control whether or not ioeventfd is used for +virtqueue notify by setting ioeventfd= to on or off (default). + +-net nic accepts vectors=V for all models, but it's silently ignored +except for virtio-net-pci (model=virtio). With -device, only devices +that support it accept it. + +Not all devices are available with -device at this time. All PCI +devices and ne2k_isa are. + +Some PCI devices aren't available with -net nic, e.g. i82558a. + +Bug: usb-net does not work, yet. Patch posted. + +=== Graphics Devices === + +Host and guest part of graphics devices have always been separate. + +The old way to define the guest graphics device is -vga VGA. + +The new way is -device. Map from -vga argument to -device: + + std -device VGA + cirrus -device cirrus-vga + vmware -device vmware-svga + xenfb not yet available with -device + +As for all PCI devices, you can add bus=PCI-BUS,addr=DEVFN to control +the PCI device address. + +-device VGA supports properties bios-offset and bios-size, but they +aren't used with machine type "pc". + +Bug: -device cirrus-vga and -device vmware-svga require -nodefaults. + +Bug: the new way requires PCI; ISA VGA is not yet available with +-device. + +Bug: the new way doesn't work for machine type "pc", because it +violates obscure device initialization ordering constraints. + +=== Audio Devices === + +Host and guest part of audio devices have always been separate. + +The old way to define guest audio devices is -soundhw C1,... + +The new way is to define each guest audio device separately with +-device. + +Map from -soundhw sound card name to -device: + + ac97 -device AC97 + cs4231a -device cs4231a,iobase=IOADDR,irq=IRQ,dma=DMA + es1370 -device ES1370 + gus -device gus,iobase=IOADDR,irq=IRQ,dma=DMA,freq=F + sb16 -device sb16,iobase=IOADDR,irq=IRQ,dma=DMA,dma16=DMA16,version=V + adlib not yet available with -device + pcspk not yet available with -device + +For PCI devices, you can add bus=PCI-BUS,addr=DEVFN to control the PCI +device address, as usual. + +=== USB Devices === + +The old way to define a virtual USB device is -usbdevice DRIVER:OPTS... + +The new way is -device DEVNAME,DEV-OPTS... Details depend on DRIVER: + +* mouse -device usb-mouse +* tablet -device usb-tablet +* keyboard -device usb-kdb +* wacom-tablet -device usb-wacom-tablet +* host:... See "Host Device Assignment" +* disk:... See "Block Devices" +* serial:... See "Character Devices" +* braille See "Character Devices" +* net:... See "Network Devices" +* bt:... not yet available with -device + +=== Watchdog Devices === + +Host and guest part of watchdog devices have always been separate. + +The old way to define a guest watchdog device is -watchdog DEVNAME. +The new way is -device DEVNAME. For PCI devices, you can add +bus=PCI-BUS,addr=DEVFN to control the PCI device address, as usual. + +=== Host Device Assignment === + +QEMU supports assigning host PCI devices (qemu-kvm only at this time) +and host USB devices. + +The old way to assign a host PCI device is + + -pcidevice host=ADDR,dma=none,id=ID + +The new way is + + -device pci-assign,host=ADDR,iommu=IOMMU,id=ID + +The old dma=none becomes iommu=0 with -device. + +The old way to assign a host USB device is + + -usbdevice host:auto:BUS.ADDR:VID:PRID + +where any of BUS, ADDR, VID, PRID can be the wildcard *. + +The new way is + + -device usb-host,hostbus=BUS,hostaddr=ADDR,vendorid=VID,productid=PRID + +where left out or zero BUS, ADDR, VID, PRID serve as wildcard. diff --git a/docs/specs/acpi_pci_hotplug.txt b/docs/specs/acpi_pci_hotplug.txt new file mode 100644 index 0000000..f0f74a7 --- /dev/null +++ b/docs/specs/acpi_pci_hotplug.txt @@ -0,0 +1,37 @@ +QEMU<->ACPI BIOS PCI hotplug interface +-------------------------------------- + +QEMU supports PCI hotplug via ACPI, for PCI bus 0. This document +describes the interface between QEMU and the ACPI BIOS. + +ACPI GPE block (IO ports 0xafe0-0xafe3, byte access): +----------------------------------------- + +Generic ACPI GPE block. Bit 1 (GPE.1) used to notify PCI hotplug/eject +event to ACPI BIOS, via SCI interrupt. + +PCI slot injection notification pending (IO port 0xae00-0xae03, 4-byte access): +--------------------------------------------------------------- +Slot injection notification pending. One bit per slot. + +Read by ACPI BIOS GPE.1 handler to notify OS of injection +events. + +PCI slot removal notification (IO port 0xae04-0xae07, 4-byte access): +----------------------------------------------------- +Slot removal notification pending. One bit per slot. + +Read by ACPI BIOS GPE.1 handler to notify OS of removal +events. + +PCI device eject (IO port 0xae08-0xae0b, 4-byte access): +---------------------------------------- + +Used by ACPI BIOS _EJ0 method to request device removal. One bit per slot. +Reads return 0. + +PCI removability status (IO port 0xae0c-0xae0f, 4-byte access): +----------------------------------------------- + +Used by ACPI BIOS _RMV method to indicate removability status to OS. One +bit per slot. diff --git a/docs/specs/ivshmem_device_spec.txt b/docs/specs/ivshmem_device_spec.txt new file mode 100644 index 0000000..23dd2ba --- /dev/null +++ b/docs/specs/ivshmem_device_spec.txt @@ -0,0 +1,96 @@ + +Device Specification for Inter-VM shared memory device +------------------------------------------------------ + +The Inter-VM shared memory device is designed to share a region of memory to +userspace in multiple virtual guests. The memory region does not belong to any +guest, but is a POSIX memory object on the host. Optionally, the device may +support sending interrupts to other guests sharing the same memory region. + + +The Inter-VM PCI device +----------------------- + +*BARs* + +The device supports three BARs. BAR0 is a 1 Kbyte MMIO region to support +registers. BAR1 is used for MSI-X when it is enabled in the device. BAR2 is +used to map the shared memory object from the host. The size of BAR2 is +specified when the guest is started and must be a power of 2 in size. + +*Registers* + +The device currently supports 4 registers of 32-bits each. Registers +are used for synchronization between guests sharing the same memory object when +interrupts are supported (this requires using the shared memory server). + +The server assigns each VM an ID number and sends this ID number to the Qemu +process when the guest starts. + +enum ivshmem_registers { + IntrMask = 0, + IntrStatus = 4, + IVPosition = 8, + Doorbell = 12 +}; + +The first two registers are the interrupt mask and status registers. Mask and +status are only used with pin-based interrupts. They are unused with MSI +interrupts. + +Status Register: The status register is set to 1 when an interrupt occurs. + +Mask Register: The mask register is bitwise ANDed with the interrupt status +and the result will raise an interrupt if it is non-zero. However, since 1 is +the only value the status will be set to, it is only the first bit of the mask +that has any effect. Therefore interrupts can be masked by setting the first +bit to 0 and unmasked by setting the first bit to 1. + +IVPosition Register: The IVPosition register is read-only and reports the +guest's ID number. The guest IDs are non-negative integers. When using the +server, since the server is a separate process, the VM ID will only be set when +the device is ready (shared memory is received from the server and accessible via +the device). If the device is not ready, the IVPosition will return -1. +Applications should ensure that they have a valid VM ID before accessing the +shared memory. + +Doorbell Register: To interrupt another guest, a guest must write to the +Doorbell register. The doorbell register is 32-bits, logically divided into +two 16-bit fields. The high 16-bits are the guest ID to interrupt and the low +16-bits are the interrupt vector to trigger. The semantics of the value +written to the doorbell depends on whether the device is using MSI or a regular +pin-based interrupt. In short, MSI uses vectors while regular interrupts set the +status register. + +Regular Interrupts + +If regular interrupts are used (due to either a guest not supporting MSI or the +user specifying not to use them on startup) then the value written to the lower +16-bits of the Doorbell register results is arbitrary and will trigger an +interrupt in the destination guest. + +Message Signalled Interrupts + +A ivshmem device may support multiple MSI vectors. If so, the lower 16-bits +written to the Doorbell register must be between 0 and the maximum number of +vectors the guest supports. The lower 16 bits written to the doorbell is the +MSI vector that will be raised in the destination guest. The number of MSI +vectors is configurable but it is set when the VM is started. + +The important thing to remember with MSI is that it is only a signal, no status +is set (since MSI interrupts are not shared). All information other than the +interrupt itself should be communicated via the shared memory region. Devices +supporting multiple MSI vectors can use different vectors to indicate different +events have occurred. The semantics of interrupt vectors are left to the +user's discretion. + + +Usage in the Guest +------------------ + +The shared memory device is intended to be used with the provided UIO driver. +Very little configuration is needed. The guest should map BAR0 to access the +registers (an array of 32-bit ints allows simple writing) and map BAR2 to +access the shared memory region itself. The size of the shared memory region +is specified when the guest (or shared memory server) is started. A guest may +map the whole shared memory region or only part of it. diff --git a/docs/specs/qed_spec.txt b/docs/specs/qed_spec.txt new file mode 100644 index 0000000..1d5fa87 --- /dev/null +++ b/docs/specs/qed_spec.txt @@ -0,0 +1,130 @@ +=Specification= + +The file format looks like this: + + +----------+----------+----------+-----+ + | cluster0 | cluster1 | cluster2 | ... | + +----------+----------+----------+-----+ + +The first cluster begins with the '''header'''. The header contains information about where regular clusters start; this allows the header to be extensible and store extra information about the image file. A regular cluster may be a '''data cluster''', an '''L2''', or an '''L1 table'''. L1 and L2 tables are composed of one or more contiguous clusters. + +Normally the file size will be a multiple of the cluster size. If the file size is not a multiple, extra information after the last cluster may not be preserved if data is written. Legitimate extra information should use space between the header and the first regular cluster. + +All fields are little-endian. + +==Header== + Header { + uint32_t magic; /* QED\0 */ + + uint32_t cluster_size; /* in bytes */ + uint32_t table_size; /* for L1 and L2 tables, in clusters */ + uint32_t header_size; /* in clusters */ + + uint64_t features; /* format feature bits */ + uint64_t compat_features; /* compat feature bits */ + uint64_t autoclear_features; /* self-resetting feature bits */ + + uint64_t l1_table_offset; /* in bytes */ + uint64_t image_size; /* total logical image size, in bytes */ + + /* if (features & QED_F_BACKING_FILE) */ + uint32_t backing_filename_offset; /* in bytes from start of header */ + uint32_t backing_filename_size; /* in bytes */ + } + +Field descriptions: +* ''cluster_size'' must be a power of 2 in range [2^12, 2^26]. +* ''table_size'' must be a power of 2 in range [1, 16]. +* ''header_size'' is the number of clusters used by the header and any additional information stored before regular clusters. +* ''features'', ''compat_features'', and ''autoclear_features'' are file format extension bitmaps. They work as follows: +** An image with unknown ''features'' bits enabled must not be opened. File format changes that are not backwards-compatible must use ''features'' bits. +** An image with unknown ''compat_features'' bits enabled can be opened safely. The unknown features are simply ignored and represent backwards-compatible changes to the file format. +** An image with unknown ''autoclear_features'' bits enable can be opened safely after clearing the unknown bits. This allows for backwards-compatible changes to the file format which degrade gracefully and can be re-enabled again by a new program later. +* ''l1_table_offset'' is the offset of the first byte of the L1 table in the image file and must be a multiple of ''cluster_size''. +* ''image_size'' is the block device size seen by the guest and must be a multiple of 512 bytes. +* ''backing_filename_offset'' and ''backing_filename_size'' describe a string in (byte offset, byte size) form. It is not NUL-terminated and has no alignment constraints. The string must be stored within the first ''header_size'' clusters. The backing filename may be an absolute path or relative to the image file. + +Feature bits: +* QED_F_BACKING_FILE = 0x01. The image uses a backing file. +* QED_F_NEED_CHECK = 0x02. The image needs a consistency check before use. +* QED_F_BACKING_FORMAT_NO_PROBE = 0x04. The backing file is a raw disk image and no file format autodetection should be attempted. This should be used to ensure that raw backing files are never detected as an image format if they happen to contain magic constants. + +There are currently no defined ''compat_features'' or ''autoclear_features'' bits. + +Fields predicated on a feature bit are only used when that feature is set. The fields always take up header space, regardless of whether or not the feature bit is set. + +==Tables== + +Tables provide the translation from logical offsets in the block device to cluster offsets in the file. + + #define TABLE_NOFFSETS (table_size * cluster_size / sizeof(uint64_t)) + + Table { + uint64_t offsets[TABLE_NOFFSETS]; + } + +The tables are organized as follows: + + +----------+ + | L1 table | + +----------+ + ,------' | '------. + +----------+ | +----------+ + | L2 table | ... | L2 table | + +----------+ +----------+ + ,------' | '------. + +----------+ | +----------+ + | Data | ... | Data | + +----------+ +----------+ + +A table is made up of one or more contiguous clusters. The table_size header field determines table size for an image file. For example, cluster_size=64 KB and table_size=4 results in 256 KB tables. + +The logical image size must be less than or equal to the maximum possible size of clusters rooted by the L1 table: + header.image_size <= TABLE_NOFFSETS * TABLE_NOFFSETS * header.cluster_size + +L1, L2, and data cluster offsets must be aligned to header.cluster_size. The following offsets have special meanings: + +===L2 table offsets=== +* 0 - unallocated. The L2 table is not yet allocated. + +===Data cluster offsets=== +* 0 - unallocated. The data cluster is not yet allocated. + +Future format extensions may wish to store per-offset information. The least significant 12 bits of an offset are reserved for this purpose and must be set to zero. Image files with cluster_size > 2^12 will have more unused bits which should also be zeroed. + +===Unallocated L2 tables and data clusters=== +Reads to an unallocated area of the image file access the backing file. If there is no backing file, then zeroes are produced. The backing file may be smaller than the image file and reads of unallocated areas beyond the end of the backing file produce zeroes. + +Writes to an unallocated area cause a new data clusters to be allocated, and a new L2 table if that is also unallocated. The new data cluster is populated with data from the backing file (or zeroes if no backing file) and the data being written. + +===Logical offset translation=== +Logical offsets are translated into cluster offsets as follows: + + table_bits table_bits cluster_bits + <--------> <--------> <---------------> + +----------+----------+-----------------+ + | L1 index | L2 index | byte offset | + +----------+----------+-----------------+ + + Structure of a logical offset + + offset_mask = ~(cluster_size - 1) # mask for the image file byte offset + + def logical_to_cluster_offset(l1_index, l2_index, byte_offset): + l2_offset = l1_table[l1_index] + l2_table = load_table(l2_offset) + cluster_offset = l2_table[l2_index] & offset_mask + return cluster_offset + byte_offset + +==Consistency checking== + +This section is informational and included to provide background on the use of the QED_F_NEED_CHECK ''features'' bit. + +The QED_F_NEED_CHECK bit is used to mark an image as dirty before starting an operation that could leave the image in an inconsistent state if interrupted by a crash or power failure. A dirty image must be checked on open because its metadata may not be consistent. + +Consistency check includes the following invariants: +# Each cluster is referenced once and only once. It is an inconsistency to have a cluster referenced more than once by L1 or L2 tables. A cluster has been leaked if it has no references. +# Offsets must be within the image file size and must be ''cluster_size'' aligned. +# Table offsets must at least ''table_size'' * ''cluster_size'' bytes from the end of the image file so that there is space for the entire table. + +The consistency check process starts by from ''l1_table_offset'' and scans all L2 tables. After the check completes with no other errors besides leaks, the QED_F_NEED_CHECK bit can be cleared and the image can be accessed. diff --git a/docs/tracing.txt b/docs/tracing.txt new file mode 100644 index 0000000..21183f9 --- /dev/null +++ b/docs/tracing.txt @@ -0,0 +1,189 @@ += Tracing = + +== Introduction == + +This document describes the tracing infrastructure in QEMU and how to use it +for debugging, profiling, and observing execution. + +== Quickstart == + +1. Build with the 'simple' trace backend: + + ./configure --trace-backend=simple + make + +2. Enable trace events you are interested in: + + $EDITOR trace-events # remove "disable" from events you want + +3. Run the virtual machine to produce a trace file: + + qemu ... # your normal QEMU invocation + +4. Pretty-print the binary trace file: + + ./simpletrace.py trace-events trace-* + +== Trace events == + +There is a set of static trace events declared in the trace-events source +file. Each trace event declaration names the event, its arguments, and the +format string which can be used for pretty-printing: + + qemu_malloc(size_t size, void *ptr) "size %zu ptr %p" + qemu_free(void *ptr) "ptr %p" + +The trace-events file is processed by the tracetool script during build to +generate code for the trace events. Trace events are invoked directly from +source code like this: + + #include "trace.h" /* needed for trace event prototype */ + + void *qemu_malloc(size_t size) + { + void *ptr; + if (!size && !allow_zero_malloc()) { + abort(); + } + ptr = oom_check(malloc(size ? size : 1)); + trace_qemu_malloc(size, ptr); /* <-- trace event */ + return ptr; + } + +=== Declaring trace events === + +The tracetool script produces the trace.h header file which is included by +every source file that uses trace events. Since many source files include +trace.h, it uses a minimum of types and other header files included to keep +the namespace clean and compile times and dependencies down. + +Trace events should use types as follows: + + * Use stdint.h types for fixed-size types. Most offsets and guest memory + addresses are best represented with uint32_t or uint64_t. Use fixed-size + types over primitive types whose size may change depending on the host + (32-bit versus 64-bit) so trace events don't truncate values or break + the build. + + * Use void * for pointers to structs or for arrays. The trace.h header + cannot include all user-defined struct declarations and it is therefore + necessary to use void * for pointers to structs. + + * For everything else, use primitive scalar types (char, int, long) with the + appropriate signedness. + +Format strings should reflect the types defined in the trace event. Take +special care to use PRId64 and PRIu64 for int64_t and uint64_t types, +respectively. This ensures portability between 32- and 64-bit platforms. Note +that format strings must begin and end with double quotes. When using +portability macros, ensure they are preceded and followed by double quotes: +"value %"PRIx64"". + +=== Hints for adding new trace events === + +1. Trace state changes in the code. Interesting points in the code usually + involve a state change like starting, stopping, allocating, freeing. State + changes are good trace events because they can be used to understand the + execution of the system. + +2. Trace guest operations. Guest I/O accesses like reading device registers + are good trace events because they can be used to understand guest + interactions. + +3. Use correlator fields so the context of an individual line of trace output + can be understood. For example, trace the pointer returned by malloc and + used as an argument to free. This way mallocs and frees can be matched up. + Trace events with no context are not very useful. + +4. Name trace events after their function. If there are multiple trace events + in one function, append a unique distinguisher at the end of the name. + +5. Declare trace events with the "disable" keyword. Some trace events can + produce a lot of output and users are typically only interested in a subset + of trace events. Marking trace events disabled by default saves the user + from having to manually disable noisy trace events. + +== Trace backends == + +The tracetool script automates tedious trace event code generation and also +keeps the trace event declarations independent of the trace backend. The trace +events are not tightly coupled to a specific trace backend, such as LTTng or +SystemTap. Support for trace backends can be added by extending the tracetool +script. + +The trace backend is chosen at configure time and only one trace backend can +be built into the binary: + + ./configure --trace-backend=simple + +For a list of supported trace backends, try ./configure --help or see below. + +The following subsections describe the supported trace backends. + +=== Nop === + +The "nop" backend generates empty trace event functions so that the compiler +can optimize out trace events completely. This is the default and imposes no +performance penalty. + +=== Simpletrace === + +The "simple" backend supports common use cases and comes as part of the QEMU +source tree. It may not be as powerful as platform-specific or third-party +trace backends but it is portable. This is the recommended trace backend +unless you have specific needs for more advanced backends. + +=== Stderr === + +The "stderr" backend sends trace events directly to standard error output +during emulation. + +==== Monitor commands ==== + +* info trace + Display the contents of trace buffer. This command dumps the trace buffer + with simple formatting. For full pretty-printing, use the simpletrace.py + script on a binary trace file. + + The trace buffer is written into until full. The full trace buffer is + flushed and emptied. This means the 'info trace' will display few or no + entries if the buffer has just been flushed. + +* info trace-events + View available trace events and their state. State 1 means enabled, state 0 + means disabled. + +* trace-event NAME on|off + Enable/disable a given trace event. + +* trace-file on|off|flush|set + Enable/disable/flush the trace file or set the trace file name. + +==== Enabling/disabling trace events programmatically ==== + +The st_change_trace_event_state() function can be used to enable or disable trace +events at runtime inside QEMU: + + #include "trace.h" + + st_change_trace_event_state("virtio_irq", true); /* enable */ + [...] + st_change_trace_event_state("virtio_irq", false); /* disable */ + +==== Analyzing trace files ==== + +The "simple" backend produces binary trace files that can be formatted with the +simpletrace.py script. The script takes the trace-events file and the binary +trace: + + ./simpletrace.py trace-events trace-12345 + +You must ensure that the same trace-events file was used to build QEMU, +otherwise trace event declarations may have changed and output will not be +consistent. + +=== LTTng Userspace Tracer === + +The "ust" backend uses the LTTng Userspace Tracer library. There are no +monitor commands built into QEMU, instead UST utilities should be used to list, +enable/disable, and dump traces. diff --git a/dyngen-exec.h b/dyngen-exec.h new file mode 100644 index 0000000..db00fba --- /dev/null +++ b/dyngen-exec.h @@ -0,0 +1,84 @@ +/* + * dyngen defines for micro operation code + * + * Copyright (c) 2003 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 . + */ +#if !defined(__DYNGEN_EXEC_H__) +#define __DYNGEN_EXEC_H__ + +#include "qemu-common.h" + +#ifdef __OpenBSD__ +#include +#endif + +/* XXX: This may be wrong for 64-bit ILP32 hosts. */ +typedef void * host_reg_t; + +#if defined(__i386__) +#define AREG0 "ebp" +#elif defined(__x86_64__) +#define AREG0 "r14" +#elif defined(_ARCH_PPC) +#define AREG0 "r27" +#elif defined(__arm__) +#define AREG0 "r7" +#elif defined(__hppa__) +#define AREG0 "r17" +#elif defined(__mips__) +#define AREG0 "s0" +#elif defined(__sparc__) +#ifdef CONFIG_SOLARIS +#define AREG0 "g2" +#else +#ifdef __sparc_v9__ +#define AREG0 "g5" +#else +#define AREG0 "g6" +#endif +#endif +#elif defined(__s390__) +#define AREG0 "r10" +#elif defined(__alpha__) +/* Note $15 is the frame pointer, so anything in op-i386.c that would + require a frame pointer, like alloca, would probably loose. */ +#define AREG0 "$15" +#elif defined(__mc68000) +#define AREG0 "%a5" +#elif defined(__ia64__) +#define AREG0 "r7" +#else +#error unsupported CPU +#endif + +#define xglue(x, y) x ## y +#define glue(x, y) xglue(x, y) +#define stringify(s) tostring(s) +#define tostring(s) #s + +/* The return address may point to the start of the next instruction. + Subtracting one gets us the call instruction itself. */ +#if defined(__s390__) && !defined(__s390x__) +# define GETPC() ((void*)(((unsigned long)__builtin_return_address(0) & 0x7fffffffUL) - 1)) +#elif defined(__arm__) +/* Thumb return addresses have the low bit set, so we need to subtract two. + This is still safe in ARM mode because instructions are 4 bytes. */ +# define GETPC() ((void *)((unsigned long)__builtin_return_address(0) - 2)) +#else +# define GETPC() ((void *)((unsigned long)__builtin_return_address(0) - 1)) +#endif + +#endif /* !defined(__DYNGEN_EXEC_H__) */ diff --git a/elf.h b/elf.h new file mode 100644 index 0000000..7067c90 --- /dev/null +++ b/elf.h @@ -0,0 +1,1240 @@ +#ifndef _QEMU_ELF_H +#define _QEMU_ELF_H + +#include + +/* 32-bit ELF base types. */ +typedef uint32_t Elf32_Addr; +typedef uint16_t Elf32_Half; +typedef uint32_t Elf32_Off; +typedef int32_t Elf32_Sword; +typedef uint32_t Elf32_Word; + +/* 64-bit ELF base types. */ +typedef uint64_t Elf64_Addr; +typedef uint16_t Elf64_Half; +typedef int16_t Elf64_SHalf; +typedef uint64_t Elf64_Off; +typedef int32_t Elf64_Sword; +typedef uint32_t Elf64_Word; +typedef uint64_t Elf64_Xword; +typedef int64_t Elf64_Sxword; + +/* These constants are for the segment types stored in the image headers */ +#define PT_NULL 0 +#define PT_LOAD 1 +#define PT_DYNAMIC 2 +#define PT_INTERP 3 +#define PT_NOTE 4 +#define PT_SHLIB 5 +#define PT_PHDR 6 +#define PT_LOPROC 0x70000000 +#define PT_HIPROC 0x7fffffff +#define PT_MIPS_REGINFO 0x70000000 +#define PT_MIPS_OPTIONS 0x70000001 + +/* Flags in the e_flags field of the header */ +/* MIPS architecture level. */ +#define EF_MIPS_ARCH_1 0x00000000 /* -mips1 code. */ +#define EF_MIPS_ARCH_2 0x10000000 /* -mips2 code. */ +#define EF_MIPS_ARCH_3 0x20000000 /* -mips3 code. */ +#define EF_MIPS_ARCH_4 0x30000000 /* -mips4 code. */ +#define EF_MIPS_ARCH_5 0x40000000 /* -mips5 code. */ +#define EF_MIPS_ARCH_32 0x50000000 /* MIPS32 code. */ +#define EF_MIPS_ARCH_64 0x60000000 /* MIPS64 code. */ + +/* The ABI of a file. */ +#define EF_MIPS_ABI_O32 0x00001000 /* O32 ABI. */ +#define EF_MIPS_ABI_O64 0x00002000 /* O32 extended for 64 bit. */ + +#define EF_MIPS_NOREORDER 0x00000001 +#define EF_MIPS_PIC 0x00000002 +#define EF_MIPS_CPIC 0x00000004 +#define EF_MIPS_ABI2 0x00000020 +#define EF_MIPS_OPTIONS_FIRST 0x00000080 +#define EF_MIPS_32BITMODE 0x00000100 +#define EF_MIPS_ABI 0x0000f000 +#define EF_MIPS_ARCH 0xf0000000 + +/* These constants define the different elf file types */ +#define ET_NONE 0 +#define ET_REL 1 +#define ET_EXEC 2 +#define ET_DYN 3 +#define ET_CORE 4 +#define ET_LOPROC 0xff00 +#define ET_HIPROC 0xffff + +/* These constants define the various ELF target machines */ +#define EM_NONE 0 +#define EM_M32 1 +#define EM_SPARC 2 +#define EM_386 3 +#define EM_68K 4 +#define EM_88K 5 +#define EM_486 6 /* Perhaps disused */ +#define EM_860 7 + +#define EM_MIPS 8 /* MIPS R3000 (officially, big-endian only) */ + +#define EM_MIPS_RS4_BE 10 /* MIPS R4000 big-endian */ + +#define EM_PARISC 15 /* HPPA */ + +#define EM_SPARC32PLUS 18 /* Sun's "v8plus" */ + +#define EM_PPC 20 /* PowerPC */ +#define EM_PPC64 21 /* PowerPC64 */ + +#define EM_ARM 40 /* ARM */ + +#define EM_SH 42 /* SuperH */ + +#define EM_SPARCV9 43 /* SPARC v9 64-bit */ + +#define EM_IA_64 50 /* HP/Intel IA-64 */ + +#define EM_X86_64 62 /* AMD x86-64 */ + +#define EM_S390 22 /* IBM S/390 */ + +#define EM_CRIS 76 /* Axis Communications 32-bit embedded processor */ + +#define EM_V850 87 /* NEC v850 */ + +#define EM_H8_300H 47 /* Hitachi H8/300H */ +#define EM_H8S 48 /* Hitachi H8S */ + +/* + * This is an interim value that we will use until the committee comes + * up with a final number. + */ +#define EM_ALPHA 0x9026 + +/* Bogus old v850 magic number, used by old tools. */ +#define EM_CYGNUS_V850 0x9080 + +/* + * This is the old interim value for S/390 architecture + */ +#define EM_S390_OLD 0xA390 + +#define EM_MICROBLAZE 189 +#define EM_MICROBLAZE_OLD 0xBAAB + +/* This is the info that is needed to parse the dynamic section of the file */ +#define DT_NULL 0 +#define DT_NEEDED 1 +#define DT_PLTRELSZ 2 +#define DT_PLTGOT 3 +#define DT_HASH 4 +#define DT_STRTAB 5 +#define DT_SYMTAB 6 +#define DT_RELA 7 +#define DT_RELASZ 8 +#define DT_RELAENT 9 +#define DT_STRSZ 10 +#define DT_SYMENT 11 +#define DT_INIT 12 +#define DT_FINI 13 +#define DT_SONAME 14 +#define DT_RPATH 15 +#define DT_SYMBOLIC 16 +#define DT_REL 17 +#define DT_RELSZ 18 +#define DT_RELENT 19 +#define DT_PLTREL 20 +#define DT_DEBUG 21 +#define DT_TEXTREL 22 +#define DT_JMPREL 23 +#define DT_BINDNOW 24 +#define DT_INIT_ARRAY 25 +#define DT_FINI_ARRAY 26 +#define DT_INIT_ARRAYSZ 27 +#define DT_FINI_ARRAYSZ 28 +#define DT_RUNPATH 29 +#define DT_FLAGS 30 +#define DT_LOOS 0x6000000d +#define DT_HIOS 0x6ffff000 +#define DT_LOPROC 0x70000000 +#define DT_HIPROC 0x7fffffff + +/* DT_ entries which fall between DT_VALRNGLO and DT_VALRNDHI use + the d_val field of the Elf*_Dyn structure. I.e. they contain scalars. */ +#define DT_VALRNGLO 0x6ffffd00 +#define DT_VALRNGHI 0x6ffffdff + +/* DT_ entries which fall between DT_ADDRRNGLO and DT_ADDRRNGHI use + the d_ptr field of the Elf*_Dyn structure. I.e. they contain pointers. */ +#define DT_ADDRRNGLO 0x6ffffe00 +#define DT_ADDRRNGHI 0x6ffffeff + +#define DT_VERSYM 0x6ffffff0 +#define DT_RELACOUNT 0x6ffffff9 +#define DT_RELCOUNT 0x6ffffffa +#define DT_FLAGS_1 0x6ffffffb +#define DT_VERDEF 0x6ffffffc +#define DT_VERDEFNUM 0x6ffffffd +#define DT_VERNEED 0x6ffffffe +#define DT_VERNEEDNUM 0x6fffffff + +#define DT_MIPS_RLD_VERSION 0x70000001 +#define DT_MIPS_TIME_STAMP 0x70000002 +#define DT_MIPS_ICHECKSUM 0x70000003 +#define DT_MIPS_IVERSION 0x70000004 +#define DT_MIPS_FLAGS 0x70000005 + #define RHF_NONE 0 + #define RHF_HARDWAY 1 + #define RHF_NOTPOT 2 +#define DT_MIPS_BASE_ADDRESS 0x70000006 +#define DT_MIPS_CONFLICT 0x70000008 +#define DT_MIPS_LIBLIST 0x70000009 +#define DT_MIPS_LOCAL_GOTNO 0x7000000a +#define DT_MIPS_CONFLICTNO 0x7000000b +#define DT_MIPS_LIBLISTNO 0x70000010 +#define DT_MIPS_SYMTABNO 0x70000011 +#define DT_MIPS_UNREFEXTNO 0x70000012 +#define DT_MIPS_GOTSYM 0x70000013 +#define DT_MIPS_HIPAGENO 0x70000014 +#define DT_MIPS_RLD_MAP 0x70000016 + +/* This info is needed when parsing the symbol table */ +#define STB_LOCAL 0 +#define STB_GLOBAL 1 +#define STB_WEAK 2 + +#define STT_NOTYPE 0 +#define STT_OBJECT 1 +#define STT_FUNC 2 +#define STT_SECTION 3 +#define STT_FILE 4 + +#define ELF_ST_BIND(x) ((x) >> 4) +#define ELF_ST_TYPE(x) (((unsigned int) x) & 0xf) +#define ELF32_ST_BIND(x) ELF_ST_BIND(x) +#define ELF32_ST_TYPE(x) ELF_ST_TYPE(x) +#define ELF64_ST_BIND(x) ELF_ST_BIND(x) +#define ELF64_ST_TYPE(x) ELF_ST_TYPE(x) + +/* Symbolic values for the entries in the auxiliary table + put on the initial stack */ +#define AT_NULL 0 /* end of vector */ +#define AT_IGNORE 1 /* entry should be ignored */ +#define AT_EXECFD 2 /* file descriptor of program */ +#define AT_PHDR 3 /* program headers for program */ +#define AT_PHENT 4 /* size of program header entry */ +#define AT_PHNUM 5 /* number of program headers */ +#define AT_PAGESZ 6 /* system page size */ +#define AT_BASE 7 /* base address of interpreter */ +#define AT_FLAGS 8 /* flags */ +#define AT_ENTRY 9 /* entry point of program */ +#define AT_NOTELF 10 /* program is not ELF */ +#define AT_UID 11 /* real uid */ +#define AT_EUID 12 /* effective uid */ +#define AT_GID 13 /* real gid */ +#define AT_EGID 14 /* effective gid */ +#define AT_PLATFORM 15 /* string identifying CPU for optimizations */ +#define AT_HWCAP 16 /* arch dependent hints at CPU capabilities */ +#define AT_CLKTCK 17 /* frequency at which times() increments */ +#define AT_FPUCW 18 /* info about fpu initialization by kernel */ +#define AT_DCACHEBSIZE 19 /* data cache block size */ +#define AT_ICACHEBSIZE 20 /* instruction cache block size */ +#define AT_UCACHEBSIZE 21 /* unified cache block size */ +#define AT_IGNOREPPC 22 /* ppc only; entry should be ignored */ +#define AT_SECURE 23 /* boolean, was exec suid-like? */ +#define AT_BASE_PLATFORM 24 /* string identifying real platforms */ +#define AT_RANDOM 25 /* address of 16 random bytes */ +#define AT_EXECFN 31 /* filename of the executable */ +#define AT_SYSINFO 32 /* address of kernel entry point */ +#define AT_SYSINFO_EHDR 33 /* address of kernel vdso */ +#define AT_L1I_CACHESHAPE 34 /* shapes of the caches: */ +#define AT_L1D_CACHESHAPE 35 /* bits 0-3: cache associativity. */ +#define AT_L2_CACHESHAPE 36 /* bits 4-7: log2 of line size. */ +#define AT_L3_CACHESHAPE 37 /* val&~255: cache size. */ + +typedef struct dynamic{ + Elf32_Sword d_tag; + union{ + Elf32_Sword d_val; + Elf32_Addr d_ptr; + } d_un; +} Elf32_Dyn; + +typedef struct { + Elf64_Sxword d_tag; /* entry tag value */ + union { + Elf64_Xword d_val; + Elf64_Addr d_ptr; + } d_un; +} Elf64_Dyn; + +/* The following are used with relocations */ +#define ELF32_R_SYM(x) ((x) >> 8) +#define ELF32_R_TYPE(x) ((x) & 0xff) + +#define ELF64_R_SYM(i) ((i) >> 32) +#define ELF64_R_TYPE(i) ((i) & 0xffffffff) +#define ELF64_R_TYPE_DATA(i) (((ELF64_R_TYPE(i) >> 8) ^ 0x00800000) - 0x00800000) + +#define R_386_NONE 0 +#define R_386_32 1 +#define R_386_PC32 2 +#define R_386_GOT32 3 +#define R_386_PLT32 4 +#define R_386_COPY 5 +#define R_386_GLOB_DAT 6 +#define R_386_JMP_SLOT 7 +#define R_386_RELATIVE 8 +#define R_386_GOTOFF 9 +#define R_386_GOTPC 10 +#define R_386_NUM 11 +/* Not a dynamic reloc, so not included in R_386_NUM. Used in TCG. */ +#define R_386_PC8 23 + +#define R_MIPS_NONE 0 +#define R_MIPS_16 1 +#define R_MIPS_32 2 +#define R_MIPS_REL32 3 +#define R_MIPS_26 4 +#define R_MIPS_HI16 5 +#define R_MIPS_LO16 6 +#define R_MIPS_GPREL16 7 +#define R_MIPS_LITERAL 8 +#define R_MIPS_GOT16 9 +#define R_MIPS_PC16 10 +#define R_MIPS_CALL16 11 +#define R_MIPS_GPREL32 12 +/* The remaining relocs are defined on Irix, although they are not + in the MIPS ELF ABI. */ +#define R_MIPS_UNUSED1 13 +#define R_MIPS_UNUSED2 14 +#define R_MIPS_UNUSED3 15 +#define R_MIPS_SHIFT5 16 +#define R_MIPS_SHIFT6 17 +#define R_MIPS_64 18 +#define R_MIPS_GOT_DISP 19 +#define R_MIPS_GOT_PAGE 20 +#define R_MIPS_GOT_OFST 21 +/* + * The following two relocation types are specified in the MIPS ABI + * conformance guide version 1.2 but not yet in the psABI. + */ +#define R_MIPS_GOTHI16 22 +#define R_MIPS_GOTLO16 23 +#define R_MIPS_SUB 24 +#define R_MIPS_INSERT_A 25 +#define R_MIPS_INSERT_B 26 +#define R_MIPS_DELETE 27 +#define R_MIPS_HIGHER 28 +#define R_MIPS_HIGHEST 29 +/* + * The following two relocation types are specified in the MIPS ABI + * conformance guide version 1.2 but not yet in the psABI. + */ +#define R_MIPS_CALLHI16 30 +#define R_MIPS_CALLLO16 31 +/* + * This range is reserved for vendor specific relocations. + */ +#define R_MIPS_LOVENDOR 100 +#define R_MIPS_HIVENDOR 127 + + +/* + * Sparc ELF relocation types + */ +#define R_SPARC_NONE 0 +#define R_SPARC_8 1 +#define R_SPARC_16 2 +#define R_SPARC_32 3 +#define R_SPARC_DISP8 4 +#define R_SPARC_DISP16 5 +#define R_SPARC_DISP32 6 +#define R_SPARC_WDISP30 7 +#define R_SPARC_WDISP22 8 +#define R_SPARC_HI22 9 +#define R_SPARC_22 10 +#define R_SPARC_13 11 +#define R_SPARC_LO10 12 +#define R_SPARC_GOT10 13 +#define R_SPARC_GOT13 14 +#define R_SPARC_GOT22 15 +#define R_SPARC_PC10 16 +#define R_SPARC_PC22 17 +#define R_SPARC_WPLT30 18 +#define R_SPARC_COPY 19 +#define R_SPARC_GLOB_DAT 20 +#define R_SPARC_JMP_SLOT 21 +#define R_SPARC_RELATIVE 22 +#define R_SPARC_UA32 23 +#define R_SPARC_PLT32 24 +#define R_SPARC_HIPLT22 25 +#define R_SPARC_LOPLT10 26 +#define R_SPARC_PCPLT32 27 +#define R_SPARC_PCPLT22 28 +#define R_SPARC_PCPLT10 29 +#define R_SPARC_10 30 +#define R_SPARC_11 31 +#define R_SPARC_64 32 +#define R_SPARC_OLO10 33 +#define R_SPARC_HH22 34 +#define R_SPARC_HM10 35 +#define R_SPARC_LM22 36 +#define R_SPARC_WDISP16 40 +#define R_SPARC_WDISP19 41 +#define R_SPARC_7 43 +#define R_SPARC_5 44 +#define R_SPARC_6 45 + +/* Bits present in AT_HWCAP, primarily for Sparc32. */ + +#define HWCAP_SPARC_FLUSH 1 /* CPU supports flush instruction. */ +#define HWCAP_SPARC_STBAR 2 +#define HWCAP_SPARC_SWAP 4 +#define HWCAP_SPARC_MULDIV 8 +#define HWCAP_SPARC_V9 16 +#define HWCAP_SPARC_ULTRA3 32 + +/* + * 68k ELF relocation types + */ +#define R_68K_NONE 0 +#define R_68K_32 1 +#define R_68K_16 2 +#define R_68K_8 3 +#define R_68K_PC32 4 +#define R_68K_PC16 5 +#define R_68K_PC8 6 +#define R_68K_GOT32 7 +#define R_68K_GOT16 8 +#define R_68K_GOT8 9 +#define R_68K_GOT32O 10 +#define R_68K_GOT16O 11 +#define R_68K_GOT8O 12 +#define R_68K_PLT32 13 +#define R_68K_PLT16 14 +#define R_68K_PLT8 15 +#define R_68K_PLT32O 16 +#define R_68K_PLT16O 17 +#define R_68K_PLT8O 18 +#define R_68K_COPY 19 +#define R_68K_GLOB_DAT 20 +#define R_68K_JMP_SLOT 21 +#define R_68K_RELATIVE 22 + +/* + * Alpha ELF relocation types + */ +#define R_ALPHA_NONE 0 /* No reloc */ +#define R_ALPHA_REFLONG 1 /* Direct 32 bit */ +#define R_ALPHA_REFQUAD 2 /* Direct 64 bit */ +#define R_ALPHA_GPREL32 3 /* GP relative 32 bit */ +#define R_ALPHA_LITERAL 4 /* GP relative 16 bit w/optimization */ +#define R_ALPHA_LITUSE 5 /* Optimization hint for LITERAL */ +#define R_ALPHA_GPDISP 6 /* Add displacement to GP */ +#define R_ALPHA_BRADDR 7 /* PC+4 relative 23 bit shifted */ +#define R_ALPHA_HINT 8 /* PC+4 relative 16 bit shifted */ +#define R_ALPHA_SREL16 9 /* PC relative 16 bit */ +#define R_ALPHA_SREL32 10 /* PC relative 32 bit */ +#define R_ALPHA_SREL64 11 /* PC relative 64 bit */ +#define R_ALPHA_GPRELHIGH 17 /* GP relative 32 bit, high 16 bits */ +#define R_ALPHA_GPRELLOW 18 /* GP relative 32 bit, low 16 bits */ +#define R_ALPHA_GPREL16 19 /* GP relative 16 bit */ +#define R_ALPHA_COPY 24 /* Copy symbol at runtime */ +#define R_ALPHA_GLOB_DAT 25 /* Create GOT entry */ +#define R_ALPHA_JMP_SLOT 26 /* Create PLT entry */ +#define R_ALPHA_RELATIVE 27 /* Adjust by program base */ +#define R_ALPHA_BRSGP 28 +#define R_ALPHA_TLSGD 29 +#define R_ALPHA_TLS_LDM 30 +#define R_ALPHA_DTPMOD64 31 +#define R_ALPHA_GOTDTPREL 32 +#define R_ALPHA_DTPREL64 33 +#define R_ALPHA_DTPRELHI 34 +#define R_ALPHA_DTPRELLO 35 +#define R_ALPHA_DTPREL16 36 +#define R_ALPHA_GOTTPREL 37 +#define R_ALPHA_TPREL64 38 +#define R_ALPHA_TPRELHI 39 +#define R_ALPHA_TPRELLO 40 +#define R_ALPHA_TPREL16 41 + +#define SHF_ALPHA_GPREL 0x10000000 + + +/* PowerPC relocations defined by the ABIs */ +#define R_PPC_NONE 0 +#define R_PPC_ADDR32 1 /* 32bit absolute address */ +#define R_PPC_ADDR24 2 /* 26bit address, 2 bits ignored. */ +#define R_PPC_ADDR16 3 /* 16bit absolute address */ +#define R_PPC_ADDR16_LO 4 /* lower 16bit of absolute address */ +#define R_PPC_ADDR16_HI 5 /* high 16bit of absolute address */ +#define R_PPC_ADDR16_HA 6 /* adjusted high 16bit */ +#define R_PPC_ADDR14 7 /* 16bit address, 2 bits ignored */ +#define R_PPC_ADDR14_BRTAKEN 8 +#define R_PPC_ADDR14_BRNTAKEN 9 +#define R_PPC_REL24 10 /* PC relative 26 bit */ +#define R_PPC_REL14 11 /* PC relative 16 bit */ +#define R_PPC_REL14_BRTAKEN 12 +#define R_PPC_REL14_BRNTAKEN 13 +#define R_PPC_GOT16 14 +#define R_PPC_GOT16_LO 15 +#define R_PPC_GOT16_HI 16 +#define R_PPC_GOT16_HA 17 +#define R_PPC_PLTREL24 18 +#define R_PPC_COPY 19 +#define R_PPC_GLOB_DAT 20 +#define R_PPC_JMP_SLOT 21 +#define R_PPC_RELATIVE 22 +#define R_PPC_LOCAL24PC 23 +#define R_PPC_UADDR32 24 +#define R_PPC_UADDR16 25 +#define R_PPC_REL32 26 +#define R_PPC_PLT32 27 +#define R_PPC_PLTREL32 28 +#define R_PPC_PLT16_LO 29 +#define R_PPC_PLT16_HI 30 +#define R_PPC_PLT16_HA 31 +#define R_PPC_SDAREL16 32 +#define R_PPC_SECTOFF 33 +#define R_PPC_SECTOFF_LO 34 +#define R_PPC_SECTOFF_HI 35 +#define R_PPC_SECTOFF_HA 36 +/* Keep this the last entry. */ +#ifndef R_PPC_NUM +#define R_PPC_NUM 37 +#endif + +/* ARM specific declarations */ + +/* Processor specific flags for the ELF header e_flags field. */ +#define EF_ARM_RELEXEC 0x01 +#define EF_ARM_HASENTRY 0x02 +#define EF_ARM_INTERWORK 0x04 +#define EF_ARM_APCS_26 0x08 +#define EF_ARM_APCS_FLOAT 0x10 +#define EF_ARM_PIC 0x20 +#define EF_ALIGN8 0x40 /* 8-bit structure alignment is in use */ +#define EF_NEW_ABI 0x80 +#define EF_OLD_ABI 0x100 + +/* Additional symbol types for Thumb */ +#define STT_ARM_TFUNC 0xd + +/* ARM-specific values for sh_flags */ +#define SHF_ARM_ENTRYSECT 0x10000000 /* Section contains an entry point */ +#define SHF_ARM_COMDEF 0x80000000 /* Section may be multiply defined + in the input to a link step */ + +/* ARM-specific program header flags */ +#define PF_ARM_SB 0x10000000 /* Segment contains the location + addressed by the static base */ + +/* ARM relocs. */ +#define R_ARM_NONE 0 /* No reloc */ +#define R_ARM_PC24 1 /* PC relative 26 bit branch */ +#define R_ARM_ABS32 2 /* Direct 32 bit */ +#define R_ARM_REL32 3 /* PC relative 32 bit */ +#define R_ARM_PC13 4 +#define R_ARM_ABS16 5 /* Direct 16 bit */ +#define R_ARM_ABS12 6 /* Direct 12 bit */ +#define R_ARM_THM_ABS5 7 +#define R_ARM_ABS8 8 /* Direct 8 bit */ +#define R_ARM_SBREL32 9 +#define R_ARM_THM_PC22 10 +#define R_ARM_THM_PC8 11 +#define R_ARM_AMP_VCALL9 12 +#define R_ARM_SWI24 13 +#define R_ARM_THM_SWI8 14 +#define R_ARM_XPC25 15 +#define R_ARM_THM_XPC22 16 +#define R_ARM_COPY 20 /* Copy symbol at runtime */ +#define R_ARM_GLOB_DAT 21 /* Create GOT entry */ +#define R_ARM_JUMP_SLOT 22 /* Create PLT entry */ +#define R_ARM_RELATIVE 23 /* Adjust by program base */ +#define R_ARM_GOTOFF 24 /* 32 bit offset to GOT */ +#define R_ARM_GOTPC 25 /* 32 bit PC relative offset to GOT */ +#define R_ARM_GOT32 26 /* 32 bit GOT entry */ +#define R_ARM_PLT32 27 /* 32 bit PLT address */ +#define R_ARM_CALL 28 +#define R_ARM_JUMP24 29 +#define R_ARM_GNU_VTENTRY 100 +#define R_ARM_GNU_VTINHERIT 101 +#define R_ARM_THM_PC11 102 /* thumb unconditional branch */ +#define R_ARM_THM_PC9 103 /* thumb conditional branch */ +#define R_ARM_RXPC25 249 +#define R_ARM_RSBREL32 250 +#define R_ARM_THM_RPC22 251 +#define R_ARM_RREL32 252 +#define R_ARM_RABS22 253 +#define R_ARM_RPC24 254 +#define R_ARM_RBASE 255 +/* Keep this the last entry. */ +#define R_ARM_NUM 256 + +/* s390 relocations defined by the ABIs */ +#define R_390_NONE 0 /* No reloc. */ +#define R_390_8 1 /* Direct 8 bit. */ +#define R_390_12 2 /* Direct 12 bit. */ +#define R_390_16 3 /* Direct 16 bit. */ +#define R_390_32 4 /* Direct 32 bit. */ +#define R_390_PC32 5 /* PC relative 32 bit. */ +#define R_390_GOT12 6 /* 12 bit GOT offset. */ +#define R_390_GOT32 7 /* 32 bit GOT offset. */ +#define R_390_PLT32 8 /* 32 bit PC relative PLT address. */ +#define R_390_COPY 9 /* Copy symbol at runtime. */ +#define R_390_GLOB_DAT 10 /* Create GOT entry. */ +#define R_390_JMP_SLOT 11 /* Create PLT entry. */ +#define R_390_RELATIVE 12 /* Adjust by program base. */ +#define R_390_GOTOFF32 13 /* 32 bit offset to GOT. */ +#define R_390_GOTPC 14 /* 32 bit PC rel. offset to GOT. */ +#define R_390_GOT16 15 /* 16 bit GOT offset. */ +#define R_390_PC16 16 /* PC relative 16 bit. */ +#define R_390_PC16DBL 17 /* PC relative 16 bit shifted by 1. */ +#define R_390_PLT16DBL 18 /* 16 bit PC rel. PLT shifted by 1. */ +#define R_390_PC32DBL 19 /* PC relative 32 bit shifted by 1. */ +#define R_390_PLT32DBL 20 /* 32 bit PC rel. PLT shifted by 1. */ +#define R_390_GOTPCDBL 21 /* 32 bit PC rel. GOT shifted by 1. */ +#define R_390_64 22 /* Direct 64 bit. */ +#define R_390_PC64 23 /* PC relative 64 bit. */ +#define R_390_GOT64 24 /* 64 bit GOT offset. */ +#define R_390_PLT64 25 /* 64 bit PC relative PLT address. */ +#define R_390_GOTENT 26 /* 32 bit PC rel. to GOT entry >> 1. */ +#define R_390_GOTOFF16 27 /* 16 bit offset to GOT. */ +#define R_390_GOTOFF64 28 /* 64 bit offset to GOT. */ +#define R_390_GOTPLT12 29 /* 12 bit offset to jump slot. */ +#define R_390_GOTPLT16 30 /* 16 bit offset to jump slot. */ +#define R_390_GOTPLT32 31 /* 32 bit offset to jump slot. */ +#define R_390_GOTPLT64 32 /* 64 bit offset to jump slot. */ +#define R_390_GOTPLTENT 33 /* 32 bit rel. offset to jump slot. */ +#define R_390_PLTOFF16 34 /* 16 bit offset from GOT to PLT. */ +#define R_390_PLTOFF32 35 /* 32 bit offset from GOT to PLT. */ +#define R_390_PLTOFF64 36 /* 16 bit offset from GOT to PLT. */ +#define R_390_TLS_LOAD 37 /* Tag for load insn in TLS code. */ +#define R_390_TLS_GDCALL 38 /* Tag for function call in general + dynamic TLS code. */ +#define R_390_TLS_LDCALL 39 /* Tag for function call in local + dynamic TLS code. */ +#define R_390_TLS_GD32 40 /* Direct 32 bit for general dynamic + thread local data. */ +#define R_390_TLS_GD64 41 /* Direct 64 bit for general dynamic + thread local data. */ +#define R_390_TLS_GOTIE12 42 /* 12 bit GOT offset for static TLS + block offset. */ +#define R_390_TLS_GOTIE32 43 /* 32 bit GOT offset for static TLS + block offset. */ +#define R_390_TLS_GOTIE64 44 /* 64 bit GOT offset for static TLS + block offset. */ +#define R_390_TLS_LDM32 45 /* Direct 32 bit for local dynamic + thread local data in LD code. */ +#define R_390_TLS_LDM64 46 /* Direct 64 bit for local dynamic + thread local data in LD code. */ +#define R_390_TLS_IE32 47 /* 32 bit address of GOT entry for + negated static TLS block offset. */ +#define R_390_TLS_IE64 48 /* 64 bit address of GOT entry for + negated static TLS block offset. */ +#define R_390_TLS_IEENT 49 /* 32 bit rel. offset to GOT entry for + negated static TLS block offset. */ +#define R_390_TLS_LE32 50 /* 32 bit negated offset relative to + static TLS block. */ +#define R_390_TLS_LE64 51 /* 64 bit negated offset relative to + static TLS block. */ +#define R_390_TLS_LDO32 52 /* 32 bit offset relative to TLS + block. */ +#define R_390_TLS_LDO64 53 /* 64 bit offset relative to TLS + block. */ +#define R_390_TLS_DTPMOD 54 /* ID of module containing symbol. */ +#define R_390_TLS_DTPOFF 55 /* Offset in TLS block. */ +#define R_390_TLS_TPOFF 56 /* Negate offset in static TLS + block. */ +/* Keep this the last entry. */ +#define R_390_NUM 57 + +/* x86-64 relocation types */ +#define R_X86_64_NONE 0 /* No reloc */ +#define R_X86_64_64 1 /* Direct 64 bit */ +#define R_X86_64_PC32 2 /* PC relative 32 bit signed */ +#define R_X86_64_GOT32 3 /* 32 bit GOT entry */ +#define R_X86_64_PLT32 4 /* 32 bit PLT address */ +#define R_X86_64_COPY 5 /* Copy symbol at runtime */ +#define R_X86_64_GLOB_DAT 6 /* Create GOT entry */ +#define R_X86_64_JUMP_SLOT 7 /* Create PLT entry */ +#define R_X86_64_RELATIVE 8 /* Adjust by program base */ +#define R_X86_64_GOTPCREL 9 /* 32 bit signed pc relative + offset to GOT */ +#define R_X86_64_32 10 /* Direct 32 bit zero extended */ +#define R_X86_64_32S 11 /* Direct 32 bit sign extended */ +#define R_X86_64_16 12 /* Direct 16 bit zero extended */ +#define R_X86_64_PC16 13 /* 16 bit sign extended pc relative */ +#define R_X86_64_8 14 /* Direct 8 bit sign extended */ +#define R_X86_64_PC8 15 /* 8 bit sign extended pc relative */ + +#define R_X86_64_NUM 16 + +/* Legal values for e_flags field of Elf64_Ehdr. */ + +#define EF_ALPHA_32BIT 1 /* All addresses are below 2GB */ + +/* HPPA specific definitions. */ + +/* Legal values for e_flags field of Elf32_Ehdr. */ + +#define EF_PARISC_TRAPNIL 0x00010000 /* Trap nil pointer dereference. */ +#define EF_PARISC_EXT 0x00020000 /* Program uses arch. extensions. */ +#define EF_PARISC_LSB 0x00040000 /* Program expects little endian. */ +#define EF_PARISC_WIDE 0x00080000 /* Program expects wide mode. */ +#define EF_PARISC_NO_KABP 0x00100000 /* No kernel assisted branch + prediction. */ +#define EF_PARISC_LAZYSWAP 0x00400000 /* Allow lazy swapping. */ +#define EF_PARISC_ARCH 0x0000ffff /* Architecture version. */ + +/* Defined values for `e_flags & EF_PARISC_ARCH' are: */ + +#define EFA_PARISC_1_0 0x020b /* PA-RISC 1.0 big-endian. */ +#define EFA_PARISC_1_1 0x0210 /* PA-RISC 1.1 big-endian. */ +#define EFA_PARISC_2_0 0x0214 /* PA-RISC 2.0 big-endian. */ + +/* Additional section indeces. */ + +#define SHN_PARISC_ANSI_COMMON 0xff00 /* Section for tenatively declared + symbols in ANSI C. */ +#define SHN_PARISC_HUGE_COMMON 0xff01 /* Common blocks in huge model. */ + +/* Legal values for sh_type field of Elf32_Shdr. */ + +#define SHT_PARISC_EXT 0x70000000 /* Contains product specific ext. */ +#define SHT_PARISC_UNWIND 0x70000001 /* Unwind information. */ +#define SHT_PARISC_DOC 0x70000002 /* Debug info for optimized code. */ + +/* Legal values for sh_flags field of Elf32_Shdr. */ + +#define SHF_PARISC_SHORT 0x20000000 /* Section with short addressing. */ +#define SHF_PARISC_HUGE 0x40000000 /* Section far from gp. */ +#define SHF_PARISC_SBP 0x80000000 /* Static branch prediction code. */ + +/* Legal values for ST_TYPE subfield of st_info (symbol type). */ + +#define STT_PARISC_MILLICODE 13 /* Millicode function entry point. */ + +#define STT_HP_OPAQUE (STT_LOOS + 0x1) +#define STT_HP_STUB (STT_LOOS + 0x2) + +/* HPPA relocs. */ + +#define R_PARISC_NONE 0 /* No reloc. */ +#define R_PARISC_DIR32 1 /* Direct 32-bit reference. */ +#define R_PARISC_DIR21L 2 /* Left 21 bits of eff. address. */ +#define R_PARISC_DIR17R 3 /* Right 17 bits of eff. address. */ +#define R_PARISC_DIR17F 4 /* 17 bits of eff. address. */ +#define R_PARISC_DIR14R 6 /* Right 14 bits of eff. address. */ +#define R_PARISC_PCREL32 9 /* 32-bit rel. address. */ +#define R_PARISC_PCREL21L 10 /* Left 21 bits of rel. address. */ +#define R_PARISC_PCREL17R 11 /* Right 17 bits of rel. address. */ +#define R_PARISC_PCREL17F 12 /* 17 bits of rel. address. */ +#define R_PARISC_PCREL14R 14 /* Right 14 bits of rel. address. */ +#define R_PARISC_DPREL21L 18 /* Left 21 bits of rel. address. */ +#define R_PARISC_DPREL14R 22 /* Right 14 bits of rel. address. */ +#define R_PARISC_GPREL21L 26 /* GP-relative, left 21 bits. */ +#define R_PARISC_GPREL14R 30 /* GP-relative, right 14 bits. */ +#define R_PARISC_LTOFF21L 34 /* LT-relative, left 21 bits. */ +#define R_PARISC_LTOFF14R 38 /* LT-relative, right 14 bits. */ +#define R_PARISC_SECREL32 41 /* 32 bits section rel. address. */ +#define R_PARISC_SEGBASE 48 /* No relocation, set segment base. */ +#define R_PARISC_SEGREL32 49 /* 32 bits segment rel. address. */ +#define R_PARISC_PLTOFF21L 50 /* PLT rel. address, left 21 bits. */ +#define R_PARISC_PLTOFF14R 54 /* PLT rel. address, right 14 bits. */ +#define R_PARISC_LTOFF_FPTR32 57 /* 32 bits LT-rel. function pointer. */ +#define R_PARISC_LTOFF_FPTR21L 58 /* LT-rel. fct ptr, left 21 bits. */ +#define R_PARISC_LTOFF_FPTR14R 62 /* LT-rel. fct ptr, right 14 bits. */ +#define R_PARISC_FPTR64 64 /* 64 bits function address. */ +#define R_PARISC_PLABEL32 65 /* 32 bits function address. */ +#define R_PARISC_PCREL64 72 /* 64 bits PC-rel. address. */ +#define R_PARISC_PCREL22F 74 /* 22 bits PC-rel. address. */ +#define R_PARISC_PCREL14WR 75 /* PC-rel. address, right 14 bits. */ +#define R_PARISC_PCREL14DR 76 /* PC rel. address, right 14 bits. */ +#define R_PARISC_PCREL16F 77 /* 16 bits PC-rel. address. */ +#define R_PARISC_PCREL16WF 78 /* 16 bits PC-rel. address. */ +#define R_PARISC_PCREL16DF 79 /* 16 bits PC-rel. address. */ +#define R_PARISC_DIR64 80 /* 64 bits of eff. address. */ +#define R_PARISC_DIR14WR 83 /* 14 bits of eff. address. */ +#define R_PARISC_DIR14DR 84 /* 14 bits of eff. address. */ +#define R_PARISC_DIR16F 85 /* 16 bits of eff. address. */ +#define R_PARISC_DIR16WF 86 /* 16 bits of eff. address. */ +#define R_PARISC_DIR16DF 87 /* 16 bits of eff. address. */ +#define R_PARISC_GPREL64 88 /* 64 bits of GP-rel. address. */ +#define R_PARISC_GPREL14WR 91 /* GP-rel. address, right 14 bits. */ +#define R_PARISC_GPREL14DR 92 /* GP-rel. address, right 14 bits. */ +#define R_PARISC_GPREL16F 93 /* 16 bits GP-rel. address. */ +#define R_PARISC_GPREL16WF 94 /* 16 bits GP-rel. address. */ +#define R_PARISC_GPREL16DF 95 /* 16 bits GP-rel. address. */ +#define R_PARISC_LTOFF64 96 /* 64 bits LT-rel. address. */ +#define R_PARISC_LTOFF14WR 99 /* LT-rel. address, right 14 bits. */ +#define R_PARISC_LTOFF14DR 100 /* LT-rel. address, right 14 bits. */ +#define R_PARISC_LTOFF16F 101 /* 16 bits LT-rel. address. */ +#define R_PARISC_LTOFF16WF 102 /* 16 bits LT-rel. address. */ +#define R_PARISC_LTOFF16DF 103 /* 16 bits LT-rel. address. */ +#define R_PARISC_SECREL64 104 /* 64 bits section rel. address. */ +#define R_PARISC_SEGREL64 112 /* 64 bits segment rel. address. */ +#define R_PARISC_PLTOFF14WR 115 /* PLT-rel. address, right 14 bits. */ +#define R_PARISC_PLTOFF14DR 116 /* PLT-rel. address, right 14 bits. */ +#define R_PARISC_PLTOFF16F 117 /* 16 bits LT-rel. address. */ +#define R_PARISC_PLTOFF16WF 118 /* 16 bits PLT-rel. address. */ +#define R_PARISC_PLTOFF16DF 119 /* 16 bits PLT-rel. address. */ +#define R_PARISC_LTOFF_FPTR64 120 /* 64 bits LT-rel. function ptr. */ +#define R_PARISC_LTOFF_FPTR14WR 123 /* LT-rel. fct. ptr., right 14 bits. */ +#define R_PARISC_LTOFF_FPTR14DR 124 /* LT-rel. fct. ptr., right 14 bits. */ +#define R_PARISC_LTOFF_FPTR16F 125 /* 16 bits LT-rel. function ptr. */ +#define R_PARISC_LTOFF_FPTR16WF 126 /* 16 bits LT-rel. function ptr. */ +#define R_PARISC_LTOFF_FPTR16DF 127 /* 16 bits LT-rel. function ptr. */ +#define R_PARISC_LORESERVE 128 +#define R_PARISC_COPY 128 /* Copy relocation. */ +#define R_PARISC_IPLT 129 /* Dynamic reloc, imported PLT */ +#define R_PARISC_EPLT 130 /* Dynamic reloc, exported PLT */ +#define R_PARISC_TPREL32 153 /* 32 bits TP-rel. address. */ +#define R_PARISC_TPREL21L 154 /* TP-rel. address, left 21 bits. */ +#define R_PARISC_TPREL14R 158 /* TP-rel. address, right 14 bits. */ +#define R_PARISC_LTOFF_TP21L 162 /* LT-TP-rel. address, left 21 bits. */ +#define R_PARISC_LTOFF_TP14R 166 /* LT-TP-rel. address, right 14 bits.*/ +#define R_PARISC_LTOFF_TP14F 167 /* 14 bits LT-TP-rel. address. */ +#define R_PARISC_TPREL64 216 /* 64 bits TP-rel. address. */ +#define R_PARISC_TPREL14WR 219 /* TP-rel. address, right 14 bits. */ +#define R_PARISC_TPREL14DR 220 /* TP-rel. address, right 14 bits. */ +#define R_PARISC_TPREL16F 221 /* 16 bits TP-rel. address. */ +#define R_PARISC_TPREL16WF 222 /* 16 bits TP-rel. address. */ +#define R_PARISC_TPREL16DF 223 /* 16 bits TP-rel. address. */ +#define R_PARISC_LTOFF_TP64 224 /* 64 bits LT-TP-rel. address. */ +#define R_PARISC_LTOFF_TP14WR 227 /* LT-TP-rel. address, right 14 bits.*/ +#define R_PARISC_LTOFF_TP14DR 228 /* LT-TP-rel. address, right 14 bits.*/ +#define R_PARISC_LTOFF_TP16F 229 /* 16 bits LT-TP-rel. address. */ +#define R_PARISC_LTOFF_TP16WF 230 /* 16 bits LT-TP-rel. address. */ +#define R_PARISC_LTOFF_TP16DF 231 /* 16 bits LT-TP-rel. address. */ +#define R_PARISC_HIRESERVE 255 + +/* Legal values for p_type field of Elf32_Phdr/Elf64_Phdr. */ + +#define PT_HP_TLS (PT_LOOS + 0x0) +#define PT_HP_CORE_NONE (PT_LOOS + 0x1) +#define PT_HP_CORE_VERSION (PT_LOOS + 0x2) +#define PT_HP_CORE_KERNEL (PT_LOOS + 0x3) +#define PT_HP_CORE_COMM (PT_LOOS + 0x4) +#define PT_HP_CORE_PROC (PT_LOOS + 0x5) +#define PT_HP_CORE_LOADABLE (PT_LOOS + 0x6) +#define PT_HP_CORE_STACK (PT_LOOS + 0x7) +#define PT_HP_CORE_SHM (PT_LOOS + 0x8) +#define PT_HP_CORE_MMF (PT_LOOS + 0x9) +#define PT_HP_PARALLEL (PT_LOOS + 0x10) +#define PT_HP_FASTBIND (PT_LOOS + 0x11) +#define PT_HP_OPT_ANNOT (PT_LOOS + 0x12) +#define PT_HP_HSL_ANNOT (PT_LOOS + 0x13) +#define PT_HP_STACK (PT_LOOS + 0x14) + +#define PT_PARISC_ARCHEXT 0x70000000 +#define PT_PARISC_UNWIND 0x70000001 + +/* Legal values for p_flags field of Elf32_Phdr/Elf64_Phdr. */ + +#define PF_PARISC_SBP 0x08000000 + +#define PF_HP_PAGE_SIZE 0x00100000 +#define PF_HP_FAR_SHARED 0x00200000 +#define PF_HP_NEAR_SHARED 0x00400000 +#define PF_HP_CODE 0x01000000 +#define PF_HP_MODIFY 0x02000000 +#define PF_HP_LAZYSWAP 0x04000000 +#define PF_HP_SBP 0x08000000 + +/* IA-64 specific declarations. */ + +/* Processor specific flags for the Ehdr e_flags field. */ +#define EF_IA_64_MASKOS 0x0000000f /* os-specific flags */ +#define EF_IA_64_ABI64 0x00000010 /* 64-bit ABI */ +#define EF_IA_64_ARCH 0xff000000 /* arch. version mask */ + +/* Processor specific values for the Phdr p_type field. */ +#define PT_IA_64_ARCHEXT (PT_LOPROC + 0) /* arch extension bits */ +#define PT_IA_64_UNWIND (PT_LOPROC + 1) /* ia64 unwind bits */ + +/* Processor specific flags for the Phdr p_flags field. */ +#define PF_IA_64_NORECOV 0x80000000 /* spec insns w/o recovery */ + +/* Processor specific values for the Shdr sh_type field. */ +#define SHT_IA_64_EXT (SHT_LOPROC + 0) /* extension bits */ +#define SHT_IA_64_UNWIND (SHT_LOPROC + 1) /* unwind bits */ + +/* Processor specific flags for the Shdr sh_flags field. */ +#define SHF_IA_64_SHORT 0x10000000 /* section near gp */ +#define SHF_IA_64_NORECOV 0x20000000 /* spec insns w/o recovery */ + +/* Processor specific values for the Dyn d_tag field. */ +#define DT_IA_64_PLT_RESERVE (DT_LOPROC + 0) +#define DT_IA_64_NUM 1 + +/* IA-64 relocations. */ +#define R_IA64_NONE 0x00 /* none */ +#define R_IA64_IMM14 0x21 /* symbol + addend, add imm14 */ +#define R_IA64_IMM22 0x22 /* symbol + addend, add imm22 */ +#define R_IA64_IMM64 0x23 /* symbol + addend, mov imm64 */ +#define R_IA64_DIR32MSB 0x24 /* symbol + addend, data4 MSB */ +#define R_IA64_DIR32LSB 0x25 /* symbol + addend, data4 LSB */ +#define R_IA64_DIR64MSB 0x26 /* symbol + addend, data8 MSB */ +#define R_IA64_DIR64LSB 0x27 /* symbol + addend, data8 LSB */ +#define R_IA64_GPREL22 0x2a /* @gprel(sym + add), add imm22 */ +#define R_IA64_GPREL64I 0x2b /* @gprel(sym + add), mov imm64 */ +#define R_IA64_GPREL32MSB 0x2c /* @gprel(sym + add), data4 MSB */ +#define R_IA64_GPREL32LSB 0x2d /* @gprel(sym + add), data4 LSB */ +#define R_IA64_GPREL64MSB 0x2e /* @gprel(sym + add), data8 MSB */ +#define R_IA64_GPREL64LSB 0x2f /* @gprel(sym + add), data8 LSB */ +#define R_IA64_LTOFF22 0x32 /* @ltoff(sym + add), add imm22 */ +#define R_IA64_LTOFF64I 0x33 /* @ltoff(sym + add), mov imm64 */ +#define R_IA64_PLTOFF22 0x3a /* @pltoff(sym + add), add imm22 */ +#define R_IA64_PLTOFF64I 0x3b /* @pltoff(sym + add), mov imm64 */ +#define R_IA64_PLTOFF64MSB 0x3e /* @pltoff(sym + add), data8 MSB */ +#define R_IA64_PLTOFF64LSB 0x3f /* @pltoff(sym + add), data8 LSB */ +#define R_IA64_FPTR64I 0x43 /* @fptr(sym + add), mov imm64 */ +#define R_IA64_FPTR32MSB 0x44 /* @fptr(sym + add), data4 MSB */ +#define R_IA64_FPTR32LSB 0x45 /* @fptr(sym + add), data4 LSB */ +#define R_IA64_FPTR64MSB 0x46 /* @fptr(sym + add), data8 MSB */ +#define R_IA64_FPTR64LSB 0x47 /* @fptr(sym + add), data8 LSB */ +#define R_IA64_PCREL60B 0x48 /* @pcrel(sym + add), brl */ +#define R_IA64_PCREL21B 0x49 /* @pcrel(sym + add), ptb, call */ +#define R_IA64_PCREL21M 0x4a /* @pcrel(sym + add), chk.s */ +#define R_IA64_PCREL21F 0x4b /* @pcrel(sym + add), fchkf */ +#define R_IA64_PCREL32MSB 0x4c /* @pcrel(sym + add), data4 MSB */ +#define R_IA64_PCREL32LSB 0x4d /* @pcrel(sym + add), data4 LSB */ +#define R_IA64_PCREL64MSB 0x4e /* @pcrel(sym + add), data8 MSB */ +#define R_IA64_PCREL64LSB 0x4f /* @pcrel(sym + add), data8 LSB */ +#define R_IA64_LTOFF_FPTR22 0x52 /* @ltoff(@fptr(s+a)), imm22 */ +#define R_IA64_LTOFF_FPTR64I 0x53 /* @ltoff(@fptr(s+a)), imm64 */ +#define R_IA64_LTOFF_FPTR32MSB 0x54 /* @ltoff(@fptr(s+a)), data4 MSB */ +#define R_IA64_LTOFF_FPTR32LSB 0x55 /* @ltoff(@fptr(s+a)), data4 LSB */ +#define R_IA64_LTOFF_FPTR64MSB 0x56 /* @ltoff(@fptr(s+a)), data8 MSB */ +#define R_IA64_LTOFF_FPTR64LSB 0x57 /* @ltoff(@fptr(s+a)), data8 LSB */ +#define R_IA64_SEGREL32MSB 0x5c /* @segrel(sym + add), data4 MSB */ +#define R_IA64_SEGREL32LSB 0x5d /* @segrel(sym + add), data4 LSB */ +#define R_IA64_SEGREL64MSB 0x5e /* @segrel(sym + add), data8 MSB */ +#define R_IA64_SEGREL64LSB 0x5f /* @segrel(sym + add), data8 LSB */ +#define R_IA64_SECREL32MSB 0x64 /* @secrel(sym + add), data4 MSB */ +#define R_IA64_SECREL32LSB 0x65 /* @secrel(sym + add), data4 LSB */ +#define R_IA64_SECREL64MSB 0x66 /* @secrel(sym + add), data8 MSB */ +#define R_IA64_SECREL64LSB 0x67 /* @secrel(sym + add), data8 LSB */ +#define R_IA64_REL32MSB 0x6c /* data 4 + REL */ +#define R_IA64_REL32LSB 0x6d /* data 4 + REL */ +#define R_IA64_REL64MSB 0x6e /* data 8 + REL */ +#define R_IA64_REL64LSB 0x6f /* data 8 + REL */ +#define R_IA64_LTV32MSB 0x74 /* symbol + addend, data4 MSB */ +#define R_IA64_LTV32LSB 0x75 /* symbol + addend, data4 LSB */ +#define R_IA64_LTV64MSB 0x76 /* symbol + addend, data8 MSB */ +#define R_IA64_LTV64LSB 0x77 /* symbol + addend, data8 LSB */ +#define R_IA64_PCREL21BI 0x79 /* @pcrel(sym + add), 21bit inst */ +#define R_IA64_PCREL22 0x7a /* @pcrel(sym + add), 22bit inst */ +#define R_IA64_PCREL64I 0x7b /* @pcrel(sym + add), 64bit inst */ +#define R_IA64_IPLTMSB 0x80 /* dynamic reloc, imported PLT, MSB */ +#define R_IA64_IPLTLSB 0x81 /* dynamic reloc, imported PLT, LSB */ +#define R_IA64_COPY 0x84 /* copy relocation */ +#define R_IA64_SUB 0x85 /* Addend and symbol difference */ +#define R_IA64_LTOFF22X 0x86 /* LTOFF22, relaxable. */ +#define R_IA64_LDXMOV 0x87 /* Use of LTOFF22X. */ +#define R_IA64_TPREL14 0x91 /* @tprel(sym + add), imm14 */ +#define R_IA64_TPREL22 0x92 /* @tprel(sym + add), imm22 */ +#define R_IA64_TPREL64I 0x93 /* @tprel(sym + add), imm64 */ +#define R_IA64_TPREL64MSB 0x96 /* @tprel(sym + add), data8 MSB */ +#define R_IA64_TPREL64LSB 0x97 /* @tprel(sym + add), data8 LSB */ +#define R_IA64_LTOFF_TPREL22 0x9a /* @ltoff(@tprel(s+a)), imm2 */ +#define R_IA64_DTPMOD64MSB 0xa6 /* @dtpmod(sym + add), data8 MSB */ +#define R_IA64_DTPMOD64LSB 0xa7 /* @dtpmod(sym + add), data8 LSB */ +#define R_IA64_LTOFF_DTPMOD22 0xaa /* @ltoff(@dtpmod(sym + add)), imm22 */ +#define R_IA64_DTPREL14 0xb1 /* @dtprel(sym + add), imm14 */ +#define R_IA64_DTPREL22 0xb2 /* @dtprel(sym + add), imm22 */ +#define R_IA64_DTPREL64I 0xb3 /* @dtprel(sym + add), imm64 */ +#define R_IA64_DTPREL32MSB 0xb4 /* @dtprel(sym + add), data4 MSB */ +#define R_IA64_DTPREL32LSB 0xb5 /* @dtprel(sym + add), data4 LSB */ +#define R_IA64_DTPREL64MSB 0xb6 /* @dtprel(sym + add), data8 MSB */ +#define R_IA64_DTPREL64LSB 0xb7 /* @dtprel(sym + add), data8 LSB */ +#define R_IA64_LTOFF_DTPREL22 0xba /* @ltoff(@dtprel(s+a)), imm22 */ + +typedef struct elf32_rel { + Elf32_Addr r_offset; + Elf32_Word r_info; +} Elf32_Rel; + +typedef struct elf64_rel { + Elf64_Addr r_offset; /* Location at which to apply the action */ + Elf64_Xword r_info; /* index and type of relocation */ +} Elf64_Rel; + +typedef struct elf32_rela{ + Elf32_Addr r_offset; + Elf32_Word r_info; + Elf32_Sword r_addend; +} Elf32_Rela; + +typedef struct elf64_rela { + Elf64_Addr r_offset; /* Location at which to apply the action */ + Elf64_Xword r_info; /* index and type of relocation */ + Elf64_Sxword r_addend; /* Constant addend used to compute value */ +} Elf64_Rela; + +typedef struct elf32_sym{ + Elf32_Word st_name; + Elf32_Addr st_value; + Elf32_Word st_size; + unsigned char st_info; + unsigned char st_other; + Elf32_Half st_shndx; +} Elf32_Sym; + +typedef struct elf64_sym { + Elf64_Word st_name; /* Symbol name, index in string tbl */ + unsigned char st_info; /* Type and binding attributes */ + unsigned char st_other; /* No defined meaning, 0 */ + Elf64_Half st_shndx; /* Associated section index */ + Elf64_Addr st_value; /* Value of the symbol */ + Elf64_Xword st_size; /* Associated symbol size */ +} Elf64_Sym; + + +#define EI_NIDENT 16 + +typedef struct elf32_hdr{ + unsigned char e_ident[EI_NIDENT]; + Elf32_Half e_type; + Elf32_Half e_machine; + Elf32_Word e_version; + Elf32_Addr e_entry; /* Entry point */ + Elf32_Off e_phoff; + Elf32_Off e_shoff; + Elf32_Word e_flags; + Elf32_Half e_ehsize; + Elf32_Half e_phentsize; + Elf32_Half e_phnum; + Elf32_Half e_shentsize; + Elf32_Half e_shnum; + Elf32_Half e_shstrndx; +} Elf32_Ehdr; + +typedef struct elf64_hdr { + unsigned char e_ident[16]; /* ELF "magic number" */ + Elf64_Half e_type; + Elf64_Half e_machine; + Elf64_Word e_version; + Elf64_Addr e_entry; /* Entry point virtual address */ + Elf64_Off e_phoff; /* Program header table file offset */ + Elf64_Off e_shoff; /* Section header table file offset */ + Elf64_Word e_flags; + Elf64_Half e_ehsize; + Elf64_Half e_phentsize; + Elf64_Half e_phnum; + Elf64_Half e_shentsize; + Elf64_Half e_shnum; + Elf64_Half e_shstrndx; +} Elf64_Ehdr; + +/* These constants define the permissions on sections in the program + header, p_flags. */ +#define PF_R 0x4 +#define PF_W 0x2 +#define PF_X 0x1 + +typedef struct elf32_phdr{ + Elf32_Word p_type; + Elf32_Off p_offset; + Elf32_Addr p_vaddr; + Elf32_Addr p_paddr; + Elf32_Word p_filesz; + Elf32_Word p_memsz; + Elf32_Word p_flags; + Elf32_Word p_align; +} Elf32_Phdr; + +typedef struct elf64_phdr { + Elf64_Word p_type; + Elf64_Word p_flags; + Elf64_Off p_offset; /* Segment file offset */ + Elf64_Addr p_vaddr; /* Segment virtual address */ + Elf64_Addr p_paddr; /* Segment physical address */ + Elf64_Xword p_filesz; /* Segment size in file */ + Elf64_Xword p_memsz; /* Segment size in memory */ + Elf64_Xword p_align; /* Segment alignment, file & memory */ +} Elf64_Phdr; + +/* sh_type */ +#define SHT_NULL 0 +#define SHT_PROGBITS 1 +#define SHT_SYMTAB 2 +#define SHT_STRTAB 3 +#define SHT_RELA 4 +#define SHT_HASH 5 +#define SHT_DYNAMIC 6 +#define SHT_NOTE 7 +#define SHT_NOBITS 8 +#define SHT_REL 9 +#define SHT_SHLIB 10 +#define SHT_DYNSYM 11 +#define SHT_NUM 12 +#define SHT_LOPROC 0x70000000 +#define SHT_HIPROC 0x7fffffff +#define SHT_LOUSER 0x80000000 +#define SHT_HIUSER 0xffffffff +#define SHT_MIPS_LIST 0x70000000 +#define SHT_MIPS_CONFLICT 0x70000002 +#define SHT_MIPS_GPTAB 0x70000003 +#define SHT_MIPS_UCODE 0x70000004 + +/* sh_flags */ +#define SHF_WRITE 0x1 +#define SHF_ALLOC 0x2 +#define SHF_EXECINSTR 0x4 +#define SHF_MASKPROC 0xf0000000 +#define SHF_MIPS_GPREL 0x10000000 + +/* special section indexes */ +#define SHN_UNDEF 0 +#define SHN_LORESERVE 0xff00 +#define SHN_LOPROC 0xff00 +#define SHN_HIPROC 0xff1f +#define SHN_ABS 0xfff1 +#define SHN_COMMON 0xfff2 +#define SHN_HIRESERVE 0xffff +#define SHN_MIPS_ACCOMON 0xff00 + +typedef struct elf32_shdr { + Elf32_Word sh_name; + Elf32_Word sh_type; + Elf32_Word sh_flags; + Elf32_Addr sh_addr; + Elf32_Off sh_offset; + Elf32_Word sh_size; + Elf32_Word sh_link; + Elf32_Word sh_info; + Elf32_Word sh_addralign; + Elf32_Word sh_entsize; +} Elf32_Shdr; + +typedef struct elf64_shdr { + Elf64_Word sh_name; /* Section name, index in string tbl */ + Elf64_Word sh_type; /* Type of section */ + Elf64_Xword sh_flags; /* Miscellaneous section attributes */ + Elf64_Addr sh_addr; /* Section virtual addr at execution */ + Elf64_Off sh_offset; /* Section file offset */ + Elf64_Xword sh_size; /* Size of section in bytes */ + Elf64_Word sh_link; /* Index of another section */ + Elf64_Word sh_info; /* Additional section information */ + Elf64_Xword sh_addralign; /* Section alignment */ + Elf64_Xword sh_entsize; /* Entry size if section holds table */ +} Elf64_Shdr; + +#define EI_MAG0 0 /* e_ident[] indexes */ +#define EI_MAG1 1 +#define EI_MAG2 2 +#define EI_MAG3 3 +#define EI_CLASS 4 +#define EI_DATA 5 +#define EI_VERSION 6 +#define EI_OSABI 7 +#define EI_PAD 8 + +#define ELFOSABI_NONE 0 /* UNIX System V ABI */ +#define ELFOSABI_SYSV 0 /* Alias. */ +#define ELFOSABI_HPUX 1 /* HP-UX */ +#define ELFOSABI_NETBSD 2 /* NetBSD. */ +#define ELFOSABI_LINUX 3 /* Linux. */ +#define ELFOSABI_SOLARIS 6 /* Sun Solaris. */ +#define ELFOSABI_AIX 7 /* IBM AIX. */ +#define ELFOSABI_IRIX 8 /* SGI Irix. */ +#define ELFOSABI_FREEBSD 9 /* FreeBSD. */ +#define ELFOSABI_TRU64 10 /* Compaq TRU64 UNIX. */ +#define ELFOSABI_MODESTO 11 /* Novell Modesto. */ +#define ELFOSABI_OPENBSD 12 /* OpenBSD. */ +#define ELFOSABI_ARM 97 /* ARM */ +#define ELFOSABI_STANDALONE 255 /* Standalone (embedded) application */ + +#define ELFMAG0 0x7f /* EI_MAG */ +#define ELFMAG1 'E' +#define ELFMAG2 'L' +#define ELFMAG3 'F' +#define ELFMAG "\177ELF" +#define SELFMAG 4 + +#define ELFCLASSNONE 0 /* EI_CLASS */ +#define ELFCLASS32 1 +#define ELFCLASS64 2 +#define ELFCLASSNUM 3 + +#define ELFDATANONE 0 /* e_ident[EI_DATA] */ +#define ELFDATA2LSB 1 +#define ELFDATA2MSB 2 + +#define EV_NONE 0 /* e_version, EI_VERSION */ +#define EV_CURRENT 1 +#define EV_NUM 2 + +/* Notes used in ET_CORE */ +#define NT_PRSTATUS 1 +#define NT_PRFPREG 2 +#define NT_PRPSINFO 3 +#define NT_TASKSTRUCT 4 +#define NT_AUXV 6 +#define NT_PRXFPREG 0x46e62b7f /* copied from gdb5.1/include/elf/common.h */ + + +/* Note header in a PT_NOTE section */ +typedef struct elf32_note { + Elf32_Word n_namesz; /* Name size */ + Elf32_Word n_descsz; /* Content size */ + Elf32_Word n_type; /* Content type */ +} Elf32_Nhdr; + +/* Note header in a PT_NOTE section */ +typedef struct elf64_note { + Elf64_Word n_namesz; /* Name size */ + Elf64_Word n_descsz; /* Content size */ + Elf64_Word n_type; /* Content type */ +} Elf64_Nhdr; + +#ifdef ELF_CLASS +#if ELF_CLASS == ELFCLASS32 + +#define elfhdr elf32_hdr +#define elf_phdr elf32_phdr +#define elf_note elf32_note +#define elf_shdr elf32_shdr +#define elf_sym elf32_sym +#define elf_addr_t Elf32_Off + +#ifdef ELF_USES_RELOCA +# define ELF_RELOC Elf32_Rela +#else +# define ELF_RELOC Elf32_Rel +#endif + +#else + +#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_addr_t Elf64_Off + +#ifdef ELF_USES_RELOCA +# define ELF_RELOC Elf64_Rela +#else +# define ELF_RELOC Elf64_Rel +#endif + +#endif /* ELF_CLASS */ + +#ifndef ElfW +# if ELF_CLASS == ELFCLASS32 +# define ElfW(x) Elf32_ ## x +# define ELFW(x) ELF32_ ## x +# else +# define ElfW(x) Elf64_ ## x +# define ELFW(x) ELF64_ ## x +# endif +#endif + +#endif /* ELF_CLASS */ + + +#endif /* _QEMU_ELF_H */ diff --git a/envlist.c b/envlist.c new file mode 100644 index 0000000..f2303cd --- /dev/null +++ b/envlist.c @@ -0,0 +1,246 @@ +#include +#include +#include +#include +#include + +#include "qemu-queue.h" +#include "envlist.h" + +struct envlist_entry { + const char *ev_var; /* actual env value */ + QLIST_ENTRY(envlist_entry) ev_link; +}; + +struct envlist { + QLIST_HEAD(, envlist_entry) el_entries; /* actual entries */ + size_t el_count; /* number of entries */ +}; + +static int envlist_parse(envlist_t *envlist, + const char *env, int (*)(envlist_t *, const char *)); + +/* + * Allocates new envlist and returns pointer to that or + * NULL in case of error. + */ +envlist_t * +envlist_create(void) +{ + envlist_t *envlist; + + if ((envlist = malloc(sizeof (*envlist))) == NULL) + return (NULL); + + QLIST_INIT(&envlist->el_entries); + envlist->el_count = 0; + + return (envlist); +} + +/* + * Releases given envlist and its entries. + */ +void +envlist_free(envlist_t *envlist) +{ + struct envlist_entry *entry; + + assert(envlist != NULL); + + while (envlist->el_entries.lh_first != NULL) { + entry = envlist->el_entries.lh_first; + QLIST_REMOVE(entry, ev_link); + + free((char *)entry->ev_var); + free(entry); + } + free(envlist); +} + +/* + * Parses comma separated list of set/modify environment + * variable entries and updates given enlist accordingly. + * + * For example: + * envlist_parse(el, "HOME=foo,SHELL=/bin/sh"); + * + * inserts/sets environment variables HOME and SHELL. + * + * Returns 0 on success, errno otherwise. + */ +int +envlist_parse_set(envlist_t *envlist, const char *env) +{ + return (envlist_parse(envlist, env, &envlist_setenv)); +} + +/* + * Parses comma separated list of unset environment variable + * entries and removes given variables from given envlist. + * + * Returns 0 on success, errno otherwise. + */ +int +envlist_parse_unset(envlist_t *envlist, const char *env) +{ + return (envlist_parse(envlist, env, &envlist_unsetenv)); +} + +/* + * Parses comma separated list of set, modify or unset entries + * and calls given callback for each entry. + * + * Returns 0 in case of success, errno otherwise. + */ +static int +envlist_parse(envlist_t *envlist, const char *env, + int (*callback)(envlist_t *, const char *)) +{ + char *tmpenv, *envvar; + char *envsave = NULL; + + assert(callback != NULL); + + if ((envlist == NULL) || (env == NULL)) + return (EINVAL); + + /* + * We need to make temporary copy of the env string + * as strtok_r(3) modifies it while it tokenizes. + */ + if ((tmpenv = strdup(env)) == NULL) + return (errno); + + envvar = strtok_r(tmpenv, ",", &envsave); + while (envvar != NULL) { + if ((*callback)(envlist, envvar) != 0) { + free(tmpenv); + return (errno); + } + envvar = strtok_r(NULL, ",", &envsave); + } + + free(tmpenv); + return (0); +} + +/* + * Sets environment value to envlist in similar manner + * than putenv(3). + * + * Returns 0 in success, errno otherwise. + */ +int +envlist_setenv(envlist_t *envlist, const char *env) +{ + struct envlist_entry *entry = NULL; + const char *eq_sign; + size_t envname_len; + + if ((envlist == NULL) || (env == NULL)) + return (EINVAL); + + /* find out first equals sign in given env */ + if ((eq_sign = strchr(env, '=')) == NULL) + return (EINVAL); + envname_len = eq_sign - env + 1; + + /* + * If there already exists variable with given name + * we remove and release it before allocating a whole + * new entry. + */ + for (entry = envlist->el_entries.lh_first; entry != NULL; + entry = entry->ev_link.le_next) { + if (strncmp(entry->ev_var, env, envname_len) == 0) + break; + } + + if (entry != NULL) { + QLIST_REMOVE(entry, ev_link); + free((char *)entry->ev_var); + free(entry); + } else { + envlist->el_count++; + } + + if ((entry = malloc(sizeof (*entry))) == NULL) + return (errno); + if ((entry->ev_var = strdup(env)) == NULL) { + free(entry); + return (errno); + } + QLIST_INSERT_HEAD(&envlist->el_entries, entry, ev_link); + + return (0); +} + +/* + * Removes given env value from envlist in similar manner + * than unsetenv(3). Returns 0 in success, errno otherwise. + */ +int +envlist_unsetenv(envlist_t *envlist, const char *env) +{ + struct envlist_entry *entry; + size_t envname_len; + + if ((envlist == NULL) || (env == NULL)) + return (EINVAL); + + /* env is not allowed to contain '=' */ + if (strchr(env, '=') != NULL) + return (EINVAL); + + /* + * Find out the requested entry and remove + * it from the list. + */ + envname_len = strlen(env); + for (entry = envlist->el_entries.lh_first; entry != NULL; + entry = entry->ev_link.le_next) { + if (strncmp(entry->ev_var, env, envname_len) == 0) + break; + } + if (entry != NULL) { + QLIST_REMOVE(entry, ev_link); + free((char *)entry->ev_var); + free(entry); + + envlist->el_count--; + } + return (0); +} + +/* + * Returns given envlist as array of strings (in same form that + * global variable environ is). Caller must free returned memory + * by calling free(3) for each element and for the array. Returned + * array and given envlist are not related (no common references). + * + * If caller provides count pointer, number of items in array is + * stored there. In case of error, NULL is returned and no memory + * is allocated. + */ +char ** +envlist_to_environ(const envlist_t *envlist, size_t *count) +{ + struct envlist_entry *entry; + char **env, **penv; + + penv = env = malloc((envlist->el_count + 1) * sizeof (char *)); + if (env == NULL) + return (NULL); + + for (entry = envlist->el_entries.lh_first; entry != NULL; + entry = entry->ev_link.le_next) { + *(penv++) = strdup(entry->ev_var); + } + *penv = NULL; /* NULL terminate the list */ + + if (count != NULL) + *count = envlist->el_count; + + return (env); +} diff --git a/envlist.h b/envlist.h new file mode 100644 index 0000000..b9addcc --- /dev/null +++ b/envlist.h @@ -0,0 +1,22 @@ +#ifndef ENVLIST_H +#define ENVLIST_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct envlist envlist_t; + +envlist_t *envlist_create(void); +void envlist_free(envlist_t *); +int envlist_setenv(envlist_t *, const char *); +int envlist_unsetenv(envlist_t *, const char *); +int envlist_parse_set(envlist_t *, const char *); +int envlist_parse_unset(envlist_t *, const char *); +char **envlist_to_environ(const envlist_t *, size_t *); + +#ifdef __cplusplus +} +#endif + +#endif /* ENVLIST_H */ diff --git a/exec-all.h b/exec-all.h new file mode 100644 index 0000000..e3a82bc --- /dev/null +++ b/exec-all.h @@ -0,0 +1,349 @@ +/* + * internal execution defines for qemu + * + * Copyright (c) 2003 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 . + */ + +#ifndef _EXEC_ALL_H_ +#define _EXEC_ALL_H_ + +#include "qemu-common.h" + +/* allow to see translation results - the slowdown should be negligible, so we leave it */ +#define DEBUG_DISAS + +/* Page tracking code uses ram addresses in system mode, and virtual + addresses in userspace mode. Define tb_page_addr_t to be an appropriate + type. */ +#if defined(CONFIG_USER_ONLY) +typedef abi_ulong tb_page_addr_t; +#else +typedef ram_addr_t tb_page_addr_t; +#endif + +/* is_jmp field values */ +#define DISAS_NEXT 0 /* next instruction can be analyzed */ +#define DISAS_JUMP 1 /* only pc was modified dynamically */ +#define DISAS_UPDATE 2 /* cpu state was modified dynamically */ +#define DISAS_TB_JUMP 3 /* only pc was modified statically */ + +typedef struct TranslationBlock TranslationBlock; + +/* XXX: make safe guess about sizes */ +#define MAX_OP_PER_INSTR 96 + +#if HOST_LONG_BITS == 32 +#define MAX_OPC_PARAM_PER_ARG 2 +#else +#define MAX_OPC_PARAM_PER_ARG 1 +#endif +#define MAX_OPC_PARAM_IARGS 4 +#define MAX_OPC_PARAM_OARGS 1 +#define MAX_OPC_PARAM_ARGS (MAX_OPC_PARAM_IARGS + MAX_OPC_PARAM_OARGS) + +/* A Call op needs up to 4 + 2N parameters on 32-bit archs, + * and up to 4 + N parameters on 64-bit archs + * (N = number of input arguments + output arguments). */ +#define MAX_OPC_PARAM (4 + (MAX_OPC_PARAM_PER_ARG * MAX_OPC_PARAM_ARGS)) +#define OPC_BUF_SIZE 640 +#define OPC_MAX_SIZE (OPC_BUF_SIZE - MAX_OP_PER_INSTR) + +/* Maximum size a TCG op can expand to. This is complicated because a + single op may require several host instructions and register reloads. + For now take a wild guess at 192 bytes, which should allow at least + a couple of fixup instructions per argument. */ +#define TCG_MAX_OP_SIZE 192 + +#define OPPARAM_BUF_SIZE (OPC_BUF_SIZE * MAX_OPC_PARAM) + +extern target_ulong gen_opc_pc[OPC_BUF_SIZE]; +extern uint8_t gen_opc_instr_start[OPC_BUF_SIZE]; +extern uint16_t gen_opc_icount[OPC_BUF_SIZE]; + +#include "qemu-log.h" + +void gen_intermediate_code(CPUState *env, struct TranslationBlock *tb); +void gen_intermediate_code_pc(CPUState *env, struct TranslationBlock *tb); +void gen_pc_load(CPUState *env, struct TranslationBlock *tb, + unsigned long searched_pc, int pc_pos, void *puc); + +void cpu_gen_init(void); +int cpu_gen_code(CPUState *env, struct TranslationBlock *tb, + int *gen_code_size_ptr); +int cpu_restore_state(struct TranslationBlock *tb, + CPUState *env, unsigned long searched_pc, + void *puc); +void cpu_resume_from_signal(CPUState *env1, void *puc); +void cpu_io_recompile(CPUState *env, void *retaddr); +TranslationBlock *tb_gen_code(CPUState *env, + target_ulong pc, target_ulong cs_base, int flags, + int cflags); +void cpu_exec_init(CPUState *env); +void QEMU_NORETURN cpu_loop_exit(void); +int page_unprotect(target_ulong address, unsigned long pc, void *puc); +void tb_invalidate_phys_page_range(tb_page_addr_t start, tb_page_addr_t end, + int is_cpu_write_access); +void tb_invalidate_page_range(target_ulong start, target_ulong end); +void tlb_flush_page(CPUState *env, target_ulong addr); +void tlb_flush(CPUState *env, int flush_global); +#if !defined(CONFIG_USER_ONLY) +void tlb_set_page(CPUState *env, target_ulong vaddr, + target_phys_addr_t paddr, int prot, + int mmu_idx, target_ulong size); +#endif + +#define CODE_GEN_ALIGN 16 /* must be >= of the size of a icache line */ + +#define CODE_GEN_PHYS_HASH_BITS 15 +#define CODE_GEN_PHYS_HASH_SIZE (1 << CODE_GEN_PHYS_HASH_BITS) + +#define MIN_CODE_GEN_BUFFER_SIZE (1024 * 1024) + +/* estimated block size for TB allocation */ +/* XXX: use a per code average code fragment size and modulate it + according to the host CPU */ +#if defined(CONFIG_SOFTMMU) +#define CODE_GEN_AVG_BLOCK_SIZE 128 +#else +#define CODE_GEN_AVG_BLOCK_SIZE 64 +#endif + +#if defined(_ARCH_PPC) || defined(__x86_64__) || defined(__arm__) || defined(__i386__) +#define USE_DIRECT_JUMP +#endif + +struct TranslationBlock { + target_ulong pc; /* simulated PC corresponding to this block (EIP + CS base) */ + target_ulong cs_base; /* CS base for this block */ + uint64_t flags; /* flags defining in which context the code was generated */ + uint16_t size; /* size of target code for this block (1 <= + size <= TARGET_PAGE_SIZE) */ + uint16_t cflags; /* compile flags */ +#define CF_COUNT_MASK 0x7fff +#define CF_LAST_IO 0x8000 /* Last insn may be an IO access. */ + + uint8_t *tc_ptr; /* pointer to the translated code */ + /* next matching tb for physical address. */ + struct TranslationBlock *phys_hash_next; + /* first and second physical page containing code. The lower bit + of the pointer tells the index in page_next[] */ + struct TranslationBlock *page_next[2]; + tb_page_addr_t page_addr[2]; + + /* the following data are used to directly call another TB from + the code of this one. */ + uint16_t tb_next_offset[2]; /* offset of original jump target */ +#ifdef USE_DIRECT_JUMP + uint16_t tb_jmp_offset[2]; /* offset of jump instruction */ +#else + unsigned long tb_next[2]; /* address of jump generated code */ +#endif + /* list of TBs jumping to this one. This is a circular list using + the two least significant bits of the pointers to tell what is + the next pointer: 0 = jmp_next[0], 1 = jmp_next[1], 2 = + jmp_first */ + struct TranslationBlock *jmp_next[2]; + struct TranslationBlock *jmp_first; + uint32_t icount; +}; + +static inline unsigned int tb_jmp_cache_hash_page(target_ulong pc) +{ + target_ulong tmp; + tmp = pc ^ (pc >> (TARGET_PAGE_BITS - TB_JMP_PAGE_BITS)); + return (tmp >> (TARGET_PAGE_BITS - TB_JMP_PAGE_BITS)) & TB_JMP_PAGE_MASK; +} + +static inline unsigned int tb_jmp_cache_hash_func(target_ulong pc) +{ + target_ulong tmp; + tmp = pc ^ (pc >> (TARGET_PAGE_BITS - TB_JMP_PAGE_BITS)); + return (((tmp >> (TARGET_PAGE_BITS - TB_JMP_PAGE_BITS)) & TB_JMP_PAGE_MASK) + | (tmp & TB_JMP_ADDR_MASK)); +} + +static inline unsigned int tb_phys_hash_func(tb_page_addr_t pc) +{ + return (pc >> 2) & (CODE_GEN_PHYS_HASH_SIZE - 1); +} + +TranslationBlock *tb_alloc(target_ulong pc); +void tb_free(TranslationBlock *tb); +void tb_flush(CPUState *env); +void tb_link_page(TranslationBlock *tb, + tb_page_addr_t phys_pc, tb_page_addr_t phys_page2); +void tb_phys_invalidate(TranslationBlock *tb, tb_page_addr_t page_addr); + +extern TranslationBlock *tb_phys_hash[CODE_GEN_PHYS_HASH_SIZE]; + +#if defined(USE_DIRECT_JUMP) + +#if defined(_ARCH_PPC) +void ppc_tb_set_jmp_target(unsigned long jmp_addr, unsigned long addr); +#define tb_set_jmp_target1 ppc_tb_set_jmp_target +#elif defined(__i386__) || defined(__x86_64__) +static inline void tb_set_jmp_target1(unsigned long jmp_addr, unsigned long addr) +{ + /* patch the branch destination */ + *(uint32_t *)jmp_addr = addr - (jmp_addr + 4); + /* no need to flush icache explicitly */ +} +#elif defined(__arm__) +static inline void tb_set_jmp_target1(unsigned long jmp_addr, unsigned long addr) +{ +#if !QEMU_GNUC_PREREQ(4, 1) + register unsigned long _beg __asm ("a1"); + register unsigned long _end __asm ("a2"); + register unsigned long _flg __asm ("a3"); +#endif + + /* we could use a ldr pc, [pc, #-4] kind of branch and avoid the flush */ + *(uint32_t *)jmp_addr = + (*(uint32_t *)jmp_addr & ~0xffffff) + | (((addr - (jmp_addr + 8)) >> 2) & 0xffffff); + +#if QEMU_GNUC_PREREQ(4, 1) + __builtin___clear_cache((char *) jmp_addr, (char *) jmp_addr + 4); +#else + /* flush icache */ + _beg = jmp_addr; + _end = jmp_addr + 4; + _flg = 0; + __asm __volatile__ ("swi 0x9f0002" : : "r" (_beg), "r" (_end), "r" (_flg)); +#endif +} +#endif + +static inline void tb_set_jmp_target(TranslationBlock *tb, + int n, unsigned long addr) +{ + unsigned long offset; + + offset = tb->tb_jmp_offset[n]; + tb_set_jmp_target1((unsigned long)(tb->tc_ptr + offset), addr); +} + +#else + +/* set the jump target */ +static inline void tb_set_jmp_target(TranslationBlock *tb, + int n, unsigned long addr) +{ + tb->tb_next[n] = addr; +} + +#endif + +static inline void tb_add_jump(TranslationBlock *tb, int n, + TranslationBlock *tb_next) +{ + /* NOTE: this test is only needed for thread safety */ + if (!tb->jmp_next[n]) { + /* patch the native jump address */ + tb_set_jmp_target(tb, n, (unsigned long)tb_next->tc_ptr); + + /* add in TB jmp circular list */ + tb->jmp_next[n] = tb_next->jmp_first; + tb_next->jmp_first = (TranslationBlock *)((long)(tb) | (n)); + } +} + +TranslationBlock *tb_find_pc(unsigned long pc_ptr); + +#include "qemu-lock.h" + +extern spinlock_t tb_lock; + +extern int tb_invalidated_flag; + +#if !defined(CONFIG_USER_ONLY) + +extern CPUWriteMemoryFunc *io_mem_write[IO_MEM_NB_ENTRIES][4]; +extern CPUReadMemoryFunc *io_mem_read[IO_MEM_NB_ENTRIES][4]; +extern void *io_mem_opaque[IO_MEM_NB_ENTRIES]; + +void tlb_fill(target_ulong addr, int is_write, int mmu_idx, + void *retaddr); + +#include "softmmu_defs.h" + +#define ACCESS_TYPE (NB_MMU_MODES + 1) +#define MEMSUFFIX _code +#define env cpu_single_env + +#define DATA_SIZE 1 +#include "softmmu_header.h" + +#define DATA_SIZE 2 +#include "softmmu_header.h" + +#define DATA_SIZE 4 +#include "softmmu_header.h" + +#define DATA_SIZE 8 +#include "softmmu_header.h" + +#undef ACCESS_TYPE +#undef MEMSUFFIX +#undef env + +#endif + +#if defined(CONFIG_USER_ONLY) +static inline tb_page_addr_t get_page_addr_code(CPUState *env1, target_ulong addr) +{ + return addr; +} +#else +/* NOTE: this function can trigger an exception */ +/* NOTE2: the returned address is not exactly the physical address: it + is the offset relative to phys_ram_base */ +static inline tb_page_addr_t get_page_addr_code(CPUState *env1, target_ulong addr) +{ + int mmu_idx, page_index, pd; + void *p; + + page_index = (addr >> TARGET_PAGE_BITS) & (CPU_TLB_SIZE - 1); + mmu_idx = cpu_mmu_index(env1); + if (unlikely(env1->tlb_table[mmu_idx][page_index].addr_code != + (addr & TARGET_PAGE_MASK))) { + ldub_code(addr); + } + pd = env1->tlb_table[mmu_idx][page_index].addr_code & ~TARGET_PAGE_MASK; + if (pd > IO_MEM_ROM && !(pd & IO_MEM_ROMD)) { +#if defined(TARGET_SPARC) || defined(TARGET_MIPS) + do_unassigned_access(addr, 0, 1, 0, 4); +#else + cpu_abort(env1, "Trying to execute code outside RAM or ROM at 0x" TARGET_FMT_lx "\n", addr); +#endif + } + p = (void *)(unsigned long)addr + + env1->tlb_table[mmu_idx][page_index].addend; + return qemu_ram_addr_from_host_nofail(p); +} +#endif + +typedef void (CPUDebugExcpHandler)(CPUState *env); + +CPUDebugExcpHandler *cpu_set_debug_excp_handler(CPUDebugExcpHandler *handler); + +/* vl.c */ +extern int singlestep; + +/* cpu-exec.c */ +extern volatile sig_atomic_t exit_request; + +#endif diff --git a/exec.c b/exec.c new file mode 100644 index 0000000..e950df2 --- /dev/null +++ b/exec.c @@ -0,0 +1,4316 @@ +/* + * virtual page mapping and translated block handling + * + * Copyright (c) 2003 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 . + */ +#include "config.h" +#ifdef _WIN32 +#include +#else +#include +#include +#endif + +#include "qemu-common.h" +#include "cpu.h" +#include "exec-all.h" +#include "tcg.h" +#include "hw/hw.h" +#include "hw/qdev.h" +#include "osdep.h" +#include "kvm.h" +#include "qemu-timer.h" +#if defined(CONFIG_USER_ONLY) +#include +#include +#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) +#include +#if __FreeBSD_version >= 700104 +#define HAVE_KINFO_GETVMMAP +#define sigqueue sigqueue_freebsd /* avoid redefinition */ +#include +#include +#include +#define _KERNEL +#include +#undef _KERNEL +#undef sigqueue +#include +#endif +#endif +#endif + +//#define DEBUG_TB_INVALIDATE +//#define DEBUG_FLUSH +//#define DEBUG_TLB +//#define DEBUG_UNASSIGNED + +/* make various TB consistency checks */ +//#define DEBUG_TB_CHECK +//#define DEBUG_TLB_CHECK + +//#define DEBUG_IOPORT +//#define DEBUG_SUBPAGE + +#if !defined(CONFIG_USER_ONLY) +/* TB consistency checks only implemented for usermode emulation. */ +#undef DEBUG_TB_CHECK +#endif + +#define SMC_BITMAP_USE_THRESHOLD 10 + +static TranslationBlock *tbs; +static int code_gen_max_blocks; +TranslationBlock *tb_phys_hash[CODE_GEN_PHYS_HASH_SIZE]; +static int nb_tbs; +/* any access to the tbs or the page table must use this lock */ +spinlock_t tb_lock = SPIN_LOCK_UNLOCKED; + +#if defined(__arm__) || defined(__sparc_v9__) +/* The prologue must be reachable with a direct jump. ARM and Sparc64 + have limited branch ranges (possibly also PPC) so place it in a + section close to code segment. */ +#define code_gen_section \ + __attribute__((__section__(".gen_code"))) \ + __attribute__((aligned (32))) +#elif defined(_WIN32) +/* Maximum alignment for Win32 is 16. */ +#define code_gen_section \ + __attribute__((aligned (16))) +#else +#define code_gen_section \ + __attribute__((aligned (32))) +#endif + +uint8_t code_gen_prologue[1024] code_gen_section; +static uint8_t *code_gen_buffer; +static unsigned long code_gen_buffer_size; +/* threshold to flush the translated code buffer */ +static unsigned long code_gen_buffer_max_size; +static uint8_t *code_gen_ptr; + +#if !defined(CONFIG_USER_ONLY) +int phys_ram_fd; +static int in_migration; + +RAMList ram_list = { .blocks = QLIST_HEAD_INITIALIZER(ram_list) }; +#endif + +CPUState *first_cpu; +/* current CPU in the current thread. It is only valid inside + cpu_exec() */ +CPUState *cpu_single_env; +/* 0 = Do not count executed instructions. + 1 = Precise instruction counting. + 2 = Adaptive rate instruction counting. */ +int use_icount = 0; +/* Current instruction counter. While executing translated code this may + include some instructions that have not yet been executed. */ +int64_t qemu_icount; + +typedef struct PageDesc { + /* list of TBs intersecting this ram page */ + TranslationBlock *first_tb; + /* in order to optimize self modifying code, we count the number + of lookups we do to a given page to use a bitmap */ + unsigned int code_write_count; + uint8_t *code_bitmap; +#if defined(CONFIG_USER_ONLY) + unsigned long flags; +#endif +} PageDesc; + +/* In system mode we want L1_MAP to be based on ram offsets, + while in user mode we want it to be based on virtual addresses. */ +#if !defined(CONFIG_USER_ONLY) +#if HOST_LONG_BITS < TARGET_PHYS_ADDR_SPACE_BITS +# define L1_MAP_ADDR_SPACE_BITS HOST_LONG_BITS +#else +# define L1_MAP_ADDR_SPACE_BITS TARGET_PHYS_ADDR_SPACE_BITS +#endif +#else +# define L1_MAP_ADDR_SPACE_BITS TARGET_VIRT_ADDR_SPACE_BITS +#endif + +/* Size of the L2 (and L3, etc) page tables. */ +#define L2_BITS 10 +#define L2_SIZE (1 << L2_BITS) + +/* The bits remaining after N lower levels of page tables. */ +#define P_L1_BITS_REM \ + ((TARGET_PHYS_ADDR_SPACE_BITS - TARGET_PAGE_BITS) % L2_BITS) +#define V_L1_BITS_REM \ + ((L1_MAP_ADDR_SPACE_BITS - TARGET_PAGE_BITS) % L2_BITS) + +/* Size of the L1 page table. Avoid silly small sizes. */ +#if P_L1_BITS_REM < 4 +#define P_L1_BITS (P_L1_BITS_REM + L2_BITS) +#else +#define P_L1_BITS P_L1_BITS_REM +#endif + +#if V_L1_BITS_REM < 4 +#define V_L1_BITS (V_L1_BITS_REM + L2_BITS) +#else +#define V_L1_BITS V_L1_BITS_REM +#endif + +#define P_L1_SIZE ((target_phys_addr_t)1 << P_L1_BITS) +#define V_L1_SIZE ((target_ulong)1 << V_L1_BITS) + +#define P_L1_SHIFT (TARGET_PHYS_ADDR_SPACE_BITS - TARGET_PAGE_BITS - P_L1_BITS) +#define V_L1_SHIFT (L1_MAP_ADDR_SPACE_BITS - TARGET_PAGE_BITS - V_L1_BITS) + +unsigned long qemu_real_host_page_size; +unsigned long qemu_host_page_bits; +unsigned long qemu_host_page_size; +unsigned long qemu_host_page_mask; + +/* This is a multi-level map on the virtual address space. + The bottom level has pointers to PageDesc. */ +static void *l1_map[V_L1_SIZE]; + +#if !defined(CONFIG_USER_ONLY) +typedef struct PhysPageDesc { + /* offset in host memory of the page + io_index in the low bits */ + ram_addr_t phys_offset; + ram_addr_t region_offset; +} PhysPageDesc; + +/* This is a multi-level map on the physical address space. + The bottom level has pointers to PhysPageDesc. */ +static void *l1_phys_map[P_L1_SIZE]; + +static void io_mem_init(void); + +/* io memory support */ +CPUWriteMemoryFunc *io_mem_write[IO_MEM_NB_ENTRIES][4]; +CPUReadMemoryFunc *io_mem_read[IO_MEM_NB_ENTRIES][4]; +void *io_mem_opaque[IO_MEM_NB_ENTRIES]; +static char io_mem_used[IO_MEM_NB_ENTRIES]; +static int io_mem_watch; +#endif + +/* log support */ +#ifdef WIN32 +static const char *logfilename = "qemu.log"; +#else +static const char *logfilename = "/tmp/qemu.log"; +#endif +FILE *logfile; +int loglevel; +static int log_append = 0; + +/* statistics */ +#if !defined(CONFIG_USER_ONLY) +static int tlb_flush_count; +#endif +static int tb_flush_count; +static int tb_phys_invalidate_count; + +#ifdef _WIN32 +static void map_exec(void *addr, long size) +{ + DWORD old_protect; + VirtualProtect(addr, size, + PAGE_EXECUTE_READWRITE, &old_protect); + +} +#else +static void map_exec(void *addr, long size) +{ + unsigned long start, end, page_size; + + page_size = getpagesize(); + start = (unsigned long)addr; + start &= ~(page_size - 1); + + end = (unsigned long)addr + size; + end += page_size - 1; + end &= ~(page_size - 1); + + mprotect((void *)start, end - start, + PROT_READ | PROT_WRITE | PROT_EXEC); +} +#endif + +static void page_init(void) +{ + /* NOTE: we can always suppose that qemu_host_page_size >= + TARGET_PAGE_SIZE */ +#ifdef _WIN32 + { + SYSTEM_INFO system_info; + + GetSystemInfo(&system_info); + qemu_real_host_page_size = system_info.dwPageSize; + } +#else + qemu_real_host_page_size = getpagesize(); +#endif + if (qemu_host_page_size == 0) + qemu_host_page_size = qemu_real_host_page_size; + if (qemu_host_page_size < TARGET_PAGE_SIZE) + qemu_host_page_size = TARGET_PAGE_SIZE; + qemu_host_page_bits = 0; + while ((1 << qemu_host_page_bits) < qemu_host_page_size) + qemu_host_page_bits++; + qemu_host_page_mask = ~(qemu_host_page_size - 1); + +#if defined(CONFIG_BSD) && defined(CONFIG_USER_ONLY) + { +#ifdef HAVE_KINFO_GETVMMAP + struct kinfo_vmentry *freep; + int i, cnt; + + freep = kinfo_getvmmap(getpid(), &cnt); + if (freep) { + mmap_lock(); + for (i = 0; i < cnt; i++) { + unsigned long startaddr, endaddr; + + startaddr = freep[i].kve_start; + endaddr = freep[i].kve_end; + if (h2g_valid(startaddr)) { + startaddr = h2g(startaddr) & TARGET_PAGE_MASK; + + if (h2g_valid(endaddr)) { + endaddr = h2g(endaddr); + page_set_flags(startaddr, endaddr, PAGE_RESERVED); + } else { +#if TARGET_ABI_BITS <= L1_MAP_ADDR_SPACE_BITS + endaddr = ~0ul; + page_set_flags(startaddr, endaddr, PAGE_RESERVED); +#endif + } + } + } + free(freep); + mmap_unlock(); + } +#else + FILE *f; + + last_brk = (unsigned long)sbrk(0); + + f = fopen("/compat/linux/proc/self/maps", "r"); + if (f) { + mmap_lock(); + + do { + unsigned long startaddr, endaddr; + int n; + + n = fscanf (f, "%lx-%lx %*[^\n]\n", &startaddr, &endaddr); + + if (n == 2 && h2g_valid(startaddr)) { + startaddr = h2g(startaddr) & TARGET_PAGE_MASK; + + if (h2g_valid(endaddr)) { + endaddr = h2g(endaddr); + } else { + endaddr = ~0ul; + } + page_set_flags(startaddr, endaddr, PAGE_RESERVED); + } + } while (!feof(f)); + + fclose(f); + mmap_unlock(); + } +#endif + } +#endif +} + +static PageDesc *page_find_alloc(tb_page_addr_t index, int alloc) +{ + PageDesc *pd; + void **lp; + int i; + +#if defined(CONFIG_USER_ONLY) + /* We can't use qemu_malloc because it may recurse into a locked mutex. */ +# define ALLOC(P, SIZE) \ + do { \ + P = mmap(NULL, SIZE, PROT_READ | PROT_WRITE, \ + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); \ + } while (0) +#else +# define ALLOC(P, SIZE) \ + do { P = qemu_mallocz(SIZE); } while (0) +#endif + + /* Level 1. Always allocated. */ + lp = l1_map + ((index >> V_L1_SHIFT) & (V_L1_SIZE - 1)); + + /* Level 2..N-1. */ + for (i = V_L1_SHIFT / L2_BITS - 1; i > 0; i--) { + void **p = *lp; + + if (p == NULL) { + if (!alloc) { + return NULL; + } + ALLOC(p, sizeof(void *) * L2_SIZE); + *lp = p; + } + + lp = p + ((index >> (i * L2_BITS)) & (L2_SIZE - 1)); + } + + pd = *lp; + if (pd == NULL) { + if (!alloc) { + return NULL; + } + ALLOC(pd, sizeof(PageDesc) * L2_SIZE); + *lp = pd; + } + +#undef ALLOC + + return pd + (index & (L2_SIZE - 1)); +} + +static inline PageDesc *page_find(tb_page_addr_t index) +{ + return page_find_alloc(index, 0); +} + +#if !defined(CONFIG_USER_ONLY) +static PhysPageDesc *phys_page_find_alloc(target_phys_addr_t index, int alloc) +{ + PhysPageDesc *pd; + void **lp; + int i; + + /* Level 1. Always allocated. */ + lp = l1_phys_map + ((index >> P_L1_SHIFT) & (P_L1_SIZE - 1)); + + /* Level 2..N-1. */ + for (i = P_L1_SHIFT / L2_BITS - 1; i > 0; i--) { + void **p = *lp; + if (p == NULL) { + if (!alloc) { + return NULL; + } + *lp = p = qemu_mallocz(sizeof(void *) * L2_SIZE); + } + lp = p + ((index >> (i * L2_BITS)) & (L2_SIZE - 1)); + } + + pd = *lp; + if (pd == NULL) { + int i; + + if (!alloc) { + return NULL; + } + + *lp = pd = qemu_malloc(sizeof(PhysPageDesc) * L2_SIZE); + + for (i = 0; i < L2_SIZE; i++) { + pd[i].phys_offset = IO_MEM_UNASSIGNED; + pd[i].region_offset = (index + i) << TARGET_PAGE_BITS; + } + } + + return pd + (index & (L2_SIZE - 1)); +} + +static inline PhysPageDesc *phys_page_find(target_phys_addr_t index) +{ + return phys_page_find_alloc(index, 0); +} + +static void tlb_protect_code(ram_addr_t ram_addr); +static void tlb_unprotect_code_phys(CPUState *env, ram_addr_t ram_addr, + target_ulong vaddr); +#define mmap_lock() do { } while(0) +#define mmap_unlock() do { } while(0) +#endif + +#define DEFAULT_CODE_GEN_BUFFER_SIZE (32 * 1024 * 1024) + +#if defined(CONFIG_USER_ONLY) +/* Currently it is not recommended to allocate big chunks of data in + user mode. It will change when a dedicated libc will be used */ +#define USE_STATIC_CODE_GEN_BUFFER +#endif + +#ifdef USE_STATIC_CODE_GEN_BUFFER +static uint8_t static_code_gen_buffer[DEFAULT_CODE_GEN_BUFFER_SIZE] + __attribute__((aligned (CODE_GEN_ALIGN))); +#endif + +static void code_gen_alloc(unsigned long tb_size) +{ +#ifdef USE_STATIC_CODE_GEN_BUFFER + code_gen_buffer = static_code_gen_buffer; + code_gen_buffer_size = DEFAULT_CODE_GEN_BUFFER_SIZE; + map_exec(code_gen_buffer, code_gen_buffer_size); +#else + code_gen_buffer_size = tb_size; + if (code_gen_buffer_size == 0) { +#if defined(CONFIG_USER_ONLY) + /* in user mode, phys_ram_size is not meaningful */ + code_gen_buffer_size = DEFAULT_CODE_GEN_BUFFER_SIZE; +#else + /* XXX: needs adjustments */ + code_gen_buffer_size = (unsigned long)(ram_size / 4); +#endif + } + if (code_gen_buffer_size < MIN_CODE_GEN_BUFFER_SIZE) + code_gen_buffer_size = MIN_CODE_GEN_BUFFER_SIZE; + /* The code gen buffer location may have constraints depending on + the host cpu and OS */ +#if defined(__linux__) + { + int flags; + void *start = NULL; + + flags = MAP_PRIVATE | MAP_ANONYMOUS; +#if defined(__x86_64__) + flags |= MAP_32BIT; + /* Cannot map more than that */ + if (code_gen_buffer_size > (800 * 1024 * 1024)) + code_gen_buffer_size = (800 * 1024 * 1024); +#elif defined(__sparc_v9__) + // Map the buffer below 2G, so we can use direct calls and branches + flags |= MAP_FIXED; + start = (void *) 0x60000000UL; + if (code_gen_buffer_size > (512 * 1024 * 1024)) + code_gen_buffer_size = (512 * 1024 * 1024); +#elif defined(__arm__) + /* Map the buffer below 32M, so we can use direct calls and branches */ + flags |= MAP_FIXED; + start = (void *) 0x01000000UL; + if (code_gen_buffer_size > 16 * 1024 * 1024) + code_gen_buffer_size = 16 * 1024 * 1024; +#elif defined(__s390x__) + /* Map the buffer so that we can use direct calls and branches. */ + /* We have a +- 4GB range on the branches; leave some slop. */ + if (code_gen_buffer_size > (3ul * 1024 * 1024 * 1024)) { + code_gen_buffer_size = 3ul * 1024 * 1024 * 1024; + } + start = (void *)0x90000000UL; +#endif + code_gen_buffer = mmap(start, code_gen_buffer_size, + PROT_WRITE | PROT_READ | PROT_EXEC, + flags, -1, 0); + if (code_gen_buffer == MAP_FAILED) { + fprintf(stderr, "Could not allocate dynamic translator buffer\n"); + exit(1); + } + } +#elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__) \ + || defined(__DragonFly__) || defined(__OpenBSD__) + { + int flags; + void *addr = NULL; + flags = MAP_PRIVATE | MAP_ANONYMOUS; +#if defined(__x86_64__) + /* FreeBSD doesn't have MAP_32BIT, use MAP_FIXED and assume + * 0x40000000 is free */ + flags |= MAP_FIXED; + addr = (void *)0x40000000; + /* Cannot map more than that */ + if (code_gen_buffer_size > (800 * 1024 * 1024)) + code_gen_buffer_size = (800 * 1024 * 1024); +#elif defined(__sparc_v9__) + // Map the buffer below 2G, so we can use direct calls and branches + flags |= MAP_FIXED; + addr = (void *) 0x60000000UL; + if (code_gen_buffer_size > (512 * 1024 * 1024)) { + code_gen_buffer_size = (512 * 1024 * 1024); + } +#endif + code_gen_buffer = mmap(addr, code_gen_buffer_size, + PROT_WRITE | PROT_READ | PROT_EXEC, + flags, -1, 0); + if (code_gen_buffer == MAP_FAILED) { + fprintf(stderr, "Could not allocate dynamic translator buffer\n"); + exit(1); + } + } +#else + code_gen_buffer = qemu_malloc(code_gen_buffer_size); + map_exec(code_gen_buffer, code_gen_buffer_size); +#endif +#endif /* !USE_STATIC_CODE_GEN_BUFFER */ + map_exec(code_gen_prologue, sizeof(code_gen_prologue)); + code_gen_buffer_max_size = code_gen_buffer_size - + (TCG_MAX_OP_SIZE * OPC_MAX_SIZE); + code_gen_max_blocks = code_gen_buffer_size / CODE_GEN_AVG_BLOCK_SIZE; + tbs = qemu_malloc(code_gen_max_blocks * sizeof(TranslationBlock)); +} + +/* Must be called before using the QEMU cpus. 'tb_size' is the size + (in bytes) allocated to the translation buffer. Zero means default + size. */ +void cpu_exec_init_all(unsigned long tb_size) +{ + cpu_gen_init(); + code_gen_alloc(tb_size); + code_gen_ptr = code_gen_buffer; + page_init(); +#if !defined(CONFIG_USER_ONLY) + io_mem_init(); +#endif +#if !defined(CONFIG_USER_ONLY) || !defined(CONFIG_USE_GUEST_BASE) + /* There's no guest base to take into account, so go ahead and + initialize the prologue now. */ + tcg_prologue_init(&tcg_ctx); +#endif +} + +#if defined(CPU_SAVE_VERSION) && !defined(CONFIG_USER_ONLY) + +static int cpu_common_post_load(void *opaque, int version_id) +{ + CPUState *env = opaque; + + /* 0x01 was CPU_INTERRUPT_EXIT. This line can be removed when the + version_id is increased. */ + env->interrupt_request &= ~0x01; + tlb_flush(env, 1); + + return 0; +} + +static const VMStateDescription vmstate_cpu_common = { + .name = "cpu_common", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .post_load = cpu_common_post_load, + .fields = (VMStateField []) { + VMSTATE_UINT32(halted, CPUState), + VMSTATE_UINT32(interrupt_request, CPUState), + VMSTATE_END_OF_LIST() + } +}; +#endif + +CPUState *qemu_get_cpu(int cpu) +{ + CPUState *env = first_cpu; + + while (env) { + if (env->cpu_index == cpu) + break; + env = env->next_cpu; + } + + return env; +} + +void cpu_exec_init(CPUState *env) +{ + CPUState **penv; + int cpu_index; + +#if defined(CONFIG_USER_ONLY) + cpu_list_lock(); +#endif + env->next_cpu = NULL; + penv = &first_cpu; + cpu_index = 0; + while (*penv != NULL) { + penv = &(*penv)->next_cpu; + cpu_index++; + } + env->cpu_index = cpu_index; + env->numa_node = 0; + QTAILQ_INIT(&env->breakpoints); + QTAILQ_INIT(&env->watchpoints); + *penv = env; +#if defined(CONFIG_USER_ONLY) + cpu_list_unlock(); +#endif +#if defined(CPU_SAVE_VERSION) && !defined(CONFIG_USER_ONLY) + vmstate_register(NULL, cpu_index, &vmstate_cpu_common, env); + register_savevm(NULL, "cpu", cpu_index, CPU_SAVE_VERSION, + cpu_save, cpu_load, env); +#endif +} + +static inline void invalidate_page_bitmap(PageDesc *p) +{ + if (p->code_bitmap) { + qemu_free(p->code_bitmap); + p->code_bitmap = NULL; + } + p->code_write_count = 0; +} + +/* Set to NULL all the 'first_tb' fields in all PageDescs. */ + +static void page_flush_tb_1 (int level, void **lp) +{ + int i; + + if (*lp == NULL) { + return; + } + if (level == 0) { + PageDesc *pd = *lp; + for (i = 0; i < L2_SIZE; ++i) { + pd[i].first_tb = NULL; + invalidate_page_bitmap(pd + i); + } + } else { + void **pp = *lp; + for (i = 0; i < L2_SIZE; ++i) { + page_flush_tb_1 (level - 1, pp + i); + } + } +} + +static void page_flush_tb(void) +{ + int i; + for (i = 0; i < V_L1_SIZE; i++) { + page_flush_tb_1(V_L1_SHIFT / L2_BITS - 1, l1_map + i); + } +} + +/* flush all the translation blocks */ +/* XXX: tb_flush is currently not thread safe */ +void tb_flush(CPUState *env1) +{ + CPUState *env; +#if defined(DEBUG_FLUSH) + printf("qemu: flush code_size=%ld nb_tbs=%d avg_tb_size=%ld\n", + (unsigned long)(code_gen_ptr - code_gen_buffer), + nb_tbs, nb_tbs > 0 ? + ((unsigned long)(code_gen_ptr - code_gen_buffer)) / nb_tbs : 0); +#endif + if ((unsigned long)(code_gen_ptr - code_gen_buffer) > code_gen_buffer_size) + cpu_abort(env1, "Internal error: code buffer overflow\n"); + + nb_tbs = 0; + + for(env = first_cpu; env != NULL; env = env->next_cpu) { + memset (env->tb_jmp_cache, 0, TB_JMP_CACHE_SIZE * sizeof (void *)); + } + + memset (tb_phys_hash, 0, CODE_GEN_PHYS_HASH_SIZE * sizeof (void *)); + page_flush_tb(); + + code_gen_ptr = code_gen_buffer; + /* XXX: flush processor icache at this point if cache flush is + expensive */ + tb_flush_count++; +} + +#ifdef DEBUG_TB_CHECK + +static void tb_invalidate_check(target_ulong address) +{ + TranslationBlock *tb; + int i; + address &= TARGET_PAGE_MASK; + for(i = 0;i < CODE_GEN_PHYS_HASH_SIZE; i++) { + for(tb = tb_phys_hash[i]; tb != NULL; tb = tb->phys_hash_next) { + if (!(address + TARGET_PAGE_SIZE <= tb->pc || + address >= tb->pc + tb->size)) { + printf("ERROR invalidate: address=" TARGET_FMT_lx + " PC=%08lx size=%04x\n", + address, (long)tb->pc, tb->size); + } + } + } +} + +/* verify that all the pages have correct rights for code */ +static void tb_page_check(void) +{ + TranslationBlock *tb; + int i, flags1, flags2; + + for(i = 0;i < CODE_GEN_PHYS_HASH_SIZE; i++) { + for(tb = tb_phys_hash[i]; tb != NULL; tb = tb->phys_hash_next) { + flags1 = page_get_flags(tb->pc); + flags2 = page_get_flags(tb->pc + tb->size - 1); + if ((flags1 & PAGE_WRITE) || (flags2 & PAGE_WRITE)) { + printf("ERROR page flags: PC=%08lx size=%04x f1=%x f2=%x\n", + (long)tb->pc, tb->size, flags1, flags2); + } + } + } +} + +#endif + +/* invalidate one TB */ +static inline void tb_remove(TranslationBlock **ptb, TranslationBlock *tb, + int next_offset) +{ + TranslationBlock *tb1; + for(;;) { + tb1 = *ptb; + if (tb1 == tb) { + *ptb = *(TranslationBlock **)((char *)tb1 + next_offset); + break; + } + ptb = (TranslationBlock **)((char *)tb1 + next_offset); + } +} + +static inline void tb_page_remove(TranslationBlock **ptb, TranslationBlock *tb) +{ + TranslationBlock *tb1; + unsigned int n1; + + for(;;) { + tb1 = *ptb; + n1 = (long)tb1 & 3; + tb1 = (TranslationBlock *)((long)tb1 & ~3); + if (tb1 == tb) { + *ptb = tb1->page_next[n1]; + break; + } + ptb = &tb1->page_next[n1]; + } +} + +static inline void tb_jmp_remove(TranslationBlock *tb, int n) +{ + TranslationBlock *tb1, **ptb; + unsigned int n1; + + ptb = &tb->jmp_next[n]; + tb1 = *ptb; + if (tb1) { + /* find tb(n) in circular list */ + for(;;) { + tb1 = *ptb; + n1 = (long)tb1 & 3; + tb1 = (TranslationBlock *)((long)tb1 & ~3); + if (n1 == n && tb1 == tb) + break; + if (n1 == 2) { + ptb = &tb1->jmp_first; + } else { + ptb = &tb1->jmp_next[n1]; + } + } + /* now we can suppress tb(n) from the list */ + *ptb = tb->jmp_next[n]; + + tb->jmp_next[n] = NULL; + } +} + +/* reset the jump entry 'n' of a TB so that it is not chained to + another TB */ +static inline void tb_reset_jump(TranslationBlock *tb, int n) +{ + tb_set_jmp_target(tb, n, (unsigned long)(tb->tc_ptr + tb->tb_next_offset[n])); +} + +void tb_phys_invalidate(TranslationBlock *tb, tb_page_addr_t page_addr) +{ + CPUState *env; + PageDesc *p; + unsigned int h, n1; + tb_page_addr_t phys_pc; + TranslationBlock *tb1, *tb2; + + /* remove the TB from the hash list */ + phys_pc = tb->page_addr[0] + (tb->pc & ~TARGET_PAGE_MASK); + h = tb_phys_hash_func(phys_pc); + tb_remove(&tb_phys_hash[h], tb, + offsetof(TranslationBlock, phys_hash_next)); + + /* remove the TB from the page list */ + if (tb->page_addr[0] != page_addr) { + p = page_find(tb->page_addr[0] >> TARGET_PAGE_BITS); + tb_page_remove(&p->first_tb, tb); + invalidate_page_bitmap(p); + } + if (tb->page_addr[1] != -1 && tb->page_addr[1] != page_addr) { + p = page_find(tb->page_addr[1] >> TARGET_PAGE_BITS); + tb_page_remove(&p->first_tb, tb); + invalidate_page_bitmap(p); + } + + tb_invalidated_flag = 1; + + /* remove the TB from the hash list */ + h = tb_jmp_cache_hash_func(tb->pc); + for(env = first_cpu; env != NULL; env = env->next_cpu) { + if (env->tb_jmp_cache[h] == tb) + env->tb_jmp_cache[h] = NULL; + } + + /* suppress this TB from the two jump lists */ + tb_jmp_remove(tb, 0); + tb_jmp_remove(tb, 1); + + /* suppress any remaining jumps to this TB */ + tb1 = tb->jmp_first; + for(;;) { + n1 = (long)tb1 & 3; + if (n1 == 2) + break; + tb1 = (TranslationBlock *)((long)tb1 & ~3); + tb2 = tb1->jmp_next[n1]; + tb_reset_jump(tb1, n1); + tb1->jmp_next[n1] = NULL; + tb1 = tb2; + } + tb->jmp_first = (TranslationBlock *)((long)tb | 2); /* fail safe */ + + tb_phys_invalidate_count++; +} + +static inline void set_bits(uint8_t *tab, int start, int len) +{ + int end, mask, end1; + + end = start + len; + tab += start >> 3; + mask = 0xff << (start & 7); + if ((start & ~7) == (end & ~7)) { + if (start < end) { + mask &= ~(0xff << (end & 7)); + *tab |= mask; + } + } else { + *tab++ |= mask; + start = (start + 8) & ~7; + end1 = end & ~7; + while (start < end1) { + *tab++ = 0xff; + start += 8; + } + if (start < end) { + mask = ~(0xff << (end & 7)); + *tab |= mask; + } + } +} + +static void build_page_bitmap(PageDesc *p) +{ + int n, tb_start, tb_end; + TranslationBlock *tb; + + p->code_bitmap = qemu_mallocz(TARGET_PAGE_SIZE / 8); + + tb = p->first_tb; + while (tb != NULL) { + n = (long)tb & 3; + tb = (TranslationBlock *)((long)tb & ~3); + /* NOTE: this is subtle as a TB may span two physical pages */ + if (n == 0) { + /* NOTE: tb_end may be after the end of the page, but + it is not a problem */ + tb_start = tb->pc & ~TARGET_PAGE_MASK; + tb_end = tb_start + tb->size; + if (tb_end > TARGET_PAGE_SIZE) + tb_end = TARGET_PAGE_SIZE; + } else { + tb_start = 0; + tb_end = ((tb->pc + tb->size) & ~TARGET_PAGE_MASK); + } + set_bits(p->code_bitmap, tb_start, tb_end - tb_start); + tb = tb->page_next[n]; + } +} + +TranslationBlock *tb_gen_code(CPUState *env, + target_ulong pc, target_ulong cs_base, + int flags, int cflags) +{ + TranslationBlock *tb; + uint8_t *tc_ptr; + tb_page_addr_t phys_pc, phys_page2; + target_ulong virt_page2; + int code_gen_size; + + phys_pc = get_page_addr_code(env, pc); + tb = tb_alloc(pc); + if (!tb) { + /* flush must be done */ + tb_flush(env); + /* cannot fail at this point */ + tb = tb_alloc(pc); + /* Don't forget to invalidate previous TB info. */ + tb_invalidated_flag = 1; + } + tc_ptr = code_gen_ptr; + tb->tc_ptr = tc_ptr; + tb->cs_base = cs_base; + tb->flags = flags; + tb->cflags = cflags; + cpu_gen_code(env, tb, &code_gen_size); + code_gen_ptr = (void *)(((unsigned long)code_gen_ptr + code_gen_size + CODE_GEN_ALIGN - 1) & ~(CODE_GEN_ALIGN - 1)); + + /* check next page if needed */ + virt_page2 = (pc + tb->size - 1) & TARGET_PAGE_MASK; + phys_page2 = -1; + if ((pc & TARGET_PAGE_MASK) != virt_page2) { + phys_page2 = get_page_addr_code(env, virt_page2); + } + tb_link_page(tb, phys_pc, phys_page2); + return tb; +} + +/* invalidate all TBs which intersect with the target physical page + starting in range [start;end[. NOTE: start and end must refer to + the same physical page. 'is_cpu_write_access' should be true if called + from a real cpu write access: the virtual CPU will exit the current + TB if code is modified inside this TB. */ +void tb_invalidate_phys_page_range(tb_page_addr_t start, tb_page_addr_t end, + int is_cpu_write_access) +{ + TranslationBlock *tb, *tb_next, *saved_tb; + CPUState *env = cpu_single_env; + tb_page_addr_t tb_start, tb_end; + PageDesc *p; + int n; +#ifdef TARGET_HAS_PRECISE_SMC + int current_tb_not_found = is_cpu_write_access; + TranslationBlock *current_tb = NULL; + int current_tb_modified = 0; + target_ulong current_pc = 0; + target_ulong current_cs_base = 0; + int current_flags = 0; +#endif /* TARGET_HAS_PRECISE_SMC */ + + p = page_find(start >> TARGET_PAGE_BITS); + if (!p) + return; + if (!p->code_bitmap && + ++p->code_write_count >= SMC_BITMAP_USE_THRESHOLD && + is_cpu_write_access) { + /* build code bitmap */ + build_page_bitmap(p); + } + + /* we remove all the TBs in the range [start, end[ */ + /* XXX: see if in some cases it could be faster to invalidate all the code */ + tb = p->first_tb; + while (tb != NULL) { + n = (long)tb & 3; + tb = (TranslationBlock *)((long)tb & ~3); + tb_next = tb->page_next[n]; + /* NOTE: this is subtle as a TB may span two physical pages */ + if (n == 0) { + /* NOTE: tb_end may be after the end of the page, but + it is not a problem */ + tb_start = tb->page_addr[0] + (tb->pc & ~TARGET_PAGE_MASK); + tb_end = tb_start + tb->size; + } else { + tb_start = tb->page_addr[1]; + tb_end = tb_start + ((tb->pc + tb->size) & ~TARGET_PAGE_MASK); + } + if (!(tb_end <= start || tb_start >= end)) { +#ifdef TARGET_HAS_PRECISE_SMC + if (current_tb_not_found) { + current_tb_not_found = 0; + current_tb = NULL; + if (env->mem_io_pc) { + /* now we have a real cpu fault */ + current_tb = tb_find_pc(env->mem_io_pc); + } + } + if (current_tb == tb && + (current_tb->cflags & CF_COUNT_MASK) != 1) { + /* If we are modifying the current TB, we must stop + its execution. We could be more precise by checking + that the modification is after the current PC, but it + would require a specialized function to partially + restore the CPU state */ + + current_tb_modified = 1; + cpu_restore_state(current_tb, env, + env->mem_io_pc, NULL); + cpu_get_tb_cpu_state(env, ¤t_pc, ¤t_cs_base, + ¤t_flags); + } +#endif /* TARGET_HAS_PRECISE_SMC */ + /* we need to do that to handle the case where a signal + occurs while doing tb_phys_invalidate() */ + saved_tb = NULL; + if (env) { + saved_tb = env->current_tb; + env->current_tb = NULL; + } + tb_phys_invalidate(tb, -1); + if (env) { + env->current_tb = saved_tb; + if (env->interrupt_request && env->current_tb) + cpu_interrupt(env, env->interrupt_request); + } + } + tb = tb_next; + } +#if !defined(CONFIG_USER_ONLY) + /* if no code remaining, no need to continue to use slow writes */ + if (!p->first_tb) { + invalidate_page_bitmap(p); + if (is_cpu_write_access) { + tlb_unprotect_code_phys(env, start, env->mem_io_vaddr); + } + } +#endif +#ifdef TARGET_HAS_PRECISE_SMC + if (current_tb_modified) { + /* we generate a block containing just the instruction + modifying the memory. It will ensure that it cannot modify + itself */ + env->current_tb = NULL; + tb_gen_code(env, current_pc, current_cs_base, current_flags, 1); + cpu_resume_from_signal(env, NULL); + } +#endif +} + +/* len must be <= 8 and start must be a multiple of len */ +static inline void tb_invalidate_phys_page_fast(tb_page_addr_t start, int len) +{ + PageDesc *p; + int offset, b; +#if 0 + if (1) { + qemu_log("modifying code at 0x%x size=%d EIP=%x PC=%08x\n", + cpu_single_env->mem_io_vaddr, len, + cpu_single_env->eip, + cpu_single_env->eip + (long)cpu_single_env->segs[R_CS].base); + } +#endif + p = page_find(start >> TARGET_PAGE_BITS); + if (!p) + return; + if (p->code_bitmap) { + offset = start & ~TARGET_PAGE_MASK; + b = p->code_bitmap[offset >> 3] >> (offset & 7); + if (b & ((1 << len) - 1)) + goto do_invalidate; + } else { + do_invalidate: + tb_invalidate_phys_page_range(start, start + len, 1); + } +} + +#if !defined(CONFIG_SOFTMMU) +static void tb_invalidate_phys_page(tb_page_addr_t addr, + unsigned long pc, void *puc) +{ + TranslationBlock *tb; + PageDesc *p; + int n; +#ifdef TARGET_HAS_PRECISE_SMC + TranslationBlock *current_tb = NULL; + CPUState *env = cpu_single_env; + int current_tb_modified = 0; + target_ulong current_pc = 0; + target_ulong current_cs_base = 0; + int current_flags = 0; +#endif + + addr &= TARGET_PAGE_MASK; + p = page_find(addr >> TARGET_PAGE_BITS); + if (!p) + return; + tb = p->first_tb; +#ifdef TARGET_HAS_PRECISE_SMC + if (tb && pc != 0) { + current_tb = tb_find_pc(pc); + } +#endif + while (tb != NULL) { + n = (long)tb & 3; + tb = (TranslationBlock *)((long)tb & ~3); +#ifdef TARGET_HAS_PRECISE_SMC + if (current_tb == tb && + (current_tb->cflags & CF_COUNT_MASK) != 1) { + /* If we are modifying the current TB, we must stop + its execution. We could be more precise by checking + that the modification is after the current PC, but it + would require a specialized function to partially + restore the CPU state */ + + current_tb_modified = 1; + cpu_restore_state(current_tb, env, pc, puc); + cpu_get_tb_cpu_state(env, ¤t_pc, ¤t_cs_base, + ¤t_flags); + } +#endif /* TARGET_HAS_PRECISE_SMC */ + tb_phys_invalidate(tb, addr); + tb = tb->page_next[n]; + } + p->first_tb = NULL; +#ifdef TARGET_HAS_PRECISE_SMC + if (current_tb_modified) { + /* we generate a block containing just the instruction + modifying the memory. It will ensure that it cannot modify + itself */ + env->current_tb = NULL; + tb_gen_code(env, current_pc, current_cs_base, current_flags, 1); + cpu_resume_from_signal(env, puc); + } +#endif +} +#endif + +/* add the tb in the target page and protect it if necessary */ +static inline void tb_alloc_page(TranslationBlock *tb, + unsigned int n, tb_page_addr_t page_addr) +{ + PageDesc *p; + TranslationBlock *last_first_tb; + + tb->page_addr[n] = page_addr; + p = page_find_alloc(page_addr >> TARGET_PAGE_BITS, 1); + tb->page_next[n] = p->first_tb; + last_first_tb = p->first_tb; + p->first_tb = (TranslationBlock *)((long)tb | n); + invalidate_page_bitmap(p); + +#if defined(TARGET_HAS_SMC) || 1 + +#if defined(CONFIG_USER_ONLY) + if (p->flags & PAGE_WRITE) { + target_ulong addr; + PageDesc *p2; + int prot; + + /* force the host page as non writable (writes will have a + page fault + mprotect overhead) */ + page_addr &= qemu_host_page_mask; + prot = 0; + for(addr = page_addr; addr < page_addr + qemu_host_page_size; + addr += TARGET_PAGE_SIZE) { + + p2 = page_find (addr >> TARGET_PAGE_BITS); + if (!p2) + continue; + prot |= p2->flags; + p2->flags &= ~PAGE_WRITE; + } + mprotect(g2h(page_addr), qemu_host_page_size, + (prot & PAGE_BITS) & ~PAGE_WRITE); +#ifdef DEBUG_TB_INVALIDATE + printf("protecting code page: 0x" TARGET_FMT_lx "\n", + page_addr); +#endif + } +#else + /* if some code is already present, then the pages are already + protected. So we handle the case where only the first TB is + allocated in a physical page */ + if (!last_first_tb) { + tlb_protect_code(page_addr); + } +#endif + +#endif /* TARGET_HAS_SMC */ +} + +/* Allocate a new translation block. Flush the translation buffer if + too many translation blocks or too much generated code. */ +TranslationBlock *tb_alloc(target_ulong pc) +{ + TranslationBlock *tb; + + if (nb_tbs >= code_gen_max_blocks || + (code_gen_ptr - code_gen_buffer) >= code_gen_buffer_max_size) + return NULL; + tb = &tbs[nb_tbs++]; + tb->pc = pc; + tb->cflags = 0; + return tb; +} + +void tb_free(TranslationBlock *tb) +{ + /* In practice this is mostly used for single use temporary TB + Ignore the hard cases and just back up if this TB happens to + be the last one generated. */ + if (nb_tbs > 0 && tb == &tbs[nb_tbs - 1]) { + code_gen_ptr = tb->tc_ptr; + nb_tbs--; + } +} + +/* add a new TB and link it to the physical page tables. phys_page2 is + (-1) to indicate that only one page contains the TB. */ +void tb_link_page(TranslationBlock *tb, + tb_page_addr_t phys_pc, tb_page_addr_t phys_page2) +{ + unsigned int h; + TranslationBlock **ptb; + + /* Grab the mmap lock to stop another thread invalidating this TB + before we are done. */ + mmap_lock(); + /* add in the physical hash table */ + h = tb_phys_hash_func(phys_pc); + ptb = &tb_phys_hash[h]; + tb->phys_hash_next = *ptb; + *ptb = tb; + + /* add in the page list */ + tb_alloc_page(tb, 0, phys_pc & TARGET_PAGE_MASK); + if (phys_page2 != -1) + tb_alloc_page(tb, 1, phys_page2); + else + tb->page_addr[1] = -1; + + tb->jmp_first = (TranslationBlock *)((long)tb | 2); + tb->jmp_next[0] = NULL; + tb->jmp_next[1] = NULL; + + /* init original jump addresses */ + if (tb->tb_next_offset[0] != 0xffff) + tb_reset_jump(tb, 0); + if (tb->tb_next_offset[1] != 0xffff) + tb_reset_jump(tb, 1); + +#ifdef DEBUG_TB_CHECK + tb_page_check(); +#endif + mmap_unlock(); +} + +/* find the TB 'tb' such that tb[0].tc_ptr <= tc_ptr < + tb[1].tc_ptr. Return NULL if not found */ +TranslationBlock *tb_find_pc(unsigned long tc_ptr) +{ + int m_min, m_max, m; + unsigned long v; + TranslationBlock *tb; + + if (nb_tbs <= 0) + return NULL; + if (tc_ptr < (unsigned long)code_gen_buffer || + tc_ptr >= (unsigned long)code_gen_ptr) + return NULL; + /* binary search (cf Knuth) */ + m_min = 0; + m_max = nb_tbs - 1; + while (m_min <= m_max) { + m = (m_min + m_max) >> 1; + tb = &tbs[m]; + v = (unsigned long)tb->tc_ptr; + if (v == tc_ptr) + return tb; + else if (tc_ptr < v) { + m_max = m - 1; + } else { + m_min = m + 1; + } + } + return &tbs[m_max]; +} + +static void tb_reset_jump_recursive(TranslationBlock *tb); + +static inline void tb_reset_jump_recursive2(TranslationBlock *tb, int n) +{ + TranslationBlock *tb1, *tb_next, **ptb; + unsigned int n1; + + tb1 = tb->jmp_next[n]; + if (tb1 != NULL) { + /* find head of list */ + for(;;) { + n1 = (long)tb1 & 3; + tb1 = (TranslationBlock *)((long)tb1 & ~3); + if (n1 == 2) + break; + tb1 = tb1->jmp_next[n1]; + } + /* we are now sure now that tb jumps to tb1 */ + tb_next = tb1; + + /* remove tb from the jmp_first list */ + ptb = &tb_next->jmp_first; + for(;;) { + tb1 = *ptb; + n1 = (long)tb1 & 3; + tb1 = (TranslationBlock *)((long)tb1 & ~3); + if (n1 == n && tb1 == tb) + break; + ptb = &tb1->jmp_next[n1]; + } + *ptb = tb->jmp_next[n]; + tb->jmp_next[n] = NULL; + + /* suppress the jump to next tb in generated code */ + tb_reset_jump(tb, n); + + /* suppress jumps in the tb on which we could have jumped */ + tb_reset_jump_recursive(tb_next); + } +} + +static void tb_reset_jump_recursive(TranslationBlock *tb) +{ + tb_reset_jump_recursive2(tb, 0); + tb_reset_jump_recursive2(tb, 1); +} + +#if defined(TARGET_HAS_ICE) +#if defined(CONFIG_USER_ONLY) +static void breakpoint_invalidate(CPUState *env, target_ulong pc) +{ + tb_invalidate_phys_page_range(pc, pc + 1, 0); +} +#else +static void breakpoint_invalidate(CPUState *env, target_ulong pc) +{ + target_phys_addr_t addr; + target_ulong pd; + ram_addr_t ram_addr; + PhysPageDesc *p; + + addr = cpu_get_phys_page_debug(env, pc); + p = phys_page_find(addr >> TARGET_PAGE_BITS); + if (!p) { + pd = IO_MEM_UNASSIGNED; + } else { + pd = p->phys_offset; + } + ram_addr = (pd & TARGET_PAGE_MASK) | (pc & ~TARGET_PAGE_MASK); + tb_invalidate_phys_page_range(ram_addr, ram_addr + 1, 0); +} +#endif +#endif /* TARGET_HAS_ICE */ + +#if defined(CONFIG_USER_ONLY) +void cpu_watchpoint_remove_all(CPUState *env, int mask) + +{ +} + +int cpu_watchpoint_insert(CPUState *env, target_ulong addr, target_ulong len, + int flags, CPUWatchpoint **watchpoint) +{ + return -ENOSYS; +} +#else +/* Add a watchpoint. */ +int cpu_watchpoint_insert(CPUState *env, target_ulong addr, target_ulong len, + int flags, CPUWatchpoint **watchpoint) +{ + target_ulong len_mask = ~(len - 1); + CPUWatchpoint *wp; + + /* sanity checks: allow power-of-2 lengths, deny unaligned watchpoints */ + if ((len != 1 && len != 2 && len != 4 && len != 8) || (addr & ~len_mask)) { + fprintf(stderr, "qemu: tried to set invalid watchpoint at " + TARGET_FMT_lx ", len=" TARGET_FMT_lu "\n", addr, len); + return -EINVAL; + } + wp = qemu_malloc(sizeof(*wp)); + + wp->vaddr = addr; + wp->len_mask = len_mask; + wp->flags = flags; + + /* keep all GDB-injected watchpoints in front */ + if (flags & BP_GDB) + QTAILQ_INSERT_HEAD(&env->watchpoints, wp, entry); + else + QTAILQ_INSERT_TAIL(&env->watchpoints, wp, entry); + + tlb_flush_page(env, addr); + + if (watchpoint) + *watchpoint = wp; + return 0; +} + +/* Remove a specific watchpoint. */ +int cpu_watchpoint_remove(CPUState *env, target_ulong addr, target_ulong len, + int flags) +{ + target_ulong len_mask = ~(len - 1); + CPUWatchpoint *wp; + + QTAILQ_FOREACH(wp, &env->watchpoints, entry) { + if (addr == wp->vaddr && len_mask == wp->len_mask + && flags == (wp->flags & ~BP_WATCHPOINT_HIT)) { + cpu_watchpoint_remove_by_ref(env, wp); + return 0; + } + } + return -ENOENT; +} + +/* Remove a specific watchpoint by reference. */ +void cpu_watchpoint_remove_by_ref(CPUState *env, CPUWatchpoint *watchpoint) +{ + QTAILQ_REMOVE(&env->watchpoints, watchpoint, entry); + + tlb_flush_page(env, watchpoint->vaddr); + + qemu_free(watchpoint); +} + +/* Remove all matching watchpoints. */ +void cpu_watchpoint_remove_all(CPUState *env, int mask) +{ + CPUWatchpoint *wp, *next; + + QTAILQ_FOREACH_SAFE(wp, &env->watchpoints, entry, next) { + if (wp->flags & mask) + cpu_watchpoint_remove_by_ref(env, wp); + } +} +#endif + +/* Add a breakpoint. */ +int cpu_breakpoint_insert(CPUState *env, target_ulong pc, int flags, + CPUBreakpoint **breakpoint) +{ +#if defined(TARGET_HAS_ICE) + CPUBreakpoint *bp; + + bp = qemu_malloc(sizeof(*bp)); + + bp->pc = pc; + bp->flags = flags; + + /* keep all GDB-injected breakpoints in front */ + if (flags & BP_GDB) + QTAILQ_INSERT_HEAD(&env->breakpoints, bp, entry); + else + QTAILQ_INSERT_TAIL(&env->breakpoints, bp, entry); + + breakpoint_invalidate(env, pc); + + if (breakpoint) + *breakpoint = bp; + return 0; +#else + return -ENOSYS; +#endif +} + +/* Remove a specific breakpoint. */ +int cpu_breakpoint_remove(CPUState *env, target_ulong pc, int flags) +{ +#if defined(TARGET_HAS_ICE) + CPUBreakpoint *bp; + + QTAILQ_FOREACH(bp, &env->breakpoints, entry) { + if (bp->pc == pc && bp->flags == flags) { + cpu_breakpoint_remove_by_ref(env, bp); + return 0; + } + } + return -ENOENT; +#else + return -ENOSYS; +#endif +} + +/* Remove a specific breakpoint by reference. */ +void cpu_breakpoint_remove_by_ref(CPUState *env, CPUBreakpoint *breakpoint) +{ +#if defined(TARGET_HAS_ICE) + QTAILQ_REMOVE(&env->breakpoints, breakpoint, entry); + + breakpoint_invalidate(env, breakpoint->pc); + + qemu_free(breakpoint); +#endif +} + +/* Remove all matching breakpoints. */ +void cpu_breakpoint_remove_all(CPUState *env, int mask) +{ +#if defined(TARGET_HAS_ICE) + CPUBreakpoint *bp, *next; + + QTAILQ_FOREACH_SAFE(bp, &env->breakpoints, entry, next) { + if (bp->flags & mask) + cpu_breakpoint_remove_by_ref(env, bp); + } +#endif +} + +/* enable or disable single step mode. EXCP_DEBUG is returned by the + CPU loop after each instruction */ +void cpu_single_step(CPUState *env, int enabled) +{ +#if defined(TARGET_HAS_ICE) + if (env->singlestep_enabled != enabled) { + env->singlestep_enabled = enabled; + if (kvm_enabled()) + kvm_update_guest_debug(env, 0); + else { + /* must flush all the translated code to avoid inconsistencies */ + /* XXX: only flush what is necessary */ + tb_flush(env); + } + } +#endif +} + +/* enable or disable low levels log */ +void cpu_set_log(int log_flags) +{ + loglevel = log_flags; + if (loglevel && !logfile) { + logfile = fopen(logfilename, log_append ? "a" : "w"); + if (!logfile) { + perror(logfilename); + _exit(1); + } +#if !defined(CONFIG_SOFTMMU) + /* must avoid mmap() usage of glibc by setting a buffer "by hand" */ + { + static char logfile_buf[4096]; + setvbuf(logfile, logfile_buf, _IOLBF, sizeof(logfile_buf)); + } +#elif !defined(_WIN32) + /* Win32 doesn't support line-buffering and requires size >= 2 */ + setvbuf(logfile, NULL, _IOLBF, 0); +#endif + log_append = 1; + } + if (!loglevel && logfile) { + fclose(logfile); + logfile = NULL; + } +} + +void cpu_set_log_filename(const char *filename) +{ + logfilename = strdup(filename); + if (logfile) { + fclose(logfile); + logfile = NULL; + } + cpu_set_log(loglevel); +} + +static void cpu_unlink_tb(CPUState *env) +{ + /* FIXME: TB unchaining isn't SMP safe. For now just ignore the + problem and hope the cpu will stop of its own accord. For userspace + emulation this often isn't actually as bad as it sounds. Often + signals are used primarily to interrupt blocking syscalls. */ + TranslationBlock *tb; + static spinlock_t interrupt_lock = SPIN_LOCK_UNLOCKED; + + spin_lock(&interrupt_lock); + tb = env->current_tb; + /* if the cpu is currently executing code, we must unlink it and + all the potentially executing TB */ + if (tb) { + env->current_tb = NULL; + tb_reset_jump_recursive(tb); + } + spin_unlock(&interrupt_lock); +} + +/* mask must never be zero, except for A20 change call */ +void cpu_interrupt(CPUState *env, int mask) +{ + int old_mask; + + old_mask = env->interrupt_request; + env->interrupt_request |= mask; + +#ifndef CONFIG_USER_ONLY + /* + * If called from iothread context, wake the target cpu in + * case its halted. + */ + if (!qemu_cpu_self(env)) { + qemu_cpu_kick(env); + return; + } +#endif + + if (use_icount) { + env->icount_decr.u16.high = 0xffff; +#ifndef CONFIG_USER_ONLY + if (!can_do_io(env) + && (mask & ~old_mask) != 0) { + cpu_abort(env, "Raised interrupt while not in I/O function"); + } +#endif + } else { + cpu_unlink_tb(env); + } +} + +void cpu_reset_interrupt(CPUState *env, int mask) +{ + env->interrupt_request &= ~mask; +} + +void cpu_exit(CPUState *env) +{ + env->exit_request = 1; + cpu_unlink_tb(env); +} + +const CPULogItem cpu_log_items[] = { + { CPU_LOG_TB_OUT_ASM, "out_asm", + "show generated host assembly code for each compiled TB" }, + { CPU_LOG_TB_IN_ASM, "in_asm", + "show target assembly code for each compiled TB" }, + { CPU_LOG_TB_OP, "op", + "show micro ops for each compiled TB" }, + { CPU_LOG_TB_OP_OPT, "op_opt", + "show micro ops " +#ifdef TARGET_I386 + "before eflags optimization and " +#endif + "after liveness analysis" }, + { CPU_LOG_INT, "int", + "show interrupts/exceptions in short format" }, + { CPU_LOG_EXEC, "exec", + "show trace before each executed TB (lots of logs)" }, + { CPU_LOG_TB_CPU, "cpu", + "show CPU state before block translation" }, +#ifdef TARGET_I386 + { CPU_LOG_PCALL, "pcall", + "show protected mode far calls/returns/exceptions" }, + { CPU_LOG_RESET, "cpu_reset", + "show CPU state before CPU resets" }, +#endif +#ifdef DEBUG_IOPORT + { CPU_LOG_IOPORT, "ioport", + "show all i/o ports accesses" }, +#endif + { 0, NULL, NULL }, +}; + +#ifndef CONFIG_USER_ONLY +static QLIST_HEAD(memory_client_list, CPUPhysMemoryClient) memory_client_list + = QLIST_HEAD_INITIALIZER(memory_client_list); + +static void cpu_notify_set_memory(target_phys_addr_t start_addr, + ram_addr_t size, + ram_addr_t phys_offset) +{ + CPUPhysMemoryClient *client; + QLIST_FOREACH(client, &memory_client_list, list) { + client->set_memory(client, start_addr, size, phys_offset); + } +} + +static int cpu_notify_sync_dirty_bitmap(target_phys_addr_t start, + target_phys_addr_t end) +{ + CPUPhysMemoryClient *client; + QLIST_FOREACH(client, &memory_client_list, list) { + int r = client->sync_dirty_bitmap(client, start, end); + if (r < 0) + return r; + } + return 0; +} + +static int cpu_notify_migration_log(int enable) +{ + CPUPhysMemoryClient *client; + QLIST_FOREACH(client, &memory_client_list, list) { + int r = client->migration_log(client, enable); + if (r < 0) + return r; + } + return 0; +} + +static void phys_page_for_each_1(CPUPhysMemoryClient *client, + int level, void **lp) +{ + int i; + + if (*lp == NULL) { + return; + } + if (level == 0) { + PhysPageDesc *pd = *lp; + for (i = 0; i < L2_SIZE; ++i) { + if (pd[i].phys_offset != IO_MEM_UNASSIGNED) { + client->set_memory(client, pd[i].region_offset, + TARGET_PAGE_SIZE, pd[i].phys_offset); + } + } + } else { + void **pp = *lp; + for (i = 0; i < L2_SIZE; ++i) { + phys_page_for_each_1(client, level - 1, pp + i); + } + } +} + +static void phys_page_for_each(CPUPhysMemoryClient *client) +{ + int i; + for (i = 0; i < P_L1_SIZE; ++i) { + phys_page_for_each_1(client, P_L1_SHIFT / L2_BITS - 1, + l1_phys_map + 1); + } +} + +void cpu_register_phys_memory_client(CPUPhysMemoryClient *client) +{ + QLIST_INSERT_HEAD(&memory_client_list, client, list); + phys_page_for_each(client); +} + +void cpu_unregister_phys_memory_client(CPUPhysMemoryClient *client) +{ + QLIST_REMOVE(client, list); +} +#endif + +static int cmp1(const char *s1, int n, const char *s2) +{ + if (strlen(s2) != n) + return 0; + return memcmp(s1, s2, n) == 0; +} + +/* takes a comma separated list of log masks. Return 0 if error. */ +int cpu_str_to_log_mask(const char *str) +{ + const CPULogItem *item; + int mask; + const char *p, *p1; + + p = str; + mask = 0; + for(;;) { + p1 = strchr(p, ','); + if (!p1) + p1 = p + strlen(p); + if(cmp1(p,p1-p,"all")) { + for(item = cpu_log_items; item->mask != 0; item++) { + mask |= item->mask; + } + } else { + for(item = cpu_log_items; item->mask != 0; item++) { + if (cmp1(p, p1 - p, item->name)) + goto found; + } + return 0; + } + found: + mask |= item->mask; + if (*p1 != ',') + break; + p = p1 + 1; + } + return mask; +} + +void cpu_abort(CPUState *env, const char *fmt, ...) +{ + va_list ap; + va_list ap2; + + va_start(ap, fmt); + va_copy(ap2, ap); + fprintf(stderr, "qemu: fatal: "); + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); +#ifdef TARGET_I386 + cpu_dump_state(env, stderr, fprintf, X86_DUMP_FPU | X86_DUMP_CCOP); +#else + cpu_dump_state(env, stderr, fprintf, 0); +#endif + if (qemu_log_enabled()) { + qemu_log("qemu: fatal: "); + qemu_log_vprintf(fmt, ap2); + qemu_log("\n"); +#ifdef TARGET_I386 + log_cpu_state(env, X86_DUMP_FPU | X86_DUMP_CCOP); +#else + log_cpu_state(env, 0); +#endif + qemu_log_flush(); + qemu_log_close(); + } + va_end(ap2); + va_end(ap); +#if defined(CONFIG_USER_ONLY) + { + struct sigaction act; + sigfillset(&act.sa_mask); + act.sa_handler = SIG_DFL; + sigaction(SIGABRT, &act, NULL); + } +#endif + abort(); +} + +CPUState *cpu_copy(CPUState *env) +{ + CPUState *new_env = cpu_init(env->cpu_model_str); + CPUState *next_cpu = new_env->next_cpu; + int cpu_index = new_env->cpu_index; +#if defined(TARGET_HAS_ICE) + CPUBreakpoint *bp; + CPUWatchpoint *wp; +#endif + + memcpy(new_env, env, sizeof(CPUState)); + + /* Preserve chaining and index. */ + new_env->next_cpu = next_cpu; + new_env->cpu_index = cpu_index; + + /* Clone all break/watchpoints. + Note: Once we support ptrace with hw-debug register access, make sure + BP_CPU break/watchpoints are handled correctly on clone. */ + QTAILQ_INIT(&env->breakpoints); + QTAILQ_INIT(&env->watchpoints); +#if defined(TARGET_HAS_ICE) + QTAILQ_FOREACH(bp, &env->breakpoints, entry) { + cpu_breakpoint_insert(new_env, bp->pc, bp->flags, NULL); + } + QTAILQ_FOREACH(wp, &env->watchpoints, entry) { + cpu_watchpoint_insert(new_env, wp->vaddr, (~wp->len_mask) + 1, + wp->flags, NULL); + } +#endif + + return new_env; +} + +#if !defined(CONFIG_USER_ONLY) + +static inline void tlb_flush_jmp_cache(CPUState *env, target_ulong addr) +{ + unsigned int i; + + /* Discard jump cache entries for any tb which might potentially + overlap the flushed page. */ + i = tb_jmp_cache_hash_page(addr - TARGET_PAGE_SIZE); + memset (&env->tb_jmp_cache[i], 0, + TB_JMP_PAGE_SIZE * sizeof(TranslationBlock *)); + + i = tb_jmp_cache_hash_page(addr); + memset (&env->tb_jmp_cache[i], 0, + TB_JMP_PAGE_SIZE * sizeof(TranslationBlock *)); +} + +static CPUTLBEntry s_cputlb_empty_entry = { + .addr_read = -1, + .addr_write = -1, + .addr_code = -1, + .addend = -1, +}; + +/* NOTE: if flush_global is true, also flush global entries (not + implemented yet) */ +void tlb_flush(CPUState *env, int flush_global) +{ + int i; + +#if defined(DEBUG_TLB) + printf("tlb_flush:\n"); +#endif + /* must reset current TB so that interrupts cannot modify the + links while we are modifying them */ + env->current_tb = NULL; + + for(i = 0; i < CPU_TLB_SIZE; i++) { + int mmu_idx; + for (mmu_idx = 0; mmu_idx < NB_MMU_MODES; mmu_idx++) { + env->tlb_table[mmu_idx][i] = s_cputlb_empty_entry; + } + } + + memset (env->tb_jmp_cache, 0, TB_JMP_CACHE_SIZE * sizeof (void *)); + + env->tlb_flush_addr = -1; + env->tlb_flush_mask = 0; + tlb_flush_count++; +} + +static inline void tlb_flush_entry(CPUTLBEntry *tlb_entry, target_ulong addr) +{ + if (addr == (tlb_entry->addr_read & + (TARGET_PAGE_MASK | TLB_INVALID_MASK)) || + addr == (tlb_entry->addr_write & + (TARGET_PAGE_MASK | TLB_INVALID_MASK)) || + addr == (tlb_entry->addr_code & + (TARGET_PAGE_MASK | TLB_INVALID_MASK))) { + *tlb_entry = s_cputlb_empty_entry; + } +} + +void tlb_flush_page(CPUState *env, target_ulong addr) +{ + int i; + int mmu_idx; + +#if defined(DEBUG_TLB) + printf("tlb_flush_page: " TARGET_FMT_lx "\n", addr); +#endif + /* Check if we need to flush due to large pages. */ + if ((addr & env->tlb_flush_mask) == env->tlb_flush_addr) { +#if defined(DEBUG_TLB) + printf("tlb_flush_page: forced full flush (" + TARGET_FMT_lx "/" TARGET_FMT_lx ")\n", + env->tlb_flush_addr, env->tlb_flush_mask); +#endif + tlb_flush(env, 1); + return; + } + /* must reset current TB so that interrupts cannot modify the + links while we are modifying them */ + env->current_tb = NULL; + + addr &= TARGET_PAGE_MASK; + i = (addr >> TARGET_PAGE_BITS) & (CPU_TLB_SIZE - 1); + for (mmu_idx = 0; mmu_idx < NB_MMU_MODES; mmu_idx++) + tlb_flush_entry(&env->tlb_table[mmu_idx][i], addr); + + tlb_flush_jmp_cache(env, addr); +} + +/* update the TLBs so that writes to code in the virtual page 'addr' + can be detected */ +static void tlb_protect_code(ram_addr_t ram_addr) +{ + cpu_physical_memory_reset_dirty(ram_addr, + ram_addr + TARGET_PAGE_SIZE, + CODE_DIRTY_FLAG); +} + +/* update the TLB so that writes in physical page 'phys_addr' are no longer + tested for self modifying code */ +static void tlb_unprotect_code_phys(CPUState *env, ram_addr_t ram_addr, + target_ulong vaddr) +{ + cpu_physical_memory_set_dirty_flags(ram_addr, CODE_DIRTY_FLAG); +} + +static inline void tlb_reset_dirty_range(CPUTLBEntry *tlb_entry, + unsigned long start, unsigned long length) +{ + unsigned long addr; + if ((tlb_entry->addr_write & ~TARGET_PAGE_MASK) == IO_MEM_RAM) { + addr = (tlb_entry->addr_write & TARGET_PAGE_MASK) + tlb_entry->addend; + if ((addr - start) < length) { + tlb_entry->addr_write = (tlb_entry->addr_write & TARGET_PAGE_MASK) | TLB_NOTDIRTY; + } + } +} + +/* Note: start and end must be within the same ram block. */ +void cpu_physical_memory_reset_dirty(ram_addr_t start, ram_addr_t end, + int dirty_flags) +{ + CPUState *env; + unsigned long length, start1; + int i; + + start &= TARGET_PAGE_MASK; + end = TARGET_PAGE_ALIGN(end); + + length = end - start; + if (length == 0) + return; + cpu_physical_memory_mask_dirty_range(start, length, dirty_flags); + + /* we modify the TLB cache so that the dirty bit will be set again + when accessing the range */ + start1 = (unsigned long)qemu_safe_ram_ptr(start); + /* Chek that we don't span multiple blocks - this breaks the + address comparisons below. */ + if ((unsigned long)qemu_safe_ram_ptr(end - 1) - start1 + != (end - 1) - start) { + abort(); + } + + for(env = first_cpu; env != NULL; env = env->next_cpu) { + int mmu_idx; + for (mmu_idx = 0; mmu_idx < NB_MMU_MODES; mmu_idx++) { + for(i = 0; i < CPU_TLB_SIZE; i++) + tlb_reset_dirty_range(&env->tlb_table[mmu_idx][i], + start1, length); + } + } +} + +int cpu_physical_memory_set_dirty_tracking(int enable) +{ + int ret = 0; + in_migration = enable; + ret = cpu_notify_migration_log(!!enable); + return ret; +} + +int cpu_physical_memory_get_dirty_tracking(void) +{ + return in_migration; +} + +int cpu_physical_sync_dirty_bitmap(target_phys_addr_t start_addr, + target_phys_addr_t end_addr) +{ + int ret; + + ret = cpu_notify_sync_dirty_bitmap(start_addr, end_addr); + return ret; +} + +static inline void tlb_update_dirty(CPUTLBEntry *tlb_entry) +{ + ram_addr_t ram_addr; + void *p; + + if ((tlb_entry->addr_write & ~TARGET_PAGE_MASK) == IO_MEM_RAM) { + p = (void *)(unsigned long)((tlb_entry->addr_write & TARGET_PAGE_MASK) + + tlb_entry->addend); + ram_addr = qemu_ram_addr_from_host_nofail(p); + if (!cpu_physical_memory_is_dirty(ram_addr)) { + tlb_entry->addr_write |= TLB_NOTDIRTY; + } + } +} + +/* update the TLB according to the current state of the dirty bits */ +void cpu_tlb_update_dirty(CPUState *env) +{ + int i; + int mmu_idx; + for (mmu_idx = 0; mmu_idx < NB_MMU_MODES; mmu_idx++) { + for(i = 0; i < CPU_TLB_SIZE; i++) + tlb_update_dirty(&env->tlb_table[mmu_idx][i]); + } +} + +static inline void tlb_set_dirty1(CPUTLBEntry *tlb_entry, target_ulong vaddr) +{ + if (tlb_entry->addr_write == (vaddr | TLB_NOTDIRTY)) + tlb_entry->addr_write = vaddr; +} + +/* update the TLB corresponding to virtual page vaddr + so that it is no longer dirty */ +static inline void tlb_set_dirty(CPUState *env, target_ulong vaddr) +{ + int i; + int mmu_idx; + + vaddr &= TARGET_PAGE_MASK; + i = (vaddr >> TARGET_PAGE_BITS) & (CPU_TLB_SIZE - 1); + for (mmu_idx = 0; mmu_idx < NB_MMU_MODES; mmu_idx++) + tlb_set_dirty1(&env->tlb_table[mmu_idx][i], vaddr); +} + +/* Our TLB does not support large pages, so remember the area covered by + large pages and trigger a full TLB flush if these are invalidated. */ +static void tlb_add_large_page(CPUState *env, target_ulong vaddr, + target_ulong size) +{ + target_ulong mask = ~(size - 1); + + if (env->tlb_flush_addr == (target_ulong)-1) { + env->tlb_flush_addr = vaddr & mask; + env->tlb_flush_mask = mask; + return; + } + /* Extend the existing region to include the new page. + This is a compromise between unnecessary flushes and the cost + of maintaining a full variable size TLB. */ + mask &= env->tlb_flush_mask; + while (((env->tlb_flush_addr ^ vaddr) & mask) != 0) { + mask <<= 1; + } + env->tlb_flush_addr &= mask; + env->tlb_flush_mask = mask; +} + +/* Add a new TLB entry. At most one entry for a given virtual address + is permitted. Only a single TARGET_PAGE_SIZE region is mapped, the + supplied size is only used by tlb_flush_page. */ +void tlb_set_page(CPUState *env, target_ulong vaddr, + target_phys_addr_t paddr, int prot, + int mmu_idx, target_ulong size) +{ + PhysPageDesc *p; + unsigned long pd; + unsigned int index; + target_ulong address; + target_ulong code_address; + unsigned long addend; + CPUTLBEntry *te; + CPUWatchpoint *wp; + target_phys_addr_t iotlb; + + assert(size >= TARGET_PAGE_SIZE); + if (size != TARGET_PAGE_SIZE) { + tlb_add_large_page(env, vaddr, size); + } + p = phys_page_find(paddr >> TARGET_PAGE_BITS); + if (!p) { + pd = IO_MEM_UNASSIGNED; + } else { + pd = p->phys_offset; + } +#if defined(DEBUG_TLB) + printf("tlb_set_page: vaddr=" TARGET_FMT_lx " paddr=0x" TARGET_FMT_plx + " prot=%x idx=%d pd=0x%08lx\n", + vaddr, paddr, prot, mmu_idx, pd); +#endif + + address = vaddr; + if ((pd & ~TARGET_PAGE_MASK) > IO_MEM_ROM && !(pd & IO_MEM_ROMD)) { + /* IO memory case (romd handled later) */ + address |= TLB_MMIO; + } + addend = (unsigned long)qemu_get_ram_ptr(pd & TARGET_PAGE_MASK); + if ((pd & ~TARGET_PAGE_MASK) <= IO_MEM_ROM) { + /* Normal RAM. */ + iotlb = pd & TARGET_PAGE_MASK; + if ((pd & ~TARGET_PAGE_MASK) == IO_MEM_RAM) + iotlb |= IO_MEM_NOTDIRTY; + else + iotlb |= IO_MEM_ROM; + } else { + /* IO handlers are currently passed a physical address. + It would be nice to pass an offset from the base address + of that region. This would avoid having to special case RAM, + and avoid full address decoding in every device. + We can't use the high bits of pd for this because + IO_MEM_ROMD uses these as a ram address. */ + iotlb = (pd & ~TARGET_PAGE_MASK); + if (p) { + iotlb += p->region_offset; + } else { + iotlb += paddr; + } + } + + code_address = address; + /* Make accesses to pages with watchpoints go via the + watchpoint trap routines. */ + QTAILQ_FOREACH(wp, &env->watchpoints, entry) { + if (vaddr == (wp->vaddr & TARGET_PAGE_MASK)) { + /* Avoid trapping reads of pages with a write breakpoint. */ + if ((prot & PAGE_WRITE) || (wp->flags & BP_MEM_READ)) { + iotlb = io_mem_watch + paddr; + address |= TLB_MMIO; + break; + } + } + } + + index = (vaddr >> TARGET_PAGE_BITS) & (CPU_TLB_SIZE - 1); + env->iotlb[mmu_idx][index] = iotlb - vaddr; + te = &env->tlb_table[mmu_idx][index]; + te->addend = addend - vaddr; + if (prot & PAGE_READ) { + te->addr_read = address; + } else { + te->addr_read = -1; + } + + if (prot & PAGE_EXEC) { + te->addr_code = code_address; + } else { + te->addr_code = -1; + } + if (prot & PAGE_WRITE) { + if ((pd & ~TARGET_PAGE_MASK) == IO_MEM_ROM || + (pd & IO_MEM_ROMD)) { + /* Write access calls the I/O callback. */ + te->addr_write = address | TLB_MMIO; + } else if ((pd & ~TARGET_PAGE_MASK) == IO_MEM_RAM && + !cpu_physical_memory_is_dirty(pd)) { + te->addr_write = address | TLB_NOTDIRTY; + } else { + te->addr_write = address; + } + } else { + te->addr_write = -1; + } +} + +#else + +void tlb_flush(CPUState *env, int flush_global) +{ +} + +void tlb_flush_page(CPUState *env, target_ulong addr) +{ +} + +/* + * Walks guest process memory "regions" one by one + * and calls callback function 'fn' for each region. + */ + +struct walk_memory_regions_data +{ + walk_memory_regions_fn fn; + void *priv; + unsigned long start; + int prot; +}; + +static int walk_memory_regions_end(struct walk_memory_regions_data *data, + abi_ulong end, int new_prot) +{ + if (data->start != -1ul) { + int rc = data->fn(data->priv, data->start, end, data->prot); + if (rc != 0) { + return rc; + } + } + + data->start = (new_prot ? end : -1ul); + data->prot = new_prot; + + return 0; +} + +static int walk_memory_regions_1(struct walk_memory_regions_data *data, + abi_ulong base, int level, void **lp) +{ + abi_ulong pa; + int i, rc; + + if (*lp == NULL) { + return walk_memory_regions_end(data, base, 0); + } + + if (level == 0) { + PageDesc *pd = *lp; + for (i = 0; i < L2_SIZE; ++i) { + int prot = pd[i].flags; + + pa = base | (i << TARGET_PAGE_BITS); + if (prot != data->prot) { + rc = walk_memory_regions_end(data, pa, prot); + if (rc != 0) { + return rc; + } + } + } + } else { + void **pp = *lp; + for (i = 0; i < L2_SIZE; ++i) { + pa = base | ((abi_ulong)i << + (TARGET_PAGE_BITS + L2_BITS * level)); + rc = walk_memory_regions_1(data, pa, level - 1, pp + i); + if (rc != 0) { + return rc; + } + } + } + + return 0; +} + +int walk_memory_regions(void *priv, walk_memory_regions_fn fn) +{ + struct walk_memory_regions_data data; + unsigned long i; + + data.fn = fn; + data.priv = priv; + data.start = -1ul; + data.prot = 0; + + for (i = 0; i < V_L1_SIZE; i++) { + int rc = walk_memory_regions_1(&data, (abi_ulong)i << V_L1_SHIFT, + V_L1_SHIFT / L2_BITS - 1, l1_map + i); + if (rc != 0) { + return rc; + } + } + + return walk_memory_regions_end(&data, 0, 0); +} + +static int dump_region(void *priv, abi_ulong start, + abi_ulong end, unsigned long prot) +{ + FILE *f = (FILE *)priv; + + (void) fprintf(f, TARGET_ABI_FMT_lx"-"TARGET_ABI_FMT_lx + " "TARGET_ABI_FMT_lx" %c%c%c\n", + start, end, end - start, + ((prot & PAGE_READ) ? 'r' : '-'), + ((prot & PAGE_WRITE) ? 'w' : '-'), + ((prot & PAGE_EXEC) ? 'x' : '-')); + + return (0); +} + +/* dump memory mappings */ +void page_dump(FILE *f) +{ + (void) fprintf(f, "%-8s %-8s %-8s %s\n", + "start", "end", "size", "prot"); + walk_memory_regions(f, dump_region); +} + +int page_get_flags(target_ulong address) +{ + PageDesc *p; + + p = page_find(address >> TARGET_PAGE_BITS); + if (!p) + return 0; + return p->flags; +} + +/* Modify the flags of a page and invalidate the code if necessary. + The flag PAGE_WRITE_ORG is positioned automatically depending + on PAGE_WRITE. The mmap_lock should already be held. */ +void page_set_flags(target_ulong start, target_ulong end, int flags) +{ + target_ulong addr, len; + + /* This function should never be called with addresses outside the + guest address space. If this assert fires, it probably indicates + a missing call to h2g_valid. */ +#if TARGET_ABI_BITS > L1_MAP_ADDR_SPACE_BITS + assert(end < ((abi_ulong)1 << L1_MAP_ADDR_SPACE_BITS)); +#endif + assert(start < end); + + start = start & TARGET_PAGE_MASK; + end = TARGET_PAGE_ALIGN(end); + + if (flags & PAGE_WRITE) { + flags |= PAGE_WRITE_ORG; + } + + for (addr = start, len = end - start; + len != 0; + len -= TARGET_PAGE_SIZE, addr += TARGET_PAGE_SIZE) { + PageDesc *p = page_find_alloc(addr >> TARGET_PAGE_BITS, 1); + + /* If the write protection bit is set, then we invalidate + the code inside. */ + if (!(p->flags & PAGE_WRITE) && + (flags & PAGE_WRITE) && + p->first_tb) { + tb_invalidate_phys_page(addr, 0, NULL); + } + p->flags = flags; + } +} + +int page_check_range(target_ulong start, target_ulong len, int flags) +{ + PageDesc *p; + target_ulong end; + target_ulong addr; + + /* This function should never be called with addresses outside the + guest address space. If this assert fires, it probably indicates + a missing call to h2g_valid. */ +#if TARGET_ABI_BITS > L1_MAP_ADDR_SPACE_BITS + assert(start < ((abi_ulong)1 << L1_MAP_ADDR_SPACE_BITS)); +#endif + + if (len == 0) { + return 0; + } + if (start + len - 1 < start) { + /* We've wrapped around. */ + return -1; + } + + end = TARGET_PAGE_ALIGN(start+len); /* must do before we loose bits in the next step */ + start = start & TARGET_PAGE_MASK; + + for (addr = start, len = end - start; + len != 0; + len -= TARGET_PAGE_SIZE, addr += TARGET_PAGE_SIZE) { + p = page_find(addr >> TARGET_PAGE_BITS); + if( !p ) + return -1; + if( !(p->flags & PAGE_VALID) ) + return -1; + + if ((flags & PAGE_READ) && !(p->flags & PAGE_READ)) + return -1; + if (flags & PAGE_WRITE) { + if (!(p->flags & PAGE_WRITE_ORG)) + return -1; + /* unprotect the page if it was put read-only because it + contains translated code */ + if (!(p->flags & PAGE_WRITE)) { + if (!page_unprotect(addr, 0, NULL)) + return -1; + } + return 0; + } + } + return 0; +} + +/* called from signal handler: invalidate the code and unprotect the + page. Return TRUE if the fault was successfully handled. */ +int page_unprotect(target_ulong address, unsigned long pc, void *puc) +{ + unsigned int prot; + PageDesc *p; + target_ulong host_start, host_end, addr; + + /* Technically this isn't safe inside a signal handler. However we + know this only ever happens in a synchronous SEGV handler, so in + practice it seems to be ok. */ + mmap_lock(); + + p = page_find(address >> TARGET_PAGE_BITS); + if (!p) { + mmap_unlock(); + return 0; + } + + /* if the page was really writable, then we change its + protection back to writable */ + if ((p->flags & PAGE_WRITE_ORG) && !(p->flags & PAGE_WRITE)) { + host_start = address & qemu_host_page_mask; + host_end = host_start + qemu_host_page_size; + + prot = 0; + for (addr = host_start ; addr < host_end ; addr += TARGET_PAGE_SIZE) { + p = page_find(addr >> TARGET_PAGE_BITS); + p->flags |= PAGE_WRITE; + prot |= p->flags; + + /* and since the content will be modified, we must invalidate + the corresponding translated code. */ + tb_invalidate_phys_page(addr, pc, puc); +#ifdef DEBUG_TB_CHECK + tb_invalidate_check(addr); +#endif + } + mprotect((void *)g2h(host_start), qemu_host_page_size, + prot & PAGE_BITS); + + mmap_unlock(); + return 1; + } + mmap_unlock(); + return 0; +} + +static inline void tlb_set_dirty(CPUState *env, + unsigned long addr, target_ulong vaddr) +{ +} +#endif /* defined(CONFIG_USER_ONLY) */ + +#if !defined(CONFIG_USER_ONLY) + +#define SUBPAGE_IDX(addr) ((addr) & ~TARGET_PAGE_MASK) +typedef struct subpage_t { + target_phys_addr_t base; + ram_addr_t sub_io_index[TARGET_PAGE_SIZE]; + ram_addr_t region_offset[TARGET_PAGE_SIZE]; +} subpage_t; + +static int subpage_register (subpage_t *mmio, uint32_t start, uint32_t end, + ram_addr_t memory, ram_addr_t region_offset); +static subpage_t *subpage_init (target_phys_addr_t base, ram_addr_t *phys, + ram_addr_t orig_memory, + ram_addr_t region_offset); +#define CHECK_SUBPAGE(addr, start_addr, start_addr2, end_addr, end_addr2, \ + need_subpage) \ + do { \ + if (addr > start_addr) \ + start_addr2 = 0; \ + else { \ + start_addr2 = start_addr & ~TARGET_PAGE_MASK; \ + if (start_addr2 > 0) \ + need_subpage = 1; \ + } \ + \ + if ((start_addr + orig_size) - addr >= TARGET_PAGE_SIZE) \ + end_addr2 = TARGET_PAGE_SIZE - 1; \ + else { \ + end_addr2 = (start_addr + orig_size - 1) & ~TARGET_PAGE_MASK; \ + if (end_addr2 < TARGET_PAGE_SIZE - 1) \ + need_subpage = 1; \ + } \ + } while (0) + +/* register physical memory. + For RAM, 'size' must be a multiple of the target page size. + If (phys_offset & ~TARGET_PAGE_MASK) != 0, then it is an + io memory page. The address used when calling the IO function is + the offset from the start of the region, plus region_offset. Both + start_addr and region_offset are rounded down to a page boundary + before calculating this offset. This should not be a problem unless + the low bits of start_addr and region_offset differ. */ +void cpu_register_physical_memory_offset(target_phys_addr_t start_addr, + ram_addr_t size, + ram_addr_t phys_offset, + ram_addr_t region_offset) +{ + target_phys_addr_t addr, end_addr; + PhysPageDesc *p; + CPUState *env; + ram_addr_t orig_size = size; + subpage_t *subpage; + + cpu_notify_set_memory(start_addr, size, phys_offset); + + if (phys_offset == IO_MEM_UNASSIGNED) { + region_offset = start_addr; + } + region_offset &= TARGET_PAGE_MASK; + size = (size + TARGET_PAGE_SIZE - 1) & TARGET_PAGE_MASK; + end_addr = start_addr + (target_phys_addr_t)size; + for(addr = start_addr; addr != end_addr; addr += TARGET_PAGE_SIZE) { + p = phys_page_find(addr >> TARGET_PAGE_BITS); + if (p && p->phys_offset != IO_MEM_UNASSIGNED) { + ram_addr_t orig_memory = p->phys_offset; + target_phys_addr_t start_addr2, end_addr2; + int need_subpage = 0; + + CHECK_SUBPAGE(addr, start_addr, start_addr2, end_addr, end_addr2, + need_subpage); + if (need_subpage) { + if (!(orig_memory & IO_MEM_SUBPAGE)) { + subpage = subpage_init((addr & TARGET_PAGE_MASK), + &p->phys_offset, orig_memory, + p->region_offset); + } else { + subpage = io_mem_opaque[(orig_memory & ~TARGET_PAGE_MASK) + >> IO_MEM_SHIFT]; + } + subpage_register(subpage, start_addr2, end_addr2, phys_offset, + region_offset); + p->region_offset = 0; + } else { + p->phys_offset = phys_offset; + if ((phys_offset & ~TARGET_PAGE_MASK) <= IO_MEM_ROM || + (phys_offset & IO_MEM_ROMD)) + phys_offset += TARGET_PAGE_SIZE; + } + } else { + p = phys_page_find_alloc(addr >> TARGET_PAGE_BITS, 1); + p->phys_offset = phys_offset; + p->region_offset = region_offset; + if ((phys_offset & ~TARGET_PAGE_MASK) <= IO_MEM_ROM || + (phys_offset & IO_MEM_ROMD)) { + phys_offset += TARGET_PAGE_SIZE; + } else { + target_phys_addr_t start_addr2, end_addr2; + int need_subpage = 0; + + CHECK_SUBPAGE(addr, start_addr, start_addr2, end_addr, + end_addr2, need_subpage); + + if (need_subpage) { + subpage = subpage_init((addr & TARGET_PAGE_MASK), + &p->phys_offset, IO_MEM_UNASSIGNED, + addr & TARGET_PAGE_MASK); + subpage_register(subpage, start_addr2, end_addr2, + phys_offset, region_offset); + p->region_offset = 0; + } + } + } + region_offset += TARGET_PAGE_SIZE; + } + + /* since each CPU stores ram addresses in its TLB cache, we must + reset the modified entries */ + /* XXX: slow ! */ + for(env = first_cpu; env != NULL; env = env->next_cpu) { + tlb_flush(env, 1); + } +} + +/* XXX: temporary until new memory mapping API */ +ram_addr_t cpu_get_physical_page_desc(target_phys_addr_t addr) +{ + PhysPageDesc *p; + + p = phys_page_find(addr >> TARGET_PAGE_BITS); + if (!p) + return IO_MEM_UNASSIGNED; + return p->phys_offset; +} + +void qemu_register_coalesced_mmio(target_phys_addr_t addr, ram_addr_t size) +{ + if (kvm_enabled()) + kvm_coalesce_mmio_region(addr, size); +} + +void qemu_unregister_coalesced_mmio(target_phys_addr_t addr, ram_addr_t size) +{ + if (kvm_enabled()) + kvm_uncoalesce_mmio_region(addr, size); +} + +void qemu_flush_coalesced_mmio_buffer(void) +{ + if (kvm_enabled()) + kvm_flush_coalesced_mmio_buffer(); +} + +#if defined(__linux__) && !defined(TARGET_S390X) + +#include + +#define HUGETLBFS_MAGIC 0x958458f6 + +static long gethugepagesize(const char *path) +{ + struct statfs fs; + int ret; + + do { + ret = statfs(path, &fs); + } while (ret != 0 && errno == EINTR); + + if (ret != 0) { + perror(path); + return 0; + } + + if (fs.f_type != HUGETLBFS_MAGIC) + fprintf(stderr, "Warning: path not on HugeTLBFS: %s\n", path); + + return fs.f_bsize; +} + +static void *file_ram_alloc(RAMBlock *block, + ram_addr_t memory, + const char *path) +{ + char *filename; + void *area; + int fd; +#ifdef MAP_POPULATE + int flags; +#endif + unsigned long hpagesize; + + hpagesize = gethugepagesize(path); + if (!hpagesize) { + return NULL; + } + + if (memory < hpagesize) { + return NULL; + } + + if (kvm_enabled() && !kvm_has_sync_mmu()) { + fprintf(stderr, "host lacks kvm mmu notifiers, -mem-path unsupported\n"); + return NULL; + } + + if (asprintf(&filename, "%s/qemu_back_mem.XXXXXX", path) == -1) { + return NULL; + } + + fd = mkstemp(filename); + if (fd < 0) { + perror("unable to create backing store for hugepages"); + free(filename); + return NULL; + } + unlink(filename); + free(filename); + + memory = (memory+hpagesize-1) & ~(hpagesize-1); + + /* + * ftruncate is not supported by hugetlbfs in older + * hosts, so don't bother bailing out on errors. + * If anything goes wrong with it under other filesystems, + * mmap will fail. + */ + if (ftruncate(fd, memory)) + perror("ftruncate"); + +#ifdef MAP_POPULATE + /* NB: MAP_POPULATE won't exhaustively alloc all phys pages in the case + * MAP_PRIVATE is requested. For mem_prealloc we mmap as MAP_SHARED + * to sidestep this quirk. + */ + flags = mem_prealloc ? MAP_POPULATE | MAP_SHARED : MAP_PRIVATE; + area = mmap(0, memory, PROT_READ | PROT_WRITE, flags, fd, 0); +#else + area = mmap(0, memory, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0); +#endif + if (area == MAP_FAILED) { + perror("file_ram_alloc: can't mmap RAM pages"); + close(fd); + return (NULL); + } + block->fd = fd; + return area; +} +#endif + +static ram_addr_t find_ram_offset(ram_addr_t size) +{ + RAMBlock *block, *next_block; + ram_addr_t offset = 0, mingap = ULONG_MAX; + + if (QLIST_EMPTY(&ram_list.blocks)) + return 0; + + QLIST_FOREACH(block, &ram_list.blocks, next) { + ram_addr_t end, next = ULONG_MAX; + + end = block->offset + block->length; + + QLIST_FOREACH(next_block, &ram_list.blocks, next) { + if (next_block->offset >= end) { + next = MIN(next, next_block->offset); + } + } + if (next - end >= size && next - end < mingap) { + offset = end; + mingap = next - end; + } + } + return offset; +} + +static ram_addr_t last_ram_offset(void) +{ + RAMBlock *block; + ram_addr_t last = 0; + + QLIST_FOREACH(block, &ram_list.blocks, next) + last = MAX(last, block->offset + block->length); + + return last; +} + +ram_addr_t qemu_ram_alloc_from_ptr(DeviceState *dev, const char *name, + ram_addr_t size, void *host) +{ + RAMBlock *new_block, *block; + + size = TARGET_PAGE_ALIGN(size); + new_block = qemu_mallocz(sizeof(*new_block)); + + if (dev && dev->parent_bus && dev->parent_bus->info->get_dev_path) { + char *id = dev->parent_bus->info->get_dev_path(dev); + if (id) { + snprintf(new_block->idstr, sizeof(new_block->idstr), "%s/", id); + qemu_free(id); + } + } + pstrcat(new_block->idstr, sizeof(new_block->idstr), name); + + QLIST_FOREACH(block, &ram_list.blocks, next) { + if (!strcmp(block->idstr, new_block->idstr)) { + fprintf(stderr, "RAMBlock \"%s\" already registered, abort!\n", + new_block->idstr); + abort(); + } + } + + if (host) { + new_block->host = host; + } else { + if (mem_path) { +#if defined (__linux__) && !defined(TARGET_S390X) + new_block->host = file_ram_alloc(new_block, size, mem_path); + if (!new_block->host) { + new_block->host = qemu_vmalloc(size); + qemu_madvise(new_block->host, size, QEMU_MADV_MERGEABLE); + } +#else + fprintf(stderr, "-mem-path option unsupported\n"); + exit(1); +#endif + } else { +#if defined(TARGET_S390X) && defined(CONFIG_KVM) + /* XXX S390 KVM requires the topmost vma of the RAM to be < 256GB */ + new_block->host = mmap((void*)0x1000000, size, + PROT_EXEC|PROT_READ|PROT_WRITE, + MAP_SHARED | MAP_ANONYMOUS, -1, 0); +#else + new_block->host = qemu_vmalloc(size); +#endif + qemu_madvise(new_block->host, size, QEMU_MADV_MERGEABLE); + } + } + + new_block->offset = find_ram_offset(size); + new_block->length = size; + + QLIST_INSERT_HEAD(&ram_list.blocks, new_block, next); + + ram_list.phys_dirty = qemu_realloc(ram_list.phys_dirty, + last_ram_offset() >> TARGET_PAGE_BITS); + memset(ram_list.phys_dirty + (new_block->offset >> TARGET_PAGE_BITS), + 0xff, size >> TARGET_PAGE_BITS); + + if (kvm_enabled()) + kvm_setup_guest_memory(new_block->host, size); + + return new_block->offset; +} + +ram_addr_t qemu_ram_alloc(DeviceState *dev, const char *name, ram_addr_t size) +{ + return qemu_ram_alloc_from_ptr(dev, name, size, NULL); +} + +void qemu_ram_free(ram_addr_t addr) +{ + RAMBlock *block; + + QLIST_FOREACH(block, &ram_list.blocks, next) { + if (addr == block->offset) { + QLIST_REMOVE(block, next); + if (mem_path) { +#if defined (__linux__) && !defined(TARGET_S390X) + if (block->fd) { + munmap(block->host, block->length); + close(block->fd); + } else { + qemu_vfree(block->host); + } +#endif + } else { +#if defined(TARGET_S390X) && defined(CONFIG_KVM) + munmap(block->host, block->length); +#else + qemu_vfree(block->host); +#endif + } + qemu_free(block); + return; + } + } + +} + +/* Return a host pointer to ram allocated with qemu_ram_alloc. + With the exception of the softmmu code in this file, this should + only be used for local memory (e.g. video ram) that the device owns, + and knows it isn't going to access beyond the end of the block. + + It should not be used for general purpose DMA. + Use cpu_physical_memory_map/cpu_physical_memory_rw instead. + */ +void *qemu_get_ram_ptr(ram_addr_t addr) +{ + RAMBlock *block; + + QLIST_FOREACH(block, &ram_list.blocks, next) { + if (addr - block->offset < block->length) { + QLIST_REMOVE(block, next); + QLIST_INSERT_HEAD(&ram_list.blocks, block, next); + return block->host + (addr - block->offset); + } + } + + fprintf(stderr, "Bad ram offset %" PRIx64 "\n", (uint64_t)addr); + abort(); + + return NULL; +} + +/* Return a host pointer to ram allocated with qemu_ram_alloc. + * Same as qemu_get_ram_ptr but avoid reordering ramblocks. + */ +void *qemu_safe_ram_ptr(ram_addr_t addr) +{ + RAMBlock *block; + + QLIST_FOREACH(block, &ram_list.blocks, next) { + if (addr - block->offset < block->length) { + return block->host + (addr - block->offset); + } + } + + fprintf(stderr, "Bad ram offset %" PRIx64 "\n", (uint64_t)addr); + abort(); + + return NULL; +} + +int qemu_ram_addr_from_host(void *ptr, ram_addr_t *ram_addr) +{ + RAMBlock *block; + uint8_t *host = ptr; + + QLIST_FOREACH(block, &ram_list.blocks, next) { + if (host - block->host < block->length) { + *ram_addr = block->offset + (host - block->host); + return 0; + } + } + return -1; +} + +/* Some of the softmmu routines need to translate from a host pointer + (typically a TLB entry) back to a ram offset. */ +ram_addr_t qemu_ram_addr_from_host_nofail(void *ptr) +{ + ram_addr_t ram_addr; + + if (qemu_ram_addr_from_host(ptr, &ram_addr)) { + fprintf(stderr, "Bad ram pointer %p\n", ptr); + abort(); + } + return ram_addr; +} + +static uint32_t unassigned_mem_readb(void *opaque, target_phys_addr_t addr) +{ +#ifdef DEBUG_UNASSIGNED + printf("Unassigned mem read " TARGET_FMT_plx "\n", addr); +#endif +#if defined(TARGET_SPARC) || defined(TARGET_MICROBLAZE) + do_unassigned_access(addr, 0, 0, 0, 1); +#endif + return 0; +} + +static uint32_t unassigned_mem_readw(void *opaque, target_phys_addr_t addr) +{ +#ifdef DEBUG_UNASSIGNED + printf("Unassigned mem read " TARGET_FMT_plx "\n", addr); +#endif +#if defined(TARGET_SPARC) || defined(TARGET_MICROBLAZE) + do_unassigned_access(addr, 0, 0, 0, 2); +#endif + return 0; +} + +static uint32_t unassigned_mem_readl(void *opaque, target_phys_addr_t addr) +{ +#ifdef DEBUG_UNASSIGNED + printf("Unassigned mem read " TARGET_FMT_plx "\n", addr); +#endif +#if defined(TARGET_SPARC) || defined(TARGET_MICROBLAZE) + do_unassigned_access(addr, 0, 0, 0, 4); +#endif + return 0; +} + +static void unassigned_mem_writeb(void *opaque, target_phys_addr_t addr, uint32_t val) +{ +#ifdef DEBUG_UNASSIGNED + printf("Unassigned mem write " TARGET_FMT_plx " = 0x%x\n", addr, val); +#endif +#if defined(TARGET_SPARC) || defined(TARGET_MICROBLAZE) + do_unassigned_access(addr, 1, 0, 0, 1); +#endif +} + +static void unassigned_mem_writew(void *opaque, target_phys_addr_t addr, uint32_t val) +{ +#ifdef DEBUG_UNASSIGNED + printf("Unassigned mem write " TARGET_FMT_plx " = 0x%x\n", addr, val); +#endif +#if defined(TARGET_SPARC) || defined(TARGET_MICROBLAZE) + do_unassigned_access(addr, 1, 0, 0, 2); +#endif +} + +static void unassigned_mem_writel(void *opaque, target_phys_addr_t addr, uint32_t val) +{ +#ifdef DEBUG_UNASSIGNED + printf("Unassigned mem write " TARGET_FMT_plx " = 0x%x\n", addr, val); +#endif +#if defined(TARGET_SPARC) || defined(TARGET_MICROBLAZE) + do_unassigned_access(addr, 1, 0, 0, 4); +#endif +} + +static CPUReadMemoryFunc * const unassigned_mem_read[3] = { + unassigned_mem_readb, + unassigned_mem_readw, + unassigned_mem_readl, +}; + +static CPUWriteMemoryFunc * const unassigned_mem_write[3] = { + unassigned_mem_writeb, + unassigned_mem_writew, + unassigned_mem_writel, +}; + +static void notdirty_mem_writeb(void *opaque, target_phys_addr_t ram_addr, + uint32_t val) +{ + int dirty_flags; + dirty_flags = cpu_physical_memory_get_dirty_flags(ram_addr); + if (!(dirty_flags & CODE_DIRTY_FLAG)) { +#if !defined(CONFIG_USER_ONLY) + tb_invalidate_phys_page_fast(ram_addr, 1); + dirty_flags = cpu_physical_memory_get_dirty_flags(ram_addr); +#endif + } + stb_p(qemu_get_ram_ptr(ram_addr), val); + dirty_flags |= (0xff & ~CODE_DIRTY_FLAG); + cpu_physical_memory_set_dirty_flags(ram_addr, dirty_flags); + /* we remove the notdirty callback only if the code has been + flushed */ + if (dirty_flags == 0xff) + tlb_set_dirty(cpu_single_env, cpu_single_env->mem_io_vaddr); +} + +static void notdirty_mem_writew(void *opaque, target_phys_addr_t ram_addr, + uint32_t val) +{ + int dirty_flags; + dirty_flags = cpu_physical_memory_get_dirty_flags(ram_addr); + if (!(dirty_flags & CODE_DIRTY_FLAG)) { +#if !defined(CONFIG_USER_ONLY) + tb_invalidate_phys_page_fast(ram_addr, 2); + dirty_flags = cpu_physical_memory_get_dirty_flags(ram_addr); +#endif + } + stw_p(qemu_get_ram_ptr(ram_addr), val); + dirty_flags |= (0xff & ~CODE_DIRTY_FLAG); + cpu_physical_memory_set_dirty_flags(ram_addr, dirty_flags); + /* we remove the notdirty callback only if the code has been + flushed */ + if (dirty_flags == 0xff) + tlb_set_dirty(cpu_single_env, cpu_single_env->mem_io_vaddr); +} + +static void notdirty_mem_writel(void *opaque, target_phys_addr_t ram_addr, + uint32_t val) +{ + int dirty_flags; + dirty_flags = cpu_physical_memory_get_dirty_flags(ram_addr); + if (!(dirty_flags & CODE_DIRTY_FLAG)) { +#if !defined(CONFIG_USER_ONLY) + tb_invalidate_phys_page_fast(ram_addr, 4); + dirty_flags = cpu_physical_memory_get_dirty_flags(ram_addr); +#endif + } + stl_p(qemu_get_ram_ptr(ram_addr), val); + dirty_flags |= (0xff & ~CODE_DIRTY_FLAG); + cpu_physical_memory_set_dirty_flags(ram_addr, dirty_flags); + /* we remove the notdirty callback only if the code has been + flushed */ + if (dirty_flags == 0xff) + tlb_set_dirty(cpu_single_env, cpu_single_env->mem_io_vaddr); +} + +static CPUReadMemoryFunc * const error_mem_read[3] = { + NULL, /* never used */ + NULL, /* never used */ + NULL, /* never used */ +}; + +static CPUWriteMemoryFunc * const notdirty_mem_write[3] = { + notdirty_mem_writeb, + notdirty_mem_writew, + notdirty_mem_writel, +}; + +/* Generate a debug exception if a watchpoint has been hit. */ +static void check_watchpoint(int offset, int len_mask, int flags) +{ + CPUState *env = cpu_single_env; + target_ulong pc, cs_base; + TranslationBlock *tb; + target_ulong vaddr; + CPUWatchpoint *wp; + int cpu_flags; + + if (env->watchpoint_hit) { + /* We re-entered the check after replacing the TB. Now raise + * the debug interrupt so that is will trigger after the + * current instruction. */ + cpu_interrupt(env, CPU_INTERRUPT_DEBUG); + return; + } + vaddr = (env->mem_io_vaddr & TARGET_PAGE_MASK) + offset; + QTAILQ_FOREACH(wp, &env->watchpoints, entry) { + if ((vaddr == (wp->vaddr & len_mask) || + (vaddr & wp->len_mask) == wp->vaddr) && (wp->flags & flags)) { + wp->flags |= BP_WATCHPOINT_HIT; + if (!env->watchpoint_hit) { + env->watchpoint_hit = wp; + tb = tb_find_pc(env->mem_io_pc); + if (!tb) { + cpu_abort(env, "check_watchpoint: could not find TB for " + "pc=%p", (void *)env->mem_io_pc); + } + cpu_restore_state(tb, env, env->mem_io_pc, NULL); + tb_phys_invalidate(tb, -1); + if (wp->flags & BP_STOP_BEFORE_ACCESS) { + env->exception_index = EXCP_DEBUG; + } else { + cpu_get_tb_cpu_state(env, &pc, &cs_base, &cpu_flags); + tb_gen_code(env, pc, cs_base, cpu_flags, 1); + } + cpu_resume_from_signal(env, NULL); + } + } else { + wp->flags &= ~BP_WATCHPOINT_HIT; + } + } +} + +/* Watchpoint access routines. Watchpoints are inserted using TLB tricks, + so these check for a hit then pass through to the normal out-of-line + phys routines. */ +static uint32_t watch_mem_readb(void *opaque, target_phys_addr_t addr) +{ + check_watchpoint(addr & ~TARGET_PAGE_MASK, ~0x0, BP_MEM_READ); + return ldub_phys(addr); +} + +static uint32_t watch_mem_readw(void *opaque, target_phys_addr_t addr) +{ + check_watchpoint(addr & ~TARGET_PAGE_MASK, ~0x1, BP_MEM_READ); + return lduw_phys(addr); +} + +static uint32_t watch_mem_readl(void *opaque, target_phys_addr_t addr) +{ + check_watchpoint(addr & ~TARGET_PAGE_MASK, ~0x3, BP_MEM_READ); + return ldl_phys(addr); +} + +static void watch_mem_writeb(void *opaque, target_phys_addr_t addr, + uint32_t val) +{ + check_watchpoint(addr & ~TARGET_PAGE_MASK, ~0x0, BP_MEM_WRITE); + stb_phys(addr, val); +} + +static void watch_mem_writew(void *opaque, target_phys_addr_t addr, + uint32_t val) +{ + check_watchpoint(addr & ~TARGET_PAGE_MASK, ~0x1, BP_MEM_WRITE); + stw_phys(addr, val); +} + +static void watch_mem_writel(void *opaque, target_phys_addr_t addr, + uint32_t val) +{ + check_watchpoint(addr & ~TARGET_PAGE_MASK, ~0x3, BP_MEM_WRITE); + stl_phys(addr, val); +} + +static CPUReadMemoryFunc * const watch_mem_read[3] = { + watch_mem_readb, + watch_mem_readw, + watch_mem_readl, +}; + +static CPUWriteMemoryFunc * const watch_mem_write[3] = { + watch_mem_writeb, + watch_mem_writew, + watch_mem_writel, +}; + +static inline uint32_t subpage_readlen (subpage_t *mmio, + target_phys_addr_t addr, + unsigned int len) +{ + unsigned int idx = SUBPAGE_IDX(addr); +#if defined(DEBUG_SUBPAGE) + printf("%s: subpage %p len %d addr " TARGET_FMT_plx " idx %d\n", __func__, + mmio, len, addr, idx); +#endif + + addr += mmio->region_offset[idx]; + idx = mmio->sub_io_index[idx]; + return io_mem_read[idx][len](io_mem_opaque[idx], addr); +} + +static inline void subpage_writelen (subpage_t *mmio, target_phys_addr_t addr, + uint32_t value, unsigned int len) +{ + unsigned int idx = SUBPAGE_IDX(addr); +#if defined(DEBUG_SUBPAGE) + printf("%s: subpage %p len %d addr " TARGET_FMT_plx " idx %d value %08x\n", + __func__, mmio, len, addr, idx, value); +#endif + + addr += mmio->region_offset[idx]; + idx = mmio->sub_io_index[idx]; + io_mem_write[idx][len](io_mem_opaque[idx], addr, value); +} + +static uint32_t subpage_readb (void *opaque, target_phys_addr_t addr) +{ + return subpage_readlen(opaque, addr, 0); +} + +static void subpage_writeb (void *opaque, target_phys_addr_t addr, + uint32_t value) +{ + subpage_writelen(opaque, addr, value, 0); +} + +static uint32_t subpage_readw (void *opaque, target_phys_addr_t addr) +{ + return subpage_readlen(opaque, addr, 1); +} + +static void subpage_writew (void *opaque, target_phys_addr_t addr, + uint32_t value) +{ + subpage_writelen(opaque, addr, value, 1); +} + +static uint32_t subpage_readl (void *opaque, target_phys_addr_t addr) +{ + return subpage_readlen(opaque, addr, 2); +} + +static void subpage_writel (void *opaque, target_phys_addr_t addr, + uint32_t value) +{ + subpage_writelen(opaque, addr, value, 2); +} + +static CPUReadMemoryFunc * const subpage_read[] = { + &subpage_readb, + &subpage_readw, + &subpage_readl, +}; + +static CPUWriteMemoryFunc * const subpage_write[] = { + &subpage_writeb, + &subpage_writew, + &subpage_writel, +}; + +static int subpage_register (subpage_t *mmio, uint32_t start, uint32_t end, + ram_addr_t memory, ram_addr_t region_offset) +{ + int idx, eidx; + + if (start >= TARGET_PAGE_SIZE || end >= TARGET_PAGE_SIZE) + return -1; + idx = SUBPAGE_IDX(start); + eidx = SUBPAGE_IDX(end); +#if defined(DEBUG_SUBPAGE) + printf("%s: %p start %08x end %08x idx %08x eidx %08x mem %ld\n", __func__, + mmio, start, end, idx, eidx, memory); +#endif + if ((memory & ~TARGET_PAGE_MASK) == IO_MEM_RAM) + memory = IO_MEM_UNASSIGNED; + memory = (memory >> IO_MEM_SHIFT) & (IO_MEM_NB_ENTRIES - 1); + for (; idx <= eidx; idx++) { + mmio->sub_io_index[idx] = memory; + mmio->region_offset[idx] = region_offset; + } + + return 0; +} + +static subpage_t *subpage_init (target_phys_addr_t base, ram_addr_t *phys, + ram_addr_t orig_memory, + ram_addr_t region_offset) +{ + subpage_t *mmio; + int subpage_memory; + + mmio = qemu_mallocz(sizeof(subpage_t)); + + mmio->base = base; + subpage_memory = cpu_register_io_memory(subpage_read, subpage_write, mmio, + DEVICE_NATIVE_ENDIAN); +#if defined(DEBUG_SUBPAGE) + printf("%s: %p base " TARGET_FMT_plx " len %08x %d\n", __func__, + mmio, base, TARGET_PAGE_SIZE, subpage_memory); +#endif + *phys = subpage_memory | IO_MEM_SUBPAGE; + subpage_register(mmio, 0, TARGET_PAGE_SIZE-1, orig_memory, region_offset); + + return mmio; +} + +static int get_free_io_mem_idx(void) +{ + int i; + + for (i = 0; iread[0](c->opaque, addr); + return val; +} + +static uint32_t swapendian_mem_readw(void *opaque, target_phys_addr_t addr) +{ + uint32_t val; + SwapEndianContainer *c = opaque; + val = bswap16(c->read[1](c->opaque, addr)); + return val; +} + +static uint32_t swapendian_mem_readl(void *opaque, target_phys_addr_t addr) +{ + uint32_t val; + SwapEndianContainer *c = opaque; + val = bswap32(c->read[2](c->opaque, addr)); + return val; +} + +static CPUReadMemoryFunc * const swapendian_readfn[3]={ + swapendian_mem_readb, + swapendian_mem_readw, + swapendian_mem_readl +}; + +static void swapendian_mem_writeb(void *opaque, target_phys_addr_t addr, + uint32_t val) +{ + SwapEndianContainer *c = opaque; + c->write[0](c->opaque, addr, val); +} + +static void swapendian_mem_writew(void *opaque, target_phys_addr_t addr, + uint32_t val) +{ + SwapEndianContainer *c = opaque; + c->write[1](c->opaque, addr, bswap16(val)); +} + +static void swapendian_mem_writel(void *opaque, target_phys_addr_t addr, + uint32_t val) +{ + SwapEndianContainer *c = opaque; + c->write[2](c->opaque, addr, bswap32(val)); +} + +static CPUWriteMemoryFunc * const swapendian_writefn[3]={ + swapendian_mem_writeb, + swapendian_mem_writew, + swapendian_mem_writel +}; + +static void swapendian_init(int io_index) +{ + SwapEndianContainer *c = qemu_malloc(sizeof(SwapEndianContainer)); + int i; + + /* Swap mmio for big endian targets */ + c->opaque = io_mem_opaque[io_index]; + for (i = 0; i < 3; i++) { + c->read[i] = io_mem_read[io_index][i]; + c->write[i] = io_mem_write[io_index][i]; + + io_mem_read[io_index][i] = swapendian_readfn[i]; + io_mem_write[io_index][i] = swapendian_writefn[i]; + } + io_mem_opaque[io_index] = c; +} + +static void swapendian_del(int io_index) +{ + if (io_mem_read[io_index][0] == swapendian_readfn[0]) { + qemu_free(io_mem_opaque[io_index]); + } +} + +/* mem_read and mem_write are arrays of functions containing the + function to access byte (index 0), word (index 1) and dword (index + 2). Functions can be omitted with a NULL function pointer. + If io_index is non zero, the corresponding io zone is + modified. If it is zero, a new io zone is allocated. The return + value can be used with cpu_register_physical_memory(). (-1) is + returned if error. */ +static int cpu_register_io_memory_fixed(int io_index, + CPUReadMemoryFunc * const *mem_read, + CPUWriteMemoryFunc * const *mem_write, + void *opaque, enum device_endian endian) +{ + int i; + + if (io_index <= 0) { + io_index = get_free_io_mem_idx(); + if (io_index == -1) + return io_index; + } else { + io_index >>= IO_MEM_SHIFT; + if (io_index >= IO_MEM_NB_ENTRIES) + return -1; + } + + for (i = 0; i < 3; ++i) { + io_mem_read[io_index][i] + = (mem_read[i] ? mem_read[i] : unassigned_mem_read[i]); + } + for (i = 0; i < 3; ++i) { + io_mem_write[io_index][i] + = (mem_write[i] ? mem_write[i] : unassigned_mem_write[i]); + } + io_mem_opaque[io_index] = opaque; + + switch (endian) { + case DEVICE_BIG_ENDIAN: +#ifndef TARGET_WORDS_BIGENDIAN + swapendian_init(io_index); +#endif + break; + case DEVICE_LITTLE_ENDIAN: +#ifdef TARGET_WORDS_BIGENDIAN + swapendian_init(io_index); +#endif + break; + case DEVICE_NATIVE_ENDIAN: + default: + break; + } + + return (io_index << IO_MEM_SHIFT); +} + +int cpu_register_io_memory(CPUReadMemoryFunc * const *mem_read, + CPUWriteMemoryFunc * const *mem_write, + void *opaque, enum device_endian endian) +{ + return cpu_register_io_memory_fixed(0, mem_read, mem_write, opaque, endian); +} + +void cpu_unregister_io_memory(int io_table_address) +{ + int i; + int io_index = io_table_address >> IO_MEM_SHIFT; + + swapendian_del(io_index); + + for (i=0;i < 3; i++) { + io_mem_read[io_index][i] = unassigned_mem_read[i]; + io_mem_write[io_index][i] = unassigned_mem_write[i]; + } + io_mem_opaque[io_index] = NULL; + io_mem_used[io_index] = 0; +} + +static void io_mem_init(void) +{ + int i; + + cpu_register_io_memory_fixed(IO_MEM_ROM, error_mem_read, + unassigned_mem_write, NULL, + DEVICE_NATIVE_ENDIAN); + cpu_register_io_memory_fixed(IO_MEM_UNASSIGNED, unassigned_mem_read, + unassigned_mem_write, NULL, + DEVICE_NATIVE_ENDIAN); + cpu_register_io_memory_fixed(IO_MEM_NOTDIRTY, error_mem_read, + notdirty_mem_write, NULL, + DEVICE_NATIVE_ENDIAN); + for (i=0; i<5; i++) + io_mem_used[i] = 1; + + io_mem_watch = cpu_register_io_memory(watch_mem_read, + watch_mem_write, NULL, + DEVICE_NATIVE_ENDIAN); +} + +#endif /* !defined(CONFIG_USER_ONLY) */ + +/* physical memory access (slow version, mainly for debug) */ +#if defined(CONFIG_USER_ONLY) +int cpu_memory_rw_debug(CPUState *env, target_ulong addr, + uint8_t *buf, int len, int is_write) +{ + int l, flags; + target_ulong page; + void * p; + + while (len > 0) { + page = addr & TARGET_PAGE_MASK; + l = (page + TARGET_PAGE_SIZE) - addr; + if (l > len) + l = len; + flags = page_get_flags(page); + if (!(flags & PAGE_VALID)) + return -1; + if (is_write) { + if (!(flags & PAGE_WRITE)) + return -1; + /* XXX: this code should not depend on lock_user */ + if (!(p = lock_user(VERIFY_WRITE, addr, l, 0))) + return -1; + memcpy(p, buf, l); + unlock_user(p, addr, l); + } else { + if (!(flags & PAGE_READ)) + return -1; + /* XXX: this code should not depend on lock_user */ + if (!(p = lock_user(VERIFY_READ, addr, l, 1))) + return -1; + memcpy(buf, p, l); + unlock_user(p, addr, 0); + } + len -= l; + buf += l; + addr += l; + } + return 0; +} + +#else +void cpu_physical_memory_rw(target_phys_addr_t addr, uint8_t *buf, + int len, int is_write) +{ + int l, io_index; + uint8_t *ptr; + uint32_t val; + target_phys_addr_t page; + unsigned long pd; + PhysPageDesc *p; + + while (len > 0) { + page = addr & TARGET_PAGE_MASK; + l = (page + TARGET_PAGE_SIZE) - addr; + if (l > len) + l = len; + p = phys_page_find(page >> TARGET_PAGE_BITS); + if (!p) { + pd = IO_MEM_UNASSIGNED; + } else { + pd = p->phys_offset; + } + + if (is_write) { + if ((pd & ~TARGET_PAGE_MASK) != IO_MEM_RAM) { + target_phys_addr_t addr1 = addr; + io_index = (pd >> IO_MEM_SHIFT) & (IO_MEM_NB_ENTRIES - 1); + if (p) + addr1 = (addr & ~TARGET_PAGE_MASK) + p->region_offset; + /* XXX: could force cpu_single_env to NULL to avoid + potential bugs */ + if (l >= 4 && ((addr1 & 3) == 0)) { + /* 32 bit write access */ + val = ldl_p(buf); + io_mem_write[io_index][2](io_mem_opaque[io_index], addr1, val); + l = 4; + } else if (l >= 2 && ((addr1 & 1) == 0)) { + /* 16 bit write access */ + val = lduw_p(buf); + io_mem_write[io_index][1](io_mem_opaque[io_index], addr1, val); + l = 2; + } else { + /* 8 bit write access */ + val = ldub_p(buf); + io_mem_write[io_index][0](io_mem_opaque[io_index], addr1, val); + l = 1; + } + } else { + unsigned long addr1; + addr1 = (pd & TARGET_PAGE_MASK) + (addr & ~TARGET_PAGE_MASK); + /* RAM case */ + ptr = qemu_get_ram_ptr(addr1); + memcpy(ptr, buf, l); + if (!cpu_physical_memory_is_dirty(addr1)) { + /* invalidate code */ + tb_invalidate_phys_page_range(addr1, addr1 + l, 0); + /* set dirty bit */ + cpu_physical_memory_set_dirty_flags( + addr1, (0xff & ~CODE_DIRTY_FLAG)); + } + } + } else { + if ((pd & ~TARGET_PAGE_MASK) > IO_MEM_ROM && + !(pd & IO_MEM_ROMD)) { + target_phys_addr_t addr1 = addr; + /* I/O case */ + io_index = (pd >> IO_MEM_SHIFT) & (IO_MEM_NB_ENTRIES - 1); + if (p) + addr1 = (addr & ~TARGET_PAGE_MASK) + p->region_offset; + if (l >= 4 && ((addr1 & 3) == 0)) { + /* 32 bit read access */ + val = io_mem_read[io_index][2](io_mem_opaque[io_index], addr1); + stl_p(buf, val); + l = 4; + } else if (l >= 2 && ((addr1 & 1) == 0)) { + /* 16 bit read access */ + val = io_mem_read[io_index][1](io_mem_opaque[io_index], addr1); + stw_p(buf, val); + l = 2; + } else { + /* 8 bit read access */ + val = io_mem_read[io_index][0](io_mem_opaque[io_index], addr1); + stb_p(buf, val); + l = 1; + } + } else { + /* RAM case */ + ptr = qemu_get_ram_ptr(pd & TARGET_PAGE_MASK) + + (addr & ~TARGET_PAGE_MASK); + memcpy(buf, ptr, l); + } + } + len -= l; + buf += l; + addr += l; + } +} + +/* used for ROM loading : can write in RAM and ROM */ +void cpu_physical_memory_write_rom(target_phys_addr_t addr, + const uint8_t *buf, int len) +{ + int l; + uint8_t *ptr; + target_phys_addr_t page; + unsigned long pd; + PhysPageDesc *p; + + while (len > 0) { + page = addr & TARGET_PAGE_MASK; + l = (page + TARGET_PAGE_SIZE) - addr; + if (l > len) + l = len; + p = phys_page_find(page >> TARGET_PAGE_BITS); + if (!p) { + pd = IO_MEM_UNASSIGNED; + } else { + pd = p->phys_offset; + } + + if ((pd & ~TARGET_PAGE_MASK) != IO_MEM_RAM && + (pd & ~TARGET_PAGE_MASK) != IO_MEM_ROM && + !(pd & IO_MEM_ROMD)) { + /* do nothing */ + } else { + unsigned long addr1; + addr1 = (pd & TARGET_PAGE_MASK) + (addr & ~TARGET_PAGE_MASK); + /* ROM/RAM case */ + ptr = qemu_get_ram_ptr(addr1); + memcpy(ptr, buf, l); + } + len -= l; + buf += l; + addr += l; + } +} + +typedef struct { + void *buffer; + target_phys_addr_t addr; + target_phys_addr_t len; +} BounceBuffer; + +static BounceBuffer bounce; + +typedef struct MapClient { + void *opaque; + void (*callback)(void *opaque); + QLIST_ENTRY(MapClient) link; +} MapClient; + +static QLIST_HEAD(map_client_list, MapClient) map_client_list + = QLIST_HEAD_INITIALIZER(map_client_list); + +void *cpu_register_map_client(void *opaque, void (*callback)(void *opaque)) +{ + MapClient *client = qemu_malloc(sizeof(*client)); + + client->opaque = opaque; + client->callback = callback; + QLIST_INSERT_HEAD(&map_client_list, client, link); + return client; +} + +void cpu_unregister_map_client(void *_client) +{ + MapClient *client = (MapClient *)_client; + + QLIST_REMOVE(client, link); + qemu_free(client); +} + +static void cpu_notify_map_clients(void) +{ + MapClient *client; + + while (!QLIST_EMPTY(&map_client_list)) { + client = QLIST_FIRST(&map_client_list); + client->callback(client->opaque); + cpu_unregister_map_client(client); + } +} + +/* Map a physical memory region into a host virtual address. + * May map a subset of the requested range, given by and returned in *plen. + * May return NULL if resources needed to perform the mapping are exhausted. + * Use only for reads OR writes - not for read-modify-write operations. + * Use cpu_register_map_client() to know when retrying the map operation is + * likely to succeed. + */ +void *cpu_physical_memory_map(target_phys_addr_t addr, + target_phys_addr_t *plen, + int is_write) +{ + target_phys_addr_t len = *plen; + target_phys_addr_t done = 0; + int l; + uint8_t *ret = NULL; + uint8_t *ptr; + target_phys_addr_t page; + unsigned long pd; + PhysPageDesc *p; + unsigned long addr1; + + while (len > 0) { + page = addr & TARGET_PAGE_MASK; + l = (page + TARGET_PAGE_SIZE) - addr; + if (l > len) + l = len; + p = phys_page_find(page >> TARGET_PAGE_BITS); + if (!p) { + pd = IO_MEM_UNASSIGNED; + } else { + pd = p->phys_offset; + } + + if ((pd & ~TARGET_PAGE_MASK) != IO_MEM_RAM) { + if (done || bounce.buffer) { + break; + } + bounce.buffer = qemu_memalign(TARGET_PAGE_SIZE, TARGET_PAGE_SIZE); + bounce.addr = addr; + bounce.len = l; + if (!is_write) { + cpu_physical_memory_rw(addr, bounce.buffer, l, 0); + } + ptr = bounce.buffer; + } else { + addr1 = (pd & TARGET_PAGE_MASK) + (addr & ~TARGET_PAGE_MASK); + ptr = qemu_get_ram_ptr(addr1); + } + if (!done) { + ret = ptr; + } else if (ret + done != ptr) { + break; + } + + len -= l; + addr += l; + done += l; + } + *plen = done; + return ret; +} + +/* Unmaps a memory region previously mapped by cpu_physical_memory_map(). + * Will also mark the memory as dirty if is_write == 1. access_len gives + * the amount of memory that was actually read or written by the caller. + */ +void cpu_physical_memory_unmap(void *buffer, target_phys_addr_t len, + int is_write, target_phys_addr_t access_len) +{ + if (buffer != bounce.buffer) { + if (is_write) { + ram_addr_t addr1 = qemu_ram_addr_from_host_nofail(buffer); + while (access_len) { + unsigned l; + l = TARGET_PAGE_SIZE; + if (l > access_len) + l = access_len; + if (!cpu_physical_memory_is_dirty(addr1)) { + /* invalidate code */ + tb_invalidate_phys_page_range(addr1, addr1 + l, 0); + /* set dirty bit */ + cpu_physical_memory_set_dirty_flags( + addr1, (0xff & ~CODE_DIRTY_FLAG)); + } + addr1 += l; + access_len -= l; + } + } + return; + } + if (is_write) { + cpu_physical_memory_write(bounce.addr, bounce.buffer, access_len); + } + qemu_vfree(bounce.buffer); + bounce.buffer = NULL; + cpu_notify_map_clients(); +} + +/* warning: addr must be aligned */ +uint32_t ldl_phys(target_phys_addr_t addr) +{ + int io_index; + uint8_t *ptr; + uint32_t val; + unsigned long pd; + PhysPageDesc *p; + + p = phys_page_find(addr >> TARGET_PAGE_BITS); + if (!p) { + pd = IO_MEM_UNASSIGNED; + } else { + pd = p->phys_offset; + } + + if ((pd & ~TARGET_PAGE_MASK) > IO_MEM_ROM && + !(pd & IO_MEM_ROMD)) { + /* I/O case */ + io_index = (pd >> IO_MEM_SHIFT) & (IO_MEM_NB_ENTRIES - 1); + if (p) + addr = (addr & ~TARGET_PAGE_MASK) + p->region_offset; + val = io_mem_read[io_index][2](io_mem_opaque[io_index], addr); + } else { + /* RAM case */ + ptr = qemu_get_ram_ptr(pd & TARGET_PAGE_MASK) + + (addr & ~TARGET_PAGE_MASK); + val = ldl_p(ptr); + } + return val; +} + +/* warning: addr must be aligned */ +uint64_t ldq_phys(target_phys_addr_t addr) +{ + int io_index; + uint8_t *ptr; + uint64_t val; + unsigned long pd; + PhysPageDesc *p; + + p = phys_page_find(addr >> TARGET_PAGE_BITS); + if (!p) { + pd = IO_MEM_UNASSIGNED; + } else { + pd = p->phys_offset; + } + + if ((pd & ~TARGET_PAGE_MASK) > IO_MEM_ROM && + !(pd & IO_MEM_ROMD)) { + /* I/O case */ + io_index = (pd >> IO_MEM_SHIFT) & (IO_MEM_NB_ENTRIES - 1); + if (p) + addr = (addr & ~TARGET_PAGE_MASK) + p->region_offset; +#ifdef TARGET_WORDS_BIGENDIAN + val = (uint64_t)io_mem_read[io_index][2](io_mem_opaque[io_index], addr) << 32; + val |= io_mem_read[io_index][2](io_mem_opaque[io_index], addr + 4); +#else + val = io_mem_read[io_index][2](io_mem_opaque[io_index], addr); + val |= (uint64_t)io_mem_read[io_index][2](io_mem_opaque[io_index], addr + 4) << 32; +#endif + } else { + /* RAM case */ + ptr = qemu_get_ram_ptr(pd & TARGET_PAGE_MASK) + + (addr & ~TARGET_PAGE_MASK); + val = ldq_p(ptr); + } + return val; +} + +/* XXX: optimize */ +uint32_t ldub_phys(target_phys_addr_t addr) +{ + uint8_t val; + cpu_physical_memory_read(addr, &val, 1); + return val; +} + +/* warning: addr must be aligned */ +uint32_t lduw_phys(target_phys_addr_t addr) +{ + int io_index; + uint8_t *ptr; + uint64_t val; + unsigned long pd; + PhysPageDesc *p; + + p = phys_page_find(addr >> TARGET_PAGE_BITS); + if (!p) { + pd = IO_MEM_UNASSIGNED; + } else { + pd = p->phys_offset; + } + + if ((pd & ~TARGET_PAGE_MASK) > IO_MEM_ROM && + !(pd & IO_MEM_ROMD)) { + /* I/O case */ + io_index = (pd >> IO_MEM_SHIFT) & (IO_MEM_NB_ENTRIES - 1); + if (p) + addr = (addr & ~TARGET_PAGE_MASK) + p->region_offset; + val = io_mem_read[io_index][1](io_mem_opaque[io_index], addr); + } else { + /* RAM case */ + ptr = qemu_get_ram_ptr(pd & TARGET_PAGE_MASK) + + (addr & ~TARGET_PAGE_MASK); + val = lduw_p(ptr); + } + return val; +} + +/* warning: addr must be aligned. The ram page is not masked as dirty + and the code inside is not invalidated. It is useful if the dirty + bits are used to track modified PTEs */ +void stl_phys_notdirty(target_phys_addr_t addr, uint32_t val) +{ + int io_index; + uint8_t *ptr; + unsigned long pd; + PhysPageDesc *p; + + p = phys_page_find(addr >> TARGET_PAGE_BITS); + if (!p) { + pd = IO_MEM_UNASSIGNED; + } else { + pd = p->phys_offset; + } + + if ((pd & ~TARGET_PAGE_MASK) != IO_MEM_RAM) { + io_index = (pd >> IO_MEM_SHIFT) & (IO_MEM_NB_ENTRIES - 1); + if (p) + addr = (addr & ~TARGET_PAGE_MASK) + p->region_offset; + io_mem_write[io_index][2](io_mem_opaque[io_index], addr, val); + } else { + unsigned long addr1 = (pd & TARGET_PAGE_MASK) + (addr & ~TARGET_PAGE_MASK); + ptr = qemu_get_ram_ptr(addr1); + stl_p(ptr, val); + + if (unlikely(in_migration)) { + if (!cpu_physical_memory_is_dirty(addr1)) { + /* invalidate code */ + tb_invalidate_phys_page_range(addr1, addr1 + 4, 0); + /* set dirty bit */ + cpu_physical_memory_set_dirty_flags( + addr1, (0xff & ~CODE_DIRTY_FLAG)); + } + } + } +} + +void stq_phys_notdirty(target_phys_addr_t addr, uint64_t val) +{ + int io_index; + uint8_t *ptr; + unsigned long pd; + PhysPageDesc *p; + + p = phys_page_find(addr >> TARGET_PAGE_BITS); + if (!p) { + pd = IO_MEM_UNASSIGNED; + } else { + pd = p->phys_offset; + } + + if ((pd & ~TARGET_PAGE_MASK) != IO_MEM_RAM) { + io_index = (pd >> IO_MEM_SHIFT) & (IO_MEM_NB_ENTRIES - 1); + if (p) + addr = (addr & ~TARGET_PAGE_MASK) + p->region_offset; +#ifdef TARGET_WORDS_BIGENDIAN + io_mem_write[io_index][2](io_mem_opaque[io_index], addr, val >> 32); + io_mem_write[io_index][2](io_mem_opaque[io_index], addr + 4, val); +#else + io_mem_write[io_index][2](io_mem_opaque[io_index], addr, val); + io_mem_write[io_index][2](io_mem_opaque[io_index], addr + 4, val >> 32); +#endif + } else { + ptr = qemu_get_ram_ptr(pd & TARGET_PAGE_MASK) + + (addr & ~TARGET_PAGE_MASK); + stq_p(ptr, val); + } +} + +/* warning: addr must be aligned */ +void stl_phys(target_phys_addr_t addr, uint32_t val) +{ + int io_index; + uint8_t *ptr; + unsigned long pd; + PhysPageDesc *p; + + p = phys_page_find(addr >> TARGET_PAGE_BITS); + if (!p) { + pd = IO_MEM_UNASSIGNED; + } else { + pd = p->phys_offset; + } + + if ((pd & ~TARGET_PAGE_MASK) != IO_MEM_RAM) { + io_index = (pd >> IO_MEM_SHIFT) & (IO_MEM_NB_ENTRIES - 1); + if (p) + addr = (addr & ~TARGET_PAGE_MASK) + p->region_offset; + io_mem_write[io_index][2](io_mem_opaque[io_index], addr, val); + } else { + unsigned long addr1; + addr1 = (pd & TARGET_PAGE_MASK) + (addr & ~TARGET_PAGE_MASK); + /* RAM case */ + ptr = qemu_get_ram_ptr(addr1); + stl_p(ptr, val); + if (!cpu_physical_memory_is_dirty(addr1)) { + /* invalidate code */ + tb_invalidate_phys_page_range(addr1, addr1 + 4, 0); + /* set dirty bit */ + cpu_physical_memory_set_dirty_flags(addr1, + (0xff & ~CODE_DIRTY_FLAG)); + } + } +} + +/* XXX: optimize */ +void stb_phys(target_phys_addr_t addr, uint32_t val) +{ + uint8_t v = val; + cpu_physical_memory_write(addr, &v, 1); +} + +/* warning: addr must be aligned */ +void stw_phys(target_phys_addr_t addr, uint32_t val) +{ + int io_index; + uint8_t *ptr; + unsigned long pd; + PhysPageDesc *p; + + p = phys_page_find(addr >> TARGET_PAGE_BITS); + if (!p) { + pd = IO_MEM_UNASSIGNED; + } else { + pd = p->phys_offset; + } + + if ((pd & ~TARGET_PAGE_MASK) != IO_MEM_RAM) { + io_index = (pd >> IO_MEM_SHIFT) & (IO_MEM_NB_ENTRIES - 1); + if (p) + addr = (addr & ~TARGET_PAGE_MASK) + p->region_offset; + io_mem_write[io_index][1](io_mem_opaque[io_index], addr, val); + } else { + unsigned long addr1; + addr1 = (pd & TARGET_PAGE_MASK) + (addr & ~TARGET_PAGE_MASK); + /* RAM case */ + ptr = qemu_get_ram_ptr(addr1); + stw_p(ptr, val); + if (!cpu_physical_memory_is_dirty(addr1)) { + /* invalidate code */ + tb_invalidate_phys_page_range(addr1, addr1 + 2, 0); + /* set dirty bit */ + cpu_physical_memory_set_dirty_flags(addr1, + (0xff & ~CODE_DIRTY_FLAG)); + } + } +} + +/* XXX: optimize */ +void stq_phys(target_phys_addr_t addr, uint64_t val) +{ + val = tswap64(val); + cpu_physical_memory_write(addr, (const uint8_t *)&val, 8); +} + +/* virtual memory access for debug (includes writing to ROM) */ +int cpu_memory_rw_debug(CPUState *env, target_ulong addr, + uint8_t *buf, int len, int is_write) +{ + int l; + target_phys_addr_t phys_addr; + target_ulong page; + + while (len > 0) { + page = addr & TARGET_PAGE_MASK; + phys_addr = cpu_get_phys_page_debug(env, page); + /* if no physical page mapped, return an error */ + if (phys_addr == -1) + return -1; + l = (page + TARGET_PAGE_SIZE) - addr; + if (l > len) + l = len; + phys_addr += (addr & ~TARGET_PAGE_MASK); + if (is_write) + cpu_physical_memory_write_rom(phys_addr, buf, l); + else + cpu_physical_memory_rw(phys_addr, buf, l, is_write); + len -= l; + buf += l; + addr += l; + } + return 0; +} +#endif + +/* in deterministic execution mode, instructions doing device I/Os + must be at the end of the TB */ +void cpu_io_recompile(CPUState *env, void *retaddr) +{ + TranslationBlock *tb; + uint32_t n, cflags; + target_ulong pc, cs_base; + uint64_t flags; + + tb = tb_find_pc((unsigned long)retaddr); + if (!tb) { + cpu_abort(env, "cpu_io_recompile: could not find TB for pc=%p", + retaddr); + } + n = env->icount_decr.u16.low + tb->icount; + cpu_restore_state(tb, env, (unsigned long)retaddr, NULL); + /* Calculate how many instructions had been executed before the fault + occurred. */ + n = n - env->icount_decr.u16.low; + /* Generate a new TB ending on the I/O insn. */ + n++; + /* On MIPS and SH, delay slot instructions can only be restarted if + they were already the first instruction in the TB. If this is not + the first instruction in a TB then re-execute the preceding + branch. */ +#if defined(TARGET_MIPS) + if ((env->hflags & MIPS_HFLAG_BMASK) != 0 && n > 1) { + env->active_tc.PC -= 4; + env->icount_decr.u16.low++; + env->hflags &= ~MIPS_HFLAG_BMASK; + } +#elif defined(TARGET_SH4) + if ((env->flags & ((DELAY_SLOT | DELAY_SLOT_CONDITIONAL))) != 0 + && n > 1) { + env->pc -= 2; + env->icount_decr.u16.low++; + env->flags &= ~(DELAY_SLOT | DELAY_SLOT_CONDITIONAL); + } +#endif + /* This should never happen. */ + if (n > CF_COUNT_MASK) + cpu_abort(env, "TB too big during recompile"); + + cflags = n | CF_LAST_IO; + pc = tb->pc; + cs_base = tb->cs_base; + flags = tb->flags; + tb_phys_invalidate(tb, -1); + /* FIXME: In theory this could raise an exception. In practice + we have already translated the block once so it's probably ok. */ + tb_gen_code(env, pc, cs_base, flags, cflags); + /* TODO: If env->pc != tb->pc (i.e. the faulting instruction was not + the first in the TB) then we end up generating a whole new TB and + repeating the fault, which is horribly inefficient. + Better would be to execute just this insn uncached, or generate a + second new TB. */ + cpu_resume_from_signal(env, NULL); +} + +#if !defined(CONFIG_USER_ONLY) + +void dump_exec_info(FILE *f, fprintf_function cpu_fprintf) +{ + int i, target_code_size, max_target_code_size; + int direct_jmp_count, direct_jmp2_count, cross_page; + TranslationBlock *tb; + + target_code_size = 0; + max_target_code_size = 0; + cross_page = 0; + direct_jmp_count = 0; + direct_jmp2_count = 0; + for(i = 0; i < nb_tbs; i++) { + tb = &tbs[i]; + target_code_size += tb->size; + if (tb->size > max_target_code_size) + max_target_code_size = tb->size; + if (tb->page_addr[1] != -1) + cross_page++; + if (tb->tb_next_offset[0] != 0xffff) { + direct_jmp_count++; + if (tb->tb_next_offset[1] != 0xffff) { + direct_jmp2_count++; + } + } + } + /* XXX: avoid using doubles ? */ + cpu_fprintf(f, "Translation buffer state:\n"); + cpu_fprintf(f, "gen code size %td/%ld\n", + code_gen_ptr - code_gen_buffer, code_gen_buffer_max_size); + cpu_fprintf(f, "TB count %d/%d\n", + nb_tbs, code_gen_max_blocks); + cpu_fprintf(f, "TB avg target size %d max=%d bytes\n", + nb_tbs ? target_code_size / nb_tbs : 0, + max_target_code_size); + cpu_fprintf(f, "TB avg host size %td bytes (expansion ratio: %0.1f)\n", + nb_tbs ? (code_gen_ptr - code_gen_buffer) / nb_tbs : 0, + target_code_size ? (double) (code_gen_ptr - code_gen_buffer) / target_code_size : 0); + cpu_fprintf(f, "cross page TB count %d (%d%%)\n", + cross_page, + nb_tbs ? (cross_page * 100) / nb_tbs : 0); + cpu_fprintf(f, "direct jump count %d (%d%%) (2 jumps=%d %d%%)\n", + direct_jmp_count, + nb_tbs ? (direct_jmp_count * 100) / nb_tbs : 0, + direct_jmp2_count, + nb_tbs ? (direct_jmp2_count * 100) / nb_tbs : 0); + cpu_fprintf(f, "\nStatistics:\n"); + cpu_fprintf(f, "TB flush count %d\n", tb_flush_count); + cpu_fprintf(f, "TB invalidate count %d\n", tb_phys_invalidate_count); + cpu_fprintf(f, "TLB flush count %d\n", tlb_flush_count); + tcg_dump_info(f, cpu_fprintf); +} + +#define MMUSUFFIX _cmmu +#define GETPC() NULL +#define env cpu_single_env +#define SOFTMMU_CODE_ACCESS + +#define SHIFT 0 +#include "softmmu_template.h" + +#define SHIFT 1 +#include "softmmu_template.h" + +#define SHIFT 2 +#include "softmmu_template.h" + +#define SHIFT 3 +#include "softmmu_template.h" + +#undef env + +#endif diff --git a/fpu/softfloat-macros.h b/fpu/softfloat-macros.h new file mode 100644 index 0000000..7838228 --- /dev/null +++ b/fpu/softfloat-macros.h @@ -0,0 +1,719 @@ + +/*============================================================================ + +This C source fragment is part of the SoftFloat IEC/IEEE Floating-point +Arithmetic Package, Release 2b. + +Written by John R. Hauser. This work was made possible in part by the +International Computer Science Institute, located at Suite 600, 1947 Center +Street, Berkeley, California 94704. Funding was partially provided by the +National Science Foundation under grant MIP-9311980. The original version +of this code was written as part of a project to build a fixed-point vector +processor in collaboration with the University of California at Berkeley, +overseen by Profs. Nelson Morgan and John Wawrzynek. More information +is available through the Web page `http://www.cs.berkeley.edu/~jhauser/ +arithmetic/SoftFloat.html'. + +THIS SOFTWARE IS DISTRIBUTED AS IS, FOR FREE. Although reasonable effort has +been made to avoid it, THIS SOFTWARE MAY CONTAIN FAULTS THAT WILL AT TIMES +RESULT IN INCORRECT BEHAVIOR. USE OF THIS SOFTWARE IS RESTRICTED TO PERSONS +AND ORGANIZATIONS WHO CAN AND WILL TAKE FULL RESPONSIBILITY FOR ALL LOSSES, +COSTS, OR OTHER PROBLEMS THEY INCUR DUE TO THE SOFTWARE, AND WHO FURTHERMORE +EFFECTIVELY INDEMNIFY JOHN HAUSER AND THE INTERNATIONAL COMPUTER SCIENCE +INSTITUTE (possibly via similar legal notice) AGAINST ALL LOSSES, COSTS, OR +OTHER PROBLEMS INCURRED BY THEIR CUSTOMERS AND CLIENTS DUE TO THE SOFTWARE. + +Derivative works are acceptable, even for commercial purposes, so long as +(1) the source code for the derivative work includes prominent notice that +the work is derivative, and (2) the source code includes prominent notice with +these four paragraphs for those parts of this code that are retained. + +=============================================================================*/ + +/*---------------------------------------------------------------------------- +| Shifts `a' right by the number of bits given in `count'. If any nonzero +| bits are shifted off, they are ``jammed'' into the least significant bit of +| the result by setting the least significant bit to 1. The value of `count' +| can be arbitrarily large; in particular, if `count' is greater than 32, the +| result will be either 0 or 1, depending on whether `a' is zero or nonzero. +| The result is stored in the location pointed to by `zPtr'. +*----------------------------------------------------------------------------*/ + +INLINE void shift32RightJamming( bits32 a, int16 count, bits32 *zPtr ) +{ + bits32 z; + + if ( count == 0 ) { + z = a; + } + else if ( count < 32 ) { + z = ( a>>count ) | ( ( a<<( ( - count ) & 31 ) ) != 0 ); + } + else { + z = ( a != 0 ); + } + *zPtr = z; + +} + +/*---------------------------------------------------------------------------- +| Shifts `a' right by the number of bits given in `count'. If any nonzero +| bits are shifted off, they are ``jammed'' into the least significant bit of +| the result by setting the least significant bit to 1. The value of `count' +| can be arbitrarily large; in particular, if `count' is greater than 64, the +| result will be either 0 or 1, depending on whether `a' is zero or nonzero. +| The result is stored in the location pointed to by `zPtr'. +*----------------------------------------------------------------------------*/ + +INLINE void shift64RightJamming( bits64 a, int16 count, bits64 *zPtr ) +{ + bits64 z; + + if ( count == 0 ) { + z = a; + } + else if ( count < 64 ) { + z = ( a>>count ) | ( ( a<<( ( - count ) & 63 ) ) != 0 ); + } + else { + z = ( a != 0 ); + } + *zPtr = z; + +} + +/*---------------------------------------------------------------------------- +| Shifts the 128-bit value formed by concatenating `a0' and `a1' right by 64 +| _plus_ the number of bits given in `count'. The shifted result is at most +| 64 nonzero bits; this is stored at the location pointed to by `z0Ptr'. The +| bits shifted off form a second 64-bit result as follows: The _last_ bit +| shifted off is the most-significant bit of the extra result, and the other +| 63 bits of the extra result are all zero if and only if _all_but_the_last_ +| bits shifted off were all zero. This extra result is stored in the location +| pointed to by `z1Ptr'. The value of `count' can be arbitrarily large. +| (This routine makes more sense if `a0' and `a1' are considered to form +| a fixed-point value with binary point between `a0' and `a1'. This fixed- +| point value is shifted right by the number of bits given in `count', and +| the integer part of the result is returned at the location pointed to by +| `z0Ptr'. The fractional part of the result may be slightly corrupted as +| described above, and is returned at the location pointed to by `z1Ptr'.) +*----------------------------------------------------------------------------*/ + +INLINE void + shift64ExtraRightJamming( + bits64 a0, bits64 a1, int16 count, bits64 *z0Ptr, bits64 *z1Ptr ) +{ + bits64 z0, z1; + int8 negCount = ( - count ) & 63; + + if ( count == 0 ) { + z1 = a1; + z0 = a0; + } + else if ( count < 64 ) { + z1 = ( a0<>count; + } + else { + if ( count == 64 ) { + z1 = a0 | ( a1 != 0 ); + } + else { + z1 = ( ( a0 | a1 ) != 0 ); + } + z0 = 0; + } + *z1Ptr = z1; + *z0Ptr = z0; + +} + +/*---------------------------------------------------------------------------- +| Shifts the 128-bit value formed by concatenating `a0' and `a1' right by the +| number of bits given in `count'. Any bits shifted off are lost. The value +| of `count' can be arbitrarily large; in particular, if `count' is greater +| than 128, the result will be 0. The result is broken into two 64-bit pieces +| which are stored at the locations pointed to by `z0Ptr' and `z1Ptr'. +*----------------------------------------------------------------------------*/ + +INLINE void + shift128Right( + bits64 a0, bits64 a1, int16 count, bits64 *z0Ptr, bits64 *z1Ptr ) +{ + bits64 z0, z1; + int8 negCount = ( - count ) & 63; + + if ( count == 0 ) { + z1 = a1; + z0 = a0; + } + else if ( count < 64 ) { + z1 = ( a0<>count ); + z0 = a0>>count; + } + else { + z1 = ( count < 64 ) ? ( a0>>( count & 63 ) ) : 0; + z0 = 0; + } + *z1Ptr = z1; + *z0Ptr = z0; + +} + +/*---------------------------------------------------------------------------- +| Shifts the 128-bit value formed by concatenating `a0' and `a1' right by the +| number of bits given in `count'. If any nonzero bits are shifted off, they +| are ``jammed'' into the least significant bit of the result by setting the +| least significant bit to 1. The value of `count' can be arbitrarily large; +| in particular, if `count' is greater than 128, the result will be either +| 0 or 1, depending on whether the concatenation of `a0' and `a1' is zero or +| nonzero. The result is broken into two 64-bit pieces which are stored at +| the locations pointed to by `z0Ptr' and `z1Ptr'. +*----------------------------------------------------------------------------*/ + +INLINE void + shift128RightJamming( + bits64 a0, bits64 a1, int16 count, bits64 *z0Ptr, bits64 *z1Ptr ) +{ + bits64 z0, z1; + int8 negCount = ( - count ) & 63; + + if ( count == 0 ) { + z1 = a1; + z0 = a0; + } + else if ( count < 64 ) { + z1 = ( a0<>count ) | ( ( a1<>count; + } + else { + if ( count == 64 ) { + z1 = a0 | ( a1 != 0 ); + } + else if ( count < 128 ) { + z1 = ( a0>>( count & 63 ) ) | ( ( ( a0<>count ); + z0 = a0>>count; + } + else { + if ( count == 64 ) { + z2 = a1; + z1 = a0; + } + else { + a2 |= a1; + if ( count < 128 ) { + z2 = a0<>( count & 63 ); + } + else { + z2 = ( count == 128 ) ? a0 : ( a0 != 0 ); + z1 = 0; + } + } + z0 = 0; + } + z2 |= ( a2 != 0 ); + } + *z2Ptr = z2; + *z1Ptr = z1; + *z0Ptr = z0; + +} + +/*---------------------------------------------------------------------------- +| Shifts the 128-bit value formed by concatenating `a0' and `a1' left by the +| number of bits given in `count'. Any bits shifted off are lost. The value +| of `count' must be less than 64. The result is broken into two 64-bit +| pieces which are stored at the locations pointed to by `z0Ptr' and `z1Ptr'. +*----------------------------------------------------------------------------*/ + +INLINE void + shortShift128Left( + bits64 a0, bits64 a1, int16 count, bits64 *z0Ptr, bits64 *z1Ptr ) +{ + + *z1Ptr = a1<>( ( - count ) & 63 ) ); + +} + +/*---------------------------------------------------------------------------- +| Shifts the 192-bit value formed by concatenating `a0', `a1', and `a2' left +| by the number of bits given in `count'. Any bits shifted off are lost. +| The value of `count' must be less than 64. The result is broken into three +| 64-bit pieces which are stored at the locations pointed to by `z0Ptr', +| `z1Ptr', and `z2Ptr'. +*----------------------------------------------------------------------------*/ + +INLINE void + shortShift192Left( + bits64 a0, + bits64 a1, + bits64 a2, + int16 count, + bits64 *z0Ptr, + bits64 *z1Ptr, + bits64 *z2Ptr + ) +{ + bits64 z0, z1, z2; + int8 negCount; + + z2 = a2<>negCount; + z0 |= a1>>negCount; + } + *z2Ptr = z2; + *z1Ptr = z1; + *z0Ptr = z0; + +} + +/*---------------------------------------------------------------------------- +| Adds the 128-bit value formed by concatenating `a0' and `a1' to the 128-bit +| value formed by concatenating `b0' and `b1'. Addition is modulo 2^128, so +| any carry out is lost. The result is broken into two 64-bit pieces which +| are stored at the locations pointed to by `z0Ptr' and `z1Ptr'. +*----------------------------------------------------------------------------*/ + +INLINE void + add128( + bits64 a0, bits64 a1, bits64 b0, bits64 b1, bits64 *z0Ptr, bits64 *z1Ptr ) +{ + bits64 z1; + + z1 = a1 + b1; + *z1Ptr = z1; + *z0Ptr = a0 + b0 + ( z1 < a1 ); + +} + +/*---------------------------------------------------------------------------- +| Adds the 192-bit value formed by concatenating `a0', `a1', and `a2' to the +| 192-bit value formed by concatenating `b0', `b1', and `b2'. Addition is +| modulo 2^192, so any carry out is lost. The result is broken into three +| 64-bit pieces which are stored at the locations pointed to by `z0Ptr', +| `z1Ptr', and `z2Ptr'. +*----------------------------------------------------------------------------*/ + +INLINE void + add192( + bits64 a0, + bits64 a1, + bits64 a2, + bits64 b0, + bits64 b1, + bits64 b2, + bits64 *z0Ptr, + bits64 *z1Ptr, + bits64 *z2Ptr + ) +{ + bits64 z0, z1, z2; + int8 carry0, carry1; + + z2 = a2 + b2; + carry1 = ( z2 < a2 ); + z1 = a1 + b1; + carry0 = ( z1 < a1 ); + z0 = a0 + b0; + z1 += carry1; + z0 += ( z1 < carry1 ); + z0 += carry0; + *z2Ptr = z2; + *z1Ptr = z1; + *z0Ptr = z0; + +} + +/*---------------------------------------------------------------------------- +| Subtracts the 128-bit value formed by concatenating `b0' and `b1' from the +| 128-bit value formed by concatenating `a0' and `a1'. Subtraction is modulo +| 2^128, so any borrow out (carry out) is lost. The result is broken into two +| 64-bit pieces which are stored at the locations pointed to by `z0Ptr' and +| `z1Ptr'. +*----------------------------------------------------------------------------*/ + +INLINE void + sub128( + bits64 a0, bits64 a1, bits64 b0, bits64 b1, bits64 *z0Ptr, bits64 *z1Ptr ) +{ + + *z1Ptr = a1 - b1; + *z0Ptr = a0 - b0 - ( a1 < b1 ); + +} + +/*---------------------------------------------------------------------------- +| Subtracts the 192-bit value formed by concatenating `b0', `b1', and `b2' +| from the 192-bit value formed by concatenating `a0', `a1', and `a2'. +| Subtraction is modulo 2^192, so any borrow out (carry out) is lost. The +| result is broken into three 64-bit pieces which are stored at the locations +| pointed to by `z0Ptr', `z1Ptr', and `z2Ptr'. +*----------------------------------------------------------------------------*/ + +INLINE void + sub192( + bits64 a0, + bits64 a1, + bits64 a2, + bits64 b0, + bits64 b1, + bits64 b2, + bits64 *z0Ptr, + bits64 *z1Ptr, + bits64 *z2Ptr + ) +{ + bits64 z0, z1, z2; + int8 borrow0, borrow1; + + z2 = a2 - b2; + borrow1 = ( a2 < b2 ); + z1 = a1 - b1; + borrow0 = ( a1 < b1 ); + z0 = a0 - b0; + z0 -= ( z1 < borrow1 ); + z1 -= borrow1; + z0 -= borrow0; + *z2Ptr = z2; + *z1Ptr = z1; + *z0Ptr = z0; + +} + +/*---------------------------------------------------------------------------- +| Multiplies `a' by `b' to obtain a 128-bit product. The product is broken +| into two 64-bit pieces which are stored at the locations pointed to by +| `z0Ptr' and `z1Ptr'. +*----------------------------------------------------------------------------*/ + +INLINE void mul64To128( bits64 a, bits64 b, bits64 *z0Ptr, bits64 *z1Ptr ) +{ + bits32 aHigh, aLow, bHigh, bLow; + bits64 z0, zMiddleA, zMiddleB, z1; + + aLow = a; + aHigh = a>>32; + bLow = b; + bHigh = b>>32; + z1 = ( (bits64) aLow ) * bLow; + zMiddleA = ( (bits64) aLow ) * bHigh; + zMiddleB = ( (bits64) aHigh ) * bLow; + z0 = ( (bits64) aHigh ) * bHigh; + zMiddleA += zMiddleB; + z0 += ( ( (bits64) ( zMiddleA < zMiddleB ) )<<32 ) + ( zMiddleA>>32 ); + zMiddleA <<= 32; + z1 += zMiddleA; + z0 += ( z1 < zMiddleA ); + *z1Ptr = z1; + *z0Ptr = z0; + +} + +/*---------------------------------------------------------------------------- +| Multiplies the 128-bit value formed by concatenating `a0' and `a1' by +| `b' to obtain a 192-bit product. The product is broken into three 64-bit +| pieces which are stored at the locations pointed to by `z0Ptr', `z1Ptr', and +| `z2Ptr'. +*----------------------------------------------------------------------------*/ + +INLINE void + mul128By64To192( + bits64 a0, + bits64 a1, + bits64 b, + bits64 *z0Ptr, + bits64 *z1Ptr, + bits64 *z2Ptr + ) +{ + bits64 z0, z1, z2, more1; + + mul64To128( a1, b, &z1, &z2 ); + mul64To128( a0, b, &z0, &more1 ); + add128( z0, more1, 0, z1, &z0, &z1 ); + *z2Ptr = z2; + *z1Ptr = z1; + *z0Ptr = z0; + +} + +/*---------------------------------------------------------------------------- +| Multiplies the 128-bit value formed by concatenating `a0' and `a1' to the +| 128-bit value formed by concatenating `b0' and `b1' to obtain a 256-bit +| product. The product is broken into four 64-bit pieces which are stored at +| the locations pointed to by `z0Ptr', `z1Ptr', `z2Ptr', and `z3Ptr'. +*----------------------------------------------------------------------------*/ + +INLINE void + mul128To256( + bits64 a0, + bits64 a1, + bits64 b0, + bits64 b1, + bits64 *z0Ptr, + bits64 *z1Ptr, + bits64 *z2Ptr, + bits64 *z3Ptr + ) +{ + bits64 z0, z1, z2, z3; + bits64 more1, more2; + + mul64To128( a1, b1, &z2, &z3 ); + mul64To128( a1, b0, &z1, &more2 ); + add128( z1, more2, 0, z2, &z1, &z2 ); + mul64To128( a0, b0, &z0, &more1 ); + add128( z0, more1, 0, z1, &z0, &z1 ); + mul64To128( a0, b1, &more1, &more2 ); + add128( more1, more2, 0, z2, &more1, &z2 ); + add128( z0, z1, 0, more1, &z0, &z1 ); + *z3Ptr = z3; + *z2Ptr = z2; + *z1Ptr = z1; + *z0Ptr = z0; + +} + +/*---------------------------------------------------------------------------- +| Returns an approximation to the 64-bit integer quotient obtained by dividing +| `b' into the 128-bit value formed by concatenating `a0' and `a1'. The +| divisor `b' must be at least 2^63. If q is the exact quotient truncated +| toward zero, the approximation returned lies between q and q + 2 inclusive. +| If the exact quotient q is larger than 64 bits, the maximum positive 64-bit +| unsigned integer is returned. +*----------------------------------------------------------------------------*/ + +static bits64 estimateDiv128To64( bits64 a0, bits64 a1, bits64 b ) +{ + bits64 b0, b1; + bits64 rem0, rem1, term0, term1; + bits64 z; + + if ( b <= a0 ) return LIT64( 0xFFFFFFFFFFFFFFFF ); + b0 = b>>32; + z = ( b0<<32 <= a0 ) ? LIT64( 0xFFFFFFFF00000000 ) : ( a0 / b0 )<<32; + mul64To128( b, z, &term0, &term1 ); + sub128( a0, a1, term0, term1, &rem0, &rem1 ); + while ( ( (sbits64) rem0 ) < 0 ) { + z -= LIT64( 0x100000000 ); + b1 = b<<32; + add128( rem0, rem1, b0, b1, &rem0, &rem1 ); + } + rem0 = ( rem0<<32 ) | ( rem1>>32 ); + z |= ( b0<<32 <= rem0 ) ? 0xFFFFFFFF : rem0 / b0; + return z; + +} + +/*---------------------------------------------------------------------------- +| Returns an approximation to the square root of the 32-bit significand given +| by `a'. Considered as an integer, `a' must be at least 2^31. If bit 0 of +| `aExp' (the least significant bit) is 1, the integer returned approximates +| 2^31*sqrt(`a'/2^31), where `a' is considered an integer. If bit 0 of `aExp' +| is 0, the integer returned approximates 2^31*sqrt(`a'/2^30). In either +| case, the approximation returned lies strictly within +/-2 of the exact +| value. +*----------------------------------------------------------------------------*/ + +static bits32 estimateSqrt32( int16 aExp, bits32 a ) +{ + static const bits16 sqrtOddAdjustments[] = { + 0x0004, 0x0022, 0x005D, 0x00B1, 0x011D, 0x019F, 0x0236, 0x02E0, + 0x039C, 0x0468, 0x0545, 0x0631, 0x072B, 0x0832, 0x0946, 0x0A67 + }; + static const bits16 sqrtEvenAdjustments[] = { + 0x0A2D, 0x08AF, 0x075A, 0x0629, 0x051A, 0x0429, 0x0356, 0x029E, + 0x0200, 0x0179, 0x0109, 0x00AF, 0x0068, 0x0034, 0x0012, 0x0002 + }; + int8 index; + bits32 z; + + index = ( a>>27 ) & 15; + if ( aExp & 1 ) { + z = 0x4000 + ( a>>17 ) - sqrtOddAdjustments[ (int)index ]; + z = ( ( a / z )<<14 ) + ( z<<15 ); + a >>= 1; + } + else { + z = 0x8000 + ( a>>17 ) - sqrtEvenAdjustments[ (int)index ]; + z = a / z + z; + z = ( 0x20000 <= z ) ? 0xFFFF8000 : ( z<<15 ); + if ( z <= a ) return (bits32) ( ( (sbits32) a )>>1 ); + } + return ( (bits32) ( ( ( (bits64) a )<<31 ) / z ) ) + ( z>>1 ); + +} + +/*---------------------------------------------------------------------------- +| Returns the number of leading 0 bits before the most-significant 1 bit of +| `a'. If `a' is zero, 32 is returned. +*----------------------------------------------------------------------------*/ + +static int8 countLeadingZeros32( bits32 a ) +{ + static const int8 countLeadingZerosHigh[] = { + 8, 7, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 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, 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, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + int8 shiftCount; + + shiftCount = 0; + if ( a < 0x10000 ) { + shiftCount += 16; + a <<= 16; + } + if ( a < 0x1000000 ) { + shiftCount += 8; + a <<= 8; + } + shiftCount += countLeadingZerosHigh[ a>>24 ]; + return shiftCount; + +} + +/*---------------------------------------------------------------------------- +| Returns the number of leading 0 bits before the most-significant 1 bit of +| `a'. If `a' is zero, 64 is returned. +*----------------------------------------------------------------------------*/ + +static int8 countLeadingZeros64( bits64 a ) +{ + int8 shiftCount; + + shiftCount = 0; + if ( a < ( (bits64) 1 )<<32 ) { + shiftCount += 32; + } + else { + a >>= 32; + } + shiftCount += countLeadingZeros32( a ); + return shiftCount; + +} + +/*---------------------------------------------------------------------------- +| Returns 1 if the 128-bit value formed by concatenating `a0' and `a1' +| is equal to the 128-bit value formed by concatenating `b0' and `b1'. +| Otherwise, returns 0. +*----------------------------------------------------------------------------*/ + +INLINE flag eq128( bits64 a0, bits64 a1, bits64 b0, bits64 b1 ) +{ + + return ( a0 == b0 ) && ( a1 == b1 ); + +} + +/*---------------------------------------------------------------------------- +| Returns 1 if the 128-bit value formed by concatenating `a0' and `a1' is less +| than or equal to the 128-bit value formed by concatenating `b0' and `b1'. +| Otherwise, returns 0. +*----------------------------------------------------------------------------*/ + +INLINE flag le128( bits64 a0, bits64 a1, bits64 b0, bits64 b1 ) +{ + + return ( a0 < b0 ) || ( ( a0 == b0 ) && ( a1 <= b1 ) ); + +} + +/*---------------------------------------------------------------------------- +| Returns 1 if the 128-bit value formed by concatenating `a0' and `a1' is less +| than the 128-bit value formed by concatenating `b0' and `b1'. Otherwise, +| returns 0. +*----------------------------------------------------------------------------*/ + +INLINE flag lt128( bits64 a0, bits64 a1, bits64 b0, bits64 b1 ) +{ + + return ( a0 < b0 ) || ( ( a0 == b0 ) && ( a1 < b1 ) ); + +} + +/*---------------------------------------------------------------------------- +| Returns 1 if the 128-bit value formed by concatenating `a0' and `a1' is +| not equal to the 128-bit value formed by concatenating `b0' and `b1'. +| Otherwise, returns 0. +*----------------------------------------------------------------------------*/ + +INLINE flag ne128( bits64 a0, bits64 a1, bits64 b0, bits64 b1 ) +{ + + return ( a0 != b0 ) || ( a1 != b1 ); + +} diff --git a/fpu/softfloat-native.c b/fpu/softfloat-native.c new file mode 100644 index 0000000..008bb53 --- /dev/null +++ b/fpu/softfloat-native.c @@ -0,0 +1,514 @@ +/* Native implementation of soft float functions. Only a single status + context is supported */ +#include "softfloat.h" +#include +#if defined(CONFIG_SOLARIS) +#include +#endif + +void set_float_rounding_mode(int val STATUS_PARAM) +{ + STATUS(float_rounding_mode) = val; +#if (defined(CONFIG_BSD) && !defined(__APPLE__) && !defined(__GLIBC__)) || \ + (defined(CONFIG_SOLARIS) && CONFIG_SOLARIS_VERSION < 10) + fpsetround(val); +#else + fesetround(val); +#endif +} + +#ifdef FLOATX80 +void set_floatx80_rounding_precision(int val STATUS_PARAM) +{ + STATUS(floatx80_rounding_precision) = val; +} +#endif + +#if defined(CONFIG_BSD) || \ + (defined(CONFIG_SOLARIS) && CONFIG_SOLARIS_VERSION < 10) +#define lrint(d) ((int32_t)rint(d)) +#define llrint(d) ((int64_t)rint(d)) +#define lrintf(f) ((int32_t)rint(f)) +#define llrintf(f) ((int64_t)rint(f)) +#define sqrtf(f) ((float)sqrt(f)) +#define remainderf(fa, fb) ((float)remainder(fa, fb)) +#define rintf(f) ((float)rint(f)) +#if !defined(__sparc__) && \ + (defined(CONFIG_SOLARIS) && CONFIG_SOLARIS_VERSION < 10) +extern long double rintl(long double); +extern long double scalbnl(long double, int); + +long long +llrintl(long double x) { + return ((long long) rintl(x)); +} + +long +lrintl(long double x) { + return ((long) rintl(x)); +} + +long double +ldexpl(long double x, int n) { + return (scalbnl(x, n)); +} +#endif +#endif + +#if defined(_ARCH_PPC) + +/* correct (but slow) PowerPC rint() (glibc version is incorrect) */ +static double qemu_rint(double x) +{ + double y = 4503599627370496.0; + if (fabs(x) >= y) + return x; + if (x < 0) + y = -y; + y = (x + y) - y; + if (y == 0.0) + y = copysign(y, x); + return y; +} + +#define rint qemu_rint +#endif + +/*---------------------------------------------------------------------------- +| Software IEC/IEEE integer-to-floating-point conversion routines. +*----------------------------------------------------------------------------*/ +float32 int32_to_float32(int v STATUS_PARAM) +{ + return (float32)v; +} + +float32 uint32_to_float32(unsigned int v STATUS_PARAM) +{ + return (float32)v; +} + +float64 int32_to_float64(int v STATUS_PARAM) +{ + return (float64)v; +} + +float64 uint32_to_float64(unsigned int v STATUS_PARAM) +{ + return (float64)v; +} + +#ifdef FLOATX80 +floatx80 int32_to_floatx80(int v STATUS_PARAM) +{ + return (floatx80)v; +} +#endif +float32 int64_to_float32( int64_t v STATUS_PARAM) +{ + return (float32)v; +} +float32 uint64_to_float32( uint64_t v STATUS_PARAM) +{ + return (float32)v; +} +float64 int64_to_float64( int64_t v STATUS_PARAM) +{ + return (float64)v; +} +float64 uint64_to_float64( uint64_t v STATUS_PARAM) +{ + return (float64)v; +} +#ifdef FLOATX80 +floatx80 int64_to_floatx80( int64_t v STATUS_PARAM) +{ + return (floatx80)v; +} +#endif + +/* XXX: this code implements the x86 behaviour, not the IEEE one. */ +#if HOST_LONG_BITS == 32 +static inline int long_to_int32(long a) +{ + return a; +} +#else +static inline int long_to_int32(long a) +{ + if (a != (int32_t)a) + a = 0x80000000; + return a; +} +#endif + +/*---------------------------------------------------------------------------- +| Software IEC/IEEE single-precision conversion routines. +*----------------------------------------------------------------------------*/ +int float32_to_int32( float32 a STATUS_PARAM) +{ + return long_to_int32(lrintf(a)); +} +int float32_to_int32_round_to_zero( float32 a STATUS_PARAM) +{ + return (int)a; +} +int64_t float32_to_int64( float32 a STATUS_PARAM) +{ + return llrintf(a); +} + +int64_t float32_to_int64_round_to_zero( float32 a STATUS_PARAM) +{ + return (int64_t)a; +} + +float64 float32_to_float64( float32 a STATUS_PARAM) +{ + return a; +} +#ifdef FLOATX80 +floatx80 float32_to_floatx80( float32 a STATUS_PARAM) +{ + return a; +} +#endif + +unsigned int float32_to_uint32( float32 a STATUS_PARAM) +{ + int64_t v; + unsigned int res; + + v = llrintf(a); + if (v < 0) { + res = 0; + } else if (v > 0xffffffff) { + res = 0xffffffff; + } else { + res = v; + } + return res; +} +unsigned int float32_to_uint32_round_to_zero( float32 a STATUS_PARAM) +{ + int64_t v; + unsigned int res; + + v = (int64_t)a; + if (v < 0) { + res = 0; + } else if (v > 0xffffffff) { + res = 0xffffffff; + } else { + res = v; + } + return res; +} + +/*---------------------------------------------------------------------------- +| Software IEC/IEEE single-precision operations. +*----------------------------------------------------------------------------*/ +float32 float32_round_to_int( float32 a STATUS_PARAM) +{ + return rintf(a); +} + +float32 float32_rem( float32 a, float32 b STATUS_PARAM) +{ + return remainderf(a, b); +} + +float32 float32_sqrt( float32 a STATUS_PARAM) +{ + return sqrtf(a); +} +int float32_compare( float32 a, float32 b STATUS_PARAM ) +{ + if (a < b) { + return float_relation_less; + } else if (a == b) { + return float_relation_equal; + } else if (a > b) { + return float_relation_greater; + } else { + return float_relation_unordered; + } +} +int float32_compare_quiet( float32 a, float32 b STATUS_PARAM ) +{ + if (isless(a, b)) { + return float_relation_less; + } else if (a == b) { + return float_relation_equal; + } else if (isgreater(a, b)) { + return float_relation_greater; + } else { + return float_relation_unordered; + } +} +int float32_is_signaling_nan( float32 a1) +{ + float32u u; + uint32_t a; + u.f = a1; + a = u.i; + return ( ( ( a>>22 ) & 0x1FF ) == 0x1FE ) && ( a & 0x003FFFFF ); +} + +int float32_is_quiet_nan( float32 a1 ) +{ + float32u u; + uint64_t a; + u.f = a1; + a = u.i; + return ( 0xFF800000 < ( a<<1 ) ); +} + +/*---------------------------------------------------------------------------- +| Software IEC/IEEE double-precision conversion routines. +*----------------------------------------------------------------------------*/ +int float64_to_int32( float64 a STATUS_PARAM) +{ + return long_to_int32(lrint(a)); +} +int float64_to_int32_round_to_zero( float64 a STATUS_PARAM) +{ + return (int)a; +} +int64_t float64_to_int64( float64 a STATUS_PARAM) +{ + return llrint(a); +} +int64_t float64_to_int64_round_to_zero( float64 a STATUS_PARAM) +{ + return (int64_t)a; +} +float32 float64_to_float32( float64 a STATUS_PARAM) +{ + return a; +} +#ifdef FLOATX80 +floatx80 float64_to_floatx80( float64 a STATUS_PARAM) +{ + return a; +} +#endif +#ifdef FLOAT128 +float128 float64_to_float128( float64 a STATUS_PARAM) +{ + return a; +} +#endif + +unsigned int float64_to_uint32( float64 a STATUS_PARAM) +{ + int64_t v; + unsigned int res; + + v = llrint(a); + if (v < 0) { + res = 0; + } else if (v > 0xffffffff) { + res = 0xffffffff; + } else { + res = v; + } + return res; +} +unsigned int float64_to_uint32_round_to_zero( float64 a STATUS_PARAM) +{ + int64_t v; + unsigned int res; + + v = (int64_t)a; + if (v < 0) { + res = 0; + } else if (v > 0xffffffff) { + res = 0xffffffff; + } else { + res = v; + } + return res; +} +uint64_t float64_to_uint64 (float64 a STATUS_PARAM) +{ + int64_t v; + + v = llrint(a + (float64)INT64_MIN); + + return v - INT64_MIN; +} +uint64_t float64_to_uint64_round_to_zero (float64 a STATUS_PARAM) +{ + int64_t v; + + v = (int64_t)(a + (float64)INT64_MIN); + + return v - INT64_MIN; +} + +/*---------------------------------------------------------------------------- +| Software IEC/IEEE double-precision operations. +*----------------------------------------------------------------------------*/ +#if defined(__sun__) && \ + (defined(CONFIG_SOLARIS) && CONFIG_SOLARIS_VERSION < 10) +static inline float64 trunc(float64 x) +{ + return x < 0 ? -floor(-x) : floor(x); +} +#endif +float64 float64_trunc_to_int( float64 a STATUS_PARAM ) +{ + return trunc(a); +} + +float64 float64_round_to_int( float64 a STATUS_PARAM ) +{ + return rint(a); +} + +float64 float64_rem( float64 a, float64 b STATUS_PARAM) +{ + return remainder(a, b); +} + +float64 float64_sqrt( float64 a STATUS_PARAM) +{ + return sqrt(a); +} +int float64_compare( float64 a, float64 b STATUS_PARAM ) +{ + if (a < b) { + return float_relation_less; + } else if (a == b) { + return float_relation_equal; + } else if (a > b) { + return float_relation_greater; + } else { + return float_relation_unordered; + } +} +int float64_compare_quiet( float64 a, float64 b STATUS_PARAM ) +{ + if (isless(a, b)) { + return float_relation_less; + } else if (a == b) { + return float_relation_equal; + } else if (isgreater(a, b)) { + return float_relation_greater; + } else { + return float_relation_unordered; + } +} +int float64_is_signaling_nan( float64 a1) +{ + float64u u; + uint64_t a; + u.f = a1; + a = u.i; + return + ( ( ( a>>51 ) & 0xFFF ) == 0xFFE ) + && ( a & LIT64( 0x0007FFFFFFFFFFFF ) ); + +} + +int float64_is_quiet_nan( float64 a1 ) +{ + float64u u; + uint64_t a; + u.f = a1; + a = u.i; + + return ( LIT64( 0xFFF0000000000000 ) < (bits64) ( a<<1 ) ); + +} + +#ifdef FLOATX80 + +/*---------------------------------------------------------------------------- +| Software IEC/IEEE extended double-precision conversion routines. +*----------------------------------------------------------------------------*/ +int floatx80_to_int32( floatx80 a STATUS_PARAM) +{ + return long_to_int32(lrintl(a)); +} +int floatx80_to_int32_round_to_zero( floatx80 a STATUS_PARAM) +{ + return (int)a; +} +int64_t floatx80_to_int64( floatx80 a STATUS_PARAM) +{ + return llrintl(a); +} +int64_t floatx80_to_int64_round_to_zero( floatx80 a STATUS_PARAM) +{ + return (int64_t)a; +} +float32 floatx80_to_float32( floatx80 a STATUS_PARAM) +{ + return a; +} +float64 floatx80_to_float64( floatx80 a STATUS_PARAM) +{ + return a; +} + +/*---------------------------------------------------------------------------- +| Software IEC/IEEE extended double-precision operations. +*----------------------------------------------------------------------------*/ +floatx80 floatx80_round_to_int( floatx80 a STATUS_PARAM) +{ + return rintl(a); +} +floatx80 floatx80_rem( floatx80 a, floatx80 b STATUS_PARAM) +{ + return remainderl(a, b); +} +floatx80 floatx80_sqrt( floatx80 a STATUS_PARAM) +{ + return sqrtl(a); +} +int floatx80_compare( floatx80 a, floatx80 b STATUS_PARAM ) +{ + if (a < b) { + return float_relation_less; + } else if (a == b) { + return float_relation_equal; + } else if (a > b) { + return float_relation_greater; + } else { + return float_relation_unordered; + } +} +int floatx80_compare_quiet( floatx80 a, floatx80 b STATUS_PARAM ) +{ + if (isless(a, b)) { + return float_relation_less; + } else if (a == b) { + return float_relation_equal; + } else if (isgreater(a, b)) { + return float_relation_greater; + } else { + return float_relation_unordered; + } +} +int floatx80_is_signaling_nan( floatx80 a1) +{ + floatx80u u; + uint64_t aLow; + u.f = a1; + + aLow = u.i.low & ~ LIT64( 0x4000000000000000 ); + return + ( ( u.i.high & 0x7FFF ) == 0x7FFF ) + && (bits64) ( aLow<<1 ) + && ( u.i.low == aLow ); +} + +int floatx80_is_quiet_nan( floatx80 a1 ) +{ + floatx80u u; + u.f = a1; + return ( ( u.i.high & 0x7FFF ) == 0x7FFF ) && (bits64) ( u.i.low<<1 ); +} + +#endif diff --git a/fpu/softfloat-native.h b/fpu/softfloat-native.h new file mode 100644 index 0000000..80b5f28 --- /dev/null +++ b/fpu/softfloat-native.h @@ -0,0 +1,492 @@ +/* Native implementation of soft float functions */ +#include + +#if (defined(CONFIG_BSD) && !defined(__APPLE__) && !defined(__GLIBC__)) \ + || defined(CONFIG_SOLARIS) +#include +#define fabsf(f) ((float)fabs(f)) +#else +#include +#endif + +#if defined(__OpenBSD__) || defined(__NetBSD__) +#include +#endif + +/* + * Define some C99-7.12.3 classification macros and + * some C99-.12.4 for Solaris systems OS less than 10, + * or Solaris 10 systems running GCC 3.x or less. + * Solaris 10 with GCC4 does not need these macros as they + * are defined in with a compiler directive + */ +#if defined(CONFIG_SOLARIS) && \ + ((CONFIG_SOLARIS_VERSION <= 9 ) || \ + ((CONFIG_SOLARIS_VERSION == 10) && (__GNUC__ < 4))) \ + || (defined(__OpenBSD__) && (OpenBSD < 200811)) +/* + * C99 7.12.3 classification macros + * and + * C99 7.12.14 comparison macros + * + * ... do not work on Solaris 10 using GNU CC 3.4.x. + * Try to workaround the missing / broken C99 math macros. + */ +#if defined(__OpenBSD__) +#define unordered(x, y) (isnan(x) || isnan(y)) +#endif + +#ifdef __NetBSD__ +#ifndef isgreater +#define isgreater(x, y) __builtin_isgreater(x, y) +#endif +#ifndef isgreaterequal +#define isgreaterequal(x, y) __builtin_isgreaterequal(x, y) +#endif +#ifndef isless +#define isless(x, y) __builtin_isless(x, y) +#endif +#ifndef islessequal +#define islessequal(x, y) __builtin_islessequal(x, y) +#endif +#ifndef isunordered +#define isunordered(x, y) __builtin_isunordered(x, y) +#endif +#endif + + +#define isnormal(x) (fpclass(x) >= FP_NZERO) +#define isgreater(x, y) ((!unordered(x, y)) && ((x) > (y))) +#define isgreaterequal(x, y) ((!unordered(x, y)) && ((x) >= (y))) +#define isless(x, y) ((!unordered(x, y)) && ((x) < (y))) +#define islessequal(x, y) ((!unordered(x, y)) && ((x) <= (y))) +#define isunordered(x,y) unordered(x, y) +#endif + +#if defined(__sun__) && !defined(CONFIG_NEEDS_LIBSUNMATH) + +#ifndef isnan +# define isnan(x) \ + (sizeof (x) == sizeof (long double) ? isnan_ld (x) \ + : sizeof (x) == sizeof (double) ? isnan_d (x) \ + : isnan_f (x)) +static inline int isnan_f (float x) { return x != x; } +static inline int isnan_d (double x) { return x != x; } +static inline int isnan_ld (long double x) { return x != x; } +#endif + +#ifndef isinf +# define isinf(x) \ + (sizeof (x) == sizeof (long double) ? isinf_ld (x) \ + : sizeof (x) == sizeof (double) ? isinf_d (x) \ + : isinf_f (x)) +static inline int isinf_f (float x) { return isnan (x - x); } +static inline int isinf_d (double x) { return isnan (x - x); } +static inline int isinf_ld (long double x) { return isnan (x - x); } +#endif +#endif + +typedef float float32; +typedef double float64; +#ifdef FLOATX80 +typedef long double floatx80; +#endif + +typedef union { + float32 f; + uint32_t i; +} float32u; +typedef union { + float64 f; + uint64_t i; +} float64u; +#ifdef FLOATX80 +typedef union { + floatx80 f; + struct { + uint64_t low; + uint16_t high; + } i; +} floatx80u; +#endif + +/*---------------------------------------------------------------------------- +| Software IEC/IEEE floating-point rounding mode. +*----------------------------------------------------------------------------*/ +#if (defined(CONFIG_BSD) && !defined(__APPLE__) && !defined(__GLIBC__)) \ + || defined(CONFIG_SOLARIS) +#if defined(__OpenBSD__) +#define FE_RM FP_RM +#define FE_RP FP_RP +#define FE_RZ FP_RZ +#endif +enum { + float_round_nearest_even = FP_RN, + float_round_down = FP_RM, + float_round_up = FP_RP, + float_round_to_zero = FP_RZ +}; +#else +enum { + float_round_nearest_even = FE_TONEAREST, + float_round_down = FE_DOWNWARD, + float_round_up = FE_UPWARD, + float_round_to_zero = FE_TOWARDZERO +}; +#endif + +typedef struct float_status { + int float_rounding_mode; +#ifdef FLOATX80 + int floatx80_rounding_precision; +#endif +} float_status; + +void set_float_rounding_mode(int val STATUS_PARAM); +#ifdef FLOATX80 +void set_floatx80_rounding_precision(int val STATUS_PARAM); +#endif + +/*---------------------------------------------------------------------------- +| Software IEC/IEEE integer-to-floating-point conversion routines. +*----------------------------------------------------------------------------*/ +float32 int32_to_float32( int STATUS_PARAM); +float32 uint32_to_float32( unsigned int STATUS_PARAM); +float64 int32_to_float64( int STATUS_PARAM); +float64 uint32_to_float64( unsigned int STATUS_PARAM); +#ifdef FLOATX80 +floatx80 int32_to_floatx80( int STATUS_PARAM); +#endif +#ifdef FLOAT128 +float128 int32_to_float128( int STATUS_PARAM); +#endif +float32 int64_to_float32( int64_t STATUS_PARAM); +float32 uint64_to_float32( uint64_t STATUS_PARAM); +float64 int64_to_float64( int64_t STATUS_PARAM); +float64 uint64_to_float64( uint64_t v STATUS_PARAM); +#ifdef FLOATX80 +floatx80 int64_to_floatx80( int64_t STATUS_PARAM); +#endif +#ifdef FLOAT128 +float128 int64_to_float128( int64_t STATUS_PARAM); +#endif + +/*---------------------------------------------------------------------------- +| Software IEC/IEEE single-precision conversion routines. +*----------------------------------------------------------------------------*/ +int float32_to_int32( float32 STATUS_PARAM); +int float32_to_int32_round_to_zero( float32 STATUS_PARAM); +unsigned int float32_to_uint32( float32 a STATUS_PARAM); +unsigned int float32_to_uint32_round_to_zero( float32 a STATUS_PARAM); +int64_t float32_to_int64( float32 STATUS_PARAM); +int64_t float32_to_int64_round_to_zero( float32 STATUS_PARAM); +float64 float32_to_float64( float32 STATUS_PARAM); +#ifdef FLOATX80 +floatx80 float32_to_floatx80( float32 STATUS_PARAM); +#endif +#ifdef FLOAT128 +float128 float32_to_float128( float32 STATUS_PARAM); +#endif + +/*---------------------------------------------------------------------------- +| Software IEC/IEEE single-precision operations. +*----------------------------------------------------------------------------*/ +float32 float32_round_to_int( float32 STATUS_PARAM); +INLINE float32 float32_add( float32 a, float32 b STATUS_PARAM) +{ + return a + b; +} +INLINE float32 float32_sub( float32 a, float32 b STATUS_PARAM) +{ + return a - b; +} +INLINE float32 float32_mul( float32 a, float32 b STATUS_PARAM) +{ + return a * b; +} +INLINE float32 float32_div( float32 a, float32 b STATUS_PARAM) +{ + return a / b; +} +float32 float32_rem( float32, float32 STATUS_PARAM); +float32 float32_sqrt( float32 STATUS_PARAM); +INLINE int float32_eq( float32 a, float32 b STATUS_PARAM) +{ + return a == b; +} +INLINE int float32_le( float32 a, float32 b STATUS_PARAM) +{ + return a <= b; +} +INLINE int float32_lt( float32 a, float32 b STATUS_PARAM) +{ + return a < b; +} +INLINE int float32_eq_signaling( float32 a, float32 b STATUS_PARAM) +{ + return a <= b && a >= b; +} +INLINE int float32_le_quiet( float32 a, float32 b STATUS_PARAM) +{ + return islessequal(a, b); +} +INLINE int float32_lt_quiet( float32 a, float32 b STATUS_PARAM) +{ + return isless(a, b); +} +INLINE int float32_unordered( float32 a, float32 b STATUS_PARAM) +{ + return isunordered(a, b); + +} +int float32_compare( float32, float32 STATUS_PARAM ); +int float32_compare_quiet( float32, float32 STATUS_PARAM ); +int float32_is_signaling_nan( float32 ); +int float32_is_quiet_nan( float32 ); + +INLINE float32 float32_abs(float32 a) +{ + return fabsf(a); +} + +INLINE float32 float32_chs(float32 a) +{ + return -a; +} + +INLINE float32 float32_is_infinity(float32 a) +{ + return fpclassify(a) == FP_INFINITE; +} + +INLINE float32 float32_is_neg(float32 a) +{ + float32u u; + u.f = a; + return u.i >> 31; +} + +INLINE float32 float32_is_zero(float32 a) +{ + return fpclassify(a) == FP_ZERO; +} + +INLINE float32 float32_scalbn(float32 a, int n) +{ + return scalbnf(a, n); +} + +/*---------------------------------------------------------------------------- +| Software IEC/IEEE double-precision conversion routines. +*----------------------------------------------------------------------------*/ +int float64_to_int32( float64 STATUS_PARAM ); +int float64_to_int32_round_to_zero( float64 STATUS_PARAM ); +unsigned int float64_to_uint32( float64 STATUS_PARAM ); +unsigned int float64_to_uint32_round_to_zero( float64 STATUS_PARAM ); +int64_t float64_to_int64( float64 STATUS_PARAM ); +int64_t float64_to_int64_round_to_zero( float64 STATUS_PARAM ); +uint64_t float64_to_uint64( float64 STATUS_PARAM ); +uint64_t float64_to_uint64_round_to_zero( float64 STATUS_PARAM ); +float32 float64_to_float32( float64 STATUS_PARAM ); +#ifdef FLOATX80 +floatx80 float64_to_floatx80( float64 STATUS_PARAM ); +#endif +#ifdef FLOAT128 +float128 float64_to_float128( float64 STATUS_PARAM ); +#endif + +/*---------------------------------------------------------------------------- +| Software IEC/IEEE double-precision operations. +*----------------------------------------------------------------------------*/ +float64 float64_round_to_int( float64 STATUS_PARAM ); +float64 float64_trunc_to_int( float64 STATUS_PARAM ); +INLINE float64 float64_add( float64 a, float64 b STATUS_PARAM) +{ + return a + b; +} +INLINE float64 float64_sub( float64 a, float64 b STATUS_PARAM) +{ + return a - b; +} +INLINE float64 float64_mul( float64 a, float64 b STATUS_PARAM) +{ + return a * b; +} +INLINE float64 float64_div( float64 a, float64 b STATUS_PARAM) +{ + return a / b; +} +float64 float64_rem( float64, float64 STATUS_PARAM ); +float64 float64_sqrt( float64 STATUS_PARAM ); +INLINE int float64_eq( float64 a, float64 b STATUS_PARAM) +{ + return a == b; +} +INLINE int float64_le( float64 a, float64 b STATUS_PARAM) +{ + return a <= b; +} +INLINE int float64_lt( float64 a, float64 b STATUS_PARAM) +{ + return a < b; +} +INLINE int float64_eq_signaling( float64 a, float64 b STATUS_PARAM) +{ + return a <= b && a >= b; +} +INLINE int float64_le_quiet( float64 a, float64 b STATUS_PARAM) +{ + return islessequal(a, b); +} +INLINE int float64_lt_quiet( float64 a, float64 b STATUS_PARAM) +{ + return isless(a, b); + +} +INLINE int float64_unordered( float64 a, float64 b STATUS_PARAM) +{ + return isunordered(a, b); + +} +int float64_compare( float64, float64 STATUS_PARAM ); +int float64_compare_quiet( float64, float64 STATUS_PARAM ); +int float64_is_signaling_nan( float64 ); +int float64_is_quiet_nan( float64 ); + +INLINE float64 float64_abs(float64 a) +{ + return fabs(a); +} + +INLINE float64 float64_chs(float64 a) +{ + return -a; +} + +INLINE float64 float64_is_infinity(float64 a) +{ + return fpclassify(a) == FP_INFINITE; +} + +INLINE float64 float64_is_neg(float64 a) +{ + float64u u; + u.f = a; + return u.i >> 63; +} + +INLINE float64 float64_is_zero(float64 a) +{ + return fpclassify(a) == FP_ZERO; +} + +INLINE float64 float64_scalbn(float64 a, int n) +{ + return scalbn(a, n); +} + +#ifdef FLOATX80 + +/*---------------------------------------------------------------------------- +| Software IEC/IEEE extended double-precision conversion routines. +*----------------------------------------------------------------------------*/ +int floatx80_to_int32( floatx80 STATUS_PARAM ); +int floatx80_to_int32_round_to_zero( floatx80 STATUS_PARAM ); +int64_t floatx80_to_int64( floatx80 STATUS_PARAM); +int64_t floatx80_to_int64_round_to_zero( floatx80 STATUS_PARAM); +float32 floatx80_to_float32( floatx80 STATUS_PARAM ); +float64 floatx80_to_float64( floatx80 STATUS_PARAM ); +#ifdef FLOAT128 +float128 floatx80_to_float128( floatx80 STATUS_PARAM ); +#endif + +/*---------------------------------------------------------------------------- +| Software IEC/IEEE extended double-precision operations. +*----------------------------------------------------------------------------*/ +floatx80 floatx80_round_to_int( floatx80 STATUS_PARAM ); +INLINE floatx80 floatx80_add( floatx80 a, floatx80 b STATUS_PARAM) +{ + return a + b; +} +INLINE floatx80 floatx80_sub( floatx80 a, floatx80 b STATUS_PARAM) +{ + return a - b; +} +INLINE floatx80 floatx80_mul( floatx80 a, floatx80 b STATUS_PARAM) +{ + return a * b; +} +INLINE floatx80 floatx80_div( floatx80 a, floatx80 b STATUS_PARAM) +{ + return a / b; +} +floatx80 floatx80_rem( floatx80, floatx80 STATUS_PARAM ); +floatx80 floatx80_sqrt( floatx80 STATUS_PARAM ); +INLINE int floatx80_eq( floatx80 a, floatx80 b STATUS_PARAM) +{ + return a == b; +} +INLINE int floatx80_le( floatx80 a, floatx80 b STATUS_PARAM) +{ + return a <= b; +} +INLINE int floatx80_lt( floatx80 a, floatx80 b STATUS_PARAM) +{ + return a < b; +} +INLINE int floatx80_eq_signaling( floatx80 a, floatx80 b STATUS_PARAM) +{ + return a <= b && a >= b; +} +INLINE int floatx80_le_quiet( floatx80 a, floatx80 b STATUS_PARAM) +{ + return islessequal(a, b); +} +INLINE int floatx80_lt_quiet( floatx80 a, floatx80 b STATUS_PARAM) +{ + return isless(a, b); + +} +INLINE int floatx80_unordered( floatx80 a, floatx80 b STATUS_PARAM) +{ + return isunordered(a, b); + +} +int floatx80_compare( floatx80, floatx80 STATUS_PARAM ); +int floatx80_compare_quiet( floatx80, floatx80 STATUS_PARAM ); +int floatx80_is_signaling_nan( floatx80 ); +int floatx80_is_quiet_nan( floatx80 ); + +INLINE floatx80 floatx80_abs(floatx80 a) +{ + return fabsl(a); +} + +INLINE floatx80 floatx80_chs(floatx80 a) +{ + return -a; +} + +INLINE floatx80 floatx80_is_infinity(floatx80 a) +{ + return fpclassify(a) == FP_INFINITE; +} + +INLINE floatx80 floatx80_is_neg(floatx80 a) +{ + floatx80u u; + u.f = a; + return u.i.high >> 15; +} + +INLINE floatx80 floatx80_is_zero(floatx80 a) +{ + return fpclassify(a) == FP_ZERO; +} + +INLINE floatx80 floatx80_scalbn(floatx80 a, int n) +{ + return scalbnl(a, n); +} + +#endif diff --git a/fpu/softfloat-specialize.h b/fpu/softfloat-specialize.h new file mode 100644 index 0000000..eb644b2 --- /dev/null +++ b/fpu/softfloat-specialize.h @@ -0,0 +1,755 @@ + +/*============================================================================ + +This C source fragment is part of the SoftFloat IEC/IEEE Floating-point +Arithmetic Package, Release 2b. + +Written by John R. Hauser. This work was made possible in part by the +International Computer Science Institute, located at Suite 600, 1947 Center +Street, Berkeley, California 94704. Funding was partially provided by the +National Science Foundation under grant MIP-9311980. The original version +of this code was written as part of a project to build a fixed-point vector +processor in collaboration with the University of California at Berkeley, +overseen by Profs. Nelson Morgan and John Wawrzynek. More information +is available through the Web page `http://www.cs.berkeley.edu/~jhauser/ +arithmetic/SoftFloat.html'. + +THIS SOFTWARE IS DISTRIBUTED AS IS, FOR FREE. Although reasonable effort has +been made to avoid it, THIS SOFTWARE MAY CONTAIN FAULTS THAT WILL AT TIMES +RESULT IN INCORRECT BEHAVIOR. USE OF THIS SOFTWARE IS RESTRICTED TO PERSONS +AND ORGANIZATIONS WHO CAN AND WILL TAKE FULL RESPONSIBILITY FOR ALL LOSSES, +COSTS, OR OTHER PROBLEMS THEY INCUR DUE TO THE SOFTWARE, AND WHO FURTHERMORE +EFFECTIVELY INDEMNIFY JOHN HAUSER AND THE INTERNATIONAL COMPUTER SCIENCE +INSTITUTE (possibly via similar legal warning) AGAINST ALL LOSSES, COSTS, OR +OTHER PROBLEMS INCURRED BY THEIR CUSTOMERS AND CLIENTS DUE TO THE SOFTWARE. + +Derivative works are acceptable, even for commercial purposes, so long as +(1) the source code for the derivative work includes prominent notice that +the work is derivative, and (2) the source code includes prominent notice with +these four paragraphs for those parts of this code that are retained. + +=============================================================================*/ + +#if defined(TARGET_MIPS) || defined(TARGET_SH4) +#define SNAN_BIT_IS_ONE 1 +#else +#define SNAN_BIT_IS_ONE 0 +#endif + +/*---------------------------------------------------------------------------- +| Raises the exceptions specified by `flags'. Floating-point traps can be +| defined here if desired. It is currently not possible for such a trap +| to substitute a result value. If traps are not implemented, this routine +| should be simply `float_exception_flags |= flags;'. +*----------------------------------------------------------------------------*/ + +void float_raise( int8 flags STATUS_PARAM ) +{ + STATUS(float_exception_flags) |= flags; +} + +/*---------------------------------------------------------------------------- +| Internal canonical NaN format. +*----------------------------------------------------------------------------*/ +typedef struct { + flag sign; + bits64 high, low; +} commonNaNT; + +/*---------------------------------------------------------------------------- +| The pattern for a default generated single-precision NaN. +*----------------------------------------------------------------------------*/ +#if defined(TARGET_SPARC) +#define float32_default_nan make_float32(0x7FFFFFFF) +#elif defined(TARGET_PPC) || defined(TARGET_ARM) || defined(TARGET_ALPHA) +#define float32_default_nan make_float32(0x7FC00000) +#elif SNAN_BIT_IS_ONE +#define float32_default_nan make_float32(0x7FBFFFFF) +#else +#define float32_default_nan make_float32(0xFFC00000) +#endif + +/*---------------------------------------------------------------------------- +| Returns 1 if the single-precision floating-point value `a' is a quiet +| NaN; otherwise returns 0. +*----------------------------------------------------------------------------*/ + +int float32_is_quiet_nan( float32 a_ ) +{ + uint32_t a = float32_val(a_); +#if SNAN_BIT_IS_ONE + return ( ( ( a>>22 ) & 0x1FF ) == 0x1FE ) && ( a & 0x003FFFFF ); +#else + return ( 0xFF800000 <= (bits32) ( a<<1 ) ); +#endif +} + +/*---------------------------------------------------------------------------- +| Returns 1 if the single-precision floating-point value `a' is a signaling +| NaN; otherwise returns 0. +*----------------------------------------------------------------------------*/ + +int float32_is_signaling_nan( float32 a_ ) +{ + uint32_t a = float32_val(a_); +#if SNAN_BIT_IS_ONE + return ( 0xFF800000 <= (bits32) ( a<<1 ) ); +#else + return ( ( ( a>>22 ) & 0x1FF ) == 0x1FE ) && ( a & 0x003FFFFF ); +#endif +} + +/*---------------------------------------------------------------------------- +| Returns a quiet NaN if the single-precision floating point value `a' is a +| signaling NaN; otherwise returns `a'. +*----------------------------------------------------------------------------*/ + +float32 float32_maybe_silence_nan( float32 a_ ) +{ + if (float32_is_signaling_nan(a_)) { +#if SNAN_BIT_IS_ONE +# if defined(TARGET_MIPS) || defined(TARGET_SH4) + return float32_default_nan; +# else +# error Rules for silencing a signaling NaN are target-specific +# endif +#else + bits32 a = float32_val(a_); + a |= (1 << 22); + return make_float32(a); +#endif + } + return a_; +} + +/*---------------------------------------------------------------------------- +| Returns the result of converting the single-precision floating-point NaN +| `a' to the canonical NaN format. If `a' is a signaling NaN, the invalid +| exception is raised. +*----------------------------------------------------------------------------*/ + +static commonNaNT float32ToCommonNaN( float32 a STATUS_PARAM ) +{ + commonNaNT z; + + if ( float32_is_signaling_nan( a ) ) float_raise( float_flag_invalid STATUS_VAR ); + z.sign = float32_val(a)>>31; + z.low = 0; + z.high = ( (bits64) float32_val(a) )<<41; + return z; +} + +/*---------------------------------------------------------------------------- +| Returns the result of converting the canonical NaN `a' to the single- +| precision floating-point format. +*----------------------------------------------------------------------------*/ + +static float32 commonNaNToFloat32( commonNaNT a ) +{ + bits32 mantissa = a.high>>41; + if ( mantissa ) + return make_float32( + ( ( (bits32) a.sign )<<31 ) | 0x7F800000 | ( a.high>>41 ) ); + else + return float32_default_nan; +} + +/*---------------------------------------------------------------------------- +| Select which NaN to propagate for a two-input operation. +| IEEE754 doesn't specify all the details of this, so the +| algorithm is target-specific. +| The routine is passed various bits of information about the +| two NaNs and should return 0 to select NaN a and 1 for NaN b. +| Note that signalling NaNs are always squashed to quiet NaNs +| by the caller, by calling floatXX_maybe_silence_nan() before +| returning them. +| +| aIsLargerSignificand is only valid if both a and b are NaNs +| of some kind, and is true if a has the larger significand, +| or if both a and b have the same significand but a is +| positive but b is negative. It is only needed for the x87 +| tie-break rule. +*----------------------------------------------------------------------------*/ + +#if defined(TARGET_ARM) +static int pickNaN(flag aIsQNaN, flag aIsSNaN, flag bIsQNaN, flag bIsSNaN, + flag aIsLargerSignificand) +{ + /* ARM mandated NaN propagation rules: take the first of: + * 1. A if it is signaling + * 2. B if it is signaling + * 3. A (quiet) + * 4. B (quiet) + * A signaling NaN is always quietened before returning it. + */ + if (aIsSNaN) { + return 0; + } else if (bIsSNaN) { + return 1; + } else if (aIsQNaN) { + return 0; + } else { + return 1; + } +} +#elif defined(TARGET_MIPS) +static int pickNaN(flag aIsQNaN, flag aIsSNaN, flag bIsQNaN, flag bIsSNaN, + flag aIsLargerSignificand) +{ + /* According to MIPS specifications, if one of the two operands is + * a sNaN, a new qNaN has to be generated. This is done in + * floatXX_maybe_silence_nan(). For qNaN inputs the specifications + * says: "When possible, this QNaN result is one of the operand QNaN + * values." In practice it seems that most implementations choose + * the first operand if both operands are qNaN. In short this gives + * the following rules: + * 1. A if it is signaling + * 2. B if it is signaling + * 3. A (quiet) + * 4. B (quiet) + * A signaling NaN is always silenced before returning it. + */ + if (aIsSNaN) { + return 0; + } else if (bIsSNaN) { + return 1; + } else if (aIsQNaN) { + return 0; + } else { + return 1; + } +} +#elif defined(TARGET_PPC) +static int pickNaN(flag aIsQNaN, flag aIsSNaN, flag bIsQNaN, flag bIsSNaN, + flag aIsLargerSignificand) +{ + /* PowerPC propagation rules: + * 1. A if it sNaN or qNaN + * 2. B if it sNaN or qNaN + * A signaling NaN is always silenced before returning it. + */ + if (aIsSNaN || aIsQNaN) { + return 0; + } else { + return 1; + } +} +#else +static int pickNaN(flag aIsQNaN, flag aIsSNaN, flag bIsQNaN, flag bIsSNaN, + flag aIsLargerSignificand) +{ + /* This implements x87 NaN propagation rules: + * SNaN + QNaN => return the QNaN + * two SNaNs => return the one with the larger significand, silenced + * two QNaNs => return the one with the larger significand + * SNaN and a non-NaN => return the SNaN, silenced + * QNaN and a non-NaN => return the QNaN + * + * If we get down to comparing significands and they are the same, + * return the NaN with the positive sign bit (if any). + */ + if (aIsSNaN) { + if (bIsSNaN) { + return aIsLargerSignificand ? 0 : 1; + } + return bIsQNaN ? 1 : 0; + } + else if (aIsQNaN) { + if (bIsSNaN || !bIsQNaN) + return 0; + else { + return aIsLargerSignificand ? 0 : 1; + } + } else { + return 1; + } +} +#endif + +/*---------------------------------------------------------------------------- +| Takes two single-precision floating-point values `a' and `b', one of which +| is a NaN, and returns the appropriate NaN result. If either `a' or `b' is a +| signaling NaN, the invalid exception is raised. +*----------------------------------------------------------------------------*/ + +static float32 propagateFloat32NaN( float32 a, float32 b STATUS_PARAM) +{ + flag aIsQuietNaN, aIsSignalingNaN, bIsQuietNaN, bIsSignalingNaN; + flag aIsLargerSignificand; + bits32 av, bv; + + aIsQuietNaN = float32_is_quiet_nan( a ); + aIsSignalingNaN = float32_is_signaling_nan( a ); + bIsQuietNaN = float32_is_quiet_nan( b ); + bIsSignalingNaN = float32_is_signaling_nan( b ); + av = float32_val(a); + bv = float32_val(b); + + if ( aIsSignalingNaN | bIsSignalingNaN ) float_raise( float_flag_invalid STATUS_VAR); + + if ( STATUS(default_nan_mode) ) + return float32_default_nan; + + if ((bits32)(av<<1) < (bits32)(bv<<1)) { + aIsLargerSignificand = 0; + } else if ((bits32)(bv<<1) < (bits32)(av<<1)) { + aIsLargerSignificand = 1; + } else { + aIsLargerSignificand = (av < bv) ? 1 : 0; + } + + if (pickNaN(aIsQuietNaN, aIsSignalingNaN, bIsQuietNaN, bIsSignalingNaN, + aIsLargerSignificand)) { + return float32_maybe_silence_nan(b); + } else { + return float32_maybe_silence_nan(a); + } +} + +/*---------------------------------------------------------------------------- +| The pattern for a default generated double-precision NaN. +*----------------------------------------------------------------------------*/ +#if defined(TARGET_SPARC) +#define float64_default_nan make_float64(LIT64( 0x7FFFFFFFFFFFFFFF )) +#elif defined(TARGET_PPC) || defined(TARGET_ARM) || defined(TARGET_ALPHA) +#define float64_default_nan make_float64(LIT64( 0x7FF8000000000000 )) +#elif SNAN_BIT_IS_ONE +#define float64_default_nan make_float64(LIT64( 0x7FF7FFFFFFFFFFFF )) +#else +#define float64_default_nan make_float64(LIT64( 0xFFF8000000000000 )) +#endif + +/*---------------------------------------------------------------------------- +| Returns 1 if the double-precision floating-point value `a' is a quiet +| NaN; otherwise returns 0. +*----------------------------------------------------------------------------*/ + +int float64_is_quiet_nan( float64 a_ ) +{ + bits64 a = float64_val(a_); +#if SNAN_BIT_IS_ONE + return + ( ( ( a>>51 ) & 0xFFF ) == 0xFFE ) + && ( a & LIT64( 0x0007FFFFFFFFFFFF ) ); +#else + return ( LIT64( 0xFFF0000000000000 ) <= (bits64) ( a<<1 ) ); +#endif +} + +/*---------------------------------------------------------------------------- +| Returns 1 if the double-precision floating-point value `a' is a signaling +| NaN; otherwise returns 0. +*----------------------------------------------------------------------------*/ + +int float64_is_signaling_nan( float64 a_ ) +{ + bits64 a = float64_val(a_); +#if SNAN_BIT_IS_ONE + return ( LIT64( 0xFFF0000000000000 ) <= (bits64) ( a<<1 ) ); +#else + return + ( ( ( a>>51 ) & 0xFFF ) == 0xFFE ) + && ( a & LIT64( 0x0007FFFFFFFFFFFF ) ); +#endif +} + +/*---------------------------------------------------------------------------- +| Returns a quiet NaN if the double-precision floating point value `a' is a +| signaling NaN; otherwise returns `a'. +*----------------------------------------------------------------------------*/ + +float64 float64_maybe_silence_nan( float64 a_ ) +{ + if (float64_is_signaling_nan(a_)) { +#if SNAN_BIT_IS_ONE +# if defined(TARGET_MIPS) || defined(TARGET_SH4) + return float64_default_nan; +# else +# error Rules for silencing a signaling NaN are target-specific +# endif +#else + bits64 a = float64_val(a_); + a |= LIT64( 0x0008000000000000 ); + return make_float64(a); +#endif + } + return a_; +} + +/*---------------------------------------------------------------------------- +| Returns the result of converting the double-precision floating-point NaN +| `a' to the canonical NaN format. If `a' is a signaling NaN, the invalid +| exception is raised. +*----------------------------------------------------------------------------*/ + +static commonNaNT float64ToCommonNaN( float64 a STATUS_PARAM) +{ + commonNaNT z; + + if ( float64_is_signaling_nan( a ) ) float_raise( float_flag_invalid STATUS_VAR); + z.sign = float64_val(a)>>63; + z.low = 0; + z.high = float64_val(a)<<12; + return z; +} + +/*---------------------------------------------------------------------------- +| Returns the result of converting the canonical NaN `a' to the double- +| precision floating-point format. +*----------------------------------------------------------------------------*/ + +static float64 commonNaNToFloat64( commonNaNT a ) +{ + bits64 mantissa = a.high>>12; + + if ( mantissa ) + return make_float64( + ( ( (bits64) a.sign )<<63 ) + | LIT64( 0x7FF0000000000000 ) + | ( a.high>>12 )); + else + return float64_default_nan; +} + +/*---------------------------------------------------------------------------- +| Takes two double-precision floating-point values `a' and `b', one of which +| is a NaN, and returns the appropriate NaN result. If either `a' or `b' is a +| signaling NaN, the invalid exception is raised. +*----------------------------------------------------------------------------*/ + +static float64 propagateFloat64NaN( float64 a, float64 b STATUS_PARAM) +{ + flag aIsQuietNaN, aIsSignalingNaN, bIsQuietNaN, bIsSignalingNaN; + flag aIsLargerSignificand; + bits64 av, bv; + + aIsQuietNaN = float64_is_quiet_nan( a ); + aIsSignalingNaN = float64_is_signaling_nan( a ); + bIsQuietNaN = float64_is_quiet_nan( b ); + bIsSignalingNaN = float64_is_signaling_nan( b ); + av = float64_val(a); + bv = float64_val(b); + + if ( aIsSignalingNaN | bIsSignalingNaN ) float_raise( float_flag_invalid STATUS_VAR); + + if ( STATUS(default_nan_mode) ) + return float64_default_nan; + + if ((bits64)(av<<1) < (bits64)(bv<<1)) { + aIsLargerSignificand = 0; + } else if ((bits64)(bv<<1) < (bits64)(av<<1)) { + aIsLargerSignificand = 1; + } else { + aIsLargerSignificand = (av < bv) ? 1 : 0; + } + + if (pickNaN(aIsQuietNaN, aIsSignalingNaN, bIsQuietNaN, bIsSignalingNaN, + aIsLargerSignificand)) { + return float64_maybe_silence_nan(b); + } else { + return float64_maybe_silence_nan(a); + } +} + +#ifdef FLOATX80 + +/*---------------------------------------------------------------------------- +| The pattern for a default generated extended double-precision NaN. The +| `high' and `low' values hold the most- and least-significant bits, +| respectively. +*----------------------------------------------------------------------------*/ +#if SNAN_BIT_IS_ONE +#define floatx80_default_nan_high 0x7FFF +#define floatx80_default_nan_low LIT64( 0xBFFFFFFFFFFFFFFF ) +#else +#define floatx80_default_nan_high 0xFFFF +#define floatx80_default_nan_low LIT64( 0xC000000000000000 ) +#endif + +/*---------------------------------------------------------------------------- +| Returns 1 if the extended double-precision floating-point value `a' is a +| quiet NaN; otherwise returns 0. This slightly differs from the same +| function for other types as floatx80 has an explicit bit. +*----------------------------------------------------------------------------*/ + +int floatx80_is_quiet_nan( floatx80 a ) +{ +#if SNAN_BIT_IS_ONE + bits64 aLow; + + aLow = a.low & ~ LIT64( 0x4000000000000000 ); + return + ( ( a.high & 0x7FFF ) == 0x7FFF ) + && (bits64) ( aLow<<1 ) + && ( a.low == aLow ); +#else + return ( ( a.high & 0x7FFF ) == 0x7FFF ) + && (LIT64( 0x8000000000000000 ) <= ((bits64) ( a.low<<1 ))); +#endif +} + +/*---------------------------------------------------------------------------- +| Returns 1 if the extended double-precision floating-point value `a' is a +| signaling NaN; otherwise returns 0. This slightly differs from the same +| function for other types as floatx80 has an explicit bit. +*----------------------------------------------------------------------------*/ + +int floatx80_is_signaling_nan( floatx80 a ) +{ +#if SNAN_BIT_IS_ONE + return ( ( a.high & 0x7FFF ) == 0x7FFF ) + && (LIT64( 0x8000000000000000 ) <= ((bits64) ( a.low<<1 ))); +#else + bits64 aLow; + + aLow = a.low & ~ LIT64( 0x4000000000000000 ); + return + ( ( a.high & 0x7FFF ) == 0x7FFF ) + && (bits64) ( aLow<<1 ) + && ( a.low == aLow ); +#endif +} + +/*---------------------------------------------------------------------------- +| Returns a quiet NaN if the extended double-precision floating point value +| `a' is a signaling NaN; otherwise returns `a'. +*----------------------------------------------------------------------------*/ + +floatx80 floatx80_maybe_silence_nan( floatx80 a ) +{ + if (floatx80_is_signaling_nan(a)) { +#if SNAN_BIT_IS_ONE +# if defined(TARGET_MIPS) || defined(TARGET_SH4) + a.low = floatx80_default_nan_low; + a.high = floatx80_default_nan_high; +# else +# error Rules for silencing a signaling NaN are target-specific +# endif +#else + a.low |= LIT64( 0xC000000000000000 ); + return a; +#endif + } + return a; +} + +/*---------------------------------------------------------------------------- +| Returns the result of converting the extended double-precision floating- +| point NaN `a' to the canonical NaN format. If `a' is a signaling NaN, the +| invalid exception is raised. +*----------------------------------------------------------------------------*/ + +static commonNaNT floatx80ToCommonNaN( floatx80 a STATUS_PARAM) +{ + commonNaNT z; + + if ( floatx80_is_signaling_nan( a ) ) float_raise( float_flag_invalid STATUS_VAR); + z.sign = a.high>>15; + z.low = 0; + z.high = a.low; + return z; +} + +/*---------------------------------------------------------------------------- +| Returns the result of converting the canonical NaN `a' to the extended +| double-precision floating-point format. +*----------------------------------------------------------------------------*/ + +static floatx80 commonNaNToFloatx80( commonNaNT a ) +{ + floatx80 z; + + if (a.high) + z.low = a.high; + else + z.low = floatx80_default_nan_low; + z.high = ( ( (bits16) a.sign )<<15 ) | 0x7FFF; + return z; +} + +/*---------------------------------------------------------------------------- +| Takes two extended double-precision floating-point values `a' and `b', one +| of which is a NaN, and returns the appropriate NaN result. If either `a' or +| `b' is a signaling NaN, the invalid exception is raised. +*----------------------------------------------------------------------------*/ + +static floatx80 propagateFloatx80NaN( floatx80 a, floatx80 b STATUS_PARAM) +{ + flag aIsQuietNaN, aIsSignalingNaN, bIsQuietNaN, bIsSignalingNaN; + flag aIsLargerSignificand; + + aIsQuietNaN = floatx80_is_quiet_nan( a ); + aIsSignalingNaN = floatx80_is_signaling_nan( a ); + bIsQuietNaN = floatx80_is_quiet_nan( b ); + bIsSignalingNaN = floatx80_is_signaling_nan( b ); + + if ( aIsSignalingNaN | bIsSignalingNaN ) float_raise( float_flag_invalid STATUS_VAR); + + if ( STATUS(default_nan_mode) ) { + a.low = floatx80_default_nan_low; + a.high = floatx80_default_nan_high; + return a; + } + + if (a.low < b.low) { + aIsLargerSignificand = 0; + } else if (b.low < a.low) { + aIsLargerSignificand = 1; + } else { + aIsLargerSignificand = (a.high < b.high) ? 1 : 0; + } + + if (pickNaN(aIsQuietNaN, aIsSignalingNaN, bIsQuietNaN, bIsSignalingNaN, + aIsLargerSignificand)) { + return floatx80_maybe_silence_nan(b); + } else { + return floatx80_maybe_silence_nan(a); + } +} + +#endif + +#ifdef FLOAT128 + +/*---------------------------------------------------------------------------- +| The pattern for a default generated quadruple-precision NaN. The `high' and +| `low' values hold the most- and least-significant bits, respectively. +*----------------------------------------------------------------------------*/ +#if SNAN_BIT_IS_ONE +#define float128_default_nan_high LIT64( 0x7FFF7FFFFFFFFFFF ) +#define float128_default_nan_low LIT64( 0xFFFFFFFFFFFFFFFF ) +#else +#define float128_default_nan_high LIT64( 0xFFFF800000000000 ) +#define float128_default_nan_low LIT64( 0x0000000000000000 ) +#endif + +/*---------------------------------------------------------------------------- +| Returns 1 if the quadruple-precision floating-point value `a' is a quiet +| NaN; otherwise returns 0. +*----------------------------------------------------------------------------*/ + +int float128_is_quiet_nan( float128 a ) +{ +#if SNAN_BIT_IS_ONE + return + ( ( ( a.high>>47 ) & 0xFFFF ) == 0xFFFE ) + && ( a.low || ( a.high & LIT64( 0x00007FFFFFFFFFFF ) ) ); +#else + return + ( LIT64( 0xFFFE000000000000 ) <= (bits64) ( a.high<<1 ) ) + && ( a.low || ( a.high & LIT64( 0x0000FFFFFFFFFFFF ) ) ); +#endif +} + +/*---------------------------------------------------------------------------- +| Returns 1 if the quadruple-precision floating-point value `a' is a +| signaling NaN; otherwise returns 0. +*----------------------------------------------------------------------------*/ + +int float128_is_signaling_nan( float128 a ) +{ +#if SNAN_BIT_IS_ONE + return + ( LIT64( 0xFFFE000000000000 ) <= (bits64) ( a.high<<1 ) ) + && ( a.low || ( a.high & LIT64( 0x0000FFFFFFFFFFFF ) ) ); +#else + return + ( ( ( a.high>>47 ) & 0xFFFF ) == 0xFFFE ) + && ( a.low || ( a.high & LIT64( 0x00007FFFFFFFFFFF ) ) ); +#endif +} + +/*---------------------------------------------------------------------------- +| Returns a quiet NaN if the quadruple-precision floating point value `a' is +| a signaling NaN; otherwise returns `a'. +*----------------------------------------------------------------------------*/ + +float128 float128_maybe_silence_nan( float128 a ) +{ + if (float128_is_signaling_nan(a)) { +#if SNAN_BIT_IS_ONE +# if defined(TARGET_MIPS) || defined(TARGET_SH4) + a.low = float128_default_nan_low; + a.high = float128_default_nan_high; +# else +# error Rules for silencing a signaling NaN are target-specific +# endif +#else + a.high |= LIT64( 0x0000800000000000 ); + return a; +#endif + } + return a; +} + +/*---------------------------------------------------------------------------- +| Returns the result of converting the quadruple-precision floating-point NaN +| `a' to the canonical NaN format. If `a' is a signaling NaN, the invalid +| exception is raised. +*----------------------------------------------------------------------------*/ + +static commonNaNT float128ToCommonNaN( float128 a STATUS_PARAM) +{ + commonNaNT z; + + if ( float128_is_signaling_nan( a ) ) float_raise( float_flag_invalid STATUS_VAR); + z.sign = a.high>>63; + shortShift128Left( a.high, a.low, 16, &z.high, &z.low ); + return z; +} + +/*---------------------------------------------------------------------------- +| Returns the result of converting the canonical NaN `a' to the quadruple- +| precision floating-point format. +*----------------------------------------------------------------------------*/ + +static float128 commonNaNToFloat128( commonNaNT a ) +{ + float128 z; + + shift128Right( a.high, a.low, 16, &z.high, &z.low ); + z.high |= ( ( (bits64) a.sign )<<63 ) | LIT64( 0x7FFF000000000000 ); + return z; +} + +/*---------------------------------------------------------------------------- +| Takes two quadruple-precision floating-point values `a' and `b', one of +| which is a NaN, and returns the appropriate NaN result. If either `a' or +| `b' is a signaling NaN, the invalid exception is raised. +*----------------------------------------------------------------------------*/ + +static float128 propagateFloat128NaN( float128 a, float128 b STATUS_PARAM) +{ + flag aIsQuietNaN, aIsSignalingNaN, bIsQuietNaN, bIsSignalingNaN; + flag aIsLargerSignificand; + + aIsQuietNaN = float128_is_quiet_nan( a ); + aIsSignalingNaN = float128_is_signaling_nan( a ); + bIsQuietNaN = float128_is_quiet_nan( b ); + bIsSignalingNaN = float128_is_signaling_nan( b ); + + if ( aIsSignalingNaN | bIsSignalingNaN ) float_raise( float_flag_invalid STATUS_VAR); + + if ( STATUS(default_nan_mode) ) { + a.low = float128_default_nan_low; + a.high = float128_default_nan_high; + return a; + } + + if (lt128(a.high<<1, a.low, b.high<<1, b.low)) { + aIsLargerSignificand = 0; + } else if (lt128(b.high<<1, b.low, a.high<<1, a.low)) { + aIsLargerSignificand = 1; + } else { + aIsLargerSignificand = (a.high < b.high) ? 1 : 0; + } + + if (pickNaN(aIsQuietNaN, aIsSignalingNaN, bIsQuietNaN, bIsSignalingNaN, + aIsLargerSignificand)) { + return float128_maybe_silence_nan(b); + } else { + return float128_maybe_silence_nan(a); + } +} + +#endif diff --git a/fpu/softfloat.c b/fpu/softfloat.c new file mode 100644 index 0000000..17842f4 --- /dev/null +++ b/fpu/softfloat.c @@ -0,0 +1,6119 @@ + +/*============================================================================ + +This C source file is part of the SoftFloat IEC/IEEE Floating-point Arithmetic +Package, Release 2b. + +Written by John R. Hauser. This work was made possible in part by the +International Computer Science Institute, located at Suite 600, 1947 Center +Street, Berkeley, California 94704. Funding was partially provided by the +National Science Foundation under grant MIP-9311980. The original version +of this code was written as part of a project to build a fixed-point vector +processor in collaboration with the University of California at Berkeley, +overseen by Profs. Nelson Morgan and John Wawrzynek. More information +is available through the Web page `http://www.cs.berkeley.edu/~jhauser/ +arithmetic/SoftFloat.html'. + +THIS SOFTWARE IS DISTRIBUTED AS IS, FOR FREE. Although reasonable effort has +been made to avoid it, THIS SOFTWARE MAY CONTAIN FAULTS THAT WILL AT TIMES +RESULT IN INCORRECT BEHAVIOR. USE OF THIS SOFTWARE IS RESTRICTED TO PERSONS +AND ORGANIZATIONS WHO CAN AND WILL TAKE FULL RESPONSIBILITY FOR ALL LOSSES, +COSTS, OR OTHER PROBLEMS THEY INCUR DUE TO THE SOFTWARE, AND WHO FURTHERMORE +EFFECTIVELY INDEMNIFY JOHN HAUSER AND THE INTERNATIONAL COMPUTER SCIENCE +INSTITUTE (possibly via similar legal warning) AGAINST ALL LOSSES, COSTS, OR +OTHER PROBLEMS INCURRED BY THEIR CUSTOMERS AND CLIENTS DUE TO THE SOFTWARE. + +Derivative works are acceptable, even for commercial purposes, so long as +(1) the source code for the derivative work includes prominent notice that +the work is derivative, and (2) the source code includes prominent notice with +these four paragraphs for those parts of this code that are retained. + +=============================================================================*/ + +#include "softfloat.h" + +/*---------------------------------------------------------------------------- +| Primitive arithmetic functions, including multi-word arithmetic, and +| division and square root approximations. (Can be specialized to target if +| desired.) +*----------------------------------------------------------------------------*/ +#include "softfloat-macros.h" + +/*---------------------------------------------------------------------------- +| Functions and definitions to determine: (1) whether tininess for underflow +| is detected before or after rounding by default, (2) what (if anything) +| happens when exceptions are raised, (3) how signaling NaNs are distinguished +| from quiet NaNs, (4) the default generated quiet NaNs, and (5) how NaNs +| are propagated from function inputs to output. These details are target- +| specific. +*----------------------------------------------------------------------------*/ +#include "softfloat-specialize.h" + +void set_float_rounding_mode(int val STATUS_PARAM) +{ + STATUS(float_rounding_mode) = val; +} + +void set_float_exception_flags(int val STATUS_PARAM) +{ + STATUS(float_exception_flags) = val; +} + +#ifdef FLOATX80 +void set_floatx80_rounding_precision(int val STATUS_PARAM) +{ + STATUS(floatx80_rounding_precision) = val; +} +#endif + +/*---------------------------------------------------------------------------- +| Takes a 64-bit fixed-point value `absZ' with binary point between bits 6 +| and 7, and returns the properly rounded 32-bit integer corresponding to the +| input. If `zSign' is 1, the input is negated before being converted to an +| integer. Bit 63 of `absZ' must be zero. Ordinarily, the fixed-point input +| is simply rounded to an integer, with the inexact exception raised if the +| input cannot be represented exactly as an integer. However, if the fixed- +| point input is too large, the invalid exception is raised and the largest +| positive or negative integer is returned. +*----------------------------------------------------------------------------*/ + +static int32 roundAndPackInt32( flag zSign, bits64 absZ STATUS_PARAM) +{ + int8 roundingMode; + flag roundNearestEven; + int8 roundIncrement, roundBits; + int32 z; + + roundingMode = STATUS(float_rounding_mode); + roundNearestEven = ( roundingMode == float_round_nearest_even ); + roundIncrement = 0x40; + if ( ! roundNearestEven ) { + if ( roundingMode == float_round_to_zero ) { + roundIncrement = 0; + } + else { + roundIncrement = 0x7F; + if ( zSign ) { + if ( roundingMode == float_round_up ) roundIncrement = 0; + } + else { + if ( roundingMode == float_round_down ) roundIncrement = 0; + } + } + } + roundBits = absZ & 0x7F; + absZ = ( absZ + roundIncrement )>>7; + absZ &= ~ ( ( ( roundBits ^ 0x40 ) == 0 ) & roundNearestEven ); + z = absZ; + if ( zSign ) z = - z; + if ( ( absZ>>32 ) || ( z && ( ( z < 0 ) ^ zSign ) ) ) { + float_raise( float_flag_invalid STATUS_VAR); + return zSign ? (sbits32) 0x80000000 : 0x7FFFFFFF; + } + if ( roundBits ) STATUS(float_exception_flags) |= float_flag_inexact; + return z; + +} + +/*---------------------------------------------------------------------------- +| Takes the 128-bit fixed-point value formed by concatenating `absZ0' and +| `absZ1', with binary point between bits 63 and 64 (between the input words), +| and returns the properly rounded 64-bit integer corresponding to the input. +| If `zSign' is 1, the input is negated before being converted to an integer. +| Ordinarily, the fixed-point input is simply rounded to an integer, with +| the inexact exception raised if the input cannot be represented exactly as +| an integer. However, if the fixed-point input is too large, the invalid +| exception is raised and the largest positive or negative integer is +| returned. +*----------------------------------------------------------------------------*/ + +static int64 roundAndPackInt64( flag zSign, bits64 absZ0, bits64 absZ1 STATUS_PARAM) +{ + int8 roundingMode; + flag roundNearestEven, increment; + int64 z; + + roundingMode = STATUS(float_rounding_mode); + roundNearestEven = ( roundingMode == float_round_nearest_even ); + increment = ( (sbits64) absZ1 < 0 ); + if ( ! roundNearestEven ) { + if ( roundingMode == float_round_to_zero ) { + increment = 0; + } + else { + if ( zSign ) { + increment = ( roundingMode == float_round_down ) && absZ1; + } + else { + increment = ( roundingMode == float_round_up ) && absZ1; + } + } + } + if ( increment ) { + ++absZ0; + if ( absZ0 == 0 ) goto overflow; + absZ0 &= ~ ( ( (bits64) ( absZ1<<1 ) == 0 ) & roundNearestEven ); + } + z = absZ0; + if ( zSign ) z = - z; + if ( z && ( ( z < 0 ) ^ zSign ) ) { + overflow: + float_raise( float_flag_invalid STATUS_VAR); + return + zSign ? (sbits64) LIT64( 0x8000000000000000 ) + : LIT64( 0x7FFFFFFFFFFFFFFF ); + } + if ( absZ1 ) STATUS(float_exception_flags) |= float_flag_inexact; + return z; + +} + +/*---------------------------------------------------------------------------- +| Returns the fraction bits of the single-precision floating-point value `a'. +*----------------------------------------------------------------------------*/ + +INLINE bits32 extractFloat32Frac( float32 a ) +{ + + return float32_val(a) & 0x007FFFFF; + +} + +/*---------------------------------------------------------------------------- +| Returns the exponent bits of the single-precision floating-point value `a'. +*----------------------------------------------------------------------------*/ + +INLINE int16 extractFloat32Exp( float32 a ) +{ + + return ( float32_val(a)>>23 ) & 0xFF; + +} + +/*---------------------------------------------------------------------------- +| Returns the sign bit of the single-precision floating-point value `a'. +*----------------------------------------------------------------------------*/ + +INLINE flag extractFloat32Sign( float32 a ) +{ + + return float32_val(a)>>31; + +} + +/*---------------------------------------------------------------------------- +| If `a' is denormal and we are in flush-to-zero mode then set the +| input-denormal exception and return zero. Otherwise just return the value. +*----------------------------------------------------------------------------*/ +static float32 float32_squash_input_denormal(float32 a STATUS_PARAM) +{ + if (STATUS(flush_inputs_to_zero)) { + if (extractFloat32Exp(a) == 0 && extractFloat32Frac(a) != 0) { + float_raise(float_flag_input_denormal STATUS_VAR); + return make_float32(float32_val(a) & 0x80000000); + } + } + return a; +} + +/*---------------------------------------------------------------------------- +| Normalizes the subnormal single-precision floating-point value represented +| by the denormalized significand `aSig'. The normalized exponent and +| significand are stored at the locations pointed to by `zExpPtr' and +| `zSigPtr', respectively. +*----------------------------------------------------------------------------*/ + +static void + normalizeFloat32Subnormal( bits32 aSig, int16 *zExpPtr, bits32 *zSigPtr ) +{ + int8 shiftCount; + + shiftCount = countLeadingZeros32( aSig ) - 8; + *zSigPtr = aSig<>7; + zSig &= ~ ( ( ( roundBits ^ 0x40 ) == 0 ) & roundNearestEven ); + if ( zSig == 0 ) zExp = 0; + return packFloat32( zSign, zExp, zSig ); + +} + +/*---------------------------------------------------------------------------- +| Takes an abstract floating-point value having sign `zSign', exponent `zExp', +| and significand `zSig', and returns the proper single-precision floating- +| point value corresponding to the abstract input. This routine is just like +| `roundAndPackFloat32' except that `zSig' does not have to be normalized. +| Bit 31 of `zSig' must be zero, and `zExp' must be 1 less than the ``true'' +| floating-point exponent. +*----------------------------------------------------------------------------*/ + +static float32 + normalizeRoundAndPackFloat32( flag zSign, int16 zExp, bits32 zSig STATUS_PARAM) +{ + int8 shiftCount; + + shiftCount = countLeadingZeros32( zSig ) - 1; + return roundAndPackFloat32( zSign, zExp - shiftCount, zSig<>52 ) & 0x7FF; + +} + +/*---------------------------------------------------------------------------- +| Returns the sign bit of the double-precision floating-point value `a'. +*----------------------------------------------------------------------------*/ + +INLINE flag extractFloat64Sign( float64 a ) +{ + + return float64_val(a)>>63; + +} + +/*---------------------------------------------------------------------------- +| If `a' is denormal and we are in flush-to-zero mode then set the +| input-denormal exception and return zero. Otherwise just return the value. +*----------------------------------------------------------------------------*/ +static float64 float64_squash_input_denormal(float64 a STATUS_PARAM) +{ + if (STATUS(flush_inputs_to_zero)) { + if (extractFloat64Exp(a) == 0 && extractFloat64Frac(a) != 0) { + float_raise(float_flag_input_denormal STATUS_VAR); + return make_float64(float64_val(a) & (1ULL << 63)); + } + } + return a; +} + +/*---------------------------------------------------------------------------- +| Normalizes the subnormal double-precision floating-point value represented +| by the denormalized significand `aSig'. The normalized exponent and +| significand are stored at the locations pointed to by `zExpPtr' and +| `zSigPtr', respectively. +*----------------------------------------------------------------------------*/ + +static void + normalizeFloat64Subnormal( bits64 aSig, int16 *zExpPtr, bits64 *zSigPtr ) +{ + int8 shiftCount; + + shiftCount = countLeadingZeros64( aSig ) - 11; + *zSigPtr = aSig<>10; + zSig &= ~ ( ( ( roundBits ^ 0x200 ) == 0 ) & roundNearestEven ); + if ( zSig == 0 ) zExp = 0; + return packFloat64( zSign, zExp, zSig ); + +} + +/*---------------------------------------------------------------------------- +| Takes an abstract floating-point value having sign `zSign', exponent `zExp', +| and significand `zSig', and returns the proper double-precision floating- +| point value corresponding to the abstract input. This routine is just like +| `roundAndPackFloat64' except that `zSig' does not have to be normalized. +| Bit 63 of `zSig' must be zero, and `zExp' must be 1 less than the ``true'' +| floating-point exponent. +*----------------------------------------------------------------------------*/ + +static float64 + normalizeRoundAndPackFloat64( flag zSign, int16 zExp, bits64 zSig STATUS_PARAM) +{ + int8 shiftCount; + + shiftCount = countLeadingZeros64( zSig ) - 1; + return roundAndPackFloat64( zSign, zExp - shiftCount, zSig<>15; + +} + +/*---------------------------------------------------------------------------- +| Normalizes the subnormal extended double-precision floating-point value +| represented by the denormalized significand `aSig'. The normalized exponent +| and significand are stored at the locations pointed to by `zExpPtr' and +| `zSigPtr', respectively. +*----------------------------------------------------------------------------*/ + +static void + normalizeFloatx80Subnormal( bits64 aSig, int32 *zExpPtr, bits64 *zSigPtr ) +{ + int8 shiftCount; + + shiftCount = countLeadingZeros64( aSig ); + *zSigPtr = aSig<>48 ) & 0x7FFF; + +} + +/*---------------------------------------------------------------------------- +| Returns the sign bit of the quadruple-precision floating-point value `a'. +*----------------------------------------------------------------------------*/ + +INLINE flag extractFloat128Sign( float128 a ) +{ + + return a.high>>63; + +} + +/*---------------------------------------------------------------------------- +| Normalizes the subnormal quadruple-precision floating-point value +| represented by the denormalized significand formed by the concatenation of +| `aSig0' and `aSig1'. The normalized exponent is stored at the location +| pointed to by `zExpPtr'. The most significant 49 bits of the normalized +| significand are stored at the location pointed to by `zSig0Ptr', and the +| least significant 64 bits of the normalized significand are stored at the +| location pointed to by `zSig1Ptr'. +*----------------------------------------------------------------------------*/ + +static void + normalizeFloat128Subnormal( + bits64 aSig0, + bits64 aSig1, + int32 *zExpPtr, + bits64 *zSig0Ptr, + bits64 *zSig1Ptr + ) +{ + int8 shiftCount; + + if ( aSig0 == 0 ) { + shiftCount = countLeadingZeros64( aSig1 ) - 15; + if ( shiftCount < 0 ) { + *zSig0Ptr = aSig1>>( - shiftCount ); + *zSig1Ptr = aSig1<<( shiftCount & 63 ); + } + else { + *zSig0Ptr = aSig1< 0, 0x95 - shiftCount, a< 0, 0x9C - shiftCount, a STATUS_VAR ); + } +} + +/*---------------------------------------------------------------------------- +| Returns the result of converting the 64-bit two's complement integer `a' +| to the double-precision floating-point format. The conversion is performed +| according to the IEC/IEEE Standard for Binary Floating-Point Arithmetic. +*----------------------------------------------------------------------------*/ + +float64 int64_to_float64( int64 a STATUS_PARAM ) +{ + flag zSign; + + if ( a == 0 ) return float64_zero; + if ( a == (sbits64) LIT64( 0x8000000000000000 ) ) { + return packFloat64( 1, 0x43E, 0 ); + } + zSign = ( a < 0 ); + return normalizeRoundAndPackFloat64( zSign, 0x43C, zSign ? - a : a STATUS_VAR ); + +} + +float64 uint64_to_float64( uint64 a STATUS_PARAM ) +{ + if ( a == 0 ) return float64_zero; + return normalizeRoundAndPackFloat64( 0, 0x43C, a STATUS_VAR ); + +} + +#ifdef FLOATX80 + +/*---------------------------------------------------------------------------- +| Returns the result of converting the 64-bit two's complement integer `a' +| to the extended double-precision floating-point format. The conversion +| is performed according to the IEC/IEEE Standard for Binary Floating-Point +| Arithmetic. +*----------------------------------------------------------------------------*/ + +floatx80 int64_to_floatx80( int64 a STATUS_PARAM ) +{ + flag zSign; + uint64 absA; + int8 shiftCount; + + if ( a == 0 ) return packFloatx80( 0, 0, 0 ); + zSign = ( a < 0 ); + absA = zSign ? - a : a; + shiftCount = countLeadingZeros64( absA ); + return packFloatx80( zSign, 0x403E - shiftCount, absA<>( - shiftCount ); + if ( (bits32) ( aSig<<( shiftCount & 31 ) ) ) { + STATUS(float_exception_flags) |= float_flag_inexact; + } + if ( aSign ) z = - z; + return z; + +} + +/*---------------------------------------------------------------------------- +| Returns the result of converting the single-precision floating-point value +| `a' to the 16-bit two's complement integer format. The conversion is +| performed according to the IEC/IEEE Standard for Binary Floating-Point +| Arithmetic, except that the conversion is always rounded toward zero. +| If `a' is a NaN, the largest positive integer is returned. Otherwise, if +| the conversion overflows, the largest integer with the same sign as `a' is +| returned. +*----------------------------------------------------------------------------*/ + +int16 float32_to_int16_round_to_zero( float32 a STATUS_PARAM ) +{ + flag aSign; + int16 aExp, shiftCount; + bits32 aSig; + int32 z; + + aSig = extractFloat32Frac( a ); + aExp = extractFloat32Exp( a ); + aSign = extractFloat32Sign( a ); + shiftCount = aExp - 0x8E; + if ( 0 <= shiftCount ) { + if ( float32_val(a) != 0xC7000000 ) { + float_raise( float_flag_invalid STATUS_VAR); + if ( ! aSign || ( ( aExp == 0xFF ) && aSig ) ) { + return 0x7FFF; + } + } + return (sbits32) 0xffff8000; + } + else if ( aExp <= 0x7E ) { + if ( aExp | aSig ) { + STATUS(float_exception_flags) |= float_flag_inexact; + } + return 0; + } + shiftCount -= 0x10; + aSig = ( aSig | 0x00800000 )<<8; + z = aSig>>( - shiftCount ); + if ( (bits32) ( aSig<<( shiftCount & 31 ) ) ) { + STATUS(float_exception_flags) |= float_flag_inexact; + } + if ( aSign ) { + z = - z; + } + return z; + +} + +/*---------------------------------------------------------------------------- +| Returns the result of converting the single-precision floating-point value +| `a' to the 64-bit two's complement integer format. The conversion is +| performed according to the IEC/IEEE Standard for Binary Floating-Point +| Arithmetic---which means in particular that the conversion is rounded +| according to the current rounding mode. If `a' is a NaN, the largest +| positive integer is returned. Otherwise, if the conversion overflows, the +| largest integer with the same sign as `a' is returned. +*----------------------------------------------------------------------------*/ + +int64 float32_to_int64( float32 a STATUS_PARAM ) +{ + flag aSign; + int16 aExp, shiftCount; + bits32 aSig; + bits64 aSig64, aSigExtra; + a = float32_squash_input_denormal(a STATUS_VAR); + + aSig = extractFloat32Frac( a ); + aExp = extractFloat32Exp( a ); + aSign = extractFloat32Sign( a ); + shiftCount = 0xBE - aExp; + if ( shiftCount < 0 ) { + float_raise( float_flag_invalid STATUS_VAR); + if ( ! aSign || ( ( aExp == 0xFF ) && aSig ) ) { + return LIT64( 0x7FFFFFFFFFFFFFFF ); + } + return (sbits64) LIT64( 0x8000000000000000 ); + } + if ( aExp ) aSig |= 0x00800000; + aSig64 = aSig; + aSig64 <<= 40; + shift64ExtraRightJamming( aSig64, 0, shiftCount, &aSig64, &aSigExtra ); + return roundAndPackInt64( aSign, aSig64, aSigExtra STATUS_VAR ); + +} + +/*---------------------------------------------------------------------------- +| Returns the result of converting the single-precision floating-point value +| `a' to the 64-bit two's complement integer format. The conversion is +| performed according to the IEC/IEEE Standard for Binary Floating-Point +| Arithmetic, except that the conversion is always rounded toward zero. If +| `a' is a NaN, the largest positive integer is returned. Otherwise, if the +| conversion overflows, the largest integer with the same sign as `a' is +| returned. +*----------------------------------------------------------------------------*/ + +int64 float32_to_int64_round_to_zero( float32 a STATUS_PARAM ) +{ + flag aSign; + int16 aExp, shiftCount; + bits32 aSig; + bits64 aSig64; + int64 z; + a = float32_squash_input_denormal(a STATUS_VAR); + + aSig = extractFloat32Frac( a ); + aExp = extractFloat32Exp( a ); + aSign = extractFloat32Sign( a ); + shiftCount = aExp - 0xBE; + if ( 0 <= shiftCount ) { + if ( float32_val(a) != 0xDF000000 ) { + float_raise( float_flag_invalid STATUS_VAR); + if ( ! aSign || ( ( aExp == 0xFF ) && aSig ) ) { + return LIT64( 0x7FFFFFFFFFFFFFFF ); + } + } + return (sbits64) LIT64( 0x8000000000000000 ); + } + else if ( aExp <= 0x7E ) { + if ( aExp | aSig ) STATUS(float_exception_flags) |= float_flag_inexact; + return 0; + } + aSig64 = aSig | 0x00800000; + aSig64 <<= 40; + z = aSig64>>( - shiftCount ); + if ( (bits64) ( aSig64<<( shiftCount & 63 ) ) ) { + STATUS(float_exception_flags) |= float_flag_inexact; + } + if ( aSign ) z = - z; + return z; + +} + +/*---------------------------------------------------------------------------- +| Returns the result of converting the single-precision floating-point value +| `a' to the double-precision floating-point format. The conversion is +| performed according to the IEC/IEEE Standard for Binary Floating-Point +| Arithmetic. +*----------------------------------------------------------------------------*/ + +float64 float32_to_float64( float32 a STATUS_PARAM ) +{ + flag aSign; + int16 aExp; + bits32 aSig; + a = float32_squash_input_denormal(a STATUS_VAR); + + aSig = extractFloat32Frac( a ); + aExp = extractFloat32Exp( a ); + aSign = extractFloat32Sign( a ); + if ( aExp == 0xFF ) { + if ( aSig ) return commonNaNToFloat64( float32ToCommonNaN( a STATUS_VAR )); + return packFloat64( aSign, 0x7FF, 0 ); + } + if ( aExp == 0 ) { + if ( aSig == 0 ) return packFloat64( aSign, 0, 0 ); + normalizeFloat32Subnormal( aSig, &aExp, &aSig ); + --aExp; + } + return packFloat64( aSign, aExp + 0x380, ( (bits64) aSig )<<29 ); + +} + +#ifdef FLOATX80 + +/*---------------------------------------------------------------------------- +| Returns the result of converting the single-precision floating-point value +| `a' to the extended double-precision floating-point format. The conversion +| is performed according to the IEC/IEEE Standard for Binary Floating-Point +| Arithmetic. +*----------------------------------------------------------------------------*/ + +floatx80 float32_to_floatx80( float32 a STATUS_PARAM ) +{ + flag aSign; + int16 aExp; + bits32 aSig; + + a = float32_squash_input_denormal(a STATUS_VAR); + aSig = extractFloat32Frac( a ); + aExp = extractFloat32Exp( a ); + aSign = extractFloat32Sign( a ); + if ( aExp == 0xFF ) { + if ( aSig ) return commonNaNToFloatx80( float32ToCommonNaN( a STATUS_VAR ) ); + return packFloatx80( aSign, 0x7FFF, LIT64( 0x8000000000000000 ) ); + } + if ( aExp == 0 ) { + if ( aSig == 0 ) return packFloatx80( aSign, 0, 0 ); + normalizeFloat32Subnormal( aSig, &aExp, &aSig ); + } + aSig |= 0x00800000; + return packFloatx80( aSign, aExp + 0x3F80, ( (bits64) aSig )<<40 ); + +} + +#endif + +#ifdef FLOAT128 + +/*---------------------------------------------------------------------------- +| Returns the result of converting the single-precision floating-point value +| `a' to the double-precision floating-point format. The conversion is +| performed according to the IEC/IEEE Standard for Binary Floating-Point +| Arithmetic. +*----------------------------------------------------------------------------*/ + +float128 float32_to_float128( float32 a STATUS_PARAM ) +{ + flag aSign; + int16 aExp; + bits32 aSig; + + a = float32_squash_input_denormal(a STATUS_VAR); + aSig = extractFloat32Frac( a ); + aExp = extractFloat32Exp( a ); + aSign = extractFloat32Sign( a ); + if ( aExp == 0xFF ) { + if ( aSig ) return commonNaNToFloat128( float32ToCommonNaN( a STATUS_VAR ) ); + return packFloat128( aSign, 0x7FFF, 0, 0 ); + } + if ( aExp == 0 ) { + if ( aSig == 0 ) return packFloat128( aSign, 0, 0, 0 ); + normalizeFloat32Subnormal( aSig, &aExp, &aSig ); + --aExp; + } + return packFloat128( aSign, aExp + 0x3F80, ( (bits64) aSig )<<25, 0 ); + +} + +#endif + +/*---------------------------------------------------------------------------- +| Rounds the single-precision floating-point value `a' to an integer, and +| returns the result as a single-precision floating-point value. The +| operation is performed according to the IEC/IEEE Standard for Binary +| Floating-Point Arithmetic. +*----------------------------------------------------------------------------*/ + +float32 float32_round_to_int( float32 a STATUS_PARAM) +{ + flag aSign; + int16 aExp; + bits32 lastBitMask, roundBitsMask; + int8 roundingMode; + bits32 z; + a = float32_squash_input_denormal(a STATUS_VAR); + + aExp = extractFloat32Exp( a ); + if ( 0x96 <= aExp ) { + if ( ( aExp == 0xFF ) && extractFloat32Frac( a ) ) { + return propagateFloat32NaN( a, a STATUS_VAR ); + } + return a; + } + if ( aExp <= 0x7E ) { + if ( (bits32) ( float32_val(a)<<1 ) == 0 ) return a; + STATUS(float_exception_flags) |= float_flag_inexact; + aSign = extractFloat32Sign( a ); + switch ( STATUS(float_rounding_mode) ) { + case float_round_nearest_even: + if ( ( aExp == 0x7E ) && extractFloat32Frac( a ) ) { + return packFloat32( aSign, 0x7F, 0 ); + } + break; + case float_round_down: + return make_float32(aSign ? 0xBF800000 : 0); + case float_round_up: + return make_float32(aSign ? 0x80000000 : 0x3F800000); + } + return packFloat32( aSign, 0, 0 ); + } + lastBitMask = 1; + lastBitMask <<= 0x96 - aExp; + roundBitsMask = lastBitMask - 1; + z = float32_val(a); + roundingMode = STATUS(float_rounding_mode); + if ( roundingMode == float_round_nearest_even ) { + z += lastBitMask>>1; + if ( ( z & roundBitsMask ) == 0 ) z &= ~ lastBitMask; + } + else if ( roundingMode != float_round_to_zero ) { + if ( extractFloat32Sign( make_float32(z) ) ^ ( roundingMode == float_round_up ) ) { + z += roundBitsMask; + } + } + z &= ~ roundBitsMask; + if ( z != float32_val(a) ) STATUS(float_exception_flags) |= float_flag_inexact; + return make_float32(z); + +} + +/*---------------------------------------------------------------------------- +| Returns the result of adding the absolute values of the single-precision +| floating-point values `a' and `b'. If `zSign' is 1, the sum is negated +| before being returned. `zSign' is ignored if the result is a NaN. +| The addition is performed according to the IEC/IEEE Standard for Binary +| Floating-Point Arithmetic. +*----------------------------------------------------------------------------*/ + +static float32 addFloat32Sigs( float32 a, float32 b, flag zSign STATUS_PARAM) +{ + int16 aExp, bExp, zExp; + bits32 aSig, bSig, zSig; + int16 expDiff; + + aSig = extractFloat32Frac( a ); + aExp = extractFloat32Exp( a ); + bSig = extractFloat32Frac( b ); + bExp = extractFloat32Exp( b ); + expDiff = aExp - bExp; + aSig <<= 6; + bSig <<= 6; + if ( 0 < expDiff ) { + if ( aExp == 0xFF ) { + if ( aSig ) return propagateFloat32NaN( a, b STATUS_VAR ); + return a; + } + if ( bExp == 0 ) { + --expDiff; + } + else { + bSig |= 0x20000000; + } + shift32RightJamming( bSig, expDiff, &bSig ); + zExp = aExp; + } + else if ( expDiff < 0 ) { + if ( bExp == 0xFF ) { + if ( bSig ) return propagateFloat32NaN( a, b STATUS_VAR ); + return packFloat32( zSign, 0xFF, 0 ); + } + if ( aExp == 0 ) { + ++expDiff; + } + else { + aSig |= 0x20000000; + } + shift32RightJamming( aSig, - expDiff, &aSig ); + zExp = bExp; + } + else { + if ( aExp == 0xFF ) { + if ( aSig | bSig ) return propagateFloat32NaN( a, b STATUS_VAR ); + return a; + } + if ( aExp == 0 ) { + if ( STATUS(flush_to_zero) ) return packFloat32( zSign, 0, 0 ); + return packFloat32( zSign, 0, ( aSig + bSig )>>6 ); + } + zSig = 0x40000000 + aSig + bSig; + zExp = aExp; + goto roundAndPack; + } + aSig |= 0x20000000; + zSig = ( aSig + bSig )<<1; + --zExp; + if ( (sbits32) zSig < 0 ) { + zSig = aSig + bSig; + ++zExp; + } + roundAndPack: + return roundAndPackFloat32( zSign, zExp, zSig STATUS_VAR ); + +} + +/*---------------------------------------------------------------------------- +| Returns the result of subtracting the absolute values of the single- +| precision floating-point values `a' and `b'. If `zSign' is 1, the +| difference is negated before being returned. `zSign' is ignored if the +| result is a NaN. The subtraction is performed according to the IEC/IEEE +| Standard for Binary Floating-Point Arithmetic. +*----------------------------------------------------------------------------*/ + +static float32 subFloat32Sigs( float32 a, float32 b, flag zSign STATUS_PARAM) +{ + int16 aExp, bExp, zExp; + bits32 aSig, bSig, zSig; + int16 expDiff; + + aSig = extractFloat32Frac( a ); + aExp = extractFloat32Exp( a ); + bSig = extractFloat32Frac( b ); + bExp = extractFloat32Exp( b ); + expDiff = aExp - bExp; + aSig <<= 7; + bSig <<= 7; + if ( 0 < expDiff ) goto aExpBigger; + if ( expDiff < 0 ) goto bExpBigger; + if ( aExp == 0xFF ) { + if ( aSig | bSig ) return propagateFloat32NaN( a, b STATUS_VAR ); + float_raise( float_flag_invalid STATUS_VAR); + return float32_default_nan; + } + if ( aExp == 0 ) { + aExp = 1; + bExp = 1; + } + if ( bSig < aSig ) goto aBigger; + if ( aSig < bSig ) goto bBigger; + return packFloat32( STATUS(float_rounding_mode) == float_round_down, 0, 0 ); + bExpBigger: + if ( bExp == 0xFF ) { + if ( bSig ) return propagateFloat32NaN( a, b STATUS_VAR ); + return packFloat32( zSign ^ 1, 0xFF, 0 ); + } + if ( aExp == 0 ) { + ++expDiff; + } + else { + aSig |= 0x40000000; + } + shift32RightJamming( aSig, - expDiff, &aSig ); + bSig |= 0x40000000; + bBigger: + zSig = bSig - aSig; + zExp = bExp; + zSign ^= 1; + goto normalizeRoundAndPack; + aExpBigger: + if ( aExp == 0xFF ) { + if ( aSig ) return propagateFloat32NaN( a, b STATUS_VAR ); + return a; + } + if ( bExp == 0 ) { + --expDiff; + } + else { + bSig |= 0x40000000; + } + shift32RightJamming( bSig, expDiff, &bSig ); + aSig |= 0x40000000; + aBigger: + zSig = aSig - bSig; + zExp = aExp; + normalizeRoundAndPack: + --zExp; + return normalizeRoundAndPackFloat32( zSign, zExp, zSig STATUS_VAR ); + +} + +/*---------------------------------------------------------------------------- +| Returns the result of adding the single-precision floating-point values `a' +| and `b'. The operation is performed according to the IEC/IEEE Standard for +| Binary Floating-Point Arithmetic. +*----------------------------------------------------------------------------*/ + +float32 float32_add( float32 a, float32 b STATUS_PARAM ) +{ + flag aSign, bSign; + a = float32_squash_input_denormal(a STATUS_VAR); + b = float32_squash_input_denormal(b STATUS_VAR); + + aSign = extractFloat32Sign( a ); + bSign = extractFloat32Sign( b ); + if ( aSign == bSign ) { + return addFloat32Sigs( a, b, aSign STATUS_VAR); + } + else { + return subFloat32Sigs( a, b, aSign STATUS_VAR ); + } + +} + +/*---------------------------------------------------------------------------- +| Returns the result of subtracting the single-precision floating-point values +| `a' and `b'. The operation is performed according to the IEC/IEEE Standard +| for Binary Floating-Point Arithmetic. +*----------------------------------------------------------------------------*/ + +float32 float32_sub( float32 a, float32 b STATUS_PARAM ) +{ + flag aSign, bSign; + a = float32_squash_input_denormal(a STATUS_VAR); + b = float32_squash_input_denormal(b STATUS_VAR); + + aSign = extractFloat32Sign( a ); + bSign = extractFloat32Sign( b ); + if ( aSign == bSign ) { + return subFloat32Sigs( a, b, aSign STATUS_VAR ); + } + else { + return addFloat32Sigs( a, b, aSign STATUS_VAR ); + } + +} + +/*---------------------------------------------------------------------------- +| Returns the result of multiplying the single-precision floating-point values +| `a' and `b'. The operation is performed according to the IEC/IEEE Standard +| for Binary Floating-Point Arithmetic. +*----------------------------------------------------------------------------*/ + +float32 float32_mul( float32 a, float32 b STATUS_PARAM ) +{ + flag aSign, bSign, zSign; + int16 aExp, bExp, zExp; + bits32 aSig, bSig; + bits64 zSig64; + bits32 zSig; + + a = float32_squash_input_denormal(a STATUS_VAR); + b = float32_squash_input_denormal(b STATUS_VAR); + + aSig = extractFloat32Frac( a ); + aExp = extractFloat32Exp( a ); + aSign = extractFloat32Sign( a ); + bSig = extractFloat32Frac( b ); + bExp = extractFloat32Exp( b ); + bSign = extractFloat32Sign( b ); + zSign = aSign ^ bSign; + if ( aExp == 0xFF ) { + if ( aSig || ( ( bExp == 0xFF ) && bSig ) ) { + return propagateFloat32NaN( a, b STATUS_VAR ); + } + if ( ( bExp | bSig ) == 0 ) { + float_raise( float_flag_invalid STATUS_VAR); + return float32_default_nan; + } + return packFloat32( zSign, 0xFF, 0 ); + } + if ( bExp == 0xFF ) { + if ( bSig ) return propagateFloat32NaN( a, b STATUS_VAR ); + if ( ( aExp | aSig ) == 0 ) { + float_raise( float_flag_invalid STATUS_VAR); + return float32_default_nan; + } + return packFloat32( zSign, 0xFF, 0 ); + } + if ( aExp == 0 ) { + if ( aSig == 0 ) return packFloat32( zSign, 0, 0 ); + normalizeFloat32Subnormal( aSig, &aExp, &aSig ); + } + if ( bExp == 0 ) { + if ( bSig == 0 ) return packFloat32( zSign, 0, 0 ); + normalizeFloat32Subnormal( bSig, &bExp, &bSig ); + } + zExp = aExp + bExp - 0x7F; + aSig = ( aSig | 0x00800000 )<<7; + bSig = ( bSig | 0x00800000 )<<8; + shift64RightJamming( ( (bits64) aSig ) * bSig, 32, &zSig64 ); + zSig = zSig64; + if ( 0 <= (sbits32) ( zSig<<1 ) ) { + zSig <<= 1; + --zExp; + } + return roundAndPackFloat32( zSign, zExp, zSig STATUS_VAR ); + +} + +/*---------------------------------------------------------------------------- +| Returns the result of dividing the single-precision floating-point value `a' +| by the corresponding value `b'. The operation is performed according to the +| IEC/IEEE Standard for Binary Floating-Point Arithmetic. +*----------------------------------------------------------------------------*/ + +float32 float32_div( float32 a, float32 b STATUS_PARAM ) +{ + flag aSign, bSign, zSign; + int16 aExp, bExp, zExp; + bits32 aSig, bSig, zSig; + a = float32_squash_input_denormal(a STATUS_VAR); + b = float32_squash_input_denormal(b STATUS_VAR); + + aSig = extractFloat32Frac( a ); + aExp = extractFloat32Exp( a ); + aSign = extractFloat32Sign( a ); + bSig = extractFloat32Frac( b ); + bExp = extractFloat32Exp( b ); + bSign = extractFloat32Sign( b ); + zSign = aSign ^ bSign; + if ( aExp == 0xFF ) { + if ( aSig ) return propagateFloat32NaN( a, b STATUS_VAR ); + if ( bExp == 0xFF ) { + if ( bSig ) return propagateFloat32NaN( a, b STATUS_VAR ); + float_raise( float_flag_invalid STATUS_VAR); + return float32_default_nan; + } + return packFloat32( zSign, 0xFF, 0 ); + } + if ( bExp == 0xFF ) { + if ( bSig ) return propagateFloat32NaN( a, b STATUS_VAR ); + return packFloat32( zSign, 0, 0 ); + } + if ( bExp == 0 ) { + if ( bSig == 0 ) { + if ( ( aExp | aSig ) == 0 ) { + float_raise( float_flag_invalid STATUS_VAR); + return float32_default_nan; + } + float_raise( float_flag_divbyzero STATUS_VAR); + return packFloat32( zSign, 0xFF, 0 ); + } + normalizeFloat32Subnormal( bSig, &bExp, &bSig ); + } + if ( aExp == 0 ) { + if ( aSig == 0 ) return packFloat32( zSign, 0, 0 ); + normalizeFloat32Subnormal( aSig, &aExp, &aSig ); + } + zExp = aExp - bExp + 0x7D; + aSig = ( aSig | 0x00800000 )<<7; + bSig = ( bSig | 0x00800000 )<<8; + if ( bSig <= ( aSig + aSig ) ) { + aSig >>= 1; + ++zExp; + } + zSig = ( ( (bits64) aSig )<<32 ) / bSig; + if ( ( zSig & 0x3F ) == 0 ) { + zSig |= ( (bits64) bSig * zSig != ( (bits64) aSig )<<32 ); + } + return roundAndPackFloat32( zSign, zExp, zSig STATUS_VAR ); + +} + +/*---------------------------------------------------------------------------- +| Returns the remainder of the single-precision floating-point value `a' +| with respect to the corresponding value `b'. The operation is performed +| according to the IEC/IEEE Standard for Binary Floating-Point Arithmetic. +*----------------------------------------------------------------------------*/ + +float32 float32_rem( float32 a, float32 b STATUS_PARAM ) +{ + flag aSign, zSign; + int16 aExp, bExp, expDiff; + bits32 aSig, bSig; + bits32 q; + bits64 aSig64, bSig64, q64; + bits32 alternateASig; + sbits32 sigMean; + a = float32_squash_input_denormal(a STATUS_VAR); + b = float32_squash_input_denormal(b STATUS_VAR); + + aSig = extractFloat32Frac( a ); + aExp = extractFloat32Exp( a ); + aSign = extractFloat32Sign( a ); + bSig = extractFloat32Frac( b ); + bExp = extractFloat32Exp( b ); + if ( aExp == 0xFF ) { + if ( aSig || ( ( bExp == 0xFF ) && bSig ) ) { + return propagateFloat32NaN( a, b STATUS_VAR ); + } + float_raise( float_flag_invalid STATUS_VAR); + return float32_default_nan; + } + if ( bExp == 0xFF ) { + if ( bSig ) return propagateFloat32NaN( a, b STATUS_VAR ); + return a; + } + if ( bExp == 0 ) { + if ( bSig == 0 ) { + float_raise( float_flag_invalid STATUS_VAR); + return float32_default_nan; + } + normalizeFloat32Subnormal( bSig, &bExp, &bSig ); + } + if ( aExp == 0 ) { + if ( aSig == 0 ) return a; + normalizeFloat32Subnormal( aSig, &aExp, &aSig ); + } + expDiff = aExp - bExp; + aSig |= 0x00800000; + bSig |= 0x00800000; + if ( expDiff < 32 ) { + aSig <<= 8; + bSig <<= 8; + if ( expDiff < 0 ) { + if ( expDiff < -1 ) return a; + aSig >>= 1; + } + q = ( bSig <= aSig ); + if ( q ) aSig -= bSig; + if ( 0 < expDiff ) { + q = ( ( (bits64) aSig )<<32 ) / bSig; + q >>= 32 - expDiff; + bSig >>= 2; + aSig = ( ( aSig>>1 )<<( expDiff - 1 ) ) - bSig * q; + } + else { + aSig >>= 2; + bSig >>= 2; + } + } + else { + if ( bSig <= aSig ) aSig -= bSig; + aSig64 = ( (bits64) aSig )<<40; + bSig64 = ( (bits64) bSig )<<40; + expDiff -= 64; + while ( 0 < expDiff ) { + q64 = estimateDiv128To64( aSig64, 0, bSig64 ); + q64 = ( 2 < q64 ) ? q64 - 2 : 0; + aSig64 = - ( ( bSig * q64 )<<38 ); + expDiff -= 62; + } + expDiff += 64; + q64 = estimateDiv128To64( aSig64, 0, bSig64 ); + q64 = ( 2 < q64 ) ? q64 - 2 : 0; + q = q64>>( 64 - expDiff ); + bSig <<= 6; + aSig = ( ( aSig64>>33 )<<( expDiff - 1 ) ) - bSig * q; + } + do { + alternateASig = aSig; + ++q; + aSig -= bSig; + } while ( 0 <= (sbits32) aSig ); + sigMean = aSig + alternateASig; + if ( ( sigMean < 0 ) || ( ( sigMean == 0 ) && ( q & 1 ) ) ) { + aSig = alternateASig; + } + zSign = ( (sbits32) aSig < 0 ); + if ( zSign ) aSig = - aSig; + return normalizeRoundAndPackFloat32( aSign ^ zSign, bExp, aSig STATUS_VAR ); + +} + +/*---------------------------------------------------------------------------- +| Returns the square root of the single-precision floating-point value `a'. +| The operation is performed according to the IEC/IEEE Standard for Binary +| Floating-Point Arithmetic. +*----------------------------------------------------------------------------*/ + +float32 float32_sqrt( float32 a STATUS_PARAM ) +{ + flag aSign; + int16 aExp, zExp; + bits32 aSig, zSig; + bits64 rem, term; + a = float32_squash_input_denormal(a STATUS_VAR); + + aSig = extractFloat32Frac( a ); + aExp = extractFloat32Exp( a ); + aSign = extractFloat32Sign( a ); + if ( aExp == 0xFF ) { + if ( aSig ) return propagateFloat32NaN( a, float32_zero STATUS_VAR ); + if ( ! aSign ) return a; + float_raise( float_flag_invalid STATUS_VAR); + return float32_default_nan; + } + if ( aSign ) { + if ( ( aExp | aSig ) == 0 ) return a; + float_raise( float_flag_invalid STATUS_VAR); + return float32_default_nan; + } + if ( aExp == 0 ) { + if ( aSig == 0 ) return float32_zero; + normalizeFloat32Subnormal( aSig, &aExp, &aSig ); + } + zExp = ( ( aExp - 0x7F )>>1 ) + 0x7E; + aSig = ( aSig | 0x00800000 )<<8; + zSig = estimateSqrt32( aExp, aSig ) + 2; + if ( ( zSig & 0x7F ) <= 5 ) { + if ( zSig < 2 ) { + zSig = 0x7FFFFFFF; + goto roundAndPack; + } + aSig >>= aExp & 1; + term = ( (bits64) zSig ) * zSig; + rem = ( ( (bits64) aSig )<<32 ) - term; + while ( (sbits64) rem < 0 ) { + --zSig; + rem += ( ( (bits64) zSig )<<1 ) | 1; + } + zSig |= ( rem != 0 ); + } + shift32RightJamming( zSig, 1, &zSig ); + roundAndPack: + return roundAndPackFloat32( 0, zExp, zSig STATUS_VAR ); + +} + +/*---------------------------------------------------------------------------- +| Returns the binary exponential of the single-precision floating-point value +| `a'. The operation is performed according to the IEC/IEEE Standard for +| Binary Floating-Point Arithmetic. +| +| Uses the following identities: +| +| 1. ------------------------------------------------------------------------- +| x x*ln(2) +| 2 = e +| +| 2. ------------------------------------------------------------------------- +| 2 3 4 5 n +| x x x x x x x +| e = 1 + --- + --- + --- + --- + --- + ... + --- + ... +| 1! 2! 3! 4! 5! n! +*----------------------------------------------------------------------------*/ + +static const float64 float32_exp2_coefficients[15] = +{ + make_float64( 0x3ff0000000000000ll ), /* 1 */ + make_float64( 0x3fe0000000000000ll ), /* 2 */ + make_float64( 0x3fc5555555555555ll ), /* 3 */ + make_float64( 0x3fa5555555555555ll ), /* 4 */ + make_float64( 0x3f81111111111111ll ), /* 5 */ + make_float64( 0x3f56c16c16c16c17ll ), /* 6 */ + make_float64( 0x3f2a01a01a01a01all ), /* 7 */ + make_float64( 0x3efa01a01a01a01all ), /* 8 */ + make_float64( 0x3ec71de3a556c734ll ), /* 9 */ + make_float64( 0x3e927e4fb7789f5cll ), /* 10 */ + make_float64( 0x3e5ae64567f544e4ll ), /* 11 */ + make_float64( 0x3e21eed8eff8d898ll ), /* 12 */ + make_float64( 0x3de6124613a86d09ll ), /* 13 */ + make_float64( 0x3da93974a8c07c9dll ), /* 14 */ + make_float64( 0x3d6ae7f3e733b81fll ), /* 15 */ +}; + +float32 float32_exp2( float32 a STATUS_PARAM ) +{ + flag aSign; + int16 aExp; + bits32 aSig; + float64 r, x, xn; + int i; + a = float32_squash_input_denormal(a STATUS_VAR); + + aSig = extractFloat32Frac( a ); + aExp = extractFloat32Exp( a ); + aSign = extractFloat32Sign( a ); + + if ( aExp == 0xFF) { + if ( aSig ) return propagateFloat32NaN( a, float32_zero STATUS_VAR ); + return (aSign) ? float32_zero : a; + } + if (aExp == 0) { + if (aSig == 0) return float32_one; + } + + float_raise( float_flag_inexact STATUS_VAR); + + /* ******************************* */ + /* using float64 for approximation */ + /* ******************************* */ + x = float32_to_float64(a STATUS_VAR); + x = float64_mul(x, float64_ln2 STATUS_VAR); + + xn = x; + r = float64_one; + for (i = 0 ; i < 15 ; i++) { + float64 f; + + f = float64_mul(xn, float32_exp2_coefficients[i] STATUS_VAR); + r = float64_add(r, f STATUS_VAR); + + xn = float64_mul(xn, x STATUS_VAR); + } + + return float64_to_float32(r, status); +} + +/*---------------------------------------------------------------------------- +| Returns the binary log of the single-precision floating-point value `a'. +| The operation is performed according to the IEC/IEEE Standard for Binary +| Floating-Point Arithmetic. +*----------------------------------------------------------------------------*/ +float32 float32_log2( float32 a STATUS_PARAM ) +{ + flag aSign, zSign; + int16 aExp; + bits32 aSig, zSig, i; + + a = float32_squash_input_denormal(a STATUS_VAR); + aSig = extractFloat32Frac( a ); + aExp = extractFloat32Exp( a ); + aSign = extractFloat32Sign( a ); + + if ( aExp == 0 ) { + if ( aSig == 0 ) return packFloat32( 1, 0xFF, 0 ); + normalizeFloat32Subnormal( aSig, &aExp, &aSig ); + } + if ( aSign ) { + float_raise( float_flag_invalid STATUS_VAR); + return float32_default_nan; + } + if ( aExp == 0xFF ) { + if ( aSig ) return propagateFloat32NaN( a, float32_zero STATUS_VAR ); + return a; + } + + aExp -= 0x7F; + aSig |= 0x00800000; + zSign = aExp < 0; + zSig = aExp << 23; + + for (i = 1 << 22; i > 0; i >>= 1) { + aSig = ( (bits64)aSig * aSig ) >> 23; + if ( aSig & 0x01000000 ) { + aSig >>= 1; + zSig |= i; + } + } + + if ( zSign ) + zSig = -zSig; + + return normalizeRoundAndPackFloat32( zSign, 0x85, zSig STATUS_VAR ); +} + +/*---------------------------------------------------------------------------- +| Returns 1 if the single-precision floating-point value `a' is equal to +| the corresponding value `b', and 0 otherwise. The comparison is performed +| according to the IEC/IEEE Standard for Binary Floating-Point Arithmetic. +*----------------------------------------------------------------------------*/ + +int float32_eq( float32 a, float32 b STATUS_PARAM ) +{ + a = float32_squash_input_denormal(a STATUS_VAR); + b = float32_squash_input_denormal(b STATUS_VAR); + + if ( ( ( extractFloat32Exp( a ) == 0xFF ) && extractFloat32Frac( a ) ) + || ( ( extractFloat32Exp( b ) == 0xFF ) && extractFloat32Frac( b ) ) + ) { + if ( float32_is_signaling_nan( a ) || float32_is_signaling_nan( b ) ) { + float_raise( float_flag_invalid STATUS_VAR); + } + return 0; + } + return ( float32_val(a) == float32_val(b) ) || + ( (bits32) ( ( float32_val(a) | float32_val(b) )<<1 ) == 0 ); + +} + +/*---------------------------------------------------------------------------- +| Returns 1 if the single-precision floating-point value `a' is less than +| or equal to the corresponding value `b', and 0 otherwise. The comparison +| is performed according to the IEC/IEEE Standard for Binary Floating-Point +| Arithmetic. +*----------------------------------------------------------------------------*/ + +int float32_le( float32 a, float32 b STATUS_PARAM ) +{ + flag aSign, bSign; + bits32 av, bv; + a = float32_squash_input_denormal(a STATUS_VAR); + b = float32_squash_input_denormal(b STATUS_VAR); + + if ( ( ( extractFloat32Exp( a ) == 0xFF ) && extractFloat32Frac( a ) ) + || ( ( extractFloat32Exp( b ) == 0xFF ) && extractFloat32Frac( b ) ) + ) { + float_raise( float_flag_invalid STATUS_VAR); + return 0; + } + aSign = extractFloat32Sign( a ); + bSign = extractFloat32Sign( b ); + av = float32_val(a); + bv = float32_val(b); + if ( aSign != bSign ) return aSign || ( (bits32) ( ( av | bv )<<1 ) == 0 ); + return ( av == bv ) || ( aSign ^ ( av < bv ) ); + +} + +/*---------------------------------------------------------------------------- +| Returns 1 if the single-precision floating-point value `a' is less than +| the corresponding value `b', and 0 otherwise. The comparison is performed +| according to the IEC/IEEE Standard for Binary Floating-Point Arithmetic. +*----------------------------------------------------------------------------*/ + +int float32_lt( float32 a, float32 b STATUS_PARAM ) +{ + flag aSign, bSign; + bits32 av, bv; + a = float32_squash_input_denormal(a STATUS_VAR); + b = float32_squash_input_denormal(b STATUS_VAR); + + if ( ( ( extractFloat32Exp( a ) == 0xFF ) && extractFloat32Frac( a ) ) + || ( ( extractFloat32Exp( b ) == 0xFF ) && extractFloat32Frac( b ) ) + ) { + float_raise( float_flag_invalid STATUS_VAR); + return 0; + } + aSign = extractFloat32Sign( a ); + bSign = extractFloat32Sign( b ); + av = float32_val(a); + bv = float32_val(b); + if ( aSign != bSign ) return aSign && ( (bits32) ( ( av | bv )<<1 ) != 0 ); + return ( av != bv ) && ( aSign ^ ( av < bv ) ); + +} + +/*---------------------------------------------------------------------------- +| Returns 1 if the single-precision floating-point value `a' is equal to +| the corresponding value `b', and 0 otherwise. The invalid exception is +| raised if either operand is a NaN. Otherwise, the comparison is performed +| according to the IEC/IEEE Standard for Binary Floating-Point Arithmetic. +*----------------------------------------------------------------------------*/ + +int float32_eq_signaling( float32 a, float32 b STATUS_PARAM ) +{ + bits32 av, bv; + a = float32_squash_input_denormal(a STATUS_VAR); + b = float32_squash_input_denormal(b STATUS_VAR); + + if ( ( ( extractFloat32Exp( a ) == 0xFF ) && extractFloat32Frac( a ) ) + || ( ( extractFloat32Exp( b ) == 0xFF ) && extractFloat32Frac( b ) ) + ) { + float_raise( float_flag_invalid STATUS_VAR); + return 0; + } + av = float32_val(a); + bv = float32_val(b); + return ( av == bv ) || ( (bits32) ( ( av | bv )<<1 ) == 0 ); + +} + +/*---------------------------------------------------------------------------- +| Returns 1 if the single-precision floating-point value `a' is less than or +| equal to the corresponding value `b', and 0 otherwise. Quiet NaNs do not +| cause an exception. Otherwise, the comparison is performed according to the +| IEC/IEEE Standard for Binary Floating-Point Arithmetic. +*----------------------------------------------------------------------------*/ + +int float32_le_quiet( float32 a, float32 b STATUS_PARAM ) +{ + flag aSign, bSign; + bits32 av, bv; + a = float32_squash_input_denormal(a STATUS_VAR); + b = float32_squash_input_denormal(b STATUS_VAR); + + if ( ( ( extractFloat32Exp( a ) == 0xFF ) && extractFloat32Frac( a ) ) + || ( ( extractFloat32Exp( b ) == 0xFF ) && extractFloat32Frac( b ) ) + ) { + if ( float32_is_signaling_nan( a ) || float32_is_signaling_nan( b ) ) { + float_raise( float_flag_invalid STATUS_VAR); + } + return 0; + } + aSign = extractFloat32Sign( a ); + bSign = extractFloat32Sign( b ); + av = float32_val(a); + bv = float32_val(b); + if ( aSign != bSign ) return aSign || ( (bits32) ( ( av | bv )<<1 ) == 0 ); + return ( av == bv ) || ( aSign ^ ( av < bv ) ); + +} + +/*---------------------------------------------------------------------------- +| Returns 1 if the single-precision floating-point value `a' is less than +| the corresponding value `b', and 0 otherwise. Quiet NaNs do not cause an +| exception. Otherwise, the comparison is performed according to the IEC/IEEE +| Standard for Binary Floating-Point Arithmetic. +*----------------------------------------------------------------------------*/ + +int float32_lt_quiet( float32 a, float32 b STATUS_PARAM ) +{ + flag aSign, bSign; + bits32 av, bv; + a = float32_squash_input_denormal(a STATUS_VAR); + b = float32_squash_input_denormal(b STATUS_VAR); + + if ( ( ( extractFloat32Exp( a ) == 0xFF ) && extractFloat32Frac( a ) ) + || ( ( extractFloat32Exp( b ) == 0xFF ) && extractFloat32Frac( b ) ) + ) { + if ( float32_is_signaling_nan( a ) || float32_is_signaling_nan( b ) ) { + float_raise( float_flag_invalid STATUS_VAR); + } + return 0; + } + aSign = extractFloat32Sign( a ); + bSign = extractFloat32Sign( b ); + av = float32_val(a); + bv = float32_val(b); + if ( aSign != bSign ) return aSign && ( (bits32) ( ( av | bv )<<1 ) != 0 ); + return ( av != bv ) && ( aSign ^ ( av < bv ) ); + +} + +/*---------------------------------------------------------------------------- +| Returns the result of converting the double-precision floating-point value +| `a' to the 32-bit two's complement integer format. The conversion is +| performed according to the IEC/IEEE Standard for Binary Floating-Point +| Arithmetic---which means in particular that the conversion is rounded +| according to the current rounding mode. If `a' is a NaN, the largest +| positive integer is returned. Otherwise, if the conversion overflows, the +| largest integer with the same sign as `a' is returned. +*----------------------------------------------------------------------------*/ + +int32 float64_to_int32( float64 a STATUS_PARAM ) +{ + flag aSign; + int16 aExp, shiftCount; + bits64 aSig; + a = float64_squash_input_denormal(a STATUS_VAR); + + aSig = extractFloat64Frac( a ); + aExp = extractFloat64Exp( a ); + aSign = extractFloat64Sign( a ); + if ( ( aExp == 0x7FF ) && aSig ) aSign = 0; + if ( aExp ) aSig |= LIT64( 0x0010000000000000 ); + shiftCount = 0x42C - aExp; + if ( 0 < shiftCount ) shift64RightJamming( aSig, shiftCount, &aSig ); + return roundAndPackInt32( aSign, aSig STATUS_VAR ); + +} + +/*---------------------------------------------------------------------------- +| Returns the result of converting the double-precision floating-point value +| `a' to the 32-bit two's complement integer format. The conversion is +| performed according to the IEC/IEEE Standard for Binary Floating-Point +| Arithmetic, except that the conversion is always rounded toward zero. +| If `a' is a NaN, the largest positive integer is returned. Otherwise, if +| the conversion overflows, the largest integer with the same sign as `a' is +| returned. +*----------------------------------------------------------------------------*/ + +int32 float64_to_int32_round_to_zero( float64 a STATUS_PARAM ) +{ + flag aSign; + int16 aExp, shiftCount; + bits64 aSig, savedASig; + int32 z; + a = float64_squash_input_denormal(a STATUS_VAR); + + aSig = extractFloat64Frac( a ); + aExp = extractFloat64Exp( a ); + aSign = extractFloat64Sign( a ); + if ( 0x41E < aExp ) { + if ( ( aExp == 0x7FF ) && aSig ) aSign = 0; + goto invalid; + } + else if ( aExp < 0x3FF ) { + if ( aExp || aSig ) STATUS(float_exception_flags) |= float_flag_inexact; + return 0; + } + aSig |= LIT64( 0x0010000000000000 ); + shiftCount = 0x433 - aExp; + savedASig = aSig; + aSig >>= shiftCount; + z = aSig; + if ( aSign ) z = - z; + if ( ( z < 0 ) ^ aSign ) { + invalid: + float_raise( float_flag_invalid STATUS_VAR); + return aSign ? (sbits32) 0x80000000 : 0x7FFFFFFF; + } + if ( ( aSig<>= shiftCount; + z = aSig; + if ( aSign ) { + z = - z; + } + if ( ( (int16_t)z < 0 ) ^ aSign ) { + invalid: + float_raise( float_flag_invalid STATUS_VAR); + return aSign ? (sbits32) 0xffff8000 : 0x7FFF; + } + if ( ( aSig<>( - shiftCount ); + if ( (bits64) ( aSig<<( shiftCount & 63 ) ) ) { + STATUS(float_exception_flags) |= float_flag_inexact; + } + } + if ( aSign ) z = - z; + return z; + +} + +/*---------------------------------------------------------------------------- +| Returns the result of converting the double-precision floating-point value +| `a' to the single-precision floating-point format. The conversion is +| performed according to the IEC/IEEE Standard for Binary Floating-Point +| Arithmetic. +*----------------------------------------------------------------------------*/ + +float32 float64_to_float32( float64 a STATUS_PARAM ) +{ + flag aSign; + int16 aExp; + bits64 aSig; + bits32 zSig; + a = float64_squash_input_denormal(a STATUS_VAR); + + aSig = extractFloat64Frac( a ); + aExp = extractFloat64Exp( a ); + aSign = extractFloat64Sign( a ); + if ( aExp == 0x7FF ) { + if ( aSig ) return commonNaNToFloat32( float64ToCommonNaN( a STATUS_VAR ) ); + return packFloat32( aSign, 0xFF, 0 ); + } + shift64RightJamming( aSig, 22, &aSig ); + zSig = aSig; + if ( aExp || zSig ) { + zSig |= 0x40000000; + aExp -= 0x381; + } + return roundAndPackFloat32( aSign, aExp, zSig STATUS_VAR ); + +} + + +/*---------------------------------------------------------------------------- +| Packs the sign `zSign', exponent `zExp', and significand `zSig' into a +| half-precision floating-point value, returning the result. After being +| shifted into the proper positions, the three fields are simply added +| together to form the result. This means that any integer portion of `zSig' +| will be added into the exponent. Since a properly normalized significand +| will have an integer portion equal to 1, the `zExp' input should be 1 less +| than the desired result exponent whenever `zSig' is a complete, normalized +| significand. +*----------------------------------------------------------------------------*/ +static bits16 packFloat16(flag zSign, int16 zExp, bits16 zSig) +{ + return (((bits32)zSign) << 15) + (((bits32)zExp) << 10) + zSig; +} + +/* Half precision floats come in two formats: standard IEEE and "ARM" format. + The latter gains extra exponent range by omitting the NaN/Inf encodings. */ + +float32 float16_to_float32( bits16 a, flag ieee STATUS_PARAM ) +{ + flag aSign; + int16 aExp; + bits32 aSig; + + aSign = a >> 15; + aExp = (a >> 10) & 0x1f; + aSig = a & 0x3ff; + + if (aExp == 0x1f && ieee) { + if (aSig) { + /* Make sure correct exceptions are raised. */ + float32ToCommonNaN(a STATUS_VAR); + aSig |= 0x200; + } + return packFloat32(aSign, 0xff, aSig << 13); + } + if (aExp == 0) { + int8 shiftCount; + + if (aSig == 0) { + return packFloat32(aSign, 0, 0); + } + + shiftCount = countLeadingZeros32( aSig ) - 21; + aSig = aSig << shiftCount; + aExp = -shiftCount; + } + return packFloat32( aSign, aExp + 0x70, aSig << 13); +} + +bits16 float32_to_float16( float32 a, flag ieee STATUS_PARAM) +{ + flag aSign; + int16 aExp; + bits32 aSig; + bits32 mask; + bits32 increment; + int8 roundingMode; + a = float32_squash_input_denormal(a STATUS_VAR); + + aSig = extractFloat32Frac( a ); + aExp = extractFloat32Exp( a ); + aSign = extractFloat32Sign( a ); + if ( aExp == 0xFF ) { + if (aSig) { + /* Make sure correct exceptions are raised. */ + float32ToCommonNaN(a STATUS_VAR); + aSig |= 0x00400000; + } + return packFloat16(aSign, 0x1f, aSig >> 13); + } + if (aExp == 0 && aSign == 0) { + return packFloat16(aSign, 0, 0); + } + /* Decimal point between bits 22 and 23. */ + aSig |= 0x00800000; + aExp -= 0x7f; + if (aExp < -14) { + mask = 0x007fffff; + if (aExp < -24) { + aExp = -25; + } else { + mask >>= 24 + aExp; + } + } else { + mask = 0x00001fff; + } + if (aSig & mask) { + float_raise( float_flag_underflow STATUS_VAR ); + roundingMode = STATUS(float_rounding_mode); + switch (roundingMode) { + case float_round_nearest_even: + increment = (mask + 1) >> 1; + if ((aSig & mask) == increment) { + increment = aSig & (increment << 1); + } + break; + case float_round_up: + increment = aSign ? 0 : mask; + break; + case float_round_down: + increment = aSign ? mask : 0; + break; + default: /* round_to_zero */ + increment = 0; + break; + } + aSig += increment; + if (aSig >= 0x01000000) { + aSig >>= 1; + aExp++; + } + } else if (aExp < -14 + && STATUS(float_detect_tininess) == float_tininess_before_rounding) { + float_raise( float_flag_underflow STATUS_VAR); + } + + if (ieee) { + if (aExp > 15) { + float_raise( float_flag_overflow | float_flag_inexact STATUS_VAR); + return packFloat16(aSign, 0x1f, 0); + } + } else { + if (aExp > 16) { + float_raise( float_flag_overflow | float_flag_inexact STATUS_VAR); + return packFloat16(aSign, 0x1f, 0x3ff); + } + } + if (aExp < -24) { + return packFloat16(aSign, 0, 0); + } + if (aExp < -14) { + aSig >>= -14 - aExp; + aExp = -14; + } + return packFloat16(aSign, aExp + 14, aSig >> 13); +} + +#ifdef FLOATX80 + +/*---------------------------------------------------------------------------- +| Returns the result of converting the double-precision floating-point value +| `a' to the extended double-precision floating-point format. The conversion +| is performed according to the IEC/IEEE Standard for Binary Floating-Point +| Arithmetic. +*----------------------------------------------------------------------------*/ + +floatx80 float64_to_floatx80( float64 a STATUS_PARAM ) +{ + flag aSign; + int16 aExp; + bits64 aSig; + + a = float64_squash_input_denormal(a STATUS_VAR); + aSig = extractFloat64Frac( a ); + aExp = extractFloat64Exp( a ); + aSign = extractFloat64Sign( a ); + if ( aExp == 0x7FF ) { + if ( aSig ) return commonNaNToFloatx80( float64ToCommonNaN( a STATUS_VAR ) ); + return packFloatx80( aSign, 0x7FFF, LIT64( 0x8000000000000000 ) ); + } + if ( aExp == 0 ) { + if ( aSig == 0 ) return packFloatx80( aSign, 0, 0 ); + normalizeFloat64Subnormal( aSig, &aExp, &aSig ); + } + return + packFloatx80( + aSign, aExp + 0x3C00, ( aSig | LIT64( 0x0010000000000000 ) )<<11 ); + +} + +#endif + +#ifdef FLOAT128 + +/*---------------------------------------------------------------------------- +| Returns the result of converting the double-precision floating-point value +| `a' to the quadruple-precision floating-point format. The conversion is +| performed according to the IEC/IEEE Standard for Binary Floating-Point +| Arithmetic. +*----------------------------------------------------------------------------*/ + +float128 float64_to_float128( float64 a STATUS_PARAM ) +{ + flag aSign; + int16 aExp; + bits64 aSig, zSig0, zSig1; + + a = float64_squash_input_denormal(a STATUS_VAR); + aSig = extractFloat64Frac( a ); + aExp = extractFloat64Exp( a ); + aSign = extractFloat64Sign( a ); + if ( aExp == 0x7FF ) { + if ( aSig ) return commonNaNToFloat128( float64ToCommonNaN( a STATUS_VAR ) ); + return packFloat128( aSign, 0x7FFF, 0, 0 ); + } + if ( aExp == 0 ) { + if ( aSig == 0 ) return packFloat128( aSign, 0, 0, 0 ); + normalizeFloat64Subnormal( aSig, &aExp, &aSig ); + --aExp; + } + shift128Right( aSig, 0, 4, &zSig0, &zSig1 ); + return packFloat128( aSign, aExp + 0x3C00, zSig0, zSig1 ); + +} + +#endif + +/*---------------------------------------------------------------------------- +| Rounds the double-precision floating-point value `a' to an integer, and +| returns the result as a double-precision floating-point value. The +| operation is performed according to the IEC/IEEE Standard for Binary +| Floating-Point Arithmetic. +*----------------------------------------------------------------------------*/ + +float64 float64_round_to_int( float64 a STATUS_PARAM ) +{ + flag aSign; + int16 aExp; + bits64 lastBitMask, roundBitsMask; + int8 roundingMode; + bits64 z; + a = float64_squash_input_denormal(a STATUS_VAR); + + aExp = extractFloat64Exp( a ); + if ( 0x433 <= aExp ) { + if ( ( aExp == 0x7FF ) && extractFloat64Frac( a ) ) { + return propagateFloat64NaN( a, a STATUS_VAR ); + } + return a; + } + if ( aExp < 0x3FF ) { + if ( (bits64) ( float64_val(a)<<1 ) == 0 ) return a; + STATUS(float_exception_flags) |= float_flag_inexact; + aSign = extractFloat64Sign( a ); + switch ( STATUS(float_rounding_mode) ) { + case float_round_nearest_even: + if ( ( aExp == 0x3FE ) && extractFloat64Frac( a ) ) { + return packFloat64( aSign, 0x3FF, 0 ); + } + break; + case float_round_down: + return make_float64(aSign ? LIT64( 0xBFF0000000000000 ) : 0); + case float_round_up: + return make_float64( + aSign ? LIT64( 0x8000000000000000 ) : LIT64( 0x3FF0000000000000 )); + } + return packFloat64( aSign, 0, 0 ); + } + lastBitMask = 1; + lastBitMask <<= 0x433 - aExp; + roundBitsMask = lastBitMask - 1; + z = float64_val(a); + roundingMode = STATUS(float_rounding_mode); + if ( roundingMode == float_round_nearest_even ) { + z += lastBitMask>>1; + if ( ( z & roundBitsMask ) == 0 ) z &= ~ lastBitMask; + } + else if ( roundingMode != float_round_to_zero ) { + if ( extractFloat64Sign( make_float64(z) ) ^ ( roundingMode == float_round_up ) ) { + z += roundBitsMask; + } + } + z &= ~ roundBitsMask; + if ( z != float64_val(a) ) + STATUS(float_exception_flags) |= float_flag_inexact; + return make_float64(z); + +} + +float64 float64_trunc_to_int( float64 a STATUS_PARAM) +{ + int oldmode; + float64 res; + oldmode = STATUS(float_rounding_mode); + STATUS(float_rounding_mode) = float_round_to_zero; + res = float64_round_to_int(a STATUS_VAR); + STATUS(float_rounding_mode) = oldmode; + return res; +} + +/*---------------------------------------------------------------------------- +| Returns the result of adding the absolute values of the double-precision +| floating-point values `a' and `b'. If `zSign' is 1, the sum is negated +| before being returned. `zSign' is ignored if the result is a NaN. +| The addition is performed according to the IEC/IEEE Standard for Binary +| Floating-Point Arithmetic. +*----------------------------------------------------------------------------*/ + +static float64 addFloat64Sigs( float64 a, float64 b, flag zSign STATUS_PARAM ) +{ + int16 aExp, bExp, zExp; + bits64 aSig, bSig, zSig; + int16 expDiff; + + aSig = extractFloat64Frac( a ); + aExp = extractFloat64Exp( a ); + bSig = extractFloat64Frac( b ); + bExp = extractFloat64Exp( b ); + expDiff = aExp - bExp; + aSig <<= 9; + bSig <<= 9; + if ( 0 < expDiff ) { + if ( aExp == 0x7FF ) { + if ( aSig ) return propagateFloat64NaN( a, b STATUS_VAR ); + return a; + } + if ( bExp == 0 ) { + --expDiff; + } + else { + bSig |= LIT64( 0x2000000000000000 ); + } + shift64RightJamming( bSig, expDiff, &bSig ); + zExp = aExp; + } + else if ( expDiff < 0 ) { + if ( bExp == 0x7FF ) { + if ( bSig ) return propagateFloat64NaN( a, b STATUS_VAR ); + return packFloat64( zSign, 0x7FF, 0 ); + } + if ( aExp == 0 ) { + ++expDiff; + } + else { + aSig |= LIT64( 0x2000000000000000 ); + } + shift64RightJamming( aSig, - expDiff, &aSig ); + zExp = bExp; + } + else { + if ( aExp == 0x7FF ) { + if ( aSig | bSig ) return propagateFloat64NaN( a, b STATUS_VAR ); + return a; + } + if ( aExp == 0 ) { + if ( STATUS(flush_to_zero) ) return packFloat64( zSign, 0, 0 ); + return packFloat64( zSign, 0, ( aSig + bSig )>>9 ); + } + zSig = LIT64( 0x4000000000000000 ) + aSig + bSig; + zExp = aExp; + goto roundAndPack; + } + aSig |= LIT64( 0x2000000000000000 ); + zSig = ( aSig + bSig )<<1; + --zExp; + if ( (sbits64) zSig < 0 ) { + zSig = aSig + bSig; + ++zExp; + } + roundAndPack: + return roundAndPackFloat64( zSign, zExp, zSig STATUS_VAR ); + +} + +/*---------------------------------------------------------------------------- +| Returns the result of subtracting the absolute values of the double- +| precision floating-point values `a' and `b'. If `zSign' is 1, the +| difference is negated before being returned. `zSign' is ignored if the +| result is a NaN. The subtraction is performed according to the IEC/IEEE +| Standard for Binary Floating-Point Arithmetic. +*----------------------------------------------------------------------------*/ + +static float64 subFloat64Sigs( float64 a, float64 b, flag zSign STATUS_PARAM ) +{ + int16 aExp, bExp, zExp; + bits64 aSig, bSig, zSig; + int16 expDiff; + + aSig = extractFloat64Frac( a ); + aExp = extractFloat64Exp( a ); + bSig = extractFloat64Frac( b ); + bExp = extractFloat64Exp( b ); + expDiff = aExp - bExp; + aSig <<= 10; + bSig <<= 10; + if ( 0 < expDiff ) goto aExpBigger; + if ( expDiff < 0 ) goto bExpBigger; + if ( aExp == 0x7FF ) { + if ( aSig | bSig ) return propagateFloat64NaN( a, b STATUS_VAR ); + float_raise( float_flag_invalid STATUS_VAR); + return float64_default_nan; + } + if ( aExp == 0 ) { + aExp = 1; + bExp = 1; + } + if ( bSig < aSig ) goto aBigger; + if ( aSig < bSig ) goto bBigger; + return packFloat64( STATUS(float_rounding_mode) == float_round_down, 0, 0 ); + bExpBigger: + if ( bExp == 0x7FF ) { + if ( bSig ) return propagateFloat64NaN( a, b STATUS_VAR ); + return packFloat64( zSign ^ 1, 0x7FF, 0 ); + } + if ( aExp == 0 ) { + ++expDiff; + } + else { + aSig |= LIT64( 0x4000000000000000 ); + } + shift64RightJamming( aSig, - expDiff, &aSig ); + bSig |= LIT64( 0x4000000000000000 ); + bBigger: + zSig = bSig - aSig; + zExp = bExp; + zSign ^= 1; + goto normalizeRoundAndPack; + aExpBigger: + if ( aExp == 0x7FF ) { + if ( aSig ) return propagateFloat64NaN( a, b STATUS_VAR ); + return a; + } + if ( bExp == 0 ) { + --expDiff; + } + else { + bSig |= LIT64( 0x4000000000000000 ); + } + shift64RightJamming( bSig, expDiff, &bSig ); + aSig |= LIT64( 0x4000000000000000 ); + aBigger: + zSig = aSig - bSig; + zExp = aExp; + normalizeRoundAndPack: + --zExp; + return normalizeRoundAndPackFloat64( zSign, zExp, zSig STATUS_VAR ); + +} + +/*---------------------------------------------------------------------------- +| Returns the result of adding the double-precision floating-point values `a' +| and `b'. The operation is performed according to the IEC/IEEE Standard for +| Binary Floating-Point Arithmetic. +*----------------------------------------------------------------------------*/ + +float64 float64_add( float64 a, float64 b STATUS_PARAM ) +{ + flag aSign, bSign; + a = float64_squash_input_denormal(a STATUS_VAR); + b = float64_squash_input_denormal(b STATUS_VAR); + + aSign = extractFloat64Sign( a ); + bSign = extractFloat64Sign( b ); + if ( aSign == bSign ) { + return addFloat64Sigs( a, b, aSign STATUS_VAR ); + } + else { + return subFloat64Sigs( a, b, aSign STATUS_VAR ); + } + +} + +/*---------------------------------------------------------------------------- +| Returns the result of subtracting the double-precision floating-point values +| `a' and `b'. The operation is performed according to the IEC/IEEE Standard +| for Binary Floating-Point Arithmetic. +*----------------------------------------------------------------------------*/ + +float64 float64_sub( float64 a, float64 b STATUS_PARAM ) +{ + flag aSign, bSign; + a = float64_squash_input_denormal(a STATUS_VAR); + b = float64_squash_input_denormal(b STATUS_VAR); + + aSign = extractFloat64Sign( a ); + bSign = extractFloat64Sign( b ); + if ( aSign == bSign ) { + return subFloat64Sigs( a, b, aSign STATUS_VAR ); + } + else { + return addFloat64Sigs( a, b, aSign STATUS_VAR ); + } + +} + +/*---------------------------------------------------------------------------- +| Returns the result of multiplying the double-precision floating-point values +| `a' and `b'. The operation is performed according to the IEC/IEEE Standard +| for Binary Floating-Point Arithmetic. +*----------------------------------------------------------------------------*/ + +float64 float64_mul( float64 a, float64 b STATUS_PARAM ) +{ + flag aSign, bSign, zSign; + int16 aExp, bExp, zExp; + bits64 aSig, bSig, zSig0, zSig1; + + a = float64_squash_input_denormal(a STATUS_VAR); + b = float64_squash_input_denormal(b STATUS_VAR); + + aSig = extractFloat64Frac( a ); + aExp = extractFloat64Exp( a ); + aSign = extractFloat64Sign( a ); + bSig = extractFloat64Frac( b ); + bExp = extractFloat64Exp( b ); + bSign = extractFloat64Sign( b ); + zSign = aSign ^ bSign; + if ( aExp == 0x7FF ) { + if ( aSig || ( ( bExp == 0x7FF ) && bSig ) ) { + return propagateFloat64NaN( a, b STATUS_VAR ); + } + if ( ( bExp | bSig ) == 0 ) { + float_raise( float_flag_invalid STATUS_VAR); + return float64_default_nan; + } + return packFloat64( zSign, 0x7FF, 0 ); + } + if ( bExp == 0x7FF ) { + if ( bSig ) return propagateFloat64NaN( a, b STATUS_VAR ); + if ( ( aExp | aSig ) == 0 ) { + float_raise( float_flag_invalid STATUS_VAR); + return float64_default_nan; + } + return packFloat64( zSign, 0x7FF, 0 ); + } + if ( aExp == 0 ) { + if ( aSig == 0 ) return packFloat64( zSign, 0, 0 ); + normalizeFloat64Subnormal( aSig, &aExp, &aSig ); + } + if ( bExp == 0 ) { + if ( bSig == 0 ) return packFloat64( zSign, 0, 0 ); + normalizeFloat64Subnormal( bSig, &bExp, &bSig ); + } + zExp = aExp + bExp - 0x3FF; + aSig = ( aSig | LIT64( 0x0010000000000000 ) )<<10; + bSig = ( bSig | LIT64( 0x0010000000000000 ) )<<11; + mul64To128( aSig, bSig, &zSig0, &zSig1 ); + zSig0 |= ( zSig1 != 0 ); + if ( 0 <= (sbits64) ( zSig0<<1 ) ) { + zSig0 <<= 1; + --zExp; + } + return roundAndPackFloat64( zSign, zExp, zSig0 STATUS_VAR ); + +} + +/*---------------------------------------------------------------------------- +| Returns the result of dividing the double-precision floating-point value `a' +| by the corresponding value `b'. The operation is performed according to +| the IEC/IEEE Standard for Binary Floating-Point Arithmetic. +*----------------------------------------------------------------------------*/ + +float64 float64_div( float64 a, float64 b STATUS_PARAM ) +{ + flag aSign, bSign, zSign; + int16 aExp, bExp, zExp; + bits64 aSig, bSig, zSig; + bits64 rem0, rem1; + bits64 term0, term1; + a = float64_squash_input_denormal(a STATUS_VAR); + b = float64_squash_input_denormal(b STATUS_VAR); + + aSig = extractFloat64Frac( a ); + aExp = extractFloat64Exp( a ); + aSign = extractFloat64Sign( a ); + bSig = extractFloat64Frac( b ); + bExp = extractFloat64Exp( b ); + bSign = extractFloat64Sign( b ); + zSign = aSign ^ bSign; + if ( aExp == 0x7FF ) { + if ( aSig ) return propagateFloat64NaN( a, b STATUS_VAR ); + if ( bExp == 0x7FF ) { + if ( bSig ) return propagateFloat64NaN( a, b STATUS_VAR ); + float_raise( float_flag_invalid STATUS_VAR); + return float64_default_nan; + } + return packFloat64( zSign, 0x7FF, 0 ); + } + if ( bExp == 0x7FF ) { + if ( bSig ) return propagateFloat64NaN( a, b STATUS_VAR ); + return packFloat64( zSign, 0, 0 ); + } + if ( bExp == 0 ) { + if ( bSig == 0 ) { + if ( ( aExp | aSig ) == 0 ) { + float_raise( float_flag_invalid STATUS_VAR); + return float64_default_nan; + } + float_raise( float_flag_divbyzero STATUS_VAR); + return packFloat64( zSign, 0x7FF, 0 ); + } + normalizeFloat64Subnormal( bSig, &bExp, &bSig ); + } + if ( aExp == 0 ) { + if ( aSig == 0 ) return packFloat64( zSign, 0, 0 ); + normalizeFloat64Subnormal( aSig, &aExp, &aSig ); + } + zExp = aExp - bExp + 0x3FD; + aSig = ( aSig | LIT64( 0x0010000000000000 ) )<<10; + bSig = ( bSig | LIT64( 0x0010000000000000 ) )<<11; + if ( bSig <= ( aSig + aSig ) ) { + aSig >>= 1; + ++zExp; + } + zSig = estimateDiv128To64( aSig, 0, bSig ); + if ( ( zSig & 0x1FF ) <= 2 ) { + mul64To128( bSig, zSig, &term0, &term1 ); + sub128( aSig, 0, term0, term1, &rem0, &rem1 ); + while ( (sbits64) rem0 < 0 ) { + --zSig; + add128( rem0, rem1, 0, bSig, &rem0, &rem1 ); + } + zSig |= ( rem1 != 0 ); + } + return roundAndPackFloat64( zSign, zExp, zSig STATUS_VAR ); + +} + +/*---------------------------------------------------------------------------- +| Returns the remainder of the double-precision floating-point value `a' +| with respect to the corresponding value `b'. The operation is performed +| according to the IEC/IEEE Standard for Binary Floating-Point Arithmetic. +*----------------------------------------------------------------------------*/ + +float64 float64_rem( float64 a, float64 b STATUS_PARAM ) +{ + flag aSign, zSign; + int16 aExp, bExp, expDiff; + bits64 aSig, bSig; + bits64 q, alternateASig; + sbits64 sigMean; + + a = float64_squash_input_denormal(a STATUS_VAR); + b = float64_squash_input_denormal(b STATUS_VAR); + aSig = extractFloat64Frac( a ); + aExp = extractFloat64Exp( a ); + aSign = extractFloat64Sign( a ); + bSig = extractFloat64Frac( b ); + bExp = extractFloat64Exp( b ); + if ( aExp == 0x7FF ) { + if ( aSig || ( ( bExp == 0x7FF ) && bSig ) ) { + return propagateFloat64NaN( a, b STATUS_VAR ); + } + float_raise( float_flag_invalid STATUS_VAR); + return float64_default_nan; + } + if ( bExp == 0x7FF ) { + if ( bSig ) return propagateFloat64NaN( a, b STATUS_VAR ); + return a; + } + if ( bExp == 0 ) { + if ( bSig == 0 ) { + float_raise( float_flag_invalid STATUS_VAR); + return float64_default_nan; + } + normalizeFloat64Subnormal( bSig, &bExp, &bSig ); + } + if ( aExp == 0 ) { + if ( aSig == 0 ) return a; + normalizeFloat64Subnormal( aSig, &aExp, &aSig ); + } + expDiff = aExp - bExp; + aSig = ( aSig | LIT64( 0x0010000000000000 ) )<<11; + bSig = ( bSig | LIT64( 0x0010000000000000 ) )<<11; + if ( expDiff < 0 ) { + if ( expDiff < -1 ) return a; + aSig >>= 1; + } + q = ( bSig <= aSig ); + if ( q ) aSig -= bSig; + expDiff -= 64; + while ( 0 < expDiff ) { + q = estimateDiv128To64( aSig, 0, bSig ); + q = ( 2 < q ) ? q - 2 : 0; + aSig = - ( ( bSig>>2 ) * q ); + expDiff -= 62; + } + expDiff += 64; + if ( 0 < expDiff ) { + q = estimateDiv128To64( aSig, 0, bSig ); + q = ( 2 < q ) ? q - 2 : 0; + q >>= 64 - expDiff; + bSig >>= 2; + aSig = ( ( aSig>>1 )<<( expDiff - 1 ) ) - bSig * q; + } + else { + aSig >>= 2; + bSig >>= 2; + } + do { + alternateASig = aSig; + ++q; + aSig -= bSig; + } while ( 0 <= (sbits64) aSig ); + sigMean = aSig + alternateASig; + if ( ( sigMean < 0 ) || ( ( sigMean == 0 ) && ( q & 1 ) ) ) { + aSig = alternateASig; + } + zSign = ( (sbits64) aSig < 0 ); + if ( zSign ) aSig = - aSig; + return normalizeRoundAndPackFloat64( aSign ^ zSign, bExp, aSig STATUS_VAR ); + +} + +/*---------------------------------------------------------------------------- +| Returns the square root of the double-precision floating-point value `a'. +| The operation is performed according to the IEC/IEEE Standard for Binary +| Floating-Point Arithmetic. +*----------------------------------------------------------------------------*/ + +float64 float64_sqrt( float64 a STATUS_PARAM ) +{ + flag aSign; + int16 aExp, zExp; + bits64 aSig, zSig, doubleZSig; + bits64 rem0, rem1, term0, term1; + a = float64_squash_input_denormal(a STATUS_VAR); + + aSig = extractFloat64Frac( a ); + aExp = extractFloat64Exp( a ); + aSign = extractFloat64Sign( a ); + if ( aExp == 0x7FF ) { + if ( aSig ) return propagateFloat64NaN( a, a STATUS_VAR ); + if ( ! aSign ) return a; + float_raise( float_flag_invalid STATUS_VAR); + return float64_default_nan; + } + if ( aSign ) { + if ( ( aExp | aSig ) == 0 ) return a; + float_raise( float_flag_invalid STATUS_VAR); + return float64_default_nan; + } + if ( aExp == 0 ) { + if ( aSig == 0 ) return float64_zero; + normalizeFloat64Subnormal( aSig, &aExp, &aSig ); + } + zExp = ( ( aExp - 0x3FF )>>1 ) + 0x3FE; + aSig |= LIT64( 0x0010000000000000 ); + zSig = estimateSqrt32( aExp, aSig>>21 ); + aSig <<= 9 - ( aExp & 1 ); + zSig = estimateDiv128To64( aSig, 0, zSig<<32 ) + ( zSig<<30 ); + if ( ( zSig & 0x1FF ) <= 5 ) { + doubleZSig = zSig<<1; + mul64To128( zSig, zSig, &term0, &term1 ); + sub128( aSig, 0, term0, term1, &rem0, &rem1 ); + while ( (sbits64) rem0 < 0 ) { + --zSig; + doubleZSig -= 2; + add128( rem0, rem1, zSig>>63, doubleZSig | 1, &rem0, &rem1 ); + } + zSig |= ( ( rem0 | rem1 ) != 0 ); + } + return roundAndPackFloat64( 0, zExp, zSig STATUS_VAR ); + +} + +/*---------------------------------------------------------------------------- +| Returns the binary log of the double-precision floating-point value `a'. +| The operation is performed according to the IEC/IEEE Standard for Binary +| Floating-Point Arithmetic. +*----------------------------------------------------------------------------*/ +float64 float64_log2( float64 a STATUS_PARAM ) +{ + flag aSign, zSign; + int16 aExp; + bits64 aSig, aSig0, aSig1, zSig, i; + a = float64_squash_input_denormal(a STATUS_VAR); + + aSig = extractFloat64Frac( a ); + aExp = extractFloat64Exp( a ); + aSign = extractFloat64Sign( a ); + + if ( aExp == 0 ) { + if ( aSig == 0 ) return packFloat64( 1, 0x7FF, 0 ); + normalizeFloat64Subnormal( aSig, &aExp, &aSig ); + } + if ( aSign ) { + float_raise( float_flag_invalid STATUS_VAR); + return float64_default_nan; + } + if ( aExp == 0x7FF ) { + if ( aSig ) return propagateFloat64NaN( a, float64_zero STATUS_VAR ); + return a; + } + + aExp -= 0x3FF; + aSig |= LIT64( 0x0010000000000000 ); + zSign = aExp < 0; + zSig = (bits64)aExp << 52; + for (i = 1LL << 51; i > 0; i >>= 1) { + mul64To128( aSig, aSig, &aSig0, &aSig1 ); + aSig = ( aSig0 << 12 ) | ( aSig1 >> 52 ); + if ( aSig & LIT64( 0x0020000000000000 ) ) { + aSig >>= 1; + zSig |= i; + } + } + + if ( zSign ) + zSig = -zSig; + return normalizeRoundAndPackFloat64( zSign, 0x408, zSig STATUS_VAR ); +} + +/*---------------------------------------------------------------------------- +| Returns 1 if the double-precision floating-point value `a' is equal to the +| corresponding value `b', and 0 otherwise. The comparison is performed +| according to the IEC/IEEE Standard for Binary Floating-Point Arithmetic. +*----------------------------------------------------------------------------*/ + +int float64_eq( float64 a, float64 b STATUS_PARAM ) +{ + bits64 av, bv; + a = float64_squash_input_denormal(a STATUS_VAR); + b = float64_squash_input_denormal(b STATUS_VAR); + + if ( ( ( extractFloat64Exp( a ) == 0x7FF ) && extractFloat64Frac( a ) ) + || ( ( extractFloat64Exp( b ) == 0x7FF ) && extractFloat64Frac( b ) ) + ) { + if ( float64_is_signaling_nan( a ) || float64_is_signaling_nan( b ) ) { + float_raise( float_flag_invalid STATUS_VAR); + } + return 0; + } + av = float64_val(a); + bv = float64_val(b); + return ( av == bv ) || ( (bits64) ( ( av | bv )<<1 ) == 0 ); + +} + +/*---------------------------------------------------------------------------- +| Returns 1 if the double-precision floating-point value `a' is less than or +| equal to the corresponding value `b', and 0 otherwise. The comparison is +| performed according to the IEC/IEEE Standard for Binary Floating-Point +| Arithmetic. +*----------------------------------------------------------------------------*/ + +int float64_le( float64 a, float64 b STATUS_PARAM ) +{ + flag aSign, bSign; + bits64 av, bv; + a = float64_squash_input_denormal(a STATUS_VAR); + b = float64_squash_input_denormal(b STATUS_VAR); + + if ( ( ( extractFloat64Exp( a ) == 0x7FF ) && extractFloat64Frac( a ) ) + || ( ( extractFloat64Exp( b ) == 0x7FF ) && extractFloat64Frac( b ) ) + ) { + float_raise( float_flag_invalid STATUS_VAR); + return 0; + } + aSign = extractFloat64Sign( a ); + bSign = extractFloat64Sign( b ); + av = float64_val(a); + bv = float64_val(b); + if ( aSign != bSign ) return aSign || ( (bits64) ( ( av | bv )<<1 ) == 0 ); + return ( av == bv ) || ( aSign ^ ( av < bv ) ); + +} + +/*---------------------------------------------------------------------------- +| Returns 1 if the double-precision floating-point value `a' is less than +| the corresponding value `b', and 0 otherwise. The comparison is performed +| according to the IEC/IEEE Standard for Binary Floating-Point Arithmetic. +*----------------------------------------------------------------------------*/ + +int float64_lt( float64 a, float64 b STATUS_PARAM ) +{ + flag aSign, bSign; + bits64 av, bv; + + a = float64_squash_input_denormal(a STATUS_VAR); + b = float64_squash_input_denormal(b STATUS_VAR); + if ( ( ( extractFloat64Exp( a ) == 0x7FF ) && extractFloat64Frac( a ) ) + || ( ( extractFloat64Exp( b ) == 0x7FF ) && extractFloat64Frac( b ) ) + ) { + float_raise( float_flag_invalid STATUS_VAR); + return 0; + } + aSign = extractFloat64Sign( a ); + bSign = extractFloat64Sign( b ); + av = float64_val(a); + bv = float64_val(b); + if ( aSign != bSign ) return aSign && ( (bits64) ( ( av | bv )<<1 ) != 0 ); + return ( av != bv ) && ( aSign ^ ( av < bv ) ); + +} + +/*---------------------------------------------------------------------------- +| Returns 1 if the double-precision floating-point value `a' is equal to the +| corresponding value `b', and 0 otherwise. The invalid exception is raised +| if either operand is a NaN. Otherwise, the comparison is performed +| according to the IEC/IEEE Standard for Binary Floating-Point Arithmetic. +*----------------------------------------------------------------------------*/ + +int float64_eq_signaling( float64 a, float64 b STATUS_PARAM ) +{ + bits64 av, bv; + a = float64_squash_input_denormal(a STATUS_VAR); + b = float64_squash_input_denormal(b STATUS_VAR); + + if ( ( ( extractFloat64Exp( a ) == 0x7FF ) && extractFloat64Frac( a ) ) + || ( ( extractFloat64Exp( b ) == 0x7FF ) && extractFloat64Frac( b ) ) + ) { + float_raise( float_flag_invalid STATUS_VAR); + return 0; + } + av = float64_val(a); + bv = float64_val(b); + return ( av == bv ) || ( (bits64) ( ( av | bv )<<1 ) == 0 ); + +} + +/*---------------------------------------------------------------------------- +| Returns 1 if the double-precision floating-point value `a' is less than or +| equal to the corresponding value `b', and 0 otherwise. Quiet NaNs do not +| cause an exception. Otherwise, the comparison is performed according to the +| IEC/IEEE Standard for Binary Floating-Point Arithmetic. +*----------------------------------------------------------------------------*/ + +int float64_le_quiet( float64 a, float64 b STATUS_PARAM ) +{ + flag aSign, bSign; + bits64 av, bv; + a = float64_squash_input_denormal(a STATUS_VAR); + b = float64_squash_input_denormal(b STATUS_VAR); + + if ( ( ( extractFloat64Exp( a ) == 0x7FF ) && extractFloat64Frac( a ) ) + || ( ( extractFloat64Exp( b ) == 0x7FF ) && extractFloat64Frac( b ) ) + ) { + if ( float64_is_signaling_nan( a ) || float64_is_signaling_nan( b ) ) { + float_raise( float_flag_invalid STATUS_VAR); + } + return 0; + } + aSign = extractFloat64Sign( a ); + bSign = extractFloat64Sign( b ); + av = float64_val(a); + bv = float64_val(b); + if ( aSign != bSign ) return aSign || ( (bits64) ( ( av | bv )<<1 ) == 0 ); + return ( av == bv ) || ( aSign ^ ( av < bv ) ); + +} + +/*---------------------------------------------------------------------------- +| Returns 1 if the double-precision floating-point value `a' is less than +| the corresponding value `b', and 0 otherwise. Quiet NaNs do not cause an +| exception. Otherwise, the comparison is performed according to the IEC/IEEE +| Standard for Binary Floating-Point Arithmetic. +*----------------------------------------------------------------------------*/ + +int float64_lt_quiet( float64 a, float64 b STATUS_PARAM ) +{ + flag aSign, bSign; + bits64 av, bv; + a = float64_squash_input_denormal(a STATUS_VAR); + b = float64_squash_input_denormal(b STATUS_VAR); + + if ( ( ( extractFloat64Exp( a ) == 0x7FF ) && extractFloat64Frac( a ) ) + || ( ( extractFloat64Exp( b ) == 0x7FF ) && extractFloat64Frac( b ) ) + ) { + if ( float64_is_signaling_nan( a ) || float64_is_signaling_nan( b ) ) { + float_raise( float_flag_invalid STATUS_VAR); + } + return 0; + } + aSign = extractFloat64Sign( a ); + bSign = extractFloat64Sign( b ); + av = float64_val(a); + bv = float64_val(b); + if ( aSign != bSign ) return aSign && ( (bits64) ( ( av | bv )<<1 ) != 0 ); + return ( av != bv ) && ( aSign ^ ( av < bv ) ); + +} + +#ifdef FLOATX80 + +/*---------------------------------------------------------------------------- +| Returns the result of converting the extended double-precision floating- +| point value `a' to the 32-bit two's complement integer format. The +| conversion is performed according to the IEC/IEEE Standard for Binary +| Floating-Point Arithmetic---which means in particular that the conversion +| is rounded according to the current rounding mode. If `a' is a NaN, the +| largest positive integer is returned. Otherwise, if the conversion +| overflows, the largest integer with the same sign as `a' is returned. +*----------------------------------------------------------------------------*/ + +int32 floatx80_to_int32( floatx80 a STATUS_PARAM ) +{ + flag aSign; + int32 aExp, shiftCount; + bits64 aSig; + + aSig = extractFloatx80Frac( a ); + aExp = extractFloatx80Exp( a ); + aSign = extractFloatx80Sign( a ); + if ( ( aExp == 0x7FFF ) && (bits64) ( aSig<<1 ) ) aSign = 0; + shiftCount = 0x4037 - aExp; + if ( shiftCount <= 0 ) shiftCount = 1; + shift64RightJamming( aSig, shiftCount, &aSig ); + return roundAndPackInt32( aSign, aSig STATUS_VAR ); + +} + +/*---------------------------------------------------------------------------- +| Returns the result of converting the extended double-precision floating- +| point value `a' to the 32-bit two's complement integer format. The +| conversion is performed according to the IEC/IEEE Standard for Binary +| Floating-Point Arithmetic, except that the conversion is always rounded +| toward zero. If `a' is a NaN, the largest positive integer is returned. +| Otherwise, if the conversion overflows, the largest integer with the same +| sign as `a' is returned. +*----------------------------------------------------------------------------*/ + +int32 floatx80_to_int32_round_to_zero( floatx80 a STATUS_PARAM ) +{ + flag aSign; + int32 aExp, shiftCount; + bits64 aSig, savedASig; + int32 z; + + aSig = extractFloatx80Frac( a ); + aExp = extractFloatx80Exp( a ); + aSign = extractFloatx80Sign( a ); + if ( 0x401E < aExp ) { + if ( ( aExp == 0x7FFF ) && (bits64) ( aSig<<1 ) ) aSign = 0; + goto invalid; + } + else if ( aExp < 0x3FFF ) { + if ( aExp || aSig ) STATUS(float_exception_flags) |= float_flag_inexact; + return 0; + } + shiftCount = 0x403E - aExp; + savedASig = aSig; + aSig >>= shiftCount; + z = aSig; + if ( aSign ) z = - z; + if ( ( z < 0 ) ^ aSign ) { + invalid: + float_raise( float_flag_invalid STATUS_VAR); + return aSign ? (sbits32) 0x80000000 : 0x7FFFFFFF; + } + if ( ( aSig<>( - shiftCount ); + if ( (bits64) ( aSig<<( shiftCount & 63 ) ) ) { + STATUS(float_exception_flags) |= float_flag_inexact; + } + if ( aSign ) z = - z; + return z; + +} + +/*---------------------------------------------------------------------------- +| Returns the result of converting the extended double-precision floating- +| point value `a' to the single-precision floating-point format. The +| conversion is performed according to the IEC/IEEE Standard for Binary +| Floating-Point Arithmetic. +*----------------------------------------------------------------------------*/ + +float32 floatx80_to_float32( floatx80 a STATUS_PARAM ) +{ + flag aSign; + int32 aExp; + bits64 aSig; + + aSig = extractFloatx80Frac( a ); + aExp = extractFloatx80Exp( a ); + aSign = extractFloatx80Sign( a ); + if ( aExp == 0x7FFF ) { + if ( (bits64) ( aSig<<1 ) ) { + return commonNaNToFloat32( floatx80ToCommonNaN( a STATUS_VAR ) ); + } + return packFloat32( aSign, 0xFF, 0 ); + } + shift64RightJamming( aSig, 33, &aSig ); + if ( aExp || aSig ) aExp -= 0x3F81; + return roundAndPackFloat32( aSign, aExp, aSig STATUS_VAR ); + +} + +/*---------------------------------------------------------------------------- +| Returns the result of converting the extended double-precision floating- +| point value `a' to the double-precision floating-point format. The +| conversion is performed according to the IEC/IEEE Standard for Binary +| Floating-Point Arithmetic. +*----------------------------------------------------------------------------*/ + +float64 floatx80_to_float64( floatx80 a STATUS_PARAM ) +{ + flag aSign; + int32 aExp; + bits64 aSig, zSig; + + aSig = extractFloatx80Frac( a ); + aExp = extractFloatx80Exp( a ); + aSign = extractFloatx80Sign( a ); + if ( aExp == 0x7FFF ) { + if ( (bits64) ( aSig<<1 ) ) { + return commonNaNToFloat64( floatx80ToCommonNaN( a STATUS_VAR ) ); + } + return packFloat64( aSign, 0x7FF, 0 ); + } + shift64RightJamming( aSig, 1, &zSig ); + if ( aExp || aSig ) aExp -= 0x3C01; + return roundAndPackFloat64( aSign, aExp, zSig STATUS_VAR ); + +} + +#ifdef FLOAT128 + +/*---------------------------------------------------------------------------- +| Returns the result of converting the extended double-precision floating- +| point value `a' to the quadruple-precision floating-point format. The +| conversion is performed according to the IEC/IEEE Standard for Binary +| Floating-Point Arithmetic. +*----------------------------------------------------------------------------*/ + +float128 floatx80_to_float128( floatx80 a STATUS_PARAM ) +{ + flag aSign; + int16 aExp; + bits64 aSig, zSig0, zSig1; + + aSig = extractFloatx80Frac( a ); + aExp = extractFloatx80Exp( a ); + aSign = extractFloatx80Sign( a ); + if ( ( aExp == 0x7FFF ) && (bits64) ( aSig<<1 ) ) { + return commonNaNToFloat128( floatx80ToCommonNaN( a STATUS_VAR ) ); + } + shift128Right( aSig<<1, 0, 16, &zSig0, &zSig1 ); + return packFloat128( aSign, aExp, zSig0, zSig1 ); + +} + +#endif + +/*---------------------------------------------------------------------------- +| Rounds the extended double-precision floating-point value `a' to an integer, +| and returns the result as an extended quadruple-precision floating-point +| value. The operation is performed according to the IEC/IEEE Standard for +| Binary Floating-Point Arithmetic. +*----------------------------------------------------------------------------*/ + +floatx80 floatx80_round_to_int( floatx80 a STATUS_PARAM ) +{ + flag aSign; + int32 aExp; + bits64 lastBitMask, roundBitsMask; + int8 roundingMode; + floatx80 z; + + aExp = extractFloatx80Exp( a ); + if ( 0x403E <= aExp ) { + if ( ( aExp == 0x7FFF ) && (bits64) ( extractFloatx80Frac( a )<<1 ) ) { + return propagateFloatx80NaN( a, a STATUS_VAR ); + } + return a; + } + if ( aExp < 0x3FFF ) { + if ( ( aExp == 0 ) + && ( (bits64) ( extractFloatx80Frac( a )<<1 ) == 0 ) ) { + return a; + } + STATUS(float_exception_flags) |= float_flag_inexact; + aSign = extractFloatx80Sign( a ); + switch ( STATUS(float_rounding_mode) ) { + case float_round_nearest_even: + if ( ( aExp == 0x3FFE ) && (bits64) ( extractFloatx80Frac( a )<<1 ) + ) { + return + packFloatx80( aSign, 0x3FFF, LIT64( 0x8000000000000000 ) ); + } + break; + case float_round_down: + return + aSign ? + packFloatx80( 1, 0x3FFF, LIT64( 0x8000000000000000 ) ) + : packFloatx80( 0, 0, 0 ); + case float_round_up: + return + aSign ? packFloatx80( 1, 0, 0 ) + : packFloatx80( 0, 0x3FFF, LIT64( 0x8000000000000000 ) ); + } + return packFloatx80( aSign, 0, 0 ); + } + lastBitMask = 1; + lastBitMask <<= 0x403E - aExp; + roundBitsMask = lastBitMask - 1; + z = a; + roundingMode = STATUS(float_rounding_mode); + if ( roundingMode == float_round_nearest_even ) { + z.low += lastBitMask>>1; + if ( ( z.low & roundBitsMask ) == 0 ) z.low &= ~ lastBitMask; + } + else if ( roundingMode != float_round_to_zero ) { + if ( extractFloatx80Sign( z ) ^ ( roundingMode == float_round_up ) ) { + z.low += roundBitsMask; + } + } + z.low &= ~ roundBitsMask; + if ( z.low == 0 ) { + ++z.high; + z.low = LIT64( 0x8000000000000000 ); + } + if ( z.low != a.low ) STATUS(float_exception_flags) |= float_flag_inexact; + return z; + +} + +/*---------------------------------------------------------------------------- +| Returns the result of adding the absolute values of the extended double- +| precision floating-point values `a' and `b'. If `zSign' is 1, the sum is +| negated before being returned. `zSign' is ignored if the result is a NaN. +| The addition is performed according to the IEC/IEEE Standard for Binary +| Floating-Point Arithmetic. +*----------------------------------------------------------------------------*/ + +static floatx80 addFloatx80Sigs( floatx80 a, floatx80 b, flag zSign STATUS_PARAM) +{ + int32 aExp, bExp, zExp; + bits64 aSig, bSig, zSig0, zSig1; + int32 expDiff; + + aSig = extractFloatx80Frac( a ); + aExp = extractFloatx80Exp( a ); + bSig = extractFloatx80Frac( b ); + bExp = extractFloatx80Exp( b ); + expDiff = aExp - bExp; + if ( 0 < expDiff ) { + if ( aExp == 0x7FFF ) { + if ( (bits64) ( aSig<<1 ) ) return propagateFloatx80NaN( a, b STATUS_VAR ); + return a; + } + if ( bExp == 0 ) --expDiff; + shift64ExtraRightJamming( bSig, 0, expDiff, &bSig, &zSig1 ); + zExp = aExp; + } + else if ( expDiff < 0 ) { + if ( bExp == 0x7FFF ) { + if ( (bits64) ( bSig<<1 ) ) return propagateFloatx80NaN( a, b STATUS_VAR ); + return packFloatx80( zSign, 0x7FFF, LIT64( 0x8000000000000000 ) ); + } + if ( aExp == 0 ) ++expDiff; + shift64ExtraRightJamming( aSig, 0, - expDiff, &aSig, &zSig1 ); + zExp = bExp; + } + else { + if ( aExp == 0x7FFF ) { + if ( (bits64) ( ( aSig | bSig )<<1 ) ) { + return propagateFloatx80NaN( a, b STATUS_VAR ); + } + return a; + } + zSig1 = 0; + zSig0 = aSig + bSig; + if ( aExp == 0 ) { + normalizeFloatx80Subnormal( zSig0, &zExp, &zSig0 ); + goto roundAndPack; + } + zExp = aExp; + goto shiftRight1; + } + zSig0 = aSig + bSig; + if ( (sbits64) zSig0 < 0 ) goto roundAndPack; + shiftRight1: + shift64ExtraRightJamming( zSig0, zSig1, 1, &zSig0, &zSig1 ); + zSig0 |= LIT64( 0x8000000000000000 ); + ++zExp; + roundAndPack: + return + roundAndPackFloatx80( + STATUS(floatx80_rounding_precision), zSign, zExp, zSig0, zSig1 STATUS_VAR ); + +} + +/*---------------------------------------------------------------------------- +| Returns the result of subtracting the absolute values of the extended +| double-precision floating-point values `a' and `b'. If `zSign' is 1, the +| difference is negated before being returned. `zSign' is ignored if the +| result is a NaN. The subtraction is performed according to the IEC/IEEE +| Standard for Binary Floating-Point Arithmetic. +*----------------------------------------------------------------------------*/ + +static floatx80 subFloatx80Sigs( floatx80 a, floatx80 b, flag zSign STATUS_PARAM ) +{ + int32 aExp, bExp, zExp; + bits64 aSig, bSig, zSig0, zSig1; + int32 expDiff; + floatx80 z; + + aSig = extractFloatx80Frac( a ); + aExp = extractFloatx80Exp( a ); + bSig = extractFloatx80Frac( b ); + bExp = extractFloatx80Exp( b ); + expDiff = aExp - bExp; + if ( 0 < expDiff ) goto aExpBigger; + if ( expDiff < 0 ) goto bExpBigger; + if ( aExp == 0x7FFF ) { + if ( (bits64) ( ( aSig | bSig )<<1 ) ) { + return propagateFloatx80NaN( a, b STATUS_VAR ); + } + float_raise( float_flag_invalid STATUS_VAR); + z.low = floatx80_default_nan_low; + z.high = floatx80_default_nan_high; + return z; + } + if ( aExp == 0 ) { + aExp = 1; + bExp = 1; + } + zSig1 = 0; + if ( bSig < aSig ) goto aBigger; + if ( aSig < bSig ) goto bBigger; + return packFloatx80( STATUS(float_rounding_mode) == float_round_down, 0, 0 ); + bExpBigger: + if ( bExp == 0x7FFF ) { + if ( (bits64) ( bSig<<1 ) ) return propagateFloatx80NaN( a, b STATUS_VAR ); + return packFloatx80( zSign ^ 1, 0x7FFF, LIT64( 0x8000000000000000 ) ); + } + if ( aExp == 0 ) ++expDiff; + shift128RightJamming( aSig, 0, - expDiff, &aSig, &zSig1 ); + bBigger: + sub128( bSig, 0, aSig, zSig1, &zSig0, &zSig1 ); + zExp = bExp; + zSign ^= 1; + goto normalizeRoundAndPack; + aExpBigger: + if ( aExp == 0x7FFF ) { + if ( (bits64) ( aSig<<1 ) ) return propagateFloatx80NaN( a, b STATUS_VAR ); + return a; + } + if ( bExp == 0 ) --expDiff; + shift128RightJamming( bSig, 0, expDiff, &bSig, &zSig1 ); + aBigger: + sub128( aSig, 0, bSig, zSig1, &zSig0, &zSig1 ); + zExp = aExp; + normalizeRoundAndPack: + return + normalizeRoundAndPackFloatx80( + STATUS(floatx80_rounding_precision), zSign, zExp, zSig0, zSig1 STATUS_VAR ); + +} + +/*---------------------------------------------------------------------------- +| Returns the result of adding the extended double-precision floating-point +| values `a' and `b'. The operation is performed according to the IEC/IEEE +| Standard for Binary Floating-Point Arithmetic. +*----------------------------------------------------------------------------*/ + +floatx80 floatx80_add( floatx80 a, floatx80 b STATUS_PARAM ) +{ + flag aSign, bSign; + + aSign = extractFloatx80Sign( a ); + bSign = extractFloatx80Sign( b ); + if ( aSign == bSign ) { + return addFloatx80Sigs( a, b, aSign STATUS_VAR ); + } + else { + return subFloatx80Sigs( a, b, aSign STATUS_VAR ); + } + +} + +/*---------------------------------------------------------------------------- +| Returns the result of subtracting the extended double-precision floating- +| point values `a' and `b'. The operation is performed according to the +| IEC/IEEE Standard for Binary Floating-Point Arithmetic. +*----------------------------------------------------------------------------*/ + +floatx80 floatx80_sub( floatx80 a, floatx80 b STATUS_PARAM ) +{ + flag aSign, bSign; + + aSign = extractFloatx80Sign( a ); + bSign = extractFloatx80Sign( b ); + if ( aSign == bSign ) { + return subFloatx80Sigs( a, b, aSign STATUS_VAR ); + } + else { + return addFloatx80Sigs( a, b, aSign STATUS_VAR ); + } + +} + +/*---------------------------------------------------------------------------- +| Returns the result of multiplying the extended double-precision floating- +| point values `a' and `b'. The operation is performed according to the +| IEC/IEEE Standard for Binary Floating-Point Arithmetic. +*----------------------------------------------------------------------------*/ + +floatx80 floatx80_mul( floatx80 a, floatx80 b STATUS_PARAM ) +{ + flag aSign, bSign, zSign; + int32 aExp, bExp, zExp; + bits64 aSig, bSig, zSig0, zSig1; + floatx80 z; + + aSig = extractFloatx80Frac( a ); + aExp = extractFloatx80Exp( a ); + aSign = extractFloatx80Sign( a ); + bSig = extractFloatx80Frac( b ); + bExp = extractFloatx80Exp( b ); + bSign = extractFloatx80Sign( b ); + zSign = aSign ^ bSign; + if ( aExp == 0x7FFF ) { + if ( (bits64) ( aSig<<1 ) + || ( ( bExp == 0x7FFF ) && (bits64) ( bSig<<1 ) ) ) { + return propagateFloatx80NaN( a, b STATUS_VAR ); + } + if ( ( bExp | bSig ) == 0 ) goto invalid; + return packFloatx80( zSign, 0x7FFF, LIT64( 0x8000000000000000 ) ); + } + if ( bExp == 0x7FFF ) { + if ( (bits64) ( bSig<<1 ) ) return propagateFloatx80NaN( a, b STATUS_VAR ); + if ( ( aExp | aSig ) == 0 ) { + invalid: + float_raise( float_flag_invalid STATUS_VAR); + z.low = floatx80_default_nan_low; + z.high = floatx80_default_nan_high; + return z; + } + return packFloatx80( zSign, 0x7FFF, LIT64( 0x8000000000000000 ) ); + } + if ( aExp == 0 ) { + if ( aSig == 0 ) return packFloatx80( zSign, 0, 0 ); + normalizeFloatx80Subnormal( aSig, &aExp, &aSig ); + } + if ( bExp == 0 ) { + if ( bSig == 0 ) return packFloatx80( zSign, 0, 0 ); + normalizeFloatx80Subnormal( bSig, &bExp, &bSig ); + } + zExp = aExp + bExp - 0x3FFE; + mul64To128( aSig, bSig, &zSig0, &zSig1 ); + if ( 0 < (sbits64) zSig0 ) { + shortShift128Left( zSig0, zSig1, 1, &zSig0, &zSig1 ); + --zExp; + } + return + roundAndPackFloatx80( + STATUS(floatx80_rounding_precision), zSign, zExp, zSig0, zSig1 STATUS_VAR ); + +} + +/*---------------------------------------------------------------------------- +| Returns the result of dividing the extended double-precision floating-point +| value `a' by the corresponding value `b'. The operation is performed +| according to the IEC/IEEE Standard for Binary Floating-Point Arithmetic. +*----------------------------------------------------------------------------*/ + +floatx80 floatx80_div( floatx80 a, floatx80 b STATUS_PARAM ) +{ + flag aSign, bSign, zSign; + int32 aExp, bExp, zExp; + bits64 aSig, bSig, zSig0, zSig1; + bits64 rem0, rem1, rem2, term0, term1, term2; + floatx80 z; + + aSig = extractFloatx80Frac( a ); + aExp = extractFloatx80Exp( a ); + aSign = extractFloatx80Sign( a ); + bSig = extractFloatx80Frac( b ); + bExp = extractFloatx80Exp( b ); + bSign = extractFloatx80Sign( b ); + zSign = aSign ^ bSign; + if ( aExp == 0x7FFF ) { + if ( (bits64) ( aSig<<1 ) ) return propagateFloatx80NaN( a, b STATUS_VAR ); + if ( bExp == 0x7FFF ) { + if ( (bits64) ( bSig<<1 ) ) return propagateFloatx80NaN( a, b STATUS_VAR ); + goto invalid; + } + return packFloatx80( zSign, 0x7FFF, LIT64( 0x8000000000000000 ) ); + } + if ( bExp == 0x7FFF ) { + if ( (bits64) ( bSig<<1 ) ) return propagateFloatx80NaN( a, b STATUS_VAR ); + return packFloatx80( zSign, 0, 0 ); + } + if ( bExp == 0 ) { + if ( bSig == 0 ) { + if ( ( aExp | aSig ) == 0 ) { + invalid: + float_raise( float_flag_invalid STATUS_VAR); + z.low = floatx80_default_nan_low; + z.high = floatx80_default_nan_high; + return z; + } + float_raise( float_flag_divbyzero STATUS_VAR); + return packFloatx80( zSign, 0x7FFF, LIT64( 0x8000000000000000 ) ); + } + normalizeFloatx80Subnormal( bSig, &bExp, &bSig ); + } + if ( aExp == 0 ) { + if ( aSig == 0 ) return packFloatx80( zSign, 0, 0 ); + normalizeFloatx80Subnormal( aSig, &aExp, &aSig ); + } + zExp = aExp - bExp + 0x3FFE; + rem1 = 0; + if ( bSig <= aSig ) { + shift128Right( aSig, 0, 1, &aSig, &rem1 ); + ++zExp; + } + zSig0 = estimateDiv128To64( aSig, rem1, bSig ); + mul64To128( bSig, zSig0, &term0, &term1 ); + sub128( aSig, rem1, term0, term1, &rem0, &rem1 ); + while ( (sbits64) rem0 < 0 ) { + --zSig0; + add128( rem0, rem1, 0, bSig, &rem0, &rem1 ); + } + zSig1 = estimateDiv128To64( rem1, 0, bSig ); + if ( (bits64) ( zSig1<<1 ) <= 8 ) { + mul64To128( bSig, zSig1, &term1, &term2 ); + sub128( rem1, 0, term1, term2, &rem1, &rem2 ); + while ( (sbits64) rem1 < 0 ) { + --zSig1; + add128( rem1, rem2, 0, bSig, &rem1, &rem2 ); + } + zSig1 |= ( ( rem1 | rem2 ) != 0 ); + } + return + roundAndPackFloatx80( + STATUS(floatx80_rounding_precision), zSign, zExp, zSig0, zSig1 STATUS_VAR ); + +} + +/*---------------------------------------------------------------------------- +| Returns the remainder of the extended double-precision floating-point value +| `a' with respect to the corresponding value `b'. The operation is performed +| according to the IEC/IEEE Standard for Binary Floating-Point Arithmetic. +*----------------------------------------------------------------------------*/ + +floatx80 floatx80_rem( floatx80 a, floatx80 b STATUS_PARAM ) +{ + flag aSign, zSign; + int32 aExp, bExp, expDiff; + bits64 aSig0, aSig1, bSig; + bits64 q, term0, term1, alternateASig0, alternateASig1; + floatx80 z; + + aSig0 = extractFloatx80Frac( a ); + aExp = extractFloatx80Exp( a ); + aSign = extractFloatx80Sign( a ); + bSig = extractFloatx80Frac( b ); + bExp = extractFloatx80Exp( b ); + if ( aExp == 0x7FFF ) { + if ( (bits64) ( aSig0<<1 ) + || ( ( bExp == 0x7FFF ) && (bits64) ( bSig<<1 ) ) ) { + return propagateFloatx80NaN( a, b STATUS_VAR ); + } + goto invalid; + } + if ( bExp == 0x7FFF ) { + if ( (bits64) ( bSig<<1 ) ) return propagateFloatx80NaN( a, b STATUS_VAR ); + return a; + } + if ( bExp == 0 ) { + if ( bSig == 0 ) { + invalid: + float_raise( float_flag_invalid STATUS_VAR); + z.low = floatx80_default_nan_low; + z.high = floatx80_default_nan_high; + return z; + } + normalizeFloatx80Subnormal( bSig, &bExp, &bSig ); + } + if ( aExp == 0 ) { + if ( (bits64) ( aSig0<<1 ) == 0 ) return a; + normalizeFloatx80Subnormal( aSig0, &aExp, &aSig0 ); + } + bSig |= LIT64( 0x8000000000000000 ); + zSign = aSign; + expDiff = aExp - bExp; + aSig1 = 0; + if ( expDiff < 0 ) { + if ( expDiff < -1 ) return a; + shift128Right( aSig0, 0, 1, &aSig0, &aSig1 ); + expDiff = 0; + } + q = ( bSig <= aSig0 ); + if ( q ) aSig0 -= bSig; + expDiff -= 64; + while ( 0 < expDiff ) { + q = estimateDiv128To64( aSig0, aSig1, bSig ); + q = ( 2 < q ) ? q - 2 : 0; + mul64To128( bSig, q, &term0, &term1 ); + sub128( aSig0, aSig1, term0, term1, &aSig0, &aSig1 ); + shortShift128Left( aSig0, aSig1, 62, &aSig0, &aSig1 ); + expDiff -= 62; + } + expDiff += 64; + if ( 0 < expDiff ) { + q = estimateDiv128To64( aSig0, aSig1, bSig ); + q = ( 2 < q ) ? q - 2 : 0; + q >>= 64 - expDiff; + mul64To128( bSig, q<<( 64 - expDiff ), &term0, &term1 ); + sub128( aSig0, aSig1, term0, term1, &aSig0, &aSig1 ); + shortShift128Left( 0, bSig, 64 - expDiff, &term0, &term1 ); + while ( le128( term0, term1, aSig0, aSig1 ) ) { + ++q; + sub128( aSig0, aSig1, term0, term1, &aSig0, &aSig1 ); + } + } + else { + term1 = 0; + term0 = bSig; + } + sub128( term0, term1, aSig0, aSig1, &alternateASig0, &alternateASig1 ); + if ( lt128( alternateASig0, alternateASig1, aSig0, aSig1 ) + || ( eq128( alternateASig0, alternateASig1, aSig0, aSig1 ) + && ( q & 1 ) ) + ) { + aSig0 = alternateASig0; + aSig1 = alternateASig1; + zSign = ! zSign; + } + return + normalizeRoundAndPackFloatx80( + 80, zSign, bExp + expDiff, aSig0, aSig1 STATUS_VAR ); + +} + +/*---------------------------------------------------------------------------- +| Returns the square root of the extended double-precision floating-point +| value `a'. The operation is performed according to the IEC/IEEE Standard +| for Binary Floating-Point Arithmetic. +*----------------------------------------------------------------------------*/ + +floatx80 floatx80_sqrt( floatx80 a STATUS_PARAM ) +{ + flag aSign; + int32 aExp, zExp; + bits64 aSig0, aSig1, zSig0, zSig1, doubleZSig0; + bits64 rem0, rem1, rem2, rem3, term0, term1, term2, term3; + floatx80 z; + + aSig0 = extractFloatx80Frac( a ); + aExp = extractFloatx80Exp( a ); + aSign = extractFloatx80Sign( a ); + if ( aExp == 0x7FFF ) { + if ( (bits64) ( aSig0<<1 ) ) return propagateFloatx80NaN( a, a STATUS_VAR ); + if ( ! aSign ) return a; + goto invalid; + } + if ( aSign ) { + if ( ( aExp | aSig0 ) == 0 ) return a; + invalid: + float_raise( float_flag_invalid STATUS_VAR); + z.low = floatx80_default_nan_low; + z.high = floatx80_default_nan_high; + return z; + } + if ( aExp == 0 ) { + if ( aSig0 == 0 ) return packFloatx80( 0, 0, 0 ); + normalizeFloatx80Subnormal( aSig0, &aExp, &aSig0 ); + } + zExp = ( ( aExp - 0x3FFF )>>1 ) + 0x3FFF; + zSig0 = estimateSqrt32( aExp, aSig0>>32 ); + shift128Right( aSig0, 0, 2 + ( aExp & 1 ), &aSig0, &aSig1 ); + zSig0 = estimateDiv128To64( aSig0, aSig1, zSig0<<32 ) + ( zSig0<<30 ); + doubleZSig0 = zSig0<<1; + mul64To128( zSig0, zSig0, &term0, &term1 ); + sub128( aSig0, aSig1, term0, term1, &rem0, &rem1 ); + while ( (sbits64) rem0 < 0 ) { + --zSig0; + doubleZSig0 -= 2; + add128( rem0, rem1, zSig0>>63, doubleZSig0 | 1, &rem0, &rem1 ); + } + zSig1 = estimateDiv128To64( rem1, 0, doubleZSig0 ); + if ( ( zSig1 & LIT64( 0x3FFFFFFFFFFFFFFF ) ) <= 5 ) { + if ( zSig1 == 0 ) zSig1 = 1; + mul64To128( doubleZSig0, zSig1, &term1, &term2 ); + sub128( rem1, 0, term1, term2, &rem1, &rem2 ); + mul64To128( zSig1, zSig1, &term2, &term3 ); + sub192( rem1, rem2, 0, 0, term2, term3, &rem1, &rem2, &rem3 ); + while ( (sbits64) rem1 < 0 ) { + --zSig1; + shortShift128Left( 0, zSig1, 1, &term2, &term3 ); + term3 |= 1; + term2 |= doubleZSig0; + add192( rem1, rem2, rem3, 0, term2, term3, &rem1, &rem2, &rem3 ); + } + zSig1 |= ( ( rem1 | rem2 | rem3 ) != 0 ); + } + shortShift128Left( 0, zSig1, 1, &zSig0, &zSig1 ); + zSig0 |= doubleZSig0; + return + roundAndPackFloatx80( + STATUS(floatx80_rounding_precision), 0, zExp, zSig0, zSig1 STATUS_VAR ); + +} + +/*---------------------------------------------------------------------------- +| Returns 1 if the extended double-precision floating-point value `a' is +| equal to the corresponding value `b', and 0 otherwise. The comparison is +| performed according to the IEC/IEEE Standard for Binary Floating-Point +| Arithmetic. +*----------------------------------------------------------------------------*/ + +int floatx80_eq( floatx80 a, floatx80 b STATUS_PARAM ) +{ + + if ( ( ( extractFloatx80Exp( a ) == 0x7FFF ) + && (bits64) ( extractFloatx80Frac( a )<<1 ) ) + || ( ( extractFloatx80Exp( b ) == 0x7FFF ) + && (bits64) ( extractFloatx80Frac( b )<<1 ) ) + ) { + if ( floatx80_is_signaling_nan( a ) + || floatx80_is_signaling_nan( b ) ) { + float_raise( float_flag_invalid STATUS_VAR); + } + return 0; + } + return + ( a.low == b.low ) + && ( ( a.high == b.high ) + || ( ( a.low == 0 ) + && ( (bits16) ( ( a.high | b.high )<<1 ) == 0 ) ) + ); + +} + +/*---------------------------------------------------------------------------- +| Returns 1 if the extended double-precision floating-point value `a' is +| less than or equal to the corresponding value `b', and 0 otherwise. The +| comparison is performed according to the IEC/IEEE Standard for Binary +| Floating-Point Arithmetic. +*----------------------------------------------------------------------------*/ + +int floatx80_le( floatx80 a, floatx80 b STATUS_PARAM ) +{ + flag aSign, bSign; + + if ( ( ( extractFloatx80Exp( a ) == 0x7FFF ) + && (bits64) ( extractFloatx80Frac( a )<<1 ) ) + || ( ( extractFloatx80Exp( b ) == 0x7FFF ) + && (bits64) ( extractFloatx80Frac( b )<<1 ) ) + ) { + float_raise( float_flag_invalid STATUS_VAR); + return 0; + } + aSign = extractFloatx80Sign( a ); + bSign = extractFloatx80Sign( b ); + if ( aSign != bSign ) { + return + aSign + || ( ( ( (bits16) ( ( a.high | b.high )<<1 ) ) | a.low | b.low ) + == 0 ); + } + return + aSign ? le128( b.high, b.low, a.high, a.low ) + : le128( a.high, a.low, b.high, b.low ); + +} + +/*---------------------------------------------------------------------------- +| Returns 1 if the extended double-precision floating-point value `a' is +| less than the corresponding value `b', and 0 otherwise. The comparison +| is performed according to the IEC/IEEE Standard for Binary Floating-Point +| Arithmetic. +*----------------------------------------------------------------------------*/ + +int floatx80_lt( floatx80 a, floatx80 b STATUS_PARAM ) +{ + flag aSign, bSign; + + if ( ( ( extractFloatx80Exp( a ) == 0x7FFF ) + && (bits64) ( extractFloatx80Frac( a )<<1 ) ) + || ( ( extractFloatx80Exp( b ) == 0x7FFF ) + && (bits64) ( extractFloatx80Frac( b )<<1 ) ) + ) { + float_raise( float_flag_invalid STATUS_VAR); + return 0; + } + aSign = extractFloatx80Sign( a ); + bSign = extractFloatx80Sign( b ); + if ( aSign != bSign ) { + return + aSign + && ( ( ( (bits16) ( ( a.high | b.high )<<1 ) ) | a.low | b.low ) + != 0 ); + } + return + aSign ? lt128( b.high, b.low, a.high, a.low ) + : lt128( a.high, a.low, b.high, b.low ); + +} + +/*---------------------------------------------------------------------------- +| Returns 1 if the extended double-precision floating-point value `a' is equal +| to the corresponding value `b', and 0 otherwise. The invalid exception is +| raised if either operand is a NaN. Otherwise, the comparison is performed +| according to the IEC/IEEE Standard for Binary Floating-Point Arithmetic. +*----------------------------------------------------------------------------*/ + +int floatx80_eq_signaling( floatx80 a, floatx80 b STATUS_PARAM ) +{ + + if ( ( ( extractFloatx80Exp( a ) == 0x7FFF ) + && (bits64) ( extractFloatx80Frac( a )<<1 ) ) + || ( ( extractFloatx80Exp( b ) == 0x7FFF ) + && (bits64) ( extractFloatx80Frac( b )<<1 ) ) + ) { + float_raise( float_flag_invalid STATUS_VAR); + return 0; + } + return + ( a.low == b.low ) + && ( ( a.high == b.high ) + || ( ( a.low == 0 ) + && ( (bits16) ( ( a.high | b.high )<<1 ) == 0 ) ) + ); + +} + +/*---------------------------------------------------------------------------- +| Returns 1 if the extended double-precision floating-point value `a' is less +| than or equal to the corresponding value `b', and 0 otherwise. Quiet NaNs +| do not cause an exception. Otherwise, the comparison is performed according +| to the IEC/IEEE Standard for Binary Floating-Point Arithmetic. +*----------------------------------------------------------------------------*/ + +int floatx80_le_quiet( floatx80 a, floatx80 b STATUS_PARAM ) +{ + flag aSign, bSign; + + if ( ( ( extractFloatx80Exp( a ) == 0x7FFF ) + && (bits64) ( extractFloatx80Frac( a )<<1 ) ) + || ( ( extractFloatx80Exp( b ) == 0x7FFF ) + && (bits64) ( extractFloatx80Frac( b )<<1 ) ) + ) { + if ( floatx80_is_signaling_nan( a ) + || floatx80_is_signaling_nan( b ) ) { + float_raise( float_flag_invalid STATUS_VAR); + } + return 0; + } + aSign = extractFloatx80Sign( a ); + bSign = extractFloatx80Sign( b ); + if ( aSign != bSign ) { + return + aSign + || ( ( ( (bits16) ( ( a.high | b.high )<<1 ) ) | a.low | b.low ) + == 0 ); + } + return + aSign ? le128( b.high, b.low, a.high, a.low ) + : le128( a.high, a.low, b.high, b.low ); + +} + +/*---------------------------------------------------------------------------- +| Returns 1 if the extended double-precision floating-point value `a' is less +| than the corresponding value `b', and 0 otherwise. Quiet NaNs do not cause +| an exception. Otherwise, the comparison is performed according to the +| IEC/IEEE Standard for Binary Floating-Point Arithmetic. +*----------------------------------------------------------------------------*/ + +int floatx80_lt_quiet( floatx80 a, floatx80 b STATUS_PARAM ) +{ + flag aSign, bSign; + + if ( ( ( extractFloatx80Exp( a ) == 0x7FFF ) + && (bits64) ( extractFloatx80Frac( a )<<1 ) ) + || ( ( extractFloatx80Exp( b ) == 0x7FFF ) + && (bits64) ( extractFloatx80Frac( b )<<1 ) ) + ) { + if ( floatx80_is_signaling_nan( a ) + || floatx80_is_signaling_nan( b ) ) { + float_raise( float_flag_invalid STATUS_VAR); + } + return 0; + } + aSign = extractFloatx80Sign( a ); + bSign = extractFloatx80Sign( b ); + if ( aSign != bSign ) { + return + aSign + && ( ( ( (bits16) ( ( a.high | b.high )<<1 ) ) | a.low | b.low ) + != 0 ); + } + return + aSign ? lt128( b.high, b.low, a.high, a.low ) + : lt128( a.high, a.low, b.high, b.low ); + +} + +#endif + +#ifdef FLOAT128 + +/*---------------------------------------------------------------------------- +| Returns the result of converting the quadruple-precision floating-point +| value `a' to the 32-bit two's complement integer format. The conversion +| is performed according to the IEC/IEEE Standard for Binary Floating-Point +| Arithmetic---which means in particular that the conversion is rounded +| according to the current rounding mode. If `a' is a NaN, the largest +| positive integer is returned. Otherwise, if the conversion overflows, the +| largest integer with the same sign as `a' is returned. +*----------------------------------------------------------------------------*/ + +int32 float128_to_int32( float128 a STATUS_PARAM ) +{ + flag aSign; + int32 aExp, shiftCount; + bits64 aSig0, aSig1; + + aSig1 = extractFloat128Frac1( a ); + aSig0 = extractFloat128Frac0( a ); + aExp = extractFloat128Exp( a ); + aSign = extractFloat128Sign( a ); + if ( ( aExp == 0x7FFF ) && ( aSig0 | aSig1 ) ) aSign = 0; + if ( aExp ) aSig0 |= LIT64( 0x0001000000000000 ); + aSig0 |= ( aSig1 != 0 ); + shiftCount = 0x4028 - aExp; + if ( 0 < shiftCount ) shift64RightJamming( aSig0, shiftCount, &aSig0 ); + return roundAndPackInt32( aSign, aSig0 STATUS_VAR ); + +} + +/*---------------------------------------------------------------------------- +| Returns the result of converting the quadruple-precision floating-point +| value `a' to the 32-bit two's complement integer format. The conversion +| is performed according to the IEC/IEEE Standard for Binary Floating-Point +| Arithmetic, except that the conversion is always rounded toward zero. If +| `a' is a NaN, the largest positive integer is returned. Otherwise, if the +| conversion overflows, the largest integer with the same sign as `a' is +| returned. +*----------------------------------------------------------------------------*/ + +int32 float128_to_int32_round_to_zero( float128 a STATUS_PARAM ) +{ + flag aSign; + int32 aExp, shiftCount; + bits64 aSig0, aSig1, savedASig; + int32 z; + + aSig1 = extractFloat128Frac1( a ); + aSig0 = extractFloat128Frac0( a ); + aExp = extractFloat128Exp( a ); + aSign = extractFloat128Sign( a ); + aSig0 |= ( aSig1 != 0 ); + if ( 0x401E < aExp ) { + if ( ( aExp == 0x7FFF ) && aSig0 ) aSign = 0; + goto invalid; + } + else if ( aExp < 0x3FFF ) { + if ( aExp || aSig0 ) STATUS(float_exception_flags) |= float_flag_inexact; + return 0; + } + aSig0 |= LIT64( 0x0001000000000000 ); + shiftCount = 0x402F - aExp; + savedASig = aSig0; + aSig0 >>= shiftCount; + z = aSig0; + if ( aSign ) z = - z; + if ( ( z < 0 ) ^ aSign ) { + invalid: + float_raise( float_flag_invalid STATUS_VAR); + return aSign ? (sbits32) 0x80000000 : 0x7FFFFFFF; + } + if ( ( aSig0<>( ( - shiftCount ) & 63 ) ); + if ( (bits64) ( aSig1<>( - shiftCount ); + if ( aSig1 + || ( shiftCount && (bits64) ( aSig0<<( shiftCount & 63 ) ) ) ) { + STATUS(float_exception_flags) |= float_flag_inexact; + } + } + if ( aSign ) z = - z; + return z; + +} + +/*---------------------------------------------------------------------------- +| Returns the result of converting the quadruple-precision floating-point +| value `a' to the single-precision floating-point format. The conversion +| is performed according to the IEC/IEEE Standard for Binary Floating-Point +| Arithmetic. +*----------------------------------------------------------------------------*/ + +float32 float128_to_float32( float128 a STATUS_PARAM ) +{ + flag aSign; + int32 aExp; + bits64 aSig0, aSig1; + bits32 zSig; + + aSig1 = extractFloat128Frac1( a ); + aSig0 = extractFloat128Frac0( a ); + aExp = extractFloat128Exp( a ); + aSign = extractFloat128Sign( a ); + if ( aExp == 0x7FFF ) { + if ( aSig0 | aSig1 ) { + return commonNaNToFloat32( float128ToCommonNaN( a STATUS_VAR ) ); + } + return packFloat32( aSign, 0xFF, 0 ); + } + aSig0 |= ( aSig1 != 0 ); + shift64RightJamming( aSig0, 18, &aSig0 ); + zSig = aSig0; + if ( aExp || zSig ) { + zSig |= 0x40000000; + aExp -= 0x3F81; + } + return roundAndPackFloat32( aSign, aExp, zSig STATUS_VAR ); + +} + +/*---------------------------------------------------------------------------- +| Returns the result of converting the quadruple-precision floating-point +| value `a' to the double-precision floating-point format. The conversion +| is performed according to the IEC/IEEE Standard for Binary Floating-Point +| Arithmetic. +*----------------------------------------------------------------------------*/ + +float64 float128_to_float64( float128 a STATUS_PARAM ) +{ + flag aSign; + int32 aExp; + bits64 aSig0, aSig1; + + aSig1 = extractFloat128Frac1( a ); + aSig0 = extractFloat128Frac0( a ); + aExp = extractFloat128Exp( a ); + aSign = extractFloat128Sign( a ); + if ( aExp == 0x7FFF ) { + if ( aSig0 | aSig1 ) { + return commonNaNToFloat64( float128ToCommonNaN( a STATUS_VAR ) ); + } + return packFloat64( aSign, 0x7FF, 0 ); + } + shortShift128Left( aSig0, aSig1, 14, &aSig0, &aSig1 ); + aSig0 |= ( aSig1 != 0 ); + if ( aExp || aSig0 ) { + aSig0 |= LIT64( 0x4000000000000000 ); + aExp -= 0x3C01; + } + return roundAndPackFloat64( aSign, aExp, aSig0 STATUS_VAR ); + +} + +#ifdef FLOATX80 + +/*---------------------------------------------------------------------------- +| Returns the result of converting the quadruple-precision floating-point +| value `a' to the extended double-precision floating-point format. The +| conversion is performed according to the IEC/IEEE Standard for Binary +| Floating-Point Arithmetic. +*----------------------------------------------------------------------------*/ + +floatx80 float128_to_floatx80( float128 a STATUS_PARAM ) +{ + flag aSign; + int32 aExp; + bits64 aSig0, aSig1; + + aSig1 = extractFloat128Frac1( a ); + aSig0 = extractFloat128Frac0( a ); + aExp = extractFloat128Exp( a ); + aSign = extractFloat128Sign( a ); + if ( aExp == 0x7FFF ) { + if ( aSig0 | aSig1 ) { + return commonNaNToFloatx80( float128ToCommonNaN( a STATUS_VAR ) ); + } + return packFloatx80( aSign, 0x7FFF, LIT64( 0x8000000000000000 ) ); + } + if ( aExp == 0 ) { + if ( ( aSig0 | aSig1 ) == 0 ) return packFloatx80( aSign, 0, 0 ); + normalizeFloat128Subnormal( aSig0, aSig1, &aExp, &aSig0, &aSig1 ); + } + else { + aSig0 |= LIT64( 0x0001000000000000 ); + } + shortShift128Left( aSig0, aSig1, 15, &aSig0, &aSig1 ); + return roundAndPackFloatx80( 80, aSign, aExp, aSig0, aSig1 STATUS_VAR ); + +} + +#endif + +/*---------------------------------------------------------------------------- +| Rounds the quadruple-precision floating-point value `a' to an integer, and +| returns the result as a quadruple-precision floating-point value. The +| operation is performed according to the IEC/IEEE Standard for Binary +| Floating-Point Arithmetic. +*----------------------------------------------------------------------------*/ + +float128 float128_round_to_int( float128 a STATUS_PARAM ) +{ + flag aSign; + int32 aExp; + bits64 lastBitMask, roundBitsMask; + int8 roundingMode; + float128 z; + + aExp = extractFloat128Exp( a ); + if ( 0x402F <= aExp ) { + if ( 0x406F <= aExp ) { + if ( ( aExp == 0x7FFF ) + && ( extractFloat128Frac0( a ) | extractFloat128Frac1( a ) ) + ) { + return propagateFloat128NaN( a, a STATUS_VAR ); + } + return a; + } + lastBitMask = 1; + lastBitMask = ( lastBitMask<<( 0x406E - aExp ) )<<1; + roundBitsMask = lastBitMask - 1; + z = a; + roundingMode = STATUS(float_rounding_mode); + if ( roundingMode == float_round_nearest_even ) { + if ( lastBitMask ) { + add128( z.high, z.low, 0, lastBitMask>>1, &z.high, &z.low ); + if ( ( z.low & roundBitsMask ) == 0 ) z.low &= ~ lastBitMask; + } + else { + if ( (sbits64) z.low < 0 ) { + ++z.high; + if ( (bits64) ( z.low<<1 ) == 0 ) z.high &= ~1; + } + } + } + else if ( roundingMode != float_round_to_zero ) { + if ( extractFloat128Sign( z ) + ^ ( roundingMode == float_round_up ) ) { + add128( z.high, z.low, 0, roundBitsMask, &z.high, &z.low ); + } + } + z.low &= ~ roundBitsMask; + } + else { + if ( aExp < 0x3FFF ) { + if ( ( ( (bits64) ( a.high<<1 ) ) | a.low ) == 0 ) return a; + STATUS(float_exception_flags) |= float_flag_inexact; + aSign = extractFloat128Sign( a ); + switch ( STATUS(float_rounding_mode) ) { + case float_round_nearest_even: + if ( ( aExp == 0x3FFE ) + && ( extractFloat128Frac0( a ) + | extractFloat128Frac1( a ) ) + ) { + return packFloat128( aSign, 0x3FFF, 0, 0 ); + } + break; + case float_round_down: + return + aSign ? packFloat128( 1, 0x3FFF, 0, 0 ) + : packFloat128( 0, 0, 0, 0 ); + case float_round_up: + return + aSign ? packFloat128( 1, 0, 0, 0 ) + : packFloat128( 0, 0x3FFF, 0, 0 ); + } + return packFloat128( aSign, 0, 0, 0 ); + } + lastBitMask = 1; + lastBitMask <<= 0x402F - aExp; + roundBitsMask = lastBitMask - 1; + z.low = 0; + z.high = a.high; + roundingMode = STATUS(float_rounding_mode); + if ( roundingMode == float_round_nearest_even ) { + z.high += lastBitMask>>1; + if ( ( ( z.high & roundBitsMask ) | a.low ) == 0 ) { + z.high &= ~ lastBitMask; + } + } + else if ( roundingMode != float_round_to_zero ) { + if ( extractFloat128Sign( z ) + ^ ( roundingMode == float_round_up ) ) { + z.high |= ( a.low != 0 ); + z.high += roundBitsMask; + } + } + z.high &= ~ roundBitsMask; + } + if ( ( z.low != a.low ) || ( z.high != a.high ) ) { + STATUS(float_exception_flags) |= float_flag_inexact; + } + return z; + +} + +/*---------------------------------------------------------------------------- +| Returns the result of adding the absolute values of the quadruple-precision +| floating-point values `a' and `b'. If `zSign' is 1, the sum is negated +| before being returned. `zSign' is ignored if the result is a NaN. +| The addition is performed according to the IEC/IEEE Standard for Binary +| Floating-Point Arithmetic. +*----------------------------------------------------------------------------*/ + +static float128 addFloat128Sigs( float128 a, float128 b, flag zSign STATUS_PARAM) +{ + int32 aExp, bExp, zExp; + bits64 aSig0, aSig1, bSig0, bSig1, zSig0, zSig1, zSig2; + int32 expDiff; + + aSig1 = extractFloat128Frac1( a ); + aSig0 = extractFloat128Frac0( a ); + aExp = extractFloat128Exp( a ); + bSig1 = extractFloat128Frac1( b ); + bSig0 = extractFloat128Frac0( b ); + bExp = extractFloat128Exp( b ); + expDiff = aExp - bExp; + if ( 0 < expDiff ) { + if ( aExp == 0x7FFF ) { + if ( aSig0 | aSig1 ) return propagateFloat128NaN( a, b STATUS_VAR ); + return a; + } + if ( bExp == 0 ) { + --expDiff; + } + else { + bSig0 |= LIT64( 0x0001000000000000 ); + } + shift128ExtraRightJamming( + bSig0, bSig1, 0, expDiff, &bSig0, &bSig1, &zSig2 ); + zExp = aExp; + } + else if ( expDiff < 0 ) { + if ( bExp == 0x7FFF ) { + if ( bSig0 | bSig1 ) return propagateFloat128NaN( a, b STATUS_VAR ); + return packFloat128( zSign, 0x7FFF, 0, 0 ); + } + if ( aExp == 0 ) { + ++expDiff; + } + else { + aSig0 |= LIT64( 0x0001000000000000 ); + } + shift128ExtraRightJamming( + aSig0, aSig1, 0, - expDiff, &aSig0, &aSig1, &zSig2 ); + zExp = bExp; + } + else { + if ( aExp == 0x7FFF ) { + if ( aSig0 | aSig1 | bSig0 | bSig1 ) { + return propagateFloat128NaN( a, b STATUS_VAR ); + } + return a; + } + add128( aSig0, aSig1, bSig0, bSig1, &zSig0, &zSig1 ); + if ( aExp == 0 ) { + if ( STATUS(flush_to_zero) ) return packFloat128( zSign, 0, 0, 0 ); + return packFloat128( zSign, 0, zSig0, zSig1 ); + } + zSig2 = 0; + zSig0 |= LIT64( 0x0002000000000000 ); + zExp = aExp; + goto shiftRight1; + } + aSig0 |= LIT64( 0x0001000000000000 ); + add128( aSig0, aSig1, bSig0, bSig1, &zSig0, &zSig1 ); + --zExp; + if ( zSig0 < LIT64( 0x0002000000000000 ) ) goto roundAndPack; + ++zExp; + shiftRight1: + shift128ExtraRightJamming( + zSig0, zSig1, zSig2, 1, &zSig0, &zSig1, &zSig2 ); + roundAndPack: + return roundAndPackFloat128( zSign, zExp, zSig0, zSig1, zSig2 STATUS_VAR ); + +} + +/*---------------------------------------------------------------------------- +| Returns the result of subtracting the absolute values of the quadruple- +| precision floating-point values `a' and `b'. If `zSign' is 1, the +| difference is negated before being returned. `zSign' is ignored if the +| result is a NaN. The subtraction is performed according to the IEC/IEEE +| Standard for Binary Floating-Point Arithmetic. +*----------------------------------------------------------------------------*/ + +static float128 subFloat128Sigs( float128 a, float128 b, flag zSign STATUS_PARAM) +{ + int32 aExp, bExp, zExp; + bits64 aSig0, aSig1, bSig0, bSig1, zSig0, zSig1; + int32 expDiff; + float128 z; + + aSig1 = extractFloat128Frac1( a ); + aSig0 = extractFloat128Frac0( a ); + aExp = extractFloat128Exp( a ); + bSig1 = extractFloat128Frac1( b ); + bSig0 = extractFloat128Frac0( b ); + bExp = extractFloat128Exp( b ); + expDiff = aExp - bExp; + shortShift128Left( aSig0, aSig1, 14, &aSig0, &aSig1 ); + shortShift128Left( bSig0, bSig1, 14, &bSig0, &bSig1 ); + if ( 0 < expDiff ) goto aExpBigger; + if ( expDiff < 0 ) goto bExpBigger; + if ( aExp == 0x7FFF ) { + if ( aSig0 | aSig1 | bSig0 | bSig1 ) { + return propagateFloat128NaN( a, b STATUS_VAR ); + } + float_raise( float_flag_invalid STATUS_VAR); + z.low = float128_default_nan_low; + z.high = float128_default_nan_high; + return z; + } + if ( aExp == 0 ) { + aExp = 1; + bExp = 1; + } + if ( bSig0 < aSig0 ) goto aBigger; + if ( aSig0 < bSig0 ) goto bBigger; + if ( bSig1 < aSig1 ) goto aBigger; + if ( aSig1 < bSig1 ) goto bBigger; + return packFloat128( STATUS(float_rounding_mode) == float_round_down, 0, 0, 0 ); + bExpBigger: + if ( bExp == 0x7FFF ) { + if ( bSig0 | bSig1 ) return propagateFloat128NaN( a, b STATUS_VAR ); + return packFloat128( zSign ^ 1, 0x7FFF, 0, 0 ); + } + if ( aExp == 0 ) { + ++expDiff; + } + else { + aSig0 |= LIT64( 0x4000000000000000 ); + } + shift128RightJamming( aSig0, aSig1, - expDiff, &aSig0, &aSig1 ); + bSig0 |= LIT64( 0x4000000000000000 ); + bBigger: + sub128( bSig0, bSig1, aSig0, aSig1, &zSig0, &zSig1 ); + zExp = bExp; + zSign ^= 1; + goto normalizeRoundAndPack; + aExpBigger: + if ( aExp == 0x7FFF ) { + if ( aSig0 | aSig1 ) return propagateFloat128NaN( a, b STATUS_VAR ); + return a; + } + if ( bExp == 0 ) { + --expDiff; + } + else { + bSig0 |= LIT64( 0x4000000000000000 ); + } + shift128RightJamming( bSig0, bSig1, expDiff, &bSig0, &bSig1 ); + aSig0 |= LIT64( 0x4000000000000000 ); + aBigger: + sub128( aSig0, aSig1, bSig0, bSig1, &zSig0, &zSig1 ); + zExp = aExp; + normalizeRoundAndPack: + --zExp; + return normalizeRoundAndPackFloat128( zSign, zExp - 14, zSig0, zSig1 STATUS_VAR ); + +} + +/*---------------------------------------------------------------------------- +| Returns the result of adding the quadruple-precision floating-point values +| `a' and `b'. The operation is performed according to the IEC/IEEE Standard +| for Binary Floating-Point Arithmetic. +*----------------------------------------------------------------------------*/ + +float128 float128_add( float128 a, float128 b STATUS_PARAM ) +{ + flag aSign, bSign; + + aSign = extractFloat128Sign( a ); + bSign = extractFloat128Sign( b ); + if ( aSign == bSign ) { + return addFloat128Sigs( a, b, aSign STATUS_VAR ); + } + else { + return subFloat128Sigs( a, b, aSign STATUS_VAR ); + } + +} + +/*---------------------------------------------------------------------------- +| Returns the result of subtracting the quadruple-precision floating-point +| values `a' and `b'. The operation is performed according to the IEC/IEEE +| Standard for Binary Floating-Point Arithmetic. +*----------------------------------------------------------------------------*/ + +float128 float128_sub( float128 a, float128 b STATUS_PARAM ) +{ + flag aSign, bSign; + + aSign = extractFloat128Sign( a ); + bSign = extractFloat128Sign( b ); + if ( aSign == bSign ) { + return subFloat128Sigs( a, b, aSign STATUS_VAR ); + } + else { + return addFloat128Sigs( a, b, aSign STATUS_VAR ); + } + +} + +/*---------------------------------------------------------------------------- +| Returns the result of multiplying the quadruple-precision floating-point +| values `a' and `b'. The operation is performed according to the IEC/IEEE +| Standard for Binary Floating-Point Arithmetic. +*----------------------------------------------------------------------------*/ + +float128 float128_mul( float128 a, float128 b STATUS_PARAM ) +{ + flag aSign, bSign, zSign; + int32 aExp, bExp, zExp; + bits64 aSig0, aSig1, bSig0, bSig1, zSig0, zSig1, zSig2, zSig3; + float128 z; + + aSig1 = extractFloat128Frac1( a ); + aSig0 = extractFloat128Frac0( a ); + aExp = extractFloat128Exp( a ); + aSign = extractFloat128Sign( a ); + bSig1 = extractFloat128Frac1( b ); + bSig0 = extractFloat128Frac0( b ); + bExp = extractFloat128Exp( b ); + bSign = extractFloat128Sign( b ); + zSign = aSign ^ bSign; + if ( aExp == 0x7FFF ) { + if ( ( aSig0 | aSig1 ) + || ( ( bExp == 0x7FFF ) && ( bSig0 | bSig1 ) ) ) { + return propagateFloat128NaN( a, b STATUS_VAR ); + } + if ( ( bExp | bSig0 | bSig1 ) == 0 ) goto invalid; + return packFloat128( zSign, 0x7FFF, 0, 0 ); + } + if ( bExp == 0x7FFF ) { + if ( bSig0 | bSig1 ) return propagateFloat128NaN( a, b STATUS_VAR ); + if ( ( aExp | aSig0 | aSig1 ) == 0 ) { + invalid: + float_raise( float_flag_invalid STATUS_VAR); + z.low = float128_default_nan_low; + z.high = float128_default_nan_high; + return z; + } + return packFloat128( zSign, 0x7FFF, 0, 0 ); + } + if ( aExp == 0 ) { + if ( ( aSig0 | aSig1 ) == 0 ) return packFloat128( zSign, 0, 0, 0 ); + normalizeFloat128Subnormal( aSig0, aSig1, &aExp, &aSig0, &aSig1 ); + } + if ( bExp == 0 ) { + if ( ( bSig0 | bSig1 ) == 0 ) return packFloat128( zSign, 0, 0, 0 ); + normalizeFloat128Subnormal( bSig0, bSig1, &bExp, &bSig0, &bSig1 ); + } + zExp = aExp + bExp - 0x4000; + aSig0 |= LIT64( 0x0001000000000000 ); + shortShift128Left( bSig0, bSig1, 16, &bSig0, &bSig1 ); + mul128To256( aSig0, aSig1, bSig0, bSig1, &zSig0, &zSig1, &zSig2, &zSig3 ); + add128( zSig0, zSig1, aSig0, aSig1, &zSig0, &zSig1 ); + zSig2 |= ( zSig3 != 0 ); + if ( LIT64( 0x0002000000000000 ) <= zSig0 ) { + shift128ExtraRightJamming( + zSig0, zSig1, zSig2, 1, &zSig0, &zSig1, &zSig2 ); + ++zExp; + } + return roundAndPackFloat128( zSign, zExp, zSig0, zSig1, zSig2 STATUS_VAR ); + +} + +/*---------------------------------------------------------------------------- +| Returns the result of dividing the quadruple-precision floating-point value +| `a' by the corresponding value `b'. The operation is performed according to +| the IEC/IEEE Standard for Binary Floating-Point Arithmetic. +*----------------------------------------------------------------------------*/ + +float128 float128_div( float128 a, float128 b STATUS_PARAM ) +{ + flag aSign, bSign, zSign; + int32 aExp, bExp, zExp; + bits64 aSig0, aSig1, bSig0, bSig1, zSig0, zSig1, zSig2; + bits64 rem0, rem1, rem2, rem3, term0, term1, term2, term3; + float128 z; + + aSig1 = extractFloat128Frac1( a ); + aSig0 = extractFloat128Frac0( a ); + aExp = extractFloat128Exp( a ); + aSign = extractFloat128Sign( a ); + bSig1 = extractFloat128Frac1( b ); + bSig0 = extractFloat128Frac0( b ); + bExp = extractFloat128Exp( b ); + bSign = extractFloat128Sign( b ); + zSign = aSign ^ bSign; + if ( aExp == 0x7FFF ) { + if ( aSig0 | aSig1 ) return propagateFloat128NaN( a, b STATUS_VAR ); + if ( bExp == 0x7FFF ) { + if ( bSig0 | bSig1 ) return propagateFloat128NaN( a, b STATUS_VAR ); + goto invalid; + } + return packFloat128( zSign, 0x7FFF, 0, 0 ); + } + if ( bExp == 0x7FFF ) { + if ( bSig0 | bSig1 ) return propagateFloat128NaN( a, b STATUS_VAR ); + return packFloat128( zSign, 0, 0, 0 ); + } + if ( bExp == 0 ) { + if ( ( bSig0 | bSig1 ) == 0 ) { + if ( ( aExp | aSig0 | aSig1 ) == 0 ) { + invalid: + float_raise( float_flag_invalid STATUS_VAR); + z.low = float128_default_nan_low; + z.high = float128_default_nan_high; + return z; + } + float_raise( float_flag_divbyzero STATUS_VAR); + return packFloat128( zSign, 0x7FFF, 0, 0 ); + } + normalizeFloat128Subnormal( bSig0, bSig1, &bExp, &bSig0, &bSig1 ); + } + if ( aExp == 0 ) { + if ( ( aSig0 | aSig1 ) == 0 ) return packFloat128( zSign, 0, 0, 0 ); + normalizeFloat128Subnormal( aSig0, aSig1, &aExp, &aSig0, &aSig1 ); + } + zExp = aExp - bExp + 0x3FFD; + shortShift128Left( + aSig0 | LIT64( 0x0001000000000000 ), aSig1, 15, &aSig0, &aSig1 ); + shortShift128Left( + bSig0 | LIT64( 0x0001000000000000 ), bSig1, 15, &bSig0, &bSig1 ); + if ( le128( bSig0, bSig1, aSig0, aSig1 ) ) { + shift128Right( aSig0, aSig1, 1, &aSig0, &aSig1 ); + ++zExp; + } + zSig0 = estimateDiv128To64( aSig0, aSig1, bSig0 ); + mul128By64To192( bSig0, bSig1, zSig0, &term0, &term1, &term2 ); + sub192( aSig0, aSig1, 0, term0, term1, term2, &rem0, &rem1, &rem2 ); + while ( (sbits64) rem0 < 0 ) { + --zSig0; + add192( rem0, rem1, rem2, 0, bSig0, bSig1, &rem0, &rem1, &rem2 ); + } + zSig1 = estimateDiv128To64( rem1, rem2, bSig0 ); + if ( ( zSig1 & 0x3FFF ) <= 4 ) { + mul128By64To192( bSig0, bSig1, zSig1, &term1, &term2, &term3 ); + sub192( rem1, rem2, 0, term1, term2, term3, &rem1, &rem2, &rem3 ); + while ( (sbits64) rem1 < 0 ) { + --zSig1; + add192( rem1, rem2, rem3, 0, bSig0, bSig1, &rem1, &rem2, &rem3 ); + } + zSig1 |= ( ( rem1 | rem2 | rem3 ) != 0 ); + } + shift128ExtraRightJamming( zSig0, zSig1, 0, 15, &zSig0, &zSig1, &zSig2 ); + return roundAndPackFloat128( zSign, zExp, zSig0, zSig1, zSig2 STATUS_VAR ); + +} + +/*---------------------------------------------------------------------------- +| Returns the remainder of the quadruple-precision floating-point value `a' +| with respect to the corresponding value `b'. The operation is performed +| according to the IEC/IEEE Standard for Binary Floating-Point Arithmetic. +*----------------------------------------------------------------------------*/ + +float128 float128_rem( float128 a, float128 b STATUS_PARAM ) +{ + flag aSign, zSign; + int32 aExp, bExp, expDiff; + bits64 aSig0, aSig1, bSig0, bSig1, q, term0, term1, term2; + bits64 allZero, alternateASig0, alternateASig1, sigMean1; + sbits64 sigMean0; + float128 z; + + aSig1 = extractFloat128Frac1( a ); + aSig0 = extractFloat128Frac0( a ); + aExp = extractFloat128Exp( a ); + aSign = extractFloat128Sign( a ); + bSig1 = extractFloat128Frac1( b ); + bSig0 = extractFloat128Frac0( b ); + bExp = extractFloat128Exp( b ); + if ( aExp == 0x7FFF ) { + if ( ( aSig0 | aSig1 ) + || ( ( bExp == 0x7FFF ) && ( bSig0 | bSig1 ) ) ) { + return propagateFloat128NaN( a, b STATUS_VAR ); + } + goto invalid; + } + if ( bExp == 0x7FFF ) { + if ( bSig0 | bSig1 ) return propagateFloat128NaN( a, b STATUS_VAR ); + return a; + } + if ( bExp == 0 ) { + if ( ( bSig0 | bSig1 ) == 0 ) { + invalid: + float_raise( float_flag_invalid STATUS_VAR); + z.low = float128_default_nan_low; + z.high = float128_default_nan_high; + return z; + } + normalizeFloat128Subnormal( bSig0, bSig1, &bExp, &bSig0, &bSig1 ); + } + if ( aExp == 0 ) { + if ( ( aSig0 | aSig1 ) == 0 ) return a; + normalizeFloat128Subnormal( aSig0, aSig1, &aExp, &aSig0, &aSig1 ); + } + expDiff = aExp - bExp; + if ( expDiff < -1 ) return a; + shortShift128Left( + aSig0 | LIT64( 0x0001000000000000 ), + aSig1, + 15 - ( expDiff < 0 ), + &aSig0, + &aSig1 + ); + shortShift128Left( + bSig0 | LIT64( 0x0001000000000000 ), bSig1, 15, &bSig0, &bSig1 ); + q = le128( bSig0, bSig1, aSig0, aSig1 ); + if ( q ) sub128( aSig0, aSig1, bSig0, bSig1, &aSig0, &aSig1 ); + expDiff -= 64; + while ( 0 < expDiff ) { + q = estimateDiv128To64( aSig0, aSig1, bSig0 ); + q = ( 4 < q ) ? q - 4 : 0; + mul128By64To192( bSig0, bSig1, q, &term0, &term1, &term2 ); + shortShift192Left( term0, term1, term2, 61, &term1, &term2, &allZero ); + shortShift128Left( aSig0, aSig1, 61, &aSig0, &allZero ); + sub128( aSig0, 0, term1, term2, &aSig0, &aSig1 ); + expDiff -= 61; + } + if ( -64 < expDiff ) { + q = estimateDiv128To64( aSig0, aSig1, bSig0 ); + q = ( 4 < q ) ? q - 4 : 0; + q >>= - expDiff; + shift128Right( bSig0, bSig1, 12, &bSig0, &bSig1 ); + expDiff += 52; + if ( expDiff < 0 ) { + shift128Right( aSig0, aSig1, - expDiff, &aSig0, &aSig1 ); + } + else { + shortShift128Left( aSig0, aSig1, expDiff, &aSig0, &aSig1 ); + } + mul128By64To192( bSig0, bSig1, q, &term0, &term1, &term2 ); + sub128( aSig0, aSig1, term1, term2, &aSig0, &aSig1 ); + } + else { + shift128Right( aSig0, aSig1, 12, &aSig0, &aSig1 ); + shift128Right( bSig0, bSig1, 12, &bSig0, &bSig1 ); + } + do { + alternateASig0 = aSig0; + alternateASig1 = aSig1; + ++q; + sub128( aSig0, aSig1, bSig0, bSig1, &aSig0, &aSig1 ); + } while ( 0 <= (sbits64) aSig0 ); + add128( + aSig0, aSig1, alternateASig0, alternateASig1, (bits64 *)&sigMean0, &sigMean1 ); + if ( ( sigMean0 < 0 ) + || ( ( ( sigMean0 | sigMean1 ) == 0 ) && ( q & 1 ) ) ) { + aSig0 = alternateASig0; + aSig1 = alternateASig1; + } + zSign = ( (sbits64) aSig0 < 0 ); + if ( zSign ) sub128( 0, 0, aSig0, aSig1, &aSig0, &aSig1 ); + return + normalizeRoundAndPackFloat128( aSign ^ zSign, bExp - 4, aSig0, aSig1 STATUS_VAR ); + +} + +/*---------------------------------------------------------------------------- +| Returns the square root of the quadruple-precision floating-point value `a'. +| The operation is performed according to the IEC/IEEE Standard for Binary +| Floating-Point Arithmetic. +*----------------------------------------------------------------------------*/ + +float128 float128_sqrt( float128 a STATUS_PARAM ) +{ + flag aSign; + int32 aExp, zExp; + bits64 aSig0, aSig1, zSig0, zSig1, zSig2, doubleZSig0; + bits64 rem0, rem1, rem2, rem3, term0, term1, term2, term3; + float128 z; + + aSig1 = extractFloat128Frac1( a ); + aSig0 = extractFloat128Frac0( a ); + aExp = extractFloat128Exp( a ); + aSign = extractFloat128Sign( a ); + if ( aExp == 0x7FFF ) { + if ( aSig0 | aSig1 ) return propagateFloat128NaN( a, a STATUS_VAR ); + if ( ! aSign ) return a; + goto invalid; + } + if ( aSign ) { + if ( ( aExp | aSig0 | aSig1 ) == 0 ) return a; + invalid: + float_raise( float_flag_invalid STATUS_VAR); + z.low = float128_default_nan_low; + z.high = float128_default_nan_high; + return z; + } + if ( aExp == 0 ) { + if ( ( aSig0 | aSig1 ) == 0 ) return packFloat128( 0, 0, 0, 0 ); + normalizeFloat128Subnormal( aSig0, aSig1, &aExp, &aSig0, &aSig1 ); + } + zExp = ( ( aExp - 0x3FFF )>>1 ) + 0x3FFE; + aSig0 |= LIT64( 0x0001000000000000 ); + zSig0 = estimateSqrt32( aExp, aSig0>>17 ); + shortShift128Left( aSig0, aSig1, 13 - ( aExp & 1 ), &aSig0, &aSig1 ); + zSig0 = estimateDiv128To64( aSig0, aSig1, zSig0<<32 ) + ( zSig0<<30 ); + doubleZSig0 = zSig0<<1; + mul64To128( zSig0, zSig0, &term0, &term1 ); + sub128( aSig0, aSig1, term0, term1, &rem0, &rem1 ); + while ( (sbits64) rem0 < 0 ) { + --zSig0; + doubleZSig0 -= 2; + add128( rem0, rem1, zSig0>>63, doubleZSig0 | 1, &rem0, &rem1 ); + } + zSig1 = estimateDiv128To64( rem1, 0, doubleZSig0 ); + if ( ( zSig1 & 0x1FFF ) <= 5 ) { + if ( zSig1 == 0 ) zSig1 = 1; + mul64To128( doubleZSig0, zSig1, &term1, &term2 ); + sub128( rem1, 0, term1, term2, &rem1, &rem2 ); + mul64To128( zSig1, zSig1, &term2, &term3 ); + sub192( rem1, rem2, 0, 0, term2, term3, &rem1, &rem2, &rem3 ); + while ( (sbits64) rem1 < 0 ) { + --zSig1; + shortShift128Left( 0, zSig1, 1, &term2, &term3 ); + term3 |= 1; + term2 |= doubleZSig0; + add192( rem1, rem2, rem3, 0, term2, term3, &rem1, &rem2, &rem3 ); + } + zSig1 |= ( ( rem1 | rem2 | rem3 ) != 0 ); + } + shift128ExtraRightJamming( zSig0, zSig1, 0, 14, &zSig0, &zSig1, &zSig2 ); + return roundAndPackFloat128( 0, zExp, zSig0, zSig1, zSig2 STATUS_VAR ); + +} + +/*---------------------------------------------------------------------------- +| Returns 1 if the quadruple-precision floating-point value `a' is equal to +| the corresponding value `b', and 0 otherwise. The comparison is performed +| according to the IEC/IEEE Standard for Binary Floating-Point Arithmetic. +*----------------------------------------------------------------------------*/ + +int float128_eq( float128 a, float128 b STATUS_PARAM ) +{ + + if ( ( ( extractFloat128Exp( a ) == 0x7FFF ) + && ( extractFloat128Frac0( a ) | extractFloat128Frac1( a ) ) ) + || ( ( extractFloat128Exp( b ) == 0x7FFF ) + && ( extractFloat128Frac0( b ) | extractFloat128Frac1( b ) ) ) + ) { + if ( float128_is_signaling_nan( a ) + || float128_is_signaling_nan( b ) ) { + float_raise( float_flag_invalid STATUS_VAR); + } + return 0; + } + return + ( a.low == b.low ) + && ( ( a.high == b.high ) + || ( ( a.low == 0 ) + && ( (bits64) ( ( a.high | b.high )<<1 ) == 0 ) ) + ); + +} + +/*---------------------------------------------------------------------------- +| Returns 1 if the quadruple-precision floating-point value `a' is less than +| or equal to the corresponding value `b', and 0 otherwise. The comparison +| is performed according to the IEC/IEEE Standard for Binary Floating-Point +| Arithmetic. +*----------------------------------------------------------------------------*/ + +int float128_le( float128 a, float128 b STATUS_PARAM ) +{ + flag aSign, bSign; + + if ( ( ( extractFloat128Exp( a ) == 0x7FFF ) + && ( extractFloat128Frac0( a ) | extractFloat128Frac1( a ) ) ) + || ( ( extractFloat128Exp( b ) == 0x7FFF ) + && ( extractFloat128Frac0( b ) | extractFloat128Frac1( b ) ) ) + ) { + float_raise( float_flag_invalid STATUS_VAR); + return 0; + } + aSign = extractFloat128Sign( a ); + bSign = extractFloat128Sign( b ); + if ( aSign != bSign ) { + return + aSign + || ( ( ( (bits64) ( ( a.high | b.high )<<1 ) ) | a.low | b.low ) + == 0 ); + } + return + aSign ? le128( b.high, b.low, a.high, a.low ) + : le128( a.high, a.low, b.high, b.low ); + +} + +/*---------------------------------------------------------------------------- +| Returns 1 if the quadruple-precision floating-point value `a' is less than +| the corresponding value `b', and 0 otherwise. The comparison is performed +| according to the IEC/IEEE Standard for Binary Floating-Point Arithmetic. +*----------------------------------------------------------------------------*/ + +int float128_lt( float128 a, float128 b STATUS_PARAM ) +{ + flag aSign, bSign; + + if ( ( ( extractFloat128Exp( a ) == 0x7FFF ) + && ( extractFloat128Frac0( a ) | extractFloat128Frac1( a ) ) ) + || ( ( extractFloat128Exp( b ) == 0x7FFF ) + && ( extractFloat128Frac0( b ) | extractFloat128Frac1( b ) ) ) + ) { + float_raise( float_flag_invalid STATUS_VAR); + return 0; + } + aSign = extractFloat128Sign( a ); + bSign = extractFloat128Sign( b ); + if ( aSign != bSign ) { + return + aSign + && ( ( ( (bits64) ( ( a.high | b.high )<<1 ) ) | a.low | b.low ) + != 0 ); + } + return + aSign ? lt128( b.high, b.low, a.high, a.low ) + : lt128( a.high, a.low, b.high, b.low ); + +} + +/*---------------------------------------------------------------------------- +| Returns 1 if the quadruple-precision floating-point value `a' is equal to +| the corresponding value `b', and 0 otherwise. The invalid exception is +| raised if either operand is a NaN. Otherwise, the comparison is performed +| according to the IEC/IEEE Standard for Binary Floating-Point Arithmetic. +*----------------------------------------------------------------------------*/ + +int float128_eq_signaling( float128 a, float128 b STATUS_PARAM ) +{ + + if ( ( ( extractFloat128Exp( a ) == 0x7FFF ) + && ( extractFloat128Frac0( a ) | extractFloat128Frac1( a ) ) ) + || ( ( extractFloat128Exp( b ) == 0x7FFF ) + && ( extractFloat128Frac0( b ) | extractFloat128Frac1( b ) ) ) + ) { + float_raise( float_flag_invalid STATUS_VAR); + return 0; + } + return + ( a.low == b.low ) + && ( ( a.high == b.high ) + || ( ( a.low == 0 ) + && ( (bits64) ( ( a.high | b.high )<<1 ) == 0 ) ) + ); + +} + +/*---------------------------------------------------------------------------- +| Returns 1 if the quadruple-precision floating-point value `a' is less than +| or equal to the corresponding value `b', and 0 otherwise. Quiet NaNs do not +| cause an exception. Otherwise, the comparison is performed according to the +| IEC/IEEE Standard for Binary Floating-Point Arithmetic. +*----------------------------------------------------------------------------*/ + +int float128_le_quiet( float128 a, float128 b STATUS_PARAM ) +{ + flag aSign, bSign; + + if ( ( ( extractFloat128Exp( a ) == 0x7FFF ) + && ( extractFloat128Frac0( a ) | extractFloat128Frac1( a ) ) ) + || ( ( extractFloat128Exp( b ) == 0x7FFF ) + && ( extractFloat128Frac0( b ) | extractFloat128Frac1( b ) ) ) + ) { + if ( float128_is_signaling_nan( a ) + || float128_is_signaling_nan( b ) ) { + float_raise( float_flag_invalid STATUS_VAR); + } + return 0; + } + aSign = extractFloat128Sign( a ); + bSign = extractFloat128Sign( b ); + if ( aSign != bSign ) { + return + aSign + || ( ( ( (bits64) ( ( a.high | b.high )<<1 ) ) | a.low | b.low ) + == 0 ); + } + return + aSign ? le128( b.high, b.low, a.high, a.low ) + : le128( a.high, a.low, b.high, b.low ); + +} + +/*---------------------------------------------------------------------------- +| Returns 1 if the quadruple-precision floating-point value `a' is less than +| the corresponding value `b', and 0 otherwise. Quiet NaNs do not cause an +| exception. Otherwise, the comparison is performed according to the IEC/IEEE +| Standard for Binary Floating-Point Arithmetic. +*----------------------------------------------------------------------------*/ + +int float128_lt_quiet( float128 a, float128 b STATUS_PARAM ) +{ + flag aSign, bSign; + + if ( ( ( extractFloat128Exp( a ) == 0x7FFF ) + && ( extractFloat128Frac0( a ) | extractFloat128Frac1( a ) ) ) + || ( ( extractFloat128Exp( b ) == 0x7FFF ) + && ( extractFloat128Frac0( b ) | extractFloat128Frac1( b ) ) ) + ) { + if ( float128_is_signaling_nan( a ) + || float128_is_signaling_nan( b ) ) { + float_raise( float_flag_invalid STATUS_VAR); + } + return 0; + } + aSign = extractFloat128Sign( a ); + bSign = extractFloat128Sign( b ); + if ( aSign != bSign ) { + return + aSign + && ( ( ( (bits64) ( ( a.high | b.high )<<1 ) ) | a.low | b.low ) + != 0 ); + } + return + aSign ? lt128( b.high, b.low, a.high, a.low ) + : lt128( a.high, a.low, b.high, b.low ); + +} + +#endif + +/* misc functions */ +float32 uint32_to_float32( unsigned int a STATUS_PARAM ) +{ + return int64_to_float32(a STATUS_VAR); +} + +float64 uint32_to_float64( unsigned int a STATUS_PARAM ) +{ + return int64_to_float64(a STATUS_VAR); +} + +unsigned int float32_to_uint32( float32 a STATUS_PARAM ) +{ + int64_t v; + unsigned int res; + + v = float32_to_int64(a STATUS_VAR); + if (v < 0) { + res = 0; + float_raise( float_flag_invalid STATUS_VAR); + } else if (v > 0xffffffff) { + res = 0xffffffff; + float_raise( float_flag_invalid STATUS_VAR); + } else { + res = v; + } + return res; +} + +unsigned int float32_to_uint32_round_to_zero( float32 a STATUS_PARAM ) +{ + int64_t v; + unsigned int res; + + v = float32_to_int64_round_to_zero(a STATUS_VAR); + if (v < 0) { + res = 0; + float_raise( float_flag_invalid STATUS_VAR); + } else if (v > 0xffffffff) { + res = 0xffffffff; + float_raise( float_flag_invalid STATUS_VAR); + } else { + res = v; + } + return res; +} + +unsigned int float32_to_uint16_round_to_zero( float32 a STATUS_PARAM ) +{ + int64_t v; + unsigned int res; + + v = float32_to_int64_round_to_zero(a STATUS_VAR); + if (v < 0) { + res = 0; + float_raise( float_flag_invalid STATUS_VAR); + } else if (v > 0xffff) { + res = 0xffff; + float_raise( float_flag_invalid STATUS_VAR); + } else { + res = v; + } + return res; +} + +unsigned int float64_to_uint32( float64 a STATUS_PARAM ) +{ + int64_t v; + unsigned int res; + + v = float64_to_int64(a STATUS_VAR); + if (v < 0) { + res = 0; + float_raise( float_flag_invalid STATUS_VAR); + } else if (v > 0xffffffff) { + res = 0xffffffff; + float_raise( float_flag_invalid STATUS_VAR); + } else { + res = v; + } + return res; +} + +unsigned int float64_to_uint32_round_to_zero( float64 a STATUS_PARAM ) +{ + int64_t v; + unsigned int res; + + v = float64_to_int64_round_to_zero(a STATUS_VAR); + if (v < 0) { + res = 0; + float_raise( float_flag_invalid STATUS_VAR); + } else if (v > 0xffffffff) { + res = 0xffffffff; + float_raise( float_flag_invalid STATUS_VAR); + } else { + res = v; + } + return res; +} + +unsigned int float64_to_uint16_round_to_zero( float64 a STATUS_PARAM ) +{ + int64_t v; + unsigned int res; + + v = float64_to_int64_round_to_zero(a STATUS_VAR); + if (v < 0) { + res = 0; + float_raise( float_flag_invalid STATUS_VAR); + } else if (v > 0xffff) { + res = 0xffff; + float_raise( float_flag_invalid STATUS_VAR); + } else { + res = v; + } + return res; +} + +/* FIXME: This looks broken. */ +uint64_t float64_to_uint64 (float64 a STATUS_PARAM) +{ + int64_t v; + + v = float64_val(int64_to_float64(INT64_MIN STATUS_VAR)); + v += float64_val(a); + v = float64_to_int64(make_float64(v) STATUS_VAR); + + return v - INT64_MIN; +} + +uint64_t float64_to_uint64_round_to_zero (float64 a STATUS_PARAM) +{ + int64_t v; + + v = float64_val(int64_to_float64(INT64_MIN STATUS_VAR)); + v += float64_val(a); + v = float64_to_int64_round_to_zero(make_float64(v) STATUS_VAR); + + return v - INT64_MIN; +} + +#define COMPARE(s, nan_exp) \ +INLINE int float ## s ## _compare_internal( float ## s a, float ## s b, \ + int is_quiet STATUS_PARAM ) \ +{ \ + flag aSign, bSign; \ + bits ## s av, bv; \ + a = float ## s ## _squash_input_denormal(a STATUS_VAR); \ + b = float ## s ## _squash_input_denormal(b STATUS_VAR); \ + \ + if (( ( extractFloat ## s ## Exp( a ) == nan_exp ) && \ + extractFloat ## s ## Frac( a ) ) || \ + ( ( extractFloat ## s ## Exp( b ) == nan_exp ) && \ + extractFloat ## s ## Frac( b ) )) { \ + if (!is_quiet || \ + float ## s ## _is_signaling_nan( a ) || \ + float ## s ## _is_signaling_nan( b ) ) { \ + float_raise( float_flag_invalid STATUS_VAR); \ + } \ + return float_relation_unordered; \ + } \ + aSign = extractFloat ## s ## Sign( a ); \ + bSign = extractFloat ## s ## Sign( b ); \ + av = float ## s ## _val(a); \ + bv = float ## s ## _val(b); \ + if ( aSign != bSign ) { \ + if ( (bits ## s) ( ( av | bv )<<1 ) == 0 ) { \ + /* zero case */ \ + return float_relation_equal; \ + } else { \ + return 1 - (2 * aSign); \ + } \ + } else { \ + if (av == bv) { \ + return float_relation_equal; \ + } else { \ + return 1 - 2 * (aSign ^ ( av < bv )); \ + } \ + } \ +} \ + \ +int float ## s ## _compare( float ## s a, float ## s b STATUS_PARAM ) \ +{ \ + return float ## s ## _compare_internal(a, b, 0 STATUS_VAR); \ +} \ + \ +int float ## s ## _compare_quiet( float ## s a, float ## s b STATUS_PARAM ) \ +{ \ + return float ## s ## _compare_internal(a, b, 1 STATUS_VAR); \ +} + +COMPARE(32, 0xff) +COMPARE(64, 0x7ff) + +INLINE int float128_compare_internal( float128 a, float128 b, + int is_quiet STATUS_PARAM ) +{ + flag aSign, bSign; + + if (( ( extractFloat128Exp( a ) == 0x7fff ) && + ( extractFloat128Frac0( a ) | extractFloat128Frac1( a ) ) ) || + ( ( extractFloat128Exp( b ) == 0x7fff ) && + ( extractFloat128Frac0( b ) | extractFloat128Frac1( b ) ) )) { + if (!is_quiet || + float128_is_signaling_nan( a ) || + float128_is_signaling_nan( b ) ) { + float_raise( float_flag_invalid STATUS_VAR); + } + return float_relation_unordered; + } + aSign = extractFloat128Sign( a ); + bSign = extractFloat128Sign( b ); + if ( aSign != bSign ) { + if ( ( ( ( a.high | b.high )<<1 ) | a.low | b.low ) == 0 ) { + /* zero case */ + return float_relation_equal; + } else { + return 1 - (2 * aSign); + } + } else { + if (a.low == b.low && a.high == b.high) { + return float_relation_equal; + } else { + return 1 - 2 * (aSign ^ ( lt128( a.high, a.low, b.high, b.low ) )); + } + } +} + +int float128_compare( float128 a, float128 b STATUS_PARAM ) +{ + return float128_compare_internal(a, b, 0 STATUS_VAR); +} + +int float128_compare_quiet( float128 a, float128 b STATUS_PARAM ) +{ + return float128_compare_internal(a, b, 1 STATUS_VAR); +} + +/* Multiply A by 2 raised to the power N. */ +float32 float32_scalbn( float32 a, int n STATUS_PARAM ) +{ + flag aSign; + int16 aExp; + bits32 aSig; + + a = float32_squash_input_denormal(a STATUS_VAR); + aSig = extractFloat32Frac( a ); + aExp = extractFloat32Exp( a ); + aSign = extractFloat32Sign( a ); + + if ( aExp == 0xFF ) { + return a; + } + if ( aExp != 0 ) + aSig |= 0x00800000; + else if ( aSig == 0 ) + return a; + + aExp += n - 1; + aSig <<= 7; + return normalizeRoundAndPackFloat32( aSign, aExp, aSig STATUS_VAR ); +} + +float64 float64_scalbn( float64 a, int n STATUS_PARAM ) +{ + flag aSign; + int16 aExp; + bits64 aSig; + + a = float64_squash_input_denormal(a STATUS_VAR); + aSig = extractFloat64Frac( a ); + aExp = extractFloat64Exp( a ); + aSign = extractFloat64Sign( a ); + + if ( aExp == 0x7FF ) { + return a; + } + if ( aExp != 0 ) + aSig |= LIT64( 0x0010000000000000 ); + else if ( aSig == 0 ) + return a; + + aExp += n - 1; + aSig <<= 10; + return normalizeRoundAndPackFloat64( aSign, aExp, aSig STATUS_VAR ); +} + +#ifdef FLOATX80 +floatx80 floatx80_scalbn( floatx80 a, int n STATUS_PARAM ) +{ + flag aSign; + int16 aExp; + bits64 aSig; + + aSig = extractFloatx80Frac( a ); + aExp = extractFloatx80Exp( a ); + aSign = extractFloatx80Sign( a ); + + if ( aExp == 0x7FF ) { + return a; + } + if (aExp == 0 && aSig == 0) + return a; + + aExp += n; + return normalizeRoundAndPackFloatx80( STATUS(floatx80_rounding_precision), + aSign, aExp, aSig, 0 STATUS_VAR ); +} +#endif + +#ifdef FLOAT128 +float128 float128_scalbn( float128 a, int n STATUS_PARAM ) +{ + flag aSign; + int32 aExp; + bits64 aSig0, aSig1; + + aSig1 = extractFloat128Frac1( a ); + aSig0 = extractFloat128Frac0( a ); + aExp = extractFloat128Exp( a ); + aSign = extractFloat128Sign( a ); + if ( aExp == 0x7FFF ) { + return a; + } + if ( aExp != 0 ) + aSig0 |= LIT64( 0x0001000000000000 ); + else if ( aSig0 == 0 && aSig1 == 0 ) + return a; + + aExp += n - 1; + return normalizeRoundAndPackFloat128( aSign, aExp, aSig0, aSig1 + STATUS_VAR ); + +} +#endif diff --git a/fpu/softfloat.h b/fpu/softfloat.h new file mode 100644 index 0000000..4a5345c --- /dev/null +++ b/fpu/softfloat.h @@ -0,0 +1,583 @@ +/*============================================================================ + +This C header file is part of the SoftFloat IEC/IEEE Floating-point Arithmetic +Package, Release 2b. + +Written by John R. Hauser. This work was made possible in part by the +International Computer Science Institute, located at Suite 600, 1947 Center +Street, Berkeley, California 94704. Funding was partially provided by the +National Science Foundation under grant MIP-9311980. The original version +of this code was written as part of a project to build a fixed-point vector +processor in collaboration with the University of California at Berkeley, +overseen by Profs. Nelson Morgan and John Wawrzynek. More information +is available through the Web page `http://www.cs.berkeley.edu/~jhauser/ +arithmetic/SoftFloat.html'. + +THIS SOFTWARE IS DISTRIBUTED AS IS, FOR FREE. Although reasonable effort has +been made to avoid it, THIS SOFTWARE MAY CONTAIN FAULTS THAT WILL AT TIMES +RESULT IN INCORRECT BEHAVIOR. USE OF THIS SOFTWARE IS RESTRICTED TO PERSONS +AND ORGANIZATIONS WHO CAN AND WILL TAKE FULL RESPONSIBILITY FOR ALL LOSSES, +COSTS, OR OTHER PROBLEMS THEY INCUR DUE TO THE SOFTWARE, AND WHO FURTHERMORE +EFFECTIVELY INDEMNIFY JOHN HAUSER AND THE INTERNATIONAL COMPUTER SCIENCE +INSTITUTE (possibly via similar legal warning) AGAINST ALL LOSSES, COSTS, OR +OTHER PROBLEMS INCURRED BY THEIR CUSTOMERS AND CLIENTS DUE TO THE SOFTWARE. + +Derivative works are acceptable, even for commercial purposes, so long as +(1) the source code for the derivative work includes prominent notice that +the work is derivative, and (2) the source code includes prominent notice with +these four paragraphs for those parts of this code that are retained. + +=============================================================================*/ + +#ifndef SOFTFLOAT_H +#define SOFTFLOAT_H + +#if defined(CONFIG_SOLARIS) && defined(CONFIG_NEEDS_LIBSUNMATH) +#include +#endif + +#include +#include "config.h" + +/*---------------------------------------------------------------------------- +| Each of the following `typedef's defines the most convenient type that holds +| integers of at least as many bits as specified. For example, `uint8' should +| be the most convenient type that can hold unsigned integers of as many as +| 8 bits. The `flag' type must be able to hold either a 0 or 1. For most +| implementations of C, `flag', `uint8', and `int8' should all be `typedef'ed +| to the same as `int'. +*----------------------------------------------------------------------------*/ +typedef uint8_t flag; +typedef uint8_t uint8; +typedef int8_t int8; +#ifndef _AIX +typedef int uint16; +typedef int int16; +#endif +typedef unsigned int uint32; +typedef signed int int32; +typedef uint64_t uint64; +typedef int64_t int64; + +/*---------------------------------------------------------------------------- +| Each of the following `typedef's defines a type that holds integers +| of _exactly_ the number of bits specified. For instance, for most +| implementation of C, `bits16' and `sbits16' should be `typedef'ed to +| `unsigned short int' and `signed short int' (or `short int'), respectively. +*----------------------------------------------------------------------------*/ +typedef uint8_t bits8; +typedef int8_t sbits8; +typedef uint16_t bits16; +typedef int16_t sbits16; +typedef uint32_t bits32; +typedef int32_t sbits32; +typedef uint64_t bits64; +typedef int64_t sbits64; + +#define LIT64( a ) a##LL +#define INLINE static inline + +/*---------------------------------------------------------------------------- +| The macro `FLOATX80' must be defined to enable the extended double-precision +| floating-point format `floatx80'. If this macro is not defined, the +| `floatx80' type will not be defined, and none of the functions that either +| input or output the `floatx80' type will be defined. The same applies to +| the `FLOAT128' macro and the quadruple-precision format `float128'. +*----------------------------------------------------------------------------*/ +#ifdef CONFIG_SOFTFLOAT +/* bit exact soft float support */ +#define FLOATX80 +#define FLOAT128 +#else +/* native float support */ +#if (defined(__i386__) || defined(__x86_64__)) && !defined(CONFIG_BSD) +#define FLOATX80 +#endif +#endif /* !CONFIG_SOFTFLOAT */ + +#define STATUS_PARAM , float_status *status +#define STATUS(field) status->field +#define STATUS_VAR , status + +/*---------------------------------------------------------------------------- +| Software IEC/IEEE floating-point ordering relations +*----------------------------------------------------------------------------*/ +enum { + float_relation_less = -1, + float_relation_equal = 0, + float_relation_greater = 1, + float_relation_unordered = 2 +}; + +#ifdef CONFIG_SOFTFLOAT +/*---------------------------------------------------------------------------- +| Software IEC/IEEE floating-point types. +*----------------------------------------------------------------------------*/ +/* Use structures for soft-float types. This prevents accidentally mixing + them with native int/float types. A sufficiently clever compiler and + sane ABI should be able to see though these structs. However + x86/gcc 3.x seems to struggle a bit, so leave them disabled by default. */ +//#define USE_SOFTFLOAT_STRUCT_TYPES +#ifdef USE_SOFTFLOAT_STRUCT_TYPES +typedef struct { + uint32_t v; +} float32; +/* The cast ensures an error if the wrong type is passed. */ +#define float32_val(x) (((float32)(x)).v) +#define make_float32(x) __extension__ ({ float32 f32_val = {x}; f32_val; }) +typedef struct { + uint64_t v; +} float64; +#define float64_val(x) (((float64)(x)).v) +#define make_float64(x) __extension__ ({ float64 f64_val = {x}; f64_val; }) +#else +typedef uint32_t float32; +typedef uint64_t float64; +#define float32_val(x) (x) +#define float64_val(x) (x) +#define make_float32(x) (x) +#define make_float64(x) (x) +#endif +#ifdef FLOATX80 +typedef struct { + uint64_t low; + uint16_t high; +} floatx80; +#endif +#ifdef FLOAT128 +typedef struct { +#ifdef HOST_WORDS_BIGENDIAN + uint64_t high, low; +#else + uint64_t low, high; +#endif +} float128; +#endif + +/*---------------------------------------------------------------------------- +| Software IEC/IEEE floating-point underflow tininess-detection mode. +*----------------------------------------------------------------------------*/ +enum { + float_tininess_after_rounding = 0, + float_tininess_before_rounding = 1 +}; + +/*---------------------------------------------------------------------------- +| Software IEC/IEEE floating-point rounding mode. +*----------------------------------------------------------------------------*/ +enum { + float_round_nearest_even = 0, + float_round_down = 1, + float_round_up = 2, + float_round_to_zero = 3 +}; + +/*---------------------------------------------------------------------------- +| Software IEC/IEEE floating-point exception flags. +*----------------------------------------------------------------------------*/ +enum { + float_flag_invalid = 1, + float_flag_divbyzero = 4, + float_flag_overflow = 8, + float_flag_underflow = 16, + float_flag_inexact = 32, + float_flag_input_denormal = 64 +}; + +typedef struct float_status { + signed char float_detect_tininess; + signed char float_rounding_mode; + signed char float_exception_flags; +#ifdef FLOATX80 + signed char floatx80_rounding_precision; +#endif + /* should denormalised results go to zero and set the inexact flag? */ + flag flush_to_zero; + /* should denormalised inputs go to zero and set the input_denormal flag? */ + flag flush_inputs_to_zero; + flag default_nan_mode; +} float_status; + +void set_float_rounding_mode(int val STATUS_PARAM); +void set_float_exception_flags(int val STATUS_PARAM); +INLINE void set_flush_to_zero(flag val STATUS_PARAM) +{ + STATUS(flush_to_zero) = val; +} +INLINE void set_flush_inputs_to_zero(flag val STATUS_PARAM) +{ + STATUS(flush_inputs_to_zero) = val; +} +INLINE void set_default_nan_mode(flag val STATUS_PARAM) +{ + STATUS(default_nan_mode) = val; +} +INLINE int get_float_exception_flags(float_status *status) +{ + return STATUS(float_exception_flags); +} +#ifdef FLOATX80 +void set_floatx80_rounding_precision(int val STATUS_PARAM); +#endif + +/*---------------------------------------------------------------------------- +| Routine to raise any or all of the software IEC/IEEE floating-point +| exception flags. +*----------------------------------------------------------------------------*/ +void float_raise( int8 flags STATUS_PARAM); + +/*---------------------------------------------------------------------------- +| Software IEC/IEEE integer-to-floating-point conversion routines. +*----------------------------------------------------------------------------*/ +float32 int32_to_float32( int STATUS_PARAM ); +float64 int32_to_float64( int STATUS_PARAM ); +float32 uint32_to_float32( unsigned int STATUS_PARAM ); +float64 uint32_to_float64( unsigned int STATUS_PARAM ); +#ifdef FLOATX80 +floatx80 int32_to_floatx80( int STATUS_PARAM ); +#endif +#ifdef FLOAT128 +float128 int32_to_float128( int STATUS_PARAM ); +#endif +float32 int64_to_float32( int64_t STATUS_PARAM ); +float32 uint64_to_float32( uint64_t STATUS_PARAM ); +float64 int64_to_float64( int64_t STATUS_PARAM ); +float64 uint64_to_float64( uint64_t STATUS_PARAM ); +#ifdef FLOATX80 +floatx80 int64_to_floatx80( int64_t STATUS_PARAM ); +#endif +#ifdef FLOAT128 +float128 int64_to_float128( int64_t STATUS_PARAM ); +#endif + +/*---------------------------------------------------------------------------- +| Software half-precision conversion routines. +*----------------------------------------------------------------------------*/ +bits16 float32_to_float16( float32, flag STATUS_PARAM ); +float32 float16_to_float32( bits16, flag STATUS_PARAM ); + +/*---------------------------------------------------------------------------- +| Software IEC/IEEE single-precision conversion routines. +*----------------------------------------------------------------------------*/ +int float32_to_int16_round_to_zero( float32 STATUS_PARAM ); +unsigned int float32_to_uint16_round_to_zero( float32 STATUS_PARAM ); +int float32_to_int32( float32 STATUS_PARAM ); +int float32_to_int32_round_to_zero( float32 STATUS_PARAM ); +unsigned int float32_to_uint32( float32 STATUS_PARAM ); +unsigned int float32_to_uint32_round_to_zero( float32 STATUS_PARAM ); +int64_t float32_to_int64( float32 STATUS_PARAM ); +int64_t float32_to_int64_round_to_zero( float32 STATUS_PARAM ); +float64 float32_to_float64( float32 STATUS_PARAM ); +#ifdef FLOATX80 +floatx80 float32_to_floatx80( float32 STATUS_PARAM ); +#endif +#ifdef FLOAT128 +float128 float32_to_float128( float32 STATUS_PARAM ); +#endif + +/*---------------------------------------------------------------------------- +| Software IEC/IEEE single-precision operations. +*----------------------------------------------------------------------------*/ +float32 float32_round_to_int( float32 STATUS_PARAM ); +float32 float32_add( float32, float32 STATUS_PARAM ); +float32 float32_sub( float32, float32 STATUS_PARAM ); +float32 float32_mul( float32, float32 STATUS_PARAM ); +float32 float32_div( float32, float32 STATUS_PARAM ); +float32 float32_rem( float32, float32 STATUS_PARAM ); +float32 float32_sqrt( float32 STATUS_PARAM ); +float32 float32_exp2( float32 STATUS_PARAM ); +float32 float32_log2( float32 STATUS_PARAM ); +int float32_eq( float32, float32 STATUS_PARAM ); +int float32_le( float32, float32 STATUS_PARAM ); +int float32_lt( float32, float32 STATUS_PARAM ); +int float32_eq_signaling( float32, float32 STATUS_PARAM ); +int float32_le_quiet( float32, float32 STATUS_PARAM ); +int float32_lt_quiet( float32, float32 STATUS_PARAM ); +int float32_compare( float32, float32 STATUS_PARAM ); +int float32_compare_quiet( float32, float32 STATUS_PARAM ); +int float32_is_quiet_nan( float32 ); +int float32_is_signaling_nan( float32 ); +float32 float32_maybe_silence_nan( float32 ); +float32 float32_scalbn( float32, int STATUS_PARAM ); + +INLINE float32 float32_abs(float32 a) +{ + /* Note that abs does *not* handle NaN specially, nor does + * it flush denormal inputs to zero. + */ + return make_float32(float32_val(a) & 0x7fffffff); +} + +INLINE float32 float32_chs(float32 a) +{ + /* Note that chs does *not* handle NaN specially, nor does + * it flush denormal inputs to zero. + */ + return make_float32(float32_val(a) ^ 0x80000000); +} + +INLINE int float32_is_infinity(float32 a) +{ + return (float32_val(a) & 0x7fffffff) == 0x7f800000; +} + +INLINE int float32_is_neg(float32 a) +{ + return float32_val(a) >> 31; +} + +INLINE int float32_is_zero(float32 a) +{ + return (float32_val(a) & 0x7fffffff) == 0; +} + +INLINE int float32_is_any_nan(float32 a) +{ + return ((float32_val(a) & ~(1 << 31)) > 0x7f800000UL); +} + +INLINE int float32_is_zero_or_denormal(float32 a) +{ + return (float32_val(a) & 0x7f800000) == 0; +} + +#define float32_zero make_float32(0) +#define float32_one make_float32(0x3f800000) +#define float32_ln2 make_float32(0x3f317218) + +/*---------------------------------------------------------------------------- +| Software IEC/IEEE double-precision conversion routines. +*----------------------------------------------------------------------------*/ +int float64_to_int16_round_to_zero( float64 STATUS_PARAM ); +unsigned int float64_to_uint16_round_to_zero( float64 STATUS_PARAM ); +int float64_to_int32( float64 STATUS_PARAM ); +int float64_to_int32_round_to_zero( float64 STATUS_PARAM ); +unsigned int float64_to_uint32( float64 STATUS_PARAM ); +unsigned int float64_to_uint32_round_to_zero( float64 STATUS_PARAM ); +int64_t float64_to_int64( float64 STATUS_PARAM ); +int64_t float64_to_int64_round_to_zero( float64 STATUS_PARAM ); +uint64_t float64_to_uint64 (float64 a STATUS_PARAM); +uint64_t float64_to_uint64_round_to_zero (float64 a STATUS_PARAM); +float32 float64_to_float32( float64 STATUS_PARAM ); +#ifdef FLOATX80 +floatx80 float64_to_floatx80( float64 STATUS_PARAM ); +#endif +#ifdef FLOAT128 +float128 float64_to_float128( float64 STATUS_PARAM ); +#endif + +/*---------------------------------------------------------------------------- +| Software IEC/IEEE double-precision operations. +*----------------------------------------------------------------------------*/ +float64 float64_round_to_int( float64 STATUS_PARAM ); +float64 float64_trunc_to_int( float64 STATUS_PARAM ); +float64 float64_add( float64, float64 STATUS_PARAM ); +float64 float64_sub( float64, float64 STATUS_PARAM ); +float64 float64_mul( float64, float64 STATUS_PARAM ); +float64 float64_div( float64, float64 STATUS_PARAM ); +float64 float64_rem( float64, float64 STATUS_PARAM ); +float64 float64_sqrt( float64 STATUS_PARAM ); +float64 float64_log2( float64 STATUS_PARAM ); +int float64_eq( float64, float64 STATUS_PARAM ); +int float64_le( float64, float64 STATUS_PARAM ); +int float64_lt( float64, float64 STATUS_PARAM ); +int float64_eq_signaling( float64, float64 STATUS_PARAM ); +int float64_le_quiet( float64, float64 STATUS_PARAM ); +int float64_lt_quiet( float64, float64 STATUS_PARAM ); +int float64_compare( float64, float64 STATUS_PARAM ); +int float64_compare_quiet( float64, float64 STATUS_PARAM ); +int float64_is_quiet_nan( float64 a ); +int float64_is_signaling_nan( float64 ); +float64 float64_maybe_silence_nan( float64 ); +float64 float64_scalbn( float64, int STATUS_PARAM ); + +INLINE float64 float64_abs(float64 a) +{ + /* Note that abs does *not* handle NaN specially, nor does + * it flush denormal inputs to zero. + */ + return make_float64(float64_val(a) & 0x7fffffffffffffffLL); +} + +INLINE float64 float64_chs(float64 a) +{ + /* Note that chs does *not* handle NaN specially, nor does + * it flush denormal inputs to zero. + */ + return make_float64(float64_val(a) ^ 0x8000000000000000LL); +} + +INLINE int float64_is_infinity(float64 a) +{ + return (float64_val(a) & 0x7fffffffffffffffLL ) == 0x7ff0000000000000LL; +} + +INLINE int float64_is_neg(float64 a) +{ + return float64_val(a) >> 63; +} + +INLINE int float64_is_zero(float64 a) +{ + return (float64_val(a) & 0x7fffffffffffffffLL) == 0; +} + +INLINE int float64_is_any_nan(float64 a) +{ + return ((float64_val(a) & ~(1ULL << 63)) > 0x7ff0000000000000ULL); +} + +#define float64_zero make_float64(0) +#define float64_one make_float64(0x3ff0000000000000LL) +#define float64_ln2 make_float64(0x3fe62e42fefa39efLL) + +#ifdef FLOATX80 + +/*---------------------------------------------------------------------------- +| Software IEC/IEEE extended double-precision conversion routines. +*----------------------------------------------------------------------------*/ +int floatx80_to_int32( floatx80 STATUS_PARAM ); +int floatx80_to_int32_round_to_zero( floatx80 STATUS_PARAM ); +int64_t floatx80_to_int64( floatx80 STATUS_PARAM ); +int64_t floatx80_to_int64_round_to_zero( floatx80 STATUS_PARAM ); +float32 floatx80_to_float32( floatx80 STATUS_PARAM ); +float64 floatx80_to_float64( floatx80 STATUS_PARAM ); +#ifdef FLOAT128 +float128 floatx80_to_float128( floatx80 STATUS_PARAM ); +#endif + +/*---------------------------------------------------------------------------- +| Software IEC/IEEE extended double-precision operations. +*----------------------------------------------------------------------------*/ +floatx80 floatx80_round_to_int( floatx80 STATUS_PARAM ); +floatx80 floatx80_add( floatx80, floatx80 STATUS_PARAM ); +floatx80 floatx80_sub( floatx80, floatx80 STATUS_PARAM ); +floatx80 floatx80_mul( floatx80, floatx80 STATUS_PARAM ); +floatx80 floatx80_div( floatx80, floatx80 STATUS_PARAM ); +floatx80 floatx80_rem( floatx80, floatx80 STATUS_PARAM ); +floatx80 floatx80_sqrt( floatx80 STATUS_PARAM ); +int floatx80_eq( floatx80, floatx80 STATUS_PARAM ); +int floatx80_le( floatx80, floatx80 STATUS_PARAM ); +int floatx80_lt( floatx80, floatx80 STATUS_PARAM ); +int floatx80_eq_signaling( floatx80, floatx80 STATUS_PARAM ); +int floatx80_le_quiet( floatx80, floatx80 STATUS_PARAM ); +int floatx80_lt_quiet( floatx80, floatx80 STATUS_PARAM ); +int floatx80_is_quiet_nan( floatx80 ); +int floatx80_is_signaling_nan( floatx80 ); +floatx80 floatx80_maybe_silence_nan( floatx80 ); +floatx80 floatx80_scalbn( floatx80, int STATUS_PARAM ); + +INLINE floatx80 floatx80_abs(floatx80 a) +{ + a.high &= 0x7fff; + return a; +} + +INLINE floatx80 floatx80_chs(floatx80 a) +{ + a.high ^= 0x8000; + return a; +} + +INLINE int floatx80_is_infinity(floatx80 a) +{ + return (a.high & 0x7fff) == 0x7fff && a.low == 0; +} + +INLINE int floatx80_is_neg(floatx80 a) +{ + return a.high >> 15; +} + +INLINE int floatx80_is_zero(floatx80 a) +{ + return (a.high & 0x7fff) == 0 && a.low == 0; +} + +INLINE int floatx80_is_any_nan(floatx80 a) +{ + return ((a.high & 0x7fff) == 0x7fff) && (a.low<<1); +} + +#endif + +#ifdef FLOAT128 + +/*---------------------------------------------------------------------------- +| Software IEC/IEEE quadruple-precision conversion routines. +*----------------------------------------------------------------------------*/ +int float128_to_int32( float128 STATUS_PARAM ); +int float128_to_int32_round_to_zero( float128 STATUS_PARAM ); +int64_t float128_to_int64( float128 STATUS_PARAM ); +int64_t float128_to_int64_round_to_zero( float128 STATUS_PARAM ); +float32 float128_to_float32( float128 STATUS_PARAM ); +float64 float128_to_float64( float128 STATUS_PARAM ); +#ifdef FLOATX80 +floatx80 float128_to_floatx80( float128 STATUS_PARAM ); +#endif + +/*---------------------------------------------------------------------------- +| Software IEC/IEEE quadruple-precision operations. +*----------------------------------------------------------------------------*/ +float128 float128_round_to_int( float128 STATUS_PARAM ); +float128 float128_add( float128, float128 STATUS_PARAM ); +float128 float128_sub( float128, float128 STATUS_PARAM ); +float128 float128_mul( float128, float128 STATUS_PARAM ); +float128 float128_div( float128, float128 STATUS_PARAM ); +float128 float128_rem( float128, float128 STATUS_PARAM ); +float128 float128_sqrt( float128 STATUS_PARAM ); +int float128_eq( float128, float128 STATUS_PARAM ); +int float128_le( float128, float128 STATUS_PARAM ); +int float128_lt( float128, float128 STATUS_PARAM ); +int float128_eq_signaling( float128, float128 STATUS_PARAM ); +int float128_le_quiet( float128, float128 STATUS_PARAM ); +int float128_lt_quiet( float128, float128 STATUS_PARAM ); +int float128_compare( float128, float128 STATUS_PARAM ); +int float128_compare_quiet( float128, float128 STATUS_PARAM ); +int float128_is_quiet_nan( float128 ); +int float128_is_signaling_nan( float128 ); +float128 float128_maybe_silence_nan( float128 ); +float128 float128_scalbn( float128, int STATUS_PARAM ); + +INLINE float128 float128_abs(float128 a) +{ + a.high &= 0x7fffffffffffffffLL; + return a; +} + +INLINE float128 float128_chs(float128 a) +{ + a.high ^= 0x8000000000000000LL; + return a; +} + +INLINE int float128_is_infinity(float128 a) +{ + return (a.high & 0x7fffffffffffffffLL) == 0x7fff000000000000LL && a.low == 0; +} + +INLINE int float128_is_neg(float128 a) +{ + return a.high >> 63; +} + +INLINE int float128_is_zero(float128 a) +{ + return (a.high & 0x7fffffffffffffffLL) == 0 && a.low == 0; +} + +INLINE int float128_is_any_nan(float128 a) +{ + return ((a.high >> 48) & 0x7fff) == 0x7fff && + ((a.low != 0) || ((a.high & 0xffffffffffffLL) != 0)); +} + +#endif + +#else /* CONFIG_SOFTFLOAT */ + +#include "softfloat-native.h" + +#endif /* !CONFIG_SOFTFLOAT */ + +#endif /* !SOFTFLOAT_H */ diff --git a/fsdev/qemu-fsdev.c b/fsdev/qemu-fsdev.c new file mode 100644 index 0000000..0b33290 --- /dev/null +++ b/fsdev/qemu-fsdev.c @@ -0,0 +1,100 @@ +/* + * Virtio 9p + * + * Copyright IBM, Corp. 2010 + * + * Authors: + * Gautham R Shenoy + * + * This work is licensed under the terms of the GNU GPL, version 2. See + * the COPYING file in the top-level directory. + * + */ +#include +#include +#include "qemu-fsdev.h" +#include "qemu-queue.h" +#include "osdep.h" +#include "qemu-common.h" +#include "qemu-config.h" + +static QTAILQ_HEAD(FsTypeEntry_head, FsTypeListEntry) fstype_entries = + QTAILQ_HEAD_INITIALIZER(fstype_entries); + +static FsTypeTable FsTypes[] = { + { .name = "local", .ops = &local_ops}, +}; + +int qemu_fsdev_add(QemuOpts *opts) +{ + struct FsTypeListEntry *fsle; + int i; + const char *fsdev_id = qemu_opts_id(opts); + const char *fstype = qemu_opt_get(opts, "fstype"); + const char *path = qemu_opt_get(opts, "path"); + const char *sec_model = qemu_opt_get(opts, "security_model"); + + if (!fsdev_id) { + fprintf(stderr, "fsdev: No id specified\n"); + return -1; + } + + if (fstype) { + for (i = 0; i < ARRAY_SIZE(FsTypes); i++) { + if (strcmp(FsTypes[i].name, fstype) == 0) { + break; + } + } + + if (i == ARRAY_SIZE(FsTypes)) { + fprintf(stderr, "fsdev: fstype %s not found\n", fstype); + return -1; + } + } else { + fprintf(stderr, "fsdev: No fstype specified\n"); + return -1; + } + + if (!sec_model) { + fprintf(stderr, "fsdev: No security_model specified.\n"); + return -1; + } + + if (!path) { + fprintf(stderr, "fsdev: No path specified.\n"); + return -1; + } + + fsle = qemu_malloc(sizeof(*fsle)); + + fsle->fse.fsdev_id = qemu_strdup(fsdev_id); + fsle->fse.path = qemu_strdup(path); + fsle->fse.security_model = qemu_strdup(sec_model); + fsle->fse.ops = FsTypes[i].ops; + + QTAILQ_INSERT_TAIL(&fstype_entries, fsle, next); + return 0; + +} + +FsTypeEntry *get_fsdev_fsentry(char *id) +{ + if (id) { + struct FsTypeListEntry *fsle; + + QTAILQ_FOREACH(fsle, &fstype_entries, next) { + if (strcmp(fsle->fse.fsdev_id, id) == 0) { + return &fsle->fse; + } + } + } + return NULL; +} + +static void fsdev_register_config(void) +{ + qemu_add_opts(&qemu_fsdev_opts); + qemu_add_opts(&qemu_virtfs_opts); +} +machine_init(fsdev_register_config); + diff --git a/fsdev/qemu-fsdev.h b/fsdev/qemu-fsdev.h new file mode 100644 index 0000000..a704043 --- /dev/null +++ b/fsdev/qemu-fsdev.h @@ -0,0 +1,55 @@ +/* + * Virtio 9p + * + * Copyright IBM, Corp. 2010 + * + * Authors: + * Gautham R Shenoy + * + * This work is licensed under the terms of the GNU GPL, version 2. See + * the COPYING file in the top-level directory. + * + */ +#ifndef QEMU_FSDEV_H +#define QEMU_FSDEV_H +#include "qemu-option.h" +#include "hw/file-op-9p.h" + + +/* + * A table to store the various file systems and their callback operations. + * ----------------- + * fstype | ops + * ----------------- + * local | local_ops + * . | + * . | + * . | + * . | + * ----------------- + * etc + */ +typedef struct FsTypeTable { + const char *name; + FileOperations *ops; +} FsTypeTable; + +/* + * Structure to store the various fsdev's passed through command line. + */ +typedef struct FsTypeEntry { + char *fsdev_id; + char *path; + char *security_model; + FileOperations *ops; +} FsTypeEntry; + +typedef struct FsTypeListEntry { + FsTypeEntry fse; + QTAILQ_ENTRY(FsTypeListEntry) next; +} FsTypeListEntry; + +int qemu_fsdev_add(QemuOpts *opts); +FsTypeEntry *get_fsdev_fsentry(char *id); +extern FileOperations local_ops; +#endif diff --git a/gdb-xml/arm-core.xml b/gdb-xml/arm-core.xml new file mode 100644 index 0000000..6012f34 --- /dev/null +++ b/gdb-xml/arm-core.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gdb-xml/arm-neon.xml b/gdb-xml/arm-neon.xml new file mode 100644 index 0000000..ce3ee03 --- /dev/null +++ b/gdb-xml/arm-neon.xml @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gdb-xml/arm-vfp.xml b/gdb-xml/arm-vfp.xml new file mode 100644 index 0000000..b20881e --- /dev/null +++ b/gdb-xml/arm-vfp.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gdb-xml/arm-vfp3.xml b/gdb-xml/arm-vfp3.xml new file mode 100644 index 0000000..227afd8 --- /dev/null +++ b/gdb-xml/arm-vfp3.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gdb-xml/cf-core.xml b/gdb-xml/cf-core.xml new file mode 100644 index 0000000..b90af30 --- /dev/null +++ b/gdb-xml/cf-core.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gdb-xml/cf-fp.xml b/gdb-xml/cf-fp.xml new file mode 100644 index 0000000..bf71c32 --- /dev/null +++ b/gdb-xml/cf-fp.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + , + + diff --git a/gdb-xml/power-altivec.xml b/gdb-xml/power-altivec.xml new file mode 100644 index 0000000..84f4d27 --- /dev/null +++ b/gdb-xml/power-altivec.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gdb-xml/power-core.xml b/gdb-xml/power-core.xml new file mode 100644 index 0000000..0c69e8c --- /dev/null +++ b/gdb-xml/power-core.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gdb-xml/power-fpu.xml b/gdb-xml/power-fpu.xml new file mode 100644 index 0000000..3870551 --- /dev/null +++ b/gdb-xml/power-fpu.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gdb-xml/power-spe.xml b/gdb-xml/power-spe.xml new file mode 100644 index 0000000..57740cc --- /dev/null +++ b/gdb-xml/power-spe.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gdb-xml/power64-core.xml b/gdb-xml/power64-core.xml new file mode 100644 index 0000000..6cc1531 --- /dev/null +++ b/gdb-xml/power64-core.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gdbstub.c b/gdbstub.c new file mode 100644 index 0000000..d6556c9 --- /dev/null +++ b/gdbstub.c @@ -0,0 +1,2694 @@ +/* + * gdb server stub + * + * Copyright (c) 2003-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 . + */ +#include "config.h" +#include "qemu-common.h" +#ifdef CONFIG_USER_ONLY +#include +#include +#include +#include +#include +#include +#include + +#include "qemu.h" +#else +#include "monitor.h" +#include "qemu-char.h" +#include "sysemu.h" +#include "gdbstub.h" +#endif + +#define MAX_PACKET_LENGTH 4096 + +#include "exec-all.h" +#include "qemu_socket.h" +#include "kvm.h" + + +enum { + GDB_SIGNAL_0 = 0, + GDB_SIGNAL_INT = 2, + GDB_SIGNAL_TRAP = 5, + GDB_SIGNAL_UNKNOWN = 143 +}; + +#ifdef CONFIG_USER_ONLY + +/* Map target signal numbers to GDB protocol signal numbers and vice + * versa. For user emulation's currently supported systems, we can + * assume most signals are defined. + */ + +static int gdb_signal_table[] = { + 0, + TARGET_SIGHUP, + TARGET_SIGINT, + TARGET_SIGQUIT, + TARGET_SIGILL, + TARGET_SIGTRAP, + TARGET_SIGABRT, + -1, /* SIGEMT */ + TARGET_SIGFPE, + TARGET_SIGKILL, + TARGET_SIGBUS, + TARGET_SIGSEGV, + TARGET_SIGSYS, + TARGET_SIGPIPE, + TARGET_SIGALRM, + TARGET_SIGTERM, + TARGET_SIGURG, + TARGET_SIGSTOP, + TARGET_SIGTSTP, + TARGET_SIGCONT, + TARGET_SIGCHLD, + TARGET_SIGTTIN, + TARGET_SIGTTOU, + TARGET_SIGIO, + TARGET_SIGXCPU, + TARGET_SIGXFSZ, + TARGET_SIGVTALRM, + TARGET_SIGPROF, + TARGET_SIGWINCH, + -1, /* SIGLOST */ + TARGET_SIGUSR1, + TARGET_SIGUSR2, +#ifdef TARGET_SIGPWR + TARGET_SIGPWR, +#else + -1, +#endif + -1, /* SIGPOLL */ + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, +#ifdef __SIGRTMIN + __SIGRTMIN + 1, + __SIGRTMIN + 2, + __SIGRTMIN + 3, + __SIGRTMIN + 4, + __SIGRTMIN + 5, + __SIGRTMIN + 6, + __SIGRTMIN + 7, + __SIGRTMIN + 8, + __SIGRTMIN + 9, + __SIGRTMIN + 10, + __SIGRTMIN + 11, + __SIGRTMIN + 12, + __SIGRTMIN + 13, + __SIGRTMIN + 14, + __SIGRTMIN + 15, + __SIGRTMIN + 16, + __SIGRTMIN + 17, + __SIGRTMIN + 18, + __SIGRTMIN + 19, + __SIGRTMIN + 20, + __SIGRTMIN + 21, + __SIGRTMIN + 22, + __SIGRTMIN + 23, + __SIGRTMIN + 24, + __SIGRTMIN + 25, + __SIGRTMIN + 26, + __SIGRTMIN + 27, + __SIGRTMIN + 28, + __SIGRTMIN + 29, + __SIGRTMIN + 30, + __SIGRTMIN + 31, + -1, /* SIGCANCEL */ + __SIGRTMIN, + __SIGRTMIN + 32, + __SIGRTMIN + 33, + __SIGRTMIN + 34, + __SIGRTMIN + 35, + __SIGRTMIN + 36, + __SIGRTMIN + 37, + __SIGRTMIN + 38, + __SIGRTMIN + 39, + __SIGRTMIN + 40, + __SIGRTMIN + 41, + __SIGRTMIN + 42, + __SIGRTMIN + 43, + __SIGRTMIN + 44, + __SIGRTMIN + 45, + __SIGRTMIN + 46, + __SIGRTMIN + 47, + __SIGRTMIN + 48, + __SIGRTMIN + 49, + __SIGRTMIN + 50, + __SIGRTMIN + 51, + __SIGRTMIN + 52, + __SIGRTMIN + 53, + __SIGRTMIN + 54, + __SIGRTMIN + 55, + __SIGRTMIN + 56, + __SIGRTMIN + 57, + __SIGRTMIN + 58, + __SIGRTMIN + 59, + __SIGRTMIN + 60, + __SIGRTMIN + 61, + __SIGRTMIN + 62, + __SIGRTMIN + 63, + __SIGRTMIN + 64, + __SIGRTMIN + 65, + __SIGRTMIN + 66, + __SIGRTMIN + 67, + __SIGRTMIN + 68, + __SIGRTMIN + 69, + __SIGRTMIN + 70, + __SIGRTMIN + 71, + __SIGRTMIN + 72, + __SIGRTMIN + 73, + __SIGRTMIN + 74, + __SIGRTMIN + 75, + __SIGRTMIN + 76, + __SIGRTMIN + 77, + __SIGRTMIN + 78, + __SIGRTMIN + 79, + __SIGRTMIN + 80, + __SIGRTMIN + 81, + __SIGRTMIN + 82, + __SIGRTMIN + 83, + __SIGRTMIN + 84, + __SIGRTMIN + 85, + __SIGRTMIN + 86, + __SIGRTMIN + 87, + __SIGRTMIN + 88, + __SIGRTMIN + 89, + __SIGRTMIN + 90, + __SIGRTMIN + 91, + __SIGRTMIN + 92, + __SIGRTMIN + 93, + __SIGRTMIN + 94, + __SIGRTMIN + 95, + -1, /* SIGINFO */ + -1, /* UNKNOWN */ + -1, /* DEFAULT */ + -1, + -1, + -1, + -1, + -1, + -1 +#endif +}; +#else +/* In system mode we only need SIGINT and SIGTRAP; other signals + are not yet supported. */ + +enum { + TARGET_SIGINT = 2, + TARGET_SIGTRAP = 5 +}; + +static int gdb_signal_table[] = { + -1, + -1, + TARGET_SIGINT, + -1, + -1, + TARGET_SIGTRAP +}; +#endif + +#ifdef CONFIG_USER_ONLY +static int target_signal_to_gdb (int sig) +{ + int i; + for (i = 0; i < ARRAY_SIZE (gdb_signal_table); i++) + if (gdb_signal_table[i] == sig) + return i; + return GDB_SIGNAL_UNKNOWN; +} +#endif + +static int gdb_signal_to_target (int sig) +{ + if (sig < ARRAY_SIZE (gdb_signal_table)) + return gdb_signal_table[sig]; + else + return -1; +} + +//#define DEBUG_GDB + +typedef struct GDBRegisterState { + int base_reg; + int num_regs; + gdb_reg_cb get_reg; + gdb_reg_cb set_reg; + const char *xml; + struct GDBRegisterState *next; +} GDBRegisterState; + +enum RSState { + RS_INACTIVE, + RS_IDLE, + RS_GETLINE, + RS_CHKSUM1, + RS_CHKSUM2, + RS_SYSCALL, +}; +typedef struct GDBState { + CPUState *c_cpu; /* current CPU for step/continue ops */ + CPUState *g_cpu; /* current CPU for other ops */ + CPUState *query_cpu; /* for q{f|s}ThreadInfo */ + enum RSState state; /* parsing state */ + char line_buf[MAX_PACKET_LENGTH]; + int line_buf_index; + int line_csum; + uint8_t last_packet[MAX_PACKET_LENGTH + 4]; + int last_packet_len; + int signal; +#ifdef CONFIG_USER_ONLY + int fd; + int running_state; +#else + CharDriverState *chr; + CharDriverState *mon_chr; +#endif +} GDBState; + +/* By default use no IRQs and no timers while single stepping so as to + * make single stepping like an ICE HW step. + */ +static int sstep_flags = SSTEP_ENABLE|SSTEP_NOIRQ|SSTEP_NOTIMER; + +static GDBState *gdbserver_state; + +/* This is an ugly hack to cope with both new and old gdb. + If gdb sends qXfer:features:read then assume we're talking to a newish + gdb that understands target descriptions. */ +static int gdb_has_xml; + +#ifdef CONFIG_USER_ONLY +/* XXX: This is not thread safe. Do we care? */ +static int gdbserver_fd = -1; + +static int get_char(GDBState *s) +{ + uint8_t ch; + int ret; + + for(;;) { + ret = recv(s->fd, &ch, 1, 0); + if (ret < 0) { + if (errno == ECONNRESET) + s->fd = -1; + if (errno != EINTR && errno != EAGAIN) + return -1; + } else if (ret == 0) { + close(s->fd); + s->fd = -1; + return -1; + } else { + break; + } + } + return ch; +} +#endif + +static gdb_syscall_complete_cb gdb_current_syscall_cb; + +static enum { + GDB_SYS_UNKNOWN, + GDB_SYS_ENABLED, + GDB_SYS_DISABLED, +} gdb_syscall_mode; + +/* If gdb is connected when the first semihosting syscall occurs then use + remote gdb syscalls. Otherwise use native file IO. */ +int use_gdb_syscalls(void) +{ + if (gdb_syscall_mode == GDB_SYS_UNKNOWN) { + gdb_syscall_mode = (gdbserver_state ? GDB_SYS_ENABLED + : GDB_SYS_DISABLED); + } + return gdb_syscall_mode == GDB_SYS_ENABLED; +} + +/* Resume execution. */ +static inline void gdb_continue(GDBState *s) +{ +#ifdef CONFIG_USER_ONLY + s->running_state = 1; +#else + vm_start(); +#endif +} + +static void put_buffer(GDBState *s, const uint8_t *buf, int len) +{ +#ifdef CONFIG_USER_ONLY + int ret; + + while (len > 0) { + ret = send(s->fd, buf, len, 0); + if (ret < 0) { + if (errno != EINTR && errno != EAGAIN) + return; + } else { + buf += ret; + len -= ret; + } + } +#else + qemu_chr_write(s->chr, buf, len); +#endif +} + +static inline int fromhex(int v) +{ + if (v >= '0' && v <= '9') + return v - '0'; + else if (v >= 'A' && v <= 'F') + return v - 'A' + 10; + else if (v >= 'a' && v <= 'f') + return v - 'a' + 10; + else + return 0; +} + +static inline int tohex(int v) +{ + if (v < 10) + return v + '0'; + else + return v - 10 + 'a'; +} + +static void memtohex(char *buf, const uint8_t *mem, int len) +{ + int i, c; + char *q; + q = buf; + for(i = 0; i < len; i++) { + c = mem[i]; + *q++ = tohex(c >> 4); + *q++ = tohex(c & 0xf); + } + *q = '\0'; +} + +static void hextomem(uint8_t *mem, const char *buf, int len) +{ + int i; + + for(i = 0; i < len; i++) { + mem[i] = (fromhex(buf[0]) << 4) | fromhex(buf[1]); + buf += 2; + } +} + +/* return -1 if error, 0 if OK */ +static int put_packet_binary(GDBState *s, const char *buf, int len) +{ + int csum, i; + uint8_t *p; + + for(;;) { + p = s->last_packet; + *(p++) = '$'; + memcpy(p, buf, len); + p += len; + csum = 0; + for(i = 0; i < len; i++) { + csum += buf[i]; + } + *(p++) = '#'; + *(p++) = tohex((csum >> 4) & 0xf); + *(p++) = tohex((csum) & 0xf); + + s->last_packet_len = p - s->last_packet; + put_buffer(s, (uint8_t *)s->last_packet, s->last_packet_len); + +#ifdef CONFIG_USER_ONLY + i = get_char(s); + if (i < 0) + return -1; + if (i == '+') + break; +#else + break; +#endif + } + return 0; +} + +/* return -1 if error, 0 if OK */ +static int put_packet(GDBState *s, const char *buf) +{ +#ifdef DEBUG_GDB + printf("reply='%s'\n", buf); +#endif + + return put_packet_binary(s, buf, strlen(buf)); +} + +/* The GDB remote protocol transfers values in target byte order. This means + we can use the raw memory access routines to access the value buffer. + Conveniently, these also handle the case where the buffer is mis-aligned. + */ +#define GET_REG8(val) do { \ + stb_p(mem_buf, val); \ + return 1; \ + } while(0) +#define GET_REG16(val) do { \ + stw_p(mem_buf, val); \ + return 2; \ + } while(0) +#define GET_REG32(val) do { \ + stl_p(mem_buf, val); \ + return 4; \ + } while(0) +#define GET_REG64(val) do { \ + stq_p(mem_buf, val); \ + return 8; \ + } while(0) + +#if TARGET_LONG_BITS == 64 +#define GET_REGL(val) GET_REG64(val) +#define ldtul_p(addr) ldq_p(addr) +#else +#define GET_REGL(val) GET_REG32(val) +#define ldtul_p(addr) ldl_p(addr) +#endif + +#if defined(TARGET_I386) + +#ifdef TARGET_X86_64 +static const int gpr_map[16] = { + R_EAX, R_EBX, R_ECX, R_EDX, R_ESI, R_EDI, R_EBP, R_ESP, + 8, 9, 10, 11, 12, 13, 14, 15 +}; +#else +#define gpr_map gpr_map32 +#endif +static const int gpr_map32[8] = { 0, 1, 2, 3, 4, 5, 6, 7 }; + +#define NUM_CORE_REGS (CPU_NB_REGS * 2 + 25) + +#define IDX_IP_REG CPU_NB_REGS +#define IDX_FLAGS_REG (IDX_IP_REG + 1) +#define IDX_SEG_REGS (IDX_FLAGS_REG + 1) +#define IDX_FP_REGS (IDX_SEG_REGS + 6) +#define IDX_XMM_REGS (IDX_FP_REGS + 16) +#define IDX_MXCSR_REG (IDX_XMM_REGS + CPU_NB_REGS) + +static int cpu_gdb_read_register(CPUState *env, uint8_t *mem_buf, int n) +{ + if (n < CPU_NB_REGS) { + if (TARGET_LONG_BITS == 64 && env->hflags & HF_CS64_MASK) { + GET_REG64(env->regs[gpr_map[n]]); + } else if (n < CPU_NB_REGS32) { + GET_REG32(env->regs[gpr_map32[n]]); + } + } else if (n >= IDX_FP_REGS && n < IDX_FP_REGS + 8) { +#ifdef USE_X86LDOUBLE + /* FIXME: byteswap float values - after fixing fpregs layout. */ + memcpy(mem_buf, &env->fpregs[n - IDX_FP_REGS], 10); +#else + memset(mem_buf, 0, 10); +#endif + return 10; + } else if (n >= IDX_XMM_REGS && n < IDX_XMM_REGS + CPU_NB_REGS) { + n -= IDX_XMM_REGS; + if (n < CPU_NB_REGS32 || + (TARGET_LONG_BITS == 64 && env->hflags & HF_CS64_MASK)) { + stq_p(mem_buf, env->xmm_regs[n].XMM_Q(0)); + stq_p(mem_buf + 8, env->xmm_regs[n].XMM_Q(1)); + return 16; + } + } else { + switch (n) { + case IDX_IP_REG: + if (TARGET_LONG_BITS == 64 && env->hflags & HF_CS64_MASK) { + GET_REG64(env->eip); + } else { + GET_REG32(env->eip); + } + case IDX_FLAGS_REG: GET_REG32(env->eflags); + + case IDX_SEG_REGS: GET_REG32(env->segs[R_CS].selector); + case IDX_SEG_REGS + 1: GET_REG32(env->segs[R_SS].selector); + case IDX_SEG_REGS + 2: GET_REG32(env->segs[R_DS].selector); + case IDX_SEG_REGS + 3: GET_REG32(env->segs[R_ES].selector); + case IDX_SEG_REGS + 4: GET_REG32(env->segs[R_FS].selector); + case IDX_SEG_REGS + 5: GET_REG32(env->segs[R_GS].selector); + + case IDX_FP_REGS + 8: GET_REG32(env->fpuc); + case IDX_FP_REGS + 9: GET_REG32((env->fpus & ~0x3800) | + (env->fpstt & 0x7) << 11); + case IDX_FP_REGS + 10: GET_REG32(0); /* ftag */ + case IDX_FP_REGS + 11: GET_REG32(0); /* fiseg */ + case IDX_FP_REGS + 12: GET_REG32(0); /* fioff */ + case IDX_FP_REGS + 13: GET_REG32(0); /* foseg */ + case IDX_FP_REGS + 14: GET_REG32(0); /* fooff */ + case IDX_FP_REGS + 15: GET_REG32(0); /* fop */ + + case IDX_MXCSR_REG: GET_REG32(env->mxcsr); + } + } + return 0; +} + +static int cpu_x86_gdb_load_seg(CPUState *env, int sreg, uint8_t *mem_buf) +{ + uint16_t selector = ldl_p(mem_buf); + + if (selector != env->segs[sreg].selector) { +#if defined(CONFIG_USER_ONLY) + cpu_x86_load_seg(env, sreg, selector); +#else + unsigned int limit, flags; + target_ulong base; + + if (!(env->cr[0] & CR0_PE_MASK) || (env->eflags & VM_MASK)) { + base = selector << 4; + limit = 0xffff; + flags = 0; + } else { + if (!cpu_x86_get_descr_debug(env, selector, &base, &limit, &flags)) + return 4; + } + cpu_x86_load_seg_cache(env, sreg, selector, base, limit, flags); +#endif + } + return 4; +} + +static int cpu_gdb_write_register(CPUState *env, uint8_t *mem_buf, int n) +{ + uint32_t tmp; + + if (n < CPU_NB_REGS) { + if (TARGET_LONG_BITS == 64 && env->hflags & HF_CS64_MASK) { + env->regs[gpr_map[n]] = ldtul_p(mem_buf); + return sizeof(target_ulong); + } else if (n < CPU_NB_REGS32) { + n = gpr_map32[n]; + env->regs[n] &= ~0xffffffffUL; + env->regs[n] |= (uint32_t)ldl_p(mem_buf); + return 4; + } + } else if (n >= IDX_FP_REGS && n < IDX_FP_REGS + 8) { +#ifdef USE_X86LDOUBLE + /* FIXME: byteswap float values - after fixing fpregs layout. */ + memcpy(&env->fpregs[n - IDX_FP_REGS], mem_buf, 10); +#endif + return 10; + } else if (n >= IDX_XMM_REGS && n < IDX_XMM_REGS + CPU_NB_REGS) { + n -= IDX_XMM_REGS; + if (n < CPU_NB_REGS32 || + (TARGET_LONG_BITS == 64 && env->hflags & HF_CS64_MASK)) { + env->xmm_regs[n].XMM_Q(0) = ldq_p(mem_buf); + env->xmm_regs[n].XMM_Q(1) = ldq_p(mem_buf + 8); + return 16; + } + } else { + switch (n) { + case IDX_IP_REG: + if (TARGET_LONG_BITS == 64 && env->hflags & HF_CS64_MASK) { + env->eip = ldq_p(mem_buf); + return 8; + } else { + env->eip &= ~0xffffffffUL; + env->eip |= (uint32_t)ldl_p(mem_buf); + return 4; + } + case IDX_FLAGS_REG: + env->eflags = ldl_p(mem_buf); + return 4; + + case IDX_SEG_REGS: return cpu_x86_gdb_load_seg(env, R_CS, mem_buf); + case IDX_SEG_REGS + 1: return cpu_x86_gdb_load_seg(env, R_SS, mem_buf); + case IDX_SEG_REGS + 2: return cpu_x86_gdb_load_seg(env, R_DS, mem_buf); + case IDX_SEG_REGS + 3: return cpu_x86_gdb_load_seg(env, R_ES, mem_buf); + case IDX_SEG_REGS + 4: return cpu_x86_gdb_load_seg(env, R_FS, mem_buf); + case IDX_SEG_REGS + 5: return cpu_x86_gdb_load_seg(env, R_GS, mem_buf); + + case IDX_FP_REGS + 8: + env->fpuc = ldl_p(mem_buf); + return 4; + case IDX_FP_REGS + 9: + tmp = ldl_p(mem_buf); + env->fpstt = (tmp >> 11) & 7; + env->fpus = tmp & ~0x3800; + return 4; + case IDX_FP_REGS + 10: /* ftag */ return 4; + case IDX_FP_REGS + 11: /* fiseg */ return 4; + case IDX_FP_REGS + 12: /* fioff */ return 4; + case IDX_FP_REGS + 13: /* foseg */ return 4; + case IDX_FP_REGS + 14: /* fooff */ return 4; + case IDX_FP_REGS + 15: /* fop */ return 4; + + case IDX_MXCSR_REG: + env->mxcsr = ldl_p(mem_buf); + return 4; + } + } + /* Unrecognised register. */ + return 0; +} + +#elif defined (TARGET_PPC) + +/* Old gdb always expects FP registers. Newer (xml-aware) gdb only + expects whatever the target description contains. Due to a + historical mishap the FP registers appear in between core integer + regs and PC, MSR, CR, and so forth. We hack round this by giving the + FP regs zero size when talking to a newer gdb. */ +#define NUM_CORE_REGS 71 +#if defined (TARGET_PPC64) +#define GDB_CORE_XML "power64-core.xml" +#else +#define GDB_CORE_XML "power-core.xml" +#endif + +static int cpu_gdb_read_register(CPUState *env, uint8_t *mem_buf, int n) +{ + if (n < 32) { + /* gprs */ + GET_REGL(env->gpr[n]); + } else if (n < 64) { + /* fprs */ + if (gdb_has_xml) + return 0; + stfq_p(mem_buf, env->fpr[n-32]); + return 8; + } else { + switch (n) { + case 64: GET_REGL(env->nip); + case 65: GET_REGL(env->msr); + case 66: + { + uint32_t cr = 0; + int i; + for (i = 0; i < 8; i++) + cr |= env->crf[i] << (32 - ((i + 1) * 4)); + GET_REG32(cr); + } + case 67: GET_REGL(env->lr); + case 68: GET_REGL(env->ctr); + case 69: GET_REGL(env->xer); + case 70: + { + if (gdb_has_xml) + return 0; + GET_REG32(0); /* fpscr */ + } + } + } + return 0; +} + +static int cpu_gdb_write_register(CPUState *env, uint8_t *mem_buf, int n) +{ + if (n < 32) { + /* gprs */ + env->gpr[n] = ldtul_p(mem_buf); + return sizeof(target_ulong); + } else if (n < 64) { + /* fprs */ + if (gdb_has_xml) + return 0; + env->fpr[n-32] = ldfq_p(mem_buf); + return 8; + } else { + switch (n) { + case 64: + env->nip = ldtul_p(mem_buf); + return sizeof(target_ulong); + case 65: + ppc_store_msr(env, ldtul_p(mem_buf)); + return sizeof(target_ulong); + case 66: + { + uint32_t cr = ldl_p(mem_buf); + int i; + for (i = 0; i < 8; i++) + env->crf[i] = (cr >> (32 - ((i + 1) * 4))) & 0xF; + return 4; + } + case 67: + env->lr = ldtul_p(mem_buf); + return sizeof(target_ulong); + case 68: + env->ctr = ldtul_p(mem_buf); + return sizeof(target_ulong); + case 69: + env->xer = ldtul_p(mem_buf); + return sizeof(target_ulong); + case 70: + /* fpscr */ + if (gdb_has_xml) + return 0; + return 4; + } + } + return 0; +} + +#elif defined (TARGET_SPARC) + +#if defined(TARGET_SPARC64) && !defined(TARGET_ABI32) +#define NUM_CORE_REGS 86 +#else +#define NUM_CORE_REGS 72 +#endif + +#ifdef TARGET_ABI32 +#define GET_REGA(val) GET_REG32(val) +#else +#define GET_REGA(val) GET_REGL(val) +#endif + +static int cpu_gdb_read_register(CPUState *env, uint8_t *mem_buf, int n) +{ + if (n < 8) { + /* g0..g7 */ + GET_REGA(env->gregs[n]); + } + if (n < 32) { + /* register window */ + GET_REGA(env->regwptr[n - 8]); + } +#if defined(TARGET_ABI32) || !defined(TARGET_SPARC64) + if (n < 64) { + /* fprs */ + GET_REG32(*((uint32_t *)&env->fpr[n - 32])); + } + /* Y, PSR, WIM, TBR, PC, NPC, FPSR, CPSR */ + switch (n) { + case 64: GET_REGA(env->y); + case 65: GET_REGA(cpu_get_psr(env)); + case 66: GET_REGA(env->wim); + case 67: GET_REGA(env->tbr); + case 68: GET_REGA(env->pc); + case 69: GET_REGA(env->npc); + case 70: GET_REGA(env->fsr); + case 71: GET_REGA(0); /* csr */ + default: GET_REGA(0); + } +#else + if (n < 64) { + /* f0-f31 */ + GET_REG32(*((uint32_t *)&env->fpr[n - 32])); + } + if (n < 80) { + /* f32-f62 (double width, even numbers only) */ + uint64_t val; + + val = (uint64_t)*((uint32_t *)&env->fpr[(n - 64) * 2 + 32]) << 32; + val |= *((uint32_t *)&env->fpr[(n - 64) * 2 + 33]); + GET_REG64(val); + } + switch (n) { + case 80: GET_REGL(env->pc); + case 81: GET_REGL(env->npc); + case 82: GET_REGL((cpu_get_ccr(env) << 32) | + ((env->asi & 0xff) << 24) | + ((env->pstate & 0xfff) << 8) | + cpu_get_cwp64(env)); + case 83: GET_REGL(env->fsr); + case 84: GET_REGL(env->fprs); + case 85: GET_REGL(env->y); + } +#endif + return 0; +} + +static int cpu_gdb_write_register(CPUState *env, uint8_t *mem_buf, int n) +{ +#if defined(TARGET_ABI32) + abi_ulong tmp; + + tmp = ldl_p(mem_buf); +#else + target_ulong tmp; + + tmp = ldtul_p(mem_buf); +#endif + + if (n < 8) { + /* g0..g7 */ + env->gregs[n] = tmp; + } else if (n < 32) { + /* register window */ + env->regwptr[n - 8] = tmp; + } +#if defined(TARGET_ABI32) || !defined(TARGET_SPARC64) + else if (n < 64) { + /* fprs */ + *((uint32_t *)&env->fpr[n - 32]) = tmp; + } else { + /* Y, PSR, WIM, TBR, PC, NPC, FPSR, CPSR */ + switch (n) { + case 64: env->y = tmp; break; + case 65: cpu_put_psr(env, tmp); break; + case 66: env->wim = tmp; break; + case 67: env->tbr = tmp; break; + case 68: env->pc = tmp; break; + case 69: env->npc = tmp; break; + case 70: env->fsr = tmp; break; + default: return 0; + } + } + return 4; +#else + else if (n < 64) { + /* f0-f31 */ + env->fpr[n] = ldfl_p(mem_buf); + return 4; + } else if (n < 80) { + /* f32-f62 (double width, even numbers only) */ + *((uint32_t *)&env->fpr[(n - 64) * 2 + 32]) = tmp >> 32; + *((uint32_t *)&env->fpr[(n - 64) * 2 + 33]) = tmp; + } else { + switch (n) { + case 80: env->pc = tmp; break; + case 81: env->npc = tmp; break; + case 82: + cpu_put_ccr(env, tmp >> 32); + env->asi = (tmp >> 24) & 0xff; + env->pstate = (tmp >> 8) & 0xfff; + cpu_put_cwp64(env, tmp & 0xff); + break; + case 83: env->fsr = tmp; break; + case 84: env->fprs = tmp; break; + case 85: env->y = tmp; break; + default: return 0; + } + } + return 8; +#endif +} +#elif defined (TARGET_ARM) + +/* Old gdb always expect FPA registers. Newer (xml-aware) gdb only expect + whatever the target description contains. Due to a historical mishap + the FPA registers appear in between core integer regs and the CPSR. + We hack round this by giving the FPA regs zero size when talking to a + newer gdb. */ +#define NUM_CORE_REGS 26 +#define GDB_CORE_XML "arm-core.xml" + +static int cpu_gdb_read_register(CPUState *env, uint8_t *mem_buf, int n) +{ + if (n < 16) { + /* Core integer register. */ + GET_REG32(env->regs[n]); + } + if (n < 24) { + /* FPA registers. */ + if (gdb_has_xml) + return 0; + memset(mem_buf, 0, 12); + return 12; + } + switch (n) { + case 24: + /* FPA status register. */ + if (gdb_has_xml) + return 0; + GET_REG32(0); + case 25: + /* CPSR */ + GET_REG32(cpsr_read(env)); + } + /* Unknown register. */ + return 0; +} + +static int cpu_gdb_write_register(CPUState *env, uint8_t *mem_buf, int n) +{ + uint32_t tmp; + + tmp = ldl_p(mem_buf); + + /* Mask out low bit of PC to workaround gdb bugs. This will probably + cause problems if we ever implement the Jazelle DBX extensions. */ + if (n == 15) + tmp &= ~1; + + if (n < 16) { + /* Core integer register. */ + env->regs[n] = tmp; + return 4; + } + if (n < 24) { /* 16-23 */ + /* FPA registers (ignored). */ + if (gdb_has_xml) + return 0; + return 12; + } + switch (n) { + case 24: + /* FPA status register (ignored). */ + if (gdb_has_xml) + return 0; + return 4; + case 25: + /* CPSR */ + cpsr_write (env, tmp, 0xffffffff); + return 4; + } + /* Unknown register. */ + return 0; +} + +#elif defined (TARGET_M68K) + +#define NUM_CORE_REGS 18 + +#define GDB_CORE_XML "cf-core.xml" + +static int cpu_gdb_read_register(CPUState *env, uint8_t *mem_buf, int n) +{ + if (n < 8) { + /* D0-D7 */ + GET_REG32(env->dregs[n]); + } else if (n < 16) { + /* A0-A7 */ + GET_REG32(env->aregs[n - 8]); + } else { + switch (n) { + case 16: GET_REG32(env->sr); + case 17: GET_REG32(env->pc); + } + } + /* FP registers not included here because they vary between + ColdFire and m68k. Use XML bits for these. */ + return 0; +} + +static int cpu_gdb_write_register(CPUState *env, uint8_t *mem_buf, int n) +{ + uint32_t tmp; + + tmp = ldl_p(mem_buf); + + if (n < 8) { + /* D0-D7 */ + env->dregs[n] = tmp; + } else if (n < 16) { + /* A0-A7 */ + env->aregs[n - 8] = tmp; + } else { + switch (n) { + case 16: env->sr = tmp; break; + case 17: env->pc = tmp; break; + default: return 0; + } + } + return 4; +} +#elif defined (TARGET_MIPS) + +#define NUM_CORE_REGS 73 + +static int cpu_gdb_read_register(CPUState *env, uint8_t *mem_buf, int n) +{ + if (n < 32) { + GET_REGL(env->active_tc.gpr[n]); + } + if (env->CP0_Config1 & (1 << CP0C1_FP)) { + if (n >= 38 && n < 70) { + if (env->CP0_Status & (1 << CP0St_FR)) + GET_REGL(env->active_fpu.fpr[n - 38].d); + else + GET_REGL(env->active_fpu.fpr[n - 38].w[FP_ENDIAN_IDX]); + } + switch (n) { + case 70: GET_REGL((int32_t)env->active_fpu.fcr31); + case 71: GET_REGL((int32_t)env->active_fpu.fcr0); + } + } + switch (n) { + case 32: GET_REGL((int32_t)env->CP0_Status); + case 33: GET_REGL(env->active_tc.LO[0]); + case 34: GET_REGL(env->active_tc.HI[0]); + case 35: GET_REGL(env->CP0_BadVAddr); + case 36: GET_REGL((int32_t)env->CP0_Cause); + case 37: GET_REGL(env->active_tc.PC | !!(env->hflags & MIPS_HFLAG_M16)); + case 72: GET_REGL(0); /* fp */ + case 89: GET_REGL((int32_t)env->CP0_PRid); + } + if (n >= 73 && n <= 88) { + /* 16 embedded regs. */ + GET_REGL(0); + } + + return 0; +} + +/* convert MIPS rounding mode in FCR31 to IEEE library */ +static unsigned int ieee_rm[] = + { + float_round_nearest_even, + float_round_to_zero, + float_round_up, + float_round_down + }; +#define RESTORE_ROUNDING_MODE \ + set_float_rounding_mode(ieee_rm[env->active_fpu.fcr31 & 3], &env->active_fpu.fp_status) + +static int cpu_gdb_write_register(CPUState *env, uint8_t *mem_buf, int n) +{ + target_ulong tmp; + + tmp = ldtul_p(mem_buf); + + if (n < 32) { + env->active_tc.gpr[n] = tmp; + return sizeof(target_ulong); + } + if (env->CP0_Config1 & (1 << CP0C1_FP) + && n >= 38 && n < 73) { + if (n < 70) { + if (env->CP0_Status & (1 << CP0St_FR)) + env->active_fpu.fpr[n - 38].d = tmp; + else + env->active_fpu.fpr[n - 38].w[FP_ENDIAN_IDX] = tmp; + } + switch (n) { + case 70: + env->active_fpu.fcr31 = tmp & 0xFF83FFFF; + /* set rounding mode */ + RESTORE_ROUNDING_MODE; +#ifndef CONFIG_SOFTFLOAT + /* no floating point exception for native float */ + SET_FP_ENABLE(env->active_fpu.fcr31, 0); +#endif + break; + case 71: env->active_fpu.fcr0 = tmp; break; + } + return sizeof(target_ulong); + } + switch (n) { + case 32: env->CP0_Status = tmp; break; + case 33: env->active_tc.LO[0] = tmp; break; + case 34: env->active_tc.HI[0] = tmp; break; + case 35: env->CP0_BadVAddr = tmp; break; + case 36: env->CP0_Cause = tmp; break; + case 37: + env->active_tc.PC = tmp & ~(target_ulong)1; + if (tmp & 1) { + env->hflags |= MIPS_HFLAG_M16; + } else { + env->hflags &= ~(MIPS_HFLAG_M16); + } + break; + case 72: /* fp, ignored */ break; + default: + if (n > 89) + return 0; + /* Other registers are readonly. Ignore writes. */ + break; + } + + return sizeof(target_ulong); +} +#elif defined (TARGET_SH4) + +/* Hint: Use "set architecture sh4" in GDB to see fpu registers */ +/* FIXME: We should use XML for this. */ + +#define NUM_CORE_REGS 59 + +static int cpu_gdb_read_register(CPUState *env, uint8_t *mem_buf, int n) +{ + if (n < 8) { + if ((env->sr & (SR_MD | SR_RB)) == (SR_MD | SR_RB)) { + GET_REGL(env->gregs[n + 16]); + } else { + GET_REGL(env->gregs[n]); + } + } else if (n < 16) { + GET_REGL(env->gregs[n]); + } else if (n >= 25 && n < 41) { + GET_REGL(env->fregs[(n - 25) + ((env->fpscr & FPSCR_FR) ? 16 : 0)]); + } else if (n >= 43 && n < 51) { + GET_REGL(env->gregs[n - 43]); + } else if (n >= 51 && n < 59) { + GET_REGL(env->gregs[n - (51 - 16)]); + } + switch (n) { + case 16: GET_REGL(env->pc); + case 17: GET_REGL(env->pr); + case 18: GET_REGL(env->gbr); + case 19: GET_REGL(env->vbr); + case 20: GET_REGL(env->mach); + case 21: GET_REGL(env->macl); + case 22: GET_REGL(env->sr); + case 23: GET_REGL(env->fpul); + case 24: GET_REGL(env->fpscr); + case 41: GET_REGL(env->ssr); + case 42: GET_REGL(env->spc); + } + + return 0; +} + +static int cpu_gdb_write_register(CPUState *env, uint8_t *mem_buf, int n) +{ + uint32_t tmp; + + tmp = ldl_p(mem_buf); + + if (n < 8) { + if ((env->sr & (SR_MD | SR_RB)) == (SR_MD | SR_RB)) { + env->gregs[n + 16] = tmp; + } else { + env->gregs[n] = tmp; + } + return 4; + } else if (n < 16) { + env->gregs[n] = tmp; + return 4; + } else if (n >= 25 && n < 41) { + env->fregs[(n - 25) + ((env->fpscr & FPSCR_FR) ? 16 : 0)] = tmp; + return 4; + } else if (n >= 43 && n < 51) { + env->gregs[n - 43] = tmp; + return 4; + } else if (n >= 51 && n < 59) { + env->gregs[n - (51 - 16)] = tmp; + return 4; + } + switch (n) { + case 16: env->pc = tmp; break; + case 17: env->pr = tmp; break; + case 18: env->gbr = tmp; break; + case 19: env->vbr = tmp; break; + case 20: env->mach = tmp; break; + case 21: env->macl = tmp; break; + case 22: env->sr = tmp; break; + case 23: env->fpul = tmp; break; + case 24: env->fpscr = tmp; break; + case 41: env->ssr = tmp; break; + case 42: env->spc = tmp; break; + default: return 0; + } + + return 4; +} +#elif defined (TARGET_MICROBLAZE) + +#define NUM_CORE_REGS (32 + 5) + +static int cpu_gdb_read_register(CPUState *env, uint8_t *mem_buf, int n) +{ + if (n < 32) { + GET_REG32(env->regs[n]); + } else { + GET_REG32(env->sregs[n - 32]); + } + return 0; +} + +static int cpu_gdb_write_register(CPUState *env, uint8_t *mem_buf, int n) +{ + uint32_t tmp; + + if (n > NUM_CORE_REGS) + return 0; + + tmp = ldl_p(mem_buf); + + if (n < 32) { + env->regs[n] = tmp; + } else { + env->sregs[n - 32] = tmp; + } + return 4; +} +#elif defined (TARGET_CRIS) + +#define NUM_CORE_REGS 49 + +static int +read_register_crisv10(CPUState *env, uint8_t *mem_buf, int n) +{ + if (n < 15) { + GET_REG32(env->regs[n]); + } + + if (n == 15) { + GET_REG32(env->pc); + } + + if (n < 32) { + switch (n) { + case 16: + GET_REG8(env->pregs[n - 16]); + break; + case 17: + GET_REG8(env->pregs[n - 16]); + break; + case 20: + case 21: + GET_REG16(env->pregs[n - 16]); + break; + default: + if (n >= 23) { + GET_REG32(env->pregs[n - 16]); + } + break; + } + } + return 0; +} + +static int cpu_gdb_read_register(CPUState *env, uint8_t *mem_buf, int n) +{ + uint8_t srs; + + if (env->pregs[PR_VR] < 32) + return read_register_crisv10(env, mem_buf, n); + + srs = env->pregs[PR_SRS]; + if (n < 16) { + GET_REG32(env->regs[n]); + } + + if (n >= 21 && n < 32) { + GET_REG32(env->pregs[n - 16]); + } + if (n >= 33 && n < 49) { + GET_REG32(env->sregs[srs][n - 33]); + } + switch (n) { + case 16: GET_REG8(env->pregs[0]); + case 17: GET_REG8(env->pregs[1]); + case 18: GET_REG32(env->pregs[2]); + case 19: GET_REG8(srs); + case 20: GET_REG16(env->pregs[4]); + case 32: GET_REG32(env->pc); + } + + return 0; +} + +static int cpu_gdb_write_register(CPUState *env, uint8_t *mem_buf, int n) +{ + uint32_t tmp; + + if (n > 49) + return 0; + + tmp = ldl_p(mem_buf); + + if (n < 16) { + env->regs[n] = tmp; + } + + if (n >= 21 && n < 32) { + env->pregs[n - 16] = tmp; + } + + /* FIXME: Should support function regs be writable? */ + switch (n) { + case 16: return 1; + case 17: return 1; + case 18: env->pregs[PR_PID] = tmp; break; + case 19: return 1; + case 20: return 2; + case 32: env->pc = tmp; break; + } + + return 4; +} +#elif defined (TARGET_ALPHA) + +#define NUM_CORE_REGS 67 + +static int cpu_gdb_read_register(CPUState *env, uint8_t *mem_buf, int n) +{ + uint64_t val; + CPU_DoubleU d; + + switch (n) { + case 0 ... 30: + val = env->ir[n]; + break; + case 32 ... 62: + d.d = env->fir[n - 32]; + val = d.ll; + break; + case 63: + val = cpu_alpha_load_fpcr(env); + break; + case 64: + val = env->pc; + break; + case 66: + val = env->unique; + break; + case 31: + case 65: + /* 31 really is the zero register; 65 is unassigned in the + gdb protocol, but is still required to occupy 8 bytes. */ + val = 0; + break; + default: + return 0; + } + GET_REGL(val); +} + +static int cpu_gdb_write_register(CPUState *env, uint8_t *mem_buf, int n) +{ + target_ulong tmp = ldtul_p(mem_buf); + CPU_DoubleU d; + + switch (n) { + case 0 ... 30: + env->ir[n] = tmp; + break; + case 32 ... 62: + d.ll = tmp; + env->fir[n - 32] = d.d; + break; + case 63: + cpu_alpha_store_fpcr(env, tmp); + break; + case 64: + env->pc = tmp; + break; + case 66: + env->unique = tmp; + break; + case 31: + case 65: + /* 31 really is the zero register; 65 is unassigned in the + gdb protocol, but is still required to occupy 8 bytes. */ + break; + default: + return 0; + } + return 8; +} +#elif defined (TARGET_S390X) + +#define NUM_CORE_REGS S390_NUM_TOTAL_REGS + +static int cpu_gdb_read_register(CPUState *env, uint8_t *mem_buf, int n) +{ + switch (n) { + case S390_PSWM_REGNUM: GET_REGL(env->psw.mask); break; + case S390_PSWA_REGNUM: GET_REGL(env->psw.addr); break; + case S390_R0_REGNUM ... S390_R15_REGNUM: + GET_REGL(env->regs[n-S390_R0_REGNUM]); break; + case S390_A0_REGNUM ... S390_A15_REGNUM: + GET_REG32(env->aregs[n-S390_A0_REGNUM]); break; + case S390_FPC_REGNUM: GET_REG32(env->fpc); break; + case S390_F0_REGNUM ... S390_F15_REGNUM: + /* XXX */ + break; + case S390_PC_REGNUM: GET_REGL(env->psw.addr); break; + case S390_CC_REGNUM: GET_REG32(env->cc); break; + } + + return 0; +} + +static int cpu_gdb_write_register(CPUState *env, uint8_t *mem_buf, int n) +{ + target_ulong tmpl; + uint32_t tmp32; + int r = 8; + tmpl = ldtul_p(mem_buf); + tmp32 = ldl_p(mem_buf); + + switch (n) { + case S390_PSWM_REGNUM: env->psw.mask = tmpl; break; + case S390_PSWA_REGNUM: env->psw.addr = tmpl; break; + case S390_R0_REGNUM ... S390_R15_REGNUM: + env->regs[n-S390_R0_REGNUM] = tmpl; break; + case S390_A0_REGNUM ... S390_A15_REGNUM: + env->aregs[n-S390_A0_REGNUM] = tmp32; r=4; break; + case S390_FPC_REGNUM: env->fpc = tmp32; r=4; break; + case S390_F0_REGNUM ... S390_F15_REGNUM: + /* XXX */ + break; + case S390_PC_REGNUM: env->psw.addr = tmpl; break; + case S390_CC_REGNUM: env->cc = tmp32; r=4; break; + } + + return r; +} +#else + +#define NUM_CORE_REGS 0 + +static int cpu_gdb_read_register(CPUState *env, uint8_t *mem_buf, int n) +{ + return 0; +} + +static int cpu_gdb_write_register(CPUState *env, uint8_t *mem_buf, int n) +{ + return 0; +} + +#endif + +static int num_g_regs = NUM_CORE_REGS; + +#ifdef GDB_CORE_XML +/* Encode data using the encoding for 'x' packets. */ +static int memtox(char *buf, const char *mem, int len) +{ + char *p = buf; + char c; + + while (len--) { + c = *(mem++); + switch (c) { + case '#': case '$': case '*': case '}': + *(p++) = '}'; + *(p++) = c ^ 0x20; + break; + default: + *(p++) = c; + break; + } + } + return p - buf; +} + +static const char *get_feature_xml(const char *p, const char **newp) +{ + size_t len; + int i; + const char *name; + static char target_xml[1024]; + + len = 0; + while (p[len] && p[len] != ':') + len++; + *newp = p + len; + + name = NULL; + if (strncmp(p, "target.xml", len) == 0) { + /* Generate the XML description for this CPU. */ + if (!target_xml[0]) { + GDBRegisterState *r; + + snprintf(target_xml, sizeof(target_xml), + "" + "" + "" + "", + GDB_CORE_XML); + + for (r = first_cpu->gdb_regs; r; r = r->next) { + pstrcat(target_xml, sizeof(target_xml), "xml); + pstrcat(target_xml, sizeof(target_xml), "\"/>"); + } + pstrcat(target_xml, sizeof(target_xml), ""); + } + return target_xml; + } + for (i = 0; ; i++) { + name = xml_builtin[i][0]; + if (!name || (strncmp(name, p, len) == 0 && strlen(name) == len)) + break; + } + return name ? xml_builtin[i][1] : NULL; +} +#endif + +static int gdb_read_register(CPUState *env, uint8_t *mem_buf, int reg) +{ + GDBRegisterState *r; + + if (reg < NUM_CORE_REGS) + return cpu_gdb_read_register(env, mem_buf, reg); + + for (r = env->gdb_regs; r; r = r->next) { + if (r->base_reg <= reg && reg < r->base_reg + r->num_regs) { + return r->get_reg(env, mem_buf, reg - r->base_reg); + } + } + return 0; +} + +static int gdb_write_register(CPUState *env, uint8_t *mem_buf, int reg) +{ + GDBRegisterState *r; + + if (reg < NUM_CORE_REGS) + return cpu_gdb_write_register(env, mem_buf, reg); + + for (r = env->gdb_regs; r; r = r->next) { + if (r->base_reg <= reg && reg < r->base_reg + r->num_regs) { + return r->set_reg(env, mem_buf, reg - r->base_reg); + } + } + return 0; +} + +/* Register a supplemental set of CPU registers. If g_pos is nonzero it + specifies the first register number and these registers are included in + a standard "g" packet. Direction is relative to gdb, i.e. get_reg is + gdb reading a CPU register, and set_reg is gdb modifying a CPU register. + */ + +void gdb_register_coprocessor(CPUState * env, + gdb_reg_cb get_reg, gdb_reg_cb set_reg, + int num_regs, const char *xml, int g_pos) +{ + GDBRegisterState *s; + GDBRegisterState **p; + static int last_reg = NUM_CORE_REGS; + + s = (GDBRegisterState *)qemu_mallocz(sizeof(GDBRegisterState)); + s->base_reg = last_reg; + s->num_regs = num_regs; + s->get_reg = get_reg; + s->set_reg = set_reg; + s->xml = xml; + p = &env->gdb_regs; + while (*p) { + /* Check for duplicates. */ + if (strcmp((*p)->xml, xml) == 0) + return; + p = &(*p)->next; + } + /* Add to end of list. */ + last_reg += num_regs; + *p = s; + if (g_pos) { + if (g_pos != s->base_reg) { + fprintf(stderr, "Error: Bad gdb register numbering for '%s'\n" + "Expected %d got %d\n", xml, g_pos, s->base_reg); + } else { + num_g_regs = last_reg; + } + } +} + +#ifndef CONFIG_USER_ONLY +static const int xlat_gdb_type[] = { + [GDB_WATCHPOINT_WRITE] = BP_GDB | BP_MEM_WRITE, + [GDB_WATCHPOINT_READ] = BP_GDB | BP_MEM_READ, + [GDB_WATCHPOINT_ACCESS] = BP_GDB | BP_MEM_ACCESS, +}; +#endif + +static int gdb_breakpoint_insert(target_ulong addr, target_ulong len, int type) +{ + CPUState *env; + int err = 0; + + if (kvm_enabled()) + return kvm_insert_breakpoint(gdbserver_state->c_cpu, addr, len, type); + + switch (type) { + case GDB_BREAKPOINT_SW: + case GDB_BREAKPOINT_HW: + for (env = first_cpu; env != NULL; env = env->next_cpu) { + err = cpu_breakpoint_insert(env, addr, BP_GDB, NULL); + if (err) + break; + } + return err; +#ifndef CONFIG_USER_ONLY + case GDB_WATCHPOINT_WRITE: + case GDB_WATCHPOINT_READ: + case GDB_WATCHPOINT_ACCESS: + for (env = first_cpu; env != NULL; env = env->next_cpu) { + err = cpu_watchpoint_insert(env, addr, len, xlat_gdb_type[type], + NULL); + if (err) + break; + } + return err; +#endif + default: + return -ENOSYS; + } +} + +static int gdb_breakpoint_remove(target_ulong addr, target_ulong len, int type) +{ + CPUState *env; + int err = 0; + + if (kvm_enabled()) + return kvm_remove_breakpoint(gdbserver_state->c_cpu, addr, len, type); + + switch (type) { + case GDB_BREAKPOINT_SW: + case GDB_BREAKPOINT_HW: + for (env = first_cpu; env != NULL; env = env->next_cpu) { + err = cpu_breakpoint_remove(env, addr, BP_GDB); + if (err) + break; + } + return err; +#ifndef CONFIG_USER_ONLY + case GDB_WATCHPOINT_WRITE: + case GDB_WATCHPOINT_READ: + case GDB_WATCHPOINT_ACCESS: + for (env = first_cpu; env != NULL; env = env->next_cpu) { + err = cpu_watchpoint_remove(env, addr, len, xlat_gdb_type[type]); + if (err) + break; + } + return err; +#endif + default: + return -ENOSYS; + } +} + +static void gdb_breakpoint_remove_all(void) +{ + CPUState *env; + + if (kvm_enabled()) { + kvm_remove_all_breakpoints(gdbserver_state->c_cpu); + return; + } + + for (env = first_cpu; env != NULL; env = env->next_cpu) { + cpu_breakpoint_remove_all(env, BP_GDB); +#ifndef CONFIG_USER_ONLY + cpu_watchpoint_remove_all(env, BP_GDB); +#endif + } +} + +static void gdb_set_cpu_pc(GDBState *s, target_ulong pc) +{ +#if defined(TARGET_I386) + cpu_synchronize_state(s->c_cpu); + s->c_cpu->eip = pc; +#elif defined (TARGET_PPC) + s->c_cpu->nip = pc; +#elif defined (TARGET_SPARC) + s->c_cpu->pc = pc; + s->c_cpu->npc = pc + 4; +#elif defined (TARGET_ARM) + s->c_cpu->regs[15] = pc; +#elif defined (TARGET_SH4) + s->c_cpu->pc = pc; +#elif defined (TARGET_MIPS) + s->c_cpu->active_tc.PC = pc & ~(target_ulong)1; + if (pc & 1) { + s->c_cpu->hflags |= MIPS_HFLAG_M16; + } else { + s->c_cpu->hflags &= ~(MIPS_HFLAG_M16); + } +#elif defined (TARGET_MICROBLAZE) + s->c_cpu->sregs[SR_PC] = pc; +#elif defined (TARGET_CRIS) + s->c_cpu->pc = pc; +#elif defined (TARGET_ALPHA) + s->c_cpu->pc = pc; +#elif defined (TARGET_S390X) + cpu_synchronize_state(s->c_cpu); + s->c_cpu->psw.addr = pc; +#endif +} + +static inline int gdb_id(CPUState *env) +{ +#if defined(CONFIG_USER_ONLY) && defined(CONFIG_USE_NPTL) + return env->host_tid; +#else + return env->cpu_index + 1; +#endif +} + +static CPUState *find_cpu(uint32_t thread_id) +{ + CPUState *env; + + for (env = first_cpu; env != NULL; env = env->next_cpu) { + if (gdb_id(env) == thread_id) { + return env; + } + } + + return NULL; +} + +static int gdb_handle_packet(GDBState *s, const char *line_buf) +{ + CPUState *env; + const char *p; + uint32_t thread; + int ch, reg_size, type, res; + char buf[MAX_PACKET_LENGTH]; + uint8_t mem_buf[MAX_PACKET_LENGTH]; + uint8_t *registers; + target_ulong addr, len; + +#ifdef DEBUG_GDB + printf("command='%s'\n", line_buf); +#endif + p = line_buf; + ch = *p++; + switch(ch) { + case '?': + /* TODO: Make this return the correct value for user-mode. */ + snprintf(buf, sizeof(buf), "T%02xthread:%02x;", GDB_SIGNAL_TRAP, + gdb_id(s->c_cpu)); + put_packet(s, buf); + /* Remove all the breakpoints when this query is issued, + * because gdb is doing and initial connect and the state + * should be cleaned up. + */ + gdb_breakpoint_remove_all(); + break; + case 'c': + if (*p != '\0') { + addr = strtoull(p, (char **)&p, 16); + gdb_set_cpu_pc(s, addr); + } + s->signal = 0; + gdb_continue(s); + return RS_IDLE; + case 'C': + s->signal = gdb_signal_to_target (strtoul(p, (char **)&p, 16)); + if (s->signal == -1) + s->signal = 0; + gdb_continue(s); + return RS_IDLE; + case 'v': + if (strncmp(p, "Cont", 4) == 0) { + int res_signal, res_thread; + + p += 4; + if (*p == '?') { + put_packet(s, "vCont;c;C;s;S"); + break; + } + res = 0; + res_signal = 0; + res_thread = 0; + while (*p) { + int action, signal; + + if (*p++ != ';') { + res = 0; + break; + } + action = *p++; + signal = 0; + if (action == 'C' || action == 'S') { + signal = strtoul(p, (char **)&p, 16); + } else if (action != 'c' && action != 's') { + res = 0; + break; + } + thread = 0; + if (*p == ':') { + thread = strtoull(p+1, (char **)&p, 16); + } + action = tolower(action); + if (res == 0 || (res == 'c' && action == 's')) { + res = action; + res_signal = signal; + res_thread = thread; + } + } + if (res) { + if (res_thread != -1 && res_thread != 0) { + env = find_cpu(res_thread); + if (env == NULL) { + put_packet(s, "E22"); + break; + } + s->c_cpu = env; + } + if (res == 's') { + cpu_single_step(s->c_cpu, sstep_flags); + } + s->signal = res_signal; + gdb_continue(s); + return RS_IDLE; + } + break; + } else { + goto unknown_command; + } + case 'k': + /* Kill the target */ + fprintf(stderr, "\nQEMU: Terminated via GDBstub\n"); + exit(0); + case 'D': + /* Detach packet */ + gdb_breakpoint_remove_all(); + gdb_syscall_mode = GDB_SYS_DISABLED; + gdb_continue(s); + put_packet(s, "OK"); + break; + case 's': + if (*p != '\0') { + addr = strtoull(p, (char **)&p, 16); + gdb_set_cpu_pc(s, addr); + } + cpu_single_step(s->c_cpu, sstep_flags); + gdb_continue(s); + return RS_IDLE; + case 'F': + { + target_ulong ret; + target_ulong err; + + ret = strtoull(p, (char **)&p, 16); + if (*p == ',') { + p++; + err = strtoull(p, (char **)&p, 16); + } else { + err = 0; + } + if (*p == ',') + p++; + type = *p; + if (gdb_current_syscall_cb) + gdb_current_syscall_cb(s->c_cpu, ret, err); + if (type == 'C') { + put_packet(s, "T02"); + } else { + gdb_continue(s); + } + } + break; + case 'g': + cpu_synchronize_state(s->g_cpu); + len = 0; + for (addr = 0; addr < num_g_regs; addr++) { + reg_size = gdb_read_register(s->g_cpu, mem_buf + len, addr); + len += reg_size; + } + memtohex(buf, mem_buf, len); + put_packet(s, buf); + break; + case 'G': + cpu_synchronize_state(s->g_cpu); + registers = mem_buf; + len = strlen(p) / 2; + hextomem((uint8_t *)registers, p, len); + for (addr = 0; addr < num_g_regs && len > 0; addr++) { + reg_size = gdb_write_register(s->g_cpu, registers, addr); + len -= reg_size; + registers += reg_size; + } + put_packet(s, "OK"); + break; + case 'm': + addr = strtoull(p, (char **)&p, 16); + if (*p == ',') + p++; + len = strtoull(p, NULL, 16); + if (cpu_memory_rw_debug(s->g_cpu, addr, mem_buf, len, 0) != 0) { + put_packet (s, "E14"); + } else { + memtohex(buf, mem_buf, len); + put_packet(s, buf); + } + break; + case 'M': + addr = strtoull(p, (char **)&p, 16); + if (*p == ',') + p++; + len = strtoull(p, (char **)&p, 16); + if (*p == ':') + p++; + hextomem(mem_buf, p, len); + if (cpu_memory_rw_debug(s->g_cpu, addr, mem_buf, len, 1) != 0) + put_packet(s, "E14"); + else + put_packet(s, "OK"); + break; + case 'p': + /* Older gdb are really dumb, and don't use 'g' if 'p' is avaialable. + This works, but can be very slow. Anything new enough to + understand XML also knows how to use this properly. */ + if (!gdb_has_xml) + goto unknown_command; + addr = strtoull(p, (char **)&p, 16); + reg_size = gdb_read_register(s->g_cpu, mem_buf, addr); + if (reg_size) { + memtohex(buf, mem_buf, reg_size); + put_packet(s, buf); + } else { + put_packet(s, "E14"); + } + break; + case 'P': + if (!gdb_has_xml) + goto unknown_command; + addr = strtoull(p, (char **)&p, 16); + if (*p == '=') + p++; + reg_size = strlen(p) / 2; + hextomem(mem_buf, p, reg_size); + gdb_write_register(s->g_cpu, mem_buf, addr); + put_packet(s, "OK"); + break; + case 'Z': + case 'z': + type = strtoul(p, (char **)&p, 16); + if (*p == ',') + p++; + addr = strtoull(p, (char **)&p, 16); + if (*p == ',') + p++; + len = strtoull(p, (char **)&p, 16); + if (ch == 'Z') + res = gdb_breakpoint_insert(addr, len, type); + else + res = gdb_breakpoint_remove(addr, len, type); + if (res >= 0) + put_packet(s, "OK"); + else if (res == -ENOSYS) + put_packet(s, ""); + else + put_packet(s, "E22"); + break; + case 'H': + type = *p++; + thread = strtoull(p, (char **)&p, 16); + if (thread == -1 || thread == 0) { + put_packet(s, "OK"); + break; + } + env = find_cpu(thread); + if (env == NULL) { + put_packet(s, "E22"); + break; + } + switch (type) { + case 'c': + s->c_cpu = env; + put_packet(s, "OK"); + break; + case 'g': + s->g_cpu = env; + put_packet(s, "OK"); + break; + default: + put_packet(s, "E22"); + break; + } + break; + case 'T': + thread = strtoull(p, (char **)&p, 16); + env = find_cpu(thread); + + if (env != NULL) { + put_packet(s, "OK"); + } else { + put_packet(s, "E22"); + } + break; + case 'q': + case 'Q': + /* parse any 'q' packets here */ + if (!strcmp(p,"qemu.sstepbits")) { + /* Query Breakpoint bit definitions */ + snprintf(buf, sizeof(buf), "ENABLE=%x,NOIRQ=%x,NOTIMER=%x", + SSTEP_ENABLE, + SSTEP_NOIRQ, + SSTEP_NOTIMER); + put_packet(s, buf); + break; + } else if (strncmp(p,"qemu.sstep",10) == 0) { + /* Display or change the sstep_flags */ + p += 10; + if (*p != '=') { + /* Display current setting */ + snprintf(buf, sizeof(buf), "0x%x", sstep_flags); + put_packet(s, buf); + break; + } + p++; + type = strtoul(p, (char **)&p, 16); + sstep_flags = type; + put_packet(s, "OK"); + break; + } else if (strcmp(p,"C") == 0) { + /* "Current thread" remains vague in the spec, so always return + * the first CPU (gdb returns the first thread). */ + put_packet(s, "QC1"); + break; + } else if (strcmp(p,"fThreadInfo") == 0) { + s->query_cpu = first_cpu; + goto report_cpuinfo; + } else if (strcmp(p,"sThreadInfo") == 0) { + report_cpuinfo: + if (s->query_cpu) { + snprintf(buf, sizeof(buf), "m%x", gdb_id(s->query_cpu)); + put_packet(s, buf); + s->query_cpu = s->query_cpu->next_cpu; + } else + put_packet(s, "l"); + break; + } else if (strncmp(p,"ThreadExtraInfo,", 16) == 0) { + thread = strtoull(p+16, (char **)&p, 16); + env = find_cpu(thread); + if (env != NULL) { + cpu_synchronize_state(env); + len = snprintf((char *)mem_buf, sizeof(mem_buf), + "CPU#%d [%s]", env->cpu_index, + env->halted ? "halted " : "running"); + memtohex(buf, mem_buf, len); + put_packet(s, buf); + } + break; + } +#ifdef CONFIG_USER_ONLY + else if (strncmp(p, "Offsets", 7) == 0) { + TaskState *ts = s->c_cpu->opaque; + + snprintf(buf, sizeof(buf), + "Text=" TARGET_ABI_FMT_lx ";Data=" TARGET_ABI_FMT_lx + ";Bss=" TARGET_ABI_FMT_lx, + ts->info->code_offset, + ts->info->data_offset, + ts->info->data_offset); + put_packet(s, buf); + break; + } +#else /* !CONFIG_USER_ONLY */ + else if (strncmp(p, "Rcmd,", 5) == 0) { + int len = strlen(p + 5); + + if ((len % 2) != 0) { + put_packet(s, "E01"); + break; + } + hextomem(mem_buf, p + 5, len); + len = len / 2; + mem_buf[len++] = 0; + qemu_chr_read(s->mon_chr, mem_buf, len); + put_packet(s, "OK"); + break; + } +#endif /* !CONFIG_USER_ONLY */ + if (strncmp(p, "Supported", 9) == 0) { + snprintf(buf, sizeof(buf), "PacketSize=%x", MAX_PACKET_LENGTH); +#ifdef GDB_CORE_XML + pstrcat(buf, sizeof(buf), ";qXfer:features:read+"); +#endif + put_packet(s, buf); + break; + } +#ifdef GDB_CORE_XML + if (strncmp(p, "Xfer:features:read:", 19) == 0) { + const char *xml; + target_ulong total_len; + + gdb_has_xml = 1; + p += 19; + xml = get_feature_xml(p, &p); + if (!xml) { + snprintf(buf, sizeof(buf), "E00"); + put_packet(s, buf); + break; + } + + if (*p == ':') + p++; + addr = strtoul(p, (char **)&p, 16); + if (*p == ',') + p++; + len = strtoul(p, (char **)&p, 16); + + total_len = strlen(xml); + if (addr > total_len) { + snprintf(buf, sizeof(buf), "E00"); + put_packet(s, buf); + break; + } + if (len > (MAX_PACKET_LENGTH - 5) / 2) + len = (MAX_PACKET_LENGTH - 5) / 2; + if (len < total_len - addr) { + buf[0] = 'm'; + len = memtox(buf + 1, xml + addr, len); + } else { + buf[0] = 'l'; + len = memtox(buf + 1, xml + addr, total_len - addr); + } + put_packet_binary(s, buf, len + 1); + break; + } +#endif + /* Unrecognised 'q' command. */ + goto unknown_command; + + default: + unknown_command: + /* put empty packet */ + buf[0] = '\0'; + put_packet(s, buf); + break; + } + return RS_IDLE; +} + +void gdb_set_stop_cpu(CPUState *env) +{ + gdbserver_state->c_cpu = env; + gdbserver_state->g_cpu = env; +} + +#ifndef CONFIG_USER_ONLY +static void gdb_vm_state_change(void *opaque, int running, int reason) +{ + GDBState *s = gdbserver_state; + CPUState *env = s->c_cpu; + char buf[256]; + const char *type; + int ret; + + if (running || (reason != EXCP_DEBUG && reason != EXCP_INTERRUPT) || + s->state == RS_INACTIVE || s->state == RS_SYSCALL) + return; + + /* disable single step if it was enable */ + cpu_single_step(env, 0); + + if (reason == EXCP_DEBUG) { + if (env->watchpoint_hit) { + switch (env->watchpoint_hit->flags & BP_MEM_ACCESS) { + case BP_MEM_READ: + type = "r"; + break; + case BP_MEM_ACCESS: + type = "a"; + break; + default: + type = ""; + break; + } + snprintf(buf, sizeof(buf), + "T%02xthread:%02x;%swatch:" TARGET_FMT_lx ";", + GDB_SIGNAL_TRAP, gdb_id(env), type, + env->watchpoint_hit->vaddr); + put_packet(s, buf); + env->watchpoint_hit = NULL; + return; + } + tb_flush(env); + ret = GDB_SIGNAL_TRAP; + } else { + ret = GDB_SIGNAL_INT; + } + snprintf(buf, sizeof(buf), "T%02xthread:%02x;", ret, gdb_id(env)); + put_packet(s, buf); +} +#endif + +/* Send a gdb syscall request. + This accepts limited printf-style format specifiers, specifically: + %x - target_ulong argument printed in hex. + %lx - 64-bit argument printed in hex. + %s - string pointer (target_ulong) and length (int) pair. */ +void gdb_do_syscall(gdb_syscall_complete_cb cb, const char *fmt, ...) +{ + va_list va; + char buf[256]; + char *p; + target_ulong addr; + uint64_t i64; + GDBState *s; + + s = gdbserver_state; + if (!s) + return; + gdb_current_syscall_cb = cb; + s->state = RS_SYSCALL; +#ifndef CONFIG_USER_ONLY + vm_stop(EXCP_DEBUG); +#endif + s->state = RS_IDLE; + va_start(va, fmt); + p = buf; + *(p++) = 'F'; + while (*fmt) { + if (*fmt == '%') { + fmt++; + switch (*fmt++) { + case 'x': + addr = va_arg(va, target_ulong); + p += snprintf(p, &buf[sizeof(buf)] - p, TARGET_FMT_lx, addr); + break; + case 'l': + if (*(fmt++) != 'x') + goto bad_format; + i64 = va_arg(va, uint64_t); + p += snprintf(p, &buf[sizeof(buf)] - p, "%" PRIx64, i64); + break; + case 's': + addr = va_arg(va, target_ulong); + p += snprintf(p, &buf[sizeof(buf)] - p, TARGET_FMT_lx "/%x", + addr, va_arg(va, int)); + break; + default: + bad_format: + fprintf(stderr, "gdbstub: Bad syscall format string '%s'\n", + fmt - 1); + break; + } + } else { + *(p++) = *(fmt++); + } + } + *p = 0; + va_end(va); + put_packet(s, buf); +#ifdef CONFIG_USER_ONLY + gdb_handlesig(s->c_cpu, 0); +#else + cpu_exit(s->c_cpu); +#endif +} + +static void gdb_read_byte(GDBState *s, int ch) +{ + int i, csum; + uint8_t reply; + +#ifndef CONFIG_USER_ONLY + if (s->last_packet_len) { + /* Waiting for a response to the last packet. If we see the start + of a new command then abandon the previous response. */ + if (ch == '-') { +#ifdef DEBUG_GDB + printf("Got NACK, retransmitting\n"); +#endif + put_buffer(s, (uint8_t *)s->last_packet, s->last_packet_len); + } +#ifdef DEBUG_GDB + else if (ch == '+') + printf("Got ACK\n"); + else + printf("Got '%c' when expecting ACK/NACK\n", ch); +#endif + if (ch == '+' || ch == '$') + s->last_packet_len = 0; + if (ch != '$') + return; + } + if (vm_running) { + /* when the CPU is running, we cannot do anything except stop + it when receiving a char */ + vm_stop(EXCP_INTERRUPT); + } else +#endif + { + switch(s->state) { + case RS_IDLE: + if (ch == '$') { + s->line_buf_index = 0; + s->state = RS_GETLINE; + } + break; + case RS_GETLINE: + if (ch == '#') { + s->state = RS_CHKSUM1; + } else if (s->line_buf_index >= sizeof(s->line_buf) - 1) { + s->state = RS_IDLE; + } else { + s->line_buf[s->line_buf_index++] = ch; + } + break; + case RS_CHKSUM1: + s->line_buf[s->line_buf_index] = '\0'; + s->line_csum = fromhex(ch) << 4; + s->state = RS_CHKSUM2; + break; + case RS_CHKSUM2: + s->line_csum |= fromhex(ch); + csum = 0; + for(i = 0; i < s->line_buf_index; i++) { + csum += s->line_buf[i]; + } + if (s->line_csum != (csum & 0xff)) { + reply = '-'; + put_buffer(s, &reply, 1); + s->state = RS_IDLE; + } else { + reply = '+'; + put_buffer(s, &reply, 1); + s->state = gdb_handle_packet(s, s->line_buf); + } + break; + default: + abort(); + } + } +} + +/* Tell the remote gdb that the process has exited. */ +void gdb_exit(CPUState *env, int code) +{ + GDBState *s; + char buf[4]; + + s = gdbserver_state; + if (!s) { + return; + } +#ifdef CONFIG_USER_ONLY + if (gdbserver_fd < 0 || s->fd < 0) { + return; + } +#endif + + snprintf(buf, sizeof(buf), "W%02x", (uint8_t)code); + put_packet(s, buf); + +#ifndef CONFIG_USER_ONLY + if (s->chr) { + qemu_chr_close(s->chr); + } +#endif +} + +#ifdef CONFIG_USER_ONLY +int +gdb_queuesig (void) +{ + GDBState *s; + + s = gdbserver_state; + + if (gdbserver_fd < 0 || s->fd < 0) + return 0; + else + return 1; +} + +int +gdb_handlesig (CPUState *env, int sig) +{ + GDBState *s; + char buf[256]; + int n; + + s = gdbserver_state; + if (gdbserver_fd < 0 || s->fd < 0) + return sig; + + /* disable single step if it was enabled */ + cpu_single_step(env, 0); + tb_flush(env); + + if (sig != 0) + { + snprintf(buf, sizeof(buf), "S%02x", target_signal_to_gdb (sig)); + put_packet(s, buf); + } + /* put_packet() might have detected that the peer terminated the + connection. */ + if (s->fd < 0) + return sig; + + sig = 0; + s->state = RS_IDLE; + s->running_state = 0; + while (s->running_state == 0) { + n = read (s->fd, buf, 256); + if (n > 0) + { + int i; + + for (i = 0; i < n; i++) + gdb_read_byte (s, buf[i]); + } + else if (n == 0 || errno != EAGAIN) + { + /* XXX: Connection closed. Should probably wait for annother + connection before continuing. */ + return sig; + } + } + sig = s->signal; + s->signal = 0; + return sig; +} + +/* Tell the remote gdb that the process has exited due to SIG. */ +void gdb_signalled(CPUState *env, int sig) +{ + GDBState *s; + char buf[4]; + + s = gdbserver_state; + if (gdbserver_fd < 0 || s->fd < 0) + return; + + snprintf(buf, sizeof(buf), "X%02x", target_signal_to_gdb (sig)); + put_packet(s, buf); +} + +static void gdb_accept(void) +{ + GDBState *s; + struct sockaddr_in sockaddr; + socklen_t len; + int val, fd; + + for(;;) { + len = sizeof(sockaddr); + fd = accept(gdbserver_fd, (struct sockaddr *)&sockaddr, &len); + if (fd < 0 && errno != EINTR) { + perror("accept"); + return; + } else if (fd >= 0) { +#ifndef _WIN32 + fcntl(fd, F_SETFD, FD_CLOEXEC); +#endif + break; + } + } + + /* set short latency */ + val = 1; + setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char *)&val, sizeof(val)); + + s = qemu_mallocz(sizeof(GDBState)); + s->c_cpu = first_cpu; + s->g_cpu = first_cpu; + s->fd = fd; + gdb_has_xml = 0; + + gdbserver_state = s; + + fcntl(fd, F_SETFL, O_NONBLOCK); +} + +static int gdbserver_open(int port) +{ + struct sockaddr_in sockaddr; + int fd, val, ret; + + fd = socket(PF_INET, SOCK_STREAM, 0); + if (fd < 0) { + perror("socket"); + return -1; + } +#ifndef _WIN32 + fcntl(fd, F_SETFD, FD_CLOEXEC); +#endif + + /* allow fast reuse */ + val = 1; + setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *)&val, sizeof(val)); + + sockaddr.sin_family = AF_INET; + sockaddr.sin_port = htons(port); + sockaddr.sin_addr.s_addr = 0; + ret = bind(fd, (struct sockaddr *)&sockaddr, sizeof(sockaddr)); + if (ret < 0) { + perror("bind"); + return -1; + } + ret = listen(fd, 0); + if (ret < 0) { + perror("listen"); + return -1; + } + return fd; +} + +int gdbserver_start(int port) +{ + gdbserver_fd = gdbserver_open(port); + if (gdbserver_fd < 0) + return -1; + /* accept connections */ + gdb_accept(); + return 0; +} + +/* Disable gdb stub for child processes. */ +void gdbserver_fork(CPUState *env) +{ + GDBState *s = gdbserver_state; + if (gdbserver_fd < 0 || s->fd < 0) + return; + close(s->fd); + s->fd = -1; + cpu_breakpoint_remove_all(env, BP_GDB); + cpu_watchpoint_remove_all(env, BP_GDB); +} +#else +static int gdb_chr_can_receive(void *opaque) +{ + /* We can handle an arbitrarily large amount of data. + Pick the maximum packet size, which is as good as anything. */ + return MAX_PACKET_LENGTH; +} + +static void gdb_chr_receive(void *opaque, const uint8_t *buf, int size) +{ + int i; + + for (i = 0; i < size; i++) { + gdb_read_byte(gdbserver_state, buf[i]); + } +} + +static void gdb_chr_event(void *opaque, int event) +{ + switch (event) { + case CHR_EVENT_OPENED: + vm_stop(EXCP_INTERRUPT); + gdb_has_xml = 0; + break; + default: + break; + } +} + +static void gdb_monitor_output(GDBState *s, const char *msg, int len) +{ + char buf[MAX_PACKET_LENGTH]; + + buf[0] = 'O'; + if (len > (MAX_PACKET_LENGTH/2) - 1) + len = (MAX_PACKET_LENGTH/2) - 1; + memtohex(buf + 1, (uint8_t *)msg, len); + put_packet(s, buf); +} + +static int gdb_monitor_write(CharDriverState *chr, const uint8_t *buf, int len) +{ + const char *p = (const char *)buf; + int max_sz; + + max_sz = (sizeof(gdbserver_state->last_packet) - 2) / 2; + for (;;) { + if (len <= max_sz) { + gdb_monitor_output(gdbserver_state, p, len); + break; + } + gdb_monitor_output(gdbserver_state, p, max_sz); + p += max_sz; + len -= max_sz; + } + return len; +} + +#ifndef _WIN32 +static void gdb_sigterm_handler(int signal) +{ + if (vm_running) + vm_stop(EXCP_INTERRUPT); +} +#endif + +int gdbserver_start(const char *device) +{ + GDBState *s; + char gdbstub_device_name[128]; + CharDriverState *chr = NULL; + CharDriverState *mon_chr; + + if (!device) + return -1; + if (strcmp(device, "none") != 0) { + if (strstart(device, "tcp:", NULL)) { + /* enforce required TCP attributes */ + snprintf(gdbstub_device_name, sizeof(gdbstub_device_name), + "%s,nowait,nodelay,server", device); + device = gdbstub_device_name; + } +#ifndef _WIN32 + else if (strcmp(device, "stdio") == 0) { + struct sigaction act; + + memset(&act, 0, sizeof(act)); + act.sa_handler = gdb_sigterm_handler; + sigaction(SIGINT, &act, NULL); + } +#endif + chr = qemu_chr_open("gdb", device, NULL); + if (!chr) + return -1; + + qemu_chr_add_handlers(chr, gdb_chr_can_receive, gdb_chr_receive, + gdb_chr_event, NULL); + } + + s = gdbserver_state; + if (!s) { + s = qemu_mallocz(sizeof(GDBState)); + gdbserver_state = s; + + qemu_add_vm_change_state_handler(gdb_vm_state_change, NULL); + + /* Initialize a monitor terminal for gdb */ + mon_chr = qemu_mallocz(sizeof(*mon_chr)); + mon_chr->chr_write = gdb_monitor_write; + monitor_init(mon_chr, 0); + } else { + if (s->chr) + qemu_chr_close(s->chr); + mon_chr = s->mon_chr; + memset(s, 0, sizeof(GDBState)); + } + s->c_cpu = first_cpu; + s->g_cpu = first_cpu; + s->chr = chr; + s->state = chr ? RS_IDLE : RS_INACTIVE; + s->mon_chr = mon_chr; + + return 0; +} +#endif diff --git a/gdbstub.h b/gdbstub.h new file mode 100644 index 0000000..d82334f --- /dev/null +++ b/gdbstub.h @@ -0,0 +1,44 @@ +#ifndef GDBSTUB_H +#define GDBSTUB_H + +#define DEFAULT_GDBSTUB_PORT "1234" + +/* GDB breakpoint/watchpoint types */ +#define GDB_BREAKPOINT_SW 0 +#define GDB_BREAKPOINT_HW 1 +#define GDB_WATCHPOINT_WRITE 2 +#define GDB_WATCHPOINT_READ 3 +#define GDB_WATCHPOINT_ACCESS 4 + +#ifdef NEED_CPU_H +typedef void (*gdb_syscall_complete_cb)(CPUState *env, + target_ulong ret, target_ulong err); + +void gdb_do_syscall(gdb_syscall_complete_cb cb, const char *fmt, ...); +int use_gdb_syscalls(void); +void gdb_set_stop_cpu(CPUState *env); +void gdb_exit(CPUState *, int); +#ifdef CONFIG_USER_ONLY +int gdb_queuesig (void); +int gdb_handlesig (CPUState *, int); +void gdb_signalled(CPUState *, int); +void gdbserver_fork(CPUState *); +#endif +/* Get or set a register. Returns the size of the register. */ +typedef int (*gdb_reg_cb)(CPUState *env, uint8_t *buf, int reg); +void gdb_register_coprocessor(CPUState *env, + gdb_reg_cb get_reg, gdb_reg_cb set_reg, + int num_regs, const char *xml, int g_pos); + +#endif + +#ifdef CONFIG_USER_ONLY +int gdbserver_start(int); +#else +int gdbserver_start(const char *port); +#endif + +/* in gdbstub-xml.c, generated by scripts/feature_to_c.sh */ +extern const char *const xml_builtin[][2]; + +#endif diff --git a/gen-icount.h b/gen-icount.h new file mode 100644 index 0000000..8879da6 --- /dev/null +++ b/gen-icount.h @@ -0,0 +1,48 @@ +#include "qemu-timer.h" + +/* Helpers for instruction counting code generation. */ + +static TCGArg *icount_arg; +static int icount_label; + +static inline void gen_icount_start(void) +{ + TCGv_i32 count; + + if (!use_icount) + return; + + icount_label = gen_new_label(); + count = tcg_temp_local_new_i32(); + tcg_gen_ld_i32(count, cpu_env, offsetof(CPUState, icount_decr.u32)); + /* This is a horrid hack to allow fixing up the value later. */ + icount_arg = gen_opparam_ptr + 1; + tcg_gen_subi_i32(count, count, 0xdeadbeef); + + tcg_gen_brcondi_i32(TCG_COND_LT, count, 0, icount_label); + tcg_gen_st16_i32(count, cpu_env, offsetof(CPUState, icount_decr.u16.low)); + tcg_temp_free_i32(count); +} + +static void gen_icount_end(TranslationBlock *tb, int num_insns) +{ + if (use_icount) { + *icount_arg = num_insns; + gen_set_label(icount_label); + tcg_gen_exit_tb((long)tb + 2); + } +} + +static inline void gen_io_start(void) +{ + TCGv_i32 tmp = tcg_const_i32(1); + tcg_gen_st_i32(tmp, cpu_env, offsetof(CPUState, can_do_io)); + tcg_temp_free_i32(tmp); +} + +static inline void gen_io_end(void) +{ + TCGv_i32 tmp = tcg_const_i32(0); + tcg_gen_st_i32(tmp, cpu_env, offsetof(CPUState, can_do_io)); + tcg_temp_free_i32(tmp); +} diff --git a/hmp-commands.hx b/hmp-commands.hx new file mode 100644 index 0000000..372bef4 --- /dev/null +++ b/hmp-commands.hx @@ -0,0 +1,1369 @@ +HXCOMM Use DEFHEADING() to define headings in both help text and texi +HXCOMM Text between STEXI and ETEXI are copied to texi version and +HXCOMM discarded from C version +HXCOMM DEF(command, args, callback, arg_string, help) is used to construct +HXCOMM monitor commands +HXCOMM HXCOMM can be used for comments, discarded from both texi and C + +STEXI +@table @option +ETEXI + + { + .name = "help|?", + .args_type = "name:s?", + .params = "[cmd]", + .help = "show the help", + .mhandler.cmd = do_help_cmd, + }, + +STEXI +@item help or ? [@var{cmd}] +@findex help +Show the help for all commands or just for command @var{cmd}. +ETEXI + + { + .name = "commit", + .args_type = "device:B", + .params = "device|all", + .help = "commit changes to the disk images (if -snapshot is used) or backing files", + .mhandler.cmd = do_commit, + }, + +STEXI +@item commit +@findex commit +Commit changes to the disk images (if -snapshot is used) or backing files. +ETEXI + + { + .name = "q|quit", + .args_type = "", + .params = "", + .help = "quit the emulator", + .user_print = monitor_user_noop, + .mhandler.cmd_new = do_quit, + }, + +STEXI +@item q or quit +@findex quit +Quit the emulator. +ETEXI + + { + .name = "block_resize", + .args_type = "device:B,size:o", + .params = "device size", + .help = "resize a block image", + .user_print = monitor_user_noop, + .mhandler.cmd_new = do_block_resize, + }, + +STEXI +@item block_resize +@findex block_resize +Resize a block image while a guest is running. Usually requires guest +action to see the updated size. Resize to a lower size is supported, +but should be used with extreme caution. Note that this command only +resizes image files, it can not resize block devices like LVM volumes. +ETEXI + + + { + .name = "eject", + .args_type = "force:-f,device:B", + .params = "[-f] device", + .help = "eject a removable medium (use -f to force it)", + .user_print = monitor_user_noop, + .mhandler.cmd_new = do_eject, + }, + +STEXI +@item eject [-f] @var{device} +@findex eject +Eject a removable medium (use -f to force it). +ETEXI + + { + .name = "drive_del", + .args_type = "id:s", + .params = "device", + .help = "remove host block device", + .user_print = monitor_user_noop, + .mhandler.cmd_new = do_drive_del, + }, + +STEXI +@item drive_del @var{device} +@findex drive_del +Remove host block device. The result is that guest generated IO is no longer +submitted against the host device underlying the disk. Once a drive has +been deleted, the QEMU Block layer returns -EIO which results in IO +errors in the guest for applications that are reading/writing to the device. +ETEXI + + { + .name = "change", + .args_type = "device:B,target:F,arg:s?", + .params = "device filename [format]", + .help = "change a removable medium, optional format", + .user_print = monitor_user_noop, + .mhandler.cmd_new = do_change, + }, + +STEXI +@item change @var{device} @var{setting} +@findex change + +Change the configuration of a device. + +@table @option +@item change @var{diskdevice} @var{filename} [@var{format}] +Change the medium for a removable disk device to point to @var{filename}. eg + +@example +(qemu) change ide1-cd0 /path/to/some.iso +@end example + +@var{format} is optional. + +@item change vnc @var{display},@var{options} +Change the configuration of the VNC server. The valid syntax for @var{display} +and @var{options} are described at @ref{sec_invocation}. eg + +@example +(qemu) change vnc localhost:1 +@end example + +@item change vnc password [@var{password}] + +Change the password associated with the VNC server. If the new password is not +supplied, the monitor will prompt for it to be entered. VNC passwords are only +significant up to 8 letters. eg + +@example +(qemu) change vnc password +Password: ******** +@end example + +@end table +ETEXI + + { + .name = "screendump", + .args_type = "filename:F", + .params = "filename", + .help = "save screen into PPM image 'filename'", + .user_print = monitor_user_noop, + .mhandler.cmd_new = do_screen_dump, + }, + +STEXI +@item screendump @var{filename} +@findex screendump +Save screen into PPM image @var{filename}. +ETEXI + + { + .name = "logfile", + .args_type = "filename:F", + .params = "filename", + .help = "output logs to 'filename'", + .mhandler.cmd = do_logfile, + }, + +STEXI +@item logfile @var{filename} +@findex logfile +Output logs to @var{filename}. +ETEXI + +#ifdef CONFIG_SIMPLE_TRACE + { + .name = "trace-event", + .args_type = "name:s,option:b", + .params = "name on|off", + .help = "changes status of a specific trace event", + .mhandler.cmd = do_change_trace_event_state, + }, + +STEXI +@item trace-event +@findex trace-event +changes status of a trace event +ETEXI + + { + .name = "trace-file", + .args_type = "op:s?,arg:F?", + .params = "on|off|flush|set [arg]", + .help = "open, close, or flush trace file, or set a new file name", + .mhandler.cmd = do_trace_file, + }, + +STEXI +@item trace-file on|off|flush +@findex trace-file +Open, close, or flush the trace file. If no argument is given, the status of the trace file is displayed. +ETEXI +#endif + + { + .name = "log", + .args_type = "items:s", + .params = "item1[,...]", + .help = "activate logging of the specified items to '/tmp/qemu.log'", + .mhandler.cmd = do_log, + }, + +STEXI +@item log @var{item1}[,...] +@findex log +Activate logging of the specified items to @file{/tmp/qemu.log}. +ETEXI + + { + .name = "savevm", + .args_type = "name:s?", + .params = "[tag|id]", + .help = "save a VM snapshot. If no tag or id are provided, a new snapshot is created", + .mhandler.cmd = do_savevm, + }, + +STEXI +@item savevm [@var{tag}|@var{id}] +@findex savevm +Create a snapshot of the whole virtual machine. If @var{tag} is +provided, it is used as human readable identifier. If there is already +a snapshot with the same tag or ID, it is replaced. More info at +@ref{vm_snapshots}. +ETEXI + + { + .name = "loadvm", + .args_type = "name:s", + .params = "tag|id", + .help = "restore a VM snapshot from its tag or id", + .mhandler.cmd = do_loadvm, + }, + +STEXI +@item loadvm @var{tag}|@var{id} +@findex loadvm +Set the whole virtual machine to the snapshot identified by the tag +@var{tag} or the unique snapshot ID @var{id}. +ETEXI + + { + .name = "delvm", + .args_type = "name:s", + .params = "tag|id", + .help = "delete a VM snapshot from its tag or id", + .mhandler.cmd = do_delvm, + }, + +STEXI +@item delvm @var{tag}|@var{id} +@findex delvm +Delete the snapshot identified by @var{tag} or @var{id}. +ETEXI + + { + .name = "singlestep", + .args_type = "option:s?", + .params = "[on|off]", + .help = "run emulation in singlestep mode or switch to normal mode", + .mhandler.cmd = do_singlestep, + }, + +STEXI +@item singlestep [off] +@findex singlestep +Run the emulation in single step mode. +If called with option off, the emulation returns to normal mode. +ETEXI + + { + .name = "stop", + .args_type = "", + .params = "", + .help = "stop emulation", + .user_print = monitor_user_noop, + .mhandler.cmd_new = do_stop, + }, + +STEXI +@item stop +@findex stop +Stop emulation. +ETEXI + + { + .name = "c|cont", + .args_type = "", + .params = "", + .help = "resume emulation", + .user_print = monitor_user_noop, + .mhandler.cmd_new = do_cont, + }, + +STEXI +@item c or cont +@findex cont +Resume emulation. +ETEXI + + { + .name = "gdbserver", + .args_type = "device:s?", + .params = "[device]", + .help = "start gdbserver on given device (default 'tcp::1234'), stop with 'none'", + .mhandler.cmd = do_gdbserver, + }, + +STEXI +@item gdbserver [@var{port}] +@findex gdbserver +Start gdbserver session (default @var{port}=1234) +ETEXI + + { + .name = "x", + .args_type = "fmt:/,addr:l", + .params = "/fmt addr", + .help = "virtual memory dump starting at 'addr'", + .mhandler.cmd = do_memory_dump, + }, + +STEXI +@item x/fmt @var{addr} +@findex x +Virtual memory dump starting at @var{addr}. +ETEXI + + { + .name = "xp", + .args_type = "fmt:/,addr:l", + .params = "/fmt addr", + .help = "physical memory dump starting at 'addr'", + .mhandler.cmd = do_physical_memory_dump, + }, + +STEXI +@item xp /@var{fmt} @var{addr} +@findex xp +Physical memory dump starting at @var{addr}. + +@var{fmt} is a format which tells the command how to format the +data. Its syntax is: @option{/@{count@}@{format@}@{size@}} + +@table @var +@item count +is the number of items to be dumped. + +@item format +can be x (hex), d (signed decimal), u (unsigned decimal), o (octal), +c (char) or i (asm instruction). + +@item size +can be b (8 bits), h (16 bits), w (32 bits) or g (64 bits). On x86, +@code{h} or @code{w} can be specified with the @code{i} format to +respectively select 16 or 32 bit code instruction size. + +@end table + +Examples: +@itemize +@item +Dump 10 instructions at the current instruction pointer: +@example +(qemu) x/10i $eip +0x90107063: ret +0x90107064: sti +0x90107065: lea 0x0(%esi,1),%esi +0x90107069: lea 0x0(%edi,1),%edi +0x90107070: ret +0x90107071: jmp 0x90107080 +0x90107073: nop +0x90107074: nop +0x90107075: nop +0x90107076: nop +@end example + +@item +Dump 80 16 bit values at the start of the video memory. +@smallexample +(qemu) xp/80hx 0xb8000 +0x000b8000: 0x0b50 0x0b6c 0x0b65 0x0b78 0x0b38 0x0b36 0x0b2f 0x0b42 +0x000b8010: 0x0b6f 0x0b63 0x0b68 0x0b73 0x0b20 0x0b56 0x0b47 0x0b41 +0x000b8020: 0x0b42 0x0b69 0x0b6f 0x0b73 0x0b20 0x0b63 0x0b75 0x0b72 +0x000b8030: 0x0b72 0x0b65 0x0b6e 0x0b74 0x0b2d 0x0b63 0x0b76 0x0b73 +0x000b8040: 0x0b20 0x0b30 0x0b35 0x0b20 0x0b4e 0x0b6f 0x0b76 0x0b20 +0x000b8050: 0x0b32 0x0b30 0x0b30 0x0b33 0x0720 0x0720 0x0720 0x0720 +0x000b8060: 0x0720 0x0720 0x0720 0x0720 0x0720 0x0720 0x0720 0x0720 +0x000b8070: 0x0720 0x0720 0x0720 0x0720 0x0720 0x0720 0x0720 0x0720 +0x000b8080: 0x0720 0x0720 0x0720 0x0720 0x0720 0x0720 0x0720 0x0720 +0x000b8090: 0x0720 0x0720 0x0720 0x0720 0x0720 0x0720 0x0720 0x0720 +@end smallexample +@end itemize +ETEXI + + { + .name = "p|print", + .args_type = "fmt:/,val:l", + .params = "/fmt expr", + .help = "print expression value (use $reg for CPU register access)", + .mhandler.cmd = do_print, + }, + +STEXI +@item p or print/@var{fmt} @var{expr} +@findex print + +Print expression value. Only the @var{format} part of @var{fmt} is +used. +ETEXI + + { + .name = "i", + .args_type = "fmt:/,addr:i,index:i.", + .params = "/fmt addr", + .help = "I/O port read", + .mhandler.cmd = do_ioport_read, + }, + +STEXI +Read I/O port. +ETEXI + + { + .name = "o", + .args_type = "fmt:/,addr:i,val:i", + .params = "/fmt addr value", + .help = "I/O port write", + .mhandler.cmd = do_ioport_write, + }, + +STEXI +Write to I/O port. +ETEXI + + { + .name = "sendkey", + .args_type = "string:s,hold_time:i?", + .params = "keys [hold_ms]", + .help = "send keys to the VM (e.g. 'sendkey ctrl-alt-f1', default hold time=100 ms)", + .mhandler.cmd = do_sendkey, + }, + +STEXI +@item sendkey @var{keys} +@findex sendkey + +Send @var{keys} to the emulator. @var{keys} could be the name of the +key or @code{#} followed by the raw value in either decimal or hexadecimal +format. Use @code{-} to press several keys simultaneously. Example: +@example +sendkey ctrl-alt-f1 +@end example + +This command is useful to send keys that your graphical user interface +intercepts at low level, such as @code{ctrl-alt-f1} in X Window. +ETEXI + + { + .name = "system_reset", + .args_type = "", + .params = "", + .help = "reset the system", + .user_print = monitor_user_noop, + .mhandler.cmd_new = do_system_reset, + }, + +STEXI +@item system_reset +@findex system_reset + +Reset the system. +ETEXI + + { + .name = "system_powerdown", + .args_type = "", + .params = "", + .help = "send system power down event", + .user_print = monitor_user_noop, + .mhandler.cmd_new = do_system_powerdown, + }, + +STEXI +@item system_powerdown +@findex system_powerdown + +Power down the system (if supported). +ETEXI + + { + .name = "sum", + .args_type = "start:i,size:i", + .params = "addr size", + .help = "compute the checksum of a memory region", + .mhandler.cmd = do_sum, + }, + +STEXI +@item sum @var{addr} @var{size} +@findex sum + +Compute the checksum of a memory region. +ETEXI + + { + .name = "usb_add", + .args_type = "devname:s", + .params = "device", + .help = "add USB device (e.g. 'host:bus.addr' or 'host:vendor_id:product_id')", + .mhandler.cmd = do_usb_add, + }, + +STEXI +@item usb_add @var{devname} +@findex usb_add + +Add the USB device @var{devname}. For details of available devices see +@ref{usb_devices} +ETEXI + + { + .name = "usb_del", + .args_type = "devname:s", + .params = "device", + .help = "remove USB device 'bus.addr'", + .mhandler.cmd = do_usb_del, + }, + +STEXI +@item usb_del @var{devname} +@findex usb_del + +Remove the USB device @var{devname} from the QEMU virtual USB +hub. @var{devname} has the syntax @code{bus.addr}. Use the monitor +command @code{info usb} to see the devices you can remove. +ETEXI + + { + .name = "device_add", + .args_type = "device:O", + .params = "driver[,prop=value][,...]", + .help = "add device, like -device on the command line", + .user_print = monitor_user_noop, + .mhandler.cmd_new = do_device_add, + }, + +STEXI +@item device_add @var{config} +@findex device_add + +Add device. +ETEXI + + { + .name = "device_del", + .args_type = "id:s", + .params = "device", + .help = "remove device", + .user_print = monitor_user_noop, + .mhandler.cmd_new = do_device_del, + }, + +STEXI +@item device_del @var{id} +@findex device_del + +Remove device @var{id}. +ETEXI + + { + .name = "cpu", + .args_type = "index:i", + .params = "index", + .help = "set the default CPU", + .user_print = monitor_user_noop, + .mhandler.cmd_new = do_cpu_set, + }, + +STEXI +@item cpu @var{index} +@findex cpu +Set the default CPU. +ETEXI + + { + .name = "mouse_move", + .args_type = "dx_str:s,dy_str:s,dz_str:s?", + .params = "dx dy [dz]", + .help = "send mouse move events", + .mhandler.cmd = do_mouse_move, + }, + +STEXI +@item mouse_move @var{dx} @var{dy} [@var{dz}] +@findex mouse_move +Move the active mouse to the specified coordinates @var{dx} @var{dy} +with optional scroll axis @var{dz}. +ETEXI + + { + .name = "mouse_button", + .args_type = "button_state:i", + .params = "state", + .help = "change mouse button state (1=L, 2=M, 4=R)", + .mhandler.cmd = do_mouse_button, + }, + +STEXI +@item mouse_button @var{val} +@findex mouse_button +Change the active mouse button state @var{val} (1=L, 2=M, 4=R). +ETEXI + + { + .name = "mouse_set", + .args_type = "index:i", + .params = "index", + .help = "set which mouse device receives events", + .mhandler.cmd = do_mouse_set, + }, + +STEXI +@item mouse_set @var{index} +@findex mouse_set +Set which mouse device receives events at given @var{index}, index +can be obtained with +@example +info mice +@end example +ETEXI + +#ifdef HAS_AUDIO + { + .name = "wavcapture", + .args_type = "path:F,freq:i?,bits:i?,nchannels:i?", + .params = "path [frequency [bits [channels]]]", + .help = "capture audio to a wave file (default frequency=44100 bits=16 channels=2)", + .mhandler.cmd = do_wav_capture, + }, +#endif +STEXI +@item wavcapture @var{filename} [@var{frequency} [@var{bits} [@var{channels}]]] +@findex wavcapture +Capture audio into @var{filename}. Using sample rate @var{frequency} +bits per sample @var{bits} and number of channels @var{channels}. + +Defaults: +@itemize @minus +@item Sample rate = 44100 Hz - CD quality +@item Bits = 16 +@item Number of channels = 2 - Stereo +@end itemize +ETEXI + +#ifdef HAS_AUDIO + { + .name = "stopcapture", + .args_type = "n:i", + .params = "capture index", + .help = "stop capture", + .mhandler.cmd = do_stop_capture, + }, +#endif +STEXI +@item stopcapture @var{index} +@findex stopcapture +Stop capture with a given @var{index}, index can be obtained with +@example +info capture +@end example +ETEXI + + { + .name = "memsave", + .args_type = "val:l,size:i,filename:s", + .params = "addr size file", + .help = "save to disk virtual memory dump starting at 'addr' of size 'size'", + .user_print = monitor_user_noop, + .mhandler.cmd_new = do_memory_save, + }, + +STEXI +@item memsave @var{addr} @var{size} @var{file} +@findex memsave +save to disk virtual memory dump starting at @var{addr} of size @var{size}. +ETEXI + + { + .name = "pmemsave", + .args_type = "val:l,size:i,filename:s", + .params = "addr size file", + .help = "save to disk physical memory dump starting at 'addr' of size 'size'", + .user_print = monitor_user_noop, + .mhandler.cmd_new = do_physical_memory_save, + }, + +STEXI +@item pmemsave @var{addr} @var{size} @var{file} +@findex pmemsave +save to disk physical memory dump starting at @var{addr} of size @var{size}. +ETEXI + + { + .name = "boot_set", + .args_type = "bootdevice:s", + .params = "bootdevice", + .help = "define new values for the boot device list", + .mhandler.cmd = do_boot_set, + }, + +STEXI +@item boot_set @var{bootdevicelist} +@findex boot_set + +Define new values for the boot device list. Those values will override +the values specified on the command line through the @code{-boot} option. + +The values that can be specified here depend on the machine type, but are +the same that can be specified in the @code{-boot} command line option. +ETEXI + +#if defined(TARGET_I386) + { + .name = "nmi", + .args_type = "cpu_index:i", + .params = "cpu", + .help = "inject an NMI on the given CPU", + .mhandler.cmd = do_inject_nmi, + }, +#endif +STEXI +@item nmi @var{cpu} +@findex nmi +Inject an NMI on the given CPU (x86 only). +ETEXI + + { + .name = "migrate", + .args_type = "detach:-d,blk:-b,inc:-i,uri:s", + .params = "[-d] [-b] [-i] uri", + .help = "migrate to URI (using -d to not wait for completion)" + "\n\t\t\t -b for migration without shared storage with" + " full copy of disk\n\t\t\t -i for migration without " + "shared storage with incremental copy of disk " + "(base image shared between src and destination)", + .user_print = monitor_user_noop, + .mhandler.cmd_new = do_migrate, + }, + + +STEXI +@item migrate [-d] [-b] [-i] @var{uri} +@findex migrate +Migrate to @var{uri} (using -d to not wait for completion). + -b for migration with full copy of disk + -i for migration with incremental copy of disk (base image is shared) +ETEXI + + { + .name = "migrate_cancel", + .args_type = "", + .params = "", + .help = "cancel the current VM migration", + .user_print = monitor_user_noop, + .mhandler.cmd_new = do_migrate_cancel, + }, + +STEXI +@item migrate_cancel +@findex migrate_cancel +Cancel the current VM migration. +ETEXI + + { + .name = "migrate_set_speed", + .args_type = "value:o", + .params = "value", + .help = "set maximum speed (in bytes) for migrations. " + "Defaults to MB if no size suffix is specified, ie. B/K/M/G/T", + .user_print = monitor_user_noop, + .mhandler.cmd_new = do_migrate_set_speed, + }, + +STEXI +@item migrate_set_speed @var{value} +@findex migrate_set_speed +Set maximum speed to @var{value} (in bytes) for migrations. +ETEXI + + { + .name = "migrate_set_downtime", + .args_type = "value:T", + .params = "value", + .help = "set maximum tolerated downtime (in seconds) for migrations", + .user_print = monitor_user_noop, + .mhandler.cmd_new = do_migrate_set_downtime, + }, + +STEXI +@item migrate_set_downtime @var{second} +@findex migrate_set_downtime +Set maximum tolerated downtime (in seconds) for migration. +ETEXI + + { + .name = "snapshot_blkdev", + .args_type = "device:B,snapshot_file:s?,format:s?", + .params = "device [new-image-file] [format]", + .help = "initiates a live snapshot\n\t\t\t" + "of device. If a new image file is specified, the\n\t\t\t" + "new image file will become the new root image.\n\t\t\t" + "If format is specified, the snapshot file will\n\t\t\t" + "be created in that format. Otherwise the\n\t\t\t" + "snapshot will be internal! (currently unsupported)", + .mhandler.cmd_new = do_snapshot_blkdev, + }, + +STEXI +@item client_migrate_info @var{protocol} @var{hostname} @var{port} @var{tls-port} @var{cert-subject} +@findex client_migrate_info +Set the spice/vnc connection info for the migration target. The spice/vnc +server will ask the spice/vnc client to automatically reconnect using the +new parameters (if specified) once the vm migration finished successfully. +ETEXI + + { + .name = "client_migrate_info", + .args_type = "protocol:s,hostname:s,port:i?,tls-port:i?,cert-subject:s?", + .params = "protocol hostname port tls-port cert-subject", + .help = "send migration info to spice/vnc client", + .user_print = monitor_user_noop, + .mhandler.cmd_new = client_migrate_info, + }, + +STEXI +@item snapshot_blkdev +@findex snapshot_blkdev +Snapshot device, using snapshot file as target if provided +ETEXI + +#if defined(TARGET_I386) + { + .name = "drive_add", + .args_type = "pci_addr:s,opts:s", + .params = "[[:]:]\n" + "[file=file][,if=type][,bus=n]\n" + "[,unit=m][,media=d][index=i]\n" + "[,cyls=c,heads=h,secs=s[,trans=t]]\n" + "[snapshot=on|off][,cache=on|off]", + .help = "add drive to PCI storage controller", + .mhandler.cmd = drive_hot_add, + }, +#endif + +STEXI +@item drive_add +@findex drive_add +Add drive to PCI storage controller. +ETEXI + +#if defined(TARGET_I386) + { + .name = "pci_add", + .args_type = "pci_addr:s,type:s,opts:s?", + .params = "auto|[[:]:] nic|storage [[vlan=n][,macaddr=addr][,model=type]] [file=file][,if=type][,bus=nr]...", + .help = "hot-add PCI device", + .mhandler.cmd = pci_device_hot_add, + }, +#endif + +STEXI +@item pci_add +@findex pci_add +Hot-add PCI device. +ETEXI + +#if defined(TARGET_I386) + { + .name = "pci_del", + .args_type = "pci_addr:s", + .params = "[[:]:]", + .help = "hot remove PCI device", + .mhandler.cmd = do_pci_device_hot_remove, + }, +#endif + +STEXI +@item pci_del +@findex pci_del +Hot remove PCI device. +ETEXI + + { + .name = "pcie_aer_inject_error", + .args_type = "advisory_non_fatal:-a,correctable:-c," + "id:s,error_status:s," + "header0:i?,header1:i?,header2:i?,header3:i?," + "prefix0:i?,prefix1:i?,prefix2:i?,prefix3:i?", + .params = "[-a] [-c] id " + " [ []]", + .help = "inject pcie aer error\n\t\t\t" + " -a for advisory non fatal error\n\t\t\t" + " -c for correctable error\n\t\t\t" + " = qdev device id\n\t\t\t" + " = error string or 32bit\n\t\t\t" + " = 32bit x 4\n\t\t\t" + " = 32bit x 4", + .user_print = pcie_aer_inject_error_print, + .mhandler.cmd_new = do_pcie_aer_inejct_error, + }, + +STEXI +@item pcie_aer_inject_error +@findex pcie_aer_inject_error +Inject PCIe AER error +ETEXI + + { + .name = "host_net_add", + .args_type = "device:s,opts:s?", + .params = "tap|user|socket|vde|dump [options]", + .help = "add host VLAN client", + .mhandler.cmd = net_host_device_add, + }, + +STEXI +@item host_net_add +@findex host_net_add +Add host VLAN client. +ETEXI + + { + .name = "host_net_remove", + .args_type = "vlan_id:i,device:s", + .params = "vlan_id name", + .help = "remove host VLAN client", + .mhandler.cmd = net_host_device_remove, + }, + +STEXI +@item host_net_remove +@findex host_net_remove +Remove host VLAN client. +ETEXI + + { + .name = "netdev_add", + .args_type = "netdev:O", + .params = "[user|tap|socket],id=str[,prop=value][,...]", + .help = "add host network device", + .user_print = monitor_user_noop, + .mhandler.cmd_new = do_netdev_add, + }, + +STEXI +@item netdev_add +@findex netdev_add +Add host network device. +ETEXI + + { + .name = "netdev_del", + .args_type = "id:s", + .params = "id", + .help = "remove host network device", + .user_print = monitor_user_noop, + .mhandler.cmd_new = do_netdev_del, + }, + +STEXI +@item netdev_del +@findex netdev_del +Remove host network device. +ETEXI + +#ifdef CONFIG_SLIRP + { + .name = "hostfwd_add", + .args_type = "arg1:s,arg2:s?,arg3:s?", + .params = "[vlan_id name] [tcp|udp]:[hostaddr]:hostport-[guestaddr]:guestport", + .help = "redirect TCP or UDP connections from host to guest (requires -net user)", + .mhandler.cmd = net_slirp_hostfwd_add, + }, +#endif +STEXI +@item hostfwd_add +@findex hostfwd_add +Redirect TCP or UDP connections from host to guest (requires -net user). +ETEXI + +#ifdef CONFIG_SLIRP + { + .name = "hostfwd_remove", + .args_type = "arg1:s,arg2:s?,arg3:s?", + .params = "[vlan_id name] [tcp|udp]:[hostaddr]:hostport", + .help = "remove host-to-guest TCP or UDP redirection", + .mhandler.cmd = net_slirp_hostfwd_remove, + }, + +#endif +STEXI +@item hostfwd_remove +@findex hostfwd_remove +Remove host-to-guest TCP or UDP redirection. +ETEXI + + { + .name = "balloon", + .args_type = "value:M", + .params = "target", + .help = "request VM to change its memory allocation (in MB)", + .user_print = monitor_user_noop, + .mhandler.cmd_async = do_balloon, + .flags = MONITOR_CMD_ASYNC, + }, + +STEXI +@item balloon @var{value} +@findex balloon +Request VM to change its memory allocation to @var{value} (in MB). +ETEXI + + { + .name = "set_link", + .args_type = "name:s,up:b", + .params = "name on|off", + .help = "change the link status of a network adapter", + .user_print = monitor_user_noop, + .mhandler.cmd_new = do_set_link, + }, + +STEXI +@item set_link @var{name} [on|off] +@findex set_link +Switch link @var{name} on (i.e. up) or off (i.e. down). +ETEXI + + { + .name = "watchdog_action", + .args_type = "action:s", + .params = "[reset|shutdown|poweroff|pause|debug|none]", + .help = "change watchdog action", + .mhandler.cmd = do_watchdog_action, + }, + +STEXI +@item watchdog_action +@findex watchdog_action +Change watchdog action. +ETEXI + + { + .name = "acl_show", + .args_type = "aclname:s", + .params = "aclname", + .help = "list rules in the access control list", + .mhandler.cmd = do_acl_show, + }, + +STEXI +@item acl_show @var{aclname} +@findex acl_show +List all the matching rules in the access control list, and the default +policy. There are currently two named access control lists, +@var{vnc.x509dname} and @var{vnc.username} matching on the x509 client +certificate distinguished name, and SASL username respectively. +ETEXI + + { + .name = "acl_policy", + .args_type = "aclname:s,policy:s", + .params = "aclname allow|deny", + .help = "set default access control list policy", + .mhandler.cmd = do_acl_policy, + }, + +STEXI +@item acl_policy @var{aclname} @code{allow|deny} +@findex acl_policy +Set the default access control list policy, used in the event that +none of the explicit rules match. The default policy at startup is +always @code{deny}. +ETEXI + + { + .name = "acl_add", + .args_type = "aclname:s,match:s,policy:s,index:i?", + .params = "aclname match allow|deny [index]", + .help = "add a match rule to the access control list", + .mhandler.cmd = do_acl_add, + }, + +STEXI +@item acl_add @var{aclname} @var{match} @code{allow|deny} [@var{index}] +@findex acl_add +Add a match rule to the access control list, allowing or denying access. +The match will normally be an exact username or x509 distinguished name, +but can optionally include wildcard globs. eg @code{*@@EXAMPLE.COM} to +allow all users in the @code{EXAMPLE.COM} kerberos realm. The match will +normally be appended to the end of the ACL, but can be inserted +earlier in the list if the optional @var{index} parameter is supplied. +ETEXI + + { + .name = "acl_remove", + .args_type = "aclname:s,match:s", + .params = "aclname match", + .help = "remove a match rule from the access control list", + .mhandler.cmd = do_acl_remove, + }, + +STEXI +@item acl_remove @var{aclname} @var{match} +@findex acl_remove +Remove the specified match rule from the access control list. +ETEXI + + { + .name = "acl_reset", + .args_type = "aclname:s", + .params = "aclname", + .help = "reset the access control list", + .mhandler.cmd = do_acl_reset, + }, + +STEXI +@item acl_reset @var{aclname} +@findex acl_reset +Remove all matches from the access control list, and set the default +policy back to @code{deny}. +ETEXI + +#if defined(TARGET_I386) + + { + .name = "mce", + .args_type = "broadcast:-b,cpu_index:i,bank:i,status:l,mcg_status:l,addr:l,misc:l", + .params = "[-b] cpu bank status mcgstatus addr misc", + .help = "inject a MCE on the given CPU [and broadcast to other CPUs with -b option]", + .mhandler.cmd = do_inject_mce, + }, + +#endif +STEXI +@item mce @var{cpu} @var{bank} @var{status} @var{mcgstatus} @var{addr} @var{misc} +@findex mce (x86) +Inject an MCE on the given CPU (x86 only). +ETEXI + + { + .name = "getfd", + .args_type = "fdname:s", + .params = "getfd name", + .help = "receive a file descriptor via SCM rights and assign it a name", + .user_print = monitor_user_noop, + .mhandler.cmd_new = do_getfd, + }, + +STEXI +@item getfd @var{fdname} +@findex getfd +If a file descriptor is passed alongside this command using the SCM_RIGHTS +mechanism on unix sockets, it is stored using the name @var{fdname} for +later use by other monitor commands. +ETEXI + + { + .name = "closefd", + .args_type = "fdname:s", + .params = "closefd name", + .help = "close a file descriptor previously passed via SCM rights", + .user_print = monitor_user_noop, + .mhandler.cmd_new = do_closefd, + }, + +STEXI +@item closefd @var{fdname} +@findex closefd +Close the file descriptor previously assigned to @var{fdname} using the +@code{getfd} command. This is only needed if the file descriptor was never +used by another monitor command. +ETEXI + + { + .name = "block_passwd", + .args_type = "device:B,password:s", + .params = "block_passwd device password", + .help = "set the password of encrypted block devices", + .user_print = monitor_user_noop, + .mhandler.cmd_new = do_block_set_passwd, + }, + +STEXI +@item block_passwd @var{device} @var{password} +@findex block_passwd +Set the encrypted device @var{device} password to @var{password} +ETEXI + + { + .name = "set_password", + .args_type = "protocol:s,password:s,connected:s?", + .params = "protocol password action-if-connected", + .help = "set spice/vnc password", + .user_print = monitor_user_noop, + .mhandler.cmd_new = set_password, + }, + +STEXI +@item set_password [ vnc | spice ] password [ action-if-connected ] +@findex set_password + +Change spice/vnc password. Use zero to make the password stay valid +forever. @var{action-if-connected} specifies what should happen in +case a connection is established: @var{fail} makes the password change +fail. @var{disconnect} changes the password and disconnects the +client. @var{keep} changes the password and keeps the connection up. +@var{keep} is the default. +ETEXI + + { + .name = "expire_password", + .args_type = "protocol:s,time:s", + .params = "protocol time", + .help = "set spice/vnc password expire-time", + .user_print = monitor_user_noop, + .mhandler.cmd_new = expire_password, + }, + +STEXI +@item expire_password [ vnc | spice ] expire-time +@findex expire_password + +Specify when a password for spice/vnc becomes +invalid. @var{expire-time} accepts: + +@table @var +@item now +Invalidate password instantly. + +@item never +Password stays valid forever. + +@item +nsec +Password stays valid for @var{nsec} seconds starting now. + +@item nsec +Password is invalidated at the given time. @var{nsec} are the seconds +passed since 1970, i.e. unix epoch. + +@end table +ETEXI + + { + .name = "info", + .args_type = "item:s?", + .params = "[subcommand]", + .help = "show various information about the system state", + .mhandler.cmd = do_info, + }, + +STEXI +@item info @var{subcommand} +@findex info +Show various information about the system state. + +@table @option +@item info version +show the version of QEMU +@item info network +show the various VLANs and the associated devices +@item info chardev +show the character devices +@item info block +show the block devices +@item info blockstats +show block device statistics +@item info registers +show the cpu registers +@item info cpus +show infos for each CPU +@item info history +show the command line history +@item info irq +show the interrupts statistics (if available) +@item info pic +show i8259 (PIC) state +@item info pci +show emulated PCI device info +@item info tlb +show virtual to physical memory mappings (i386, SH4 and SPARC only) +@item info mem +show the active virtual memory mappings (i386 only) +@item info jit +show dynamic compiler info +@item info kvm +show KVM information +@item info numa +show NUMA information +@item info kvm +show KVM information +@item info usb +show USB devices plugged on the virtual USB hub +@item info usbhost +show all USB host devices +@item info profile +show profiling information +@item info capture +show information about active capturing +@item info snapshots +show list of VM snapshots +@item info status +show the current VM status (running|paused) +@item info pcmcia +show guest PCMCIA status +@item info mice +show which guest mouse is receiving events +@item info vnc +show the vnc server status +@item info name +show the current VM name +@item info uuid +show the current VM UUID +@item info cpustats +show CPU statistics +@item info usernet +show user network stack connection states +@item info migrate +show migration status +@item info balloon +show balloon information +@item info qtree +show device tree +@item info qdm +show qdev device model list +@item info roms +show roms +@end table +ETEXI + +#ifdef CONFIG_SIMPLE_TRACE +STEXI +@item info trace +show contents of trace buffer +@item info trace-events +show available trace events and their state +ETEXI +#endif + +STEXI +@end table +ETEXI diff --git a/host-utils.c b/host-utils.c new file mode 100644 index 0000000..dc96123 --- /dev/null +++ b/host-utils.c @@ -0,0 +1,105 @@ +/* + * Utility compute operations used by translated code. + * + * Copyright (c) 2003 Fabrice Bellard + * Copyright (c) 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 +#include +#include "host-utils.h" + +//#define DEBUG_MULDIV + +/* Long integer helpers */ +#if !defined(__x86_64__) +static void add128 (uint64_t *plow, uint64_t *phigh, uint64_t a, uint64_t b) +{ + *plow += a; + /* carry test */ + if (*plow < a) + (*phigh)++; + *phigh += b; +} + +static void neg128 (uint64_t *plow, uint64_t *phigh) +{ + *plow = ~*plow; + *phigh = ~*phigh; + add128(plow, phigh, 1, 0); +} + +static void mul64 (uint64_t *plow, uint64_t *phigh, uint64_t a, uint64_t b) +{ + uint32_t a0, a1, b0, b1; + uint64_t v; + + a0 = a; + a1 = a >> 32; + + b0 = b; + b1 = b >> 32; + + v = (uint64_t)a0 * (uint64_t)b0; + *plow = v; + *phigh = 0; + + v = (uint64_t)a0 * (uint64_t)b1; + add128(plow, phigh, v << 32, v >> 32); + + v = (uint64_t)a1 * (uint64_t)b0; + add128(plow, phigh, v << 32, v >> 32); + + v = (uint64_t)a1 * (uint64_t)b1; + *phigh += v; +} + +/* Unsigned 64x64 -> 128 multiplication */ +void mulu64 (uint64_t *plow, uint64_t *phigh, uint64_t a, uint64_t b) +{ + mul64(plow, phigh, a, b); +#if defined(DEBUG_MULDIV) + printf("mulu64: 0x%016llx * 0x%016llx = 0x%016llx%016llx\n", + a, b, *phigh, *plow); +#endif +} + +/* Signed 64x64 -> 128 multiplication */ +void muls64 (uint64_t *plow, uint64_t *phigh, int64_t a, int64_t b) +{ + int sa, sb; + + sa = (a < 0); + if (sa) + a = -a; + sb = (b < 0); + if (sb) + b = -b; + mul64(plow, phigh, a, b); + if (sa ^ sb) { + neg128(plow, phigh); + } +#if defined(DEBUG_MULDIV) + printf("muls64: 0x%016llx * 0x%016llx = 0x%016llx%016llx\n", + a, b, *phigh, *plow); +#endif +} +#endif /* !defined(__x86_64__) */ diff --git a/host-utils.h b/host-utils.h new file mode 100644 index 0000000..0ddc176 --- /dev/null +++ b/host-utils.h @@ -0,0 +1,236 @@ +/* + * Utility compute operations used by translated code. + * + * Copyright (c) 2007 Thiemo Seufer + * 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 "osdep.h" + +#if defined(__x86_64__) +#define __HAVE_FAST_MULU64__ +static inline void mulu64(uint64_t *plow, uint64_t *phigh, + uint64_t a, uint64_t b) +{ + __asm__ ("mul %0\n\t" + : "=d" (*phigh), "=a" (*plow) + : "a" (a), "0" (b)); +} +#define __HAVE_FAST_MULS64__ +static inline void muls64(uint64_t *plow, uint64_t *phigh, + int64_t a, int64_t b) +{ + __asm__ ("imul %0\n\t" + : "=d" (*phigh), "=a" (*plow) + : "a" (a), "0" (b)); +} +#else +void muls64(uint64_t *phigh, uint64_t *plow, int64_t a, int64_t b); +void mulu64(uint64_t *phigh, uint64_t *plow, uint64_t a, uint64_t b); +#endif + +/* Binary search for leading zeros. */ + +static inline int clz32(uint32_t val) +{ +#if QEMU_GNUC_PREREQ(3, 4) + if (val) + return __builtin_clz(val); + else + return 32; +#else + int cnt = 0; + + if (!(val & 0xFFFF0000U)) { + cnt += 16; + val <<= 16; + } + if (!(val & 0xFF000000U)) { + cnt += 8; + val <<= 8; + } + if (!(val & 0xF0000000U)) { + cnt += 4; + val <<= 4; + } + if (!(val & 0xC0000000U)) { + cnt += 2; + val <<= 2; + } + if (!(val & 0x80000000U)) { + cnt++; + val <<= 1; + } + if (!(val & 0x80000000U)) { + cnt++; + } + return cnt; +#endif +} + +static inline int clo32(uint32_t val) +{ + return clz32(~val); +} + +static inline int clz64(uint64_t val) +{ +#if QEMU_GNUC_PREREQ(3, 4) + if (val) + return __builtin_clzll(val); + else + return 64; +#else + int cnt = 0; + + if (!(val >> 32)) { + cnt += 32; + } else { + val >>= 32; + } + + return cnt + clz32(val); +#endif +} + +static inline int clo64(uint64_t val) +{ + return clz64(~val); +} + +static inline int ctz32(uint32_t val) +{ +#if QEMU_GNUC_PREREQ(3, 4) + if (val) + return __builtin_ctz(val); + else + return 32; +#else + int cnt; + + cnt = 0; + if (!(val & 0x0000FFFFUL)) { + cnt += 16; + val >>= 16; + } + if (!(val & 0x000000FFUL)) { + cnt += 8; + val >>= 8; + } + if (!(val & 0x0000000FUL)) { + cnt += 4; + val >>= 4; + } + if (!(val & 0x00000003UL)) { + cnt += 2; + val >>= 2; + } + if (!(val & 0x00000001UL)) { + cnt++; + val >>= 1; + } + if (!(val & 0x00000001UL)) { + cnt++; + } + + return cnt; +#endif +} + +static inline int cto32(uint32_t val) +{ + return ctz32(~val); +} + +static inline int ctz64(uint64_t val) +{ +#if QEMU_GNUC_PREREQ(3, 4) + if (val) + return __builtin_ctzll(val); + else + return 64; +#else + int cnt; + + cnt = 0; + if (!((uint32_t)val)) { + cnt += 32; + val >>= 32; + } + + return cnt + ctz32(val); +#endif +} + +static inline int cto64(uint64_t val) +{ + return ctz64(~val); +} + +static inline int ctpop8(uint8_t val) +{ + val = (val & 0x55) + ((val >> 1) & 0x55); + val = (val & 0x33) + ((val >> 2) & 0x33); + val = (val & 0x0f) + ((val >> 4) & 0x0f); + + return val; +} + +static inline int ctpop16(uint16_t val) +{ + val = (val & 0x5555) + ((val >> 1) & 0x5555); + val = (val & 0x3333) + ((val >> 2) & 0x3333); + val = (val & 0x0f0f) + ((val >> 4) & 0x0f0f); + val = (val & 0x00ff) + ((val >> 8) & 0x00ff); + + return val; +} + +static inline int ctpop32(uint32_t val) +{ +#if QEMU_GNUC_PREREQ(3, 4) + return __builtin_popcount(val); +#else + val = (val & 0x55555555) + ((val >> 1) & 0x55555555); + val = (val & 0x33333333) + ((val >> 2) & 0x33333333); + val = (val & 0x0f0f0f0f) + ((val >> 4) & 0x0f0f0f0f); + val = (val & 0x00ff00ff) + ((val >> 8) & 0x00ff00ff); + val = (val & 0x0000ffff) + ((val >> 16) & 0x0000ffff); + + return val; +#endif +} + +static inline int ctpop64(uint64_t val) +{ +#if QEMU_GNUC_PREREQ(3, 4) + return __builtin_popcountll(val); +#else + val = (val & 0x5555555555555555ULL) + ((val >> 1) & 0x5555555555555555ULL); + val = (val & 0x3333333333333333ULL) + ((val >> 2) & 0x3333333333333333ULL); + val = (val & 0x0f0f0f0f0f0f0f0fULL) + ((val >> 4) & 0x0f0f0f0f0f0f0f0fULL); + val = (val & 0x00ff00ff00ff00ffULL) + ((val >> 8) & 0x00ff00ff00ff00ffULL); + val = (val & 0x0000ffff0000ffffULL) + ((val >> 16) & 0x0000ffff0000ffffULL); + val = (val & 0x00000000ffffffffULL) + ((val >> 32) & 0x00000000ffffffffULL); + + return val; +#endif +} diff --git a/hpet.h b/hpet.h new file mode 100644 index 0000000..754051a --- /dev/null +++ b/hpet.h @@ -0,0 +1,22 @@ +#ifndef __HPET__ +#define __HPET__ 1 + + + +struct hpet_info { + unsigned long hi_ireqfreq; /* Hz */ + unsigned long hi_flags; /* information */ + unsigned short hi_hpet; + unsigned short hi_timer; +}; + +#define HPET_INFO_PERIODIC 0x0001 /* timer is periodic */ + +#define HPET_IE_ON _IO('h', 0x01) /* interrupt on */ +#define HPET_IE_OFF _IO('h', 0x02) /* interrupt off */ +#define HPET_INFO _IOR('h', 0x03, struct hpet_info) +#define HPET_EPI _IO('h', 0x04) /* enable periodic */ +#define HPET_DPI _IO('h', 0x05) /* disable periodic */ +#define HPET_IRQFREQ _IOW('h', 0x6, unsigned long) /* IRQFREQ usec */ + +#endif /* !__HPET__ */ diff --git a/hppa-dis.c b/hppa-dis.c new file mode 100644 index 0000000..49f99c8 --- /dev/null +++ b/hppa-dis.c @@ -0,0 +1,2831 @@ +/* Disassembler for the PA-RISC. Somewhat derived from sparc-pinsn.c. + Copyright 1989, 1990, 1992, 1993, 1994, 1995, 1998, 1999, 2000, 2001, 2003, + 2005 Free Software Foundation, Inc. + + Contributed by the Center for Software Science at the + University of Utah (pa-gdb-bugs@cs.utah.edu). + + 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 . */ + +#include "dis-asm.h" + +/* HP PA-RISC SOM object file format: definitions internal to BFD. + Copyright 1990, 1991, 1992, 1993, 1994, 1995, 1996, 1998, 1999, 2000, + 2003 Free Software Foundation, Inc. + + Contributed by the Center for Software Science at the + University of Utah (pa-gdb-bugs@cs.utah.edu). + + This file is part of BFD, the Binary File Descriptor library. + + 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 . */ + +#ifndef _LIBHPPA_H +#define _LIBHPPA_H + +#define BYTES_IN_WORD 4 +#define PA_PAGESIZE 0x1000 + +/* The PA instruction set variants. */ +enum pa_arch {pa10 = 10, pa11 = 11, pa20 = 20, pa20w = 25}; + +/* HP PA-RISC relocation types */ + +enum hppa_reloc_field_selector_type + { + R_HPPA_FSEL = 0x0, + R_HPPA_LSSEL = 0x1, + R_HPPA_RSSEL = 0x2, + R_HPPA_LSEL = 0x3, + R_HPPA_RSEL = 0x4, + R_HPPA_LDSEL = 0x5, + R_HPPA_RDSEL = 0x6, + R_HPPA_LRSEL = 0x7, + R_HPPA_RRSEL = 0x8, + R_HPPA_NSEL = 0x9, + R_HPPA_NLSEL = 0xa, + R_HPPA_NLRSEL = 0xb, + R_HPPA_PSEL = 0xc, + R_HPPA_LPSEL = 0xd, + R_HPPA_RPSEL = 0xe, + R_HPPA_TSEL = 0xf, + R_HPPA_LTSEL = 0x10, + R_HPPA_RTSEL = 0x11, + R_HPPA_LTPSEL = 0x12, + R_HPPA_RTPSEL = 0x13 + }; + +/* /usr/include/reloc.h defines these to constants. We want to use + them in enums, so #undef them before we start using them. We might + be able to fix this another way by simply managing not to include + /usr/include/reloc.h, but currently GDB picks up these defines + somewhere. */ +#undef e_fsel +#undef e_lssel +#undef e_rssel +#undef e_lsel +#undef e_rsel +#undef e_ldsel +#undef e_rdsel +#undef e_lrsel +#undef e_rrsel +#undef e_nsel +#undef e_nlsel +#undef e_nlrsel +#undef e_psel +#undef e_lpsel +#undef e_rpsel +#undef e_tsel +#undef e_ltsel +#undef e_rtsel +#undef e_one +#undef e_two +#undef e_pcrel +#undef e_con +#undef e_plabel +#undef e_abs + +/* for compatibility */ +enum hppa_reloc_field_selector_type_alt + { + e_fsel = R_HPPA_FSEL, + e_lssel = R_HPPA_LSSEL, + e_rssel = R_HPPA_RSSEL, + e_lsel = R_HPPA_LSEL, + e_rsel = R_HPPA_RSEL, + e_ldsel = R_HPPA_LDSEL, + e_rdsel = R_HPPA_RDSEL, + e_lrsel = R_HPPA_LRSEL, + e_rrsel = R_HPPA_RRSEL, + e_nsel = R_HPPA_NSEL, + e_nlsel = R_HPPA_NLSEL, + e_nlrsel = R_HPPA_NLRSEL, + e_psel = R_HPPA_PSEL, + e_lpsel = R_HPPA_LPSEL, + e_rpsel = R_HPPA_RPSEL, + e_tsel = R_HPPA_TSEL, + e_ltsel = R_HPPA_LTSEL, + e_rtsel = R_HPPA_RTSEL, + e_ltpsel = R_HPPA_LTPSEL, + e_rtpsel = R_HPPA_RTPSEL + }; + +enum hppa_reloc_expr_type + { + R_HPPA_E_ONE = 0, + R_HPPA_E_TWO = 1, + R_HPPA_E_PCREL = 2, + R_HPPA_E_CON = 3, + R_HPPA_E_PLABEL = 7, + R_HPPA_E_ABS = 18 + }; + +/* for compatibility */ +enum hppa_reloc_expr_type_alt + { + e_one = R_HPPA_E_ONE, + e_two = R_HPPA_E_TWO, + e_pcrel = R_HPPA_E_PCREL, + e_con = R_HPPA_E_CON, + e_plabel = R_HPPA_E_PLABEL, + e_abs = R_HPPA_E_ABS + }; + + +/* Relocations for function calls must be accompanied by parameter + relocation bits. These bits describe exactly where the caller has + placed the function's arguments and where it expects to find a return + value. + + Both ELF and SOM encode this information within the addend field + of the call relocation. (Note this could break very badly if one + was to make a call like bl foo + 0x12345678). + + The high order 10 bits contain parameter relocation information, + the low order 22 bits contain the constant offset. */ + +#define HPPA_R_ARG_RELOC(a) \ + (((a) >> 22) & 0x3ff) +#define HPPA_R_CONSTANT(a) \ + ((((bfd_signed_vma)(a)) << (BFD_ARCH_SIZE-22)) >> (BFD_ARCH_SIZE-22)) +#define HPPA_R_ADDEND(r, c) \ + (((r) << 22) + ((c) & 0x3fffff)) + + +/* Some functions to manipulate PA instructions. */ + +/* Declare the functions with the unused attribute to avoid warnings. */ +static inline int sign_extend (int, int) ATTRIBUTE_UNUSED; +static inline int low_sign_extend (int, int) ATTRIBUTE_UNUSED; +static inline int sign_unext (int, int) ATTRIBUTE_UNUSED; +static inline int low_sign_unext (int, int) ATTRIBUTE_UNUSED; +static inline int re_assemble_3 (int) ATTRIBUTE_UNUSED; +static inline int re_assemble_12 (int) ATTRIBUTE_UNUSED; +static inline int re_assemble_14 (int) ATTRIBUTE_UNUSED; +static inline int re_assemble_16 (int) ATTRIBUTE_UNUSED; +static inline int re_assemble_17 (int) ATTRIBUTE_UNUSED; +static inline int re_assemble_21 (int) ATTRIBUTE_UNUSED; +static inline int re_assemble_22 (int) ATTRIBUTE_UNUSED; +static inline bfd_signed_vma hppa_field_adjust + (bfd_vma, bfd_signed_vma, enum hppa_reloc_field_selector_type_alt) + ATTRIBUTE_UNUSED; +static inline int hppa_rebuild_insn (int, int, int) ATTRIBUTE_UNUSED; + + +/* The *sign_extend functions are used to assemble various bitfields + taken from an instruction and return the resulting immediate + value. */ + +static inline int +sign_extend (int x, int len) +{ + int signbit = (1 << (len - 1)); + int mask = (signbit << 1) - 1; + return ((x & mask) ^ signbit) - signbit; +} + +static inline int +low_sign_extend (int x, int len) +{ + return (x >> 1) - ((x & 1) << (len - 1)); +} + + +/* The re_assemble_* functions prepare an immediate value for + insertion into an opcode. pa-risc uses all sorts of weird bitfields + in the instruction to hold the value. */ + +static inline int +sign_unext (int x, int len) +{ + int len_ones; + + len_ones = (1 << len) - 1; + + return x & len_ones; +} + +static inline int +low_sign_unext (int x, int len) +{ + int temp; + int sign; + + sign = (x >> (len-1)) & 1; + + temp = sign_unext (x, len-1); + + return (temp << 1) | sign; +} + +static inline int +re_assemble_3 (int as3) +{ + return (( (as3 & 4) << (13-2)) + | ((as3 & 3) << (13+1))); +} + +static inline int +re_assemble_12 (int as12) +{ + return (( (as12 & 0x800) >> 11) + | ((as12 & 0x400) >> (10 - 2)) + | ((as12 & 0x3ff) << (1 + 2))); +} + +static inline int +re_assemble_14 (int as14) +{ + return (( (as14 & 0x1fff) << 1) + | ((as14 & 0x2000) >> 13)); +} + +static inline int +re_assemble_16 (int as16) +{ + int s, t; + + /* Unusual 16-bit encoding, for wide mode only. */ + t = (as16 << 1) & 0xffff; + s = (as16 & 0x8000); + return (t ^ s ^ (s >> 1)) | (s >> 15); +} + +static inline int +re_assemble_17 (int as17) +{ + return (( (as17 & 0x10000) >> 16) + | ((as17 & 0x0f800) << (16 - 11)) + | ((as17 & 0x00400) >> (10 - 2)) + | ((as17 & 0x003ff) << (1 + 2))); +} + +static inline int +re_assemble_21 (int as21) +{ + return (( (as21 & 0x100000) >> 20) + | ((as21 & 0x0ffe00) >> 8) + | ((as21 & 0x000180) << 7) + | ((as21 & 0x00007c) << 14) + | ((as21 & 0x000003) << 12)); +} + +static inline int +re_assemble_22 (int as22) +{ + return (( (as22 & 0x200000) >> 21) + | ((as22 & 0x1f0000) << (21 - 16)) + | ((as22 & 0x00f800) << (16 - 11)) + | ((as22 & 0x000400) >> (10 - 2)) + | ((as22 & 0x0003ff) << (1 + 2))); +} + + +/* Handle field selectors for PA instructions. + The L and R (and LS, RS etc.) selectors are used in pairs to form a + full 32 bit address. eg. + + LDIL L'start,%r1 ; put left part into r1 + LDW R'start(%r1),%r2 ; add r1 and right part to form address + + This function returns sign extended values in all cases. +*/ + +static inline bfd_signed_vma +hppa_field_adjust (bfd_vma sym_val, + bfd_signed_vma addend, + enum hppa_reloc_field_selector_type_alt r_field) +{ + bfd_signed_vma value; + + value = sym_val + addend; + switch (r_field) + { + case e_fsel: + /* F: No change. */ + break; + + case e_nsel: + /* N: null selector. I don't really understand what this is all + about, but HP's documentation says "this indicates that zero + bits are to be used for the displacement on the instruction. + This fixup is used to identify three-instruction sequences to + access data (for importing shared library data)." */ + value = 0; + break; + + case e_lsel: + case e_nlsel: + /* L: Select top 21 bits. */ + value = value >> 11; + break; + + case e_rsel: + /* R: Select bottom 11 bits. */ + value = value & 0x7ff; + break; + + case e_lssel: + /* LS: Round to nearest multiple of 2048 then select top 21 bits. */ + value = value + 0x400; + value = value >> 11; + break; + + case e_rssel: + /* RS: Select bottom 11 bits for LS. + We need to return a value such that 2048 * LS'x + RS'x == x. + ie. RS'x = x - ((x + 0x400) & -0x800) + this is just a sign extension from bit 21. */ + value = ((value & 0x7ff) ^ 0x400) - 0x400; + break; + + case e_ldsel: + /* LD: Round to next multiple of 2048 then select top 21 bits. + Yes, if we are already on a multiple of 2048, we go up to the + next one. RD in this case will be -2048. */ + value = value + 0x800; + value = value >> 11; + break; + + case e_rdsel: + /* RD: Set bits 0-20 to one. */ + value = value | -0x800; + break; + + case e_lrsel: + case e_nlrsel: + /* LR: L with rounding of the addend to nearest 8k. */ + value = sym_val + ((addend + 0x1000) & -0x2000); + value = value >> 11; + break; + + case e_rrsel: + /* RR: R with rounding of the addend to nearest 8k. + We need to return a value such that 2048 * LR'x + RR'x == x + ie. RR'x = s+a - (s + (((a + 0x1000) & -0x2000) & -0x800)) + . = s+a - ((s & -0x800) + ((a + 0x1000) & -0x2000)) + . = (s & 0x7ff) + a - ((a + 0x1000) & -0x2000) */ + value = (sym_val & 0x7ff) + (((addend & 0x1fff) ^ 0x1000) - 0x1000); + break; + + default: + abort (); + } + return value; +} + +/* PA-RISC OPCODES */ +#define get_opcode(insn) (((insn) >> 26) & 0x3f) + +enum hppa_opcode_type +{ + /* None of the opcodes in the first group generate relocs, so we + aren't too concerned about them. */ + OP_SYSOP = 0x00, + OP_MEMMNG = 0x01, + OP_ALU = 0x02, + OP_NDXMEM = 0x03, + OP_SPOP = 0x04, + OP_DIAG = 0x05, + OP_FMPYADD = 0x06, + OP_UNDEF07 = 0x07, + OP_COPRW = 0x09, + OP_COPRDW = 0x0b, + OP_COPR = 0x0c, + OP_FLOAT = 0x0e, + OP_PRDSPEC = 0x0f, + OP_UNDEF15 = 0x15, + OP_UNDEF1d = 0x1d, + OP_FMPYSUB = 0x26, + OP_FPFUSED = 0x2e, + OP_SHEXDP0 = 0x34, + OP_SHEXDP1 = 0x35, + OP_SHEXDP2 = 0x36, + OP_UNDEF37 = 0x37, + OP_SHEXDP3 = 0x3c, + OP_SHEXDP4 = 0x3d, + OP_MULTMED = 0x3e, + OP_UNDEF3f = 0x3f, + + OP_LDIL = 0x08, + OP_ADDIL = 0x0a, + + OP_LDO = 0x0d, + OP_LDB = 0x10, + OP_LDH = 0x11, + OP_LDW = 0x12, + OP_LDWM = 0x13, + OP_STB = 0x18, + OP_STH = 0x19, + OP_STW = 0x1a, + OP_STWM = 0x1b, + + OP_LDD = 0x14, + OP_STD = 0x1c, + + OP_FLDW = 0x16, + OP_LDWL = 0x17, + OP_FSTW = 0x1e, + OP_STWL = 0x1f, + + OP_COMBT = 0x20, + OP_COMIBT = 0x21, + OP_COMBF = 0x22, + OP_COMIBF = 0x23, + OP_CMPBDT = 0x27, + OP_ADDBT = 0x28, + OP_ADDIBT = 0x29, + OP_ADDBF = 0x2a, + OP_ADDIBF = 0x2b, + OP_CMPBDF = 0x2f, + OP_BVB = 0x30, + OP_BB = 0x31, + OP_MOVB = 0x32, + OP_MOVIB = 0x33, + OP_CMPIBD = 0x3b, + + OP_COMICLR = 0x24, + OP_SUBI = 0x25, + OP_ADDIT = 0x2c, + OP_ADDI = 0x2d, + + OP_BE = 0x38, + OP_BLE = 0x39, + OP_BL = 0x3a +}; + + +/* Insert VALUE into INSN using R_FORMAT to determine exactly what + bits to change. */ + +static inline int +hppa_rebuild_insn (int insn, int value, int r_format) +{ + switch (r_format) + { + case 11: + return (insn & ~ 0x7ff) | low_sign_unext (value, 11); + + case 12: + return (insn & ~ 0x1ffd) | re_assemble_12 (value); + + + case 10: + return (insn & ~ 0x3ff1) | re_assemble_14 (value & -8); + + case -11: + return (insn & ~ 0x3ff9) | re_assemble_14 (value & -4); + + case 14: + return (insn & ~ 0x3fff) | re_assemble_14 (value); + + + case -10: + return (insn & ~ 0xfff1) | re_assemble_16 (value & -8); + + case -16: + return (insn & ~ 0xfff9) | re_assemble_16 (value & -4); + + case 16: + return (insn & ~ 0xffff) | re_assemble_16 (value); + + + case 17: + return (insn & ~ 0x1f1ffd) | re_assemble_17 (value); + + case 21: + return (insn & ~ 0x1fffff) | re_assemble_21 (value); + + case 22: + return (insn & ~ 0x3ff1ffd) | re_assemble_22 (value); + + case 32: + return value; + + default: + abort (); + } + return insn; +} + +#endif /* _LIBHPPA_H */ +/* Table of opcodes for the PA-RISC. + Copyright 1990, 1991, 1992, 1993, 1994, 1995, 1996, 1998, 1999, 2000, + 2001, 2002, 2003, 2004, 2005 + Free Software Foundation, Inc. + + Contributed by the Center for Software Science at the + University of Utah (pa-gdb-bugs@cs.utah.edu). + +This file is part of GAS, the GNU Assembler, and GDB, the GNU disassembler. + +GAS/GDB 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 1, or (at your option) +any later version. + +GAS/GDB 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 GAS or GDB; see the file COPYING. +If not, see . */ + +#if !defined(__STDC__) && !defined(const) +#define const +#endif + +/* + * Structure of an opcode table entry. + */ + +/* There are two kinds of delay slot nullification: normal which is + * controled by the nullification bit, and conditional, which depends + * on the direction of the branch and its success or failure. + * + * NONE is unfortunately #defined in the hiux system include files. + * #undef it away. + */ +#undef NONE +struct pa_opcode +{ + const char *name; + unsigned long int match; /* Bits that must be set... */ + unsigned long int mask; /* ... in these bits. */ + const char *args; + enum pa_arch arch; + char flags; +}; + +/* Enables strict matching. Opcodes with match errors are skipped + when this bit is set. */ +#define FLAG_STRICT 0x1 + +/* + All hppa opcodes are 32 bits. + + The match component is a mask saying which bits must match a + particular opcode in order for an instruction to be an instance + of that opcode. + + The args component is a string containing one character for each operand of + the instruction. Characters used as a prefix allow any second character to + be used without conflicting with the main operand characters. + + Bit positions in this description follow HP usage of lsb = 31, + "at" is lsb of field. + + In the args field, the following characters must match exactly: + + '+,() ' + + In the args field, the following characters are unused: + + ' " - / 34 6789:; ' + '@ C M [\] ' + '` e g } ' + + Here are all the characters: + + ' !"#$%&'()*+-,./0123456789:;<=>?' + '@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_' + '`abcdefghijklmnopqrstuvwxyz{|}~ ' + +Kinds of operands: + x integer register field at 15. + b integer register field at 10. + t integer register field at 31. + a integer register field at 10 and 15 (for PERMH) + 5 5 bit immediate at 15. + s 2 bit space specifier at 17. + S 3 bit space specifier at 18. + V 5 bit immediate value at 31 + i 11 bit immediate value at 31 + j 14 bit immediate value at 31 + k 21 bit immediate value at 31 + l 16 bit immediate value at 31 (wide mode only, unusual encoding). + n nullification for branch instructions + N nullification for spop and copr instructions + w 12 bit branch displacement + W 17 bit branch displacement (PC relative) + X 22 bit branch displacement (PC relative) + z 17 bit branch displacement (just a number, not an address) + +Also these: + + . 2 bit shift amount at 25 + * 4 bit shift amount at 25 + p 5 bit shift count at 26 (to support the SHD instruction) encoded as + 31-p + ~ 6 bit shift count at 20,22:26 encoded as 63-~. + P 5 bit bit position at 26 + q 6 bit bit position at 20,22:26 + T 5 bit field length at 31 (encoded as 32-T) + % 6 bit field length at 23,27:31 (variable extract/deposit) + | 6 bit field length at 19,27:31 (fixed extract/deposit) + A 13 bit immediate at 18 (to support the BREAK instruction) + ^ like b, but describes a control register + ! sar (cr11) register + D 26 bit immediate at 31 (to support the DIAG instruction) + $ 9 bit immediate at 28 (to support POPBTS) + + v 3 bit Special Function Unit identifier at 25 + O 20 bit Special Function Unit operation split between 15 bits at 20 + and 5 bits at 31 + o 15 bit Special Function Unit operation at 20 + 2 22 bit Special Function Unit operation split between 17 bits at 20 + and 5 bits at 31 + 1 15 bit Special Function Unit operation split between 10 bits at 20 + and 5 bits at 31 + 0 10 bit Special Function Unit operation split between 5 bits at 20 + and 5 bits at 31 + u 3 bit coprocessor unit identifier at 25 + F Source Floating Point Operand Format Completer encoded 2 bits at 20 + I Source Floating Point Operand Format Completer encoded 1 bits at 20 + (for 0xe format FP instructions) + G Destination Floating Point Operand Format Completer encoded 2 bits at 18 + H Floating Point Operand Format at 26 for 'fmpyadd' and 'fmpysub' + (very similar to 'F') + + r 5 bit immediate value at 31 (for the break instruction) + (very similar to V above, except the value is unsigned instead of + low_sign_ext) + R 5 bit immediate value at 15 (for the ssm, rsm, probei instructions) + (same as r above, except the value is in a different location) + U 10 bit immediate value at 15 (for SSM, RSM on pa2.0) + Q 5 bit immediate value at 10 (a bit position specified in + the bb instruction. It's the same as r above, except the + value is in a different location) + B 5 bit immediate value at 10 (a bit position specified in + the bb instruction. Similar to Q, but 64 bit handling is + different. + Z %r1 -- implicit target of addil instruction. + L ,%r2 completer for new syntax branch + { Source format completer for fcnv + _ Destination format completer for fcnv + h cbit for fcmp + = gfx tests for ftest + d 14 bit offset for single precision FP long load/store. + # 14 bit offset for double precision FP load long/store. + J Yet another 14 bit offset for load/store with ma,mb completers. + K Yet another 14 bit offset for load/store with ma,mb completers. + y 16 bit offset for word aligned load/store (PA2.0 wide). + & 16 bit offset for dword aligned load/store (PA2.0 wide). + < 16 bit offset for load/store with ma,mb completers (PA2.0 wide). + > 16 bit offset for load/store with ma,mb completers (PA2.0 wide). + Y %sr0,%r31 -- implicit target of be,l instruction. + @ implicit immediate value of 0 + +Completer operands all have 'c' as the prefix: + + cx indexed load and store completer. + cX indexed load and store completer. Like cx, but emits a space + after in disassembler. + cm short load and store completer. + cM short load and store completer. Like cm, but emits a space + after in disassembler. + cq long load and store completer (like cm, but inserted into a + different location in the target instruction). + cs store bytes short completer. + cA store bytes short completer. Like cs, but emits a space + after in disassembler. + ce long load/store completer for LDW/STW with a different encoding + than the others + cc load cache control hint + cd load and clear cache control hint + cC store cache control hint + co ordered access + + cp branch link and push completer + cP branch pop completer + cl branch link completer + cg branch gate completer + + cw read/write completer for PROBE + cW wide completer for MFCTL + cL local processor completer for cache control + cZ System Control Completer (to support LPA, LHA, etc.) + + ci correction completer for DCOR + ca add completer + cy 32 bit add carry completer + cY 64 bit add carry completer + cv signed overflow trap completer + ct trap on condition completer for ADDI, SUB + cT trap on condition completer for UADDCM + cb 32 bit borrow completer for SUB + cB 64 bit borrow completer for SUB + + ch left/right half completer + cH signed/unsigned saturation completer + cS signed/unsigned completer at 21 + cz zero/sign extension completer. + c* permutation completer + +Condition operands all have '?' as the prefix: + + ?f Floating point compare conditions (encoded as 5 bits at 31) + + ?a add conditions + ?A 64 bit add conditions + ?@ add branch conditions followed by nullify + ?d non-negated add branch conditions + ?D negated add branch conditions + ?w wide mode non-negated add branch conditions + ?W wide mode negated add branch conditions + + ?s compare/subtract conditions + ?S 64 bit compare/subtract conditions + ?t non-negated compare and branch conditions + ?n 32 bit compare and branch conditions followed by nullify + ?N 64 bit compare and branch conditions followed by nullify + ?Q 64 bit compare and branch conditions for CMPIB instruction + + ?l logical conditions + ?L 64 bit logical conditions + + ?b branch on bit conditions + ?B 64 bit branch on bit conditions + + ?x shift/extract/deposit conditions + ?X 64 bit shift/extract/deposit conditions + ?y shift/extract/deposit conditions followed by nullify for conditional + branches + + ?u unit conditions + ?U 64 bit unit conditions + +Floating point registers all have 'f' as a prefix: + + ft target register at 31 + fT target register with L/R halves at 31 + fa operand 1 register at 10 + fA operand 1 register with L/R halves at 10 + fX Same as fA, except prints a space before register during disasm + fb operand 2 register at 15 + fB operand 2 register with L/R halves at 15 + fC operand 3 register with L/R halves at 16:18,21:23 + fe Like fT, but encoding is different. + fE Same as fe, except prints a space before register during disasm. + fx target register at 15 (only for PA 2.0 long format FLDD/FSTD). + +Float registers for fmpyadd and fmpysub: + + fi mult operand 1 register at 10 + fj mult operand 2 register at 15 + fk mult target register at 20 + fl add/sub operand register at 25 + fm add/sub target register at 31 + +*/ + + +#if 0 +/* List of characters not to put a space after. Note that + "," is included, as the "spopN" operations use literal + commas in their completer sections. */ +static const char *const completer_chars = ",CcY<>?!@+&U~FfGHINnOoZMadu|/=0123%e$m}"; +#endif + +/* The order of the opcodes in this table is significant: + + * The assembler requires that all instances of the same mnemonic be + consecutive. If they aren't, the assembler will bomb at runtime. + + * Immediate fields use pa_get_absolute_expression to parse the + string. It will generate a "bad expression" error if passed + a register name. Thus, register index variants of an opcode + need to precede immediate variants. + + * The disassembler does not care about the order of the opcodes + except in cases where implicit addressing is used. + + Here are the rules for ordering the opcodes of a mnemonic: + + 1) Opcodes with FLAG_STRICT should precede opcodes without + FLAG_STRICT. + + 2) Opcodes with FLAG_STRICT should be ordered as follows: + register index opcodes, short immediate opcodes, and finally + long immediate opcodes. When both pa10 and pa11 variants + of the same opcode are available, the pa10 opcode should + come first for correct architectural promotion. + + 3) When implicit addressing is available for an opcode, the + implicit opcode should precede the explicit opcode. + + 4) Opcodes without FLAG_STRICT should be ordered as follows: + register index opcodes, long immediate opcodes, and finally + short immediate opcodes. */ + +static const struct pa_opcode pa_opcodes[] = +{ + +/* Pseudo-instructions. */ + +{ "ldi", 0x34000000, 0xffe00000, "l,x", pa20w, 0},/* ldo val(r0),r */ +{ "ldi", 0x34000000, 0xffe0c000, "j,x", pa10, 0},/* ldo val(r0),r */ + +{ "cmpib", 0xec000000, 0xfc000000, "?Qn5,b,w", pa20, FLAG_STRICT}, +{ "cmpib", 0x84000000, 0xf4000000, "?nn5,b,w", pa10, FLAG_STRICT}, +{ "comib", 0x84000000, 0xfc000000, "?nn5,b,w", pa10, 0}, /* comib{tf}*/ +/* This entry is for the disassembler only. It will never be used by + assembler. */ +{ "comib", 0x8c000000, 0xfc000000, "?nn5,b,w", pa10, 0}, /* comib{tf}*/ +{ "cmpb", 0x9c000000, 0xdc000000, "?Nnx,b,w", pa20, FLAG_STRICT}, +{ "cmpb", 0x80000000, 0xf4000000, "?nnx,b,w", pa10, FLAG_STRICT}, +{ "comb", 0x80000000, 0xfc000000, "?nnx,b,w", pa10, 0}, /* comb{tf} */ +/* This entry is for the disassembler only. It will never be used by + assembler. */ +{ "comb", 0x88000000, 0xfc000000, "?nnx,b,w", pa10, 0}, /* comb{tf} */ +{ "addb", 0xa0000000, 0xf4000000, "?Wnx,b,w", pa20w, FLAG_STRICT}, +{ "addb", 0xa0000000, 0xfc000000, "?@nx,b,w", pa10, 0}, /* addb{tf} */ +/* This entry is for the disassembler only. It will never be used by + assembler. */ +{ "addb", 0xa8000000, 0xfc000000, "?@nx,b,w", pa10, 0}, +{ "addib", 0xa4000000, 0xf4000000, "?Wn5,b,w", pa20w, FLAG_STRICT}, +{ "addib", 0xa4000000, 0xfc000000, "?@n5,b,w", pa10, 0}, /* addib{tf}*/ +/* This entry is for the disassembler only. It will never be used by + assembler. */ +{ "addib", 0xac000000, 0xfc000000, "?@n5,b,w", pa10, 0}, /* addib{tf}*/ +{ "nop", 0x08000240, 0xffffffff, "", pa10, 0}, /* or 0,0,0 */ +{ "copy", 0x08000240, 0xffe0ffe0, "x,t", pa10, 0}, /* or r,0,t */ +{ "mtsar", 0x01601840, 0xffe0ffff, "x", pa10, 0}, /* mtctl r,cr11 */ + +/* Loads and Stores for integer registers. */ + +{ "ldd", 0x0c0000c0, 0xfc00d3c0, "cxccx(b),t", pa20, FLAG_STRICT}, +{ "ldd", 0x0c0000c0, 0xfc0013c0, "cxccx(s,b),t", pa20, FLAG_STRICT}, +{ "ldd", 0x0c0010e0, 0xfc1ff3e0, "cocc@(b),t", pa20, FLAG_STRICT}, +{ "ldd", 0x0c0010e0, 0xfc1f33e0, "cocc@(s,b),t", pa20, FLAG_STRICT}, +{ "ldd", 0x0c0010c0, 0xfc00d3c0, "cmcc5(b),t", pa20, FLAG_STRICT}, +{ "ldd", 0x0c0010c0, 0xfc0013c0, "cmcc5(s,b),t", pa20, FLAG_STRICT}, +{ "ldd", 0x50000000, 0xfc000002, "cq&(b),x", pa20w, FLAG_STRICT}, +{ "ldd", 0x50000000, 0xfc00c002, "cq#(b),x", pa20, FLAG_STRICT}, +{ "ldd", 0x50000000, 0xfc000002, "cq#(s,b),x", pa20, FLAG_STRICT}, +{ "ldw", 0x0c000080, 0xfc00dfc0, "cXx(b),t", pa10, FLAG_STRICT}, +{ "ldw", 0x0c000080, 0xfc001fc0, "cXx(s,b),t", pa10, FLAG_STRICT}, +{ "ldw", 0x0c000080, 0xfc00d3c0, "cxccx(b),t", pa11, FLAG_STRICT}, +{ "ldw", 0x0c000080, 0xfc0013c0, "cxccx(s,b),t", pa11, FLAG_STRICT}, +{ "ldw", 0x0c0010a0, 0xfc1ff3e0, "cocc@(b),t", pa20, FLAG_STRICT}, +{ "ldw", 0x0c0010a0, 0xfc1f33e0, "cocc@(s,b),t", pa20, FLAG_STRICT}, +{ "ldw", 0x0c001080, 0xfc00dfc0, "cM5(b),t", pa10, FLAG_STRICT}, +{ "ldw", 0x0c001080, 0xfc001fc0, "cM5(s,b),t", pa10, FLAG_STRICT}, +{ "ldw", 0x0c001080, 0xfc00d3c0, "cmcc5(b),t", pa11, FLAG_STRICT}, +{ "ldw", 0x0c001080, 0xfc0013c0, "cmcc5(s,b),t", pa11, FLAG_STRICT}, +{ "ldw", 0x4c000000, 0xfc000000, "ce<(b),x", pa20w, FLAG_STRICT}, +{ "ldw", 0x5c000004, 0xfc000006, "ce>(b),x", pa20w, FLAG_STRICT}, +{ "ldw", 0x48000000, 0xfc000000, "l(b),x", pa20w, FLAG_STRICT}, +{ "ldw", 0x5c000004, 0xfc00c006, "ceK(b),x", pa20, FLAG_STRICT}, +{ "ldw", 0x5c000004, 0xfc000006, "ceK(s,b),x", pa20, FLAG_STRICT}, +{ "ldw", 0x4c000000, 0xfc00c000, "ceJ(b),x", pa10, FLAG_STRICT}, +{ "ldw", 0x4c000000, 0xfc000000, "ceJ(s,b),x", pa10, FLAG_STRICT}, +{ "ldw", 0x48000000, 0xfc00c000, "j(b),x", pa10, 0}, +{ "ldw", 0x48000000, 0xfc000000, "j(s,b),x", pa10, 0}, +{ "ldh", 0x0c000040, 0xfc00dfc0, "cXx(b),t", pa10, FLAG_STRICT}, +{ "ldh", 0x0c000040, 0xfc001fc0, "cXx(s,b),t", pa10, FLAG_STRICT}, +{ "ldh", 0x0c000040, 0xfc00d3c0, "cxccx(b),t", pa11, FLAG_STRICT}, +{ "ldh", 0x0c000040, 0xfc0013c0, "cxccx(s,b),t", pa11, FLAG_STRICT}, +{ "ldh", 0x0c001060, 0xfc1ff3e0, "cocc@(b),t", pa20, FLAG_STRICT}, +{ "ldh", 0x0c001060, 0xfc1f33e0, "cocc@(s,b),t", pa20, FLAG_STRICT}, +{ "ldh", 0x0c001040, 0xfc00dfc0, "cM5(b),t", pa10, FLAG_STRICT}, +{ "ldh", 0x0c001040, 0xfc001fc0, "cM5(s,b),t", pa10, FLAG_STRICT}, +{ "ldh", 0x0c001040, 0xfc00d3c0, "cmcc5(b),t", pa11, FLAG_STRICT}, +{ "ldh", 0x0c001040, 0xfc0013c0, "cmcc5(s,b),t", pa11, FLAG_STRICT}, +{ "ldh", 0x44000000, 0xfc000000, "l(b),x", pa20w, FLAG_STRICT}, +{ "ldh", 0x44000000, 0xfc00c000, "j(b),x", pa10, 0}, +{ "ldh", 0x44000000, 0xfc000000, "j(s,b),x", pa10, 0}, +{ "ldb", 0x0c000000, 0xfc00dfc0, "cXx(b),t", pa10, FLAG_STRICT}, +{ "ldb", 0x0c000000, 0xfc001fc0, "cXx(s,b),t", pa10, FLAG_STRICT}, +{ "ldb", 0x0c000000, 0xfc00d3c0, "cxccx(b),t", pa11, FLAG_STRICT}, +{ "ldb", 0x0c000000, 0xfc0013c0, "cxccx(s,b),t", pa11, FLAG_STRICT}, +{ "ldb", 0x0c001020, 0xfc1ff3e0, "cocc@(b),t", pa20, FLAG_STRICT}, +{ "ldb", 0x0c001020, 0xfc1f33e0, "cocc@(s,b),t", pa20, FLAG_STRICT}, +{ "ldb", 0x0c001000, 0xfc00dfc0, "cM5(b),t", pa10, FLAG_STRICT}, +{ "ldb", 0x0c001000, 0xfc001fc0, "cM5(s,b),t", pa10, FLAG_STRICT}, +{ "ldb", 0x0c001000, 0xfc00d3c0, "cmcc5(b),t", pa11, FLAG_STRICT}, +{ "ldb", 0x0c001000, 0xfc0013c0, "cmcc5(s,b),t", pa11, FLAG_STRICT}, +{ "ldb", 0x40000000, 0xfc000000, "l(b),x", pa20w, FLAG_STRICT}, +{ "ldb", 0x40000000, 0xfc00c000, "j(b),x", pa10, 0}, +{ "ldb", 0x40000000, 0xfc000000, "j(s,b),x", pa10, 0}, +{ "std", 0x0c0012e0, 0xfc00f3ff, "cocCx,@(b)", pa20, FLAG_STRICT}, +{ "std", 0x0c0012e0, 0xfc0033ff, "cocCx,@(s,b)", pa20, FLAG_STRICT}, +{ "std", 0x0c0012c0, 0xfc00d3c0, "cmcCx,V(b)", pa20, FLAG_STRICT}, +{ "std", 0x0c0012c0, 0xfc0013c0, "cmcCx,V(s,b)", pa20, FLAG_STRICT}, +{ "std", 0x70000000, 0xfc000002, "cqx,&(b)", pa20w, FLAG_STRICT}, +{ "std", 0x70000000, 0xfc00c002, "cqx,#(b)", pa20, FLAG_STRICT}, +{ "std", 0x70000000, 0xfc000002, "cqx,#(s,b)", pa20, FLAG_STRICT}, +{ "stw", 0x0c0012a0, 0xfc00f3ff, "cocCx,@(b)", pa20, FLAG_STRICT}, +{ "stw", 0x0c0012a0, 0xfc0033ff, "cocCx,@(s,b)", pa20, FLAG_STRICT}, +{ "stw", 0x0c001280, 0xfc00dfc0, "cMx,V(b)", pa10, FLAG_STRICT}, +{ "stw", 0x0c001280, 0xfc001fc0, "cMx,V(s,b)", pa10, FLAG_STRICT}, +{ "stw", 0x0c001280, 0xfc00d3c0, "cmcCx,V(b)", pa11, FLAG_STRICT}, +{ "stw", 0x0c001280, 0xfc0013c0, "cmcCx,V(s,b)", pa11, FLAG_STRICT}, +{ "stw", 0x6c000000, 0xfc000000, "cex,<(b)", pa20w, FLAG_STRICT}, +{ "stw", 0x7c000004, 0xfc000006, "cex,>(b)", pa20w, FLAG_STRICT}, +{ "stw", 0x68000000, 0xfc000000, "x,l(b)", pa20w, FLAG_STRICT}, +{ "stw", 0x7c000004, 0xfc00c006, "cex,K(b)", pa20, FLAG_STRICT}, +{ "stw", 0x7c000004, 0xfc000006, "cex,K(s,b)", pa20, FLAG_STRICT}, +{ "stw", 0x6c000000, 0xfc00c000, "cex,J(b)", pa10, FLAG_STRICT}, +{ "stw", 0x6c000000, 0xfc000000, "cex,J(s,b)", pa10, FLAG_STRICT}, +{ "stw", 0x68000000, 0xfc00c000, "x,j(b)", pa10, 0}, +{ "stw", 0x68000000, 0xfc000000, "x,j(s,b)", pa10, 0}, +{ "sth", 0x0c001260, 0xfc00f3ff, "cocCx,@(b)", pa20, FLAG_STRICT}, +{ "sth", 0x0c001260, 0xfc0033ff, "cocCx,@(s,b)", pa20, FLAG_STRICT}, +{ "sth", 0x0c001240, 0xfc00dfc0, "cMx,V(b)", pa10, FLAG_STRICT}, +{ "sth", 0x0c001240, 0xfc001fc0, "cMx,V(s,b)", pa10, FLAG_STRICT}, +{ "sth", 0x0c001240, 0xfc00d3c0, "cmcCx,V(b)", pa11, FLAG_STRICT}, +{ "sth", 0x0c001240, 0xfc0013c0, "cmcCx,V(s,b)", pa11, FLAG_STRICT}, +{ "sth", 0x64000000, 0xfc000000, "x,l(b)", pa20w, FLAG_STRICT}, +{ "sth", 0x64000000, 0xfc00c000, "x,j(b)", pa10, 0}, +{ "sth", 0x64000000, 0xfc000000, "x,j(s,b)", pa10, 0}, +{ "stb", 0x0c001220, 0xfc00f3ff, "cocCx,@(b)", pa20, FLAG_STRICT}, +{ "stb", 0x0c001220, 0xfc0033ff, "cocCx,@(s,b)", pa20, FLAG_STRICT}, +{ "stb", 0x0c001200, 0xfc00dfc0, "cMx,V(b)", pa10, FLAG_STRICT}, +{ "stb", 0x0c001200, 0xfc001fc0, "cMx,V(s,b)", pa10, FLAG_STRICT}, +{ "stb", 0x0c001200, 0xfc00d3c0, "cmcCx,V(b)", pa11, FLAG_STRICT}, +{ "stb", 0x0c001200, 0xfc0013c0, "cmcCx,V(s,b)", pa11, FLAG_STRICT}, +{ "stb", 0x60000000, 0xfc000000, "x,l(b)", pa20w, FLAG_STRICT}, +{ "stb", 0x60000000, 0xfc00c000, "x,j(b)", pa10, 0}, +{ "stb", 0x60000000, 0xfc000000, "x,j(s,b)", pa10, 0}, +{ "ldwm", 0x4c000000, 0xfc00c000, "j(b),x", pa10, 0}, +{ "ldwm", 0x4c000000, 0xfc000000, "j(s,b),x", pa10, 0}, +{ "stwm", 0x6c000000, 0xfc00c000, "x,j(b)", pa10, 0}, +{ "stwm", 0x6c000000, 0xfc000000, "x,j(s,b)", pa10, 0}, +{ "ldwx", 0x0c000080, 0xfc00dfc0, "cXx(b),t", pa10, FLAG_STRICT}, +{ "ldwx", 0x0c000080, 0xfc001fc0, "cXx(s,b),t", pa10, FLAG_STRICT}, +{ "ldwx", 0x0c000080, 0xfc00d3c0, "cxccx(b),t", pa11, FLAG_STRICT}, +{ "ldwx", 0x0c000080, 0xfc0013c0, "cxccx(s,b),t", pa11, FLAG_STRICT}, +{ "ldwx", 0x0c000080, 0xfc00dfc0, "cXx(b),t", pa10, 0}, +{ "ldwx", 0x0c000080, 0xfc001fc0, "cXx(s,b),t", pa10, 0}, +{ "ldhx", 0x0c000040, 0xfc00dfc0, "cXx(b),t", pa10, FLAG_STRICT}, +{ "ldhx", 0x0c000040, 0xfc001fc0, "cXx(s,b),t", pa10, FLAG_STRICT}, +{ "ldhx", 0x0c000040, 0xfc00d3c0, "cxccx(b),t", pa11, FLAG_STRICT}, +{ "ldhx", 0x0c000040, 0xfc0013c0, "cxccx(s,b),t", pa11, FLAG_STRICT}, +{ "ldhx", 0x0c000040, 0xfc00dfc0, "cXx(b),t", pa10, 0}, +{ "ldhx", 0x0c000040, 0xfc001fc0, "cXx(s,b),t", pa10, 0}, +{ "ldbx", 0x0c000000, 0xfc00dfc0, "cXx(b),t", pa10, FLAG_STRICT}, +{ "ldbx", 0x0c000000, 0xfc001fc0, "cXx(s,b),t", pa10, FLAG_STRICT}, +{ "ldbx", 0x0c000000, 0xfc00d3c0, "cxccx(b),t", pa11, FLAG_STRICT}, +{ "ldbx", 0x0c000000, 0xfc0013c0, "cxccx(s,b),t", pa11, FLAG_STRICT}, +{ "ldbx", 0x0c000000, 0xfc00dfc0, "cXx(b),t", pa10, 0}, +{ "ldbx", 0x0c000000, 0xfc001fc0, "cXx(s,b),t", pa10, 0}, +{ "ldwa", 0x0c000180, 0xfc00dfc0, "cXx(b),t", pa10, FLAG_STRICT}, +{ "ldwa", 0x0c000180, 0xfc00d3c0, "cxccx(b),t", pa11, FLAG_STRICT}, +{ "ldwa", 0x0c0011a0, 0xfc1ff3e0, "cocc@(b),t", pa20, FLAG_STRICT}, +{ "ldwa", 0x0c001180, 0xfc00dfc0, "cM5(b),t", pa10, FLAG_STRICT}, +{ "ldwa", 0x0c001180, 0xfc00d3c0, "cmcc5(b),t", pa11, FLAG_STRICT}, +{ "ldcw", 0x0c0001c0, 0xfc00dfc0, "cXx(b),t", pa10, FLAG_STRICT}, +{ "ldcw", 0x0c0001c0, 0xfc001fc0, "cXx(s,b),t", pa10, FLAG_STRICT}, +{ "ldcw", 0x0c0001c0, 0xfc00d3c0, "cxcdx(b),t", pa11, FLAG_STRICT}, +{ "ldcw", 0x0c0001c0, 0xfc0013c0, "cxcdx(s,b),t", pa11, FLAG_STRICT}, +{ "ldcw", 0x0c0011c0, 0xfc00dfc0, "cM5(b),t", pa10, FLAG_STRICT}, +{ "ldcw", 0x0c0011c0, 0xfc001fc0, "cM5(s,b),t", pa10, FLAG_STRICT}, +{ "ldcw", 0x0c0011c0, 0xfc00d3c0, "cmcd5(b),t", pa11, FLAG_STRICT}, +{ "ldcw", 0x0c0011c0, 0xfc0013c0, "cmcd5(s,b),t", pa11, FLAG_STRICT}, +{ "stwa", 0x0c0013a0, 0xfc00d3ff, "cocCx,@(b)", pa20, FLAG_STRICT}, +{ "stwa", 0x0c001380, 0xfc00dfc0, "cMx,V(b)", pa10, FLAG_STRICT}, +{ "stwa", 0x0c001380, 0xfc00d3c0, "cmcCx,V(b)", pa11, FLAG_STRICT}, +{ "stby", 0x0c001300, 0xfc00dfc0, "cAx,V(b)", pa10, FLAG_STRICT}, +{ "stby", 0x0c001300, 0xfc001fc0, "cAx,V(s,b)", pa10, FLAG_STRICT}, +{ "stby", 0x0c001300, 0xfc00d3c0, "cscCx,V(b)", pa11, FLAG_STRICT}, +{ "stby", 0x0c001300, 0xfc0013c0, "cscCx,V(s,b)", pa11, FLAG_STRICT}, +{ "ldda", 0x0c000100, 0xfc00d3c0, "cxccx(b),t", pa20, FLAG_STRICT}, +{ "ldda", 0x0c001120, 0xfc1ff3e0, "cocc@(b),t", pa20, FLAG_STRICT}, +{ "ldda", 0x0c001100, 0xfc00d3c0, "cmcc5(b),t", pa20, FLAG_STRICT}, +{ "ldcd", 0x0c000140, 0xfc00d3c0, "cxcdx(b),t", pa20, FLAG_STRICT}, +{ "ldcd", 0x0c000140, 0xfc0013c0, "cxcdx(s,b),t", pa20, FLAG_STRICT}, +{ "ldcd", 0x0c001140, 0xfc00d3c0, "cmcd5(b),t", pa20, FLAG_STRICT}, +{ "ldcd", 0x0c001140, 0xfc0013c0, "cmcd5(s,b),t", pa20, FLAG_STRICT}, +{ "stda", 0x0c0013e0, 0xfc00f3ff, "cocCx,@(b)", pa20, FLAG_STRICT}, +{ "stda", 0x0c0013c0, 0xfc00d3c0, "cmcCx,V(b)", pa20, FLAG_STRICT}, +{ "ldwax", 0x0c000180, 0xfc00dfc0, "cXx(b),t", pa10, FLAG_STRICT}, +{ "ldwax", 0x0c000180, 0xfc00d3c0, "cxccx(b),t", pa11, FLAG_STRICT}, +{ "ldwax", 0x0c000180, 0xfc00dfc0, "cXx(b),t", pa10, 0}, +{ "ldcwx", 0x0c0001c0, 0xfc00dfc0, "cXx(b),t", pa10, FLAG_STRICT}, +{ "ldcwx", 0x0c0001c0, 0xfc001fc0, "cXx(s,b),t", pa10, FLAG_STRICT}, +{ "ldcwx", 0x0c0001c0, 0xfc00d3c0, "cxcdx(b),t", pa11, FLAG_STRICT}, +{ "ldcwx", 0x0c0001c0, 0xfc0013c0, "cxcdx(s,b),t", pa11, FLAG_STRICT}, +{ "ldcwx", 0x0c0001c0, 0xfc00dfc0, "cXx(b),t", pa10, 0}, +{ "ldcwx", 0x0c0001c0, 0xfc001fc0, "cXx(s,b),t", pa10, 0}, +{ "ldws", 0x0c001080, 0xfc00dfc0, "cM5(b),t", pa10, FLAG_STRICT}, +{ "ldws", 0x0c001080, 0xfc001fc0, "cM5(s,b),t", pa10, FLAG_STRICT}, +{ "ldws", 0x0c001080, 0xfc00d3c0, "cmcc5(b),t", pa11, FLAG_STRICT}, +{ "ldws", 0x0c001080, 0xfc0013c0, "cmcc5(s,b),t", pa11, FLAG_STRICT}, +{ "ldws", 0x0c001080, 0xfc00dfc0, "cM5(b),t", pa10, 0}, +{ "ldws", 0x0c001080, 0xfc001fc0, "cM5(s,b),t", pa10, 0}, +{ "ldhs", 0x0c001040, 0xfc00dfc0, "cM5(b),t", pa10, FLAG_STRICT}, +{ "ldhs", 0x0c001040, 0xfc001fc0, "cM5(s,b),t", pa10, FLAG_STRICT}, +{ "ldhs", 0x0c001040, 0xfc00d3c0, "cmcc5(b),t", pa11, FLAG_STRICT}, +{ "ldhs", 0x0c001040, 0xfc0013c0, "cmcc5(s,b),t", pa11, FLAG_STRICT}, +{ "ldhs", 0x0c001040, 0xfc00dfc0, "cM5(b),t", pa10, 0}, +{ "ldhs", 0x0c001040, 0xfc001fc0, "cM5(s,b),t", pa10, 0}, +{ "ldbs", 0x0c001000, 0xfc00dfc0, "cM5(b),t", pa10, FLAG_STRICT}, +{ "ldbs", 0x0c001000, 0xfc001fc0, "cM5(s,b),t", pa10, FLAG_STRICT}, +{ "ldbs", 0x0c001000, 0xfc00d3c0, "cmcc5(b),t", pa11, FLAG_STRICT}, +{ "ldbs", 0x0c001000, 0xfc0013c0, "cmcc5(s,b),t", pa11, FLAG_STRICT}, +{ "ldbs", 0x0c001000, 0xfc00dfc0, "cM5(b),t", pa10, 0}, +{ "ldbs", 0x0c001000, 0xfc001fc0, "cM5(s,b),t", pa10, 0}, +{ "ldwas", 0x0c001180, 0xfc00dfc0, "cM5(b),t", pa10, FLAG_STRICT}, +{ "ldwas", 0x0c001180, 0xfc00d3c0, "cmcc5(b),t", pa11, FLAG_STRICT}, +{ "ldwas", 0x0c001180, 0xfc00dfc0, "cM5(b),t", pa10, 0}, +{ "ldcws", 0x0c0011c0, 0xfc00dfc0, "cM5(b),t", pa10, FLAG_STRICT}, +{ "ldcws", 0x0c0011c0, 0xfc001fc0, "cM5(s,b),t", pa10, FLAG_STRICT}, +{ "ldcws", 0x0c0011c0, 0xfc00d3c0, "cmcd5(b),t", pa11, FLAG_STRICT}, +{ "ldcws", 0x0c0011c0, 0xfc0013c0, "cmcd5(s,b),t", pa11, FLAG_STRICT}, +{ "ldcws", 0x0c0011c0, 0xfc00dfc0, "cM5(b),t", pa10, 0}, +{ "ldcws", 0x0c0011c0, 0xfc001fc0, "cM5(s,b),t", pa10, 0}, +{ "stws", 0x0c001280, 0xfc00dfc0, "cMx,V(b)", pa10, FLAG_STRICT}, +{ "stws", 0x0c001280, 0xfc001fc0, "cMx,V(s,b)", pa10, FLAG_STRICT}, +{ "stws", 0x0c001280, 0xfc00d3c0, "cmcCx,V(b)", pa11, FLAG_STRICT}, +{ "stws", 0x0c001280, 0xfc0013c0, "cmcCx,V(s,b)", pa11, FLAG_STRICT}, +{ "stws", 0x0c001280, 0xfc00dfc0, "cMx,V(b)", pa10, 0}, +{ "stws", 0x0c001280, 0xfc001fc0, "cMx,V(s,b)", pa10, 0}, +{ "sths", 0x0c001240, 0xfc00dfc0, "cMx,V(b)", pa10, FLAG_STRICT}, +{ "sths", 0x0c001240, 0xfc001fc0, "cMx,V(s,b)", pa10, FLAG_STRICT}, +{ "sths", 0x0c001240, 0xfc00d3c0, "cmcCx,V(b)", pa11, FLAG_STRICT}, +{ "sths", 0x0c001240, 0xfc0013c0, "cmcCx,V(s,b)", pa11, FLAG_STRICT}, +{ "sths", 0x0c001240, 0xfc00dfc0, "cMx,V(b)", pa10, 0}, +{ "sths", 0x0c001240, 0xfc001fc0, "cMx,V(s,b)", pa10, 0}, +{ "stbs", 0x0c001200, 0xfc00dfc0, "cMx,V(b)", pa10, FLAG_STRICT}, +{ "stbs", 0x0c001200, 0xfc001fc0, "cMx,V(s,b)", pa10, FLAG_STRICT}, +{ "stbs", 0x0c001200, 0xfc00d3c0, "cmcCx,V(b)", pa11, FLAG_STRICT}, +{ "stbs", 0x0c001200, 0xfc0013c0, "cmcCx,V(s,b)", pa11, FLAG_STRICT}, +{ "stbs", 0x0c001200, 0xfc00dfc0, "cMx,V(b)", pa10, 0}, +{ "stbs", 0x0c001200, 0xfc001fc0, "cMx,V(s,b)", pa10, 0}, +{ "stwas", 0x0c001380, 0xfc00dfc0, "cMx,V(b)", pa10, FLAG_STRICT}, +{ "stwas", 0x0c001380, 0xfc00d3c0, "cmcCx,V(b)", pa11, FLAG_STRICT}, +{ "stwas", 0x0c001380, 0xfc00dfc0, "cMx,V(b)", pa10, 0}, +{ "stdby", 0x0c001340, 0xfc00d3c0, "cscCx,V(b)", pa20, FLAG_STRICT}, +{ "stdby", 0x0c001340, 0xfc0013c0, "cscCx,V(s,b)", pa20, FLAG_STRICT}, +{ "stbys", 0x0c001300, 0xfc00dfc0, "cAx,V(b)", pa10, FLAG_STRICT}, +{ "stbys", 0x0c001300, 0xfc001fc0, "cAx,V(s,b)", pa10, FLAG_STRICT}, +{ "stbys", 0x0c001300, 0xfc00d3c0, "cscCx,V(b)", pa11, FLAG_STRICT}, +{ "stbys", 0x0c001300, 0xfc0013c0, "cscCx,V(s,b)", pa11, FLAG_STRICT}, +{ "stbys", 0x0c001300, 0xfc00dfc0, "cAx,V(b)", pa10, 0}, +{ "stbys", 0x0c001300, 0xfc001fc0, "cAx,V(s,b)", pa10, 0}, + +/* Immediate instructions. */ +{ "ldo", 0x34000000, 0xfc000000, "l(b),x", pa20w, 0}, +{ "ldo", 0x34000000, 0xfc00c000, "j(b),x", pa10, 0}, +{ "ldil", 0x20000000, 0xfc000000, "k,b", pa10, 0}, +{ "addil", 0x28000000, 0xfc000000, "k,b,Z", pa10, 0}, +{ "addil", 0x28000000, 0xfc000000, "k,b", pa10, 0}, + +/* Branching instructions. */ +{ "b", 0xe8008000, 0xfc00e000, "cpnXL", pa20, FLAG_STRICT}, +{ "b", 0xe800a000, 0xfc00e000, "clnXL", pa20, FLAG_STRICT}, +{ "b", 0xe8000000, 0xfc00e000, "clnW,b", pa10, FLAG_STRICT}, +{ "b", 0xe8002000, 0xfc00e000, "cgnW,b", pa10, FLAG_STRICT}, +{ "b", 0xe8000000, 0xffe0e000, "nW", pa10, 0}, /* b,l foo,r0 */ +{ "bl", 0xe8000000, 0xfc00e000, "nW,b", pa10, 0}, +{ "gate", 0xe8002000, 0xfc00e000, "nW,b", pa10, 0}, +{ "blr", 0xe8004000, 0xfc00e001, "nx,b", pa10, 0}, +{ "bv", 0xe800c000, 0xfc00fffd, "nx(b)", pa10, 0}, +{ "bv", 0xe800c000, 0xfc00fffd, "n(b)", pa10, 0}, +{ "bve", 0xe800f001, 0xfc1ffffd, "cpn(b)L", pa20, FLAG_STRICT}, +{ "bve", 0xe800f000, 0xfc1ffffd, "cln(b)L", pa20, FLAG_STRICT}, +{ "bve", 0xe800d001, 0xfc1ffffd, "cPn(b)", pa20, FLAG_STRICT}, +{ "bve", 0xe800d000, 0xfc1ffffd, "n(b)", pa20, FLAG_STRICT}, +{ "be", 0xe4000000, 0xfc000000, "clnz(S,b),Y", pa10, FLAG_STRICT}, +{ "be", 0xe4000000, 0xfc000000, "clnz(b),Y", pa10, FLAG_STRICT}, +{ "be", 0xe0000000, 0xfc000000, "nz(S,b)", pa10, 0}, +{ "be", 0xe0000000, 0xfc000000, "nz(b)", pa10, 0}, +{ "ble", 0xe4000000, 0xfc000000, "nz(S,b)", pa10, 0}, +{ "movb", 0xc8000000, 0xfc000000, "?ynx,b,w", pa10, 0}, +{ "movib", 0xcc000000, 0xfc000000, "?yn5,b,w", pa10, 0}, +{ "combt", 0x80000000, 0xfc000000, "?tnx,b,w", pa10, 0}, +{ "combf", 0x88000000, 0xfc000000, "?tnx,b,w", pa10, 0}, +{ "comibt", 0x84000000, 0xfc000000, "?tn5,b,w", pa10, 0}, +{ "comibf", 0x8c000000, 0xfc000000, "?tn5,b,w", pa10, 0}, +{ "addbt", 0xa0000000, 0xfc000000, "?dnx,b,w", pa10, 0}, +{ "addbf", 0xa8000000, 0xfc000000, "?dnx,b,w", pa10, 0}, +{ "addibt", 0xa4000000, 0xfc000000, "?dn5,b,w", pa10, 0}, +{ "addibf", 0xac000000, 0xfc000000, "?dn5,b,w", pa10, 0}, +{ "bb", 0xc0004000, 0xffe06000, "?bnx,!,w", pa10, FLAG_STRICT}, +{ "bb", 0xc0006000, 0xffe06000, "?Bnx,!,w", pa20, FLAG_STRICT}, +{ "bb", 0xc4004000, 0xfc006000, "?bnx,Q,w", pa10, FLAG_STRICT}, +{ "bb", 0xc4004000, 0xfc004000, "?Bnx,B,w", pa20, FLAG_STRICT}, +{ "bvb", 0xc0004000, 0xffe04000, "?bnx,w", pa10, 0}, +{ "clrbts", 0xe8004005, 0xffffffff, "", pa20, FLAG_STRICT}, +{ "popbts", 0xe8004005, 0xfffff007, "$", pa20, FLAG_STRICT}, +{ "pushnom", 0xe8004001, 0xffffffff, "", pa20, FLAG_STRICT}, +{ "pushbts", 0xe8004001, 0xffe0ffff, "x", pa20, FLAG_STRICT}, + +/* Computation Instructions. */ + +{ "cmpclr", 0x080008a0, 0xfc000fe0, "?Sx,b,t", pa20, FLAG_STRICT}, +{ "cmpclr", 0x08000880, 0xfc000fe0, "?sx,b,t", pa10, FLAG_STRICT}, +{ "comclr", 0x08000880, 0xfc000fe0, "?sx,b,t", pa10, 0}, +{ "or", 0x08000260, 0xfc000fe0, "?Lx,b,t", pa20, FLAG_STRICT}, +{ "or", 0x08000240, 0xfc000fe0, "?lx,b,t", pa10, 0}, +{ "xor", 0x080002a0, 0xfc000fe0, "?Lx,b,t", pa20, FLAG_STRICT}, +{ "xor", 0x08000280, 0xfc000fe0, "?lx,b,t", pa10, 0}, +{ "and", 0x08000220, 0xfc000fe0, "?Lx,b,t", pa20, FLAG_STRICT}, +{ "and", 0x08000200, 0xfc000fe0, "?lx,b,t", pa10, 0}, +{ "andcm", 0x08000020, 0xfc000fe0, "?Lx,b,t", pa20, FLAG_STRICT}, +{ "andcm", 0x08000000, 0xfc000fe0, "?lx,b,t", pa10, 0}, +{ "uxor", 0x080003a0, 0xfc000fe0, "?Ux,b,t", pa20, FLAG_STRICT}, +{ "uxor", 0x08000380, 0xfc000fe0, "?ux,b,t", pa10, 0}, +{ "uaddcm", 0x080009a0, 0xfc000fa0, "cT?Ux,b,t", pa20, FLAG_STRICT}, +{ "uaddcm", 0x08000980, 0xfc000fa0, "cT?ux,b,t", pa10, FLAG_STRICT}, +{ "uaddcm", 0x08000980, 0xfc000fe0, "?ux,b,t", pa10, 0}, +{ "uaddcmt", 0x080009c0, 0xfc000fe0, "?ux,b,t", pa10, 0}, +{ "dcor", 0x08000ba0, 0xfc1f0fa0, "ci?Ub,t", pa20, FLAG_STRICT}, +{ "dcor", 0x08000b80, 0xfc1f0fa0, "ci?ub,t", pa10, FLAG_STRICT}, +{ "dcor", 0x08000b80, 0xfc1f0fe0, "?ub,t", pa10, 0}, +{ "idcor", 0x08000bc0, 0xfc1f0fe0, "?ub,t", pa10, 0}, +{ "addi", 0xb0000000, 0xfc000000, "ct?ai,b,x", pa10, FLAG_STRICT}, +{ "addi", 0xb4000000, 0xfc000000, "cv?ai,b,x", pa10, FLAG_STRICT}, +{ "addi", 0xb4000000, 0xfc000800, "?ai,b,x", pa10, 0}, +{ "addio", 0xb4000800, 0xfc000800, "?ai,b,x", pa10, 0}, +{ "addit", 0xb0000000, 0xfc000800, "?ai,b,x", pa10, 0}, +{ "addito", 0xb0000800, 0xfc000800, "?ai,b,x", pa10, 0}, +{ "add", 0x08000720, 0xfc0007e0, "cY?Ax,b,t", pa20, FLAG_STRICT}, +{ "add", 0x08000700, 0xfc0007e0, "cy?ax,b,t", pa10, FLAG_STRICT}, +{ "add", 0x08000220, 0xfc0003e0, "ca?Ax,b,t", pa20, FLAG_STRICT}, +{ "add", 0x08000200, 0xfc0003e0, "ca?ax,b,t", pa10, FLAG_STRICT}, +{ "add", 0x08000600, 0xfc000fe0, "?ax,b,t", pa10, 0}, +{ "addl", 0x08000a00, 0xfc000fe0, "?ax,b,t", pa10, 0}, +{ "addo", 0x08000e00, 0xfc000fe0, "?ax,b,t", pa10, 0}, +{ "addc", 0x08000700, 0xfc000fe0, "?ax,b,t", pa10, 0}, +{ "addco", 0x08000f00, 0xfc000fe0, "?ax,b,t", pa10, 0}, +{ "sub", 0x080004e0, 0xfc0007e0, "ct?Sx,b,t", pa20, FLAG_STRICT}, +{ "sub", 0x080004c0, 0xfc0007e0, "ct?sx,b,t", pa10, FLAG_STRICT}, +{ "sub", 0x08000520, 0xfc0007e0, "cB?Sx,b,t", pa20, FLAG_STRICT}, +{ "sub", 0x08000500, 0xfc0007e0, "cb?sx,b,t", pa10, FLAG_STRICT}, +{ "sub", 0x08000420, 0xfc0007e0, "cv?Sx,b,t", pa20, FLAG_STRICT}, +{ "sub", 0x08000400, 0xfc0007e0, "cv?sx,b,t", pa10, FLAG_STRICT}, +{ "sub", 0x08000400, 0xfc000fe0, "?sx,b,t", pa10, 0}, +{ "subo", 0x08000c00, 0xfc000fe0, "?sx,b,t", pa10, 0}, +{ "subb", 0x08000500, 0xfc000fe0, "?sx,b,t", pa10, 0}, +{ "subbo", 0x08000d00, 0xfc000fe0, "?sx,b,t", pa10, 0}, +{ "subt", 0x080004c0, 0xfc000fe0, "?sx,b,t", pa10, 0}, +{ "subto", 0x08000cc0, 0xfc000fe0, "?sx,b,t", pa10, 0}, +{ "ds", 0x08000440, 0xfc000fe0, "?sx,b,t", pa10, 0}, +{ "subi", 0x94000000, 0xfc000000, "cv?si,b,x", pa10, FLAG_STRICT}, +{ "subi", 0x94000000, 0xfc000800, "?si,b,x", pa10, 0}, +{ "subio", 0x94000800, 0xfc000800, "?si,b,x", pa10, 0}, +{ "cmpiclr", 0x90000800, 0xfc000800, "?Si,b,x", pa20, FLAG_STRICT}, +{ "cmpiclr", 0x90000000, 0xfc000800, "?si,b,x", pa10, FLAG_STRICT}, +{ "comiclr", 0x90000000, 0xfc000800, "?si,b,x", pa10, 0}, +{ "shladd", 0x08000220, 0xfc000320, "ca?Ax,.,b,t", pa20, FLAG_STRICT}, +{ "shladd", 0x08000200, 0xfc000320, "ca?ax,.,b,t", pa10, FLAG_STRICT}, +{ "sh1add", 0x08000640, 0xfc000fe0, "?ax,b,t", pa10, 0}, +{ "sh1addl", 0x08000a40, 0xfc000fe0, "?ax,b,t", pa10, 0}, +{ "sh1addo", 0x08000e40, 0xfc000fe0, "?ax,b,t", pa10, 0}, +{ "sh2add", 0x08000680, 0xfc000fe0, "?ax,b,t", pa10, 0}, +{ "sh2addl", 0x08000a80, 0xfc000fe0, "?ax,b,t", pa10, 0}, +{ "sh2addo", 0x08000e80, 0xfc000fe0, "?ax,b,t", pa10, 0}, +{ "sh3add", 0x080006c0, 0xfc000fe0, "?ax,b,t", pa10, 0}, +{ "sh3addl", 0x08000ac0, 0xfc000fe0, "?ax,b,t", pa10, 0}, +{ "sh3addo", 0x08000ec0, 0xfc000fe0, "?ax,b,t", pa10, 0}, + +/* Subword Operation Instructions. */ + +{ "hadd", 0x08000300, 0xfc00ff20, "cHx,b,t", pa20, FLAG_STRICT}, +{ "havg", 0x080002c0, 0xfc00ffe0, "x,b,t", pa20, FLAG_STRICT}, +{ "hshl", 0xf8008800, 0xffe0fc20, "x,*,t", pa20, FLAG_STRICT}, +{ "hshladd", 0x08000700, 0xfc00ff20, "x,.,b,t", pa20, FLAG_STRICT}, +{ "hshr", 0xf800c800, 0xfc1ff820, "cSb,*,t", pa20, FLAG_STRICT}, +{ "hshradd", 0x08000500, 0xfc00ff20, "x,.,b,t", pa20, FLAG_STRICT}, +{ "hsub", 0x08000100, 0xfc00ff20, "cHx,b,t", pa20, FLAG_STRICT}, +{ "mixh", 0xf8008400, 0xfc009fe0, "chx,b,t", pa20, FLAG_STRICT}, +{ "mixw", 0xf8008000, 0xfc009fe0, "chx,b,t", pa20, FLAG_STRICT}, +{ "permh", 0xf8000000, 0xfc009020, "c*a,t", pa20, FLAG_STRICT}, + + +/* Extract and Deposit Instructions. */ + +{ "shrpd", 0xd0000200, 0xfc001fe0, "?Xx,b,!,t", pa20, FLAG_STRICT}, +{ "shrpd", 0xd0000400, 0xfc001400, "?Xx,b,~,t", pa20, FLAG_STRICT}, +{ "shrpw", 0xd0000000, 0xfc001fe0, "?xx,b,!,t", pa10, FLAG_STRICT}, +{ "shrpw", 0xd0000800, 0xfc001c00, "?xx,b,p,t", pa10, FLAG_STRICT}, +{ "vshd", 0xd0000000, 0xfc001fe0, "?xx,b,t", pa10, 0}, +{ "shd", 0xd0000800, 0xfc001c00, "?xx,b,p,t", pa10, 0}, +{ "extrd", 0xd0001200, 0xfc001ae0, "cS?Xb,!,%,x", pa20, FLAG_STRICT}, +{ "extrd", 0xd8000000, 0xfc000000, "cS?Xb,q,|,x", pa20, FLAG_STRICT}, +{ "extrw", 0xd0001000, 0xfc001be0, "cS?xb,!,T,x", pa10, FLAG_STRICT}, +{ "extrw", 0xd0001800, 0xfc001800, "cS?xb,P,T,x", pa10, FLAG_STRICT}, +{ "vextru", 0xd0001000, 0xfc001fe0, "?xb,T,x", pa10, 0}, +{ "vextrs", 0xd0001400, 0xfc001fe0, "?xb,T,x", pa10, 0}, +{ "extru", 0xd0001800, 0xfc001c00, "?xb,P,T,x", pa10, 0}, +{ "extrs", 0xd0001c00, 0xfc001c00, "?xb,P,T,x", pa10, 0}, +{ "depd", 0xd4000200, 0xfc001ae0, "cz?Xx,!,%,b", pa20, FLAG_STRICT}, +{ "depd", 0xf0000000, 0xfc000000, "cz?Xx,~,|,b", pa20, FLAG_STRICT}, +{ "depdi", 0xd4001200, 0xfc001ae0, "cz?X5,!,%,b", pa20, FLAG_STRICT}, +{ "depdi", 0xf4000000, 0xfc000000, "cz?X5,~,|,b", pa20, FLAG_STRICT}, +{ "depw", 0xd4000000, 0xfc001be0, "cz?xx,!,T,b", pa10, FLAG_STRICT}, +{ "depw", 0xd4000800, 0xfc001800, "cz?xx,p,T,b", pa10, FLAG_STRICT}, +{ "depwi", 0xd4001000, 0xfc001be0, "cz?x5,!,T,b", pa10, FLAG_STRICT}, +{ "depwi", 0xd4001800, 0xfc001800, "cz?x5,p,T,b", pa10, FLAG_STRICT}, +{ "zvdep", 0xd4000000, 0xfc001fe0, "?xx,T,b", pa10, 0}, +{ "vdep", 0xd4000400, 0xfc001fe0, "?xx,T,b", pa10, 0}, +{ "zdep", 0xd4000800, 0xfc001c00, "?xx,p,T,b", pa10, 0}, +{ "dep", 0xd4000c00, 0xfc001c00, "?xx,p,T,b", pa10, 0}, +{ "zvdepi", 0xd4001000, 0xfc001fe0, "?x5,T,b", pa10, 0}, +{ "vdepi", 0xd4001400, 0xfc001fe0, "?x5,T,b", pa10, 0}, +{ "zdepi", 0xd4001800, 0xfc001c00, "?x5,p,T,b", pa10, 0}, +{ "depi", 0xd4001c00, 0xfc001c00, "?x5,p,T,b", pa10, 0}, + +/* System Control Instructions. */ + +{ "break", 0x00000000, 0xfc001fe0, "r,A", pa10, 0}, +{ "rfi", 0x00000c00, 0xffffff1f, "cr", pa10, FLAG_STRICT}, +{ "rfi", 0x00000c00, 0xffffffff, "", pa10, 0}, +{ "rfir", 0x00000ca0, 0xffffffff, "", pa11, 0}, +{ "ssm", 0x00000d60, 0xfc00ffe0, "U,t", pa20, FLAG_STRICT}, +{ "ssm", 0x00000d60, 0xffe0ffe0, "R,t", pa10, 0}, +{ "rsm", 0x00000e60, 0xfc00ffe0, "U,t", pa20, FLAG_STRICT}, +{ "rsm", 0x00000e60, 0xffe0ffe0, "R,t", pa10, 0}, +{ "mtsm", 0x00001860, 0xffe0ffff, "x", pa10, 0}, +{ "ldsid", 0x000010a0, 0xfc1fffe0, "(b),t", pa10, 0}, +{ "ldsid", 0x000010a0, 0xfc1f3fe0, "(s,b),t", pa10, 0}, +{ "mtsp", 0x00001820, 0xffe01fff, "x,S", pa10, 0}, +{ "mtctl", 0x00001840, 0xfc00ffff, "x,^", pa10, 0}, +{ "mtsarcm", 0x016018C0, 0xffe0ffff, "x", pa20, FLAG_STRICT}, +{ "mfia", 0x000014A0, 0xffffffe0, "t", pa20, FLAG_STRICT}, +{ "mfsp", 0x000004a0, 0xffff1fe0, "S,t", pa10, 0}, +{ "mfctl", 0x016048a0, 0xffffffe0, "cW!,t", pa20, FLAG_STRICT}, +{ "mfctl", 0x000008a0, 0xfc1fffe0, "^,t", pa10, 0}, +{ "sync", 0x00000400, 0xffffffff, "", pa10, 0}, +{ "syncdma", 0x00100400, 0xffffffff, "", pa10, 0}, +{ "probe", 0x04001180, 0xfc00ffa0, "cw(b),x,t", pa10, FLAG_STRICT}, +{ "probe", 0x04001180, 0xfc003fa0, "cw(s,b),x,t", pa10, FLAG_STRICT}, +{ "probei", 0x04003180, 0xfc00ffa0, "cw(b),R,t", pa10, FLAG_STRICT}, +{ "probei", 0x04003180, 0xfc003fa0, "cw(s,b),R,t", pa10, FLAG_STRICT}, +{ "prober", 0x04001180, 0xfc00ffe0, "(b),x,t", pa10, 0}, +{ "prober", 0x04001180, 0xfc003fe0, "(s,b),x,t", pa10, 0}, +{ "proberi", 0x04003180, 0xfc00ffe0, "(b),R,t", pa10, 0}, +{ "proberi", 0x04003180, 0xfc003fe0, "(s,b),R,t", pa10, 0}, +{ "probew", 0x040011c0, 0xfc00ffe0, "(b),x,t", pa10, 0}, +{ "probew", 0x040011c0, 0xfc003fe0, "(s,b),x,t", pa10, 0}, +{ "probewi", 0x040031c0, 0xfc00ffe0, "(b),R,t", pa10, 0}, +{ "probewi", 0x040031c0, 0xfc003fe0, "(s,b),R,t", pa10, 0}, +{ "lpa", 0x04001340, 0xfc00ffc0, "cZx(b),t", pa10, 0}, +{ "lpa", 0x04001340, 0xfc003fc0, "cZx(s,b),t", pa10, 0}, +{ "lci", 0x04001300, 0xfc00ffe0, "x(b),t", pa11, 0}, +{ "lci", 0x04001300, 0xfc003fe0, "x(s,b),t", pa11, 0}, +{ "pdtlb", 0x04001600, 0xfc00ffdf, "cLcZx(b)", pa20, FLAG_STRICT}, +{ "pdtlb", 0x04001600, 0xfc003fdf, "cLcZx(s,b)", pa20, FLAG_STRICT}, +{ "pdtlb", 0x04001600, 0xfc1fffdf, "cLcZ@(b)", pa20, FLAG_STRICT}, +{ "pdtlb", 0x04001600, 0xfc1f3fdf, "cLcZ@(s,b)", pa20, FLAG_STRICT}, +{ "pdtlb", 0x04001200, 0xfc00ffdf, "cZx(b)", pa10, 0}, +{ "pdtlb", 0x04001200, 0xfc003fdf, "cZx(s,b)", pa10, 0}, +{ "pitlb", 0x04000600, 0xfc001fdf, "cLcZx(S,b)", pa20, FLAG_STRICT}, +{ "pitlb", 0x04000600, 0xfc1f1fdf, "cLcZ@(S,b)", pa20, FLAG_STRICT}, +{ "pitlb", 0x04000200, 0xfc001fdf, "cZx(S,b)", pa10, 0}, +{ "pdtlbe", 0x04001240, 0xfc00ffdf, "cZx(b)", pa10, 0}, +{ "pdtlbe", 0x04001240, 0xfc003fdf, "cZx(s,b)", pa10, 0}, +{ "pitlbe", 0x04000240, 0xfc001fdf, "cZx(S,b)", pa10, 0}, +{ "idtlba", 0x04001040, 0xfc00ffff, "x,(b)", pa10, 0}, +{ "idtlba", 0x04001040, 0xfc003fff, "x,(s,b)", pa10, 0}, +{ "iitlba", 0x04000040, 0xfc001fff, "x,(S,b)", pa10, 0}, +{ "idtlbp", 0x04001000, 0xfc00ffff, "x,(b)", pa10, 0}, +{ "idtlbp", 0x04001000, 0xfc003fff, "x,(s,b)", pa10, 0}, +{ "iitlbp", 0x04000000, 0xfc001fff, "x,(S,b)", pa10, 0}, +{ "pdc", 0x04001380, 0xfc00ffdf, "cZx(b)", pa10, 0}, +{ "pdc", 0x04001380, 0xfc003fdf, "cZx(s,b)", pa10, 0}, +{ "fdc", 0x04001280, 0xfc00ffdf, "cZx(b)", pa10, FLAG_STRICT}, +{ "fdc", 0x04001280, 0xfc003fdf, "cZx(s,b)", pa10, FLAG_STRICT}, +{ "fdc", 0x04003280, 0xfc00ffff, "5(b)", pa20, FLAG_STRICT}, +{ "fdc", 0x04003280, 0xfc003fff, "5(s,b)", pa20, FLAG_STRICT}, +{ "fdc", 0x04001280, 0xfc00ffdf, "cZx(b)", pa10, 0}, +{ "fdc", 0x04001280, 0xfc003fdf, "cZx(s,b)", pa10, 0}, +{ "fic", 0x040013c0, 0xfc00dfdf, "cZx(b)", pa20, FLAG_STRICT}, +{ "fic", 0x04000280, 0xfc001fdf, "cZx(S,b)", pa10, 0}, +{ "fdce", 0x040012c0, 0xfc00ffdf, "cZx(b)", pa10, 0}, +{ "fdce", 0x040012c0, 0xfc003fdf, "cZx(s,b)", pa10, 0}, +{ "fice", 0x040002c0, 0xfc001fdf, "cZx(S,b)", pa10, 0}, +{ "diag", 0x14000000, 0xfc000000, "D", pa10, 0}, +{ "idtlbt", 0x04001800, 0xfc00ffff, "x,b", pa20, FLAG_STRICT}, +{ "iitlbt", 0x04000800, 0xfc00ffff, "x,b", pa20, FLAG_STRICT}, + +/* These may be specific to certain versions of the PA. Joel claimed + they were 72000 (7200?) specific. However, I'm almost certain the + mtcpu/mfcpu were undocumented, but available in the older 700 machines. */ +{ "mtcpu", 0x14001600, 0xfc00ffff, "x,^", pa10, 0}, +{ "mfcpu", 0x14001A00, 0xfc00ffff, "^,x", pa10, 0}, +{ "tocen", 0x14403600, 0xffffffff, "", pa10, 0}, +{ "tocdis", 0x14401620, 0xffffffff, "", pa10, 0}, +{ "shdwgr", 0x14402600, 0xffffffff, "", pa10, 0}, +{ "grshdw", 0x14400620, 0xffffffff, "", pa10, 0}, + +/* gfw and gfr are not in the HP PA 1.1 manual, but they are in either + the Timex FPU or the Mustang ERS (not sure which) manual. */ +{ "gfw", 0x04001680, 0xfc00ffdf, "cZx(b)", pa11, 0}, +{ "gfw", 0x04001680, 0xfc003fdf, "cZx(s,b)", pa11, 0}, +{ "gfr", 0x04001a80, 0xfc00ffdf, "cZx(b)", pa11, 0}, +{ "gfr", 0x04001a80, 0xfc003fdf, "cZx(s,b)", pa11, 0}, + +/* Floating Point Coprocessor Instructions. */ + +{ "fldw", 0x24000000, 0xfc00df80, "cXx(b),fT", pa10, FLAG_STRICT}, +{ "fldw", 0x24000000, 0xfc001f80, "cXx(s,b),fT", pa10, FLAG_STRICT}, +{ "fldw", 0x24000000, 0xfc00d380, "cxccx(b),fT", pa11, FLAG_STRICT}, +{ "fldw", 0x24000000, 0xfc001380, "cxccx(s,b),fT", pa11, FLAG_STRICT}, +{ "fldw", 0x24001020, 0xfc1ff3a0, "cocc@(b),fT", pa20, FLAG_STRICT}, +{ "fldw", 0x24001020, 0xfc1f33a0, "cocc@(s,b),fT", pa20, FLAG_STRICT}, +{ "fldw", 0x24001000, 0xfc00df80, "cM5(b),fT", pa10, FLAG_STRICT}, +{ "fldw", 0x24001000, 0xfc001f80, "cM5(s,b),fT", pa10, FLAG_STRICT}, +{ "fldw", 0x24001000, 0xfc00d380, "cmcc5(b),fT", pa11, FLAG_STRICT}, +{ "fldw", 0x24001000, 0xfc001380, "cmcc5(s,b),fT", pa11, FLAG_STRICT}, +{ "fldw", 0x5c000000, 0xfc000004, "y(b),fe", pa20w, FLAG_STRICT}, +{ "fldw", 0x58000000, 0xfc000000, "cJy(b),fe", pa20w, FLAG_STRICT}, +{ "fldw", 0x5c000000, 0xfc00c004, "d(b),fe", pa20, FLAG_STRICT}, +{ "fldw", 0x5c000000, 0xfc000004, "d(s,b),fe", pa20, FLAG_STRICT}, +{ "fldw", 0x58000000, 0xfc00c000, "cJd(b),fe", pa20, FLAG_STRICT}, +{ "fldw", 0x58000000, 0xfc000000, "cJd(s,b),fe", pa20, FLAG_STRICT}, +{ "fldd", 0x2c000000, 0xfc00dfc0, "cXx(b),ft", pa10, FLAG_STRICT}, +{ "fldd", 0x2c000000, 0xfc001fc0, "cXx(s,b),ft", pa10, FLAG_STRICT}, +{ "fldd", 0x2c000000, 0xfc00d3c0, "cxccx(b),ft", pa11, FLAG_STRICT}, +{ "fldd", 0x2c000000, 0xfc0013c0, "cxccx(s,b),ft", pa11, FLAG_STRICT}, +{ "fldd", 0x2c001020, 0xfc1ff3e0, "cocc@(b),ft", pa20, FLAG_STRICT}, +{ "fldd", 0x2c001020, 0xfc1f33e0, "cocc@(s,b),ft", pa20, FLAG_STRICT}, +{ "fldd", 0x2c001000, 0xfc00dfc0, "cM5(b),ft", pa10, FLAG_STRICT}, +{ "fldd", 0x2c001000, 0xfc001fc0, "cM5(s,b),ft", pa10, FLAG_STRICT}, +{ "fldd", 0x2c001000, 0xfc00d3c0, "cmcc5(b),ft", pa11, FLAG_STRICT}, +{ "fldd", 0x2c001000, 0xfc0013c0, "cmcc5(s,b),ft", pa11, FLAG_STRICT}, +{ "fldd", 0x50000002, 0xfc000002, "cq&(b),fx", pa20w, FLAG_STRICT}, +{ "fldd", 0x50000002, 0xfc00c002, "cq#(b),fx", pa20, FLAG_STRICT}, +{ "fldd", 0x50000002, 0xfc000002, "cq#(s,b),fx", pa20, FLAG_STRICT}, +{ "fstw", 0x24000200, 0xfc00df80, "cXfT,x(b)", pa10, FLAG_STRICT}, +{ "fstw", 0x24000200, 0xfc001f80, "cXfT,x(s,b)", pa10, FLAG_STRICT}, +{ "fstw", 0x24000200, 0xfc00d380, "cxcCfT,x(b)", pa11, FLAG_STRICT}, +{ "fstw", 0x24000200, 0xfc001380, "cxcCfT,x(s,b)", pa11, FLAG_STRICT}, +{ "fstw", 0x24001220, 0xfc1ff3a0, "cocCfT,@(b)", pa20, FLAG_STRICT}, +{ "fstw", 0x24001220, 0xfc1f33a0, "cocCfT,@(s,b)", pa20, FLAG_STRICT}, +{ "fstw", 0x24001200, 0xfc00df80, "cMfT,5(b)", pa10, FLAG_STRICT}, +{ "fstw", 0x24001200, 0xfc001f80, "cMfT,5(s,b)", pa10, FLAG_STRICT}, +{ "fstw", 0x24001200, 0xfc00df80, "cMfT,5(b)", pa10, FLAG_STRICT}, +{ "fstw", 0x24001200, 0xfc001f80, "cMfT,5(s,b)", pa10, FLAG_STRICT}, +{ "fstw", 0x7c000000, 0xfc000004, "fE,y(b)", pa20w, FLAG_STRICT}, +{ "fstw", 0x78000000, 0xfc000000, "cJfE,y(b)", pa20w, FLAG_STRICT}, +{ "fstw", 0x7c000000, 0xfc00c004, "fE,d(b)", pa20, FLAG_STRICT}, +{ "fstw", 0x7c000000, 0xfc000004, "fE,d(s,b)", pa20, FLAG_STRICT}, +{ "fstw", 0x78000000, 0xfc00c000, "cJfE,d(b)", pa20, FLAG_STRICT}, +{ "fstw", 0x78000000, 0xfc000000, "cJfE,d(s,b)", pa20, FLAG_STRICT}, +{ "fstd", 0x2c000200, 0xfc00dfc0, "cXft,x(b)", pa10, FLAG_STRICT}, +{ "fstd", 0x2c000200, 0xfc001fc0, "cXft,x(s,b)", pa10, FLAG_STRICT}, +{ "fstd", 0x2c000200, 0xfc00d3c0, "cxcCft,x(b)", pa11, FLAG_STRICT}, +{ "fstd", 0x2c000200, 0xfc0013c0, "cxcCft,x(s,b)", pa11, FLAG_STRICT}, +{ "fstd", 0x2c001220, 0xfc1ff3e0, "cocCft,@(b)", pa20, FLAG_STRICT}, +{ "fstd", 0x2c001220, 0xfc1f33e0, "cocCft,@(s,b)", pa20, FLAG_STRICT}, +{ "fstd", 0x2c001200, 0xfc00dfc0, "cMft,5(b)", pa10, FLAG_STRICT}, +{ "fstd", 0x2c001200, 0xfc001fc0, "cMft,5(s,b)", pa10, FLAG_STRICT}, +{ "fstd", 0x2c001200, 0xfc00d3c0, "cmcCft,5(b)", pa11, FLAG_STRICT}, +{ "fstd", 0x2c001200, 0xfc0013c0, "cmcCft,5(s,b)", pa11, FLAG_STRICT}, +{ "fstd", 0x70000002, 0xfc000002, "cqfx,&(b)", pa20w, FLAG_STRICT}, +{ "fstd", 0x70000002, 0xfc00c002, "cqfx,#(b)", pa20, FLAG_STRICT}, +{ "fstd", 0x70000002, 0xfc000002, "cqfx,#(s,b)", pa20, FLAG_STRICT}, +{ "fldwx", 0x24000000, 0xfc00df80, "cXx(b),fT", pa10, FLAG_STRICT}, +{ "fldwx", 0x24000000, 0xfc001f80, "cXx(s,b),fT", pa10, FLAG_STRICT}, +{ "fldwx", 0x24000000, 0xfc00d380, "cxccx(b),fT", pa11, FLAG_STRICT}, +{ "fldwx", 0x24000000, 0xfc001380, "cxccx(s,b),fT", pa11, FLAG_STRICT}, +{ "fldwx", 0x24000000, 0xfc00df80, "cXx(b),fT", pa10, 0}, +{ "fldwx", 0x24000000, 0xfc001f80, "cXx(s,b),fT", pa10, 0}, +{ "flddx", 0x2c000000, 0xfc00dfc0, "cXx(b),ft", pa10, FLAG_STRICT}, +{ "flddx", 0x2c000000, 0xfc001fc0, "cXx(s,b),ft", pa10, FLAG_STRICT}, +{ "flddx", 0x2c000000, 0xfc00d3c0, "cxccx(b),ft", pa11, FLAG_STRICT}, +{ "flddx", 0x2c000000, 0xfc0013c0, "cxccx(s,b),ft", pa11, FLAG_STRICT}, +{ "flddx", 0x2c000000, 0xfc00dfc0, "cXx(b),ft", pa10, 0}, +{ "flddx", 0x2c000000, 0xfc001fc0, "cXx(s,b),ft", pa10, 0}, +{ "fstwx", 0x24000200, 0xfc00df80, "cxfT,x(b)", pa10, FLAG_STRICT}, +{ "fstwx", 0x24000200, 0xfc001f80, "cxfT,x(s,b)", pa10, FLAG_STRICT}, +{ "fstwx", 0x24000200, 0xfc00d380, "cxcCfT,x(b)", pa11, FLAG_STRICT}, +{ "fstwx", 0x24000200, 0xfc001380, "cxcCfT,x(s,b)", pa11, FLAG_STRICT}, +{ "fstwx", 0x24000200, 0xfc00df80, "cxfT,x(b)", pa10, 0}, +{ "fstwx", 0x24000200, 0xfc001f80, "cxfT,x(s,b)", pa10, 0}, +{ "fstdx", 0x2c000200, 0xfc00dfc0, "cxft,x(b)", pa10, FLAG_STRICT}, +{ "fstdx", 0x2c000200, 0xfc001fc0, "cxft,x(s,b)", pa10, FLAG_STRICT}, +{ "fstdx", 0x2c000200, 0xfc00d3c0, "cxcCft,x(b)", pa11, FLAG_STRICT}, +{ "fstdx", 0x2c000200, 0xfc0013c0, "cxcCft,x(s,b)", pa11, FLAG_STRICT}, +{ "fstdx", 0x2c000200, 0xfc00dfc0, "cxft,x(b)", pa10, 0}, +{ "fstdx", 0x2c000200, 0xfc001fc0, "cxft,x(s,b)", pa10, 0}, +{ "fstqx", 0x3c000200, 0xfc00dfc0, "cxft,x(b)", pa10, 0}, +{ "fstqx", 0x3c000200, 0xfc001fc0, "cxft,x(s,b)", pa10, 0}, +{ "fldws", 0x24001000, 0xfc00df80, "cm5(b),fT", pa10, FLAG_STRICT}, +{ "fldws", 0x24001000, 0xfc001f80, "cm5(s,b),fT", pa10, FLAG_STRICT}, +{ "fldws", 0x24001000, 0xfc00d380, "cmcc5(b),fT", pa11, FLAG_STRICT}, +{ "fldws", 0x24001000, 0xfc001380, "cmcc5(s,b),fT", pa11, FLAG_STRICT}, +{ "fldws", 0x24001000, 0xfc00df80, "cm5(b),fT", pa10, 0}, +{ "fldws", 0x24001000, 0xfc001f80, "cm5(s,b),fT", pa10, 0}, +{ "fldds", 0x2c001000, 0xfc00dfc0, "cm5(b),ft", pa10, FLAG_STRICT}, +{ "fldds", 0x2c001000, 0xfc001fc0, "cm5(s,b),ft", pa10, FLAG_STRICT}, +{ "fldds", 0x2c001000, 0xfc00d3c0, "cmcc5(b),ft", pa11, FLAG_STRICT}, +{ "fldds", 0x2c001000, 0xfc0013c0, "cmcc5(s,b),ft", pa11, FLAG_STRICT}, +{ "fldds", 0x2c001000, 0xfc00dfc0, "cm5(b),ft", pa10, 0}, +{ "fldds", 0x2c001000, 0xfc001fc0, "cm5(s,b),ft", pa10, 0}, +{ "fstws", 0x24001200, 0xfc00df80, "cmfT,5(b)", pa10, FLAG_STRICT}, +{ "fstws", 0x24001200, 0xfc001f80, "cmfT,5(s,b)", pa10, FLAG_STRICT}, +{ "fstws", 0x24001200, 0xfc00d380, "cmcCfT,5(b)", pa11, FLAG_STRICT}, +{ "fstws", 0x24001200, 0xfc001380, "cmcCfT,5(s,b)", pa11, FLAG_STRICT}, +{ "fstws", 0x24001200, 0xfc00df80, "cmfT,5(b)", pa10, 0}, +{ "fstws", 0x24001200, 0xfc001f80, "cmfT,5(s,b)", pa10, 0}, +{ "fstds", 0x2c001200, 0xfc00dfc0, "cmft,5(b)", pa10, FLAG_STRICT}, +{ "fstds", 0x2c001200, 0xfc001fc0, "cmft,5(s,b)", pa10, FLAG_STRICT}, +{ "fstds", 0x2c001200, 0xfc00d3c0, "cmcCft,5(b)", pa11, FLAG_STRICT}, +{ "fstds", 0x2c001200, 0xfc0013c0, "cmcCft,5(s,b)", pa11, FLAG_STRICT}, +{ "fstds", 0x2c001200, 0xfc00dfc0, "cmft,5(b)", pa10, 0}, +{ "fstds", 0x2c001200, 0xfc001fc0, "cmft,5(s,b)", pa10, 0}, +{ "fstqs", 0x3c001200, 0xfc00dfc0, "cmft,5(b)", pa10, 0}, +{ "fstqs", 0x3c001200, 0xfc001fc0, "cmft,5(s,b)", pa10, 0}, +{ "fadd", 0x30000600, 0xfc00e7e0, "Ffa,fb,fT", pa10, 0}, +{ "fadd", 0x38000600, 0xfc00e720, "IfA,fB,fT", pa10, 0}, +{ "fsub", 0x30002600, 0xfc00e7e0, "Ffa,fb,fT", pa10, 0}, +{ "fsub", 0x38002600, 0xfc00e720, "IfA,fB,fT", pa10, 0}, +{ "fmpy", 0x30004600, 0xfc00e7e0, "Ffa,fb,fT", pa10, 0}, +{ "fmpy", 0x38004600, 0xfc00e720, "IfA,fB,fT", pa10, 0}, +{ "fdiv", 0x30006600, 0xfc00e7e0, "Ffa,fb,fT", pa10, 0}, +{ "fdiv", 0x38006600, 0xfc00e720, "IfA,fB,fT", pa10, 0}, +{ "fsqrt", 0x30008000, 0xfc1fe7e0, "Ffa,fT", pa10, 0}, +{ "fsqrt", 0x38008000, 0xfc1fe720, "FfA,fT", pa10, 0}, +{ "fabs", 0x30006000, 0xfc1fe7e0, "Ffa,fT", pa10, 0}, +{ "fabs", 0x38006000, 0xfc1fe720, "FfA,fT", pa10, 0}, +{ "frem", 0x30008600, 0xfc00e7e0, "Ffa,fb,fT", pa10, 0}, +{ "frem", 0x38008600, 0xfc00e720, "FfA,fB,fT", pa10, 0}, +{ "frnd", 0x3000a000, 0xfc1fe7e0, "Ffa,fT", pa10, 0}, +{ "frnd", 0x3800a000, 0xfc1fe720, "FfA,fT", pa10, 0}, +{ "fcpy", 0x30004000, 0xfc1fe7e0, "Ffa,fT", pa10, 0}, +{ "fcpy", 0x38004000, 0xfc1fe720, "FfA,fT", pa10, 0}, +{ "fcnvff", 0x30000200, 0xfc1f87e0, "FGfa,fT", pa10, 0}, +{ "fcnvff", 0x38000200, 0xfc1f8720, "FGfA,fT", pa10, 0}, +{ "fcnvxf", 0x30008200, 0xfc1f87e0, "FGfa,fT", pa10, 0}, +{ "fcnvxf", 0x38008200, 0xfc1f8720, "FGfA,fT", pa10, 0}, +{ "fcnvfx", 0x30010200, 0xfc1f87e0, "FGfa,fT", pa10, 0}, +{ "fcnvfx", 0x38010200, 0xfc1f8720, "FGfA,fT", pa10, 0}, +{ "fcnvfxt", 0x30018200, 0xfc1f87e0, "FGfa,fT", pa10, 0}, +{ "fcnvfxt", 0x38018200, 0xfc1f8720, "FGfA,fT", pa10, 0}, +{ "fmpyfadd", 0xb8000000, 0xfc000020, "IfA,fB,fC,fT", pa20, FLAG_STRICT}, +{ "fmpynfadd", 0xb8000020, 0xfc000020, "IfA,fB,fC,fT", pa20, FLAG_STRICT}, +{ "fneg", 0x3000c000, 0xfc1fe7e0, "Ffa,fT", pa20, FLAG_STRICT}, +{ "fneg", 0x3800c000, 0xfc1fe720, "IfA,fT", pa20, FLAG_STRICT}, +{ "fnegabs", 0x3000e000, 0xfc1fe7e0, "Ffa,fT", pa20, FLAG_STRICT}, +{ "fnegabs", 0x3800e000, 0xfc1fe720, "IfA,fT", pa20, FLAG_STRICT}, +{ "fcnv", 0x30000200, 0xfc1c0720, "{_fa,fT", pa20, FLAG_STRICT}, +{ "fcnv", 0x38000200, 0xfc1c0720, "FGfA,fT", pa20, FLAG_STRICT}, +{ "fcmp", 0x30000400, 0xfc00e7e0, "F?ffa,fb", pa10, FLAG_STRICT}, +{ "fcmp", 0x38000400, 0xfc00e720, "I?ffA,fB", pa10, FLAG_STRICT}, +{ "fcmp", 0x30000400, 0xfc0007e0, "F?ffa,fb,h", pa20, FLAG_STRICT}, +{ "fcmp", 0x38000400, 0xfc000720, "I?ffA,fB,h", pa20, FLAG_STRICT}, +{ "fcmp", 0x30000400, 0xfc00e7e0, "F?ffa,fb", pa10, 0}, +{ "fcmp", 0x38000400, 0xfc00e720, "I?ffA,fB", pa10, 0}, +{ "xmpyu", 0x38004700, 0xfc00e720, "fX,fB,fT", pa11, 0}, +{ "fmpyadd", 0x18000000, 0xfc000000, "Hfi,fj,fk,fl,fm", pa11, 0}, +{ "fmpysub", 0x98000000, 0xfc000000, "Hfi,fj,fk,fl,fm", pa11, 0}, +{ "ftest", 0x30002420, 0xffffffff, "", pa10, FLAG_STRICT}, +{ "ftest", 0x30002420, 0xffffffe0, ",=", pa20, FLAG_STRICT}, +{ "ftest", 0x30000420, 0xffff1fff, "m", pa20, FLAG_STRICT}, +{ "fid", 0x30000000, 0xffffffff, "", pa11, 0}, + +/* Performance Monitor Instructions. */ + +{ "pmdis", 0x30000280, 0xffffffdf, "N", pa20, FLAG_STRICT}, +{ "pmenb", 0x30000680, 0xffffffff, "", pa20, FLAG_STRICT}, + +/* Assist Instructions. */ + +{ "spop0", 0x10000000, 0xfc000600, "v,ON", pa10, 0}, +{ "spop1", 0x10000200, 0xfc000600, "v,oNt", pa10, 0}, +{ "spop2", 0x10000400, 0xfc000600, "v,1Nb", pa10, 0}, +{ "spop3", 0x10000600, 0xfc000600, "v,0Nx,b", pa10, 0}, +{ "copr", 0x30000000, 0xfc000000, "u,2N", pa10, 0}, +{ "cldw", 0x24000000, 0xfc00de00, "ucXx(b),t", pa10, FLAG_STRICT}, +{ "cldw", 0x24000000, 0xfc001e00, "ucXx(s,b),t", pa10, FLAG_STRICT}, +{ "cldw", 0x24000000, 0xfc00d200, "ucxccx(b),t", pa11, FLAG_STRICT}, +{ "cldw", 0x24000000, 0xfc001200, "ucxccx(s,b),t", pa11, FLAG_STRICT}, +{ "cldw", 0x24001000, 0xfc00d200, "ucocc@(b),t", pa20, FLAG_STRICT}, +{ "cldw", 0x24001000, 0xfc001200, "ucocc@(s,b),t", pa20, FLAG_STRICT}, +{ "cldw", 0x24001000, 0xfc00de00, "ucM5(b),t", pa10, FLAG_STRICT}, +{ "cldw", 0x24001000, 0xfc001e00, "ucM5(s,b),t", pa10, FLAG_STRICT}, +{ "cldw", 0x24001000, 0xfc00d200, "ucmcc5(b),t", pa11, FLAG_STRICT}, +{ "cldw", 0x24001000, 0xfc001200, "ucmcc5(s,b),t", pa11, FLAG_STRICT}, +{ "cldd", 0x2c000000, 0xfc00de00, "ucXx(b),t", pa10, FLAG_STRICT}, +{ "cldd", 0x2c000000, 0xfc001e00, "ucXx(s,b),t", pa10, FLAG_STRICT}, +{ "cldd", 0x2c000000, 0xfc00d200, "ucxccx(b),t", pa11, FLAG_STRICT}, +{ "cldd", 0x2c000000, 0xfc001200, "ucxccx(s,b),t", pa11, FLAG_STRICT}, +{ "cldd", 0x2c001000, 0xfc00d200, "ucocc@(b),t", pa20, FLAG_STRICT}, +{ "cldd", 0x2c001000, 0xfc001200, "ucocc@(s,b),t", pa20, FLAG_STRICT}, +{ "cldd", 0x2c001000, 0xfc00de00, "ucM5(b),t", pa10, FLAG_STRICT}, +{ "cldd", 0x2c001000, 0xfc001e00, "ucM5(s,b),t", pa10, FLAG_STRICT}, +{ "cldd", 0x2c001000, 0xfc00d200, "ucmcc5(b),t", pa11, FLAG_STRICT}, +{ "cldd", 0x2c001000, 0xfc001200, "ucmcc5(s,b),t", pa11, FLAG_STRICT}, +{ "cstw", 0x24000200, 0xfc00de00, "ucXt,x(b)", pa10, FLAG_STRICT}, +{ "cstw", 0x24000200, 0xfc001e00, "ucXt,x(s,b)", pa10, FLAG_STRICT}, +{ "cstw", 0x24000200, 0xfc00d200, "ucxcCt,x(b)", pa11, FLAG_STRICT}, +{ "cstw", 0x24000200, 0xfc001200, "ucxcCt,x(s,b)", pa11, FLAG_STRICT}, +{ "cstw", 0x24001200, 0xfc00d200, "ucocCt,@(b)", pa20, FLAG_STRICT}, +{ "cstw", 0x24001200, 0xfc001200, "ucocCt,@(s,b)", pa20, FLAG_STRICT}, +{ "cstw", 0x24001200, 0xfc00de00, "ucMt,5(b)", pa10, FLAG_STRICT}, +{ "cstw", 0x24001200, 0xfc001e00, "ucMt,5(s,b)", pa10, FLAG_STRICT}, +{ "cstw", 0x24001200, 0xfc00d200, "ucmcCt,5(b)", pa11, FLAG_STRICT}, +{ "cstw", 0x24001200, 0xfc001200, "ucmcCt,5(s,b)", pa11, FLAG_STRICT}, +{ "cstd", 0x2c000200, 0xfc00de00, "ucXt,x(b)", pa10, FLAG_STRICT}, +{ "cstd", 0x2c000200, 0xfc001e00, "ucXt,x(s,b)", pa10, FLAG_STRICT}, +{ "cstd", 0x2c000200, 0xfc00d200, "ucxcCt,x(b)", pa11, FLAG_STRICT}, +{ "cstd", 0x2c000200, 0xfc001200, "ucxcCt,x(s,b)", pa11, FLAG_STRICT}, +{ "cstd", 0x2c001200, 0xfc00d200, "ucocCt,@(b)", pa20, FLAG_STRICT}, +{ "cstd", 0x2c001200, 0xfc001200, "ucocCt,@(s,b)", pa20, FLAG_STRICT}, +{ "cstd", 0x2c001200, 0xfc00de00, "ucMt,5(b)", pa10, FLAG_STRICT}, +{ "cstd", 0x2c001200, 0xfc001e00, "ucMt,5(s,b)", pa10, FLAG_STRICT}, +{ "cstd", 0x2c001200, 0xfc00d200, "ucmcCt,5(b)", pa11, FLAG_STRICT}, +{ "cstd", 0x2c001200, 0xfc001200, "ucmcCt,5(s,b)", pa11, FLAG_STRICT}, +{ "cldwx", 0x24000000, 0xfc00de00, "ucXx(b),t", pa10, FLAG_STRICT}, +{ "cldwx", 0x24000000, 0xfc001e00, "ucXx(s,b),t", pa10, FLAG_STRICT}, +{ "cldwx", 0x24000000, 0xfc00d200, "ucxccx(b),t", pa11, FLAG_STRICT}, +{ "cldwx", 0x24000000, 0xfc001200, "ucxccx(s,b),t", pa11, FLAG_STRICT}, +{ "cldwx", 0x24000000, 0xfc00de00, "ucXx(b),t", pa10, 0}, +{ "cldwx", 0x24000000, 0xfc001e00, "ucXx(s,b),t", pa10, 0}, +{ "clddx", 0x2c000000, 0xfc00de00, "ucXx(b),t", pa10, FLAG_STRICT}, +{ "clddx", 0x2c000000, 0xfc001e00, "ucXx(s,b),t", pa10, FLAG_STRICT}, +{ "clddx", 0x2c000000, 0xfc00d200, "ucxccx(b),t", pa11, FLAG_STRICT}, +{ "clddx", 0x2c000000, 0xfc001200, "ucxccx(s,b),t", pa11, FLAG_STRICT}, +{ "clddx", 0x2c000000, 0xfc00de00, "ucXx(b),t", pa10, 0}, +{ "clddx", 0x2c000000, 0xfc001e00, "ucXx(s,b),t", pa10, 0}, +{ "cstwx", 0x24000200, 0xfc00de00, "ucXt,x(b)", pa10, FLAG_STRICT}, +{ "cstwx", 0x24000200, 0xfc001e00, "ucXt,x(s,b)", pa10, FLAG_STRICT}, +{ "cstwx", 0x24000200, 0xfc00d200, "ucxcCt,x(b)", pa11, FLAG_STRICT}, +{ "cstwx", 0x24000200, 0xfc001200, "ucxcCt,x(s,b)", pa11, FLAG_STRICT}, +{ "cstwx", 0x24000200, 0xfc00de00, "ucXt,x(b)", pa10, 0}, +{ "cstwx", 0x24000200, 0xfc001e00, "ucXt,x(s,b)", pa10, 0}, +{ "cstdx", 0x2c000200, 0xfc00de00, "ucXt,x(b)", pa10, FLAG_STRICT}, +{ "cstdx", 0x2c000200, 0xfc001e00, "ucXt,x(s,b)", pa10, FLAG_STRICT}, +{ "cstdx", 0x2c000200, 0xfc00d200, "ucxcCt,x(b)", pa11, FLAG_STRICT}, +{ "cstdx", 0x2c000200, 0xfc001200, "ucxcCt,x(s,b)", pa11, FLAG_STRICT}, +{ "cstdx", 0x2c000200, 0xfc00de00, "ucXt,x(b)", pa10, 0}, +{ "cstdx", 0x2c000200, 0xfc001e00, "ucXt,x(s,b)", pa10, 0}, +{ "cldws", 0x24001000, 0xfc00de00, "ucM5(b),t", pa10, FLAG_STRICT}, +{ "cldws", 0x24001000, 0xfc001e00, "ucM5(s,b),t", pa10, FLAG_STRICT}, +{ "cldws", 0x24001000, 0xfc00d200, "ucmcc5(b),t", pa11, FLAG_STRICT}, +{ "cldws", 0x24001000, 0xfc001200, "ucmcc5(s,b),t", pa11, FLAG_STRICT}, +{ "cldws", 0x24001000, 0xfc00de00, "ucM5(b),t", pa10, 0}, +{ "cldws", 0x24001000, 0xfc001e00, "ucM5(s,b),t", pa10, 0}, +{ "cldds", 0x2c001000, 0xfc00de00, "ucM5(b),t", pa10, FLAG_STRICT}, +{ "cldds", 0x2c001000, 0xfc001e00, "ucM5(s,b),t", pa10, FLAG_STRICT}, +{ "cldds", 0x2c001000, 0xfc00d200, "ucmcc5(b),t", pa11, FLAG_STRICT}, +{ "cldds", 0x2c001000, 0xfc001200, "ucmcc5(s,b),t", pa11, FLAG_STRICT}, +{ "cldds", 0x2c001000, 0xfc00de00, "ucM5(b),t", pa10, 0}, +{ "cldds", 0x2c001000, 0xfc001e00, "ucM5(s,b),t", pa10, 0}, +{ "cstws", 0x24001200, 0xfc00de00, "ucMt,5(b)", pa10, FLAG_STRICT}, +{ "cstws", 0x24001200, 0xfc001e00, "ucMt,5(s,b)", pa10, FLAG_STRICT}, +{ "cstws", 0x24001200, 0xfc00d200, "ucmcCt,5(b)", pa11, FLAG_STRICT}, +{ "cstws", 0x24001200, 0xfc001200, "ucmcCt,5(s,b)", pa11, FLAG_STRICT}, +{ "cstws", 0x24001200, 0xfc00de00, "ucMt,5(b)", pa10, 0}, +{ "cstws", 0x24001200, 0xfc001e00, "ucMt,5(s,b)", pa10, 0}, +{ "cstds", 0x2c001200, 0xfc00de00, "ucMt,5(b)", pa10, FLAG_STRICT}, +{ "cstds", 0x2c001200, 0xfc001e00, "ucMt,5(s,b)", pa10, FLAG_STRICT}, +{ "cstds", 0x2c001200, 0xfc00d200, "ucmcCt,5(b)", pa11, FLAG_STRICT}, +{ "cstds", 0x2c001200, 0xfc001200, "ucmcCt,5(s,b)", pa11, FLAG_STRICT}, +{ "cstds", 0x2c001200, 0xfc00de00, "ucMt,5(b)", pa10, 0}, +{ "cstds", 0x2c001200, 0xfc001e00, "ucMt,5(s,b)", pa10, 0}, + +/* More pseudo instructions which must follow the main table. */ +{ "call", 0xe800f000, 0xfc1ffffd, "n(b)", pa20, FLAG_STRICT}, +{ "call", 0xe800a000, 0xffe0e000, "nW", pa10, FLAG_STRICT}, +{ "ret", 0xe840d000, 0xfffffffd, "n", pa20, FLAG_STRICT}, + +}; + +#define NUMOPCODES ((sizeof pa_opcodes)/(sizeof pa_opcodes[0])) + +/* SKV 12/18/92. Added some denotations for various operands. */ + +#define PA_IMM11_AT_31 'i' +#define PA_IMM14_AT_31 'j' +#define PA_IMM21_AT_31 'k' +#define PA_DISP12 'w' +#define PA_DISP17 'W' + +#define N_HPPA_OPERAND_FORMATS 5 + +/* Integer register names, indexed by the numbers which appear in the + opcodes. */ +static const char *const reg_names[] = +{ + "flags", "r1", "rp", "r3", "r4", "r5", "r6", "r7", "r8", "r9", + "r10", "r11", "r12", "r13", "r14", "r15", "r16", "r17", "r18", "r19", + "r20", "r21", "r22", "r23", "r24", "r25", "r26", "dp", "ret0", "ret1", + "sp", "r31" +}; + +/* Floating point register names, indexed by the numbers which appear in the + opcodes. */ +static const char *const fp_reg_names[] = +{ + "fpsr", "fpe2", "fpe4", "fpe6", + "fr4", "fr5", "fr6", "fr7", "fr8", + "fr9", "fr10", "fr11", "fr12", "fr13", "fr14", "fr15", + "fr16", "fr17", "fr18", "fr19", "fr20", "fr21", "fr22", "fr23", + "fr24", "fr25", "fr26", "fr27", "fr28", "fr29", "fr30", "fr31" +}; + +typedef unsigned int CORE_ADDR; + +/* Get at various relevent fields of an instruction word. */ + +#define MASK_5 0x1f +#define MASK_10 0x3ff +#define MASK_11 0x7ff +#define MASK_14 0x3fff +#define MASK_16 0xffff +#define MASK_21 0x1fffff + +/* These macros get bit fields using HP's numbering (MSB = 0). */ + +#define GET_FIELD(X, FROM, TO) \ + ((X) >> (31 - (TO)) & ((1 << ((TO) - (FROM) + 1)) - 1)) + +#define GET_BIT(X, WHICH) \ + GET_FIELD (X, WHICH, WHICH) + +/* Some of these have been converted to 2-d arrays because they + consume less storage this way. If the maintenance becomes a + problem, convert them back to const 1-d pointer arrays. */ +static const char *const control_reg[] = +{ + "rctr", "cr1", "cr2", "cr3", "cr4", "cr5", "cr6", "cr7", + "pidr1", "pidr2", "ccr", "sar", "pidr3", "pidr4", + "iva", "eiem", "itmr", "pcsq", "pcoq", "iir", "isr", + "ior", "ipsw", "eirr", "tr0", "tr1", "tr2", "tr3", + "tr4", "tr5", "tr6", "tr7" +}; + +static const char *const compare_cond_names[] = +{ + "", ",=", ",<", ",<=", ",<<", ",<<=", ",sv", ",od", + ",tr", ",<>", ",>=", ",>", ",>>=", ",>>", ",nsv", ",ev" +}; +static const char *const compare_cond_64_names[] = +{ + "", ",*=", ",*<", ",*<=", ",*<<", ",*<<=", ",*sv", ",*od", + ",*tr", ",*<>", ",*>=", ",*>", ",*>>=", ",*>>", ",*nsv", ",*ev" +}; +static const char *const cmpib_cond_64_names[] = +{ + ",*<<", ",*=", ",*<", ",*<=", ",*>>=", ",*<>", ",*>=", ",*>" +}; +static const char *const add_cond_names[] = +{ + "", ",=", ",<", ",<=", ",nuv", ",znv", ",sv", ",od", + ",tr", ",<>", ",>=", ",>", ",uv", ",vnz", ",nsv", ",ev" +}; +static const char *const add_cond_64_names[] = +{ + "", ",*=", ",*<", ",*<=", ",*nuv", ",*znv", ",*sv", ",*od", + ",*tr", ",*<>", ",*>=", ",*>", ",*uv", ",*vnz", ",*nsv", ",*ev" +}; +static const char *const wide_add_cond_names[] = +{ + "", ",=", ",<", ",<=", ",nuv", ",*=", ",*<", ",*<=", + ",tr", ",<>", ",>=", ",>", ",uv", ",*<>", ",*>=", ",*>" +}; +static const char *const logical_cond_names[] = +{ + "", ",=", ",<", ",<=", 0, 0, 0, ",od", + ",tr", ",<>", ",>=", ",>", 0, 0, 0, ",ev"}; +static const char *const logical_cond_64_names[] = +{ + "", ",*=", ",*<", ",*<=", 0, 0, 0, ",*od", + ",*tr", ",*<>", ",*>=", ",*>", 0, 0, 0, ",*ev"}; +static const char *const unit_cond_names[] = +{ + "", ",swz", ",sbz", ",shz", ",sdc", ",swc", ",sbc", ",shc", + ",tr", ",nwz", ",nbz", ",nhz", ",ndc", ",nwc", ",nbc", ",nhc" +}; +static const char *const unit_cond_64_names[] = +{ + "", ",*swz", ",*sbz", ",*shz", ",*sdc", ",*swc", ",*sbc", ",*shc", + ",*tr", ",*nwz", ",*nbz", ",*nhz", ",*ndc", ",*nwc", ",*nbc", ",*nhc" +}; +static const char *const shift_cond_names[] = +{ + "", ",=", ",<", ",od", ",tr", ",<>", ",>=", ",ev" +}; +static const char *const shift_cond_64_names[] = +{ + "", ",*=", ",*<", ",*od", ",*tr", ",*<>", ",*>=", ",*ev" +}; +static const char *const bb_cond_64_names[] = +{ + ",*<", ",*>=" +}; +static const char *const index_compl_names[] = {"", ",m", ",s", ",sm"}; +static const char *const short_ldst_compl_names[] = {"", ",ma", "", ",mb"}; +static const char *const short_bytes_compl_names[] = +{ + "", ",b,m", ",e", ",e,m" +}; +static const char *const float_format_names[] = {",sgl", ",dbl", "", ",quad"}; +static const char *const fcnv_fixed_names[] = {",w", ",dw", "", ",qw"}; +static const char *const fcnv_ufixed_names[] = {",uw", ",udw", "", ",uqw"}; +static const char *const float_comp_names[] = +{ + ",false?", ",false", ",?", ",!<=>", ",=", ",=t", ",?=", ",!<>", + ",!?>=", ",<", ",?<", ",!>=", ",!?>", ",<=", ",?<=", ",!>", + ",!?<=", ",>", ",?>", ",!<=", ",!?<", ",>=", ",?>=", ",!<", + ",!?=", ",<>", ",!=", ",!=t", ",!?", ",<=>", ",true?", ",true" +}; +static const char *const signed_unsigned_names[] = {",u", ",s"}; +static const char *const mix_half_names[] = {",l", ",r"}; +static const char *const saturation_names[] = {",us", ",ss", 0, ""}; +static const char *const read_write_names[] = {",r", ",w"}; +static const char *const add_compl_names[] = { 0, "", ",l", ",tsv" }; + +/* For a bunch of different instructions form an index into a + completer name table. */ +#define GET_COMPL(insn) (GET_FIELD (insn, 26, 26) | \ + GET_FIELD (insn, 18, 18) << 1) + +#define GET_COND(insn) (GET_FIELD ((insn), 16, 18) + \ + (GET_FIELD ((insn), 19, 19) ? 8 : 0)) + +/* Utility function to print registers. Put these first, so gcc's function + inlining can do its stuff. */ + +#define fputs_filtered(STR,F) (*info->fprintf_func) (info->stream, "%s", STR) + +static void +fput_reg (unsigned reg, disassemble_info *info) +{ + (*info->fprintf_func) (info->stream, reg ? reg_names[reg] : "r0"); +} + +static void +fput_fp_reg (unsigned reg, disassemble_info *info) +{ + (*info->fprintf_func) (info->stream, reg ? fp_reg_names[reg] : "fr0"); +} + +static void +fput_fp_reg_r (unsigned reg, disassemble_info *info) +{ + /* Special case floating point exception registers. */ + if (reg < 4) + (*info->fprintf_func) (info->stream, "fpe%d", reg * 2 + 1); + else + (*info->fprintf_func) (info->stream, "%sR", + reg ? fp_reg_names[reg] : "fr0"); +} + +static void +fput_creg (unsigned reg, disassemble_info *info) +{ + (*info->fprintf_func) (info->stream, control_reg[reg]); +} + +/* Print constants with sign. */ + +static void +fput_const (unsigned num, disassemble_info *info) +{ + if ((int) num < 0) + (*info->fprintf_func) (info->stream, "-%x", - (int) num); + else + (*info->fprintf_func) (info->stream, "%x", num); +} + +/* Routines to extract various sized constants out of hppa + instructions. */ + +/* Extract a 3-bit space register number from a be, ble, mtsp or mfsp. */ +static int +extract_3 (unsigned word) +{ + return GET_FIELD (word, 18, 18) << 2 | GET_FIELD (word, 16, 17); +} + +static int +extract_5_load (unsigned word) +{ + return low_sign_extend (word >> 16 & MASK_5, 5); +} + +/* Extract the immediate field from a st{bhw}s instruction. */ + +static int +extract_5_store (unsigned word) +{ + return low_sign_extend (word & MASK_5, 5); +} + +/* Extract the immediate field from a break instruction. */ + +static unsigned +extract_5r_store (unsigned word) +{ + return (word & MASK_5); +} + +/* Extract the immediate field from a {sr}sm instruction. */ + +static unsigned +extract_5R_store (unsigned word) +{ + return (word >> 16 & MASK_5); +} + +/* Extract the 10 bit immediate field from a {sr}sm instruction. */ + +static unsigned +extract_10U_store (unsigned word) +{ + return (word >> 16 & MASK_10); +} + +/* Extract the immediate field from a bb instruction. */ + +static unsigned +extract_5Q_store (unsigned word) +{ + return (word >> 21 & MASK_5); +} + +/* Extract an 11 bit immediate field. */ + +static int +extract_11 (unsigned word) +{ + return low_sign_extend (word & MASK_11, 11); +} + +/* Extract a 14 bit immediate field. */ + +static int +extract_14 (unsigned word) +{ + return low_sign_extend (word & MASK_14, 14); +} + +/* Extract a 16 bit immediate field (PA2.0 wide only). */ + +static int +extract_16 (unsigned word) +{ + int m15, m0, m1; + + m0 = GET_BIT (word, 16); + m1 = GET_BIT (word, 17); + m15 = GET_BIT (word, 31); + word = (word >> 1) & 0x1fff; + word = word | (m15 << 15) | ((m15 ^ m0) << 14) | ((m15 ^ m1) << 13); + return sign_extend (word, 16); +} + +/* Extract a 21 bit constant. */ + +static int +extract_21 (unsigned word) +{ + int val; + + word &= MASK_21; + word <<= 11; + val = GET_FIELD (word, 20, 20); + val <<= 11; + val |= GET_FIELD (word, 9, 19); + val <<= 2; + val |= GET_FIELD (word, 5, 6); + val <<= 5; + val |= GET_FIELD (word, 0, 4); + val <<= 2; + val |= GET_FIELD (word, 7, 8); + return sign_extend (val, 21) << 11; +} + +/* Extract a 12 bit constant from branch instructions. */ + +static int +extract_12 (unsigned word) +{ + return sign_extend (GET_FIELD (word, 19, 28) + | GET_FIELD (word, 29, 29) << 10 + | (word & 0x1) << 11, 12) << 2; +} + +/* Extract a 17 bit constant from branch instructions, returning the + 19 bit signed value. */ + +static int +extract_17 (unsigned word) +{ + return sign_extend (GET_FIELD (word, 19, 28) + | GET_FIELD (word, 29, 29) << 10 + | GET_FIELD (word, 11, 15) << 11 + | (word & 0x1) << 16, 17) << 2; +} + +static int +extract_22 (unsigned word) +{ + return sign_extend (GET_FIELD (word, 19, 28) + | GET_FIELD (word, 29, 29) << 10 + | GET_FIELD (word, 11, 15) << 11 + | GET_FIELD (word, 6, 10) << 16 + | (word & 0x1) << 21, 22) << 2; +} + +/* Print one instruction. */ + +int +print_insn_hppa (bfd_vma memaddr, disassemble_info *info) +{ + bfd_byte buffer[4]; + unsigned int insn, i; + + { + int status = + (*info->read_memory_func) (memaddr, buffer, sizeof (buffer), info); + if (status != 0) + { + (*info->memory_error_func) (status, memaddr, info); + return -1; + } + } + + insn = bfd_getb32 (buffer); + + for (i = 0; i < NUMOPCODES; ++i) + { + const struct pa_opcode *opcode = &pa_opcodes[i]; + + if ((insn & opcode->mask) == opcode->match) + { + const char *s; +#ifndef BFD64 + if (opcode->arch == pa20w) + continue; +#endif + (*info->fprintf_func) (info->stream, "%s", opcode->name); + + if (!strchr ("cfCY?-+nHNZFIuv{", opcode->args[0])) + (*info->fprintf_func) (info->stream, " "); + for (s = opcode->args; *s != '\0'; ++s) + { + switch (*s) + { + case 'x': + fput_reg (GET_FIELD (insn, 11, 15), info); + break; + case 'a': + case 'b': + fput_reg (GET_FIELD (insn, 6, 10), info); + break; + case '^': + fput_creg (GET_FIELD (insn, 6, 10), info); + break; + case 't': + fput_reg (GET_FIELD (insn, 27, 31), info); + break; + + /* Handle floating point registers. */ + case 'f': + switch (*++s) + { + case 't': + fput_fp_reg (GET_FIELD (insn, 27, 31), info); + break; + case 'T': + if (GET_FIELD (insn, 25, 25)) + fput_fp_reg_r (GET_FIELD (insn, 27, 31), info); + else + fput_fp_reg (GET_FIELD (insn, 27, 31), info); + break; + case 'a': + if (GET_FIELD (insn, 25, 25)) + fput_fp_reg_r (GET_FIELD (insn, 6, 10), info); + else + fput_fp_reg (GET_FIELD (insn, 6, 10), info); + break; + + /* 'fA' will not generate a space before the regsiter + name. Normally that is fine. Except that it + causes problems with xmpyu which has no FP format + completer. */ + case 'X': + fputs_filtered (" ", info); + /* FALLTHRU */ + + case 'A': + if (GET_FIELD (insn, 24, 24)) + fput_fp_reg_r (GET_FIELD (insn, 6, 10), info); + else + fput_fp_reg (GET_FIELD (insn, 6, 10), info); + break; + case 'b': + if (GET_FIELD (insn, 25, 25)) + fput_fp_reg_r (GET_FIELD (insn, 11, 15), info); + else + fput_fp_reg (GET_FIELD (insn, 11, 15), info); + break; + case 'B': + if (GET_FIELD (insn, 19, 19)) + fput_fp_reg_r (GET_FIELD (insn, 11, 15), info); + else + fput_fp_reg (GET_FIELD (insn, 11, 15), info); + break; + case 'C': + { + int reg = GET_FIELD (insn, 21, 22); + reg |= GET_FIELD (insn, 16, 18) << 2; + if (GET_FIELD (insn, 23, 23) != 0) + fput_fp_reg_r (reg, info); + else + fput_fp_reg (reg, info); + break; + } + case 'i': + { + int reg = GET_FIELD (insn, 6, 10); + + reg |= (GET_FIELD (insn, 26, 26) << 4); + fput_fp_reg (reg, info); + break; + } + case 'j': + { + int reg = GET_FIELD (insn, 11, 15); + + reg |= (GET_FIELD (insn, 26, 26) << 4); + fput_fp_reg (reg, info); + break; + } + case 'k': + { + int reg = GET_FIELD (insn, 27, 31); + + reg |= (GET_FIELD (insn, 26, 26) << 4); + fput_fp_reg (reg, info); + break; + } + case 'l': + { + int reg = GET_FIELD (insn, 21, 25); + + reg |= (GET_FIELD (insn, 26, 26) << 4); + fput_fp_reg (reg, info); + break; + } + case 'm': + { + int reg = GET_FIELD (insn, 16, 20); + + reg |= (GET_FIELD (insn, 26, 26) << 4); + fput_fp_reg (reg, info); + break; + } + + /* 'fe' will not generate a space before the register + name. Normally that is fine. Except that it + causes problems with fstw fe,y(b) which has no FP + format completer. */ + case 'E': + fputs_filtered (" ", info); + /* FALLTHRU */ + + case 'e': + if (GET_FIELD (insn, 30, 30)) + fput_fp_reg_r (GET_FIELD (insn, 11, 15), info); + else + fput_fp_reg (GET_FIELD (insn, 11, 15), info); + break; + case 'x': + fput_fp_reg (GET_FIELD (insn, 11, 15), info); + break; + } + break; + + case '5': + fput_const (extract_5_load (insn), info); + break; + case 's': + { + int space = GET_FIELD (insn, 16, 17); + /* Zero means implicit addressing, not use of sr0. */ + if (space != 0) + (*info->fprintf_func) (info->stream, "sr%d", space); + } + break; + + case 'S': + (*info->fprintf_func) (info->stream, "sr%d", + extract_3 (insn)); + break; + + /* Handle completers. */ + case 'c': + switch (*++s) + { + case 'x': + (*info->fprintf_func) + (info->stream, "%s", + index_compl_names[GET_COMPL (insn)]); + break; + case 'X': + (*info->fprintf_func) + (info->stream, "%s ", + index_compl_names[GET_COMPL (insn)]); + break; + case 'm': + (*info->fprintf_func) + (info->stream, "%s", + short_ldst_compl_names[GET_COMPL (insn)]); + break; + case 'M': + (*info->fprintf_func) + (info->stream, "%s ", + short_ldst_compl_names[GET_COMPL (insn)]); + break; + case 'A': + (*info->fprintf_func) + (info->stream, "%s ", + short_bytes_compl_names[GET_COMPL (insn)]); + break; + case 's': + (*info->fprintf_func) + (info->stream, "%s", + short_bytes_compl_names[GET_COMPL (insn)]); + break; + case 'c': + case 'C': + switch (GET_FIELD (insn, 20, 21)) + { + case 1: + (*info->fprintf_func) (info->stream, ",bc "); + break; + case 2: + (*info->fprintf_func) (info->stream, ",sl "); + break; + default: + (*info->fprintf_func) (info->stream, " "); + } + break; + case 'd': + switch (GET_FIELD (insn, 20, 21)) + { + case 1: + (*info->fprintf_func) (info->stream, ",co "); + break; + default: + (*info->fprintf_func) (info->stream, " "); + } + break; + case 'o': + (*info->fprintf_func) (info->stream, ",o"); + break; + case 'g': + (*info->fprintf_func) (info->stream, ",gate"); + break; + case 'p': + (*info->fprintf_func) (info->stream, ",l,push"); + break; + case 'P': + (*info->fprintf_func) (info->stream, ",pop"); + break; + case 'l': + case 'L': + (*info->fprintf_func) (info->stream, ",l"); + break; + case 'w': + (*info->fprintf_func) + (info->stream, "%s ", + read_write_names[GET_FIELD (insn, 25, 25)]); + break; + case 'W': + (*info->fprintf_func) (info->stream, ",w "); + break; + case 'r': + if (GET_FIELD (insn, 23, 26) == 5) + (*info->fprintf_func) (info->stream, ",r"); + break; + case 'Z': + if (GET_FIELD (insn, 26, 26)) + (*info->fprintf_func) (info->stream, ",m "); + else + (*info->fprintf_func) (info->stream, " "); + break; + case 'i': + if (GET_FIELD (insn, 25, 25)) + (*info->fprintf_func) (info->stream, ",i"); + break; + case 'z': + if (!GET_FIELD (insn, 21, 21)) + (*info->fprintf_func) (info->stream, ",z"); + break; + case 'a': + (*info->fprintf_func) + (info->stream, "%s", + add_compl_names[GET_FIELD (insn, 20, 21)]); + break; + case 'Y': + (*info->fprintf_func) + (info->stream, ",dc%s", + add_compl_names[GET_FIELD (insn, 20, 21)]); + break; + case 'y': + (*info->fprintf_func) + (info->stream, ",c%s", + add_compl_names[GET_FIELD (insn, 20, 21)]); + break; + case 'v': + if (GET_FIELD (insn, 20, 20)) + (*info->fprintf_func) (info->stream, ",tsv"); + break; + case 't': + (*info->fprintf_func) (info->stream, ",tc"); + if (GET_FIELD (insn, 20, 20)) + (*info->fprintf_func) (info->stream, ",tsv"); + break; + case 'B': + (*info->fprintf_func) (info->stream, ",db"); + if (GET_FIELD (insn, 20, 20)) + (*info->fprintf_func) (info->stream, ",tsv"); + break; + case 'b': + (*info->fprintf_func) (info->stream, ",b"); + if (GET_FIELD (insn, 20, 20)) + (*info->fprintf_func) (info->stream, ",tsv"); + break; + case 'T': + if (GET_FIELD (insn, 25, 25)) + (*info->fprintf_func) (info->stream, ",tc"); + break; + case 'S': + /* EXTRD/W has a following condition. */ + if (*(s + 1) == '?') + (*info->fprintf_func) + (info->stream, "%s", + signed_unsigned_names[GET_FIELD (insn, 21, 21)]); + else + (*info->fprintf_func) + (info->stream, "%s ", + signed_unsigned_names[GET_FIELD (insn, 21, 21)]); + break; + case 'h': + (*info->fprintf_func) + (info->stream, "%s", + mix_half_names[GET_FIELD (insn, 17, 17)]); + break; + case 'H': + (*info->fprintf_func) + (info->stream, "%s ", + saturation_names[GET_FIELD (insn, 24, 25)]); + break; + case '*': + (*info->fprintf_func) + (info->stream, ",%d%d%d%d ", + GET_FIELD (insn, 17, 18), GET_FIELD (insn, 20, 21), + GET_FIELD (insn, 22, 23), GET_FIELD (insn, 24, 25)); + break; + + case 'q': + { + int m, a; + + m = GET_FIELD (insn, 28, 28); + a = GET_FIELD (insn, 29, 29); + + if (m && !a) + fputs_filtered (",ma ", info); + else if (m && a) + fputs_filtered (",mb ", info); + else + fputs_filtered (" ", info); + break; + } + + case 'J': + { + int opc = GET_FIELD (insn, 0, 5); + + if (opc == 0x16 || opc == 0x1e) + { + if (GET_FIELD (insn, 29, 29) == 0) + fputs_filtered (",ma ", info); + else + fputs_filtered (",mb ", info); + } + else + fputs_filtered (" ", info); + break; + } + + case 'e': + { + int opc = GET_FIELD (insn, 0, 5); + + if (opc == 0x13 || opc == 0x1b) + { + if (GET_FIELD (insn, 18, 18) == 1) + fputs_filtered (",mb ", info); + else + fputs_filtered (",ma ", info); + } + else if (opc == 0x17 || opc == 0x1f) + { + if (GET_FIELD (insn, 31, 31) == 1) + fputs_filtered (",ma ", info); + else + fputs_filtered (",mb ", info); + } + else + fputs_filtered (" ", info); + + break; + } + } + break; + + /* Handle conditions. */ + case '?': + { + s++; + switch (*s) + { + case 'f': + (*info->fprintf_func) + (info->stream, "%s ", + float_comp_names[GET_FIELD (insn, 27, 31)]); + break; + + /* These four conditions are for the set of instructions + which distinguish true/false conditions by opcode + rather than by the 'f' bit (sigh): comb, comib, + addb, addib. */ + case 't': + fputs_filtered + (compare_cond_names[GET_FIELD (insn, 16, 18)], info); + break; + case 'n': + fputs_filtered + (compare_cond_names[GET_FIELD (insn, 16, 18) + + GET_FIELD (insn, 4, 4) * 8], + info); + break; + case 'N': + fputs_filtered + (compare_cond_64_names[GET_FIELD (insn, 16, 18) + + GET_FIELD (insn, 2, 2) * 8], + info); + break; + case 'Q': + fputs_filtered + (cmpib_cond_64_names[GET_FIELD (insn, 16, 18)], + info); + break; + case '@': + fputs_filtered + (add_cond_names[GET_FIELD (insn, 16, 18) + + GET_FIELD (insn, 4, 4) * 8], + info); + break; + case 's': + (*info->fprintf_func) + (info->stream, "%s ", + compare_cond_names[GET_COND (insn)]); + break; + case 'S': + (*info->fprintf_func) + (info->stream, "%s ", + compare_cond_64_names[GET_COND (insn)]); + break; + case 'a': + (*info->fprintf_func) + (info->stream, "%s ", + add_cond_names[GET_COND (insn)]); + break; + case 'A': + (*info->fprintf_func) + (info->stream, "%s ", + add_cond_64_names[GET_COND (insn)]); + break; + case 'd': + (*info->fprintf_func) + (info->stream, "%s", + add_cond_names[GET_FIELD (insn, 16, 18)]); + break; + + case 'W': + (*info->fprintf_func) + (info->stream, "%s", + wide_add_cond_names[GET_FIELD (insn, 16, 18) + + GET_FIELD (insn, 4, 4) * 8]); + break; + + case 'l': + (*info->fprintf_func) + (info->stream, "%s ", + logical_cond_names[GET_COND (insn)]); + break; + case 'L': + (*info->fprintf_func) + (info->stream, "%s ", + logical_cond_64_names[GET_COND (insn)]); + break; + case 'u': + (*info->fprintf_func) + (info->stream, "%s ", + unit_cond_names[GET_COND (insn)]); + break; + case 'U': + (*info->fprintf_func) + (info->stream, "%s ", + unit_cond_64_names[GET_COND (insn)]); + break; + case 'y': + case 'x': + case 'b': + (*info->fprintf_func) + (info->stream, "%s", + shift_cond_names[GET_FIELD (insn, 16, 18)]); + + /* If the next character in args is 'n', it will handle + putting out the space. */ + if (s[1] != 'n') + (*info->fprintf_func) (info->stream, " "); + break; + case 'X': + (*info->fprintf_func) + (info->stream, "%s ", + shift_cond_64_names[GET_FIELD (insn, 16, 18)]); + break; + case 'B': + (*info->fprintf_func) + (info->stream, "%s", + bb_cond_64_names[GET_FIELD (insn, 16, 16)]); + + /* If the next character in args is 'n', it will handle + putting out the space. */ + if (s[1] != 'n') + (*info->fprintf_func) (info->stream, " "); + break; + } + break; + } + + case 'V': + fput_const (extract_5_store (insn), info); + break; + case 'r': + fput_const (extract_5r_store (insn), info); + break; + case 'R': + fput_const (extract_5R_store (insn), info); + break; + case 'U': + fput_const (extract_10U_store (insn), info); + break; + case 'B': + case 'Q': + fput_const (extract_5Q_store (insn), info); + break; + case 'i': + fput_const (extract_11 (insn), info); + break; + case 'j': + fput_const (extract_14 (insn), info); + break; + case 'k': + fputs_filtered ("L%", info); + fput_const (extract_21 (insn), info); + break; + case '<': + case 'l': + /* 16-bit long disp., PA2.0 wide only. */ + fput_const (extract_16 (insn), info); + break; + case 'n': + if (insn & 0x2) + (*info->fprintf_func) (info->stream, ",n "); + else + (*info->fprintf_func) (info->stream, " "); + break; + case 'N': + if ((insn & 0x20) && s[1]) + (*info->fprintf_func) (info->stream, ",n "); + else if (insn & 0x20) + (*info->fprintf_func) (info->stream, ",n"); + else if (s[1]) + (*info->fprintf_func) (info->stream, " "); + break; + case 'w': + (*info->print_address_func) + (memaddr + 8 + extract_12 (insn), info); + break; + case 'W': + /* 17 bit PC-relative branch. */ + (*info->print_address_func) + ((memaddr + 8 + extract_17 (insn)), info); + break; + case 'z': + /* 17 bit displacement. This is an offset from a register + so it gets disasssembled as just a number, not any sort + of address. */ + fput_const (extract_17 (insn), info); + break; + + case 'Z': + /* addil %r1 implicit output. */ + fputs_filtered ("r1", info); + break; + + case 'Y': + /* be,l %sr0,%r31 implicit output. */ + fputs_filtered ("sr0,r31", info); + break; + + case '@': + (*info->fprintf_func) (info->stream, "0"); + break; + + case '.': + (*info->fprintf_func) (info->stream, "%d", + GET_FIELD (insn, 24, 25)); + break; + case '*': + (*info->fprintf_func) (info->stream, "%d", + GET_FIELD (insn, 22, 25)); + break; + case '!': + fputs_filtered ("sar", info); + break; + case 'p': + (*info->fprintf_func) (info->stream, "%d", + 31 - GET_FIELD (insn, 22, 26)); + break; + case '~': + { + int num; + num = GET_FIELD (insn, 20, 20) << 5; + num |= GET_FIELD (insn, 22, 26); + (*info->fprintf_func) (info->stream, "%d", 63 - num); + break; + } + case 'P': + (*info->fprintf_func) (info->stream, "%d", + GET_FIELD (insn, 22, 26)); + break; + case 'q': + { + int num; + num = GET_FIELD (insn, 20, 20) << 5; + num |= GET_FIELD (insn, 22, 26); + (*info->fprintf_func) (info->stream, "%d", num); + break; + } + case 'T': + (*info->fprintf_func) (info->stream, "%d", + 32 - GET_FIELD (insn, 27, 31)); + break; + case '%': + { + int num; + num = (GET_FIELD (insn, 23, 23) + 1) * 32; + num -= GET_FIELD (insn, 27, 31); + (*info->fprintf_func) (info->stream, "%d", num); + break; + } + case '|': + { + int num; + num = (GET_FIELD (insn, 19, 19) + 1) * 32; + num -= GET_FIELD (insn, 27, 31); + (*info->fprintf_func) (info->stream, "%d", num); + break; + } + case '$': + fput_const (GET_FIELD (insn, 20, 28), info); + break; + case 'A': + fput_const (GET_FIELD (insn, 6, 18), info); + break; + case 'D': + fput_const (GET_FIELD (insn, 6, 31), info); + break; + case 'v': + (*info->fprintf_func) (info->stream, ",%d", + GET_FIELD (insn, 23, 25)); + break; + case 'O': + fput_const ((GET_FIELD (insn, 6,20) << 5 | + GET_FIELD (insn, 27, 31)), info); + break; + case 'o': + fput_const (GET_FIELD (insn, 6, 20), info); + break; + case '2': + fput_const ((GET_FIELD (insn, 6, 22) << 5 | + GET_FIELD (insn, 27, 31)), info); + break; + case '1': + fput_const ((GET_FIELD (insn, 11, 20) << 5 | + GET_FIELD (insn, 27, 31)), info); + break; + case '0': + fput_const ((GET_FIELD (insn, 16, 20) << 5 | + GET_FIELD (insn, 27, 31)), info); + break; + case 'u': + (*info->fprintf_func) (info->stream, ",%d", + GET_FIELD (insn, 23, 25)); + break; + case 'F': + /* If no destination completer and not before a completer + for fcmp, need a space here. */ + if (s[1] == 'G' || s[1] == '?') + fputs_filtered + (float_format_names[GET_FIELD (insn, 19, 20)], info); + else + (*info->fprintf_func) + (info->stream, "%s ", + float_format_names[GET_FIELD (insn, 19, 20)]); + break; + case 'G': + (*info->fprintf_func) + (info->stream, "%s ", + float_format_names[GET_FIELD (insn, 17, 18)]); + break; + case 'H': + if (GET_FIELD (insn, 26, 26) == 1) + (*info->fprintf_func) (info->stream, "%s ", + float_format_names[0]); + else + (*info->fprintf_func) (info->stream, "%s ", + float_format_names[1]); + break; + case 'I': + /* If no destination completer and not before a completer + for fcmp, need a space here. */ + if (s[1] == '?') + fputs_filtered + (float_format_names[GET_FIELD (insn, 20, 20)], info); + else + (*info->fprintf_func) + (info->stream, "%s ", + float_format_names[GET_FIELD (insn, 20, 20)]); + break; + + case 'J': + fput_const (extract_14 (insn), info); + break; + + case '#': + { + int sign = GET_FIELD (insn, 31, 31); + int imm10 = GET_FIELD (insn, 18, 27); + int disp; + + if (sign) + disp = (-1 << 10) | imm10; + else + disp = imm10; + + disp <<= 3; + fput_const (disp, info); + break; + } + case 'K': + case 'd': + { + int sign = GET_FIELD (insn, 31, 31); + int imm11 = GET_FIELD (insn, 18, 28); + int disp; + + if (sign) + disp = (-1 << 11) | imm11; + else + disp = imm11; + + disp <<= 2; + fput_const (disp, info); + break; + } + + case '>': + case 'y': + { + /* 16-bit long disp., PA2.0 wide only. */ + int disp = extract_16 (insn); + disp &= ~3; + fput_const (disp, info); + break; + } + + case '&': + { + /* 16-bit long disp., PA2.0 wide only. */ + int disp = extract_16 (insn); + disp &= ~7; + fput_const (disp, info); + break; + } + + case '_': + break; /* Dealt with by '{' */ + + case '{': + { + int sub = GET_FIELD (insn, 14, 16); + int df = GET_FIELD (insn, 17, 18); + int sf = GET_FIELD (insn, 19, 20); + const char * const * source = float_format_names; + const char * const * dest = float_format_names; + const char *t = ""; + + if (sub == 4) + { + fputs_filtered (",UND ", info); + break; + } + if ((sub & 3) == 3) + t = ",t"; + if ((sub & 3) == 1) + source = sub & 4 ? fcnv_ufixed_names : fcnv_fixed_names; + if (sub & 2) + dest = sub & 4 ? fcnv_ufixed_names : fcnv_fixed_names; + + (*info->fprintf_func) (info->stream, "%s%s%s ", + t, source[sf], dest[df]); + break; + } + + case 'm': + { + int y = GET_FIELD (insn, 16, 18); + + if (y != 1) + fput_const ((y ^ 1) - 1, info); + } + break; + + case 'h': + { + int cbit; + + cbit = GET_FIELD (insn, 16, 18); + + if (cbit > 0) + (*info->fprintf_func) (info->stream, ",%d", cbit - 1); + break; + } + + case '=': + { + int cond = GET_FIELD (insn, 27, 31); + + switch (cond) + { + case 0: fputs_filtered (" ", info); break; + case 1: fputs_filtered ("acc ", info); break; + case 2: fputs_filtered ("rej ", info); break; + case 5: fputs_filtered ("acc8 ", info); break; + case 6: fputs_filtered ("rej8 ", info); break; + case 9: fputs_filtered ("acc6 ", info); break; + case 13: fputs_filtered ("acc4 ", info); break; + case 17: fputs_filtered ("acc2 ", info); break; + default: break; + } + break; + } + + case 'X': + (*info->print_address_func) + (memaddr + 8 + extract_22 (insn), info); + break; + case 'L': + fputs_filtered (",rp", info); + break; + default: + (*info->fprintf_func) (info->stream, "%c", *s); + break; + } + } + return sizeof (insn); + } + } + (*info->fprintf_func) (info->stream, "#%8x", insn); + return sizeof (insn); +} diff --git a/hppa.ld b/hppa.ld new file mode 100644 index 0000000..9a4b22c --- /dev/null +++ b/hppa.ld @@ -0,0 +1,213 @@ +/* Default linker script, for normal executables */ +OUTPUT_FORMAT("elf32-hppa-linux", "elf32-hppa-linux", + "elf32-hppa-linux") +OUTPUT_ARCH(hppa:hppa1.1) +ENTRY(_start) +SECTIONS +{ + /* Read-only sections, merged into text segment: */ + PROVIDE (__executable_start = 0x60000000); . = 0x60000000 + SIZEOF_HEADERS; + .interp : { *(.interp) } + .hash : { *(.hash) } + .dynsym : { *(.dynsym) } + .dynstr : { *(.dynstr) } + .gnu.version : { *(.gnu.version) } + .gnu.version_d : { *(.gnu.version_d) } + .gnu.version_r : { *(.gnu.version_r) } + .rel.init : { *(.rel.init) } + .rela.init : { *(.rela.init) } + .rel.text : { *(.rel.text .rel.text.* .rel.gnu.linkonce.t.*) } + .rela.text : { *(.rela.text .rela.text.* .rela.gnu.linkonce.t.*) } + .rel.fini : { *(.rel.fini) } + .rela.fini : { *(.rela.fini) } + .rel.rodata : { *(.rel.rodata .rel.rodata.* .rel.gnu.linkonce.r.*) } + .rela.rodata : { *(.rela.rodata .rela.rodata.* .rela.gnu.linkonce.r.*) } + .rel.data.rel.ro : { *(.rel.data.rel.ro* .rel.gnu.linkonce.d.rel.ro.*) } + .rela.data.rel.ro : { *(.rela.data.rel.ro* .rela.gnu.linkonce.d.rel.ro.*) } + .rel.data : { *(.rel.data .rel.data.* .rel.gnu.linkonce.d.*) } + .rela.data : { *(.rela.data .rela.data.* .rela.gnu.linkonce.d.*) } + .rel.tdata : { *(.rel.tdata .rel.tdata.* .rel.gnu.linkonce.td.*) } + .rela.tdata : { *(.rela.tdata .rela.tdata.* .rela.gnu.linkonce.td.*) } + .rel.tbss : { *(.rel.tbss .rel.tbss.* .rel.gnu.linkonce.tb.*) } + .rela.tbss : { *(.rela.tbss .rela.tbss.* .rela.gnu.linkonce.tb.*) } + .rel.ctors : { *(.rel.ctors) } + .rela.ctors : { *(.rela.ctors) } + .rel.dtors : { *(.rel.dtors) } + .rela.dtors : { *(.rela.dtors) } + .rel.got : { *(.rel.got) } + .rela.got : { *(.rela.got) } + .rel.sdata : { *(.rel.sdata .rel.sdata.* .rel.gnu.linkonce.s.*) } + .rela.sdata : { *(.rela.sdata .rela.sdata.* .rela.gnu.linkonce.s.*) } + .rel.sbss : { *(.rel.sbss .rel.sbss.* .rel.gnu.linkonce.sb.*) } + .rela.sbss : { *(.rela.sbss .rela.sbss.* .rela.gnu.linkonce.sb.*) } + .rel.sdata2 : { *(.rel.sdata2 .rel.sdata2.* .rel.gnu.linkonce.s2.*) } + .rela.sdata2 : { *(.rela.sdata2 .rela.sdata2.* .rela.gnu.linkonce.s2.*) } + .rel.sbss2 : { *(.rel.sbss2 .rel.sbss2.* .rel.gnu.linkonce.sb2.*) } + .rela.sbss2 : { *(.rela.sbss2 .rela.sbss2.* .rela.gnu.linkonce.sb2.*) } + .rel.bss : { *(.rel.bss .rel.bss.* .rel.gnu.linkonce.b.*) } + .rela.bss : { *(.rela.bss .rela.bss.* .rela.gnu.linkonce.b.*) } + .rel.plt : { *(.rel.plt) } + .rela.plt : { *(.rela.plt) } + .init : + { + KEEP (*(.init)) + } =0x08000240 + .text : + { + *(.text .stub .text.* .gnu.linkonce.t.*) + KEEP (*(.text.*personality*)) + /* .gnu.warning sections are handled specially by elf32.em. */ + *(.gnu.warning) + } =0x08000240 + .fini : + { + KEEP (*(.fini)) + } =0x08000240 + PROVIDE (__etext = .); + PROVIDE (_etext = .); + PROVIDE (etext = .); + .rodata : { *(.rodata .rodata.* .gnu.linkonce.r.*) } + .rodata1 : { *(.rodata1) } + .sdata2 : + { + *(.sdata2 .sdata2.* .gnu.linkonce.s2.*) + } + .sbss2 : { *(.sbss2 .sbss2.* .gnu.linkonce.sb2.*) } + .PARISC.unwind : { *(.PARISC.unwind) } + .eh_frame_hdr : { *(.eh_frame_hdr) } + .eh_frame : ONLY_IF_RO { KEEP (*(.eh_frame)) } + .gcc_except_table : ONLY_IF_RO { *(.gcc_except_table .gcc_except_table.*) } + /* Adjust the address for the data segment. We want to adjust up to + the same address within the page on the next page up. */ + . = ALIGN(0x10000) + (. & (0x10000 - 1)); + /* Exception handling */ + .eh_frame : ONLY_IF_RW { KEEP (*(.eh_frame)) } + .gcc_except_table : ONLY_IF_RW { *(.gcc_except_table .gcc_except_table.*) } + /* Thread Local Storage sections */ + .tdata : { *(.tdata .tdata.* .gnu.linkonce.td.*) } + .tbss : { *(.tbss .tbss.* .gnu.linkonce.tb.*) *(.tcommon) } + .preinit_array : + { + PROVIDE_HIDDEN (__preinit_array_start = .); + KEEP (*(.preinit_array)) + PROVIDE_HIDDEN (__preinit_array_end = .); + } + .init_array : + { + PROVIDE_HIDDEN (__init_array_start = .); + KEEP (*(SORT(.init_array.*))) + KEEP (*(.init_array)) + PROVIDE_HIDDEN (__init_array_end = .); + } + .fini_array : + { + PROVIDE_HIDDEN (__fini_array_start = .); + KEEP (*(.fini_array)) + KEEP (*(SORT(.fini_array.*))) + PROVIDE_HIDDEN (__fini_array_end = .); + } + .ctors : + { + /* gcc uses crtbegin.o to find the start of + the constructors, so we make sure it is + first. Because this is a wildcard, it + doesn't matter if the user does not + actually link against crtbegin.o; the + linker won't look for a file to match a + wildcard. The wildcard also means that it + doesn't matter which directory crtbegin.o + is in. */ + KEEP (*crtbegin*.o(.ctors)) + /* We don't want to include the .ctor section from + the crtend.o file until after the sorted ctors. + The .ctor section from the crtend file contains the + end of ctors marker and it must be last */ + KEEP (*(EXCLUDE_FILE (*crtend*.o ) .ctors)) + KEEP (*(SORT(.ctors.*))) + KEEP (*(.ctors)) + } + .dtors : + { + KEEP (*crtbegin*.o(.dtors)) + KEEP (*(EXCLUDE_FILE (*crtend*.o ) .dtors)) + KEEP (*(SORT(.dtors.*))) + KEEP (*(.dtors)) + } + .jcr : { KEEP (*(.jcr)) } + .data.rel.ro : { *(.data.rel.ro.local* .gnu.linkonce.d.rel.ro.local.*) *(.data.rel.ro* .gnu.linkonce.d.rel.ro.*) } + .dynamic : { *(.dynamic) } + .data : + { + PROVIDE ($global$ = .); + *(.data .data.* .gnu.linkonce.d.*) + KEEP (*(.gnu.linkonce.d.*personality*)) + SORT(CONSTRUCTORS) + } + .data1 : { *(.data1) } + .plt : { *(.plt) } + .got : { *(.got.plt) *(.got) } + /* We want the small data sections together, so single-instruction offsets + can access them all, and initialized data all before uninitialized, so + we can shorten the on-disk segment size. */ + .sdata : + { + *(.sdata .sdata.* .gnu.linkonce.s.*) + } + _edata = .; PROVIDE (edata = .); + __bss_start = .; + .sbss : + { + *(.dynsbss) + *(.sbss .sbss.* .gnu.linkonce.sb.*) + *(.scommon) + } + .bss : + { + *(.dynbss) + *(.bss .bss.* .gnu.linkonce.b.*) + *(COMMON) + /* Align here to ensure that the .bss section occupies space up to + _end. Align after .bss to ensure correct alignment even if the + .bss section disappears because there are no input sections. + FIXME: Why do we need it? When there is no .bss section, we don't + pad the .data section. */ + . = ALIGN(. != 0 ? 32 / 8 : 1); + } + . = ALIGN(32 / 8); + . = ALIGN(32 / 8); + _end = .; PROVIDE (end = .); + /* Stabs debugging sections. */ + .stab 0 : { *(.stab) } + .stabstr 0 : { *(.stabstr) } + .stab.excl 0 : { *(.stab.excl) } + .stab.exclstr 0 : { *(.stab.exclstr) } + .stab.index 0 : { *(.stab.index) } + .stab.indexstr 0 : { *(.stab.indexstr) } + .comment 0 : { *(.comment) } + /* DWARF debug sections. + Symbols in the DWARF debugging sections are relative to the beginning + of the section so we begin them at 0. */ + /* DWARF 1 */ + .debug 0 : { *(.debug) } + .line 0 : { *(.line) } + /* GNU DWARF 1 extensions */ + .debug_srcinfo 0 : { *(.debug_srcinfo) } + .debug_sfnames 0 : { *(.debug_sfnames) } + /* DWARF 1.1 and DWARF 2 */ + .debug_aranges 0 : { *(.debug_aranges) } + .debug_pubnames 0 : { *(.debug_pubnames) } + /* DWARF 2 */ + .debug_info 0 : { *(.debug_info .gnu.linkonce.wi.*) } + .debug_abbrev 0 : { *(.debug_abbrev) } + .debug_line 0 : { *(.debug_line) } + .debug_frame 0 : { *(.debug_frame) } + .debug_str 0 : { *(.debug_str) } + .debug_loc 0 : { *(.debug_loc) } + .debug_macinfo 0 : { *(.debug_macinfo) } + /* SGI/MIPS DWARF 2 extensions */ + .debug_weaknames 0 : { *(.debug_weaknames) } + .debug_funcnames 0 : { *(.debug_funcnames) } + .debug_typenames 0 : { *(.debug_typenames) } + .debug_varnames 0 : { *(.debug_varnames) } + /DISCARD/ : { *(.note.GNU-stack) } +} diff --git a/hw/9p.h b/hw/9p.h new file mode 100644 index 0000000..d9951d6 --- /dev/null +++ b/hw/9p.h @@ -0,0 +1,24 @@ +/* + * Virtio 9p + * + * Copyright IBM, Corp. 2010 + * + * Authors: + * Aneesh Kumar K.V + * + * This work is licensed under the terms of the GNU GPL, version 2. See + * the COPYING file in the top-level directory. + * + */ + +#ifndef QEMU_9P_H +#define QEMU_9P_H + +typedef struct V9fsConf +{ + /* tag name for the device */ + char *tag; + char *fsdev_id; +} V9fsConf; + +#endif diff --git a/hw/a9mpcore.c b/hw/a9mpcore.c new file mode 100644 index 0000000..b5e5328 --- /dev/null +++ b/hw/a9mpcore.c @@ -0,0 +1,29 @@ +/* + * Cortex-A9MPCore internal peripheral emulation. + * + * Copyright (c) 2009 CodeSourcery. + * Written by Paul Brook + * + * This code is licenced under the GPL. + */ + +/* 64 external IRQ lines. */ +#define GIC_NIRQ 96 +#include "mpcore.c" + +static SysBusDeviceInfo mpcore_priv_info = { + .init = mpcore_priv_init, + .qdev.name = "a9mpcore_priv", + .qdev.size = sizeof(mpcore_priv_state), + .qdev.props = (Property[]) { + DEFINE_PROP_UINT32("num-cpu", mpcore_priv_state, num_cpu, 1), + DEFINE_PROP_END_OF_LIST(), + } +}; + +static void a9mpcore_register_devices(void) +{ + sysbus_register_withprop(&mpcore_priv_info); +} + +device_init(a9mpcore_register_devices) diff --git a/hw/ac97.c b/hw/ac97.c new file mode 100644 index 0000000..d71072d --- /dev/null +++ b/hw/ac97.c @@ -0,0 +1,1351 @@ +/* + * 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. + */ + +#include "hw.h" +#include "audiodev.h" +#include "audio/audio.h" +#include "pci.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_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 VOL_MASK 0x1f +#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 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]; + uint32_t base[2]; + int bup_flag; +} 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]; + + cpu_physical_memory_read (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"); + qemu_set_irq (s->dev.irq[0], 1); + } + else { + s->glob_sta &= ~masks[r - s->bm_regs]; + dolog ("set irq level=0\n"); + qemu_set_irq (s->dev.irq[0], 0); + } +} + +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_store: 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]); +} + +#ifdef USE_MIXER +static void set_volume (AC97LinkState *s, int index, + audmixerctl_t mt, uint32_t val) +{ + int mute = (val >> MUTE_SHIFT) & 1; + uint8_t rvol = VOL_MASK - (val & VOL_MASK); + uint8_t lvol = VOL_MASK - ((val >> 8) & VOL_MASK); + rvol = 255 * rvol / VOL_MASK; + lvol = 255 * lvol / VOL_MASK; + +#ifdef SOFT_VOLUME + if (index == AC97_Master_Volume_Mute) { + AUD_set_volume_out (s->voice_po, mute, lvol, rvol); + } + else { + AUD_set_volume (mt, &mute, &lvol, &rvol); + } +#else + AUD_set_volume (mt, &mute, &lvol, &rvol); +#endif + + rvol = VOL_MASK - ((VOL_MASK * rvol) / 255); + lvol = VOL_MASK - ((VOL_MASK * lvol) / 255); + mixer_store (s, index, val); +} + +static audrecsource_t ac97_to_aud_record_source (uint8_t i) +{ + switch (i) { + case REC_MIC: + return AUD_REC_MIC; + + case REC_CD: + return AUD_REC_CD; + + case REC_VIDEO: + return AUD_REC_VIDEO; + + case REC_AUX: + return AUD_REC_AUX; + + case REC_LINE_IN: + return AUD_REC_LINE_IN; + + case REC_PHONE: + return AUD_REC_PHONE; + + default: + dolog ("Unknown record source %d, using MIC\n", i); + return AUD_REC_MIC; + } +} + +static uint8_t aud_to_ac97_record_source (audrecsource_t rs) +{ + switch (rs) { + case AUD_REC_MIC: + return REC_MIC; + + case AUD_REC_CD: + return REC_CD; + + case AUD_REC_VIDEO: + return REC_VIDEO; + + case AUD_REC_AUX: + return REC_AUX; + + case AUD_REC_LINE_IN: + return REC_LINE_IN; + + case AUD_REC_PHONE: + return REC_PHONE; + + default: + dolog ("Unknown audio recording source %d using MIC\n", rs); + return REC_MIC; + } +} + +static void record_select (AC97LinkState *s, uint32_t val) +{ + uint8_t rs = val & REC_MASK; + uint8_t ls = (val >> 8) & REC_MASK; + audrecsource_t ars = ac97_to_aud_record_source (rs); + audrecsource_t als = ac97_to_aud_record_source (ls); + AUD_set_record_source (&als, &ars); + rs = aud_to_ac97_record_source (ars); + ls = aud_to_ac97_record_source (als); + mixer_store (s, AC97_Record_Select, rs | (ls << 8)); +} +#endif + +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_Master_Volume_Mono_Mute , 0x8000); + mixer_store (s, AC97_PC_BEEP_Volume_Mute , 0x0000); + + mixer_store (s, AC97_Phone_Volume_Mute , 0x8008); + mixer_store (s, AC97_Mic_Volume_Mute , 0x8008); + mixer_store (s, AC97_CD_Volume_Mute , 0x8808); + mixer_store (s, AC97_Aux_Volume_Mute , 0x8808); + mixer_store (s, AC97_Record_Gain_Mic_Mute , 0x8000); + 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); + +#ifdef USE_MIXER + record_select (s, 0); + set_volume (s, AC97_Master_Volume_Mute, AUD_MIXER_VOLUME , 0x8000); + set_volume (s, AC97_PCM_Out_Volume_Mute, AUD_MIXER_PCM , 0x8808); + set_volume (s, AC97_Line_In_Volume_Mute, AUD_MIXER_LINE_IN, 0x8808); +#endif + 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->base[0]; + 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->base[0]; + s->cas = 0; + switch (index) { + case AC97_Reset: + mixer_reset (s); + break; + case AC97_Powerdown_Ctrl_Stat: + val &= ~0xf; + val |= mixer_load (s, index) & 0xf; + mixer_store (s, index, val); + break; +#ifdef USE_MIXER + case AC97_Master_Volume_Mute: + set_volume (s, index, AUD_MIXER_VOLUME, val); + break; + case AC97_PCM_Out_Volume_Mute: + set_volume (s, index, AUD_MIXER_PCM, val); + break; + case AC97_Line_In_Volume_Mute: + set_volume (s, index, AUD_MIXER_LINE_IN, val); + break; + case AC97_Record_Select: + record_select (s, val); + break; +#endif + 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; + 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 - s->base[1]; + 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 - s->base[1]; + 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 - s->base[1]; + 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 - s->base[1]; + 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 - s->base[1]; + 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 - s->base[1]; + 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)); + cpu_physical_memory_read (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) +{ + int written = 0; + + 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; + written += 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; + } + cpu_physical_memory_write (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 written = 0, 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); + written += temp; + 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, + .minimum_version_id_old = 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; + +#ifdef USE_MIXER + record_select (s, mixer_load (s, AC97_Record_Select)); +#define V_(a, b) set_volume (s, a, b, mixer_load (s, a)) + V_ (AC97_Master_Volume_Mute, AUD_MIXER_VOLUME); + V_ (AC97_PCM_Out_Volume_Mute, AUD_MIXER_PCM); + V_ (AC97_Line_In_Volume_Mute, AUD_MIXER_LINE_IN); +#undef V_ +#endif + 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, + .minimum_version_id_old = 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 void ac97_map (PCIDevice *pci_dev, int region_num, + pcibus_t addr, pcibus_t size, int type) +{ + AC97LinkState *s = DO_UPCAST (AC97LinkState, dev, pci_dev); + PCIDevice *d = &s->dev; + + if (!region_num) { + s->base[0] = addr; + register_ioport_read (addr, 256 * 1, 1, nam_readb, d); + register_ioport_read (addr, 256 * 2, 2, nam_readw, d); + register_ioport_read (addr, 256 * 4, 4, nam_readl, d); + register_ioport_write (addr, 256 * 1, 1, nam_writeb, d); + register_ioport_write (addr, 256 * 2, 2, nam_writew, d); + register_ioport_write (addr, 256 * 4, 4, nam_writel, d); + } + else { + s->base[1] = addr; + register_ioport_read (addr, 64 * 1, 1, nabm_readb, d); + register_ioport_read (addr, 64 * 2, 2, nabm_readw, d); + register_ioport_read (addr, 64 * 4, 4, nabm_readl, d); + register_ioport_write (addr, 64 * 1, 1, nabm_writeb, d); + register_ioport_write (addr, 64 * 2, 2, nabm_writew, d); + register_ioport_write (addr, 64 * 4, 4, nabm_writel, d); + } +} + +static void ac97_on_reset (void *opaque) +{ + AC97LinkState *s = opaque; + + 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 int ac97_initfn (PCIDevice *dev) +{ + AC97LinkState *s = DO_UPCAST (AC97LinkState, dev, dev); + uint8_t *c = s->dev.config; + + pci_config_set_vendor_id (c, PCI_VENDOR_ID_INTEL); /* ro */ + pci_config_set_device_id (c, PCI_DEVICE_ID_INTEL_82801AA_5); /* ro */ + + /* 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_REVISION_ID] = 0x01; /* rid revision ro */ + c[PCI_CLASS_PROG] = 0x00; /* pi programming interface ro */ + pci_config_set_class (c, PCI_CLASS_MULTIMEDIA_AUDIO); /* 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; + + c[PCI_SUBSYSTEM_VENDOR_ID] = 0x86; /* svid subsystem vendor id rwo */ + c[PCI_SUBSYSTEM_VENDOR_ID + 1] = 0x80; + + c[PCI_SUBSYSTEM_ID] = 0x00; /* sid subsystem id rwo */ + c[PCI_SUBSYSTEM_ID + 1] = 0x00; + + c[PCI_INTERRUPT_LINE] = 0x00; /* intr_ln interrupt line rw */ + /* TODO: RST# value should be 0. */ + c[PCI_INTERRUPT_PIN] = 0x01; /* intr_pn interrupt pin ro */ + + pci_register_bar (&s->dev, 0, 256 * 4, PCI_BASE_ADDRESS_SPACE_IO, + ac97_map); + pci_register_bar (&s->dev, 1, 64 * 4, PCI_BASE_ADDRESS_SPACE_IO, ac97_map); + qemu_register_reset (ac97_on_reset, s); + AUD_register_card ("ac97", &s->card); + ac97_on_reset (s); + return 0; +} + +int ac97_init (PCIBus *bus) +{ + pci_create_simple (bus, -1, "AC97"); + return 0; +} + +static PCIDeviceInfo ac97_info = { + .qdev.name = "AC97", + .qdev.desc = "Intel 82801AA AC97 Audio", + .qdev.size = sizeof (AC97LinkState), + .qdev.vmsd = &vmstate_ac97, + .init = ac97_initfn, +}; + +static void ac97_register (void) +{ + pci_qdev_register (&ac97_info); +} +device_init (ac97_register); + diff --git a/hw/acpi.c b/hw/acpi.c new file mode 100644 index 0000000..8071e7b --- /dev/null +++ b/hw/acpi.c @@ -0,0 +1,199 @@ +/* + * 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 + */ +#include "hw.h" +#include "pc.h" +#include "acpi.h" + +struct acpi_table_header +{ + char signature [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 */ +} __attribute__((packed)); + +char *acpi_tables; +size_t acpi_tables_len; + +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; +} + +int acpi_table_add(const char *t) +{ + static const char *dfl_id = "QEMUQEMU"; + char buf[1024], *p, *f; + struct acpi_table_header acpi_hdr; + unsigned long val; + uint32_t length; + struct acpi_table_header *acpi_hdr_p; + size_t off; + + memset(&acpi_hdr, 0, sizeof(acpi_hdr)); + + if (get_param_value(buf, sizeof(buf), "sig", t)) { + strncpy(acpi_hdr.signature, buf, 4); + } else { + strncpy(acpi_hdr.signature, dfl_id, 4); + } + if (get_param_value(buf, sizeof(buf), "rev", t)) { + val = strtoul(buf, &p, 10); + if (val > 255 || *p != '\0') + goto out; + } else { + val = 1; + } + acpi_hdr.revision = (int8_t)val; + + if (get_param_value(buf, sizeof(buf), "oem_id", t)) { + strncpy(acpi_hdr.oem_id, buf, 6); + } else { + strncpy(acpi_hdr.oem_id, dfl_id, 6); + } + + if (get_param_value(buf, sizeof(buf), "oem_table_id", t)) { + strncpy(acpi_hdr.oem_table_id, buf, 8); + } else { + strncpy(acpi_hdr.oem_table_id, dfl_id, 8); + } + + if (get_param_value(buf, sizeof(buf), "oem_rev", t)) { + val = strtol(buf, &p, 10); + if(*p != '\0') + goto out; + } else { + val = 1; + } + acpi_hdr.oem_revision = cpu_to_le32(val); + + if (get_param_value(buf, sizeof(buf), "asl_compiler_id", t)) { + strncpy(acpi_hdr.asl_compiler_id, buf, 4); + } else { + strncpy(acpi_hdr.asl_compiler_id, dfl_id, 4); + } + + if (get_param_value(buf, sizeof(buf), "asl_compiler_rev", t)) { + val = strtol(buf, &p, 10); + if(*p != '\0') + goto out; + } else { + val = 1; + } + acpi_hdr.asl_compiler_revision = cpu_to_le32(val); + + if (!get_param_value(buf, sizeof(buf), "data", t)) { + buf[0] = '\0'; + } + + length = sizeof(acpi_hdr); + + f = buf; + while (buf[0]) { + struct stat s; + char *n = strchr(f, ':'); + if (n) + *n = '\0'; + if(stat(f, &s) < 0) { + fprintf(stderr, "Can't stat file '%s': %s\n", f, strerror(errno)); + goto out; + } + length += s.st_size; + if (!n) + break; + *n = ':'; + f = n + 1; + } + + if (!acpi_tables) { + acpi_tables_len = sizeof(uint16_t); + acpi_tables = qemu_mallocz(acpi_tables_len); + } + acpi_tables = qemu_realloc(acpi_tables, + acpi_tables_len + sizeof(uint16_t) + length); + p = acpi_tables + acpi_tables_len; + acpi_tables_len += sizeof(uint16_t) + length; + + *(uint16_t*)p = cpu_to_le32(length); + p += sizeof(uint16_t); + memcpy(p, &acpi_hdr, sizeof(acpi_hdr)); + off = sizeof(acpi_hdr); + + f = buf; + while (buf[0]) { + struct stat s; + int fd; + char *n = strchr(f, ':'); + if (n) + *n = '\0'; + fd = open(f, O_RDONLY); + + if(fd < 0) + goto out; + if(fstat(fd, &s) < 0) { + close(fd); + goto out; + } + + /* off < length is necessary because file size can be changed + under our foot */ + while(s.st_size && off < length) { + int r; + r = read(fd, p + off, s.st_size); + if (r > 0) { + off += r; + s.st_size -= r; + } else if ((r < 0 && errno != EINTR) || r == 0) { + close(fd); + goto out; + } + } + + close(fd); + if (!n) + break; + f = n + 1; + } + if (off < length) { + /* don't pass random value in process to guest */ + memset(p + off, 0, length - off); + } + + acpi_hdr_p = (struct acpi_table_header*)p; + acpi_hdr_p->length = cpu_to_le32(length); + acpi_hdr_p->checksum = acpi_checksum((uint8_t*)p, length); + /* increase number of tables */ + (*(uint16_t*)acpi_tables) = + cpu_to_le32(le32_to_cpu(*(uint16_t*)acpi_tables) + 1); + return 0; +out: + if (acpi_tables) { + qemu_free(acpi_tables); + acpi_tables = NULL; + } + return -1; +} diff --git a/hw/acpi.h b/hw/acpi.h new file mode 100644 index 0000000..5949958 --- /dev/null +++ b/hw/acpi.h @@ -0,0 +1,78 @@ +#ifndef QEMU_HW_ACPI_H +#define QEMU_HW_ACPI_H +/* + * Copyright (c) 2009 Isaku Yamahata + * VA Linux Systems Japan K.K. + * + * 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 + * . + */ + +/* from linux include/acpi/actype.h */ +/* Default ACPI register widths */ + +#define ACPI_GPE_REGISTER_WIDTH 8 +#define ACPI_PM1_REGISTER_WIDTH 16 +#define ACPI_PM2_REGISTER_WIDTH 8 +#define ACPI_PM_TIMER_WIDTH 32 + +/* PM Timer ticks per second (HZ) */ +#define PM_TIMER_FREQUENCY 3579545 + + +/* ACPI fixed hardware registers */ + +/* from linux/drivers/acpi/acpica/aclocal.h */ +/* Masks used to access the bit_registers */ + +/* PM1x_STS */ +#define ACPI_BITMASK_TIMER_STATUS 0x0001 +#define ACPI_BITMASK_BUS_MASTER_STATUS 0x0010 +#define ACPI_BITMASK_GLOBAL_LOCK_STATUS 0x0020 +#define ACPI_BITMASK_POWER_BUTTON_STATUS 0x0100 +#define ACPI_BITMASK_SLEEP_BUTTON_STATUS 0x0200 +#define ACPI_BITMASK_RT_CLOCK_STATUS 0x0400 +#define ACPI_BITMASK_PCIEXP_WAKE_STATUS 0x4000 /* ACPI 3.0 */ +#define ACPI_BITMASK_WAKE_STATUS 0x8000 + +#define ACPI_BITMASK_ALL_FIXED_STATUS (\ + ACPI_BITMASK_TIMER_STATUS | \ + ACPI_BITMASK_BUS_MASTER_STATUS | \ + ACPI_BITMASK_GLOBAL_LOCK_STATUS | \ + ACPI_BITMASK_POWER_BUTTON_STATUS | \ + ACPI_BITMASK_SLEEP_BUTTON_STATUS | \ + ACPI_BITMASK_RT_CLOCK_STATUS | \ + ACPI_BITMASK_WAKE_STATUS) + +/* PM1x_EN */ +#define ACPI_BITMASK_TIMER_ENABLE 0x0001 +#define ACPI_BITMASK_GLOBAL_LOCK_ENABLE 0x0020 +#define ACPI_BITMASK_POWER_BUTTON_ENABLE 0x0100 +#define ACPI_BITMASK_SLEEP_BUTTON_ENABLE 0x0200 +#define ACPI_BITMASK_RT_CLOCK_ENABLE 0x0400 +#define ACPI_BITMASK_PCIEXP_WAKE_DISABLE 0x4000 /* ACPI 3.0 */ + +/* PM1x_CNT */ +#define ACPI_BITMASK_SCI_ENABLE 0x0001 +#define ACPI_BITMASK_BUS_MASTER_RLD 0x0002 +#define ACPI_BITMASK_GLOBAL_LOCK_RELEASE 0x0004 +#define ACPI_BITMASK_SLEEP_TYPE 0x1C00 +#define ACPI_BITMASK_SLEEP_ENABLE 0x2000 + +/* PM2_CNT */ +#define ACPI_BITMASK_ARB_DISABLE 0x0001 + +/* PM_TMR */ + +#endif /* !QEMU_HW_ACPI_H */ diff --git a/hw/acpi_piix4.c b/hw/acpi_piix4.c new file mode 100644 index 0000000..5bbc2b5 --- /dev/null +++ b/hw/acpi_piix4.c @@ -0,0 +1,672 @@ +/* + * 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 + */ +#include "hw.h" +#include "pc.h" +#include "apm.h" +#include "pm_smbus.h" +#include "pci.h" +#include "acpi.h" +#include "sysemu.h" +#include "range.h" + +//#define DEBUG + +#ifdef DEBUG +# define PIIX4_DPRINTF(format, ...) printf(format, ## __VA_ARGS__) +#else +# define PIIX4_DPRINTF(format, ...) do { } while (0) +#endif + +#define ACPI_DBG_IO_ADDR 0xb044 + +#define GPE_BASE 0xafe0 +#define PCI_BASE 0xae00 +#define PCI_EJ_BASE 0xae08 +#define PCI_RMV_BASE 0xae0c + +#define PIIX4_PCI_HOTPLUG_STATUS 2 + +struct gpe_regs { + uint16_t sts; /* status */ + uint16_t en; /* enabled */ +}; + +struct pci_status { + uint32_t up; + uint32_t down; +}; + +typedef struct PIIX4PMState { + PCIDevice dev; + IORange ioport; + uint16_t pmsts; + uint16_t pmen; + uint16_t pmcntrl; + + APMState apm; + + QEMUTimer *tmr_timer; + int64_t tmr_overflow_time; + + PMSMBus smb; + uint32_t smb_io_base; + + qemu_irq irq; + qemu_irq cmos_s3; + qemu_irq smi_irq; + int kvm_enabled; + + /* for pci hotplug */ + struct gpe_regs gpe; + struct pci_status pci0_status; + uint32_t pci0_hotplug_enable; +} PIIX4PMState; + +static void piix4_acpi_system_hot_add_init(PCIBus *bus, PIIX4PMState *s); + +#define ACPI_ENABLE 0xf1 +#define ACPI_DISABLE 0xf0 + +static uint32_t get_pmtmr(PIIX4PMState *s) +{ + uint32_t d; + d = muldiv64(qemu_get_clock(vm_clock), PM_TIMER_FREQUENCY, get_ticks_per_sec()); + return d & 0xffffff; +} + +static int get_pmsts(PIIX4PMState *s) +{ + int64_t d; + + d = muldiv64(qemu_get_clock(vm_clock), PM_TIMER_FREQUENCY, + get_ticks_per_sec()); + if (d >= s->tmr_overflow_time) + s->pmsts |= ACPI_BITMASK_TIMER_STATUS; + return s->pmsts; +} + +static void pm_update_sci(PIIX4PMState *s) +{ + int sci_level, pmsts; + int64_t expire_time; + + pmsts = get_pmsts(s); + sci_level = (((pmsts & s->pmen) & + (ACPI_BITMASK_RT_CLOCK_ENABLE | + ACPI_BITMASK_POWER_BUTTON_ENABLE | + ACPI_BITMASK_GLOBAL_LOCK_ENABLE | + ACPI_BITMASK_TIMER_ENABLE)) != 0) || + (((s->gpe.sts & s->gpe.en) & PIIX4_PCI_HOTPLUG_STATUS) != 0); + + qemu_set_irq(s->irq, sci_level); + /* schedule a timer interruption if needed */ + if ((s->pmen & ACPI_BITMASK_TIMER_ENABLE) && + !(pmsts & ACPI_BITMASK_TIMER_STATUS)) { + expire_time = muldiv64(s->tmr_overflow_time, get_ticks_per_sec(), + PM_TIMER_FREQUENCY); + qemu_mod_timer(s->tmr_timer, expire_time); + } else { + qemu_del_timer(s->tmr_timer); + } +} + +static void pm_tmr_timer(void *opaque) +{ + PIIX4PMState *s = opaque; + pm_update_sci(s); +} + +static void pm_ioport_write(IORange *ioport, uint64_t addr, unsigned width, + uint64_t val) +{ + PIIX4PMState *s = container_of(ioport, PIIX4PMState, ioport); + + if (width != 2) { + PIIX4_DPRINTF("PM write port=0x%04x width=%d val=0x%08x\n", + (unsigned)addr, width, (unsigned)val); + } + + switch(addr) { + case 0x00: + { + int64_t d; + int pmsts; + pmsts = get_pmsts(s); + if (pmsts & val & ACPI_BITMASK_TIMER_STATUS) { + /* if TMRSTS is reset, then compute the new overflow time */ + d = muldiv64(qemu_get_clock(vm_clock), PM_TIMER_FREQUENCY, + get_ticks_per_sec()); + s->tmr_overflow_time = (d + 0x800000LL) & ~0x7fffffLL; + } + s->pmsts &= ~val; + pm_update_sci(s); + } + break; + case 0x02: + s->pmen = val; + pm_update_sci(s); + break; + case 0x04: + { + int sus_typ; + s->pmcntrl = val & ~(ACPI_BITMASK_SLEEP_ENABLE); + if (val & ACPI_BITMASK_SLEEP_ENABLE) { + /* change suspend type */ + sus_typ = (val >> 10) & 7; + switch(sus_typ) { + case 0: /* soft power off */ + qemu_system_shutdown_request(); + break; + case 1: + /* ACPI_BITMASK_WAKE_STATUS should be set on resume. + Pretend that resume was caused by power button */ + s->pmsts |= (ACPI_BITMASK_WAKE_STATUS | + ACPI_BITMASK_POWER_BUTTON_STATUS); + qemu_system_reset_request(); + if (s->cmos_s3) { + qemu_irq_raise(s->cmos_s3); + } + default: + break; + } + } + } + break; + default: + break; + } + PIIX4_DPRINTF("PM writew port=0x%04x val=0x%04x\n", addr, val); +} + +static void pm_ioport_read(IORange *ioport, uint64_t addr, unsigned width, + uint64_t *data) +{ + PIIX4PMState *s = container_of(ioport, PIIX4PMState, ioport); + uint32_t val; + + switch(addr) { + case 0x00: + val = get_pmsts(s); + break; + case 0x02: + val = s->pmen; + break; + case 0x04: + val = s->pmcntrl; + break; + case 0x08: + val = get_pmtmr(s); + break; + default: + val = 0; + break; + } + PIIX4_DPRINTF("PM readw port=0x%04x val=0x%04x\n", addr, val); + *data = val; +} + +static const IORangeOps pm_iorange_ops = { + .read = pm_ioport_read, + .write = pm_ioport_write, +}; + +static void apm_ctrl_changed(uint32_t val, void *arg) +{ + PIIX4PMState *s = arg; + + /* ACPI specs 3.0, 4.7.2.5 */ + if (val == ACPI_ENABLE) { + s->pmcntrl |= ACPI_BITMASK_SCI_ENABLE; + } else if (val == ACPI_DISABLE) { + s->pmcntrl &= ~ACPI_BITMASK_SCI_ENABLE; + } + + if (s->dev.config[0x5b] & (1 << 1)) { + if (s->smi_irq) { + qemu_irq_raise(s->smi_irq); + } + } +} + +static void acpi_dbg_writel(void *opaque, uint32_t addr, uint32_t val) +{ + PIIX4_DPRINTF("ACPI: DBG: 0x%08x\n", val); +} + +static void pm_io_space_update(PIIX4PMState *s) +{ + uint32_t pm_io_base; + + if (s->dev.config[0x80] & 1) { + pm_io_base = le32_to_cpu(*(uint32_t *)(s->dev.config + 0x40)); + pm_io_base &= 0xffc0; + + /* XXX: need to improve memory and ioport allocation */ + PIIX4_DPRINTF("PM: mapping to 0x%x\n", pm_io_base); + iorange_init(&s->ioport, &pm_iorange_ops, pm_io_base, 64); + ioport_register(&s->ioport); + } +} + +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)) + pm_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; +} + +static const VMStateDescription vmstate_gpe = { + .name = "gpe", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField []) { + VMSTATE_UINT16(sts, struct gpe_regs), + VMSTATE_UINT16(en, struct gpe_regs), + VMSTATE_END_OF_LIST() + } +}; + +static const VMStateDescription vmstate_pci_status = { + .name = "pci_status", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField []) { + VMSTATE_UINT32(up, struct pci_status), + VMSTATE_UINT32(down, struct pci_status), + VMSTATE_END_OF_LIST() + } +}; + +static const VMStateDescription vmstate_acpi = { + .name = "piix4_pm", + .version_id = 2, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .post_load = vmstate_acpi_post_load, + .fields = (VMStateField []) { + VMSTATE_PCI_DEVICE(dev, PIIX4PMState), + VMSTATE_UINT16(pmsts, PIIX4PMState), + VMSTATE_UINT16(pmen, PIIX4PMState), + VMSTATE_UINT16(pmcntrl, PIIX4PMState), + VMSTATE_STRUCT(apm, PIIX4PMState, 0, vmstate_apm, APMState), + VMSTATE_TIMER(tmr_timer, PIIX4PMState), + VMSTATE_INT64(tmr_overflow_time, PIIX4PMState), + VMSTATE_STRUCT(gpe, PIIX4PMState, 2, vmstate_gpe, struct gpe_regs), + VMSTATE_STRUCT(pci0_status, PIIX4PMState, 2, vmstate_pci_status, + struct pci_status), + VMSTATE_END_OF_LIST() + } +}; + +static void piix4_update_hotplug(PIIX4PMState *s) +{ + PCIDevice *dev = &s->dev; + BusState *bus = qdev_get_parent_bus(&dev->qdev); + DeviceState *qdev, *next; + + s->pci0_hotplug_enable = ~0; + + QLIST_FOREACH_SAFE(qdev, &bus->children, sibling, next) { + PCIDeviceInfo *info = container_of(qdev->info, PCIDeviceInfo, qdev); + PCIDevice *pdev = DO_UPCAST(PCIDevice, qdev, qdev); + int slot = PCI_SLOT(pdev->devfn); + + if (info->no_hotplug) { + s->pci0_hotplug_enable &= ~(1 << slot); + } + } +} + +static void piix4_reset(void *opaque) +{ + PIIX4PMState *s = opaque; + uint8_t *pci_conf = s->dev.config; + + pci_conf[0x58] = 0; + pci_conf[0x59] = 0; + pci_conf[0x5a] = 0; + pci_conf[0x5b] = 0; + + if (s->kvm_enabled) { + /* Mark SMM as already inited (until KVM supports SMM). */ + pci_conf[0x5B] = 0x02; + } + piix4_update_hotplug(s); +} + +static void piix4_powerdown(void *opaque, int irq, int power_failing) +{ + PIIX4PMState *s = opaque; + + if (!s) { + qemu_system_shutdown_request(); + } else if (s->pmen & ACPI_BITMASK_POWER_BUTTON_ENABLE) { + s->pmsts |= ACPI_BITMASK_POWER_BUTTON_STATUS; + pm_update_sci(s); + } +} + +static int piix4_pm_initfn(PCIDevice *dev) +{ + PIIX4PMState *s = DO_UPCAST(PIIX4PMState, dev, dev); + uint8_t *pci_conf; + + pci_conf = s->dev.config; + pci_config_set_vendor_id(pci_conf, PCI_VENDOR_ID_INTEL); + pci_config_set_device_id(pci_conf, PCI_DEVICE_ID_INTEL_82371AB_3); + pci_conf[0x06] = 0x80; + pci_conf[0x07] = 0x02; + pci_conf[0x08] = 0x03; // revision number + pci_conf[0x09] = 0x00; + pci_config_set_class(pci_conf, PCI_CLASS_BRIDGE_OTHER); + pci_conf[0x3d] = 0x01; // interrupt pin 1 + + pci_conf[0x40] = 0x01; /* PM io base read only bit */ + + /* APM */ + apm_init(&s->apm, apm_ctrl_changed, s); + + register_ioport_write(ACPI_DBG_IO_ADDR, 4, 4, acpi_dbg_writel, s); + + if (s->kvm_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[0x5f] = (parallel_hds[0] != NULL ? 0x80 : 0) | 0x10; + pci_conf[0x63] = 0x60; + pci_conf[0x67] = (serial_hds[0] != NULL ? 0x08 : 0) | + (serial_hds[1] != NULL ? 0x90 : 0); + + pci_conf[0x90] = s->smb_io_base | 1; + pci_conf[0x91] = s->smb_io_base >> 8; + pci_conf[0xd2] = 0x09; + register_ioport_write(s->smb_io_base, 64, 1, smb_ioport_writeb, &s->smb); + register_ioport_read(s->smb_io_base, 64, 1, smb_ioport_readb, &s->smb); + + s->tmr_timer = qemu_new_timer(vm_clock, pm_tmr_timer, s); + + qemu_system_powerdown = *qemu_allocate_irqs(piix4_powerdown, s, 1); + + pm_smbus_init(&s->dev.qdev, &s->smb); + qemu_register_reset(piix4_reset, s); + piix4_acpi_system_hot_add_init(dev->bus, s); + + return 0; +} + +i2c_bus *piix4_pm_init(PCIBus *bus, int devfn, uint32_t smb_io_base, + qemu_irq sci_irq, qemu_irq cmos_s3, qemu_irq smi_irq, + int kvm_enabled) +{ + PCIDevice *dev; + PIIX4PMState *s; + + dev = pci_create(bus, devfn, "PIIX4_PM"); + qdev_prop_set_uint32(&dev->qdev, "smb_io_base", smb_io_base); + + s = DO_UPCAST(PIIX4PMState, dev, dev); + s->irq = sci_irq; + s->cmos_s3 = cmos_s3; + s->smi_irq = smi_irq; + s->kvm_enabled = kvm_enabled; + + qdev_init_nofail(&dev->qdev); + + return s->smb.smbus; +} + +static PCIDeviceInfo piix4_pm_info = { + .qdev.name = "PIIX4_PM", + .qdev.desc = "PM", + .qdev.size = sizeof(PIIX4PMState), + .qdev.vmsd = &vmstate_acpi, + .qdev.no_user = 1, + .no_hotplug = 1, + .init = piix4_pm_initfn, + .config_write = pm_write_config, + .qdev.props = (Property[]) { + DEFINE_PROP_UINT32("smb_io_base", PIIX4PMState, smb_io_base, 0), + DEFINE_PROP_END_OF_LIST(), + } +}; + +static void piix4_pm_register(void) +{ + pci_qdev_register(&piix4_pm_info); +} + +device_init(piix4_pm_register); + +static uint32_t gpe_read_val(uint16_t val, uint32_t addr) +{ + if (addr & 1) + return (val >> 8) & 0xff; + return val & 0xff; +} + +static uint32_t gpe_readb(void *opaque, uint32_t addr) +{ + uint32_t val = 0; + PIIX4PMState *s = opaque; + struct gpe_regs *g = &s->gpe; + + switch (addr) { + case GPE_BASE: + case GPE_BASE + 1: + val = gpe_read_val(g->sts, addr); + break; + case GPE_BASE + 2: + case GPE_BASE + 3: + val = gpe_read_val(g->en, addr); + break; + default: + break; + } + + PIIX4_DPRINTF("gpe read %x == %x\n", addr, val); + return val; +} + +static void gpe_write_val(uint16_t *cur, int addr, uint32_t val) +{ + if (addr & 1) + *cur = (*cur & 0xff) | (val << 8); + else + *cur = (*cur & 0xff00) | (val & 0xff); +} + +static void gpe_reset_val(uint16_t *cur, int addr, uint32_t val) +{ + uint16_t x1, x0 = val & 0xff; + int shift = (addr & 1) ? 8 : 0; + + x1 = (*cur >> shift) & 0xff; + + x1 = x1 & ~x0; + + *cur = (*cur & (0xff << (8 - shift))) | (x1 << shift); +} + +static void gpe_writeb(void *opaque, uint32_t addr, uint32_t val) +{ + PIIX4PMState *s = opaque; + struct gpe_regs *g = &s->gpe; + + switch (addr) { + case GPE_BASE: + case GPE_BASE + 1: + gpe_reset_val(&g->sts, addr, val); + break; + case GPE_BASE + 2: + case GPE_BASE + 3: + gpe_write_val(&g->en, addr, val); + break; + default: + break; + } + + pm_update_sci(s); + + PIIX4_DPRINTF("gpe write %x <== %d\n", addr, val); +} + +static uint32_t pcihotplug_read(void *opaque, uint32_t addr) +{ + uint32_t val = 0; + struct pci_status *g = opaque; + switch (addr) { + case PCI_BASE: + val = g->up; + break; + case PCI_BASE + 4: + val = g->down; + break; + default: + break; + } + + PIIX4_DPRINTF("pcihotplug read %x == %x\n", addr, val); + return val; +} + +static void pcihotplug_write(void *opaque, uint32_t addr, uint32_t val) +{ + struct pci_status *g = opaque; + switch (addr) { + case PCI_BASE: + g->up = val; + break; + case PCI_BASE + 4: + g->down = val; + break; + } + + PIIX4_DPRINTF("pcihotplug write %x <== %d\n", addr, val); +} + +static uint32_t pciej_read(void *opaque, uint32_t addr) +{ + PIIX4_DPRINTF("pciej read %x\n", addr); + return 0; +} + +static void pciej_write(void *opaque, uint32_t addr, uint32_t val) +{ + BusState *bus = opaque; + DeviceState *qdev, *next; + PCIDevice *dev; + int slot = ffs(val) - 1; + + QLIST_FOREACH_SAFE(qdev, &bus->children, sibling, next) { + dev = DO_UPCAST(PCIDevice, qdev, qdev); + if (PCI_SLOT(dev->devfn) == slot) { + qdev_free(qdev); + } + } + + + PIIX4_DPRINTF("pciej write %x <== %d\n", addr, val); +} + +static uint32_t pcirmv_read(void *opaque, uint32_t addr) +{ + PIIX4PMState *s = opaque; + + return s->pci0_hotplug_enable; +} + +static void pcirmv_write(void *opaque, uint32_t addr, uint32_t val) +{ + return; +} + +static int piix4_device_hotplug(DeviceState *qdev, PCIDevice *dev, + PCIHotplugState state); + +static void piix4_acpi_system_hot_add_init(PCIBus *bus, PIIX4PMState *s) +{ + struct pci_status *pci0_status = &s->pci0_status; + + register_ioport_write(GPE_BASE, 4, 1, gpe_writeb, s); + register_ioport_read(GPE_BASE, 4, 1, gpe_readb, s); + + register_ioport_write(PCI_BASE, 8, 4, pcihotplug_write, pci0_status); + register_ioport_read(PCI_BASE, 8, 4, pcihotplug_read, pci0_status); + + register_ioport_write(PCI_EJ_BASE, 4, 4, pciej_write, bus); + register_ioport_read(PCI_EJ_BASE, 4, 4, pciej_read, bus); + + register_ioport_write(PCI_RMV_BASE, 4, 4, pcirmv_write, s); + register_ioport_read(PCI_RMV_BASE, 4, 4, pcirmv_read, s); + + pci_bus_hotplug(bus, piix4_device_hotplug, &s->dev.qdev); +} + +static void enable_device(PIIX4PMState *s, int slot) +{ + s->gpe.sts |= PIIX4_PCI_HOTPLUG_STATUS; + s->pci0_status.up |= (1 << slot); +} + +static void disable_device(PIIX4PMState *s, int slot) +{ + s->gpe.sts |= PIIX4_PCI_HOTPLUG_STATUS; + s->pci0_status.down |= (1 << slot); +} + +static int piix4_device_hotplug(DeviceState *qdev, PCIDevice *dev, + PCIHotplugState state) +{ + int slot = PCI_SLOT(dev->devfn); + PIIX4PMState *s = DO_UPCAST(PIIX4PMState, dev, + DO_UPCAST(PCIDevice, qdev, qdev)); + + /* 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 (state == PCI_COLDPLUG_ENABLED) { + return 0; + } + + s->pci0_status.up = 0; + s->pci0_status.down = 0; + if (state == PCI_HOTPLUG_ENABLED) { + enable_device(s, slot); + } else { + disable_device(s, slot); + } + + pm_update_sci(s); + + return 0; +} diff --git a/hw/adb.c b/hw/adb.c new file mode 100644 index 0000000..99b30f6 --- /dev/null +++ b/hw/adb.c @@ -0,0 +1,480 @@ +/* + * 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.h" +#include "ppc_mac.h" +#include "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_DONGLE 1 +#define ADB_KEYBOARD 2 +#define ADB_MOUSE 3 +#define ADB_TABLET 4 +#define ADB_MODEM 5 +#define ADB_MISC 7 + +/* error codes */ +#define ADB_RET_NOTPRESENT (-2) + +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]; + if (d->devreset) { + d->devreset(d); + } + } + return 0; + } + devaddr = buf[0] >> 4; + for(i = 0; i < s->nb_devices; i++) { + d = &s->devices[i]; + if (d->devaddr == devaddr) { + return d->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; +} + +ADBDevice *adb_register_device(ADBBusState *s, int devaddr, + ADBDeviceRequest *devreq, + ADBDeviceReset *devreset, + void *opaque) +{ + ADBDevice *d; + if (s->nb_devices >= MAX_ADB_DEVICES) + return NULL; + d = &s->devices[s->nb_devices++]; + d->bus = s; + d->devaddr = devaddr; + d->devreq = devreq; + d->devreset = devreset; + d->opaque = opaque; + qemu_register_reset((QEMUResetHandler *)devreset, d); + return d; +} + +/***************************************************************/ +/* Keyboard ADB device */ + +typedef struct KBDState { + uint8_t data[128]; + int rptr, wptr, count; +} KBDState; + +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) +{ + ADBDevice *d = opaque; + KBDState *s = d->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 = d->opaque; + 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 = d->opaque; + 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 void adb_kbd_save(QEMUFile *f, void *opaque) +{ + KBDState *s = (KBDState *)opaque; + + qemu_put_buffer(f, s->data, sizeof(s->data)); + qemu_put_sbe32s(f, &s->rptr); + qemu_put_sbe32s(f, &s->wptr); + qemu_put_sbe32s(f, &s->count); +} + +static int adb_kbd_load(QEMUFile *f, void *opaque, int version_id) +{ + KBDState *s = (KBDState *)opaque; + + if (version_id != 1) + return -EINVAL; + + qemu_get_buffer(f, s->data, sizeof(s->data)); + qemu_get_sbe32s(f, &s->rptr); + qemu_get_sbe32s(f, &s->wptr); + qemu_get_sbe32s(f, &s->count); + + return 0; +} + +static int adb_kbd_reset(ADBDevice *d) +{ + KBDState *s = d->opaque; + + d->handler = 1; + d->devaddr = ADB_KEYBOARD; + memset(s, 0, sizeof(KBDState)); + + return 0; +} + +void adb_kbd_init(ADBBusState *bus) +{ + ADBDevice *d; + KBDState *s; + s = qemu_mallocz(sizeof(KBDState)); + d = adb_register_device(bus, ADB_KEYBOARD, adb_kbd_request, + adb_kbd_reset, s); + qemu_add_kbd_event_handler(adb_kbd_put_keycode, d); + register_savevm(NULL, "adb_kbd", -1, 1, adb_kbd_save, + adb_kbd_load, s); +} + +/***************************************************************/ +/* Mouse ADB device */ + +typedef struct MouseState { + int buttons_state, last_buttons_state; + int dx, dy, dz; +} MouseState; + +static void adb_mouse_event(void *opaque, + int dx1, int dy1, int dz1, int buttons_state) +{ + ADBDevice *d = opaque; + MouseState *s = d->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 = d->opaque; + 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 = d->opaque; + 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 int adb_mouse_reset(ADBDevice *d) +{ + MouseState *s = d->opaque; + + d->handler = 2; + d->devaddr = ADB_MOUSE; + memset(s, 0, sizeof(MouseState)); + + return 0; +} + +static void adb_mouse_save(QEMUFile *f, void *opaque) +{ + MouseState *s = (MouseState *)opaque; + + qemu_put_sbe32s(f, &s->buttons_state); + qemu_put_sbe32s(f, &s->last_buttons_state); + qemu_put_sbe32s(f, &s->dx); + qemu_put_sbe32s(f, &s->dy); + qemu_put_sbe32s(f, &s->dz); +} + +static int adb_mouse_load(QEMUFile *f, void *opaque, int version_id) +{ + MouseState *s = (MouseState *)opaque; + + if (version_id != 1) + return -EINVAL; + + qemu_get_sbe32s(f, &s->buttons_state); + qemu_get_sbe32s(f, &s->last_buttons_state); + qemu_get_sbe32s(f, &s->dx); + qemu_get_sbe32s(f, &s->dy); + qemu_get_sbe32s(f, &s->dz); + + return 0; +} + +void adb_mouse_init(ADBBusState *bus) +{ + ADBDevice *d; + MouseState *s; + + s = qemu_mallocz(sizeof(MouseState)); + d = adb_register_device(bus, ADB_MOUSE, adb_mouse_request, + adb_mouse_reset, s); + qemu_add_mouse_event_handler(adb_mouse_event, d, 0, "QEMU ADB Mouse"); + register_savevm(NULL, "adb_mouse", -1, 1, adb_mouse_save, + adb_mouse_load, s); +} diff --git a/hw/adlib.c b/hw/adlib.c new file mode 100644 index 0000000..1d8092b --- /dev/null +++ b/hw/adlib.c @@ -0,0 +1,338 @@ +/* + * 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.h" +#include "audiodev.h" +#include "audio/audio.h" +#include "isa.h" + +//#define DEBUG + +#define ADLIB_KILL_TIMERS 1 + +#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) + +static struct { + int port; + int freq; +} conf = {0x220, 44100}; + +typedef struct { + QEMUSoundCard card; + 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 +} 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; + int status; + + s->active = 1; + AUD_set_active_out (s->voice, 1); + + adlib_kill_timers (s); + +#ifdef HAS_YMF262 + status = YMF262Write (0, a, val); +#else + status = 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_get_clock (vm_clock) + 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 + + if (s->mixbuf) { + qemu_free (s->mixbuf); + } + + s->active = 0; + s->enabled = 0; + AUD_remove_card (&s->card); +} + +int Adlib_init (qemu_irq *pic) +{ + AdlibState *s = &glob_adlib; + struct audsettings as; + +#ifdef HAS_YMF262 + if (YMF262Init (1, 14318180, conf.freq)) { + dolog ("YMF262Init %d failed\n", conf.freq); + return -1; + } + else { + YMF262SetTimerHandler (0, timer_handler, 0); + s->enabled = 1; + } +#else + s->opl = OPLCreate (OPL_TYPE_YM3812, 3579545, conf.freq); + if (!s->opl) { + dolog ("OPLCreate %d failed\n", conf.freq); + return -1; + } + else { + OPLSetTimerHandler (s->opl, timer_handler, 0); + s->enabled = 1; + } +#endif + + as.freq = conf.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); + return -1; + } + + s->samples = AUD_get_buffer_size_out (s->voice) >> SHIFT; + s->mixbuf = qemu_mallocz (s->samples << SHIFT); + + register_ioport_read (0x388, 4, 1, adlib_read, s); + register_ioport_write (0x388, 4, 1, adlib_write, s); + + register_ioport_read (conf.port, 4, 1, adlib_read, s); + register_ioport_write (conf.port, 4, 1, adlib_write, s); + + register_ioport_read (conf.port + 8, 2, 1, adlib_read, s); + register_ioport_write (conf.port + 8, 2, 1, adlib_write, s); + + return 0; +} diff --git a/hw/ads7846.c b/hw/ads7846.c new file mode 100644 index 0000000..b3bbeaf --- /dev/null +++ b/hw/ads7846.c @@ -0,0 +1,170 @@ +/* + * TI ADS7846 / TSC2046 chip emulation. + * + * Copyright (c) 2006 Openedhand Ltd. + * Written by Andrzej Zaborowski + * + * This code is licensed under the GNU GPL v2. + */ + +#include "ssi.h" +#include "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 void ads7846_save(QEMUFile *f, void *opaque) +{ + ADS7846State *s = (ADS7846State *) opaque; + int i; + + for (i = 0; i < 8; i ++) + qemu_put_be32(f, s->input[i]); + qemu_put_be32(f, s->noise); + qemu_put_be32(f, s->cycle); + qemu_put_be32(f, s->output); +} + +static int ads7846_load(QEMUFile *f, void *opaque, int version_id) +{ + ADS7846State *s = (ADS7846State *) opaque; + int i; + + for (i = 0; i < 8; i ++) + s->input[i] = qemu_get_be32(f); + s->noise = qemu_get_be32(f); + s->cycle = qemu_get_be32(f); + s->output = qemu_get_be32(f); + + s->pressure = 0; + ads7846_int_update(s); + + return 0; +} + +static int ads7846_init(SSISlave *dev) +{ + ADS7846State *s = FROM_SSI_SLAVE(ADS7846State, dev); + + qdev_init_gpio_out(&dev->qdev, &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); + + register_savevm(NULL, "ads7846", -1, 0, ads7846_save, ads7846_load, s); + return 0; +} + +static SSISlaveInfo ads7846_info = { + .qdev.name ="ads7846", + .qdev.size = sizeof(ADS7846State), + .init = ads7846_init, + .transfer = ads7846_transfer +}; + +static void ads7846_register_devices(void) +{ + ssi_register_slave(&ads7846_info); +} + +device_init(ads7846_register_devices) diff --git a/hw/alpha_palcode.c b/hw/alpha_palcode.c new file mode 100644 index 0000000..033b542 --- /dev/null +++ b/hw/alpha_palcode.c @@ -0,0 +1,1048 @@ +/* + * Alpha emulation - PALcode emulation for qemu. + * + * Copyright (c) 2007 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 . + */ + +#include +#include +#include + +#include "cpu.h" +#include "exec-all.h" + +/* Shared handlers */ +static void pal_reset (CPUState *env); +/* Console handlers */ +static void pal_console_call (CPUState *env, uint32_t palcode); +/* OpenVMS handlers */ +static void pal_openvms_call (CPUState *env, uint32_t palcode); +/* UNIX / Linux handlers */ +static void pal_unix_call (CPUState *env, uint32_t palcode); + +pal_handler_t pal_handlers[] = { + /* Console handler */ + { + .reset = &pal_reset, + .call_pal = &pal_console_call, + }, + /* OpenVMS handler */ + { + .reset = &pal_reset, + .call_pal = &pal_openvms_call, + }, + /* UNIX / Linux handler */ + { + .reset = &pal_reset, + .call_pal = &pal_unix_call, + }, +}; + +#if 0 +/* One must explicitly check that the TB is valid and the FOE bit is reset */ +static void update_itb (void) +{ + /* This writes into a temp register, not the actual one */ + mtpr(TB_TAG); + mtpr(TB_CTL); + /* This commits the TB update */ + mtpr(ITB_PTE); +} + +static void update_dtb (void); +{ + mtpr(TB_CTL); + /* This write into a temp register, not the actual one */ + mtpr(TB_TAG); + /* This commits the TB update */ + mtpr(DTB_PTE); +} +#endif + +static void pal_reset (CPUState *env) +{ +} + +static void do_swappal (CPUState *env, uint64_t palid) +{ + pal_handler_t *pal_handler; + + switch (palid) { + case 0 ... 2: + pal_handler = &pal_handlers[palid]; + env->pal_handler = pal_handler; + env->ipr[IPR_PAL_BASE] = -1ULL; + (*pal_handler->reset)(env); + break; + case 3 ... 255: + /* Unknown identifier */ + env->ir[0] = 1; + return; + default: + /* We were given the entry point address */ + env->pal_handler = NULL; + env->ipr[IPR_PAL_BASE] = palid; + env->pc = env->ipr[IPR_PAL_BASE]; + cpu_loop_exit(); + } +} + +static void pal_console_call (CPUState *env, uint32_t palcode) +{ + uint64_t palid; + + if (palcode < 0x00000080) { + /* Privileged palcodes */ + if (!(env->ps >> 3)) { + /* TODO: generate privilege exception */ + } + } + switch (palcode) { + case 0x00000000: + /* HALT */ + /* REQUIRED */ + break; + case 0x00000001: + /* CFLUSH */ + break; + case 0x00000002: + /* DRAINA */ + /* REQUIRED */ + /* Implemented as no-op */ + break; + case 0x00000009: + /* CSERVE */ + /* REQUIRED */ + break; + case 0x0000000A: + /* SWPPAL */ + /* REQUIRED */ + palid = env->ir[16]; + do_swappal(env, palid); + break; + case 0x00000080: + /* BPT */ + /* REQUIRED */ + break; + case 0x00000081: + /* BUGCHK */ + /* REQUIRED */ + break; + case 0x00000086: + /* IMB */ + /* REQUIRED */ + /* Implemented as no-op */ + break; + case 0x0000009E: + /* RDUNIQUE */ + /* REQUIRED */ + break; + case 0x0000009F: + /* WRUNIQUE */ + /* REQUIRED */ + break; + case 0x000000AA: + /* GENTRAP */ + /* REQUIRED */ + break; + default: + break; + } +} + +static void pal_openvms_call (CPUState *env, uint32_t palcode) +{ + uint64_t palid, val, oldval; + + if (palcode < 0x00000080) { + /* Privileged palcodes */ + if (!(env->ps >> 3)) { + /* TODO: generate privilege exception */ + } + } + switch (palcode) { + case 0x00000000: + /* HALT */ + /* REQUIRED */ + break; + case 0x00000001: + /* CFLUSH */ + break; + case 0x00000002: + /* DRAINA */ + /* REQUIRED */ + /* Implemented as no-op */ + break; + case 0x00000003: + /* LDQP */ + break; + case 0x00000004: + /* STQP */ + break; + case 0x00000005: + /* SWPCTX */ + break; + case 0x00000006: + /* MFPR_ASN */ + if (cpu_alpha_mfpr(env, IPR_ASN, &val) == 0) + env->ir[0] = val; + break; + case 0x00000007: + /* MTPR_ASTEN */ + val = env->ir[16]; + if (cpu_alpha_mtpr(env, IPR_ASTEN, val, &oldval) == 1) + env->ir[0] = val; + break; + case 0x00000008: + /* MTPR_ASTSR */ + val = env->ir[16]; + if (cpu_alpha_mtpr(env, IPR_ASTSR, val, &oldval) == 1) + env->ir[0] = val; + break; + case 0x00000009: + /* CSERVE */ + /* REQUIRED */ + break; + case 0x0000000A: + /* SWPPAL */ + /* REQUIRED */ + palid = env->ir[16]; + do_swappal(env, palid); + break; + case 0x0000000B: + /* MFPR_FEN */ + if (cpu_alpha_mfpr(env, IPR_FEN, &val) == 0) + env->ir[0] = val; + break; + case 0x0000000C: + /* MTPR_FEN */ + val = env->ir[16]; + if (cpu_alpha_mtpr(env, IPR_FEN, val, &oldval) == 1) + env->ir[0] = val; + break; + case 0x0000000D: + /* MTPR_IPIR */ + val = env->ir[16]; + if (cpu_alpha_mtpr(env, IPR_IPIR, val, &oldval) == 1) + env->ir[0] = val; + break; + case 0x0000000E: + /* MFPR_IPL */ + if (cpu_alpha_mfpr(env, IPR_IPL, &val) == 0) + env->ir[0] = val; + break; + case 0x0000000F: + /* MTPR_IPL */ + val = env->ir[16]; + if (cpu_alpha_mtpr(env, IPR_IPL, val, &oldval) == 1) + env->ir[0] = val; + break; + case 0x00000010: + /* MFPR_MCES */ + if (cpu_alpha_mfpr(env, IPR_MCES, &val) == 0) + env->ir[0] = val; + break; + case 0x00000011: + /* MTPR_MCES */ + val = env->ir[16]; + if (cpu_alpha_mtpr(env, IPR_MCES, val, &oldval) == 1) + env->ir[0] = val; + break; + case 0x00000012: + /* MFPR_PCBB */ + if (cpu_alpha_mfpr(env, IPR_PCBB, &val) == 0) + env->ir[0] = val; + break; + case 0x00000013: + /* MFPR_PRBR */ + if (cpu_alpha_mfpr(env, IPR_PRBR, &val) == 0) + env->ir[0] = val; + break; + case 0x00000014: + /* MTPR_PRBR */ + val = env->ir[16]; + if (cpu_alpha_mtpr(env, IPR_PRBR, val, &oldval) == 1) + env->ir[0] = val; + break; + case 0x00000015: + /* MFPR_PTBR */ + if (cpu_alpha_mfpr(env, IPR_PTBR, &val) == 0) + env->ir[0] = val; + break; + case 0x00000016: + /* MFPR_SCBB */ + if (cpu_alpha_mfpr(env, IPR_SCBB, &val) == 0) + env->ir[0] = val; + break; + case 0x00000017: + /* MTPR_SCBB */ + val = env->ir[16]; + if (cpu_alpha_mtpr(env, IPR_SCBB, val, &oldval) == 1) + env->ir[0] = val; + break; + case 0x00000018: + /* MTPR_SIRR */ + val = env->ir[16]; + if (cpu_alpha_mtpr(env, IPR_SIRR, val, &oldval) == 1) + env->ir[0] = val; + break; + case 0x00000019: + /* MFPR_SISR */ + if (cpu_alpha_mfpr(env, IPR_SISR, &val) == 0) + env->ir[0] = val; + break; + case 0x0000001A: + /* MFPR_TBCHK */ + if (cpu_alpha_mfpr(env, IPR_TBCHK, &val) == 0) + env->ir[0] = val; + break; + case 0x0000001B: + /* MTPR_TBIA */ + val = env->ir[16]; + if (cpu_alpha_mtpr(env, IPR_TBIA, val, &oldval) == 1) + env->ir[0] = val; + break; + case 0x0000001C: + /* MTPR_TBIAP */ + val = env->ir[16]; + if (cpu_alpha_mtpr(env, IPR_TBIAP, val, &oldval) == 1) + env->ir[0] = val; + break; + case 0x0000001D: + /* MTPR_TBIS */ + val = env->ir[16]; + if (cpu_alpha_mtpr(env, IPR_TBIS, val, &oldval) == 1) + env->ir[0] = val; + break; + case 0x0000001E: + /* MFPR_ESP */ + if (cpu_alpha_mfpr(env, IPR_ESP, &val) == 0) + env->ir[0] = val; + break; + case 0x0000001F: + /* MTPR_ESP */ + val = env->ir[16]; + if (cpu_alpha_mtpr(env, IPR_ESP, val, &oldval) == 1) + env->ir[0] = val; + break; + case 0x00000020: + /* MFPR_SSP */ + if (cpu_alpha_mfpr(env, IPR_SSP, &val) == 0) + env->ir[0] = val; + break; + case 0x00000021: + /* MTPR_SSP */ + val = env->ir[16]; + if (cpu_alpha_mtpr(env, IPR_SSP, val, &oldval) == 1) + env->ir[0] = val; + break; + case 0x00000022: + /* MFPR_USP */ + if (cpu_alpha_mfpr(env, IPR_USP, &val) == 0) + env->ir[0] = val; + break; + case 0x00000023: + /* MTPR_USP */ + val = env->ir[16]; + if (cpu_alpha_mtpr(env, IPR_USP, val, &oldval) == 1) + env->ir[0] = val; + break; + case 0x00000024: + /* MTPR_TBISD */ + val = env->ir[16]; + if (cpu_alpha_mtpr(env, IPR_TBISD, val, &oldval) == 1) + env->ir[0] = val; + break; + case 0x00000025: + /* MTPR_TBISI */ + val = env->ir[16]; + if (cpu_alpha_mtpr(env, IPR_TBISI, val, &oldval) == 1) + env->ir[0] = val; + break; + case 0x00000026: + /* MFPR_ASTEN */ + if (cpu_alpha_mfpr(env, IPR_ASTEN, &val) == 0) + env->ir[0] = val; + break; + case 0x00000027: + /* MFPR_ASTSR */ + if (cpu_alpha_mfpr(env, IPR_ASTSR, &val) == 0) + env->ir[0] = val; + break; + case 0x00000029: + /* MFPR_VPTB */ + if (cpu_alpha_mfpr(env, IPR_VPTB, &val) == 0) + env->ir[0] = val; + break; + case 0x0000002A: + /* MTPR_VPTB */ + val = env->ir[16]; + if (cpu_alpha_mtpr(env, IPR_VPTB, val, &oldval) == 1) + env->ir[0] = val; + break; + case 0x0000002B: + /* MTPR_PERFMON */ + val = env->ir[16]; + if (cpu_alpha_mtpr(env, IPR_PERFMON, val, &oldval) == 1) + env->ir[0] = val; + break; + case 0x0000002E: + /* MTPR_DATFX */ + val = env->ir[16]; + if (cpu_alpha_mtpr(env, IPR_DATFX, val, &oldval) == 1) + env->ir[0] = val; + break; + case 0x0000003E: + /* WTINT */ + break; + case 0x0000003F: + /* MFPR_WHAMI */ + if (cpu_alpha_mfpr(env, IPR_WHAMI, &val) == 0) + env->ir[0] = val; + break; + case 0x00000080: + /* BPT */ + /* REQUIRED */ + break; + case 0x00000081: + /* BUGCHK */ + /* REQUIRED */ + break; + case 0x00000082: + /* CHME */ + break; + case 0x00000083: + /* CHMK */ + break; + case 0x00000084: + /* CHMS */ + break; + case 0x00000085: + /* CHMU */ + break; + case 0x00000086: + /* IMB */ + /* REQUIRED */ + /* Implemented as no-op */ + break; + case 0x00000087: + /* INSQHIL */ + break; + case 0x00000088: + /* INSQTIL */ + break; + case 0x00000089: + /* INSQHIQ */ + break; + case 0x0000008A: + /* INSQTIQ */ + break; + case 0x0000008B: + /* INSQUEL */ + break; + case 0x0000008C: + /* INSQUEQ */ + break; + case 0x0000008D: + /* INSQUEL/D */ + break; + case 0x0000008E: + /* INSQUEQ/D */ + break; + case 0x0000008F: + /* PROBER */ + break; + case 0x00000090: + /* PROBEW */ + break; + case 0x00000091: + /* RD_PS */ + break; + case 0x00000092: + /* REI */ + break; + case 0x00000093: + /* REMQHIL */ + break; + case 0x00000094: + /* REMQTIL */ + break; + case 0x00000095: + /* REMQHIQ */ + break; + case 0x00000096: + /* REMQTIQ */ + break; + case 0x00000097: + /* REMQUEL */ + break; + case 0x00000098: + /* REMQUEQ */ + break; + case 0x00000099: + /* REMQUEL/D */ + break; + case 0x0000009A: + /* REMQUEQ/D */ + break; + case 0x0000009B: + /* SWASTEN */ + break; + case 0x0000009C: + /* WR_PS_SW */ + break; + case 0x0000009D: + /* RSCC */ + break; + case 0x0000009E: + /* READ_UNQ */ + /* REQUIRED */ + break; + case 0x0000009F: + /* WRITE_UNQ */ + /* REQUIRED */ + break; + case 0x000000A0: + /* AMOVRR */ + break; + case 0x000000A1: + /* AMOVRM */ + break; + case 0x000000A2: + /* INSQHILR */ + break; + case 0x000000A3: + /* INSQTILR */ + break; + case 0x000000A4: + /* INSQHIQR */ + break; + case 0x000000A5: + /* INSQTIQR */ + break; + case 0x000000A6: + /* REMQHILR */ + break; + case 0x000000A7: + /* REMQTILR */ + break; + case 0x000000A8: + /* REMQHIQR */ + break; + case 0x000000A9: + /* REMQTIQR */ + break; + case 0x000000AA: + /* GENTRAP */ + /* REQUIRED */ + break; + case 0x000000AE: + /* CLRFEN */ + break; + default: + break; + } +} + +static void pal_unix_call (CPUState *env, uint32_t palcode) +{ + uint64_t palid, val, oldval; + + if (palcode < 0x00000080) { + /* Privileged palcodes */ + if (!(env->ps >> 3)) { + /* TODO: generate privilege exception */ + } + } + switch (palcode) { + case 0x00000000: + /* HALT */ + /* REQUIRED */ + break; + case 0x00000001: + /* CFLUSH */ + break; + case 0x00000002: + /* DRAINA */ + /* REQUIRED */ + /* Implemented as no-op */ + break; + case 0x00000009: + /* CSERVE */ + /* REQUIRED */ + break; + case 0x0000000A: + /* SWPPAL */ + /* REQUIRED */ + palid = env->ir[16]; + do_swappal(env, palid); + break; + case 0x0000000D: + /* WRIPIR */ + val = env->ir[16]; + if (cpu_alpha_mtpr(env, IPR_IPIR, val, &oldval) == 1) + env->ir[0] = val; + break; + case 0x00000010: + /* RDMCES */ + if (cpu_alpha_mfpr(env, IPR_MCES, &val) == 0) + env->ir[0] = val; + break; + case 0x00000011: + /* WRMCES */ + val = env->ir[16]; + if (cpu_alpha_mtpr(env, IPR_MCES, val, &oldval) == 1) + env->ir[0] = val; + break; + case 0x0000002B: + /* WRFEN */ + val = env->ir[16]; + if (cpu_alpha_mtpr(env, IPR_PERFMON, val, &oldval) == 1) + env->ir[0] = val; + break; + case 0x0000002D: + /* WRVPTPTR */ + break; + case 0x00000030: + /* SWPCTX */ + break; + case 0x00000031: + /* WRVAL */ + break; + case 0x00000032: + /* RDVAL */ + break; + case 0x00000033: + /* TBI */ + val = env->ir[16]; + if (cpu_alpha_mtpr(env, IPR_TBIS, val, &oldval) == 1) + env->ir[0] = val; + break; + case 0x00000034: + /* WRENT */ + break; + case 0x00000035: + /* SWPIPL */ + break; + case 0x00000036: + /* RDPS */ + break; + case 0x00000037: + /* WRKGP */ + break; + case 0x00000038: + /* WRUSP */ + val = env->ir[16]; + if (cpu_alpha_mtpr(env, IPR_USP, val, &oldval) == 1) + env->ir[0] = val; + break; + case 0x00000039: + /* WRPERFMON */ + val = env->ir[16]; + if (cpu_alpha_mtpr(env, IPR_PERFMON, val, &oldval) == 1) + env->ir[0] = val; + break; + case 0x0000003A: + /* RDUSP */ + if (cpu_alpha_mfpr(env, IPR_USP, &val) == 0) + env->ir[0] = val; + break; + case 0x0000003C: + /* WHAMI */ + if (cpu_alpha_mfpr(env, IPR_WHAMI, &val) == 0) + env->ir[0] = val; + break; + case 0x0000003D: + /* RETSYS */ + break; + case 0x0000003E: + /* WTINT */ + break; + case 0x0000003F: + /* RTI */ + if (cpu_alpha_mfpr(env, IPR_WHAMI, &val) == 0) + env->ir[0] = val; + break; + case 0x00000080: + /* BPT */ + /* REQUIRED */ + break; + case 0x00000081: + /* BUGCHK */ + /* REQUIRED */ + break; + case 0x00000083: + /* CALLSYS */ + break; + case 0x00000086: + /* IMB */ + /* REQUIRED */ + /* Implemented as no-op */ + break; + case 0x00000092: + /* URTI */ + break; + case 0x0000009E: + /* RDUNIQUE */ + /* REQUIRED */ + break; + case 0x0000009F: + /* WRUNIQUE */ + /* REQUIRED */ + break; + case 0x000000AA: + /* GENTRAP */ + /* REQUIRED */ + break; + case 0x000000AE: + /* CLRFEN */ + break; + default: + break; + } +} + +void call_pal (CPUState *env) +{ + pal_handler_t *pal_handler = env->pal_handler; + + switch (env->exception_index) { + case EXCP_RESET: + (*pal_handler->reset)(env); + break; + case EXCP_MCHK: + (*pal_handler->machine_check)(env); + break; + case EXCP_ARITH: + (*pal_handler->arithmetic)(env); + break; + case EXCP_INTERRUPT: + (*pal_handler->interrupt)(env); + break; + case EXCP_DFAULT: + (*pal_handler->dfault)(env); + break; + case EXCP_DTB_MISS_PAL: + (*pal_handler->dtb_miss_pal)(env); + break; + case EXCP_DTB_MISS_NATIVE: + (*pal_handler->dtb_miss_native)(env); + break; + case EXCP_UNALIGN: + (*pal_handler->unalign)(env); + break; + case EXCP_ITB_MISS: + (*pal_handler->itb_miss)(env); + break; + case EXCP_ITB_ACV: + (*pal_handler->itb_acv)(env); + break; + case EXCP_OPCDEC: + (*pal_handler->opcdec)(env); + break; + case EXCP_FEN: + (*pal_handler->fen)(env); + break; + default: + if (env->exception_index >= EXCP_CALL_PAL && + env->exception_index < EXCP_CALL_PALP) { + /* Unprivileged PAL call */ + (*pal_handler->call_pal) + (env, (env->exception_index - EXCP_CALL_PAL) >> 6); + } else if (env->exception_index >= EXCP_CALL_PALP && + env->exception_index < EXCP_CALL_PALE) { + /* Privileged PAL call */ + (*pal_handler->call_pal) + (env, ((env->exception_index - EXCP_CALL_PALP) >> 6) + 0x80); + } else { + /* Should never happen */ + } + break; + } + env->ipr[IPR_EXC_ADDR] &= ~1; +} + +void pal_init (CPUState *env) +{ + do_swappal(env, 0); +} + +#if 0 +static uint64_t get_ptebase (CPUState *env, uint64_t vaddr) +{ + uint64_t virbnd, ptbr; + + if ((env->features & FEATURE_VIRBND)) { + cpu_alpha_mfpr(env, IPR_VIRBND, &virbnd); + if (vaddr >= virbnd) + cpu_alpha_mfpr(env, IPR_SYSPTBR, &ptbr); + else + cpu_alpha_mfpr(env, IPR_PTBR, &ptbr); + } else { + cpu_alpha_mfpr(env, IPR_PTBR, &ptbr); + } + + return ptbr; +} + +static int get_page_bits (CPUState *env) +{ + /* XXX */ + return 13; +} + +static int get_pte (uint64_t *pfnp, int *zbitsp, int *protp, + uint64_t ptebase, int page_bits, uint64_t level, + int mmu_idx, int rw) +{ + uint64_t pteaddr, pte, pfn; + uint8_t gh; + int ure, uwe, kre, kwe, foE, foR, foW, v, ret, ar, is_user; + + /* XXX: TOFIX */ + is_user = mmu_idx == MMU_USER_IDX; + pteaddr = (ptebase << page_bits) + (8 * level); + pte = ldq_raw(pteaddr); + /* Decode all interresting PTE fields */ + pfn = pte >> 32; + uwe = (pte >> 13) & 1; + kwe = (pte >> 12) & 1; + ure = (pte >> 9) & 1; + kre = (pte >> 8) & 1; + gh = (pte >> 5) & 3; + foE = (pte >> 3) & 1; + foW = (pte >> 2) & 1; + foR = (pte >> 1) & 1; + v = pte & 1; + ret = 0; + if (!v) + ret = 0x1; + /* Check access rights */ + ar = 0; + if (is_user) { + if (ure) + ar |= PAGE_READ; + if (uwe) + ar |= PAGE_WRITE; + if (rw == 1 && !uwe) + ret |= 0x2; + if (rw != 1 && !ure) + ret |= 0x2; + } else { + if (kre) + ar |= PAGE_READ; + if (kwe) + ar |= PAGE_WRITE; + if (rw == 1 && !kwe) + ret |= 0x2; + if (rw != 1 && !kre) + ret |= 0x2; + } + if (rw == 0 && foR) + ret |= 0x4; + if (rw == 2 && foE) + ret |= 0x8; + if (rw == 1 && foW) + ret |= 0xC; + *pfnp = pfn; + if (zbitsp != NULL) + *zbitsp = page_bits + (3 * gh); + if (protp != NULL) + *protp = ar; + + return ret; +} + +static int paddr_from_pte (uint64_t *paddr, int *zbitsp, int *prot, + uint64_t ptebase, int page_bits, + uint64_t vaddr, int mmu_idx, int rw) +{ + uint64_t pfn, page_mask, lvl_mask, level1, level2, level3; + int lvl_bits, ret; + + page_mask = (1ULL << page_bits) - 1ULL; + lvl_bits = page_bits - 3; + lvl_mask = (1ULL << lvl_bits) - 1ULL; + level3 = (vaddr >> page_bits) & lvl_mask; + level2 = (vaddr >> (page_bits + lvl_bits)) & lvl_mask; + level1 = (vaddr >> (page_bits + (2 * lvl_bits))) & lvl_mask; + /* Level 1 PTE */ + ret = get_pte(&pfn, NULL, NULL, ptebase, page_bits, level1, 0, 0); + switch (ret) { + case 3: + /* Access violation */ + return 2; + case 2: + /* translation not valid */ + return 1; + default: + /* OK */ + break; + } + /* Level 2 PTE */ + ret = get_pte(&pfn, NULL, NULL, pfn, page_bits, level2, 0, 0); + switch (ret) { + case 3: + /* Access violation */ + return 2; + case 2: + /* translation not valid */ + return 1; + default: + /* OK */ + break; + } + /* Level 3 PTE */ + ret = get_pte(&pfn, zbitsp, prot, pfn, page_bits, level3, mmu_idx, rw); + if (ret & 0x1) { + /* Translation not valid */ + ret = 1; + } else if (ret & 2) { + /* Access violation */ + ret = 2; + } else { + switch (ret & 0xC) { + case 0: + /* OK */ + ret = 0; + break; + case 0x4: + /* Fault on read */ + ret = 3; + break; + case 0x8: + /* Fault on execute */ + ret = 4; + break; + case 0xC: + /* Fault on write */ + ret = 5; + break; + } + } + *paddr = (pfn << page_bits) | (vaddr & page_mask); + + return 0; +} + +static int virtual_to_physical (CPUState *env, uint64_t *physp, + int *zbitsp, int *protp, + uint64_t virtual, int mmu_idx, int rw) +{ + uint64_t sva, ptebase; + int seg, page_bits, ret; + + sva = ((int64_t)(virtual << (64 - VA_BITS))) >> (64 - VA_BITS); + if (sva != virtual) + seg = -1; + else + seg = sva >> (VA_BITS - 2); + virtual &= ~(0xFFFFFC0000000000ULL << (VA_BITS - 43)); + ptebase = get_ptebase(env, virtual); + page_bits = get_page_bits(env); + ret = 0; + switch (seg) { + case 0: + /* seg1: 3 levels of PTE */ + ret = paddr_from_pte(physp, zbitsp, protp, ptebase, page_bits, + virtual, mmu_idx, rw); + break; + case 1: + /* seg1: 2 levels of PTE */ + ret = paddr_from_pte(physp, zbitsp, protp, ptebase, page_bits, + virtual, mmu_idx, rw); + break; + case 2: + /* kernel segment */ + if (mmu_idx != 0) { + ret = 2; + } else { + *physp = virtual; + } + break; + case 3: + /* seg1: TB mapped */ + ret = paddr_from_pte(physp, zbitsp, protp, ptebase, page_bits, + virtual, mmu_idx, rw); + break; + default: + ret = 1; + break; + } + + return ret; +} + +/* XXX: code provision */ +int cpu_ppc_handle_mmu_fault (CPUState *env, uint32_t address, int rw, + int mmu_idx, int is_softmmu) +{ + uint64_t physical, page_size, end; + int prot, zbits, ret; + + ret = virtual_to_physical(env, &physical, &zbits, &prot, + address, mmu_idx, rw); + + switch (ret) { + case 0: + /* No fault */ + page_size = 1ULL << zbits; + address &= ~(page_size - 1); + /* FIXME: page_size should probably be passed to tlb_set_page, + and this loop removed. */ + for (end = physical + page_size; physical < end; physical += 0x1000) { + tlb_set_page(env, address, physical, prot, mmu_idx, + TARGET_PAGE_SIZE); + address += 0x1000; + } + ret = 0; + break; +#if 0 + case 1: + env->exception_index = EXCP_DFAULT; + env->ipr[IPR_EXC_ADDR] = address; + ret = 1; + break; + case 2: + env->exception_index = EXCP_ACCESS_VIOLATION; + env->ipr[IPR_EXC_ADDR] = address; + ret = 1; + break; + case 3: + env->exception_index = EXCP_FAULT_ON_READ; + env->ipr[IPR_EXC_ADDR] = address; + ret = 1; + break; + case 4: + env->exception_index = EXCP_FAULT_ON_EXECUTE; + env->ipr[IPR_EXC_ADDR] = address; + ret = 1; + case 5: + env->exception_index = EXCP_FAULT_ON_WRITE; + env->ipr[IPR_EXC_ADDR] = address; + ret = 1; +#endif + default: + /* Should never happen */ + env->exception_index = EXCP_MCHK; + env->ipr[IPR_EXC_ADDR] = address; + ret = 1; + break; + } + + return ret; +} +#endif diff --git a/hw/an5206.c b/hw/an5206.c new file mode 100644 index 0000000..b9f19a9 --- /dev/null +++ b/hw/an5206.c @@ -0,0 +1,101 @@ +/* + * Arnewsh 5206 ColdFire system emulation. + * + * Copyright (c) 2007 CodeSourcery. + * + * This code is licenced under the GPL + */ + +#include "hw.h" +#include "pc.h" +#include "mcf.h" +#include "sysemu.h" +#include "boards.h" +#include "loader.h" +#include "elf.h" + +#define KERNEL_LOAD_ADDR 0x10000 +#define AN5206_MBAR_ADDR 0x10000000 +#define AN5206_RAMBAR_ADDR 0x20000000 + +/* Stub functions for hardware that doesn't exist. */ +void pic_info(Monitor *mon) +{ +} + +void irq_info(Monitor *mon) +{ +} + +/* Board init. */ + +static void an5206_init(ram_addr_t ram_size, + const char *boot_device, + const char *kernel_filename, const char *kernel_cmdline, + const char *initrd_filename, const char *cpu_model) +{ + CPUState *env; + int kernel_size; + uint64_t elf_entry; + target_phys_addr_t entry; + + if (!cpu_model) + cpu_model = "m5206"; + env = cpu_init(cpu_model); + if (!env) { + hw_error("Unable to find m68k CPU definition\n"); + } + + /* 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 */ + cpu_register_physical_memory(0, ram_size, + qemu_ram_alloc(NULL, "an5206.ram", ram_size) | IO_MEM_RAM); + + /* Internal SRAM. */ + cpu_register_physical_memory(AN5206_RAMBAR_ADDR, 512, + qemu_ram_alloc(NULL, "an5206.sram", 512) | IO_MEM_RAM); + + mcf5206_init(AN5206_MBAR_ADDR, env); + + /* Load kernel. */ + if (!kernel_filename) { + 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); + } + 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/apb_pci.c b/hw/apb_pci.c new file mode 100644 index 0000000..84e9af7 --- /dev/null +++ b/hw/apb_pci.c @@ -0,0 +1,482 @@ +/* + * QEMU Ultrasparc APB PCI host + * + * 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. + */ + +/* 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 "sysbus.h" +#include "pci.h" +#include "pci_host.h" +#include "pci_bridge.h" +#include "pci_internals.h" +#include "rwhandler.h" +#include "apb_pci.h" +#include "sysemu.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 + +/* + * 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 (1 << 31) +#define SOFT_POR (1 << 30) +#define SOFT_XIR (1 << 29) +#define BTN_POR (1 << 28) +#define BTN_XIR (1 << 27) +#define RESET_MASK 0xf8000000 +#define RESET_WCMASK 0x98000000 +#define RESET_WMASK 0x60000000 + +typedef struct APBState { + SysBusDevice busdev; + PCIBus *bus; + ReadWriteHandler pci_config_handler; + uint32_t iommu[4]; + uint32_t pci_control[16]; + uint32_t pci_irq_map[8]; + uint32_t obio_irq_map[32]; + qemu_irq pci_irqs[32]; + uint32_t reset_control; + unsigned int nr_resets; +} APBState; + +static void apb_config_writel (void *opaque, target_phys_addr_t addr, + uint32_t val) +{ + APBState *s = opaque; + + APB_DPRINTF("%s: addr " TARGET_FMT_lx " val %x\n", __func__, addr, val); + + switch (addr & 0xffff) { + case 0x30 ... 0x4f: /* DMA error registers */ + /* XXX: not implemented yet */ + break; + case 0x200 ... 0x20b: /* IOMMU */ + s->iommu[(addr & 0xf) >> 2] = val; + break; + case 0x20c ... 0x3ff: /* IOMMU flush */ + break; + case 0xc00 ... 0xc3f: /* PCI interrupt control */ + if (addr & 4) { + s->pci_irq_map[(addr & 0x3f) >> 3] &= PBM_PCI_IMR_MASK; + s->pci_irq_map[(addr & 0x3f) >> 3] |= val & ~PBM_PCI_IMR_MASK; + } + 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 uint32_t apb_config_readl (void *opaque, + target_phys_addr_t addr) +{ + APBState *s = opaque; + uint32_t val; + + switch (addr & 0xffff) { + case 0x30 ... 0x4f: /* DMA error registers */ + val = 0; + /* XXX: not implemented yet */ + break; + case 0x200 ... 0x20b: /* IOMMU */ + val = s->iommu[(addr & 0xf) >> 2]; + break; + case 0x20c ... 0x3ff: /* IOMMU flush */ + val = 0; + break; + case 0xc00 ... 0xc3f: /* PCI interrupt control */ + if (addr & 4) { + val = s->pci_irq_map[(addr & 0x3f) >> 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_lx " -> %x\n", __func__, addr, val); + + return val; +} + +static CPUWriteMemoryFunc * const apb_config_write[] = { + &apb_config_writel, + &apb_config_writel, + &apb_config_writel, +}; + +static CPUReadMemoryFunc * const apb_config_read[] = { + &apb_config_readl, + &apb_config_readl, + &apb_config_readl, +}; + +static void apb_pci_config_write(ReadWriteHandler *h, pcibus_t addr, + uint32_t val, int size) +{ + APBState *s = container_of(h, APBState, pci_config_handler); + + val = qemu_bswap_len(val, size); + APB_DPRINTF("%s: addr " TARGET_FMT_lx " val %x\n", __func__, addr, val); + pci_data_write(s->bus, addr, val, size); +} + +static uint32_t apb_pci_config_read(ReadWriteHandler *h, pcibus_t addr, + int size) +{ + uint32_t ret; + APBState *s = container_of(h, APBState, pci_config_handler); + + ret = pci_data_read(s->bus, addr, size); + ret = qemu_bswap_len(ret, size); + APB_DPRINTF("%s: addr " TARGET_FMT_lx " -> %x\n", __func__, addr, ret); + return ret; +} + +static void pci_apb_iowriteb (void *opaque, target_phys_addr_t addr, + uint32_t val) +{ + cpu_outb(addr & IOPORTS_MASK, val); +} + +static void pci_apb_iowritew (void *opaque, target_phys_addr_t addr, + uint32_t val) +{ + cpu_outw(addr & IOPORTS_MASK, bswap16(val)); +} + +static void pci_apb_iowritel (void *opaque, target_phys_addr_t addr, + uint32_t val) +{ + cpu_outl(addr & IOPORTS_MASK, bswap32(val)); +} + +static uint32_t pci_apb_ioreadb (void *opaque, target_phys_addr_t addr) +{ + uint32_t val; + + val = cpu_inb(addr & IOPORTS_MASK); + return val; +} + +static uint32_t pci_apb_ioreadw (void *opaque, target_phys_addr_t addr) +{ + uint32_t val; + + val = bswap16(cpu_inw(addr & IOPORTS_MASK)); + return val; +} + +static uint32_t pci_apb_ioreadl (void *opaque, target_phys_addr_t addr) +{ + uint32_t val; + + val = bswap32(cpu_inl(addr & IOPORTS_MASK)); + return val; +} + +static CPUWriteMemoryFunc * const pci_apb_iowrite[] = { + &pci_apb_iowriteb, + &pci_apb_iowritew, + &pci_apb_iowritel, +}; + +static CPUReadMemoryFunc * const pci_apb_ioread[] = { + &pci_apb_ioreadb, + &pci_apb_ioreadw, + &pci_apb_ioreadl, +}; + +/* 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 + irq_num; +} + +static void pci_apb_set_irq(void *opaque, int irq_num, int level) +{ + APBState *s = opaque; + + /* PCI IRQ map onto the first 32 INO. */ + if (irq_num < 32) { + if (s->pci_irq_map[irq_num >> 2] & PBM_PCI_IMR_ENABLED) { + APB_DPRINTF("%s: set irq %d level %d\n", __func__, irq_num, level); + qemu_set_irq(s->pci_irqs[irq_num], level); + } else { + APB_DPRINTF("%s: not enabled: lower irq %d\n", __func__, irq_num); + qemu_irq_lower(s->pci_irqs[irq_num]); + } + } +} + +static int apb_pci_bridge_initfn(PCIDevice *dev) +{ + int rc; + + rc = pci_bridge_initfn(dev); + if (rc < 0) { + return rc; + } + + pci_config_set_vendor_id(dev->config, PCI_VENDOR_ID_SUN); + pci_config_set_device_id(dev->config, PCI_DEVICE_ID_SUN_SIMBA); + + /* + * 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); + pci_set_byte(dev->config + PCI_REVISION_ID, 0x11); + return 0; +} + +PCIBus *pci_apb_init(target_phys_addr_t special_base, + target_phys_addr_t mem_base, + qemu_irq *pic, PCIBus **bus2, PCIBus **bus3) +{ + DeviceState *dev; + SysBusDevice *s; + APBState *d; + unsigned int i; + PCIDevice *pci_dev; + PCIBridge *br; + + /* Ultrasparc PBM main bus */ + dev = qdev_create(NULL, "pbm"); + qdev_init_nofail(dev); + s = sysbus_from_qdev(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 = FROM_SYSBUS(APBState, s); + + d->bus = pci_register_bus(&d->busdev.qdev, "pci", + pci_apb_set_irq, pci_pbm_map_irq, d, + 0, 32); + pci_bus_set_mem_base(d->bus, mem_base); + + for (i = 0; i < 32; i++) { + sysbus_connect_irq(s, i, pic[i]); + } + + pci_create_simple(d->bus, 0, "pbm"); + + /* APB secondary busses */ + pci_dev = pci_create_multifunction(d->bus, PCI_DEVFN(1, 0), true, + "pbm-bridge"); + br = DO_UPCAST(PCIBridge, dev, 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(d->bus, PCI_DEVFN(1, 1), true, + "pbm-bridge"); + br = DO_UPCAST(PCIBridge, dev, 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 d->bus; +} + +static void pci_pbm_reset(DeviceState *d) +{ + unsigned int i; + APBState *s = container_of(d, APBState, busdev.qdev); + + for (i = 0; i < 8; i++) { + s->pci_irq_map[i] &= PBM_PCI_IMR_MASK; + } + + if (s->nr_resets++ == 0) { + /* Power on reset */ + s->reset_control = POR; + } +} + +static int pci_pbm_init_device(SysBusDevice *dev) +{ + APBState *s; + int pci_config, apb_config, pci_ioport; + unsigned int i; + + s = FROM_SYSBUS(APBState, dev); + for (i = 0; i < 8; i++) { + s->pci_irq_map[i] = (0x1f << 6) | (i << 2); + } + for (i = 0; i < 32; i++) { + sysbus_init_irq(dev, &s->pci_irqs[i]); + } + + /* apb_config */ + apb_config = cpu_register_io_memory(apb_config_read, + apb_config_write, s, + DEVICE_NATIVE_ENDIAN); + /* at region 0 */ + sysbus_init_mmio(dev, 0x10000ULL, apb_config); + + /* PCI configuration space */ + s->pci_config_handler.read = apb_pci_config_read; + s->pci_config_handler.write = apb_pci_config_write; + pci_config = cpu_register_io_memory_simple(&s->pci_config_handler, + DEVICE_NATIVE_ENDIAN); + assert(pci_config >= 0); + /* at region 1 */ + sysbus_init_mmio(dev, 0x1000000ULL, pci_config); + + /* pci_ioport */ + pci_ioport = cpu_register_io_memory(pci_apb_ioread, + pci_apb_iowrite, s, + DEVICE_NATIVE_ENDIAN); + /* at region 2 */ + sysbus_init_mmio(dev, 0x10000ULL, pci_ioport); + + return 0; +} + +static int pbm_pci_host_init(PCIDevice *d) +{ + pci_config_set_vendor_id(d->config, PCI_VENDOR_ID_SUN); + pci_config_set_device_id(d->config, PCI_DEVICE_ID_SUN_SABRE); + 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); + pci_config_set_class(d->config, PCI_CLASS_BRIDGE_HOST); + return 0; +} + +static PCIDeviceInfo pbm_pci_host_info = { + .qdev.name = "pbm", + .qdev.size = sizeof(PCIDevice), + .init = pbm_pci_host_init, + .is_bridge = 1, +}; + +static SysBusDeviceInfo pbm_host_info = { + .qdev.name = "pbm", + .qdev.size = sizeof(APBState), + .qdev.reset = pci_pbm_reset, + .init = pci_pbm_init_device, +}; + +static PCIDeviceInfo pbm_pci_bridge_info = { + .qdev.name = "pbm-bridge", + .qdev.size = sizeof(PCIBridge), + .qdev.vmsd = &vmstate_pci_device, + .qdev.reset = pci_bridge_reset, + .init = apb_pci_bridge_initfn, + .exit = pci_bridge_exitfn, + .config_write = pci_bridge_write_config, + .is_bridge = 1, +}; + +static void pbm_register_devices(void) +{ + sysbus_register_withprop(&pbm_host_info); + pci_qdev_register(&pbm_pci_host_info); + pci_qdev_register(&pbm_pci_bridge_info); +} + +device_init(pbm_register_devices) diff --git a/hw/apb_pci.h b/hw/apb_pci.h new file mode 100644 index 0000000..8869f9d --- /dev/null +++ b/hw/apb_pci.h @@ -0,0 +1,9 @@ +#ifndef APB_PCI_H +#define APB_PCI_H + +#include "qemu-common.h" + +PCIBus *pci_apb_init(target_phys_addr_t special_base, + target_phys_addr_t mem_base, + qemu_irq *pic, PCIBus **bus2, PCIBus **bus3); +#endif diff --git a/hw/apic.c b/hw/apic.c new file mode 100644 index 0000000..218d1bb --- /dev/null +++ b/hw/apic.c @@ -0,0 +1,1033 @@ +/* + * 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 + */ +#include "hw.h" +#include "apic.h" +#include "ioapic.h" +#include "qemu-timer.h" +#include "host-utils.h" +#include "sysbus.h" +#include "trace.h" + +/* APIC Local Vector Table */ +#define APIC_LVT_TIMER 0 +#define APIC_LVT_THERMAL 1 +#define APIC_LVT_PERFORM 2 +#define APIC_LVT_LINT0 3 +#define APIC_LVT_LINT1 4 +#define APIC_LVT_ERROR 5 +#define APIC_LVT_NB 6 + +/* APIC delivery modes */ +#define APIC_DM_FIXED 0 +#define APIC_DM_LOWPRI 1 +#define APIC_DM_SMI 2 +#define APIC_DM_NMI 4 +#define APIC_DM_INIT 5 +#define APIC_DM_SIPI 6 +#define APIC_DM_EXTINT 7 + +/* APIC destination mode */ +#define APIC_DESTMODE_FLAT 0xf +#define APIC_DESTMODE_CLUSTER 1 + +#define APIC_TRIGGER_EDGE 0 +#define APIC_TRIGGER_LEVEL 1 + +#define APIC_LVT_TIMER_PERIODIC (1<<17) +#define APIC_LVT_MASKED (1<<16) +#define APIC_LVT_LEVEL_TRIGGER (1<<15) +#define APIC_LVT_REMOTE_IRR (1<<14) +#define APIC_INPUT_POLARITY (1<<13) +#define APIC_SEND_PENDING (1<<12) + +#define ESR_ILLEGAL_ADDRESS (1 << 7) + +#define APIC_SV_DIRECTED_IO (1<<12) +#define APIC_SV_ENABLE (1<<8) + +#define MAX_APICS 255 +#define MAX_APIC_WORDS 8 + +/* Intel APIC constants: from include/asm/msidef.h */ +#define MSI_DATA_VECTOR_SHIFT 0 +#define MSI_DATA_VECTOR_MASK 0x000000ff +#define MSI_DATA_DELIVERY_MODE_SHIFT 8 +#define MSI_DATA_TRIGGER_SHIFT 15 +#define MSI_DATA_LEVEL_SHIFT 14 +#define MSI_ADDR_DEST_MODE_SHIFT 2 +#define MSI_ADDR_DEST_ID_SHIFT 12 +#define MSI_ADDR_DEST_ID_MASK 0x00ffff0 + +#define MSI_ADDR_SIZE 0x100000 + +typedef struct APICState APICState; + +struct APICState { + SysBusDevice busdev; + void *cpu_env; + uint32_t apicbase; + uint8_t id; + uint8_t arb_id; + uint8_t tpr; + uint32_t spurious_vec; + uint8_t log_dest; + uint8_t dest_mode; + uint32_t isr[8]; /* in service register */ + uint32_t tmr[8]; /* trigger mode register */ + uint32_t irr[8]; /* interrupt request register */ + uint32_t lvt[APIC_LVT_NB]; + uint32_t esr; /* error register */ + uint32_t icr[2]; + + uint32_t divide_conf; + int count_shift; + uint32_t initial_count; + int64_t initial_count_load_time, next_time; + uint32_t idx; + QEMUTimer *timer; + int sipi_vector; + int wait_for_sipi; +}; + +static APICState *local_apics[MAX_APICS + 1]; +static int apic_irq_delivered; + +static void apic_set_irq(APICState *s, int vector_num, int trigger_mode); +static void apic_update_irq(APICState *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 fls_bit(uint32_t value) +{ + return 31 - clz32(value); +} + +/* Find first bit starting from lsb */ +static int ffs_bit(uint32_t value) +{ + return ctz32(value); +} + +static inline void set_bit(uint32_t *tab, int index) +{ + int i, mask; + i = index >> 5; + mask = 1 << (index & 0x1f); + tab[i] |= mask; +} + +static inline void reset_bit(uint32_t *tab, int index) +{ + int i, mask; + i = index >> 5; + mask = 1 << (index & 0x1f); + tab[i] &= ~mask; +} + +static inline int get_bit(uint32_t *tab, int index) +{ + int i, mask; + i = index >> 5; + mask = 1 << (index & 0x1f); + return !!(tab[i] & mask); +} + +static void apic_local_deliver(APICState *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(s->cpu_env, CPU_INTERRUPT_SMI); + break; + + case APIC_DM_NMI: + cpu_interrupt(s->cpu_env, CPU_INTERRUPT_NMI); + break; + + case APIC_DM_EXTINT: + cpu_interrupt(s->cpu_env, 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 *d, int level) +{ + APICState *s = DO_UPCAST(APICState, busdev.qdev, d); + + 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; + reset_bit(s->irr, lvt & 0xff); + /* fall through */ + case APIC_DM_EXTINT: + cpu_reset_interrupt(s->cpu_env, CPU_INTERRUPT_HARD); + break; + } + } +} + +#define foreach_apic(apic, deliver_bitmask, code) \ +{\ + int __i, __j, __mask;\ + for(__i = 0; __i < MAX_APIC_WORDS; __i++) {\ + __mask = deliver_bitmask[__i];\ + if (__mask) {\ + for(__j = 0; __j < 32; __j++) {\ + if (__mask & (1 << __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 polarity, + uint8_t trigger_mode) +{ + APICState *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 + 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(apic_iter->cpu_env, CPU_INTERRUPT_SMI) ); + return; + + case APIC_DM_NMI: + foreach_apic(apic_iter, deliver_bitmask, + cpu_interrupt(apic_iter->cpu_env, CPU_INTERRUPT_NMI) ); + return; + + case APIC_DM_INIT: + /* normal INIT IPI sent to processors */ + foreach_apic(apic_iter, deliver_bitmask, + cpu_interrupt(apic_iter->cpu_env, 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 polarity, uint8_t trigger_mode) +{ + uint32_t deliver_bitmask[MAX_APIC_WORDS]; + + trace_apic_deliver_irq(dest, dest_mode, delivery_mode, vector_num, + polarity, trigger_mode); + + apic_get_delivery_bitmask(deliver_bitmask, dest, dest_mode); + apic_bus_deliver(deliver_bitmask, delivery_mode, vector_num, polarity, + trigger_mode); +} + +void cpu_set_apic_base(DeviceState *d, uint64_t val) +{ + APICState *s = DO_UPCAST(APICState, busdev.qdev, d); + + trace_cpu_set_apic_base(val); + + if (!s) + return; + 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; + } +} + +uint64_t cpu_get_apic_base(DeviceState *d) +{ + APICState *s = DO_UPCAST(APICState, busdev.qdev, d); + + trace_cpu_get_apic_base(s ? (uint64_t)s->apicbase: 0); + + return s ? s->apicbase : 0; +} + +void cpu_set_apic_tpr(DeviceState *d, uint8_t val) +{ + APICState *s = DO_UPCAST(APICState, busdev.qdev, d); + + if (!s) + return; + s->tpr = (val & 0x0f) << 4; + apic_update_irq(s); +} + +uint8_t cpu_get_apic_tpr(DeviceState *d) +{ + APICState *s = DO_UPCAST(APICState, busdev.qdev, d); + + return s ? s->tpr >> 4 : 0; +} + +/* 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 + fls_bit(tab[i]); + } + } + return -1; +} + +static int apic_get_ppr(APICState *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(APICState *s) +{ + /* XXX: arbitration */ + return 0; +} + + +/* + * <0 - low prio interrupt, + * 0 - no interrupt, + * >0 - interrupt number + */ +static int apic_irq_pending(APICState *s) +{ + int irrv, ppr; + 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(APICState *s) +{ + if (!(s->spurious_vec & APIC_SV_ENABLE)) { + return; + } + if (apic_irq_pending(s) > 0) { + cpu_interrupt(s->cpu_env, CPU_INTERRUPT_HARD); + } +} + +void apic_reset_irq_delivered(void) +{ + trace_apic_reset_irq_delivered(apic_irq_delivered); + + apic_irq_delivered = 0; +} + +int apic_get_irq_delivered(void) +{ + trace_apic_get_irq_delivered(apic_irq_delivered); + + return apic_irq_delivered; +} + +static void apic_set_irq(APICState *s, int vector_num, int trigger_mode) +{ + apic_irq_delivered += !get_bit(s->irr, vector_num); + + trace_apic_set_irq(apic_irq_delivered); + + set_bit(s->irr, vector_num); + if (trigger_mode) + set_bit(s->tmr, vector_num); + else + reset_bit(s->tmr, vector_num); + apic_update_irq(s); +} + +static void apic_eoi(APICState *s) +{ + int isrv; + isrv = get_highest_priority_int(s->isr); + if (isrv < 0) + return; + reset_bit(s->isr, isrv); + if (!(s->spurious_vec & APIC_SV_DIRECTED_IO) && get_bit(s->tmr, isrv)) { + ioapic_eoi_broadcast(isrv); + } + apic_update_irq(s); +} + +static int apic_find_dest(uint8_t dest) +{ + APICState *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) +{ + APICState *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) + 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) + 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)) { + set_bit(deliver_bitmask, i); + } + } + } else { + break; + } + } + } +} + +void apic_init_reset(DeviceState *d) +{ + APICState *s = DO_UPCAST(APICState, busdev.qdev, d); + int i; + + if (!s) + return; + + 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] = 1 << 16; /* mask LVT */ + 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 = 1; +} + +static void apic_startup(APICState *s, int vector_num) +{ + s->sipi_vector = vector_num; + cpu_interrupt(s->cpu_env, CPU_INTERRUPT_SIPI); +} + +void apic_sipi(DeviceState *d) +{ + APICState *s = DO_UPCAST(APICState, busdev.qdev, d); + + cpu_reset_interrupt(s->cpu_env, CPU_INTERRUPT_SIPI); + + if (!s->wait_for_sipi) + return; + cpu_x86_load_seg_cache_sipi(s->cpu_env, s->sipi_vector); + s->wait_for_sipi = 0; +} + +static void apic_deliver(DeviceState *d, uint8_t dest, uint8_t dest_mode, + uint8_t delivery_mode, uint8_t vector_num, + uint8_t polarity, uint8_t trigger_mode) +{ + APICState *s = DO_UPCAST(APICState, busdev.qdev, d); + uint32_t deliver_bitmask[MAX_APIC_WORDS]; + int dest_shorthand = (s->icr[0] >> 18) & 3; + APICState *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)); + 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)); + 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, polarity, + trigger_mode); +} + +int apic_get_interrupt(DeviceState *d) +{ + APICState *s = DO_UPCAST(APICState, busdev.qdev, d); + 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; + + intno = apic_irq_pending(s); + + if (intno == 0) { + return -1; + } else if (intno < 0) { + return s->spurious_vec & 0xff; + } + reset_bit(s->irr, intno); + set_bit(s->isr, intno); + apic_update_irq(s); + return intno; +} + +int apic_accept_pic_intr(DeviceState *d) +{ + APICState *s = DO_UPCAST(APICState, busdev.qdev, d); + 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(APICState *s) +{ + int64_t d; + uint32_t val; + d = (qemu_get_clock(vm_clock) - 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(APICState *s, int64_t current_time) +{ + int64_t next_time, d; + + if (!(s->lvt[APIC_LVT_TIMER] & APIC_LVT_MASKED)) { + 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) + goto no_timer; + d = ((d / ((uint64_t)s->initial_count + 1)) + 1) * ((uint64_t)s->initial_count + 1); + } else { + if (d >= s->initial_count) + goto no_timer; + d = (uint64_t)s->initial_count + 1; + } + next_time = s->initial_count_load_time + (d << s->count_shift); + qemu_mod_timer(s->timer, next_time); + s->next_time = next_time; + } else { + no_timer: + qemu_del_timer(s->timer); + } +} + +static void apic_timer(void *opaque) +{ + APICState *s = opaque; + + apic_local_deliver(s, APIC_LVT_TIMER); + apic_timer_update(s, s->next_time); +} + +static uint32_t apic_mem_readb(void *opaque, target_phys_addr_t addr) +{ + return 0; +} + +static uint32_t apic_mem_readw(void *opaque, target_phys_addr_t addr) +{ + return 0; +} + +static void apic_mem_writeb(void *opaque, target_phys_addr_t addr, uint32_t val) +{ +} + +static void apic_mem_writew(void *opaque, target_phys_addr_t addr, uint32_t val) +{ +} + +static uint32_t apic_mem_readl(void *opaque, target_phys_addr_t addr) +{ + DeviceState *d; + APICState *s; + uint32_t val; + int index; + + d = cpu_get_current_apic(); + if (!d) { + return 0; + } + s = DO_UPCAST(APICState, busdev.qdev, d); + + index = (addr >> 4) & 0xff; + switch(index) { + case 0x02: /* id */ + val = s->id << 24; + break; + case 0x03: /* version */ + val = 0x11 | ((APIC_LVT_NB - 1) << 16); /* version 0x11 */ + break; + case 0x08: + 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; + 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(target_phys_addr_t 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, 0, trigger_mode); +} + +static void apic_mem_writel(void *opaque, target_phys_addr_t addr, uint32_t val) +{ + DeviceState *d; + APICState *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; + } + + d = cpu_get_current_apic(); + if (!d) { + return; + } + s = DO_UPCAST(APICState, busdev.qdev, d); + + trace_apic_mem_writel(addr, val); + + switch(index) { + case 0x02: + s->id = (val >> 24); + break; + case 0x03: + break; + case 0x08: + s->tpr = val; + 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(d, (s->icr[1] >> 24) & 0xff, (s->icr[0] >> 11) & 1, + (s->icr[0] >> 8) & 7, (s->icr[0] & 0xff), + (s->icr[0] >> 14) & 1, (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_get_clock(vm_clock)); + } + break; + case 0x38: + s->initial_count = val; + s->initial_count_load_time = qemu_get_clock(vm_clock); + 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; + } +} + +/* This function is only used for old state version 1 and 2 */ +static int apic_load_old(QEMUFile *f, void *opaque, int version_id) +{ + APICState *s = opaque; + 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) + qemu_get_timer(f, s->timer); + return 0; +} + +static const VMStateDescription vmstate_apic = { + .name = "apic", + .version_id = 3, + .minimum_version_id = 3, + .minimum_version_id_old = 1, + .load_state_old = apic_load_old, + .fields = (VMStateField []) { + VMSTATE_UINT32(apicbase, APICState), + VMSTATE_UINT8(id, APICState), + VMSTATE_UINT8(arb_id, APICState), + VMSTATE_UINT8(tpr, APICState), + VMSTATE_UINT32(spurious_vec, APICState), + VMSTATE_UINT8(log_dest, APICState), + VMSTATE_UINT8(dest_mode, APICState), + VMSTATE_UINT32_ARRAY(isr, APICState, 8), + VMSTATE_UINT32_ARRAY(tmr, APICState, 8), + VMSTATE_UINT32_ARRAY(irr, APICState, 8), + VMSTATE_UINT32_ARRAY(lvt, APICState, APIC_LVT_NB), + VMSTATE_UINT32(esr, APICState), + VMSTATE_UINT32_ARRAY(icr, APICState, 2), + VMSTATE_UINT32(divide_conf, APICState), + VMSTATE_INT32(count_shift, APICState), + VMSTATE_UINT32(initial_count, APICState), + VMSTATE_INT64(initial_count_load_time, APICState), + VMSTATE_INT64(next_time, APICState), + VMSTATE_TIMER(timer, APICState), + VMSTATE_END_OF_LIST() + } +}; + +static void apic_reset(DeviceState *d) +{ + APICState *s = DO_UPCAST(APICState, busdev.qdev, d); + int bsp; + + bsp = cpu_is_bsp(s->cpu_env); + s->apicbase = 0xfee00000 | + (bsp ? MSR_IA32_APICBASE_BSP : 0) | MSR_IA32_APICBASE_ENABLE; + + apic_init_reset(d); + + if (bsp) { + /* + * LINT0 delivery mode on CPU #0 is set to ExtInt at initialization + * time typically by BIOS, so PIC interrupt can be delivered to the + * processor when local APIC is enabled. + */ + s->lvt[APIC_LVT_LINT0] = 0x700; + } +} + +static CPUReadMemoryFunc * const apic_mem_read[3] = { + apic_mem_readb, + apic_mem_readw, + apic_mem_readl, +}; + +static CPUWriteMemoryFunc * const apic_mem_write[3] = { + apic_mem_writeb, + apic_mem_writew, + apic_mem_writel, +}; + +static int apic_init1(SysBusDevice *dev) +{ + APICState *s = FROM_SYSBUS(APICState, dev); + int apic_io_memory; + static int last_apic_idx; + + if (last_apic_idx >= MAX_APICS) { + return -1; + } + apic_io_memory = cpu_register_io_memory(apic_mem_read, + apic_mem_write, NULL, + DEVICE_NATIVE_ENDIAN); + sysbus_init_mmio(dev, MSI_ADDR_SIZE, apic_io_memory); + + s->timer = qemu_new_timer(vm_clock, apic_timer, s); + s->idx = last_apic_idx++; + local_apics[s->idx] = s; + return 0; +} + +static SysBusDeviceInfo apic_info = { + .init = apic_init1, + .qdev.name = "apic", + .qdev.size = sizeof(APICState), + .qdev.vmsd = &vmstate_apic, + .qdev.reset = apic_reset, + .qdev.no_user = 1, + .qdev.props = (Property[]) { + DEFINE_PROP_UINT8("id", APICState, id, -1), + DEFINE_PROP_PTR("cpu_env", APICState, cpu_env), + DEFINE_PROP_END_OF_LIST(), + } +}; + +static void apic_register_devices(void) +{ + sysbus_register_withprop(&apic_info); +} + +device_init(apic_register_devices) diff --git a/hw/apic.h b/hw/apic.h new file mode 100644 index 0000000..8a0c9d0 --- /dev/null +++ b/hw/apic.h @@ -0,0 +1,27 @@ +#ifndef APIC_H +#define APIC_H + +#include "qemu-common.h" + +/* apic.c */ +void apic_deliver_irq(uint8_t dest, uint8_t dest_mode, + uint8_t delivery_mode, + uint8_t vector_num, uint8_t polarity, + uint8_t trigger_mode); +int apic_accept_pic_intr(DeviceState *s); +void apic_deliver_pic_intr(DeviceState *s, int level); +int apic_get_interrupt(DeviceState *s); +void apic_reset_irq_delivered(void); +int apic_get_irq_delivered(void); +void cpu_set_apic_base(DeviceState *s, uint64_t val); +uint64_t cpu_get_apic_base(DeviceState *s); +void cpu_set_apic_tpr(DeviceState *s, uint8_t val); +uint8_t cpu_get_apic_tpr(DeviceState *s); +void apic_init_reset(DeviceState *s); +void apic_sipi(DeviceState *s); + +/* pc.c */ +int cpu_is_bsp(CPUState *env); +DeviceState *cpu_get_current_apic(void); + +#endif diff --git a/hw/apm.c b/hw/apm.c new file mode 100644 index 0000000..cdda72f --- /dev/null +++ b/hw/apm.c @@ -0,0 +1,86 @@ +/* + * 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 + */ + +#include "apm.h" +#include "hw.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, uint32_t addr, uint32_t val) +{ + APMState *apm = opaque; + addr &= 1; + APM_DPRINTF("apm_ioport_writeb addr=0x%x val=0x%02x\n", addr, val); + if (addr == 0) { + apm->apmc = val; + + if (apm->callback) { + (apm->callback)(val, apm->arg); + } + } else { + apm->apms = val; + } +} + +static uint32_t apm_ioport_readb(void *opaque, uint32_t addr) +{ + 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%x val=0x%02x\n", addr, val); + return val; +} + +const VMStateDescription vmstate_apm = { + .name = "APM State", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT8(apmc, APMState), + VMSTATE_UINT8(apms, APMState), + VMSTATE_END_OF_LIST() + } +}; + +void apm_init(APMState *apm, apm_ctrl_changed_t callback, void *arg) +{ + apm->callback = callback; + apm->arg = arg; + + /* ioport 0xb2, 0xb3 */ + register_ioport_write(APM_CNT_IOPORT, 2, 1, apm_ioport_writeb, apm); + register_ioport_read(APM_CNT_IOPORT, 2, 1, apm_ioport_readb, apm); +} diff --git a/hw/apm.h b/hw/apm.h new file mode 100644 index 0000000..f7c741e --- /dev/null +++ b/hw/apm.h @@ -0,0 +1,22 @@ +#ifndef APM_H +#define APM_H + +#include +#include "qemu-common.h" +#include "hw.h" + +typedef void (*apm_ctrl_changed_t)(uint32_t val, void *arg); + +typedef struct APMState { + uint8_t apmc; + uint8_t apms; + + apm_ctrl_changed_t callback; + void *arg; +} APMState; + +void apm_init(APMState *s, apm_ctrl_changed_t callback, void *arg); + +extern const VMStateDescription vmstate_apm; + +#endif /* APM_H */ diff --git a/hw/applesmc.c b/hw/applesmc.c new file mode 100644 index 0000000..23ed328 --- /dev/null +++ b/hw/applesmc.c @@ -0,0 +1,241 @@ +/* + * Apple SMC controller + * + * Copyright (c) 2007 Alexander Graf + * + * Authors: Alexander Graf + * Susanne Graf + * + * 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 . + * + * ***************************************************************** + * + * 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.h" +#include "isa.h" +#include "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_MAX_DATA_LENGTH 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; +}; + +struct AppleSMCStatus { + ISADevice dev; + 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_writeb(void *opaque, uint32_t addr, uint32_t val) +{ + struct AppleSMCStatus *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(struct AppleSMCStatus *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_writeb(void *opaque, uint32_t addr, uint32_t val) +{ + struct AppleSMCStatus *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 uint32_t applesmc_io_data_readb(void *opaque, uint32_t addr1) +{ + struct AppleSMCStatus *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 uint32_t applesmc_io_cmd_readb(void *opaque, uint32_t addr1) +{ + struct AppleSMCStatus *s = opaque; + + smc_debug("CMD Read B: %#x\n", addr1); + return s->status; +} + +static void applesmc_add_key(struct AppleSMCStatus *s, const char *key, + int len, const char *data) +{ + struct AppleSMCData *def; + + def = qemu_mallocz(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) +{ + struct AppleSMCStatus *s = DO_UPCAST(struct AppleSMCStatus, dev.qdev, 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 int applesmc_isa_init(ISADevice *dev) +{ + struct AppleSMCStatus *s = DO_UPCAST(struct AppleSMCStatus, dev, dev); + + register_ioport_read(s->iobase + APPLESMC_DATA_PORT, 4, 1, + applesmc_io_data_readb, s); + register_ioport_read(s->iobase + APPLESMC_CMD_PORT, 4, 1, + applesmc_io_cmd_readb, s); + register_ioport_write(s->iobase + APPLESMC_DATA_PORT, 4, 1, + applesmc_io_data_writeb, s); + register_ioport_write(s->iobase + APPLESMC_CMD_PORT, 4, 1, + applesmc_io_cmd_writeb, s); + + 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->qdev); + + return 0; +} + +static ISADeviceInfo applesmc_isa_info = { + .qdev.name = "isa-applesmc", + .qdev.size = sizeof(struct AppleSMCStatus), + .qdev.reset = qdev_applesmc_isa_reset, + .init = applesmc_isa_init, + .qdev.props = (Property[]) { + DEFINE_PROP_HEX32("iobase", struct AppleSMCStatus, iobase, + APPLESMC_DEFAULT_IOBASE), + DEFINE_PROP_STRING("osk", struct AppleSMCStatus, osk), + DEFINE_PROP_END_OF_LIST(), + }, +}; + +static void applesmc_register_devices(void) +{ + isa_qdev_register(&applesmc_isa_info); +} + +device_init(applesmc_register_devices) diff --git a/hw/arm-misc.h b/hw/arm-misc.h new file mode 100644 index 0000000..010acb4 --- /dev/null +++ b/hw/arm-misc.h @@ -0,0 +1,46 @@ +/* + * Misc ARM declarations + * + * Copyright (c) 2006 CodeSourcery. + * Written by Paul Brook + * + * This code is licenced under the LGPL. + * + */ + +#ifndef ARM_MISC_H +#define ARM_MISC_H 1 + +/* The CPU is also modeled as an interrupt controller. */ +#define ARM_PIC_CPU_IRQ 0 +#define ARM_PIC_CPU_FIQ 1 +qemu_irq *arm_pic_init_cpu(CPUState *env); + +/* armv7m.c */ +qemu_irq *armv7m_init(int flash_size, int sram_size, + const char *kernel_filename, const char *cpu_model); + +/* arm_boot.c */ +struct arm_boot_info { + int ram_size; + const char *kernel_filename; + const char *kernel_cmdline; + const char *initrd_filename; + target_phys_addr_t loader_start; + target_phys_addr_t smp_loader_start; + target_phys_addr_t smp_priv_base; + int nb_cpus; + int board_id; + int (*atag_board)(struct arm_boot_info *info, void *p); + /* Used internally by arm_boot.c */ + int is_linux; + target_phys_addr_t initrd_size; + target_phys_addr_t entry; +}; +void arm_load_kernel(CPUState *env, struct arm_boot_info *info); + +/* Multiplication factor to convert from system clock ticks to qemu timer + ticks. */ +extern int system_clock_scale; + +#endif /* !ARM_MISC_H */ diff --git a/hw/arm11mpcore.c b/hw/arm11mpcore.c new file mode 100644 index 0000000..3bbd885 --- /dev/null +++ b/hw/arm11mpcore.c @@ -0,0 +1,112 @@ +/* + * ARM11MPCore internal peripheral emulation. + * + * Copyright (c) 2006-2007 CodeSourcery. + * Written by Paul Brook + * + * This code is licenced under the GPL. + */ + +/* ??? The MPCore TRM says the on-chip controller has 224 external IRQ lines + (+ 32 internal). However my test chip only exposes/reports 32. + More importantly Linux falls over if more than 32 are present! */ +#define GIC_NIRQ 64 +#include "mpcore.c" + +/* 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 busdev; + SysBusDevice *priv; + qemu_irq cpuic[32]; + qemu_irq rvic[4][64]; + uint32_t num_cpu; +} 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 mpcore_rirq_map(SysBusDevice *dev, target_phys_addr_t base) +{ + mpcore_rirq_state *s = FROM_SYSBUS(mpcore_rirq_state, dev); + sysbus_mmio_map(s->priv, 0, base); +} + +static int realview_mpcore_init(SysBusDevice *dev) +{ + mpcore_rirq_state *s = FROM_SYSBUS(mpcore_rirq_state, dev); + DeviceState *gic; + DeviceState *priv; + int n; + int i; + + priv = qdev_create(NULL, "arm11mpcore_priv"); + qdev_prop_set_uint32(priv, "num-cpu", s->num_cpu); + qdev_init_nofail(priv); + s->priv = sysbus_from_qdev(priv); + sysbus_pass_irq(dev, 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++) { + gic = sysbus_create_simple("realview_gic", 0x10040000 + n * 0x10000, + 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->qdev, mpcore_rirq_set_irq, 64); + sysbus_init_mmio_cb(dev, 0x2000, mpcore_rirq_map); + return 0; +} + +static SysBusDeviceInfo mpcore_rirq_info = { + .init = realview_mpcore_init, + .qdev.name = "realview_mpcore", + .qdev.size = sizeof(mpcore_rirq_state), + .qdev.props = (Property[]) { + DEFINE_PROP_UINT32("num-cpu", mpcore_rirq_state, num_cpu, 1), + DEFINE_PROP_END_OF_LIST(), + } +}; + +static SysBusDeviceInfo mpcore_priv_info = { + .init = mpcore_priv_init, + .qdev.name = "arm11mpcore_priv", + .qdev.size = sizeof(mpcore_priv_state), + .qdev.props = (Property[]) { + DEFINE_PROP_UINT32("num-cpu", mpcore_priv_state, num_cpu, 1), + DEFINE_PROP_END_OF_LIST(), + } +}; + +static void arm11mpcore_register_devices(void) +{ + sysbus_register_withprop(&mpcore_rirq_info); + sysbus_register_withprop(&mpcore_priv_info); +} + +device_init(arm11mpcore_register_devices) diff --git a/hw/arm_boot.c b/hw/arm_boot.c new file mode 100644 index 0000000..620550b --- /dev/null +++ b/hw/arm_boot.c @@ -0,0 +1,283 @@ +/* + * ARM kernel loader. + * + * Copyright (c) 2006-2007 CodeSourcery. + * Written by Paul Brook + * + * This code is licenced under the GPL. + */ + +#include "hw.h" +#include "arm-misc.h" +#include "sysemu.h" +#include "loader.h" +#include "elf.h" + +#define KERNEL_ARGS_ADDR 0x100 +#define KERNEL_LOAD_ADDR 0x00010000 +#define INITRD_LOAD_ADDR 0x00800000 + +/* The worlds second smallest bootloader. Set r0-r2, then jump to kernel. */ +static uint32_t bootloader[] = { + 0xe3a00000, /* mov r0, #0 */ + 0xe3a01000, /* mov r1, #0x?? */ + 0xe3811c00, /* orr r1, r1, #0x??00 */ + 0xe59f2000, /* ldr r2, [pc, #0] */ + 0xe59ff000, /* ldr pc, [pc, #0] */ + 0, /* Address of kernel args. Set by integratorcp_init. */ + 0 /* Kernel entry point. Set by integratorcp_init. */ +}; + +/* Entry point for secondary CPUs. Enable interrupt controller and + Issue WFI until start address is written to system controller. */ +static uint32_t smpboot[] = { + 0xe59f0020, /* ldr r0, privbase */ + 0xe3a01001, /* mov r1, #1 */ + 0xe5801100, /* str r1, [r0, #0x100] */ + 0xe3a00201, /* mov r0, #0x10000000 */ + 0xe3800030, /* orr r0, #0x30 */ + 0xe320f003, /* wfi */ + 0xe5901000, /* ldr r1, [r0] */ + 0xe1110001, /* tst r1, r1 */ + 0x0afffffb, /* beq */ + 0xe12fff11, /* bx r1 */ + 0 /* privbase: Private memory region base address. */ +}; + +#define WRITE_WORD(p, value) do { \ + stl_phys_notdirty(p, value); \ + p += 4; \ +} while (0) + +static void set_kernel_args(struct arm_boot_info *info, + int initrd_size, target_phys_addr_t base) +{ + target_phys_addr_t 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->loader_start + INITRD_LOAD_ADDR); + 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, (void *)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(struct arm_boot_info *info, + int initrd_size, target_phys_addr_t base) +{ + target_phys_addr_t p; + const char *s; + + + /* 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->loader_start + INITRD_LOAD_ADDR); + 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, (void *)s, strlen(s) + 1); + } else { + WRITE_WORD(p, 0); + } +} + +static void main_cpu_reset(void *opaque) +{ + CPUState *env = opaque; + struct arm_boot_info *info = env->boot_info; + + cpu_reset(env); + if (info) { + if (!info->is_linux) { + /* Jump to the entry point. */ + env->regs[15] = info->entry & 0xfffffffe; + env->thumb = info->entry & 1; + } else { + env->regs[15] = info->loader_start; + if (old_param) { + set_kernel_args_old(info, info->initrd_size, + info->loader_start); + } else { + set_kernel_args(info, info->initrd_size, info->loader_start); + } + } + } + /* TODO: Reset secondary CPUs. */ +} + +void arm_load_kernel(CPUState *env, struct arm_boot_info *info) +{ + int kernel_size; + int initrd_size; + int n; + int is_linux = 0; + uint64_t elf_entry; + target_phys_addr_t entry; + int big_endian; + + /* Load the kernel. */ + if (!info->kernel_filename) { + fprintf(stderr, "Kernel image must be specified\n"); + exit(1); + } + + if (info->nb_cpus == 0) + info->nb_cpus = 1; + env->boot_info = info; + +#ifdef TARGET_WORDS_BIGENDIAN + big_endian = 1; +#else + big_endian = 0; +#endif + + /* Assume that raw images are linux kernels, and ELF images are not. */ + kernel_size = load_elf(info->kernel_filename, NULL, NULL, &elf_entry, + NULL, NULL, big_endian, ELF_MACHINE, 1); + entry = elf_entry; + if (kernel_size < 0) { + kernel_size = load_uimage(info->kernel_filename, &entry, NULL, + &is_linux); + } + if (kernel_size < 0) { + entry = info->loader_start + KERNEL_LOAD_ADDR; + kernel_size = load_image_targphys(info->kernel_filename, entry, + ram_size - KERNEL_LOAD_ADDR); + 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) { + if (info->initrd_filename) { + initrd_size = load_image_targphys(info->initrd_filename, + info->loader_start + + INITRD_LOAD_ADDR, + ram_size - INITRD_LOAD_ADDR); + if (initrd_size < 0) { + fprintf(stderr, "qemu: could not load initrd '%s'\n", + info->initrd_filename); + exit(1); + } + } else { + initrd_size = 0; + } + bootloader[1] |= info->board_id & 0xff; + bootloader[2] |= (info->board_id >> 8) & 0xff; + bootloader[5] = info->loader_start + KERNEL_ARGS_ADDR; + bootloader[6] = entry; + for (n = 0; n < sizeof(bootloader) / 4; n++) { + bootloader[n] = tswap32(bootloader[n]); + } + rom_add_blob_fixed("bootloader", bootloader, sizeof(bootloader), + info->loader_start); + if (info->nb_cpus > 1) { + smpboot[10] = info->smp_priv_base; + for (n = 0; n < sizeof(smpboot) / 4; n++) { + smpboot[n] = tswap32(smpboot[n]); + } + rom_add_blob_fixed("smpboot", smpboot, sizeof(smpboot), + info->smp_loader_start); + } + info->initrd_size = initrd_size; + } + info->is_linux = is_linux; + qemu_register_reset(main_cpu_reset, env); +} diff --git a/hw/arm_gic.c b/hw/arm_gic.c new file mode 100644 index 0000000..e6b1953 --- /dev/null +++ b/hw/arm_gic.c @@ -0,0 +1,749 @@ +/* + * ARM Generic/Distributed Interrupt Controller + * + * Copyright (c) 2006-2007 CodeSourcery. + * Written by Paul Brook + * + * This code is licenced 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. */ + +//#define DEBUG_GIC + +#ifdef DEBUG_GIC +#define DPRINTF(fmt, ...) \ +do { printf("arm_gic: " fmt , ## __VA_ARGS__); } while (0) +#else +#define DPRINTF(fmt, ...) do {} while(0) +#endif + +#ifdef NVIC +static const uint8_t gic_id[] = +{ 0x00, 0xb0, 0x1b, 0x00, 0x0d, 0xe0, 0x05, 0xb1 }; +/* The NVIC has 16 internal vectors. However these are not exposed + through the normal GIC interface. */ +#define GIC_BASE_IRQ 32 +#else +static const uint8_t gic_id[] = +{ 0x90, 0x13, 0x04, 0x00, 0x0d, 0xf0, 0x05, 0xb1 }; +#define GIC_BASE_IRQ 0 +#endif + +#define FROM_SYSBUSGIC(type, dev) \ + DO_UPCAST(type, gic, FROM_SYSBUS(gic_state, dev)) + +typedef struct gic_irq_state +{ + /* ??? The documentation seems to imply the enable bits are global, even + for per-cpu interrupts. This seems strange. */ + unsigned enabled:1; + unsigned pending:NCPU; + unsigned active:NCPU; + unsigned level:NCPU; + unsigned model:1; /* 0 = N:N, 1 = 1:N */ + unsigned trigger:1; /* nonzero = edge triggered. */ +} gic_irq_state; + +#define ALL_CPU_MASK ((1 << NCPU) - 1) +#if NCPU > 1 +#define NUM_CPU(s) ((s)->num_cpu) +#else +#define NUM_CPU(s) 1 +#endif + +#define GIC_SET_ENABLED(irq) s->irq_state[irq].enabled = 1 +#define GIC_CLEAR_ENABLED(irq) s->irq_state[irq].enabled = 0 +#define GIC_TEST_ENABLED(irq) s->irq_state[irq].enabled +#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_TEST_PENDING(irq, cm) ((s->irq_state[irq].pending & (cm)) != 0) +#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 = 1 +#define GIC_CLEAR_MODEL(irq) s->irq_state[irq].model = 0 +#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_TRIGGER(irq) s->irq_state[irq].trigger = 1 +#define GIC_CLEAR_TRIGGER(irq) s->irq_state[irq].trigger = 0 +#define GIC_TEST_TRIGGER(irq) s->irq_state[irq].trigger +#define GIC_GET_PRIORITY(irq, cpu) \ + (((irq) < 32) ? s->priority1[irq][cpu] : s->priority2[(irq) - 32]) +#ifdef NVIC +#define GIC_TARGET(irq) 1 +#else +#define GIC_TARGET(irq) s->irq_target[irq] +#endif + +typedef struct gic_state +{ + SysBusDevice busdev; + qemu_irq parent_irq[NCPU]; + int enabled; + int cpu_enabled[NCPU]; + + gic_irq_state irq_state[GIC_NIRQ]; +#ifndef NVIC + int irq_target[GIC_NIRQ]; +#endif + int priority1[32][NCPU]; + int priority2[GIC_NIRQ - 32]; + int last_active[GIC_NIRQ][NCPU]; + + int priority_mask[NCPU]; + int running_irq[NCPU]; + int running_priority[NCPU]; + int current_pending[NCPU]; + +#if NCPU > 1 + int num_cpu; +#endif + + int iomemtype; +} gic_state; + +/* TODO: Many places that call this routine could be optimized. */ +/* Update interrupt status after enabled or pending bits have been changed. */ +static void gic_update(gic_state *s) +{ + int best_irq; + int best_prio; + int irq; + int level; + int cpu; + int cm; + + for (cpu = 0; cpu < NUM_CPU(s); cpu++) { + cm = 1 << cpu; + s->current_pending[cpu] = 1023; + if (!s->enabled || !s->cpu_enabled[cpu]) { + qemu_irq_lower(s->parent_irq[cpu]); + return; + } + best_prio = 0x100; + best_irq = 1023; + for (irq = 0; irq < GIC_NIRQ; irq++) { + if (GIC_TEST_ENABLED(irq) && GIC_TEST_PENDING(irq, cm)) { + if (GIC_GET_PRIORITY(irq, cpu) < best_prio) { + best_prio = GIC_GET_PRIORITY(irq, cpu); + best_irq = irq; + } + } + } + level = 0; + if (best_prio <= s->priority_mask[cpu]) { + s->current_pending[cpu] = best_irq; + if (best_prio < s->running_priority[cpu]) { + DPRINTF("Raised pending IRQ %d\n", best_irq); + level = 1; + } + } + qemu_set_irq(s->parent_irq[cpu], level); + } +} + +static void __attribute__((unused)) +gic_set_pending_private(gic_state *s, int cpu, int irq) +{ + int cm = 1 << cpu; + + if (GIC_TEST_PENDING(irq, cm)) + return; + + DPRINTF("Set %d pending cpu %d\n", irq, cpu); + GIC_SET_PENDING(irq, cm); + gic_update(s); +} + +/* Process a change in an external IRQ input. */ +static void gic_set_irq(void *opaque, int irq, int level) +{ + gic_state *s = (gic_state *)opaque; + /* The first external input line is internal interrupt 32. */ + irq += 32; + if (level == GIC_TEST_LEVEL(irq, ALL_CPU_MASK)) + return; + + if (level) { + GIC_SET_LEVEL(irq, ALL_CPU_MASK); + if (GIC_TEST_TRIGGER(irq) || GIC_TEST_ENABLED(irq)) { + DPRINTF("Set %d pending mask %x\n", irq, GIC_TARGET(irq)); + GIC_SET_PENDING(irq, GIC_TARGET(irq)); + } + } else { + GIC_CLEAR_LEVEL(irq, ALL_CPU_MASK); + } + gic_update(s); +} + +static void gic_set_running_irq(gic_state *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); +} + +static uint32_t gic_acknowledge_irq(gic_state *s, int cpu) +{ + int new_irq; + int cm = 1 << cpu; + new_irq = s->current_pending[cpu]; + if (new_irq == 1023 + || GIC_GET_PRIORITY(new_irq, cpu) >= s->running_priority[cpu]) { + DPRINTF("ACK no pending IRQ\n"); + return 1023; + } + s->last_active[new_irq][cpu] = s->running_irq[cpu]; + /* Clear pending flags for both level and edge triggered interrupts. + Level triggered IRQs will be reasserted once they become inactive. */ + GIC_CLEAR_PENDING(new_irq, GIC_TEST_MODEL(new_irq) ? ALL_CPU_MASK : cm); + gic_set_running_irq(s, cpu, new_irq); + DPRINTF("ACK %d\n", new_irq); + return new_irq; +} + +static void gic_complete_irq(gic_state * s, int cpu, int irq) +{ + int update = 0; + int cm = 1 << cpu; + DPRINTF("EOI %d\n", irq); + if (s->running_irq[cpu] == 1023) + return; /* No active IRQ. */ + if (irq != 1023) { + /* Mark level triggered interrupts as pending if they are still + raised. */ + if (!GIC_TEST_TRIGGER(irq) && GIC_TEST_ENABLED(irq) + && 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 (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, target_phys_addr_t offset) +{ + gic_state *s = (gic_state *)opaque; + uint32_t res; + int irq; + int i; + int cpu; + int cm; + int mask; + + cpu = gic_get_current_cpu(); + cm = 1 << cpu; + if (offset < 0x100) { +#ifndef NVIC + if (offset == 0) + return s->enabled; + if (offset == 4) + return ((GIC_NIRQ / 32) - 1) | ((NUM_CPU(s) - 1) << 5); + if (offset < 0x08) + return 0; +#endif + 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 >= GIC_NIRQ) + goto bad_reg; + res = 0; + for (i = 0; i < 8; i++) { + if (GIC_TEST_ENABLED(irq + i)) { + 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 >= GIC_NIRQ) + goto bad_reg; + res = 0; + mask = (irq < 32) ? cm : ALL_CPU_MASK; + for (i = 0; i < 8; i++) { + if (GIC_TEST_PENDING(irq + i, mask)) { + res |= (1 << i); + } + } + } else if (offset < 0x400) { + /* Interrupt Active. */ + irq = (offset - 0x300) * 8 + GIC_BASE_IRQ; + if (irq >= GIC_NIRQ) + goto bad_reg; + res = 0; + mask = (irq < 32) ? 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 >= GIC_NIRQ) + goto bad_reg; + res = GIC_GET_PRIORITY(irq, cpu); +#ifndef NVIC + } else if (offset < 0xc00) { + /* Interrupt CPU Target. */ + irq = (offset - 0x800) + GIC_BASE_IRQ; + if (irq >= GIC_NIRQ) + goto bad_reg; + if (irq >= 29 && irq <= 31) { + res = cm; + } else { + res = GIC_TARGET(irq); + } + } else if (offset < 0xf00) { + /* Interrupt Configuration. */ + irq = (offset - 0xc00) * 2 + GIC_BASE_IRQ; + if (irq >= GIC_NIRQ) + goto bad_reg; + res = 0; + for (i = 0; i < 4; i++) { + if (GIC_TEST_MODEL(irq + i)) + res |= (1 << (i * 2)); + if (GIC_TEST_TRIGGER(irq + i)) + res |= (2 << (i * 2)); + } +#endif + } 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: + hw_error("gic_dist_readb: Bad offset %x\n", (int)offset); + return 0; +} + +static uint32_t gic_dist_readw(void *opaque, target_phys_addr_t offset) +{ + uint32_t val; + val = gic_dist_readb(opaque, offset); + val |= gic_dist_readb(opaque, offset + 1) << 8; + return val; +} + +static uint32_t gic_dist_readl(void *opaque, target_phys_addr_t offset) +{ + uint32_t val; +#ifdef NVIC + gic_state *s = (gic_state *)opaque; + uint32_t addr; + addr = offset; + if (addr < 0x100 || addr > 0xd00) + return nvic_readl(s, addr); +#endif + val = gic_dist_readw(opaque, offset); + val |= gic_dist_readw(opaque, offset + 2) << 16; + return val; +} + +static void gic_dist_writeb(void *opaque, target_phys_addr_t offset, + uint32_t value) +{ + gic_state *s = (gic_state *)opaque; + int irq; + int i; + int cpu; + + cpu = gic_get_current_cpu(); + if (offset < 0x100) { +#ifdef NVIC + goto bad_reg; +#else + if (offset == 0) { + s->enabled = (value & 1); + DPRINTF("Distribution %sabled\n", s->enabled ? "En" : "Dis"); + } else if (offset < 4) { + /* ignored. */ + } else { + goto bad_reg; + } +#endif + } else if (offset < 0x180) { + /* Interrupt Set Enable. */ + irq = (offset - 0x100) * 8 + GIC_BASE_IRQ; + if (irq >= GIC_NIRQ) + goto bad_reg; + if (irq < 16) + value = 0xff; + for (i = 0; i < 8; i++) { + if (value & (1 << i)) { + int mask = (irq < 32) ? (1 << cpu) : GIC_TARGET(irq); + if (!GIC_TEST_ENABLED(irq + i)) + DPRINTF("Enabled IRQ %d\n", irq + i); + GIC_SET_ENABLED(irq + i); + /* If a raised level triggered IRQ enabled then mark + is as pending. */ + if (GIC_TEST_LEVEL(irq + i, mask) + && !GIC_TEST_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 >= GIC_NIRQ) + goto bad_reg; + if (irq < 16) + value = 0; + for (i = 0; i < 8; i++) { + if (value & (1 << i)) { + if (GIC_TEST_ENABLED(irq + i)) + DPRINTF("Disabled IRQ %d\n", irq + i); + GIC_CLEAR_ENABLED(irq + i); + } + } + } else if (offset < 0x280) { + /* Interrupt Set Pending. */ + irq = (offset - 0x200) * 8 + GIC_BASE_IRQ; + if (irq >= GIC_NIRQ) + goto bad_reg; + if (irq < 16) + irq = 0; + + for (i = 0; i < 8; i++) { + if (value & (1 << i)) { + GIC_SET_PENDING(irq + i, GIC_TARGET(irq)); + } + } + } else if (offset < 0x300) { + /* Interrupt Clear Pending. */ + irq = (offset - 0x280) * 8 + GIC_BASE_IRQ; + if (irq >= GIC_NIRQ) + goto bad_reg; + 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 >= GIC_NIRQ) + goto bad_reg; + if (irq < 32) { + s->priority1[irq][cpu] = value; + } else { + s->priority2[irq - 32] = value; + } +#ifndef NVIC + } else if (offset < 0xc00) { + /* Interrupt CPU Target. */ + irq = (offset - 0x800) + GIC_BASE_IRQ; + if (irq >= GIC_NIRQ) + goto bad_reg; + if (irq < 29) + value = 0; + else if (irq < 32) + 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 >= GIC_NIRQ) + goto bad_reg; + if (irq < 32) + value |= 0xaa; + for (i = 0; i < 4; i++) { + if (value & (1 << (i * 2))) { + GIC_SET_MODEL(irq + i); + } else { + GIC_CLEAR_MODEL(irq + i); + } + if (value & (2 << (i * 2))) { + GIC_SET_TRIGGER(irq + i); + } else { + GIC_CLEAR_TRIGGER(irq + i); + } + } +#endif + } else { + /* 0xf00 is only handled for 32-bit writes. */ + goto bad_reg; + } + gic_update(s); + return; +bad_reg: + hw_error("gic_dist_writeb: Bad offset %x\n", (int)offset); +} + +static void gic_dist_writew(void *opaque, target_phys_addr_t offset, + uint32_t value) +{ + gic_dist_writeb(opaque, offset, value & 0xff); + gic_dist_writeb(opaque, offset + 1, value >> 8); +} + +static void gic_dist_writel(void *opaque, target_phys_addr_t offset, + uint32_t value) +{ + gic_state *s = (gic_state *)opaque; +#ifdef NVIC + uint32_t addr; + addr = offset; + if (addr < 0x100 || (addr > 0xd00 && addr != 0xf00)) { + nvic_writel(s, addr, value); + return; + } +#endif + if (offset == 0xf00) { + int cpu; + int irq; + int mask; + + cpu = gic_get_current_cpu(); + irq = value & 0x3ff; + switch ((value >> 24) & 3) { + case 0: + mask = (value >> 16) & ALL_CPU_MASK; + break; + case 1: + mask = 1 << cpu; + break; + case 2: + mask = ALL_CPU_MASK ^ (1 << cpu); + break; + default: + DPRINTF("Bad Soft Int target filter\n"); + mask = ALL_CPU_MASK; + break; + } + GIC_SET_PENDING(irq, mask); + gic_update(s); + return; + } + gic_dist_writew(opaque, offset, value & 0xffff); + gic_dist_writew(opaque, offset + 2, value >> 16); +} + +static CPUReadMemoryFunc * const gic_dist_readfn[] = { + gic_dist_readb, + gic_dist_readw, + gic_dist_readl +}; + +static CPUWriteMemoryFunc * const gic_dist_writefn[] = { + gic_dist_writeb, + gic_dist_writew, + gic_dist_writel +}; + +#ifndef NVIC +static uint32_t gic_cpu_read(gic_state *s, int cpu, int offset) +{ + switch (offset) { + case 0x00: /* Control */ + return s->cpu_enabled[cpu]; + case 0x04: /* Priority mask */ + return s->priority_mask[cpu]; + case 0x08: /* Binary Point */ + /* ??? Not implemented. */ + return 0; + case 0x0c: /* Acknowledge */ + return gic_acknowledge_irq(s, cpu); + case 0x14: /* Runing Priority */ + return s->running_priority[cpu]; + case 0x18: /* Highest Pending Interrupt */ + return s->current_pending[cpu]; + default: + hw_error("gic_cpu_read: Bad offset %x\n", (int)offset); + return 0; + } +} + +static void gic_cpu_write(gic_state *s, int cpu, int offset, uint32_t value) +{ + switch (offset) { + case 0x00: /* Control */ + s->cpu_enabled[cpu] = (value & 1); + DPRINTF("CPU %d %sabled\n", cpu, s->cpu_enabled ? "En" : "Dis"); + break; + case 0x04: /* Priority mask */ + s->priority_mask[cpu] = (value & 0xff); + break; + case 0x08: /* Binary Point */ + /* ??? Not implemented. */ + break; + case 0x10: /* End Of Interrupt */ + return gic_complete_irq(s, cpu, value & 0x3ff); + default: + hw_error("gic_cpu_write: Bad offset %x\n", (int)offset); + return; + } + gic_update(s); +} +#endif + +static void gic_reset(gic_state *s) +{ + int i; + memset(s->irq_state, 0, GIC_NIRQ * sizeof(gic_irq_state)); + for (i = 0 ; i < NUM_CPU(s); i++) { + s->priority_mask[i] = 0xf0; + s->current_pending[i] = 1023; + s->running_irq[i] = 1023; + s->running_priority[i] = 0x100; +#ifdef NVIC + /* The NVIC doesn't have per-cpu interfaces, so enable by default. */ + s->cpu_enabled[i] = 1; +#else + s->cpu_enabled[i] = 0; +#endif + } + for (i = 0; i < 16; i++) { + GIC_SET_ENABLED(i); + GIC_SET_TRIGGER(i); + } +#ifdef NVIC + /* The NVIC is always enabled. */ + s->enabled = 1; +#else + s->enabled = 0; +#endif +} + +static void gic_save(QEMUFile *f, void *opaque) +{ + gic_state *s = (gic_state *)opaque; + int i; + int j; + + qemu_put_be32(f, s->enabled); + for (i = 0; i < NUM_CPU(s); i++) { + qemu_put_be32(f, s->cpu_enabled[i]); +#ifndef NVIC + qemu_put_be32(f, s->irq_target[i]); +#endif + for (j = 0; j < 32; j++) + qemu_put_be32(f, s->priority1[j][i]); + for (j = 0; j < GIC_NIRQ; j++) + qemu_put_be32(f, s->last_active[j][i]); + qemu_put_be32(f, s->priority_mask[i]); + qemu_put_be32(f, s->running_irq[i]); + qemu_put_be32(f, s->running_priority[i]); + qemu_put_be32(f, s->current_pending[i]); + } + for (i = 0; i < GIC_NIRQ - 32; i++) { + qemu_put_be32(f, s->priority2[i]); + } + for (i = 0; i < GIC_NIRQ; i++) { + qemu_put_byte(f, s->irq_state[i].enabled); + qemu_put_byte(f, s->irq_state[i].pending); + qemu_put_byte(f, s->irq_state[i].active); + qemu_put_byte(f, s->irq_state[i].level); + qemu_put_byte(f, s->irq_state[i].model); + qemu_put_byte(f, s->irq_state[i].trigger); + } +} + +static int gic_load(QEMUFile *f, void *opaque, int version_id) +{ + gic_state *s = (gic_state *)opaque; + int i; + int j; + + if (version_id != 1) + return -EINVAL; + + s->enabled = qemu_get_be32(f); + for (i = 0; i < NUM_CPU(s); i++) { + s->cpu_enabled[i] = qemu_get_be32(f); +#ifndef NVIC + s->irq_target[i] = qemu_get_be32(f); +#endif + for (j = 0; j < 32; j++) + s->priority1[j][i] = qemu_get_be32(f); + for (j = 0; j < GIC_NIRQ; j++) + s->last_active[j][i] = qemu_get_be32(f); + s->priority_mask[i] = qemu_get_be32(f); + s->running_irq[i] = qemu_get_be32(f); + s->running_priority[i] = qemu_get_be32(f); + s->current_pending[i] = qemu_get_be32(f); + } + for (i = 0; i < GIC_NIRQ - 32; i++) { + s->priority2[i] = qemu_get_be32(f); + } + for (i = 0; i < GIC_NIRQ; i++) { + s->irq_state[i].enabled = qemu_get_byte(f); + s->irq_state[i].pending = qemu_get_byte(f); + s->irq_state[i].active = qemu_get_byte(f); + s->irq_state[i].level = qemu_get_byte(f); + s->irq_state[i].model = qemu_get_byte(f); + s->irq_state[i].trigger = qemu_get_byte(f); + } + + return 0; +} + +#if NCPU > 1 +static void gic_init(gic_state *s, int num_cpu) +#else +static void gic_init(gic_state *s) +#endif +{ + int i; + +#if NCPU > 1 + s->num_cpu = num_cpu; +#endif + qdev_init_gpio_in(&s->busdev.qdev, gic_set_irq, GIC_NIRQ - 32); + for (i = 0; i < NUM_CPU(s); i++) { + sysbus_init_irq(&s->busdev, &s->parent_irq[i]); + } + s->iomemtype = cpu_register_io_memory(gic_dist_readfn, + gic_dist_writefn, s, + DEVICE_NATIVE_ENDIAN); + gic_reset(s); + register_savevm(NULL, "arm_gic", -1, 1, gic_save, gic_load, s); +} diff --git a/hw/arm_pic.c b/hw/arm_pic.c new file mode 100644 index 0000000..f44568c --- /dev/null +++ b/hw/arm_pic.c @@ -0,0 +1,49 @@ +/* + * Generic ARM Programmable Interrupt Controller support. + * + * Copyright (c) 2006 CodeSourcery. + * Written by Paul Brook + * + * This code is licenced under the LGPL + */ + +#include "hw.h" +#include "pc.h" +#include "arm-misc.h" + +/* Stub functions for hardware that doesn't exist. */ +void pic_info(Monitor *mon) +{ +} + +void irq_info(Monitor *mon) +{ +} + + +/* Input 0 is IRQ and input 1 is FIQ. */ +static void arm_pic_cpu_handler(void *opaque, int irq, int level) +{ + CPUState *env = (CPUState *)opaque; + switch (irq) { + case ARM_PIC_CPU_IRQ: + if (level) + cpu_interrupt(env, CPU_INTERRUPT_HARD); + else + cpu_reset_interrupt(env, CPU_INTERRUPT_HARD); + break; + case ARM_PIC_CPU_FIQ: + if (level) + cpu_interrupt(env, CPU_INTERRUPT_FIQ); + else + cpu_reset_interrupt(env, CPU_INTERRUPT_FIQ); + break; + default: + hw_error("arm_pic_cpu_handler: Bad interrput line %d\n", irq); + } +} + +qemu_irq *arm_pic_init_cpu(CPUState *env) +{ + return qemu_allocate_irqs(arm_pic_cpu_handler, env, 2); +} diff --git a/hw/arm_sysctl.c b/hw/arm_sysctl.c new file mode 100644 index 0000000..d8b062c --- /dev/null +++ b/hw/arm_sysctl.c @@ -0,0 +1,264 @@ +/* + * Status and system control registers for ARM RealView/Versatile boards. + * + * Copyright (c) 2006-2007 CodeSourcery. + * Written by Paul Brook + * + * This code is licenced under the GPL. + */ + +#include "hw.h" +#include "qemu-timer.h" +#include "sysbus.h" +#include "primecell.h" +#include "sysemu.h" + +#define LOCK_VALUE 0xa05f + +typedef struct { + SysBusDevice busdev; + 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; +} arm_sysctl_state; + +static const VMStateDescription vmstate_arm_sysctl = { + .name = "realview_sysctl", + .version_id = 1, + .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_END_OF_LIST() + } +}; + +static void arm_sysctl_reset(DeviceState *d) +{ + arm_sysctl_state *s = FROM_SYSBUS(arm_sysctl_state, sysbus_from_qdev(d)); + + s->leds = 0; + s->lockval = 0; + s->cfgdata1 = 0; + s->cfgdata2 = 0; + s->flags = 0; + s->resetlevel = 0; +} + +static uint32_t arm_sysctl_read(void *opaque, target_phys_addr_t offset) +{ + 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 */ + return s->resetlevel; + case 0x44: /* PCICTL */ + return 1; + case 0x48: /* MCI */ + return 0; + case 0x4c: /* FLASH */ + return 0; + case 0x50: /* CLCD */ + return 0x1000; + case 0x54: /* CLCDSER */ + return 0; + case 0x58: /* BOOTCS */ + return 0; + case 0x5c: /* 24MHz */ + return muldiv64(qemu_get_clock(vm_clock), 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; + default: + printf ("arm_sysctl_read: Bad register offset 0x%x\n", (int)offset); + return 0; + } +} + +static void arm_sysctl_write(void *opaque, target_phys_addr_t offset, + uint32_t val) +{ + arm_sysctl_state *s = (arm_sysctl_state *)opaque; + + switch (offset) { + case 0x08: /* LED */ + s->leds = val; + 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 */ + if (s->lockval == LOCK_VALUE) { + s->resetlevel = val; + if (val & 0x100) + qemu_system_reset_request (); + } + break; + case 0x44: /* PCICTL */ + /* nothing to do. */ + break; + case 0x4c: /* FLASH */ + case 0x50: /* CLCD */ + 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; + default: + printf ("arm_sysctl_write: Bad register offset 0x%x\n", (int)offset); + return; + } +} + +static CPUReadMemoryFunc * const arm_sysctl_readfn[] = { + arm_sysctl_read, + arm_sysctl_read, + arm_sysctl_read +}; + +static CPUWriteMemoryFunc * const arm_sysctl_writefn[] = { + arm_sysctl_write, + arm_sysctl_write, + arm_sysctl_write +}; + +static int arm_sysctl_init1(SysBusDevice *dev) +{ + arm_sysctl_state *s = FROM_SYSBUS(arm_sysctl_state, dev); + int iomemtype; + + iomemtype = cpu_register_io_memory(arm_sysctl_readfn, + arm_sysctl_writefn, s, + DEVICE_NATIVE_ENDIAN); + sysbus_init_mmio(dev, 0x1000, iomemtype); + /* ??? Save/restore. */ + return 0; +} + +/* Legacy helper function. */ +void arm_sysctl_init(uint32_t base, uint32_t sys_id, uint32_t proc_id) +{ + DeviceState *dev; + + dev = qdev_create(NULL, "realview_sysctl"); + qdev_prop_set_uint32(dev, "sys_id", sys_id); + qdev_init_nofail(dev); + qdev_prop_set_uint32(dev, "proc_id", proc_id); + sysbus_mmio_map(sysbus_from_qdev(dev), 0, base); +} + +static SysBusDeviceInfo arm_sysctl_info = { + .init = arm_sysctl_init1, + .qdev.name = "realview_sysctl", + .qdev.size = sizeof(arm_sysctl_state), + .qdev.vmsd = &vmstate_arm_sysctl, + .qdev.reset = arm_sysctl_reset, + .qdev.props = (Property[]) { + DEFINE_PROP_UINT32("sys_id", arm_sysctl_state, sys_id, 0), + DEFINE_PROP_UINT32("proc_id", arm_sysctl_state, proc_id, 0), + DEFINE_PROP_END_OF_LIST(), + } +}; + +static void arm_sysctl_register_devices(void) +{ + sysbus_register_withprop(&arm_sysctl_info); +} + +device_init(arm_sysctl_register_devices) diff --git a/hw/arm_timer.c b/hw/arm_timer.c new file mode 100644 index 0000000..82f05de --- /dev/null +++ b/hw/arm_timer.c @@ -0,0 +1,357 @@ +/* + * ARM PrimeCell Timer modules. + * + * Copyright (c) 2005-2006 CodeSourcery. + * Written by Paul Brook + * + * This code is licenced under the GPL. + */ + +#include "sysbus.h" +#include "qemu-timer.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, target_phys_addr_t 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: + hw_error("arm_timer_read: Bad offset %x\n", (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, target_phys_addr_t 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: + hw_error("arm_timer_write: Bad offset %x\n", (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 void arm_timer_save(QEMUFile *f, void *opaque) +{ + arm_timer_state *s = (arm_timer_state *)opaque; + qemu_put_be32(f, s->control); + qemu_put_be32(f, s->limit); + qemu_put_be32(f, s->int_level); + qemu_put_ptimer(f, s->timer); +} + +static int arm_timer_load(QEMUFile *f, void *opaque, int version_id) +{ + arm_timer_state *s = (arm_timer_state *)opaque; + + if (version_id != 1) + return -EINVAL; + + s->control = qemu_get_be32(f); + s->limit = qemu_get_be32(f); + s->int_level = qemu_get_be32(f); + qemu_get_ptimer(f, s->timer); + return 0; +} + +static arm_timer_state *arm_timer_init(uint32_t freq) +{ + arm_timer_state *s; + QEMUBH *bh; + + s = (arm_timer_state *)qemu_mallocz(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); + register_savevm(NULL, "arm_timer", -1, 1, arm_timer_save, arm_timer_load, s); + return s; +} + +/* ARM PrimeCell SP804 dual timer module. + Docs for this device don't seem to be publicly available. This + implementation is based on guesswork, the linux kernel sources and the + Integrator/CP timer modules. */ + +typedef struct { + SysBusDevice busdev; + arm_timer_state *timer[2]; + int level[2]; + qemu_irq irq; +} sp804_state; + +/* Merge the IRQs from the two component devices. */ +static void sp804_set_irq(void *opaque, int irq, int level) +{ + sp804_state *s = (sp804_state *)opaque; + + s->level[irq] = level; + qemu_set_irq(s->irq, s->level[0] || s->level[1]); +} + +static uint32_t sp804_read(void *opaque, target_phys_addr_t offset) +{ + sp804_state *s = (sp804_state *)opaque; + + /* ??? Don't know the PrimeCell ID for this device. */ + if (offset < 0x20) { + return arm_timer_read(s->timer[0], offset); + } else { + return arm_timer_read(s->timer[1], offset - 0x20); + } +} + +static void sp804_write(void *opaque, target_phys_addr_t offset, + uint32_t value) +{ + sp804_state *s = (sp804_state *)opaque; + + if (offset < 0x20) { + arm_timer_write(s->timer[0], offset, value); + } else { + arm_timer_write(s->timer[1], offset - 0x20, value); + } +} + +static CPUReadMemoryFunc * const sp804_readfn[] = { + sp804_read, + sp804_read, + sp804_read +}; + +static CPUWriteMemoryFunc * const sp804_writefn[] = { + sp804_write, + sp804_write, + sp804_write +}; + +static void sp804_save(QEMUFile *f, void *opaque) +{ + sp804_state *s = (sp804_state *)opaque; + qemu_put_be32(f, s->level[0]); + qemu_put_be32(f, s->level[1]); +} + +static int sp804_load(QEMUFile *f, void *opaque, int version_id) +{ + sp804_state *s = (sp804_state *)opaque; + + if (version_id != 1) + return -EINVAL; + + s->level[0] = qemu_get_be32(f); + s->level[1] = qemu_get_be32(f); + return 0; +} + +static int sp804_init(SysBusDevice *dev) +{ + int iomemtype; + sp804_state *s = FROM_SYSBUS(sp804_state, dev); + qemu_irq *qi; + + qi = qemu_allocate_irqs(sp804_set_irq, s, 2); + sysbus_init_irq(dev, &s->irq); + /* ??? The timers are actually configurable between 32kHz and 1MHz, but + we don't implement that. */ + s->timer[0] = arm_timer_init(1000000); + s->timer[1] = arm_timer_init(1000000); + s->timer[0]->irq = qi[0]; + s->timer[1]->irq = qi[1]; + iomemtype = cpu_register_io_memory(sp804_readfn, + sp804_writefn, s, DEVICE_NATIVE_ENDIAN); + sysbus_init_mmio(dev, 0x1000, iomemtype); + register_savevm(&dev->qdev, "sp804", -1, 1, sp804_save, sp804_load, s); + return 0; +} + + +/* Integrator/CP timer module. */ + +typedef struct { + SysBusDevice busdev; + arm_timer_state *timer[3]; +} icp_pit_state; + +static uint32_t icp_pit_read(void *opaque, target_phys_addr_t offset) +{ + icp_pit_state *s = (icp_pit_state *)opaque; + int n; + + /* ??? Don't know the PrimeCell ID for this device. */ + n = offset >> 8; + if (n > 3) { + hw_error("sp804_read: Bad timer %d\n", n); + } + + return arm_timer_read(s->timer[n], offset & 0xff); +} + +static void icp_pit_write(void *opaque, target_phys_addr_t offset, + uint32_t value) +{ + icp_pit_state *s = (icp_pit_state *)opaque; + int n; + + n = offset >> 8; + if (n > 3) { + hw_error("sp804_write: Bad timer %d\n", n); + } + + arm_timer_write(s->timer[n], offset & 0xff, value); +} + + +static CPUReadMemoryFunc * const icp_pit_readfn[] = { + icp_pit_read, + icp_pit_read, + icp_pit_read +}; + +static CPUWriteMemoryFunc * const icp_pit_writefn[] = { + icp_pit_write, + icp_pit_write, + icp_pit_write +}; + +static int icp_pit_init(SysBusDevice *dev) +{ + int iomemtype; + icp_pit_state *s = FROM_SYSBUS(icp_pit_state, 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); + + iomemtype = cpu_register_io_memory(icp_pit_readfn, + icp_pit_writefn, s, + DEVICE_NATIVE_ENDIAN); + sysbus_init_mmio(dev, 0x1000, iomemtype); + /* This device has no state to save/restore. The component timers will + save themselves. */ + return 0; +} + +static void arm_timer_register_devices(void) +{ + sysbus_register_dev("integrator_pit", sizeof(icp_pit_state), icp_pit_init); + sysbus_register_dev("sp804", sizeof(sp804_state), sp804_init); +} + +device_init(arm_timer_register_devices) diff --git a/hw/armv7m.c b/hw/armv7m.c new file mode 100644 index 0000000..304cd34 --- /dev/null +++ b/hw/armv7m.c @@ -0,0 +1,263 @@ +/* + * ARMV7M System emulation. + * + * Copyright (c) 2006-2007 CodeSourcery. + * Written by Paul Brook + * + * This code is licenced under the GPL. + */ + +#include "sysbus.h" +#include "arm-misc.h" +#include "sysemu.h" +#include "loader.h" +#include "elf.h" + +/* Bitbanded IO. Each word corresponds to a single bit. */ + +/* Get the byte address of the real memory for a bitband acess. */ +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, target_phys_addr_t 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, target_phys_addr_t 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, target_phys_addr_t 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, (uint8_t *)&v, 2); + return (v & mask) != 0; +} + +static void bitband_writew(void *opaque, target_phys_addr_t 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, (uint8_t *)&v, 2); + if (value & 1) + v |= mask; + else + v &= ~mask; + cpu_physical_memory_write(addr, (uint8_t *)&v, 2); +} + +static uint32_t bitband_readl(void *opaque, target_phys_addr_t 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, (uint8_t *)&v, 4); + return (v & mask) != 0; +} + +static void bitband_writel(void *opaque, target_phys_addr_t 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, (uint8_t *)&v, 4); + if (value & 1) + v |= mask; + else + v &= ~mask; + cpu_physical_memory_write(addr, (uint8_t *)&v, 4); +} + +static CPUReadMemoryFunc * const bitband_readfn[] = { + bitband_readb, + bitband_readw, + bitband_readl +}; + +static CPUWriteMemoryFunc * const bitband_writefn[] = { + bitband_writeb, + bitband_writew, + bitband_writel +}; + +typedef struct { + SysBusDevice busdev; + uint32_t base; +} BitBandState; + +static int bitband_init(SysBusDevice *dev) +{ + BitBandState *s = FROM_SYSBUS(BitBandState, dev); + int iomemtype; + + iomemtype = cpu_register_io_memory(bitband_readfn, bitband_writefn, + &s->base, DEVICE_NATIVE_ENDIAN); + sysbus_init_mmio(dev, 0x02000000, iomemtype); + return 0; +} + +static void armv7m_bitband_init(void) +{ + DeviceState *dev; + + dev = qdev_create(NULL, "ARM,bitband-memory"); + qdev_prop_set_uint32(dev, "base", 0x20000000); + qdev_init_nofail(dev); + sysbus_mmio_map(sysbus_from_qdev(dev), 0, 0x22000000); + + dev = qdev_create(NULL, "ARM,bitband-memory"); + qdev_prop_set_uint32(dev, "base", 0x40000000); + qdev_init_nofail(dev); + sysbus_mmio_map(sysbus_from_qdev(dev), 0, 0x42000000); +} + +/* Board init. */ + +static void armv7m_reset(void *opaque) +{ + cpu_reset((CPUState *)opaque); +} + +/* Init CPU and memory for a v7-M based board. + flash_size and sram_size are in kb. + Returns the NVIC array. */ + +qemu_irq *armv7m_init(int flash_size, int sram_size, + const char *kernel_filename, const char *cpu_model) +{ + CPUState *env; + DeviceState *nvic; + /* FIXME: make this local state. */ + static qemu_irq pic[64]; + qemu_irq *cpu_pic; + int image_size; + uint64_t entry; + uint64_t lowaddr; + int i; + int big_endian; + + flash_size *= 1024; + sram_size *= 1024; + + if (!cpu_model) + cpu_model = "cortex-m3"; + env = cpu_init(cpu_model); + if (!env) { + fprintf(stderr, "Unable to find CPU definition\n"); + exit(1); + } + +#if 0 + /* > 32Mb SRAM gets complicated because it overlaps the bitband area. + We don't have proper commandline options, so allocate half of memory + as SRAM, up to a maximum of 32Mb, and the rest as code. */ + if (ram_size > (512 + 32) * 1024 * 1024) + ram_size = (512 + 32) * 1024 * 1024; + sram_size = (ram_size / 2) & TARGET_PAGE_MASK; + if (sram_size > 32 * 1024 * 1024) + sram_size = 32 * 1024 * 1024; + code_size = ram_size - sram_size; +#endif + + /* Flash programming is done via the SCU, so pretend it is ROM. */ + cpu_register_physical_memory(0, flash_size, + qemu_ram_alloc(NULL, "armv7m.flash", + flash_size) | IO_MEM_ROM); + cpu_register_physical_memory(0x20000000, sram_size, + qemu_ram_alloc(NULL, "armv7m.sram", + sram_size) | IO_MEM_RAM); + armv7m_bitband_init(); + + nvic = qdev_create(NULL, "armv7m_nvic"); + env->nvic = nvic; + qdev_init_nofail(nvic); + cpu_pic = arm_pic_init_cpu(env); + sysbus_connect_irq(sysbus_from_qdev(nvic), 0, cpu_pic[ARM_PIC_CPU_IRQ]); + for (i = 0; i < 64; i++) { + pic[i] = qdev_get_gpio_in(nvic, i); + } + +#ifdef TARGET_WORDS_BIGENDIAN + big_endian = 1; +#else + big_endian = 0; +#endif + + 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, flash_size); + lowaddr = 0; + } + if (image_size < 0) { + fprintf(stderr, "qemu: could not load kernel '%s'\n", + 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. */ + cpu_register_physical_memory(0xfffff000, 0x1000, + qemu_ram_alloc(NULL, "armv7m.hack", + 0x1000) | IO_MEM_RAM); + + qemu_register_reset(armv7m_reset, env); + return pic; +} + +static SysBusDeviceInfo bitband_info = { + .init = bitband_init, + .qdev.name = "ARM,bitband-memory", + .qdev.size = sizeof(BitBandState), + .qdev.props = (Property[]) { + DEFINE_PROP_UINT32("base", BitBandState, base, 0), + DEFINE_PROP_END_OF_LIST(), + } +}; + +static void armv7m_register_devices(void) +{ + sysbus_register_withprop(&bitband_info); +} + +device_init(armv7m_register_devices) diff --git a/hw/armv7m_nvic.c b/hw/armv7m_nvic.c new file mode 100644 index 0000000..6c7ce01 --- /dev/null +++ b/hw/armv7m_nvic.c @@ -0,0 +1,409 @@ +/* + * ARM Nested Vectored Interrupt Controller + * + * Copyright (c) 2006-2007 CodeSourcery. + * Written by Paul Brook + * + * This code is licenced under the GPL. + * + * The ARMv7M System controller is fairly tightly tied in with the + * NVIC. Much of that is also implemented here. + */ + +#include "sysbus.h" +#include "qemu-timer.h" +#include "arm-misc.h" + +/* 32 internal lines (16 used for system exceptions) plus 64 external + interrupt lines. */ +#define GIC_NIRQ 96 +#define NCPU 1 +#define NVIC 1 + +/* Only a single "CPU" interface is present. */ +static inline int +gic_get_current_cpu(void) +{ + return 0; +} + +static uint32_t nvic_readl(void *opaque, uint32_t offset); +static void nvic_writel(void *opaque, uint32_t offset, uint32_t value); + +#include "arm_gic.c" + +typedef struct { + gic_state gic; + struct { + uint32_t control; + uint32_t reload; + int64_t tick; + QEMUTimer *timer; + } systick; +} nvic_state; + +/* 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) +{ + if (reset) + s->systick.tick = qemu_get_clock(vm_clock); + s->systick.tick += (s->systick.reload + 1) * systick_scale(s); + qemu_mod_timer(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); + } +} + +/* 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); + 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); +} + +static uint32_t nvic_readl(void *opaque, uint32_t offset) +{ + nvic_state *s = (nvic_state *)opaque; + uint32_t val; + int irq; + + switch (offset) { + case 4: /* Interrupt Control Type. */ + return (GIC_NIRQ / 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_get_clock(vm_clock); + 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. */ + return cpu_single_env->cp15.c0_cpuid; + case 0xd04: /* Interrypt 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 < GIC_NIRQ; 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. */ + return cpu_single_env->v7m.vecbase; + case 0xd0c: /* Application Interrupt/Reset Control. */ + return 0xfa05000; + case 0xd10: /* System Control. */ + /* TODO: Implement SLEEPONEXIT. */ + return 0; + case 0xd14: /* Configuration Control. */ + /* TODO: Implement Configuration Control bits. */ + return 0; + case 0xd18: case 0xd1c: case 0xd20: /* System Handler Priority. */ + irq = offset - 0xd14; + val = 0; + val |= s->gic.priority1[irq++][0]; + val |= s->gic.priority1[irq++][0] << 8; + val |= s->gic.priority1[irq++][0] << 16; + val |= s->gic.priority1[irq][0] << 24; + return val; + 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. */ + hw_error("Not implemented: Configurable Fault Status."); + 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. */ + goto bad_reg; + 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: + bad_reg: + hw_error("NVIC: Bad read offset 0x%x\n", offset); + } +} + +static void nvic_writel(void *opaque, uint32_t offset, uint32_t value) +{ + nvic_state *s = (nvic_state *)opaque; + 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_get_clock(vm_clock); + if (value & SYSTICK_ENABLE) { + if (s->systick.tick) { + s->systick.tick += now; + qemu_mod_timer(s->systick.timer, s->systick.tick); + } else { + systick_reload(s, 1); + } + } else { + qemu_del_timer(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_single_env->v7m.vecbase = value & 0xffffff80; + break; + case 0xd0c: /* Application Interrupt/Reset Control. */ + if ((value >> 16) == 0x05fa) { + if (value & 2) { + hw_error("VECTCLRACTIVE not implemented"); + } + if (value & 5) { + hw_error("System reset"); + } + } + break; + case 0xd10: /* System Control. */ + case 0xd14: /* Configuration Control. */ + /* TODO: Implement control registers. */ + goto bad_reg; + case 0xd18: case 0xd1c: case 0xd20: /* System Handler Priority. */ + { + int irq; + irq = offset - 0xd14; + s->gic.priority1[irq++][0] = value & 0xff; + s->gic.priority1[irq++][0] = (value >> 8) & 0xff; + s->gic.priority1[irq++][0] = (value >> 16) & 0xff; + s->gic.priority1[irq][0] = (value >> 24) & 0xff; + gic_update(&s->gic); + } + 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. */ + goto bad_reg; + default: + bad_reg: + hw_error("NVIC: Bad write offset 0x%x\n", offset); + } +} + +static void nvic_save(QEMUFile *f, void *opaque) +{ + nvic_state *s = (nvic_state *)opaque; + + qemu_put_be32(f, s->systick.control); + qemu_put_be32(f, s->systick.reload); + qemu_put_be64(f, s->systick.tick); + qemu_put_timer(f, s->systick.timer); +} + +static int nvic_load(QEMUFile *f, void *opaque, int version_id) +{ + nvic_state *s = (nvic_state *)opaque; + + if (version_id != 1) + return -EINVAL; + + s->systick.control = qemu_get_be32(f); + s->systick.reload = qemu_get_be32(f); + s->systick.tick = qemu_get_be64(f); + qemu_get_timer(f, s->systick.timer); + + return 0; +} + +static int armv7m_nvic_init(SysBusDevice *dev) +{ + nvic_state *s= FROM_SYSBUSGIC(nvic_state, dev); + + gic_init(&s->gic); + cpu_register_physical_memory(0xe000e000, 0x1000, s->gic.iomemtype); + s->systick.timer = qemu_new_timer(vm_clock, systick_timer_tick, s); + register_savevm(&dev->qdev, "armv7m_nvic", -1, 1, nvic_save, nvic_load, s); + return 0; +} + +static void armv7m_nvic_register_devices(void) +{ + sysbus_register_dev("armv7m_nvic", sizeof(nvic_state), armv7m_nvic_init); +} + +device_init(armv7m_nvic_register_devices) diff --git a/hw/audiodev.h b/hw/audiodev.h new file mode 100644 index 0000000..8e930b2 --- /dev/null +++ b/hw/audiodev.h @@ -0,0 +1,20 @@ +/* es1370.c */ +int es1370_init(PCIBus *bus); + +/* sb16.c */ +int SB16_init(qemu_irq *pic); + +/* adlib.c */ +int Adlib_init(qemu_irq *pic); + +/* gus.c */ +int GUS_init(qemu_irq *pic); + +/* ac97.c */ +int ac97_init(PCIBus *buf); + +/* cs4231a.c */ +int cs4231a_init(qemu_irq *pic); + +/* intel-hda.c + hda-audio.c */ +int intel_hda_and_codec_init(PCIBus *bus); diff --git a/hw/axis_dev88.c b/hw/axis_dev88.c new file mode 100644 index 0000000..57b5e2f --- /dev/null +++ b/hw/axis_dev88.c @@ -0,0 +1,357 @@ +/* + * 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 "sysbus.h" +#include "net.h" +#include "flash.h" +#include "boards.h" +#include "sysemu.h" +#include "etraxfs.h" +#include "loader.h" +#include "elf.h" +#include "cris-boot.h" + +#define D(x) +#define DNAND(x) + +struct nand_state_t +{ + NANDFlashState *nand; + unsigned int rdy:1; + unsigned int ale:1; + unsigned int cle:1; + unsigned int ce:1; +}; + +static struct nand_state_t nand_state; +static uint32_t nand_readl (void *opaque, target_phys_addr_t addr) +{ + 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_writel (void *opaque, target_phys_addr_t addr, uint32_t value) +{ + struct nand_state_t *s = opaque; + int rdy; + + DNAND(printf("%s addr=%x v=%x\n", __func__, addr, 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 CPUReadMemoryFunc * const nand_read[] = { + &nand_readl, + &nand_readl, + &nand_readl, +}; + +static CPUWriteMemoryFunc * const nand_write[] = { + &nand_writel, + &nand_writel, + &nand_writel, +}; + + +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 +{ + struct nand_state_t *nand; + struct tempsensor_t tempsensor; + uint32_t regs[0x5c / 4]; +} gpio_state; + +static uint32_t gpio_readl (void *opaque, target_phys_addr_t addr) +{ + 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_writel (void *opaque, target_phys_addr_t addr, uint32_t value) +{ + struct gpio_state_t *s = opaque; + D(printf("%s %x=%x\n", __func__, addr, 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 CPUReadMemoryFunc * const gpio_read[] = { + NULL, NULL, + &gpio_readl, +}; + +static CPUWriteMemoryFunc * const gpio_write[] = { + NULL, NULL, + &gpio_writel, +}; + +#define INTMEM_SIZE (128 * 1024) + +static struct cris_load_info li; + +static +void axisdev88_init (ram_addr_t ram_size, + const char *boot_device, + const char *kernel_filename, const char *kernel_cmdline, + const char *initrd_filename, const char *cpu_model) +{ + CPUState *env; + DeviceState *dev; + SysBusDevice *s; + qemu_irq irq[30], nmi[2], *cpu_irq; + void *etraxfs_dmac; + struct etraxfs_dma_client *eth[2] = {NULL, NULL}; + int i; + int nand_regs; + int gpio_regs; + ram_addr_t phys_ram; + ram_addr_t phys_intmem; + + /* init CPUs */ + if (cpu_model == NULL) { + cpu_model = "crisv32"; + } + env = cpu_init(cpu_model); + + /* allocate RAM */ + phys_ram = qemu_ram_alloc(NULL, "axisdev88.ram", ram_size); + cpu_register_physical_memory(0x40000000, ram_size, phys_ram | IO_MEM_RAM); + + /* The ETRAX-FS has 128Kb on chip ram, the docs refer to it as the + internal memory. */ + phys_intmem = qemu_ram_alloc(NULL, "axisdev88.chipram", INTMEM_SIZE); + cpu_register_physical_memory(0x38000000, INTMEM_SIZE, + phys_intmem | IO_MEM_RAM); + + + /* Attach a NAND flash to CS1. */ + nand_state.nand = nand_init(NAND_MFR_STMICRO, 0x39); + nand_regs = cpu_register_io_memory(nand_read, nand_write, &nand_state, + DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(0x10000000, 0x05000000, nand_regs); + + gpio_state.nand = &nand_state; + gpio_regs = cpu_register_io_memory(gpio_read, gpio_write, &gpio_state, + DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(0x3001a000, 0x5c, gpio_regs); + + + cpu_irq = cris_pic_init_cpu(env); + 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 = sysbus_from_qdev(dev); + sysbus_mmio_map(s, 0, 0x3001c000); + sysbus_connect_irq(s, 0, cpu_irq[0]); + sysbus_connect_irq(s, 1, cpu_irq[1]); + 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. */ + eth[0] = etraxfs_eth_init(&nd_table[0], 0x30034000, 1); + if (nb_nics > 1) + eth[1] = etraxfs_eth_init(&nd_table[1], 0x30036000, 2); + + /* The DMA Connector block is missing, hardwire things for now. */ + etraxfs_dmac_connect_client(etraxfs_dmac, 0, eth[0]); + etraxfs_dmac_connect_client(etraxfs_dmac, 1, eth[0] + 1); + if (eth[1]) { + etraxfs_dmac_connect_client(etraxfs_dmac, 6, eth[1]); + etraxfs_dmac_connect_client(etraxfs_dmac, 7, eth[1] + 1); + } + + /* 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) { + fprintf(stderr, "Kernel image must be specified\n"); + exit(1); + } + + li.image_filename = kernel_filename; + li.cmdline = kernel_cmdline; + cris_load_image(env, &li); +} + +static QEMUMachine axisdev88_machine = { + .name = "axis-dev88", + .desc = "AXIS devboard 88", + .init = axisdev88_init, +}; + +static void axisdev88_machine_init(void) +{ + qemu_register_machine(&axisdev88_machine); +} + +machine_init(axisdev88_machine_init); diff --git a/hw/baum.c b/hw/baum.c new file mode 100644 index 0000000..21326ae --- /dev/null +++ b/hw/baum.c @@ -0,0 +1,642 @@ +/* + * QEMU Baum Braille Device + * + * Copyright (c) 2008 Samuel Thibault + * + * 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-char.h" +#include "qemu-timer.h" +#include "usb.h" +#include "baum.h" +#include +#include +#include +#ifdef CONFIG_SDL +#include +#endif + +#if 0 +#define DPRINTF(fmt, ...) \ + printf(fmt, ## __VA_ARGS__) +#else +#define DPRINTF(fmt, ...) +#endif + +#define ESC 0x1B + +#define BAUM_REQ_DisplayData 0x01 +#define BAUM_REQ_GetVersionNumber 0x05 +#define BAUM_REQ_GetKeys 0x08 +#define BAUM_REQ_SetMode 0x12 +#define BAUM_REQ_SetProtocol 0x15 +#define BAUM_REQ_GetDeviceIdentity 0x84 +#define BAUM_REQ_GetSerialNumber 0x8A + +#define BAUM_RSP_CellCount 0x01 +#define BAUM_RSP_VersionNumber 0x05 +#define BAUM_RSP_ModeSetting 0x11 +#define BAUM_RSP_CommunicationChannel 0x16 +#define BAUM_RSP_PowerdownSignal 0x17 +#define BAUM_RSP_HorizontalSensors 0x20 +#define BAUM_RSP_VerticalSensors 0x21 +#define BAUM_RSP_RoutingKeys 0x22 +#define BAUM_RSP_Switches 0x23 +#define BAUM_RSP_TopKeys 0x24 +#define BAUM_RSP_HorizontalSensor 0x25 +#define BAUM_RSP_VerticalSensor 0x26 +#define BAUM_RSP_RoutingKey 0x27 +#define BAUM_RSP_FrontKeys6 0x28 +#define BAUM_RSP_BackKeys6 0x29 +#define BAUM_RSP_CommandKeys 0x2B +#define BAUM_RSP_FrontKeys10 0x2C +#define BAUM_RSP_BackKeys10 0x2D +#define BAUM_RSP_EntryKeys 0x33 +#define BAUM_RSP_JoyStick 0x34 +#define BAUM_RSP_ErrorCode 0x40 +#define BAUM_RSP_InfoBlock 0x42 +#define BAUM_RSP_DeviceIdentity 0x84 +#define BAUM_RSP_SerialNumber 0x8A +#define BAUM_RSP_BluetoothName 0x8C + +#define BAUM_TL1 0x01 +#define BAUM_TL2 0x02 +#define BAUM_TL3 0x04 +#define BAUM_TR1 0x08 +#define BAUM_TR2 0x10 +#define BAUM_TR3 0x20 + +#define BUF_SIZE 256 + +typedef struct { + CharDriverState *chr; + + brlapi_handle_t *brlapi; + int brlapi_fd; + unsigned int x, y; + + uint8_t in_buf[BUF_SIZE]; + uint8_t in_buf_used; + uint8_t out_buf[BUF_SIZE]; + uint8_t out_buf_used, out_buf_ptr; + + QEMUTimer *cellCount_timer; +} BaumDriverState; + +/* Let's assume NABCC by default */ +static const uint8_t nabcc_translation[256] = { + [0] = ' ', +#ifndef BRLAPI_DOTS +#define BRLAPI_DOTS(d1,d2,d3,d4,d5,d6,d7,d8) \ + ((d1?BRLAPI_DOT1:0)|\ + (d2?BRLAPI_DOT2:0)|\ + (d3?BRLAPI_DOT3:0)|\ + (d4?BRLAPI_DOT4:0)|\ + (d5?BRLAPI_DOT5:0)|\ + (d6?BRLAPI_DOT6:0)|\ + (d7?BRLAPI_DOT7:0)|\ + (d8?BRLAPI_DOT8:0)) +#endif + [BRLAPI_DOTS(1,0,0,0,0,0,0,0)] = 'a', + [BRLAPI_DOTS(1,1,0,0,0,0,0,0)] = 'b', + [BRLAPI_DOTS(1,0,0,1,0,0,0,0)] = 'c', + [BRLAPI_DOTS(1,0,0,1,1,0,0,0)] = 'd', + [BRLAPI_DOTS(1,0,0,0,1,0,0,0)] = 'e', + [BRLAPI_DOTS(1,1,0,1,0,0,0,0)] = 'f', + [BRLAPI_DOTS(1,1,0,1,1,0,0,0)] = 'g', + [BRLAPI_DOTS(1,1,0,0,1,0,0,0)] = 'h', + [BRLAPI_DOTS(0,1,0,1,0,0,0,0)] = 'i', + [BRLAPI_DOTS(0,1,0,1,1,0,0,0)] = 'j', + [BRLAPI_DOTS(1,0,1,0,0,0,0,0)] = 'k', + [BRLAPI_DOTS(1,1,1,0,0,0,0,0)] = 'l', + [BRLAPI_DOTS(1,0,1,1,0,0,0,0)] = 'm', + [BRLAPI_DOTS(1,0,1,1,1,0,0,0)] = 'n', + [BRLAPI_DOTS(1,0,1,0,1,0,0,0)] = 'o', + [BRLAPI_DOTS(1,1,1,1,0,0,0,0)] = 'p', + [BRLAPI_DOTS(1,1,1,1,1,0,0,0)] = 'q', + [BRLAPI_DOTS(1,1,1,0,1,0,0,0)] = 'r', + [BRLAPI_DOTS(0,1,1,1,0,0,0,0)] = 's', + [BRLAPI_DOTS(0,1,1,1,1,0,0,0)] = 't', + [BRLAPI_DOTS(1,0,1,0,0,1,0,0)] = 'u', + [BRLAPI_DOTS(1,1,1,0,0,1,0,0)] = 'v', + [BRLAPI_DOTS(0,1,0,1,1,1,0,0)] = 'w', + [BRLAPI_DOTS(1,0,1,1,0,1,0,0)] = 'x', + [BRLAPI_DOTS(1,0,1,1,1,1,0,0)] = 'y', + [BRLAPI_DOTS(1,0,1,0,1,1,0,0)] = 'z', + + [BRLAPI_DOTS(1,0,0,0,0,0,1,0)] = 'A', + [BRLAPI_DOTS(1,1,0,0,0,0,1,0)] = 'B', + [BRLAPI_DOTS(1,0,0,1,0,0,1,0)] = 'C', + [BRLAPI_DOTS(1,0,0,1,1,0,1,0)] = 'D', + [BRLAPI_DOTS(1,0,0,0,1,0,1,0)] = 'E', + [BRLAPI_DOTS(1,1,0,1,0,0,1,0)] = 'F', + [BRLAPI_DOTS(1,1,0,1,1,0,1,0)] = 'G', + [BRLAPI_DOTS(1,1,0,0,1,0,1,0)] = 'H', + [BRLAPI_DOTS(0,1,0,1,0,0,1,0)] = 'I', + [BRLAPI_DOTS(0,1,0,1,1,0,1,0)] = 'J', + [BRLAPI_DOTS(1,0,1,0,0,0,1,0)] = 'K', + [BRLAPI_DOTS(1,1,1,0,0,0,1,0)] = 'L', + [BRLAPI_DOTS(1,0,1,1,0,0,1,0)] = 'M', + [BRLAPI_DOTS(1,0,1,1,1,0,1,0)] = 'N', + [BRLAPI_DOTS(1,0,1,0,1,0,1,0)] = 'O', + [BRLAPI_DOTS(1,1,1,1,0,0,1,0)] = 'P', + [BRLAPI_DOTS(1,1,1,1,1,0,1,0)] = 'Q', + [BRLAPI_DOTS(1,1,1,0,1,0,1,0)] = 'R', + [BRLAPI_DOTS(0,1,1,1,0,0,1,0)] = 'S', + [BRLAPI_DOTS(0,1,1,1,1,0,1,0)] = 'T', + [BRLAPI_DOTS(1,0,1,0,0,1,1,0)] = 'U', + [BRLAPI_DOTS(1,1,1,0,0,1,1,0)] = 'V', + [BRLAPI_DOTS(0,1,0,1,1,1,1,0)] = 'W', + [BRLAPI_DOTS(1,0,1,1,0,1,1,0)] = 'X', + [BRLAPI_DOTS(1,0,1,1,1,1,1,0)] = 'Y', + [BRLAPI_DOTS(1,0,1,0,1,1,1,0)] = 'Z', + + [BRLAPI_DOTS(0,0,1,0,1,1,0,0)] = '0', + [BRLAPI_DOTS(0,1,0,0,0,0,0,0)] = '1', + [BRLAPI_DOTS(0,1,1,0,0,0,0,0)] = '2', + [BRLAPI_DOTS(0,1,0,0,1,0,0,0)] = '3', + [BRLAPI_DOTS(0,1,0,0,1,1,0,0)] = '4', + [BRLAPI_DOTS(0,1,0,0,0,1,0,0)] = '5', + [BRLAPI_DOTS(0,1,1,0,1,0,0,0)] = '6', + [BRLAPI_DOTS(0,1,1,0,1,1,0,0)] = '7', + [BRLAPI_DOTS(0,1,1,0,0,1,0,0)] = '8', + [BRLAPI_DOTS(0,0,1,0,1,0,0,0)] = '9', + + [BRLAPI_DOTS(0,0,0,1,0,1,0,0)] = '.', + [BRLAPI_DOTS(0,0,1,1,0,1,0,0)] = '+', + [BRLAPI_DOTS(0,0,1,0,0,1,0,0)] = '-', + [BRLAPI_DOTS(1,0,0,0,0,1,0,0)] = '*', + [BRLAPI_DOTS(0,0,1,1,0,0,0,0)] = '/', + [BRLAPI_DOTS(1,1,1,0,1,1,0,0)] = '(', + [BRLAPI_DOTS(0,1,1,1,1,1,0,0)] = ')', + + [BRLAPI_DOTS(1,1,1,1,0,1,0,0)] = '&', + [BRLAPI_DOTS(0,0,1,1,1,1,0,0)] = '#', + + [BRLAPI_DOTS(0,0,0,0,0,1,0,0)] = ',', + [BRLAPI_DOTS(0,0,0,0,1,1,0,0)] = ';', + [BRLAPI_DOTS(1,0,0,0,1,1,0,0)] = ':', + [BRLAPI_DOTS(0,1,1,1,0,1,0,0)] = '!', + [BRLAPI_DOTS(1,0,0,1,1,1,0,0)] = '?', + [BRLAPI_DOTS(0,0,0,0,1,0,0,0)] = '"', + [BRLAPI_DOTS(0,0,1,0,0,0,0,0)] ='\'', + [BRLAPI_DOTS(0,0,0,1,0,0,0,0)] = '`', + [BRLAPI_DOTS(0,0,0,1,1,0,1,0)] = '^', + [BRLAPI_DOTS(0,0,0,1,1,0,0,0)] = '~', + [BRLAPI_DOTS(0,1,0,1,0,1,1,0)] = '[', + [BRLAPI_DOTS(1,1,0,1,1,1,1,0)] = ']', + [BRLAPI_DOTS(0,1,0,1,0,1,0,0)] = '{', + [BRLAPI_DOTS(1,1,0,1,1,1,0,0)] = '}', + [BRLAPI_DOTS(1,1,1,1,1,1,0,0)] = '=', + [BRLAPI_DOTS(1,1,0,0,0,1,0,0)] = '<', + [BRLAPI_DOTS(0,0,1,1,1,0,0,0)] = '>', + [BRLAPI_DOTS(1,1,0,1,0,1,0,0)] = '$', + [BRLAPI_DOTS(1,0,0,1,0,1,0,0)] = '%', + [BRLAPI_DOTS(0,0,0,1,0,0,1,0)] = '@', + [BRLAPI_DOTS(1,1,0,0,1,1,0,0)] = '|', + [BRLAPI_DOTS(1,1,0,0,1,1,1,0)] ='\\', + [BRLAPI_DOTS(0,0,0,1,1,1,0,0)] = '_', +}; + +/* The serial port can receive more of our data */ +static void baum_accept_input(struct CharDriverState *chr) +{ + BaumDriverState *baum = chr->opaque; + int room, first; + + if (!baum->out_buf_used) + return; + room = qemu_chr_can_read(chr); + if (!room) + return; + if (room > baum->out_buf_used) + room = baum->out_buf_used; + + first = BUF_SIZE - baum->out_buf_ptr; + if (room > first) { + qemu_chr_read(chr, baum->out_buf + baum->out_buf_ptr, first); + baum->out_buf_ptr = 0; + baum->out_buf_used -= first; + room -= first; + } + qemu_chr_read(chr, baum->out_buf + baum->out_buf_ptr, room); + baum->out_buf_ptr += room; + baum->out_buf_used -= room; +} + +/* We want to send a packet */ +static void baum_write_packet(BaumDriverState *baum, const uint8_t *buf, int len) +{ + uint8_t io_buf[1 + 2 * len], *cur = io_buf; + int room; + *cur++ = ESC; + while (len--) + if ((*cur++ = *buf++) == ESC) + *cur++ = ESC; + room = qemu_chr_can_read(baum->chr); + len = cur - io_buf; + if (len <= room) { + /* Fits */ + qemu_chr_read(baum->chr, io_buf, len); + } else { + int first; + uint8_t out; + /* Can't fit all, send what can be, and store the rest. */ + qemu_chr_read(baum->chr, io_buf, room); + len -= room; + cur = io_buf + room; + if (len > BUF_SIZE - baum->out_buf_used) { + /* Can't even store it, drop the previous data... */ + assert(len <= BUF_SIZE); + baum->out_buf_used = 0; + baum->out_buf_ptr = 0; + } + out = baum->out_buf_ptr; + baum->out_buf_used += len; + first = BUF_SIZE - baum->out_buf_ptr; + if (len > first) { + memcpy(baum->out_buf + out, cur, first); + out = 0; + len -= first; + cur += first; + } + memcpy(baum->out_buf + out, cur, len); + } +} + +/* Called when the other end seems to have a wrong idea of our display size */ +static void baum_cellCount_timer_cb(void *opaque) +{ + BaumDriverState *baum = opaque; + uint8_t cell_count[] = { BAUM_RSP_CellCount, baum->x * baum->y }; + DPRINTF("Timeout waiting for DisplayData, sending cell count\n"); + baum_write_packet(baum, cell_count, sizeof(cell_count)); +} + +/* Try to interpret a whole incoming packet */ +static int baum_eat_packet(BaumDriverState *baum, const uint8_t *buf, int len) +{ + const uint8_t *cur = buf; + uint8_t req = 0; + + if (!len--) + return 0; + if (*cur++ != ESC) { + while (*cur != ESC) { + if (!len--) + return 0; + cur++; + } + DPRINTF("Dropped %d bytes!\n", cur - buf); + } + +#define EAT(c) do {\ + if (!len--) \ + return 0; \ + if ((c = *cur++) == ESC) { \ + if (!len--) \ + return 0; \ + if (*cur++ != ESC) { \ + DPRINTF("Broken packet %#2x, tossing\n", req); \ + if (qemu_timer_pending(baum->cellCount_timer)) { \ + qemu_del_timer(baum->cellCount_timer); \ + baum_cellCount_timer_cb(baum); \ + } \ + return (cur - 2 - buf); \ + } \ + } \ +} while (0) + + EAT(req); + switch (req) { + case BAUM_REQ_DisplayData: + { + uint8_t cells[baum->x * baum->y], c; + uint8_t text[baum->x * baum->y]; + uint8_t zero[baum->x * baum->y]; + int cursor = BRLAPI_CURSOR_OFF; + int i; + + /* Allow 100ms to complete the DisplayData packet */ + qemu_mod_timer(baum->cellCount_timer, qemu_get_clock(vm_clock) + + get_ticks_per_sec() / 10); + for (i = 0; i < baum->x * baum->y ; i++) { + EAT(c); + cells[i] = c; + if ((c & (BRLAPI_DOT7|BRLAPI_DOT8)) + == (BRLAPI_DOT7|BRLAPI_DOT8)) { + cursor = i + 1; + c &= ~(BRLAPI_DOT7|BRLAPI_DOT8); + } + if (!(c = nabcc_translation[c])) + c = '?'; + text[i] = c; + } + qemu_del_timer(baum->cellCount_timer); + + memset(zero, 0, sizeof(zero)); + + brlapi_writeArguments_t wa = { + .displayNumber = BRLAPI_DISPLAY_DEFAULT, + .regionBegin = 1, + .regionSize = baum->x * baum->y, + .text = (char *)text, + .textSize = baum->x * baum->y, + .andMask = zero, + .orMask = cells, + .cursor = cursor, + .charset = (char *)"ISO-8859-1", + }; + + if (brlapi__write(baum->brlapi, &wa) == -1) + brlapi_perror("baum brlapi_write"); + break; + } + case BAUM_REQ_SetMode: + { + uint8_t mode, setting; + DPRINTF("SetMode\n"); + EAT(mode); + EAT(setting); + /* ignore */ + break; + } + case BAUM_REQ_SetProtocol: + { + uint8_t protocol; + DPRINTF("SetProtocol\n"); + EAT(protocol); + /* ignore */ + break; + } + case BAUM_REQ_GetDeviceIdentity: + { + uint8_t identity[17] = { BAUM_RSP_DeviceIdentity, + 'B','a','u','m',' ','V','a','r','i','o' }; + DPRINTF("GetDeviceIdentity\n"); + identity[11] = '0' + baum->x / 10; + identity[12] = '0' + baum->x % 10; + baum_write_packet(baum, identity, sizeof(identity)); + break; + } + case BAUM_REQ_GetVersionNumber: + { + uint8_t version[] = { BAUM_RSP_VersionNumber, 1 }; /* ? */ + DPRINTF("GetVersionNumber\n"); + baum_write_packet(baum, version, sizeof(version)); + break; + } + case BAUM_REQ_GetSerialNumber: + { + uint8_t serial[] = { BAUM_RSP_SerialNumber, + '0','0','0','0','0','0','0','0' }; + DPRINTF("GetSerialNumber\n"); + baum_write_packet(baum, serial, sizeof(serial)); + break; + } + case BAUM_REQ_GetKeys: + { + DPRINTF("Get%0#2x\n", req); + /* ignore */ + break; + } + default: + DPRINTF("unrecognized request %0#2x\n", req); + do + if (!len--) + return 0; + while (*cur++ != ESC); + cur--; + break; + } + return cur - buf; +} + +/* The other end is writing some data. Store it and try to interpret */ +static int baum_write(CharDriverState *chr, const uint8_t *buf, int len) +{ + BaumDriverState *baum = chr->opaque; + int tocopy, cur, eaten, orig_len = len; + + if (!len) + return 0; + if (!baum->brlapi) + return len; + + while (len) { + /* Complete our buffer as much as possible */ + tocopy = len; + if (tocopy > BUF_SIZE - baum->in_buf_used) + tocopy = BUF_SIZE - baum->in_buf_used; + + memcpy(baum->in_buf + baum->in_buf_used, buf, tocopy); + baum->in_buf_used += tocopy; + buf += tocopy; + len -= tocopy; + + /* Interpret it as much as possible */ + cur = 0; + while (cur < baum->in_buf_used && + (eaten = baum_eat_packet(baum, baum->in_buf + cur, baum->in_buf_used - cur))) + cur += eaten; + + /* Shift the remainder */ + if (cur) { + memmove(baum->in_buf, baum->in_buf + cur, baum->in_buf_used - cur); + baum->in_buf_used -= cur; + } + + /* And continue if any data left */ + } + return orig_len; +} + +/* The other end sent us some event */ +static void baum_send_event(CharDriverState *chr, int event) +{ + BaumDriverState *baum = chr->opaque; + switch (event) { + case CHR_EVENT_BREAK: + break; + case CHR_EVENT_OPENED: + /* Reset state */ + baum->in_buf_used = 0; + break; + } +} + +/* Send the key code to the other end */ +static void baum_send_key(BaumDriverState *baum, uint8_t type, uint8_t value) { + uint8_t packet[] = { type, value }; + DPRINTF("writing key %x %x\n", type, value); + baum_write_packet(baum, packet, sizeof(packet)); +} + +/* We got some data on the BrlAPI socket */ +static void baum_chr_read(void *opaque) +{ + BaumDriverState *baum = opaque; + brlapi_keyCode_t code; + int ret; + if (!baum->brlapi) + return; + while ((ret = brlapi__readKey(baum->brlapi, 0, &code)) == 1) { + DPRINTF("got key %"BRLAPI_PRIxKEYCODE"\n", code); + /* Emulate */ + switch (code & BRLAPI_KEY_TYPE_MASK) { + case BRLAPI_KEY_TYPE_CMD: + switch (code & BRLAPI_KEY_CMD_BLK_MASK) { + case BRLAPI_KEY_CMD_ROUTE: + baum_send_key(baum, BAUM_RSP_RoutingKey, (code & BRLAPI_KEY_CMD_ARG_MASK)+1); + baum_send_key(baum, BAUM_RSP_RoutingKey, 0); + break; + case 0: + switch (code & BRLAPI_KEY_CMD_ARG_MASK) { + case BRLAPI_KEY_CMD_FWINLT: + baum_send_key(baum, BAUM_RSP_TopKeys, BAUM_TL2); + baum_send_key(baum, BAUM_RSP_TopKeys, 0); + break; + case BRLAPI_KEY_CMD_FWINRT: + baum_send_key(baum, BAUM_RSP_TopKeys, BAUM_TR2); + baum_send_key(baum, BAUM_RSP_TopKeys, 0); + break; + case BRLAPI_KEY_CMD_LNUP: + baum_send_key(baum, BAUM_RSP_TopKeys, BAUM_TR1); + baum_send_key(baum, BAUM_RSP_TopKeys, 0); + break; + case BRLAPI_KEY_CMD_LNDN: + baum_send_key(baum, BAUM_RSP_TopKeys, BAUM_TR3); + baum_send_key(baum, BAUM_RSP_TopKeys, 0); + break; + case BRLAPI_KEY_CMD_TOP: + baum_send_key(baum, BAUM_RSP_TopKeys, BAUM_TL1|BAUM_TR1); + baum_send_key(baum, BAUM_RSP_TopKeys, 0); + break; + case BRLAPI_KEY_CMD_BOT: + baum_send_key(baum, BAUM_RSP_TopKeys, BAUM_TL3|BAUM_TR3); + baum_send_key(baum, BAUM_RSP_TopKeys, 0); + break; + case BRLAPI_KEY_CMD_TOP_LEFT: + baum_send_key(baum, BAUM_RSP_TopKeys, BAUM_TL2|BAUM_TR1); + baum_send_key(baum, BAUM_RSP_TopKeys, 0); + break; + case BRLAPI_KEY_CMD_BOT_LEFT: + baum_send_key(baum, BAUM_RSP_TopKeys, BAUM_TL2|BAUM_TR3); + baum_send_key(baum, BAUM_RSP_TopKeys, 0); + break; + case BRLAPI_KEY_CMD_HOME: + baum_send_key(baum, BAUM_RSP_TopKeys, BAUM_TL2|BAUM_TR1|BAUM_TR3); + baum_send_key(baum, BAUM_RSP_TopKeys, 0); + break; + case BRLAPI_KEY_CMD_PREFMENU: + baum_send_key(baum, BAUM_RSP_TopKeys, BAUM_TL1|BAUM_TL3|BAUM_TR1); + baum_send_key(baum, BAUM_RSP_TopKeys, 0); + break; + } + } + break; + case BRLAPI_KEY_TYPE_SYM: + break; + } + } + if (ret == -1 && (brlapi_errno != BRLAPI_ERROR_LIBCERR || errno != EINTR)) { + brlapi_perror("baum: brlapi_readKey"); + brlapi__closeConnection(baum->brlapi); + qemu_free(baum->brlapi); + baum->brlapi = NULL; + } +} + +static void baum_close(struct CharDriverState *chr) +{ + BaumDriverState *baum = chr->opaque; + + qemu_free_timer(baum->cellCount_timer); + if (baum->brlapi) { + brlapi__closeConnection(baum->brlapi); + qemu_free(baum->brlapi); + } + qemu_free(baum); +} + +CharDriverState *chr_baum_init(QemuOpts *opts) +{ + BaumDriverState *baum; + CharDriverState *chr; + brlapi_handle_t *handle; +#ifdef CONFIG_SDL + SDL_SysWMinfo info; +#endif + int tty; + + baum = qemu_mallocz(sizeof(BaumDriverState)); + baum->chr = chr = qemu_mallocz(sizeof(CharDriverState)); + + chr->opaque = baum; + chr->chr_write = baum_write; + chr->chr_send_event = baum_send_event; + chr->chr_accept_input = baum_accept_input; + chr->chr_close = baum_close; + + handle = qemu_mallocz(brlapi_getHandleSize()); + baum->brlapi = handle; + + baum->brlapi_fd = brlapi__openConnection(handle, NULL, NULL); + if (baum->brlapi_fd == -1) { + brlapi_perror("baum_init: brlapi_openConnection"); + goto fail_handle; + } + + baum->cellCount_timer = qemu_new_timer(vm_clock, baum_cellCount_timer_cb, baum); + + if (brlapi__getDisplaySize(handle, &baum->x, &baum->y) == -1) { + brlapi_perror("baum_init: brlapi_getDisplaySize"); + goto fail; + } + +#ifdef CONFIG_SDL + memset(&info, 0, sizeof(info)); + SDL_VERSION(&info.version); + if (SDL_GetWMInfo(&info)) + tty = info.info.x11.wmwindow; + else +#endif + tty = BRLAPI_TTY_DEFAULT; + + if (brlapi__enterTtyMode(handle, tty, NULL) == -1) { + brlapi_perror("baum_init: brlapi_enterTtyMode"); + goto fail; + } + + qemu_set_fd_handler(baum->brlapi_fd, baum_chr_read, NULL, baum); + + qemu_chr_generic_open(chr); + + return chr; + +fail: + qemu_free_timer(baum->cellCount_timer); + brlapi__closeConnection(handle); +fail_handle: + qemu_free(handle); + qemu_free(chr); + qemu_free(baum); + return NULL; +} diff --git a/hw/baum.h b/hw/baum.h new file mode 100644 index 0000000..8af710f --- /dev/null +++ b/hw/baum.h @@ -0,0 +1,26 @@ +/* + * QEMU Baum + * + * Copyright (c) 2008 Samuel Thibault + * + * 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. + */ + +/* char device */ +CharDriverState *chr_baum_init(QemuOpts *opts); diff --git a/hw/bitbang_i2c.c b/hw/bitbang_i2c.c new file mode 100644 index 0000000..4ee99a1 --- /dev/null +++ b/hw/bitbang_i2c.c @@ -0,0 +1,228 @@ +/* + * Bit-Bang i2c emulation extracted from + * Marvell MV88W8618 / Freecom MusicPal emulation. + * + * Copyright (c) 2008 Jan Kiszka + * + * This code is licenced under the GNU GPL v2. + */ +#include "hw.h" +#include "bitbang_i2c.h" +#include "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 +} bitbang_i2c_state; + +struct bitbang_i2c_interface { + i2c_bus *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: + 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_nack(i2c->bus); + } else { + DPRINTF("ACKED\n"); + } + return bitbang_i2c_ret(i2c, 1); + } + abort(); +} + +bitbang_i2c_interface *bitbang_i2c_init(i2c_bus *bus) +{ + bitbang_i2c_interface *s; + + s = qemu_mallocz(sizeof(bitbang_i2c_interface)); + + s->bus = bus; + s->last_data = 1; + s->last_clock = 1; + s->device_out = 1; + + return s; +} + +/* GPIO interface. */ +typedef struct { + SysBusDevice busdev; + 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 *dev) +{ + GPIOI2CState *s = FROM_SYSBUS(GPIOI2CState, dev); + i2c_bus *bus; + + sysbus_init_mmio(dev, 0x0, 0); + + bus = i2c_init_bus(&dev->qdev, "i2c"); + s->bitbang = bitbang_i2c_init(bus); + + qdev_init_gpio_in(&dev->qdev, bitbang_i2c_gpio_set, 2); + qdev_init_gpio_out(&dev->qdev, &s->out, 1); + + return 0; +} + +static SysBusDeviceInfo gpio_i2c_info = { + .init = gpio_i2c_init, + .qdev.name = "gpio_i2c", + .qdev.desc = "Virtual GPIO to I2C bridge", + .qdev.size = sizeof(GPIOI2CState), +}; + +static void bitbang_i2c_register(void) +{ + sysbus_register_withprop(&gpio_i2c_info); +} + +device_init(bitbang_i2c_register) diff --git a/hw/bitbang_i2c.h b/hw/bitbang_i2c.h new file mode 100644 index 0000000..519d2dc --- /dev/null +++ b/hw/bitbang_i2c.h @@ -0,0 +1,14 @@ +#ifndef BITBANG_I2C_H +#define BITBANG_I2C_H + +#include "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(i2c_bus *bus); +int bitbang_i2c_set(bitbang_i2c_interface *i2c, int line, int level); + +#endif diff --git a/hw/blizzard.c b/hw/blizzard.c new file mode 100644 index 0000000..5f329ad --- /dev/null +++ b/hw/blizzard.c @@ -0,0 +1,998 @@ +/* + * Epson S1D13744/S1D13745 (Blizzard/Hailstorm/Tornado) LCD/TV controller. + * + * Copyright (C) 2008 Nokia Corporation + * Written by Andrzej Zaborowski + * + * 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 . + */ + +#include "qemu-common.h" +#include "sysemu.h" +#include "console.h" +#include "devices.h" +#include "vga_int.h" +#include "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; + DisplayState *state; + 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 inline void blizzard_rgb2yuv(int r, int g, int b, + int *y, int *u, int *v) +{ + *y = 0x10 + ((0x838 * r + 0x1022 * g + 0x322 * b) >> 13); + *u = 0x80 + ((0xe0e * b - 0x04c1 * r - 0x94e * g) >> 13); + *v = 0x80 + ((0xe0e * r - 0x0bc7 * g - 0x247 * b) >> 13); +} + +static void blizzard_window(BlizzardState *s) +{ + 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] = (ds_get_bits_per_pixel(s->state) + 7) >> 3; + 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 = qemu_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; + } + + return; +} + +static void blizzard_update_display(void *opaque) +{ + BlizzardState *s = (BlizzardState *) opaque; + int y, bypp, bypl, bwidth; + uint8_t *src, *dst; + + if (!s->enable) + return; + + if (s->x != ds_get_width(s->state) || s->y != ds_get_height(s->state)) { + s->invalidate = 1; + qemu_console_resize(s->state, s->x, s->y); + } + + if (s->invalidate) { + s->invalidate = 0; + + if (s->blank) { + bypp = (ds_get_bits_per_pixel(s->state) + 7) >> 3; + memset(ds_get_data(s->state), 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 = (ds_get_bits_per_pixel(s->state) + 7) >> 3; + 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 = ds_get_data(s->state) + bypl * y + bypp * s->mx[0]; + for (; y < s->my[1]; y ++, src += bypl, dst += bypl) + memcpy(dst, src, bwidth); + + dpy_update(s->state, 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; +} + +static void blizzard_screen_dump(void *opaque, const char *filename) { + BlizzardState *s = (BlizzardState *) opaque; + + blizzard_update_display(opaque); + if (s && ds_get_data(s->state)) + ppm_save(filename, s->state->surface); +} + +#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" + +void *s1d13745_init(qemu_irq gpio_int) +{ + BlizzardState *s = (BlizzardState *) qemu_mallocz(sizeof(*s)); + + s->fb = qemu_malloc(0x180000); + + s->state = graphic_console_init(blizzard_update_display, + blizzard_invalidate_display, + blizzard_screen_dump, NULL, s); + + switch (ds_get_bits_per_pixel(s->state)) { + case 0: + s->line_fn_tab[0] = s->line_fn_tab[1] = + qemu_mallocz(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/blizzard_template.h b/hw/blizzard_template.h new file mode 100644 index 0000000..42f4e90 --- /dev/null +++ b/hw/blizzard_template.h @@ -0,0 +1,136 @@ +/* + * QEMU Epson S1D13744/S1D13745 templates + * + * Copyright (C) 2008 Nokia Corporation + * Written by Andrzej Zaborowski + * + * 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 . + */ + +#define SKIP_PIXEL(to) to += deststep +#if DEPTH == 8 +# define PIXEL_TYPE uint8_t +# define COPY_PIXEL(to, from) *to = from; SKIP_PIXEL(to) +# define COPY_PIXEL1(to, from) *to ++ = from +#elif DEPTH == 15 || DEPTH == 16 +# define PIXEL_TYPE uint16_t +# define COPY_PIXEL(to, from) *to = from; SKIP_PIXEL(to) +# define COPY_PIXEL1(to, from) *to ++ = from +#elif DEPTH == 24 +# define PIXEL_TYPE uint8_t +# define COPY_PIXEL(to, from) \ + to[0] = from; to[1] = (from) >> 8; to[2] = (from) >> 16; SKIP_PIXEL(to) +# define COPY_PIXEL1(to, from) \ + *to ++ = from; *to ++ = (from) >> 8; *to ++ = (from) >> 16 +#elif DEPTH == 32 +# define PIXEL_TYPE uint32_t +# define COPY_PIXEL(to, from) *to = from; SKIP_PIXEL(to) +# 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 = lduw_raw(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/boards.h b/hw/boards.h new file mode 100644 index 0000000..6f0f0d7 --- /dev/null +++ b/hw/boards.h @@ -0,0 +1,38 @@ +/* Declarations for use by board files for creating devices. */ + +#ifndef HW_BOARDS_H +#define HW_BOARDS_H + +#include "qdev.h" + +typedef void QEMUMachineInitFunc(ram_addr_t ram_size, + const char *boot_device, + const char *kernel_filename, + const char *kernel_cmdline, + const char *initrd_filename, + const char *cpu_model); + +typedef struct QEMUMachine { + const char *name; + const char *alias; + const char *desc; + QEMUMachineInitFunc *init; + int use_scsi; + int max_cpus; + unsigned int no_serial:1, + no_parallel:1, + use_virtcon:1, + no_vga:1, + no_floppy:1, + no_cdrom:1, + no_sdcard:1; + int is_default; + GlobalProperty *compat_props; + struct QEMUMachine *next; +} QEMUMachine; + +int qemu_register_machine(QEMUMachine *m); + +extern QEMUMachine *current_machine; + +#endif diff --git a/hw/bonito.c b/hw/bonito.c new file mode 100644 index 0000000..65a4a63 --- /dev/null +++ b/hw/bonito.c @@ -0,0 +1,813 @@ +/* + * 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. + */ + +/* + * 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 + +#include "hw.h" +#include "pci.h" +#include "pc.h" +#include "mips.h" +#include "pci_host.h" +#include "sysemu.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<> 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 uint32_t bonito_readl(void *opaque, target_phys_addr_t addr) +{ + PCIBonitoState *s = opaque; + uint32_t saddr; + + saddr = (addr - BONITO_REGBASE) >> 2; + + DPRINTF("bonito_readl "TARGET_FMT_plx" \n", addr); + switch (saddr) { + case BONITO_INTISR: + return s->regs[saddr]; + default: + return s->regs[saddr]; + } +} + +static CPUWriteMemoryFunc * const bonito_write[] = { + NULL, + NULL, + bonito_writel, +}; + +static CPUReadMemoryFunc * const bonito_read[] = { + NULL, + NULL, + bonito_readl, +}; + +static void bonito_pciconf_writel(void *opaque, target_phys_addr_t addr, + uint32_t val) +{ + PCIBonitoState *s = opaque; + + DPRINTF("bonito_pciconf_writel "TARGET_FMT_plx" val %x \n", addr, val); + s->dev.config_write(&s->dev, addr, val, 4); +} + +static uint32_t bonito_pciconf_readl(void *opaque, target_phys_addr_t addr) +{ + + PCIBonitoState *s = opaque; + + DPRINTF("bonito_pciconf_readl "TARGET_FMT_plx"\n", addr); + return s->dev.config_read(&s->dev, addr, 4); +} + +/* north bridge PCI configure space. 0x1fe0 0000 - 0x1fe0 00ff */ +static CPUWriteMemoryFunc * const bonito_pciconf_write[] = { + NULL, + NULL, + bonito_pciconf_writel, +}; + +static CPUReadMemoryFunc * const bonito_pciconf_read[] = { + NULL, + NULL, + bonito_pciconf_readl, +}; + +static uint32_t bonito_ldma_readl(void *opaque, target_phys_addr_t addr) +{ + uint32_t val; + PCIBonitoState *s = opaque; + + val = ((uint32_t *)(&s->bonldma))[addr/sizeof(uint32_t)]; + + return val; +} + +static void bonito_ldma_writel(void *opaque, target_phys_addr_t addr, + uint32_t val) +{ + PCIBonitoState *s = opaque; + + ((uint32_t *)(&s->bonldma))[addr/sizeof(uint32_t)] = val & 0xffffffff; +} + +static CPUWriteMemoryFunc * const bonito_ldma_write[] = { + NULL, + NULL, + bonito_ldma_writel, +}; + +static CPUReadMemoryFunc * const bonito_ldma_read[] = { + NULL, + NULL, + bonito_ldma_readl, +}; + +static uint32_t bonito_cop_readl(void *opaque, target_phys_addr_t addr) +{ + uint32_t val; + PCIBonitoState *s = opaque; + + val = ((uint32_t *)(&s->boncop))[addr/sizeof(uint32_t)]; + + return val; +} + +static void bonito_cop_writel(void *opaque, target_phys_addr_t addr, + uint32_t val) +{ + PCIBonitoState *s = opaque; + + ((uint32_t *)(&s->boncop))[addr/sizeof(uint32_t)] = val & 0xffffffff; +} + +static CPUWriteMemoryFunc * const bonito_cop_write[] = { + NULL, + NULL, + bonito_cop_writel, +}; + +static CPUReadMemoryFunc * const bonito_cop_read[] = { + NULL, + NULL, + bonito_cop_readl, +}; + +static uint32_t bonito_sbridge_pciaddr(void *opaque, target_phys_addr_t addr) +{ + PCIBonitoState *s = opaque; + 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 = ffs(idsel) - 1; + 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(s->pcihost->bus), devno, funno, regno); + DPRINTF("cfgaddr %x pciaddr %x busno %x devno %d funno %d regno %d \n", + cfgaddr, pciaddr, pci_bus_num(s->pcihost->bus), devno, funno, regno); + + return pciaddr; +} + +static void bonito_spciconf_writeb(void *opaque, target_phys_addr_t addr, + uint32_t val) +{ + PCIBonitoState *s = opaque; + 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 */ + s->pcihost->config_reg = (pciaddr) | (1u << 31); + pci_data_write(s->pcihost->bus, s->pcihost->config_reg, val & 0xff, 1); + + /* clear PCI_STATUS_REC_MASTER_ABORT and PCI_STATUS_REC_TARGET_ABORT */ + status = pci_get_word(s->dev.config + PCI_STATUS); + status &= ~(PCI_STATUS_REC_MASTER_ABORT | PCI_STATUS_REC_TARGET_ABORT); + pci_set_word(s->dev.config + PCI_STATUS, status); +} + +static void bonito_spciconf_writew(void *opaque, target_phys_addr_t addr, + uint32_t val) +{ + PCIBonitoState *s = opaque; + 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 */ + s->pcihost->config_reg = (pciaddr) | (1u << 31); + pci_data_write(s->pcihost->bus, s->pcihost->config_reg, val, 2); + + /* clear PCI_STATUS_REC_MASTER_ABORT and PCI_STATUS_REC_TARGET_ABORT */ + status = pci_get_word(s->dev.config + PCI_STATUS); + status &= ~(PCI_STATUS_REC_MASTER_ABORT | PCI_STATUS_REC_TARGET_ABORT); + pci_set_word(s->dev.config + PCI_STATUS, status); +} + +static void bonito_spciconf_writel(void *opaque, target_phys_addr_t addr, + uint32_t val) +{ + PCIBonitoState *s = opaque; + 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 */ + s->pcihost->config_reg = (pciaddr) | (1u << 31); + pci_data_write(s->pcihost->bus, s->pcihost->config_reg, val, 4); + + /* clear PCI_STATUS_REC_MASTER_ABORT and PCI_STATUS_REC_TARGET_ABORT */ + status = pci_get_word(s->dev.config + PCI_STATUS); + status &= ~(PCI_STATUS_REC_MASTER_ABORT | PCI_STATUS_REC_TARGET_ABORT); + pci_set_word(s->dev.config + PCI_STATUS, status); +} + +static uint32_t bonito_spciconf_readb(void *opaque, target_phys_addr_t addr) +{ + PCIBonitoState *s = opaque; + 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 */ + s->pcihost->config_reg = (pciaddr) | (1u << 31); + + /* clear PCI_STATUS_REC_MASTER_ABORT and PCI_STATUS_REC_TARGET_ABORT */ + status = pci_get_word(s->dev.config + PCI_STATUS); + status &= ~(PCI_STATUS_REC_MASTER_ABORT | PCI_STATUS_REC_TARGET_ABORT); + pci_set_word(s->dev.config + PCI_STATUS, status); + + return pci_data_read(s->pcihost->bus, s->pcihost->config_reg, 1); +} + +static uint32_t bonito_spciconf_readw(void *opaque, target_phys_addr_t addr) +{ + PCIBonitoState *s = opaque; + 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 */ + s->pcihost->config_reg = (pciaddr) | (1u << 31); + + /* clear PCI_STATUS_REC_MASTER_ABORT and PCI_STATUS_REC_TARGET_ABORT */ + status = pci_get_word(s->dev.config + PCI_STATUS); + status &= ~(PCI_STATUS_REC_MASTER_ABORT | PCI_STATUS_REC_TARGET_ABORT); + pci_set_word(s->dev.config + PCI_STATUS, status); + + return pci_data_read(s->pcihost->bus, s->pcihost->config_reg, 2); +} + +static uint32_t bonito_spciconf_readl(void *opaque, target_phys_addr_t addr) +{ + PCIBonitoState *s = opaque; + 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 */ + s->pcihost->config_reg = (pciaddr) | (1u << 31); + + /* clear PCI_STATUS_REC_MASTER_ABORT and PCI_STATUS_REC_TARGET_ABORT */ + status = pci_get_word(s->dev.config + PCI_STATUS); + status &= ~(PCI_STATUS_REC_MASTER_ABORT | PCI_STATUS_REC_TARGET_ABORT); + pci_set_word(s->dev.config + PCI_STATUS, status); + + return pci_data_read(s->pcihost->bus, s->pcihost->config_reg, 4); +} + +/* south bridge PCI configure space. 0x1fe8 0000 - 0x1fef ffff */ +static CPUWriteMemoryFunc * const bonito_spciconf_write[] = { + bonito_spciconf_writeb, + bonito_spciconf_writew, + bonito_spciconf_writel, +}; + +static CPUReadMemoryFunc * const bonito_spciconf_read[] = { + bonito_spciconf_readb, + bonito_spciconf_readw, + bonito_spciconf_readl, +}; + +#define BONITO_IRQ_BASE 32 + +static void pci_bonito_set_irq(void *opaque, int irq_num, int level) +{ + qemu_irq *pic = opaque; + int internal_irq = irq_num - BONITO_IRQ_BASE; + + if (bonito_state->regs[BONITO_INTEDGE] & (1<regs[BONITO_INTPOL] & (1<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, + .minimum_version_id_old = 1, + .fields = (VMStateField []) { + VMSTATE_PCI_DEVICE(dev, PCIBonitoState), + VMSTATE_END_OF_LIST() + } +}; + +static int bonito_pcihost_initfn(SysBusDevice *dev) +{ + return 0; +} + +static int bonito_initfn(PCIDevice *dev) +{ + PCIBonitoState *s = DO_UPCAST(PCIBonitoState, dev, dev); + + /* Bonito North Bridge, built on FPGA, VENDOR_ID/DEVICE_ID are "undefined" */ + pci_config_set_vendor_id(dev->config, 0xdf53); + pci_config_set_device_id(dev->config, 0x00d5); + pci_config_set_class(dev->config, PCI_CLASS_BRIDGE_HOST); + pci_config_set_prog_interface(dev->config, 0x00); + pci_config_set_revision(dev->config, 0x01); + + /* set the north bridge register mapping */ + s->bonito_reg_handle = cpu_register_io_memory(bonito_read, bonito_write, s, + DEVICE_NATIVE_ENDIAN); + s->bonito_reg_start = BONITO_INTERNAL_REG_BASE; + s->bonito_reg_length = BONITO_INTERNAL_REG_SIZE; + cpu_register_physical_memory(s->bonito_reg_start, s->bonito_reg_length, + s->bonito_reg_handle); + + /* set the north bridge pci configure mapping */ + s->bonito_pciconf_handle = cpu_register_io_memory(bonito_pciconf_read, + bonito_pciconf_write, s, + DEVICE_NATIVE_ENDIAN); + s->bonito_pciconf_start = BONITO_PCICONFIG_BASE; + s->bonito_pciconf_length = BONITO_PCICONFIG_SIZE; + cpu_register_physical_memory(s->bonito_pciconf_start, s->bonito_pciconf_length, + s->bonito_pciconf_handle); + + /* set the south bridge pci configure mapping */ + s->bonito_spciconf_handle = cpu_register_io_memory(bonito_spciconf_read, + bonito_spciconf_write, s, + DEVICE_NATIVE_ENDIAN); + s->bonito_spciconf_start = BONITO_SPCICONFIG_BASE; + s->bonito_spciconf_length = BONITO_SPCICONFIG_SIZE; + cpu_register_physical_memory(s->bonito_spciconf_start, s->bonito_spciconf_length, + s->bonito_spciconf_handle); + + s->bonito_ldma_handle = cpu_register_io_memory(bonito_ldma_read, + bonito_ldma_write, s, + DEVICE_NATIVE_ENDIAN); + s->bonito_ldma_start = 0xbfe00200; + s->bonito_ldma_length = 0x100; + cpu_register_physical_memory(s->bonito_ldma_start, s->bonito_ldma_length, + s->bonito_ldma_handle); + + s->bonito_cop_handle = cpu_register_io_memory(bonito_cop_read, + bonito_cop_write, s, + DEVICE_NATIVE_ENDIAN); + s->bonito_cop_start = 0xbfe00300; + s->bonito_cop_length = 0x100; + cpu_register_physical_memory(s->bonito_cop_start, s->bonito_cop_length, + s->bonito_cop_handle); + + /* Map PCI IO Space 0x1fd0 0000 - 0x1fd1 0000 */ + s->bonito_pciio_start = BONITO_PCIIO_BASE; + s->bonito_pciio_length = BONITO_PCIIO_SIZE; + isa_mem_base = s->bonito_pciio_start; + isa_mmio_init(s->bonito_pciio_start, s->bonito_pciio_length); + + /* add pci local io mapping */ + s->bonito_localio_start = BONITO_DEV_BASE; + s->bonito_localio_length = BONITO_DEV_SIZE; + isa_mmio_init(s->bonito_localio_start, s->bonito_localio_length); + + /* 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); + + return 0; +} + +PCIBus *bonito_init(qemu_irq *pic) +{ + DeviceState *dev; + PCIBus *b; + BonitoState *pcihost; + PCIBonitoState *s; + PCIDevice *d; + + dev = qdev_create(NULL, "Bonito-pcihost"); + pcihost = FROM_SYSBUS(BonitoState, sysbus_from_qdev(dev)); + b = pci_register_bus(&pcihost->busdev.qdev, "pci", pci_bonito_set_irq, + pci_bonito_map_irq, pic, 0x28, 32); + pcihost->bus = b; + qdev_init_nofail(dev); + + d = pci_create_simple(b, PCI_DEVFN(0, 0), "Bonito"); + s = DO_UPCAST(PCIBonitoState, dev, d); + s->pcihost = pcihost; + bonito_state = s; + + return b; +} + +static PCIDeviceInfo bonito_info = { + .qdev.name = "Bonito", + .qdev.desc = "Host bridge", + .qdev.size = sizeof(PCIBonitoState), + .qdev.vmsd = &vmstate_bonito, + .qdev.no_user = 1, + .init = bonito_initfn, +}; + +static SysBusDeviceInfo bonito_pcihost_info = { + .init = bonito_pcihost_initfn, + .qdev.name = "Bonito-pcihost", + .qdev.size = sizeof(BonitoState), + .qdev.no_user = 1, +}; + +static void bonito_register(void) +{ + sysbus_register_withprop(&bonito_pcihost_info); + pci_qdev_register(&bonito_info); +} +device_init(bonito_register); diff --git a/hw/bt-hci-csr.c b/hw/bt-hci-csr.c new file mode 100644 index 0000000..982577d --- /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 + * + * 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 . + */ + +#include "qemu-common.h" +#include "qemu-char.h" +#include "qemu-timer.h" +#include "irq.h" +#include "sysemu.h" +#include "net.h" +#include "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) + qemu_mod_timer(s->out_tm, qemu_get_clock(vm_clock) + 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 negotation 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 *) + qemu_mallocz(sizeof(struct csrhci_s)); + + s->chr.opaque = s; + s->chr.chr_write = csrhci_write; + s->chr.chr_ioctl = csrhci_ioctl; + + 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 = qemu_new_timer(vm_clock, 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 0000000..f1ee92c --- /dev/null +++ b/hw/bt-hci.c @@ -0,0 +1,2221 @@ +/* + * QEMU Bluetooth HCI logic. + * + * Copyright (C) 2007 OpenMoko, Inc. + * Copyright (C) 2008 Andrzej Zaborowski + * + * 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 . + */ + +#include "qemu-common.h" +#include "qemu-timer.h" +#include "usb.h" +#include "net.h" +#include "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 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, ¶ms, 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, ¶ms, 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, + ¶ms, 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) +{ + qemu_mod_timer(timer, qemu_get_clock(vm_clock) + + 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 = + qemu_new_timer(vm_clock, 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)) { + qemu_del_timer(hci->lm.handle[handle].acl_mode_timer); + qemu_free_timer(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(¶ms.bdaddr, bdaddr); + params.link_type = ACL_LINK; + params.encr_mode = 0x00; /* Encryption not required */ + bt_hci_event(hci, EVT_CONN_COMPLETE, ¶ms, 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 = qemu_mallocz(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) { + qemu_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(¶ms.bdaddr, &host->bd_addr); + params.link_type = ACL_LINK; + params.encr_mode = 0x00; /* Encryption not required */ + bt_hci_event(hci, EVT_CONN_COMPLETE, ¶ms, 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(¶ms.bdaddr, &link->host->bd_addr); + memcpy(¶ms.dev_class, &link->host->class, sizeof(params.dev_class)); + params.link_type = ACL_LINK; + bt_hci_event(hci, EVT_CONN_REQUEST, ¶ms, EVT_CONN_REQUEST_SIZE); + return; +} + +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(¶ms.bdaddr, &link->slave->bd_addr); + params.encr_mode = 0x00; /* Encryption not required */ + bt_hci_event(hci, EVT_CONN_COMPLETE, ¶ms, 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; + qemu_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, + ¶ms, 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, + ¶ms, 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; + + qemu_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, + ¶ms, 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; + int len; + + 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(¶ms.bdaddr, &slave->bd_addr); + len = snprintf(params.name, sizeof(params.name), + "%s", slave->lmp_name ?: ""); + memset(params.name + len, 0, sizeof(params.name) - len); + bt_hci_event(hci, EVT_REMOTE_NAME_REQ_COMPLETE, + ¶ms, 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, + ¶ms, 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, + ¶ms, 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, + ¶ms, 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, ¶ms, 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); + + qemu_mod_timer(link->acl_mode_timer, qemu_get_clock(vm_clock) + + 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); + + qemu_del_timer(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) + qemu_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: qemu_del_timer(sl->acl_mode_timer); for all links */ + qemu_del_timer(hci->lm.inquiry_done); + qemu_del_timer(hci->lm.inquiry_next); + qemu_del_timer(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, ¶ms, 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, ¶ms, 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, ¶ms, 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, ¶ms, 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, + ¶ms, 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, ¶ms, 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, ¶ms, 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, ¶ms, 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) + strncpy(params.name, hci->device.lmp_name, sizeof(params.name)); + + bt_hci_event_complete(hci, ¶ms, 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, ¶ms, 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, ¶ms, 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, ¶ms, 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, ¶ms, 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, ¶ms, 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; + qemu_del_timer(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; + qemu_del_timer(hci->lm.inquiry_done); + qemu_del_timer(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) + qemu_free((void *) hci->device.lmp_name); + hci->device.lmp_name = qemu_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 = qemu_mallocz(sizeof(struct bt_hci_s)); + + s->lm.inquiry_done = qemu_new_timer(vm_clock, bt_hci_inquiry_done, s); + s->lm.inquiry_next = qemu_new_timer(vm_clock, bt_hci_inquiry_next, s); + s->conn_accept_timer = + qemu_new_timer(vm_clock, 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; +} + +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) + qemu_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. */ + + qemu_free_timer(hci->lm.inquiry_done); + qemu_free_timer(hci->lm.inquiry_next); + qemu_free_timer(hci->conn_accept_timer); + + qemu_free(hci); +} diff --git a/hw/bt-hid.c b/hw/bt-hid.c new file mode 100644 index 0000000..abdfd35 --- /dev/null +++ b/hw/bt-hid.c @@ -0,0 +1,571 @@ +/* + * QEMU Bluetooth HID Profile wrapper for USB HID. + * + * Copyright (C) 2007-2008 OpenMoko, Inc. + * Written by Andrzej Zaborowski + * + * 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 . + */ + +#include "qemu-common.h" +#include "usb.h" +#include "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; + USBDevice *usbdev; + + 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); + + s->usbdev->info->handle_reset(s->usbdev); + 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) +{ + USBPacket p; + + if (s->data_type == BT_DATA_OUTPUT) { + p.pid = USB_TOKEN_OUT; + p.devep = 1; + p.data = s->dataout.buffer; + p.len = s->dataout.len; + s->dataout.len = s->usbdev->info->handle_data(s->usbdev, &p); + + return s->dataout.len; + } + + if (s->data_type == BT_DATA_FEATURE) { + /* XXX: + * does this send a USB_REQ_CLEAR_FEATURE/USB_REQ_SET_FEATURE + * or a SET_REPORT? */ + p.devep = 0; + } + + return -1; +} + +static int bt_hid_in(struct bt_hid_device_s *s) +{ + USBPacket p; + + p.pid = USB_TOKEN_IN; + p.devep = 1; + p.data = s->datain.buffer; + p.len = sizeof(s->datain.buffer); + s->datain.len = s->usbdev->info->handle_data(s->usbdev, &p); + + 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->usbdev->info->handle_control(s->usbdev, SET_PROTOCOL, s->proto, 0, 0, + NULL); + 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->usbdev->info->handle_control(s->usbdev, GET_IDLE, 0, 0, 1, + s->control->sdu_out(s->control, 1)); + 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; + } + + /* We don't need to know about the Idle Rate here really, + * so just pass it on to the device. */ + ret = s->usbdev->info->handle_control(s->usbdev, + SET_IDLE, data[1], 0, 0, NULL) ? + BT_HS_SUCCESSFUL : BT_HS_ERR_INVALID_PARAMETER; + /* 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(void *opaque) +{ + struct bt_hid_device_s *hid = opaque; + + /* 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->usbdev->info->handle_reset(hid->usbdev); + 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->usbdev->info->handle_destroy(hid->usbdev); + + qemu_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, + USBDevice *dev, enum peripheral_minor_class minor) +{ + struct bt_hid_device_s *s = qemu_mallocz(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); + + s->usbdev = dev; + s->btdev.device.lmp_name = s->usbdev->product_desc; + usb_hid_datain_cb(s->usbdev, s, bt_hid_datain); + + 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) +{ + USBDevice *dev = usb_create_simple(NULL /* FIXME */, "usb-kbd"); + return bt_hid_init(net, dev, class_keyboard); +} diff --git a/hw/bt-l2cap.c b/hw/bt-l2cap.c new file mode 100644 index 0000000..7e2f668 --- /dev/null +++ b/hw/bt-l2cap.c @@ -0,0 +1,1362 @@ +/* + * QEMU Bluetooth L2CAP logic. + * + * Copyright (C) 2008 Andrzej Zaborowski + * + * 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 . + */ + +#include "qemu-common.h" +#include "qemu-timer.h" +#include "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) + qemu_mod_timer(ch->retransmission_timer); + else + qemu_del_timer(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) + qemu_mod_timer(ch->monitor_timer); + else + qemu_del_timer(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(¶ms->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, ¶ms, 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 = qemu_mallocz(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 { + qemu_free(ch); + + 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); + qemu_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; + } + return l2cap_sframe_in(ch, le16_to_cpup((void *) hdr->data)); + } + + 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; + + return ch->params.sdu_in(ch->params.opaque, hdr->data + 2, len - 4); + + 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); + return ch->params.sdu_in(ch->params.opaque, ch->sdu, ch->len_total); + + 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; + + return 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); + qemu_free(l2cap->cid[cid]); + } + + if (l2cap->role) + qemu_free(l2cap); + else + qemu_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 = qemu_mallocz(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 = qemu_mallocz(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 = qemu_mallocz(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 0000000..cdf2d95 --- /dev/null +++ b/hw/bt-sdp.c @@ -0,0 +1,967 @@ +/* + * Service Discover Protocol server for QEMU L2CAP devices + * + * Copyright (C) 2008 Andrzej Zaborowski + * + * 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 . + */ + +#include "qemu-common.h" +#include "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 ++) { + qemu_free(sdp->service_list[i].attribute_list->pair); + qemu_free(sdp->service_list[i].attribute_list); + qemu_free(sdp->service_list[i].uuid); + } + qemu_free(sdp->service_list); + qemu_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 = 1 << ffs(record->uuids - 1); + record->attribute_list = + qemu_mallocz(record->attributes * sizeof(*record->attribute_list)); + record->uuid = + qemu_mallocz(record->uuids * sizeof(*record->uuid)); + data = qemu_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 = + qemu_mallocz(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 " QEMU_VERSION)) + + /* 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 " QEMU_VERSION)) + + /* 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 " QEMU_VERSION)) + + /* 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 = qemu_mallocz(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/bt.c b/hw/bt.c new file mode 100644 index 0000000..34bf004 --- /dev/null +++ b/hw/bt.c @@ -0,0 +1,121 @@ +/* + * Convenience functions for bluetooth. + * + * Copyright (C) 2008 Andrzej Zaborowski + * + * 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 . + */ + +#include "qemu-common.h" +#include "net.h" +#include "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 = qemu_mallocz(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) +{ + qemu_free(link); +} + +static void bt_dummy_destroy(struct bt_device_s *device) +{ + bt_device_done(device); + qemu_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; +} diff --git a/hw/bt.h b/hw/bt.h new file mode 100644 index 0000000..4a702ad --- /dev/null +++ b/hw/bt.h @@ -0,0 +1,2183 @@ +/* + * QEMU Bluetooth HCI helpers. + * + * Copyright (C) 2007 OpenMoko, Inc. + * Written by Andrzej Zaborowski + * + * Useful definitions taken from BlueZ project's headers. + * Copyright (C) 2000-2001 Qualcomm Incorporated + * Copyright (C) 2002-2003 Maxim Krasnyansky + * Copyright (C) 2002-2006 Marcel Holtmann + * + * 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 . + */ + +/* BD Address */ +typedef struct { + uint8_t b[6]; +} __attribute__((packed)) bdaddr_t; + +#define BDADDR_ANY (&(bdaddr_t) {{0, 0, 0, 0, 0, 0}}) +#define BDADDR_ALL (&(bdaddr_t) {{0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}) +#define BDADDR_LOCAL (&(bdaddr_t) {{0, 0, 0, 0xff, 0xff, 0xff}}) + +/* Copy, swap, convert BD Address */ +static inline int bacmp(const bdaddr_t *ba1, const bdaddr_t *ba2) +{ + return memcmp(ba1, ba2, sizeof(bdaddr_t)); +} +static inline void bacpy(bdaddr_t *dst, const bdaddr_t *src) +{ + memcpy(dst, src, sizeof(bdaddr_t)); +} + +#define BAINIT(orig) { .b = { \ + (orig)->b[0], (orig)->b[1], (orig)->b[2], \ + (orig)->b[3], (orig)->b[4], (orig)->b[5], \ +}, } + +/* The twisted structures of a bluetooth environment */ +struct bt_device_s; +struct bt_scatternet_s; +struct bt_piconet_s; +struct bt_link_s; + +struct bt_scatternet_s { + struct bt_device_s *slave; +}; + +struct bt_link_s { + struct bt_device_s *slave, *host; + uint16_t handle; /* Master (host) side handle */ + uint16_t acl_interval; + enum { + acl_active, + acl_hold, + acl_sniff, + acl_parked, + } acl_mode; +}; + +struct bt_device_s { + int lt_addr; + bdaddr_t bd_addr; + int mtu; + int setup; + struct bt_scatternet_s *net; + + uint8_t key[16]; + int key_present; + uint8_t class[3]; + + uint8_t reject_reason; + + uint64_t lmp_caps; + const char *lmp_name; + void (*lmp_connection_request)(struct bt_link_s *link); + void (*lmp_connection_complete)(struct bt_link_s *link); + void (*lmp_disconnect_master)(struct bt_link_s *link); + void (*lmp_disconnect_slave)(struct bt_link_s *link); + void (*lmp_acl_data)(struct bt_link_s *link, const uint8_t *data, + int start, int len); + void (*lmp_acl_resp)(struct bt_link_s *link, const uint8_t *data, + int start, int len); + void (*lmp_mode_change)(struct bt_link_s *link); + + void (*handle_destroy)(struct bt_device_s *device); + struct bt_device_s *next; /* Next in the piconet/scatternet */ + + int inquiry_scan; + int page_scan; + + uint16_t clkoff; /* Note: Always little-endian */ +}; + +/* bt.c */ +void bt_device_init(struct bt_device_s *dev, struct bt_scatternet_s *net); +void bt_device_done(struct bt_device_s *dev); + +/* bt-hci.c */ +struct HCIInfo *bt_new_hci(struct bt_scatternet_s *net); + +/* bt-vhci.c */ +void bt_vhci_init(struct HCIInfo *info); + +/* bt-hci-csr.c */ +enum { + csrhci_pin_reset, + csrhci_pin_wakeup, + __csrhci_pins, +}; +qemu_irq *csrhci_pins_get(CharDriverState *chr); +CharDriverState *uart_hci_init(qemu_irq wakeup); + +/* bt-l2cap.c */ +struct bt_l2cap_device_s; +struct bt_l2cap_conn_params_s; +struct bt_l2cap_psm_s; +void bt_l2cap_device_init(struct bt_l2cap_device_s *dev, + struct bt_scatternet_s *net); +void bt_l2cap_device_done(struct bt_l2cap_device_s *dev); +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_device_s { + struct bt_device_s device; + struct bt_l2cap_psm_s *first_psm; +}; + +struct bt_l2cap_conn_params_s { + /* Input */ + uint8_t *(*sdu_out)(struct bt_l2cap_conn_params_s *chan, int len); + void (*sdu_submit)(struct bt_l2cap_conn_params_s *chan); + int remote_mtu; + /* Output */ + void *opaque; + void (*sdu_in)(void *opaque, const uint8_t *data, int len); + void (*close)(void *opaque); +}; + +enum bt_l2cap_psm_predef { + BT_PSM_SDP = 0x0001, + BT_PSM_RFCOMM = 0x0003, + BT_PSM_TELEPHONY = 0x0005, + BT_PSM_TCS = 0x0007, + BT_PSM_BNEP = 0x000f, + BT_PSM_HID_CTRL = 0x0011, + BT_PSM_HID_INTR = 0x0013, + BT_PSM_UPNP = 0x0015, + BT_PSM_AVCTP = 0x0017, + BT_PSM_AVDTP = 0x0019, +}; + +/* bt-sdp.c */ +void bt_l2cap_sdp_init(struct bt_l2cap_device_s *dev); + +/* bt-hid.c */ +struct bt_device_s *bt_mouse_init(struct bt_scatternet_s *net); +struct bt_device_s *bt_tablet_init(struct bt_scatternet_s *net); +struct bt_device_s *bt_keyboard_init(struct bt_scatternet_s *net); + +/* Link Management Protocol layer defines */ + +#define LLID_ACLU_CONT 0x1 +#define LLID_ACLU_START 0x2 +#define LLID_ACLC 0x3 + +enum lmp_pdu_type { + LMP_NAME_REQ = 0x0001, + LMP_NAME_RES = 0x0002, + LMP_ACCEPTED = 0x0003, + LMP_NOT_ACCEPTED = 0x0004, + LMP_CLKOFFSET_REQ = 0x0005, + LMP_CLKOFFSET_RES = 0x0006, + LMP_DETACH = 0x0007, + LMP_IN_RAND = 0x0008, + LMP_COMB_KEY = 0x0009, + LMP_UNIT_KEY = 0x000a, + LMP_AU_RAND = 0x000b, + LMP_SRES = 0x000c, + LMP_TEMP_RAND = 0x000d, + LMP_TEMP_KEY = 0x000e, + LMP_CRYPT_MODE_REQ = 0x000f, + LMP_CRYPT_KEY_SIZE_REQ = 0x0010, + LMP_START_ENCRYPT_REQ = 0x0011, + LMP_STOP_ENCRYPT_REQ = 0x0012, + LMP_SWITCH_REQ = 0x0013, + LMP_HOLD = 0x0014, + LMP_HOLD_REQ = 0x0015, + LMP_SNIFF_REQ = 0x0017, + LMP_UNSNIFF_REQ = 0x0018, + LMP_LMP_PARK_REQ = 0x0019, + LMP_SET_BCAST_SCAN_WND = 0x001b, + LMP_MODIFY_BEACON = 0x001c, + LMP_UNPARK_BD_ADDR_REQ = 0x001d, + LMP_UNPARK_PM_ADDR_REQ = 0x001e, + LMP_INCR_POWER_REQ = 0x001f, + LMP_DECR_POWER_REQ = 0x0020, + LMP_MAX_POWER = 0x0021, + LMP_MIN_POWER = 0x0022, + LMP_AUTO_RATE = 0x0023, + LMP_PREFERRED_RATE = 0x0024, + LMP_VERSION_REQ = 0x0025, + LMP_VERSION_RES = 0x0026, + LMP_FEATURES_REQ = 0x0027, + LMP_FEATURES_RES = 0x0028, + LMP_QUALITY_OF_SERVICE = 0x0029, + LMP_QOS_REQ = 0x002a, + LMP_RM_SCO_LINK_REQ = 0x002b, + LMP_SCO_LINK_REQ = 0x002c, + LMP_MAX_SLOT = 0x002d, + LMP_MAX_SLOT_REQ = 0x002e, + LMP_TIMING_ACCURACY_REQ = 0x002f, + LMP_TIMING_ACCURACY_RES = 0x0030, + LMP_SETUP_COMPLETE = 0x0031, + LMP_USE_SEMIPERM_KEY = 0x0032, + LMP_HOST_CONNECTION_REQ = 0x0033, + LMP_SLOT_OFFSET = 0x0034, + LMP_PAGE_MODE_REQ = 0x0035, + LMP_PAGE_SCAN_MODE_REQ = 0x0036, + LMP_SUPERVISION_TIMEOUT = 0x0037, + LMP_TEST_ACTIVATE = 0x0038, + LMP_TEST_CONTROL = 0x0039, + LMP_CRYPT_KEY_MASK_REQ = 0x003a, + LMP_CRYPT_KEY_MASK_RES = 0x003b, + LMP_SET_AFH = 0x003c, + LMP_ACCEPTED_EXT = 0x7f01, + LMP_NOT_ACCEPTED_EXT = 0x7f02, + LMP_FEATURES_REQ_EXT = 0x7f03, + LMP_FEATURES_RES_EXT = 0x7f04, + LMP_PACKET_TYPE_TBL_REQ = 0x7f0b, + LMP_ESCO_LINK_REQ = 0x7f0c, + LMP_RM_ESCO_LINK_REQ = 0x7f0d, + LMP_CHANNEL_CLASS_REQ = 0x7f10, + LMP_CHANNEL_CLASS = 0x7f11, +}; + +/* Host Controller Interface layer defines */ + +enum hci_packet_type { + HCI_COMMAND_PKT = 0x01, + HCI_ACLDATA_PKT = 0x02, + HCI_SCODATA_PKT = 0x03, + HCI_EVENT_PKT = 0x04, + HCI_VENDOR_PKT = 0xff, +}; + +enum bt_packet_type { + HCI_2DH1 = 1 << 1, + HCI_3DH1 = 1 << 2, + HCI_DM1 = 1 << 3, + HCI_DH1 = 1 << 4, + HCI_2DH3 = 1 << 8, + HCI_3DH3 = 1 << 9, + HCI_DM3 = 1 << 10, + HCI_DH3 = 1 << 11, + HCI_2DH5 = 1 << 12, + HCI_3DH5 = 1 << 13, + HCI_DM5 = 1 << 14, + HCI_DH5 = 1 << 15, +}; + +enum sco_packet_type { + HCI_HV1 = 1 << 5, + HCI_HV2 = 1 << 6, + HCI_HV3 = 1 << 7, +}; + +enum ev_packet_type { + HCI_EV3 = 1 << 3, + HCI_EV4 = 1 << 4, + HCI_EV5 = 1 << 5, + HCI_2EV3 = 1 << 6, + HCI_3EV3 = 1 << 7, + HCI_2EV5 = 1 << 8, + HCI_3EV5 = 1 << 9, +}; + +enum hci_error_code { + HCI_SUCCESS = 0x00, + HCI_UNKNOWN_COMMAND = 0x01, + HCI_NO_CONNECTION = 0x02, + HCI_HARDWARE_FAILURE = 0x03, + HCI_PAGE_TIMEOUT = 0x04, + HCI_AUTHENTICATION_FAILURE = 0x05, + HCI_PIN_OR_KEY_MISSING = 0x06, + HCI_MEMORY_FULL = 0x07, + HCI_CONNECTION_TIMEOUT = 0x08, + HCI_MAX_NUMBER_OF_CONNECTIONS = 0x09, + HCI_MAX_NUMBER_OF_SCO_CONNECTIONS = 0x0a, + HCI_ACL_CONNECTION_EXISTS = 0x0b, + HCI_COMMAND_DISALLOWED = 0x0c, + HCI_REJECTED_LIMITED_RESOURCES = 0x0d, + HCI_REJECTED_SECURITY = 0x0e, + HCI_REJECTED_PERSONAL = 0x0f, + HCI_HOST_TIMEOUT = 0x10, + HCI_UNSUPPORTED_FEATURE = 0x11, + HCI_INVALID_PARAMETERS = 0x12, + HCI_OE_USER_ENDED_CONNECTION = 0x13, + HCI_OE_LOW_RESOURCES = 0x14, + HCI_OE_POWER_OFF = 0x15, + HCI_CONNECTION_TERMINATED = 0x16, + HCI_REPEATED_ATTEMPTS = 0x17, + HCI_PAIRING_NOT_ALLOWED = 0x18, + HCI_UNKNOWN_LMP_PDU = 0x19, + HCI_UNSUPPORTED_REMOTE_FEATURE = 0x1a, + HCI_SCO_OFFSET_REJECTED = 0x1b, + HCI_SCO_INTERVAL_REJECTED = 0x1c, + HCI_AIR_MODE_REJECTED = 0x1d, + HCI_INVALID_LMP_PARAMETERS = 0x1e, + HCI_UNSPECIFIED_ERROR = 0x1f, + HCI_UNSUPPORTED_LMP_PARAMETER_VALUE = 0x20, + HCI_ROLE_CHANGE_NOT_ALLOWED = 0x21, + HCI_LMP_RESPONSE_TIMEOUT = 0x22, + HCI_LMP_ERROR_TRANSACTION_COLLISION = 0x23, + HCI_LMP_PDU_NOT_ALLOWED = 0x24, + HCI_ENCRYPTION_MODE_NOT_ACCEPTED = 0x25, + HCI_UNIT_LINK_KEY_USED = 0x26, + HCI_QOS_NOT_SUPPORTED = 0x27, + HCI_INSTANT_PASSED = 0x28, + HCI_PAIRING_NOT_SUPPORTED = 0x29, + HCI_TRANSACTION_COLLISION = 0x2a, + HCI_QOS_UNACCEPTABLE_PARAMETER = 0x2c, + HCI_QOS_REJECTED = 0x2d, + HCI_CLASSIFICATION_NOT_SUPPORTED = 0x2e, + HCI_INSUFFICIENT_SECURITY = 0x2f, + HCI_PARAMETER_OUT_OF_RANGE = 0x30, + HCI_ROLE_SWITCH_PENDING = 0x32, + HCI_SLOT_VIOLATION = 0x34, + HCI_ROLE_SWITCH_FAILED = 0x35, +}; + +enum acl_flag_bits { + ACL_CONT = 1 << 0, + ACL_START = 1 << 1, + ACL_ACTIVE_BCAST = 1 << 2, + ACL_PICO_BCAST = 1 << 3, +}; + +enum baseband_link_type { + SCO_LINK = 0x00, + ACL_LINK = 0x01, +}; + +enum lmp_feature_bits0 { + LMP_3SLOT = 1 << 0, + LMP_5SLOT = 1 << 1, + LMP_ENCRYPT = 1 << 2, + LMP_SOFFSET = 1 << 3, + LMP_TACCURACY = 1 << 4, + LMP_RSWITCH = 1 << 5, + LMP_HOLD_MODE = 1 << 6, + LMP_SNIFF_MODE = 1 << 7, +}; + +enum lmp_feature_bits1 { + LMP_PARK = 1 << 0, + LMP_RSSI = 1 << 1, + LMP_QUALITY = 1 << 2, + LMP_SCO = 1 << 3, + LMP_HV2 = 1 << 4, + LMP_HV3 = 1 << 5, + LMP_ULAW = 1 << 6, + LMP_ALAW = 1 << 7, +}; + +enum lmp_feature_bits2 { + LMP_CVSD = 1 << 0, + LMP_PSCHEME = 1 << 1, + LMP_PCONTROL = 1 << 2, + LMP_TRSP_SCO = 1 << 3, + LMP_BCAST_ENC = 1 << 7, +}; + +enum lmp_feature_bits3 { + LMP_EDR_ACL_2M = 1 << 1, + LMP_EDR_ACL_3M = 1 << 2, + LMP_ENH_ISCAN = 1 << 3, + LMP_ILACE_ISCAN = 1 << 4, + LMP_ILACE_PSCAN = 1 << 5, + LMP_RSSI_INQ = 1 << 6, + LMP_ESCO = 1 << 7, +}; + +enum lmp_feature_bits4 { + LMP_EV4 = 1 << 0, + LMP_EV5 = 1 << 1, + LMP_AFH_CAP_SLV = 1 << 3, + LMP_AFH_CLS_SLV = 1 << 4, + LMP_EDR_3SLOT = 1 << 7, +}; + +enum lmp_feature_bits5 { + LMP_EDR_5SLOT = 1 << 0, + LMP_SNIFF_SUBR = 1 << 1, + LMP_AFH_CAP_MST = 1 << 3, + LMP_AFH_CLS_MST = 1 << 4, + LMP_EDR_ESCO_2M = 1 << 5, + LMP_EDR_ESCO_3M = 1 << 6, + LMP_EDR_3S_ESCO = 1 << 7, +}; + +enum lmp_feature_bits6 { + LMP_EXT_INQ = 1 << 0, +}; + +enum lmp_feature_bits7 { + LMP_EXT_FEAT = 1 << 7, +}; + +enum hci_link_policy { + HCI_LP_RSWITCH = 1 << 0, + HCI_LP_HOLD = 1 << 1, + HCI_LP_SNIFF = 1 << 2, + HCI_LP_PARK = 1 << 3, +}; + +enum hci_link_mode { + HCI_LM_ACCEPT = 1 << 15, + HCI_LM_MASTER = 1 << 0, + HCI_LM_AUTH = 1 << 1, + HCI_LM_ENCRYPT = 1 << 2, + HCI_LM_TRUSTED = 1 << 3, + HCI_LM_RELIABLE = 1 << 4, + HCI_LM_SECURE = 1 << 5, +}; + +/* HCI Commands */ + +/* Link Control */ +#define OGF_LINK_CTL 0x01 + +#define OCF_INQUIRY 0x0001 +typedef struct { + uint8_t lap[3]; + uint8_t length; /* 1.28s units */ + uint8_t num_rsp; +} __attribute__ ((packed)) inquiry_cp; +#define INQUIRY_CP_SIZE 5 + +typedef struct { + uint8_t status; + bdaddr_t bdaddr; +} __attribute__ ((packed)) status_bdaddr_rp; +#define STATUS_BDADDR_RP_SIZE 7 + +#define OCF_INQUIRY_CANCEL 0x0002 + +#define OCF_PERIODIC_INQUIRY 0x0003 +typedef struct { + uint16_t max_period; /* 1.28s units */ + uint16_t min_period; /* 1.28s units */ + uint8_t lap[3]; + uint8_t length; /* 1.28s units */ + uint8_t num_rsp; +} __attribute__ ((packed)) periodic_inquiry_cp; +#define PERIODIC_INQUIRY_CP_SIZE 9 + +#define OCF_EXIT_PERIODIC_INQUIRY 0x0004 + +#define OCF_CREATE_CONN 0x0005 +typedef struct { + bdaddr_t bdaddr; + uint16_t pkt_type; + uint8_t pscan_rep_mode; + uint8_t pscan_mode; + uint16_t clock_offset; + uint8_t role_switch; +} __attribute__ ((packed)) create_conn_cp; +#define CREATE_CONN_CP_SIZE 13 + +#define OCF_DISCONNECT 0x0006 +typedef struct { + uint16_t handle; + uint8_t reason; +} __attribute__ ((packed)) disconnect_cp; +#define DISCONNECT_CP_SIZE 3 + +#define OCF_ADD_SCO 0x0007 +typedef struct { + uint16_t handle; + uint16_t pkt_type; +} __attribute__ ((packed)) add_sco_cp; +#define ADD_SCO_CP_SIZE 4 + +#define OCF_CREATE_CONN_CANCEL 0x0008 +typedef struct { + uint8_t status; + bdaddr_t bdaddr; +} __attribute__ ((packed)) create_conn_cancel_cp; +#define CREATE_CONN_CANCEL_CP_SIZE 6 + +typedef struct { + uint8_t status; + bdaddr_t bdaddr; +} __attribute__ ((packed)) create_conn_cancel_rp; +#define CREATE_CONN_CANCEL_RP_SIZE 7 + +#define OCF_ACCEPT_CONN_REQ 0x0009 +typedef struct { + bdaddr_t bdaddr; + uint8_t role; +} __attribute__ ((packed)) accept_conn_req_cp; +#define ACCEPT_CONN_REQ_CP_SIZE 7 + +#define OCF_REJECT_CONN_REQ 0x000A +typedef struct { + bdaddr_t bdaddr; + uint8_t reason; +} __attribute__ ((packed)) reject_conn_req_cp; +#define REJECT_CONN_REQ_CP_SIZE 7 + +#define OCF_LINK_KEY_REPLY 0x000B +typedef struct { + bdaddr_t bdaddr; + uint8_t link_key[16]; +} __attribute__ ((packed)) link_key_reply_cp; +#define LINK_KEY_REPLY_CP_SIZE 22 + +#define OCF_LINK_KEY_NEG_REPLY 0x000C + +#define OCF_PIN_CODE_REPLY 0x000D +typedef struct { + bdaddr_t bdaddr; + uint8_t pin_len; + uint8_t pin_code[16]; +} __attribute__ ((packed)) pin_code_reply_cp; +#define PIN_CODE_REPLY_CP_SIZE 23 + +#define OCF_PIN_CODE_NEG_REPLY 0x000E + +#define OCF_SET_CONN_PTYPE 0x000F +typedef struct { + uint16_t handle; + uint16_t pkt_type; +} __attribute__ ((packed)) set_conn_ptype_cp; +#define SET_CONN_PTYPE_CP_SIZE 4 + +#define OCF_AUTH_REQUESTED 0x0011 +typedef struct { + uint16_t handle; +} __attribute__ ((packed)) auth_requested_cp; +#define AUTH_REQUESTED_CP_SIZE 2 + +#define OCF_SET_CONN_ENCRYPT 0x0013 +typedef struct { + uint16_t handle; + uint8_t encrypt; +} __attribute__ ((packed)) set_conn_encrypt_cp; +#define SET_CONN_ENCRYPT_CP_SIZE 3 + +#define OCF_CHANGE_CONN_LINK_KEY 0x0015 +typedef struct { + uint16_t handle; +} __attribute__ ((packed)) change_conn_link_key_cp; +#define CHANGE_CONN_LINK_KEY_CP_SIZE 2 + +#define OCF_MASTER_LINK_KEY 0x0017 +typedef struct { + uint8_t key_flag; +} __attribute__ ((packed)) master_link_key_cp; +#define MASTER_LINK_KEY_CP_SIZE 1 + +#define OCF_REMOTE_NAME_REQ 0x0019 +typedef struct { + bdaddr_t bdaddr; + uint8_t pscan_rep_mode; + uint8_t pscan_mode; + uint16_t clock_offset; +} __attribute__ ((packed)) remote_name_req_cp; +#define REMOTE_NAME_REQ_CP_SIZE 10 + +#define OCF_REMOTE_NAME_REQ_CANCEL 0x001A +typedef struct { + bdaddr_t bdaddr; +} __attribute__ ((packed)) remote_name_req_cancel_cp; +#define REMOTE_NAME_REQ_CANCEL_CP_SIZE 6 + +typedef struct { + uint8_t status; + bdaddr_t bdaddr; +} __attribute__ ((packed)) remote_name_req_cancel_rp; +#define REMOTE_NAME_REQ_CANCEL_RP_SIZE 7 + +#define OCF_READ_REMOTE_FEATURES 0x001B +typedef struct { + uint16_t handle; +} __attribute__ ((packed)) read_remote_features_cp; +#define READ_REMOTE_FEATURES_CP_SIZE 2 + +#define OCF_READ_REMOTE_EXT_FEATURES 0x001C +typedef struct { + uint16_t handle; + uint8_t page_num; +} __attribute__ ((packed)) read_remote_ext_features_cp; +#define READ_REMOTE_EXT_FEATURES_CP_SIZE 3 + +#define OCF_READ_REMOTE_VERSION 0x001D +typedef struct { + uint16_t handle; +} __attribute__ ((packed)) read_remote_version_cp; +#define READ_REMOTE_VERSION_CP_SIZE 2 + +#define OCF_READ_CLOCK_OFFSET 0x001F +typedef struct { + uint16_t handle; +} __attribute__ ((packed)) read_clock_offset_cp; +#define READ_CLOCK_OFFSET_CP_SIZE 2 + +#define OCF_READ_LMP_HANDLE 0x0020 +typedef struct { + uint16_t handle; +} __attribute__ ((packed)) read_lmp_handle_cp; +#define READ_LMP_HANDLE_CP_SIZE 2 + +typedef struct { + uint8_t status; + uint16_t handle; + uint8_t lmp_handle; + uint32_t reserved; +} __attribute__ ((packed)) read_lmp_handle_rp; +#define READ_LMP_HANDLE_RP_SIZE 8 + +#define OCF_SETUP_SYNC_CONN 0x0028 +typedef struct { + uint16_t handle; + uint32_t tx_bandwith; + uint32_t rx_bandwith; + uint16_t max_latency; + uint16_t voice_setting; + uint8_t retrans_effort; + uint16_t pkt_type; +} __attribute__ ((packed)) setup_sync_conn_cp; +#define SETUP_SYNC_CONN_CP_SIZE 17 + +#define OCF_ACCEPT_SYNC_CONN_REQ 0x0029 +typedef struct { + bdaddr_t bdaddr; + uint32_t tx_bandwith; + uint32_t rx_bandwith; + uint16_t max_latency; + uint16_t voice_setting; + uint8_t retrans_effort; + uint16_t pkt_type; +} __attribute__ ((packed)) accept_sync_conn_req_cp; +#define ACCEPT_SYNC_CONN_REQ_CP_SIZE 21 + +#define OCF_REJECT_SYNC_CONN_REQ 0x002A +typedef struct { + bdaddr_t bdaddr; + uint8_t reason; +} __attribute__ ((packed)) reject_sync_conn_req_cp; +#define REJECT_SYNC_CONN_REQ_CP_SIZE 7 + +/* Link Policy */ +#define OGF_LINK_POLICY 0x02 + +#define OCF_HOLD_MODE 0x0001 +typedef struct { + uint16_t handle; + uint16_t max_interval; + uint16_t min_interval; +} __attribute__ ((packed)) hold_mode_cp; +#define HOLD_MODE_CP_SIZE 6 + +#define OCF_SNIFF_MODE 0x0003 +typedef struct { + uint16_t handle; + uint16_t max_interval; + uint16_t min_interval; + uint16_t attempt; + uint16_t timeout; +} __attribute__ ((packed)) sniff_mode_cp; +#define SNIFF_MODE_CP_SIZE 10 + +#define OCF_EXIT_SNIFF_MODE 0x0004 +typedef struct { + uint16_t handle; +} __attribute__ ((packed)) exit_sniff_mode_cp; +#define EXIT_SNIFF_MODE_CP_SIZE 2 + +#define OCF_PARK_MODE 0x0005 +typedef struct { + uint16_t handle; + uint16_t max_interval; + uint16_t min_interval; +} __attribute__ ((packed)) park_mode_cp; +#define PARK_MODE_CP_SIZE 6 + +#define OCF_EXIT_PARK_MODE 0x0006 +typedef struct { + uint16_t handle; +} __attribute__ ((packed)) exit_park_mode_cp; +#define EXIT_PARK_MODE_CP_SIZE 2 + +#define OCF_QOS_SETUP 0x0007 +typedef struct { + uint8_t service_type; /* 1 = best effort */ + uint32_t token_rate; /* Byte per seconds */ + uint32_t peak_bandwidth; /* Byte per seconds */ + uint32_t latency; /* Microseconds */ + uint32_t delay_variation; /* Microseconds */ +} __attribute__ ((packed)) hci_qos; +#define HCI_QOS_CP_SIZE 17 +typedef struct { + uint16_t handle; + uint8_t flags; /* Reserved */ + hci_qos qos; +} __attribute__ ((packed)) qos_setup_cp; +#define QOS_SETUP_CP_SIZE (3 + HCI_QOS_CP_SIZE) + +#define OCF_ROLE_DISCOVERY 0x0009 +typedef struct { + uint16_t handle; +} __attribute__ ((packed)) role_discovery_cp; +#define ROLE_DISCOVERY_CP_SIZE 2 +typedef struct { + uint8_t status; + uint16_t handle; + uint8_t role; +} __attribute__ ((packed)) role_discovery_rp; +#define ROLE_DISCOVERY_RP_SIZE 4 + +#define OCF_SWITCH_ROLE 0x000B +typedef struct { + bdaddr_t bdaddr; + uint8_t role; +} __attribute__ ((packed)) switch_role_cp; +#define SWITCH_ROLE_CP_SIZE 7 + +#define OCF_READ_LINK_POLICY 0x000C +typedef struct { + uint16_t handle; +} __attribute__ ((packed)) read_link_policy_cp; +#define READ_LINK_POLICY_CP_SIZE 2 +typedef struct { + uint8_t status; + uint16_t handle; + uint16_t policy; +} __attribute__ ((packed)) read_link_policy_rp; +#define READ_LINK_POLICY_RP_SIZE 5 + +#define OCF_WRITE_LINK_POLICY 0x000D +typedef struct { + uint16_t handle; + uint16_t policy; +} __attribute__ ((packed)) write_link_policy_cp; +#define WRITE_LINK_POLICY_CP_SIZE 4 +typedef struct { + uint8_t status; + uint16_t handle; +} __attribute__ ((packed)) write_link_policy_rp; +#define WRITE_LINK_POLICY_RP_SIZE 3 + +#define OCF_READ_DEFAULT_LINK_POLICY 0x000E + +#define OCF_WRITE_DEFAULT_LINK_POLICY 0x000F + +#define OCF_FLOW_SPECIFICATION 0x0010 + +#define OCF_SNIFF_SUBRATE 0x0011 +typedef struct { + uint16_t handle; + uint16_t max_remote_latency; + uint16_t max_local_latency; + uint16_t min_remote_timeout; + uint16_t min_local_timeout; +} __attribute__ ((packed)) sniff_subrate_cp; +#define SNIFF_SUBRATE_CP_SIZE 10 + +/* Host Controller and Baseband */ +#define OGF_HOST_CTL 0x03 + +#define OCF_SET_EVENT_MASK 0x0001 +typedef struct { + uint8_t mask[8]; +} __attribute__ ((packed)) set_event_mask_cp; +#define SET_EVENT_MASK_CP_SIZE 8 + +#define OCF_RESET 0x0003 + +#define OCF_SET_EVENT_FLT 0x0005 +typedef struct { + uint8_t flt_type; + uint8_t cond_type; + uint8_t condition[0]; +} __attribute__ ((packed)) set_event_flt_cp; +#define SET_EVENT_FLT_CP_SIZE 2 + +enum bt_filter_type { + FLT_CLEAR_ALL = 0x00, + FLT_INQ_RESULT = 0x01, + FLT_CONN_SETUP = 0x02, +}; +enum inq_result_cond_type { + INQ_RESULT_RETURN_ALL = 0x00, + INQ_RESULT_RETURN_CLASS = 0x01, + INQ_RESULT_RETURN_BDADDR = 0x02, +}; +enum conn_setup_cond_type { + CONN_SETUP_ALLOW_ALL = 0x00, + CONN_SETUP_ALLOW_CLASS = 0x01, + CONN_SETUP_ALLOW_BDADDR = 0x02, +}; +enum conn_setup_cond { + CONN_SETUP_AUTO_OFF = 0x01, + CONN_SETUP_AUTO_ON = 0x02, +}; + +#define OCF_FLUSH 0x0008 +typedef struct { + uint16_t handle; +} __attribute__ ((packed)) flush_cp; +#define FLUSH_CP_SIZE 2 + +typedef struct { + uint8_t status; + uint16_t handle; +} __attribute__ ((packed)) flush_rp; +#define FLUSH_RP_SIZE 3 + +#define OCF_READ_PIN_TYPE 0x0009 +typedef struct { + uint8_t status; + uint8_t pin_type; +} __attribute__ ((packed)) read_pin_type_rp; +#define READ_PIN_TYPE_RP_SIZE 2 + +#define OCF_WRITE_PIN_TYPE 0x000A +typedef struct { + uint8_t pin_type; +} __attribute__ ((packed)) write_pin_type_cp; +#define WRITE_PIN_TYPE_CP_SIZE 1 + +#define OCF_CREATE_NEW_UNIT_KEY 0x000B + +#define OCF_READ_STORED_LINK_KEY 0x000D +typedef struct { + bdaddr_t bdaddr; + uint8_t read_all; +} __attribute__ ((packed)) read_stored_link_key_cp; +#define READ_STORED_LINK_KEY_CP_SIZE 7 +typedef struct { + uint8_t status; + uint16_t max_keys; + uint16_t num_keys; +} __attribute__ ((packed)) read_stored_link_key_rp; +#define READ_STORED_LINK_KEY_RP_SIZE 5 + +#define OCF_WRITE_STORED_LINK_KEY 0x0011 +typedef struct { + uint8_t num_keys; + /* variable length part */ +} __attribute__ ((packed)) write_stored_link_key_cp; +#define WRITE_STORED_LINK_KEY_CP_SIZE 1 +typedef struct { + uint8_t status; + uint8_t num_keys; +} __attribute__ ((packed)) write_stored_link_key_rp; +#define READ_WRITE_LINK_KEY_RP_SIZE 2 + +#define OCF_DELETE_STORED_LINK_KEY 0x0012 +typedef struct { + bdaddr_t bdaddr; + uint8_t delete_all; +} __attribute__ ((packed)) delete_stored_link_key_cp; +#define DELETE_STORED_LINK_KEY_CP_SIZE 7 +typedef struct { + uint8_t status; + uint16_t num_keys; +} __attribute__ ((packed)) delete_stored_link_key_rp; +#define DELETE_STORED_LINK_KEY_RP_SIZE 3 + +#define OCF_CHANGE_LOCAL_NAME 0x0013 +typedef struct { + char name[248]; +} __attribute__ ((packed)) change_local_name_cp; +#define CHANGE_LOCAL_NAME_CP_SIZE 248 + +#define OCF_READ_LOCAL_NAME 0x0014 +typedef struct { + uint8_t status; + char name[248]; +} __attribute__ ((packed)) read_local_name_rp; +#define READ_LOCAL_NAME_RP_SIZE 249 + +#define OCF_READ_CONN_ACCEPT_TIMEOUT 0x0015 +typedef struct { + uint8_t status; + uint16_t timeout; +} __attribute__ ((packed)) read_conn_accept_timeout_rp; +#define READ_CONN_ACCEPT_TIMEOUT_RP_SIZE 3 + +#define OCF_WRITE_CONN_ACCEPT_TIMEOUT 0x0016 +typedef struct { + uint16_t timeout; +} __attribute__ ((packed)) write_conn_accept_timeout_cp; +#define WRITE_CONN_ACCEPT_TIMEOUT_CP_SIZE 2 + +#define OCF_READ_PAGE_TIMEOUT 0x0017 +typedef struct { + uint8_t status; + uint16_t timeout; +} __attribute__ ((packed)) read_page_timeout_rp; +#define READ_PAGE_TIMEOUT_RP_SIZE 3 + +#define OCF_WRITE_PAGE_TIMEOUT 0x0018 +typedef struct { + uint16_t timeout; +} __attribute__ ((packed)) write_page_timeout_cp; +#define WRITE_PAGE_TIMEOUT_CP_SIZE 2 + +#define OCF_READ_SCAN_ENABLE 0x0019 +typedef struct { + uint8_t status; + uint8_t enable; +} __attribute__ ((packed)) read_scan_enable_rp; +#define READ_SCAN_ENABLE_RP_SIZE 2 + +#define OCF_WRITE_SCAN_ENABLE 0x001A +typedef struct { + uint8_t scan_enable; +} __attribute__ ((packed)) write_scan_enable_cp; +#define WRITE_SCAN_ENABLE_CP_SIZE 1 + +enum scan_enable_bits { + SCAN_DISABLED = 0, + SCAN_INQUIRY = 1 << 0, + SCAN_PAGE = 1 << 1, +}; + +#define OCF_READ_PAGE_ACTIVITY 0x001B +typedef struct { + uint8_t status; + uint16_t interval; + uint16_t window; +} __attribute__ ((packed)) read_page_activity_rp; +#define READ_PAGE_ACTIVITY_RP_SIZE 5 + +#define OCF_WRITE_PAGE_ACTIVITY 0x001C +typedef struct { + uint16_t interval; + uint16_t window; +} __attribute__ ((packed)) write_page_activity_cp; +#define WRITE_PAGE_ACTIVITY_CP_SIZE 4 + +#define OCF_READ_INQ_ACTIVITY 0x001D +typedef struct { + uint8_t status; + uint16_t interval; + uint16_t window; +} __attribute__ ((packed)) read_inq_activity_rp; +#define READ_INQ_ACTIVITY_RP_SIZE 5 + +#define OCF_WRITE_INQ_ACTIVITY 0x001E +typedef struct { + uint16_t interval; + uint16_t window; +} __attribute__ ((packed)) write_inq_activity_cp; +#define WRITE_INQ_ACTIVITY_CP_SIZE 4 + +#define OCF_READ_AUTH_ENABLE 0x001F + +#define OCF_WRITE_AUTH_ENABLE 0x0020 + +#define AUTH_DISABLED 0x00 +#define AUTH_ENABLED 0x01 + +#define OCF_READ_ENCRYPT_MODE 0x0021 + +#define OCF_WRITE_ENCRYPT_MODE 0x0022 + +#define ENCRYPT_DISABLED 0x00 +#define ENCRYPT_P2P 0x01 +#define ENCRYPT_BOTH 0x02 + +#define OCF_READ_CLASS_OF_DEV 0x0023 +typedef struct { + uint8_t status; + uint8_t dev_class[3]; +} __attribute__ ((packed)) read_class_of_dev_rp; +#define READ_CLASS_OF_DEV_RP_SIZE 4 + +#define OCF_WRITE_CLASS_OF_DEV 0x0024 +typedef struct { + uint8_t dev_class[3]; +} __attribute__ ((packed)) write_class_of_dev_cp; +#define WRITE_CLASS_OF_DEV_CP_SIZE 3 + +#define OCF_READ_VOICE_SETTING 0x0025 +typedef struct { + uint8_t status; + uint16_t voice_setting; +} __attribute__ ((packed)) read_voice_setting_rp; +#define READ_VOICE_SETTING_RP_SIZE 3 + +#define OCF_WRITE_VOICE_SETTING 0x0026 +typedef struct { + uint16_t voice_setting; +} __attribute__ ((packed)) write_voice_setting_cp; +#define WRITE_VOICE_SETTING_CP_SIZE 2 + +#define OCF_READ_AUTOMATIC_FLUSH_TIMEOUT 0x0027 + +#define OCF_WRITE_AUTOMATIC_FLUSH_TIMEOUT 0x0028 + +#define OCF_READ_NUM_BROADCAST_RETRANS 0x0029 + +#define OCF_WRITE_NUM_BROADCAST_RETRANS 0x002A + +#define OCF_READ_HOLD_MODE_ACTIVITY 0x002B + +#define OCF_WRITE_HOLD_MODE_ACTIVITY 0x002C + +#define OCF_READ_TRANSMIT_POWER_LEVEL 0x002D +typedef struct { + uint16_t handle; + uint8_t type; +} __attribute__ ((packed)) read_transmit_power_level_cp; +#define READ_TRANSMIT_POWER_LEVEL_CP_SIZE 3 +typedef struct { + uint8_t status; + uint16_t handle; + int8_t level; +} __attribute__ ((packed)) read_transmit_power_level_rp; +#define READ_TRANSMIT_POWER_LEVEL_RP_SIZE 4 + +#define OCF_HOST_BUFFER_SIZE 0x0033 +typedef struct { + uint16_t acl_mtu; + uint8_t sco_mtu; + uint16_t acl_max_pkt; + uint16_t sco_max_pkt; +} __attribute__ ((packed)) host_buffer_size_cp; +#define HOST_BUFFER_SIZE_CP_SIZE 7 + +#define OCF_HOST_NUMBER_OF_COMPLETED_PACKETS 0x0035 + +#define OCF_READ_LINK_SUPERVISION_TIMEOUT 0x0036 +typedef struct { + uint8_t status; + uint16_t handle; + uint16_t link_sup_to; +} __attribute__ ((packed)) read_link_supervision_timeout_rp; +#define READ_LINK_SUPERVISION_TIMEOUT_RP_SIZE 5 + +#define OCF_WRITE_LINK_SUPERVISION_TIMEOUT 0x0037 +typedef struct { + uint16_t handle; + uint16_t link_sup_to; +} __attribute__ ((packed)) write_link_supervision_timeout_cp; +#define WRITE_LINK_SUPERVISION_TIMEOUT_CP_SIZE 4 +typedef struct { + uint8_t status; + uint16_t handle; +} __attribute__ ((packed)) write_link_supervision_timeout_rp; +#define WRITE_LINK_SUPERVISION_TIMEOUT_RP_SIZE 3 + +#define OCF_READ_NUM_SUPPORTED_IAC 0x0038 + +#define MAX_IAC_LAP 0x40 +#define OCF_READ_CURRENT_IAC_LAP 0x0039 +typedef struct { + uint8_t status; + uint8_t num_current_iac; + uint8_t lap[MAX_IAC_LAP][3]; +} __attribute__ ((packed)) read_current_iac_lap_rp; +#define READ_CURRENT_IAC_LAP_RP_SIZE 2+3*MAX_IAC_LAP + +#define OCF_WRITE_CURRENT_IAC_LAP 0x003A +typedef struct { + uint8_t num_current_iac; + uint8_t lap[MAX_IAC_LAP][3]; +} __attribute__ ((packed)) write_current_iac_lap_cp; +#define WRITE_CURRENT_IAC_LAP_CP_SIZE 1+3*MAX_IAC_LAP + +#define OCF_READ_PAGE_SCAN_PERIOD_MODE 0x003B + +#define OCF_WRITE_PAGE_SCAN_PERIOD_MODE 0x003C + +#define OCF_READ_PAGE_SCAN_MODE 0x003D + +#define OCF_WRITE_PAGE_SCAN_MODE 0x003E + +#define OCF_SET_AFH_CLASSIFICATION 0x003F +typedef struct { + uint8_t map[10]; +} __attribute__ ((packed)) set_afh_classification_cp; +#define SET_AFH_CLASSIFICATION_CP_SIZE 10 +typedef struct { + uint8_t status; +} __attribute__ ((packed)) set_afh_classification_rp; +#define SET_AFH_CLASSIFICATION_RP_SIZE 1 + +#define OCF_READ_INQUIRY_SCAN_TYPE 0x0042 +typedef struct { + uint8_t status; + uint8_t type; +} __attribute__ ((packed)) read_inquiry_scan_type_rp; +#define READ_INQUIRY_SCAN_TYPE_RP_SIZE 2 + +#define OCF_WRITE_INQUIRY_SCAN_TYPE 0x0043 +typedef struct { + uint8_t type; +} __attribute__ ((packed)) write_inquiry_scan_type_cp; +#define WRITE_INQUIRY_SCAN_TYPE_CP_SIZE 1 +typedef struct { + uint8_t status; +} __attribute__ ((packed)) write_inquiry_scan_type_rp; +#define WRITE_INQUIRY_SCAN_TYPE_RP_SIZE 1 + +#define OCF_READ_INQUIRY_MODE 0x0044 +typedef struct { + uint8_t status; + uint8_t mode; +} __attribute__ ((packed)) read_inquiry_mode_rp; +#define READ_INQUIRY_MODE_RP_SIZE 2 + +#define OCF_WRITE_INQUIRY_MODE 0x0045 +typedef struct { + uint8_t mode; +} __attribute__ ((packed)) write_inquiry_mode_cp; +#define WRITE_INQUIRY_MODE_CP_SIZE 1 +typedef struct { + uint8_t status; +} __attribute__ ((packed)) write_inquiry_mode_rp; +#define WRITE_INQUIRY_MODE_RP_SIZE 1 + +#define OCF_READ_PAGE_SCAN_TYPE 0x0046 + +#define OCF_WRITE_PAGE_SCAN_TYPE 0x0047 + +#define OCF_READ_AFH_MODE 0x0048 +typedef struct { + uint8_t status; + uint8_t mode; +} __attribute__ ((packed)) read_afh_mode_rp; +#define READ_AFH_MODE_RP_SIZE 2 + +#define OCF_WRITE_AFH_MODE 0x0049 +typedef struct { + uint8_t mode; +} __attribute__ ((packed)) write_afh_mode_cp; +#define WRITE_AFH_MODE_CP_SIZE 1 +typedef struct { + uint8_t status; +} __attribute__ ((packed)) write_afh_mode_rp; +#define WRITE_AFH_MODE_RP_SIZE 1 + +#define OCF_READ_EXT_INQUIRY_RESPONSE 0x0051 +typedef struct { + uint8_t status; + uint8_t fec; + uint8_t data[240]; +} __attribute__ ((packed)) read_ext_inquiry_response_rp; +#define READ_EXT_INQUIRY_RESPONSE_RP_SIZE 242 + +#define OCF_WRITE_EXT_INQUIRY_RESPONSE 0x0052 +typedef struct { + uint8_t fec; + uint8_t data[240]; +} __attribute__ ((packed)) write_ext_inquiry_response_cp; +#define WRITE_EXT_INQUIRY_RESPONSE_CP_SIZE 241 +typedef struct { + uint8_t status; +} __attribute__ ((packed)) write_ext_inquiry_response_rp; +#define WRITE_EXT_INQUIRY_RESPONSE_RP_SIZE 1 + +/* Informational Parameters */ +#define OGF_INFO_PARAM 0x04 + +#define OCF_READ_LOCAL_VERSION 0x0001 +typedef struct { + uint8_t status; + uint8_t hci_ver; + uint16_t hci_rev; + uint8_t lmp_ver; + uint16_t manufacturer; + uint16_t lmp_subver; +} __attribute__ ((packed)) read_local_version_rp; +#define READ_LOCAL_VERSION_RP_SIZE 9 + +#define OCF_READ_LOCAL_COMMANDS 0x0002 +typedef struct { + uint8_t status; + uint8_t commands[64]; +} __attribute__ ((packed)) read_local_commands_rp; +#define READ_LOCAL_COMMANDS_RP_SIZE 65 + +#define OCF_READ_LOCAL_FEATURES 0x0003 +typedef struct { + uint8_t status; + uint8_t features[8]; +} __attribute__ ((packed)) read_local_features_rp; +#define READ_LOCAL_FEATURES_RP_SIZE 9 + +#define OCF_READ_LOCAL_EXT_FEATURES 0x0004 +typedef struct { + uint8_t page_num; +} __attribute__ ((packed)) read_local_ext_features_cp; +#define READ_LOCAL_EXT_FEATURES_CP_SIZE 1 +typedef struct { + uint8_t status; + uint8_t page_num; + uint8_t max_page_num; + uint8_t features[8]; +} __attribute__ ((packed)) read_local_ext_features_rp; +#define READ_LOCAL_EXT_FEATURES_RP_SIZE 11 + +#define OCF_READ_BUFFER_SIZE 0x0005 +typedef struct { + uint8_t status; + uint16_t acl_mtu; + uint8_t sco_mtu; + uint16_t acl_max_pkt; + uint16_t sco_max_pkt; +} __attribute__ ((packed)) read_buffer_size_rp; +#define READ_BUFFER_SIZE_RP_SIZE 8 + +#define OCF_READ_COUNTRY_CODE 0x0007 +typedef struct { + uint8_t status; + uint8_t country_code; +} __attribute__ ((packed)) read_country_code_rp; +#define READ_COUNTRY_CODE_RP_SIZE 2 + +#define OCF_READ_BD_ADDR 0x0009 +typedef struct { + uint8_t status; + bdaddr_t bdaddr; +} __attribute__ ((packed)) read_bd_addr_rp; +#define READ_BD_ADDR_RP_SIZE 7 + +/* Status params */ +#define OGF_STATUS_PARAM 0x05 + +#define OCF_READ_FAILED_CONTACT_COUNTER 0x0001 +typedef struct { + uint8_t status; + uint16_t handle; + uint8_t counter; +} __attribute__ ((packed)) read_failed_contact_counter_rp; +#define READ_FAILED_CONTACT_COUNTER_RP_SIZE 4 + +#define OCF_RESET_FAILED_CONTACT_COUNTER 0x0002 +typedef struct { + uint8_t status; + uint16_t handle; +} __attribute__ ((packed)) reset_failed_contact_counter_rp; +#define RESET_FAILED_CONTACT_COUNTER_RP_SIZE 4 + +#define OCF_READ_LINK_QUALITY 0x0003 +typedef struct { + uint16_t handle; +} __attribute__ ((packed)) read_link_quality_cp; +#define READ_LINK_QUALITY_CP_SIZE 4 + +typedef struct { + uint8_t status; + uint16_t handle; + uint8_t link_quality; +} __attribute__ ((packed)) read_link_quality_rp; +#define READ_LINK_QUALITY_RP_SIZE 4 + +#define OCF_READ_RSSI 0x0005 +typedef struct { + uint8_t status; + uint16_t handle; + int8_t rssi; +} __attribute__ ((packed)) read_rssi_rp; +#define READ_RSSI_RP_SIZE 4 + +#define OCF_READ_AFH_MAP 0x0006 +typedef struct { + uint8_t status; + uint16_t handle; + uint8_t mode; + uint8_t map[10]; +} __attribute__ ((packed)) read_afh_map_rp; +#define READ_AFH_MAP_RP_SIZE 14 + +#define OCF_READ_CLOCK 0x0007 +typedef struct { + uint16_t handle; + uint8_t which_clock; +} __attribute__ ((packed)) read_clock_cp; +#define READ_CLOCK_CP_SIZE 3 +typedef struct { + uint8_t status; + uint16_t handle; + uint32_t clock; + uint16_t accuracy; +} __attribute__ ((packed)) read_clock_rp; +#define READ_CLOCK_RP_SIZE 9 + +/* Testing commands */ +#define OGF_TESTING_CMD 0x3e + +/* Vendor specific commands */ +#define OGF_VENDOR_CMD 0x3f + +/* HCI Events */ + +#define EVT_INQUIRY_COMPLETE 0x01 + +#define EVT_INQUIRY_RESULT 0x02 +typedef struct { + uint8_t num_responses; + bdaddr_t bdaddr; + uint8_t pscan_rep_mode; + uint8_t pscan_period_mode; + uint8_t pscan_mode; + uint8_t dev_class[3]; + uint16_t clock_offset; +} __attribute__ ((packed)) inquiry_info; +#define INQUIRY_INFO_SIZE 14 + +#define EVT_CONN_COMPLETE 0x03 +typedef struct { + uint8_t status; + uint16_t handle; + bdaddr_t bdaddr; + uint8_t link_type; + uint8_t encr_mode; +} __attribute__ ((packed)) evt_conn_complete; +#define EVT_CONN_COMPLETE_SIZE 11 + +#define EVT_CONN_REQUEST 0x04 +typedef struct { + bdaddr_t bdaddr; + uint8_t dev_class[3]; + uint8_t link_type; +} __attribute__ ((packed)) evt_conn_request; +#define EVT_CONN_REQUEST_SIZE 10 + +#define EVT_DISCONN_COMPLETE 0x05 +typedef struct { + uint8_t status; + uint16_t handle; + uint8_t reason; +} __attribute__ ((packed)) evt_disconn_complete; +#define EVT_DISCONN_COMPLETE_SIZE 4 + +#define EVT_AUTH_COMPLETE 0x06 +typedef struct { + uint8_t status; + uint16_t handle; +} __attribute__ ((packed)) evt_auth_complete; +#define EVT_AUTH_COMPLETE_SIZE 3 + +#define EVT_REMOTE_NAME_REQ_COMPLETE 0x07 +typedef struct { + uint8_t status; + bdaddr_t bdaddr; + char name[248]; +} __attribute__ ((packed)) evt_remote_name_req_complete; +#define EVT_REMOTE_NAME_REQ_COMPLETE_SIZE 255 + +#define EVT_ENCRYPT_CHANGE 0x08 +typedef struct { + uint8_t status; + uint16_t handle; + uint8_t encrypt; +} __attribute__ ((packed)) evt_encrypt_change; +#define EVT_ENCRYPT_CHANGE_SIZE 5 + +#define EVT_CHANGE_CONN_LINK_KEY_COMPLETE 0x09 +typedef struct { + uint8_t status; + uint16_t handle; +} __attribute__ ((packed)) evt_change_conn_link_key_complete; +#define EVT_CHANGE_CONN_LINK_KEY_COMPLETE_SIZE 3 + +#define EVT_MASTER_LINK_KEY_COMPLETE 0x0A +typedef struct { + uint8_t status; + uint16_t handle; + uint8_t key_flag; +} __attribute__ ((packed)) evt_master_link_key_complete; +#define EVT_MASTER_LINK_KEY_COMPLETE_SIZE 4 + +#define EVT_READ_REMOTE_FEATURES_COMPLETE 0x0B +typedef struct { + uint8_t status; + uint16_t handle; + uint8_t features[8]; +} __attribute__ ((packed)) evt_read_remote_features_complete; +#define EVT_READ_REMOTE_FEATURES_COMPLETE_SIZE 11 + +#define EVT_READ_REMOTE_VERSION_COMPLETE 0x0C +typedef struct { + uint8_t status; + uint16_t handle; + uint8_t lmp_ver; + uint16_t manufacturer; + uint16_t lmp_subver; +} __attribute__ ((packed)) evt_read_remote_version_complete; +#define EVT_READ_REMOTE_VERSION_COMPLETE_SIZE 8 + +#define EVT_QOS_SETUP_COMPLETE 0x0D +typedef struct { + uint8_t status; + uint16_t handle; + uint8_t flags; /* Reserved */ + hci_qos qos; +} __attribute__ ((packed)) evt_qos_setup_complete; +#define EVT_QOS_SETUP_COMPLETE_SIZE (4 + HCI_QOS_CP_SIZE) + +#define EVT_CMD_COMPLETE 0x0E +typedef struct { + uint8_t ncmd; + uint16_t opcode; +} __attribute__ ((packed)) evt_cmd_complete; +#define EVT_CMD_COMPLETE_SIZE 3 + +#define EVT_CMD_STATUS 0x0F +typedef struct { + uint8_t status; + uint8_t ncmd; + uint16_t opcode; +} __attribute__ ((packed)) evt_cmd_status; +#define EVT_CMD_STATUS_SIZE 4 + +#define EVT_HARDWARE_ERROR 0x10 +typedef struct { + uint8_t code; +} __attribute__ ((packed)) evt_hardware_error; +#define EVT_HARDWARE_ERROR_SIZE 1 + +#define EVT_FLUSH_OCCURRED 0x11 +typedef struct { + uint16_t handle; +} __attribute__ ((packed)) evt_flush_occured; +#define EVT_FLUSH_OCCURRED_SIZE 2 + +#define EVT_ROLE_CHANGE 0x12 +typedef struct { + uint8_t status; + bdaddr_t bdaddr; + uint8_t role; +} __attribute__ ((packed)) evt_role_change; +#define EVT_ROLE_CHANGE_SIZE 8 + +#define EVT_NUM_COMP_PKTS 0x13 +typedef struct { + uint8_t num_hndl; + struct { + uint16_t handle; + uint16_t num_packets; + } connection[0]; +} __attribute__ ((packed)) evt_num_comp_pkts; +#define EVT_NUM_COMP_PKTS_SIZE(num_hndl) (1 + 4 * (num_hndl)) + +#define EVT_MODE_CHANGE 0x14 +typedef struct { + uint8_t status; + uint16_t handle; + uint8_t mode; + uint16_t interval; +} __attribute__ ((packed)) evt_mode_change; +#define EVT_MODE_CHANGE_SIZE 6 + +#define EVT_RETURN_LINK_KEYS 0x15 +typedef struct { + uint8_t num_keys; + /* variable length part */ +} __attribute__ ((packed)) evt_return_link_keys; +#define EVT_RETURN_LINK_KEYS_SIZE 1 + +#define EVT_PIN_CODE_REQ 0x16 +typedef struct { + bdaddr_t bdaddr; +} __attribute__ ((packed)) evt_pin_code_req; +#define EVT_PIN_CODE_REQ_SIZE 6 + +#define EVT_LINK_KEY_REQ 0x17 +typedef struct { + bdaddr_t bdaddr; +} __attribute__ ((packed)) evt_link_key_req; +#define EVT_LINK_KEY_REQ_SIZE 6 + +#define EVT_LINK_KEY_NOTIFY 0x18 +typedef struct { + bdaddr_t bdaddr; + uint8_t link_key[16]; + uint8_t key_type; +} __attribute__ ((packed)) evt_link_key_notify; +#define EVT_LINK_KEY_NOTIFY_SIZE 23 + +#define EVT_LOOPBACK_COMMAND 0x19 + +#define EVT_DATA_BUFFER_OVERFLOW 0x1A +typedef struct { + uint8_t link_type; +} __attribute__ ((packed)) evt_data_buffer_overflow; +#define EVT_DATA_BUFFER_OVERFLOW_SIZE 1 + +#define EVT_MAX_SLOTS_CHANGE 0x1B +typedef struct { + uint16_t handle; + uint8_t max_slots; +} __attribute__ ((packed)) evt_max_slots_change; +#define EVT_MAX_SLOTS_CHANGE_SIZE 3 + +#define EVT_READ_CLOCK_OFFSET_COMPLETE 0x1C +typedef struct { + uint8_t status; + uint16_t handle; + uint16_t clock_offset; +} __attribute__ ((packed)) evt_read_clock_offset_complete; +#define EVT_READ_CLOCK_OFFSET_COMPLETE_SIZE 5 + +#define EVT_CONN_PTYPE_CHANGED 0x1D +typedef struct { + uint8_t status; + uint16_t handle; + uint16_t ptype; +} __attribute__ ((packed)) evt_conn_ptype_changed; +#define EVT_CONN_PTYPE_CHANGED_SIZE 5 + +#define EVT_QOS_VIOLATION 0x1E +typedef struct { + uint16_t handle; +} __attribute__ ((packed)) evt_qos_violation; +#define EVT_QOS_VIOLATION_SIZE 2 + +#define EVT_PSCAN_REP_MODE_CHANGE 0x20 +typedef struct { + bdaddr_t bdaddr; + uint8_t pscan_rep_mode; +} __attribute__ ((packed)) evt_pscan_rep_mode_change; +#define EVT_PSCAN_REP_MODE_CHANGE_SIZE 7 + +#define EVT_FLOW_SPEC_COMPLETE 0x21 +typedef struct { + uint8_t status; + uint16_t handle; + uint8_t flags; + uint8_t direction; + hci_qos qos; +} __attribute__ ((packed)) evt_flow_spec_complete; +#define EVT_FLOW_SPEC_COMPLETE_SIZE (5 + HCI_QOS_CP_SIZE) + +#define EVT_INQUIRY_RESULT_WITH_RSSI 0x22 +typedef struct { + uint8_t num_responses; + bdaddr_t bdaddr; + uint8_t pscan_rep_mode; + uint8_t pscan_period_mode; + uint8_t dev_class[3]; + uint16_t clock_offset; + int8_t rssi; +} __attribute__ ((packed)) inquiry_info_with_rssi; +#define INQUIRY_INFO_WITH_RSSI_SIZE 15 +typedef struct { + uint8_t num_responses; + bdaddr_t bdaddr; + uint8_t pscan_rep_mode; + uint8_t pscan_period_mode; + uint8_t pscan_mode; + uint8_t dev_class[3]; + uint16_t clock_offset; + int8_t rssi; +} __attribute__ ((packed)) inquiry_info_with_rssi_and_pscan_mode; +#define INQUIRY_INFO_WITH_RSSI_AND_PSCAN_MODE_SIZE 16 + +#define EVT_READ_REMOTE_EXT_FEATURES_COMPLETE 0x23 +typedef struct { + uint8_t status; + uint16_t handle; + uint8_t page_num; + uint8_t max_page_num; + uint8_t features[8]; +} __attribute__ ((packed)) evt_read_remote_ext_features_complete; +#define EVT_READ_REMOTE_EXT_FEATURES_COMPLETE_SIZE 13 + +#define EVT_SYNC_CONN_COMPLETE 0x2C +typedef struct { + uint8_t status; + uint16_t handle; + bdaddr_t bdaddr; + uint8_t link_type; + uint8_t trans_interval; + uint8_t retrans_window; + uint16_t rx_pkt_len; + uint16_t tx_pkt_len; + uint8_t air_mode; +} __attribute__ ((packed)) evt_sync_conn_complete; +#define EVT_SYNC_CONN_COMPLETE_SIZE 17 + +#define EVT_SYNC_CONN_CHANGED 0x2D +typedef struct { + uint8_t status; + uint16_t handle; + uint8_t trans_interval; + uint8_t retrans_window; + uint16_t rx_pkt_len; + uint16_t tx_pkt_len; +} __attribute__ ((packed)) evt_sync_conn_changed; +#define EVT_SYNC_CONN_CHANGED_SIZE 9 + +#define EVT_SNIFF_SUBRATE 0x2E +typedef struct { + uint8_t status; + uint16_t handle; + uint16_t max_remote_latency; + uint16_t max_local_latency; + uint16_t min_remote_timeout; + uint16_t min_local_timeout; +} __attribute__ ((packed)) evt_sniff_subrate; +#define EVT_SNIFF_SUBRATE_SIZE 11 + +#define EVT_EXTENDED_INQUIRY_RESULT 0x2F +typedef struct { + bdaddr_t bdaddr; + uint8_t pscan_rep_mode; + uint8_t pscan_period_mode; + uint8_t dev_class[3]; + uint16_t clock_offset; + int8_t rssi; + uint8_t data[240]; +} __attribute__ ((packed)) extended_inquiry_info; +#define EXTENDED_INQUIRY_INFO_SIZE 254 + +#define EVT_TESTING 0xFE + +#define EVT_VENDOR 0xFF + +/* Command opcode pack/unpack */ +#define cmd_opcode_pack(ogf, ocf) (uint16_t)((ocf & 0x03ff)|(ogf << 10)) +#define cmd_opcode_ogf(op) (op >> 10) +#define cmd_opcode_ocf(op) (op & 0x03ff) + +/* ACL handle and flags pack/unpack */ +#define acl_handle_pack(h, f) (uint16_t)(((h) & 0x0fff)|((f) << 12)) +#define acl_handle(h) ((h) & 0x0fff) +#define acl_flags(h) ((h) >> 12) + +/* HCI Packet structures */ +#define HCI_COMMAND_HDR_SIZE 3 +#define HCI_EVENT_HDR_SIZE 2 +#define HCI_ACL_HDR_SIZE 4 +#define HCI_SCO_HDR_SIZE 3 + +struct hci_command_hdr { + uint16_t opcode; /* OCF & OGF */ + uint8_t plen; +} __attribute__ ((packed)); + +struct hci_event_hdr { + uint8_t evt; + uint8_t plen; +} __attribute__ ((packed)); + +struct hci_acl_hdr { + uint16_t handle; /* Handle & Flags(PB, BC) */ + uint16_t dlen; +} __attribute__ ((packed)); + +struct hci_sco_hdr { + uint16_t handle; + uint8_t dlen; +} __attribute__ ((packed)); + +/* L2CAP layer defines */ + +enum bt_l2cap_lm_bits { + L2CAP_LM_MASTER = 1 << 0, + L2CAP_LM_AUTH = 1 << 1, + L2CAP_LM_ENCRYPT = 1 << 2, + L2CAP_LM_TRUSTED = 1 << 3, + L2CAP_LM_RELIABLE = 1 << 4, + L2CAP_LM_SECURE = 1 << 5, +}; + +enum bt_l2cap_cid_predef { + L2CAP_CID_INVALID = 0x0000, + L2CAP_CID_SIGNALLING= 0x0001, + L2CAP_CID_GROUP = 0x0002, + L2CAP_CID_ALLOC = 0x0040, +}; + +/* L2CAP command codes */ +enum bt_l2cap_cmd { + L2CAP_COMMAND_REJ = 1, + L2CAP_CONN_REQ, + L2CAP_CONN_RSP, + L2CAP_CONF_REQ, + L2CAP_CONF_RSP, + L2CAP_DISCONN_REQ, + L2CAP_DISCONN_RSP, + L2CAP_ECHO_REQ, + L2CAP_ECHO_RSP, + L2CAP_INFO_REQ, + L2CAP_INFO_RSP, +}; + +enum bt_l2cap_sar_bits { + L2CAP_SAR_NO_SEG = 0, + L2CAP_SAR_START, + L2CAP_SAR_END, + L2CAP_SAR_CONT, +}; + +/* L2CAP structures */ +typedef struct { + uint16_t len; + uint16_t cid; + uint8_t data[0]; +} __attribute__ ((packed)) l2cap_hdr; +#define L2CAP_HDR_SIZE 4 + +typedef struct { + uint8_t code; + uint8_t ident; + uint16_t len; +} __attribute__ ((packed)) l2cap_cmd_hdr; +#define L2CAP_CMD_HDR_SIZE 4 + +typedef struct { + uint16_t reason; +} __attribute__ ((packed)) l2cap_cmd_rej; +#define L2CAP_CMD_REJ_SIZE 2 + +typedef struct { + uint16_t dcid; + uint16_t scid; +} __attribute__ ((packed)) l2cap_cmd_rej_cid; +#define L2CAP_CMD_REJ_CID_SIZE 4 + +/* reject reason */ +enum bt_l2cap_rej_reason { + L2CAP_REJ_CMD_NOT_UNDERSTOOD = 0, + L2CAP_REJ_SIG_TOOBIG, + L2CAP_REJ_CID_INVAL, +}; + +typedef struct { + uint16_t psm; + uint16_t scid; +} __attribute__ ((packed)) l2cap_conn_req; +#define L2CAP_CONN_REQ_SIZE 4 + +typedef struct { + uint16_t dcid; + uint16_t scid; + uint16_t result; + uint16_t status; +} __attribute__ ((packed)) l2cap_conn_rsp; +#define L2CAP_CONN_RSP_SIZE 8 + +/* connect result */ +enum bt_l2cap_conn_res { + L2CAP_CR_SUCCESS = 0, + L2CAP_CR_PEND, + L2CAP_CR_BAD_PSM, + L2CAP_CR_SEC_BLOCK, + L2CAP_CR_NO_MEM, +}; + +/* connect status */ +enum bt_l2cap_conn_stat { + L2CAP_CS_NO_INFO = 0, + L2CAP_CS_AUTHEN_PEND, + L2CAP_CS_AUTHOR_PEND, +}; + +typedef struct { + uint16_t dcid; + uint16_t flags; + uint8_t data[0]; +} __attribute__ ((packed)) l2cap_conf_req; +#define L2CAP_CONF_REQ_SIZE(datalen) (4 + (datalen)) + +typedef struct { + uint16_t scid; + uint16_t flags; + uint16_t result; + uint8_t data[0]; +} __attribute__ ((packed)) l2cap_conf_rsp; +#define L2CAP_CONF_RSP_SIZE(datalen) (6 + datalen) + +enum bt_l2cap_conf_res { + L2CAP_CONF_SUCCESS = 0, + L2CAP_CONF_UNACCEPT, + L2CAP_CONF_REJECT, + L2CAP_CONF_UNKNOWN, +}; + +typedef struct { + uint8_t type; + uint8_t len; + uint8_t val[0]; +} __attribute__ ((packed)) l2cap_conf_opt; +#define L2CAP_CONF_OPT_SIZE 2 + +enum bt_l2cap_conf_val { + L2CAP_CONF_MTU = 1, + L2CAP_CONF_FLUSH_TO, + L2CAP_CONF_QOS, + L2CAP_CONF_RFC, + L2CAP_CONF_RFC_MODE = L2CAP_CONF_RFC, +}; + +typedef struct { + uint8_t flags; + uint8_t service_type; + uint32_t token_rate; + uint32_t token_bucket_size; + uint32_t peak_bandwidth; + uint32_t latency; + uint32_t delay_variation; +} __attribute__ ((packed)) l2cap_conf_opt_qos; +#define L2CAP_CONF_OPT_QOS_SIZE 22 + +enum bt_l2cap_conf_opt_qos_st { + L2CAP_CONF_QOS_NO_TRAFFIC = 0x00, + L2CAP_CONF_QOS_BEST_EFFORT, + L2CAP_CONF_QOS_GUARANTEED, +}; + +#define L2CAP_CONF_QOS_WILDCARD 0xffffffff + +enum bt_l2cap_mode { + L2CAP_MODE_BASIC = 0, + L2CAP_MODE_RETRANS = 1, + L2CAP_MODE_FLOWCTL = 2, +}; + +typedef struct { + uint16_t dcid; + uint16_t scid; +} __attribute__ ((packed)) l2cap_disconn_req; +#define L2CAP_DISCONN_REQ_SIZE 4 + +typedef struct { + uint16_t dcid; + uint16_t scid; +} __attribute__ ((packed)) l2cap_disconn_rsp; +#define L2CAP_DISCONN_RSP_SIZE 4 + +typedef struct { + uint16_t type; +} __attribute__ ((packed)) l2cap_info_req; +#define L2CAP_INFO_REQ_SIZE 2 + +typedef struct { + uint16_t type; + uint16_t result; + uint8_t data[0]; +} __attribute__ ((packed)) l2cap_info_rsp; +#define L2CAP_INFO_RSP_SIZE 4 + +/* info type */ +enum bt_l2cap_info_type { + L2CAP_IT_CL_MTU = 1, + L2CAP_IT_FEAT_MASK, +}; + +/* info result */ +enum bt_l2cap_info_result { + L2CAP_IR_SUCCESS = 0, + L2CAP_IR_NOTSUPP, +}; + +/* Service Discovery Protocol defines */ +/* Note that all multibyte values in lower layer protocols (above in this file) + * are little-endian while SDP is big-endian. */ + +/* Protocol UUIDs */ +enum sdp_proto_uuid { + SDP_UUID = 0x0001, + UDP_UUID = 0x0002, + RFCOMM_UUID = 0x0003, + TCP_UUID = 0x0004, + TCS_BIN_UUID = 0x0005, + TCS_AT_UUID = 0x0006, + OBEX_UUID = 0x0008, + IP_UUID = 0x0009, + FTP_UUID = 0x000a, + HTTP_UUID = 0x000c, + WSP_UUID = 0x000e, + BNEP_UUID = 0x000f, + UPNP_UUID = 0x0010, + HIDP_UUID = 0x0011, + HCRP_CTRL_UUID = 0x0012, + HCRP_DATA_UUID = 0x0014, + HCRP_NOTE_UUID = 0x0016, + AVCTP_UUID = 0x0017, + AVDTP_UUID = 0x0019, + CMTP_UUID = 0x001b, + UDI_UUID = 0x001d, + MCAP_CTRL_UUID = 0x001e, + MCAP_DATA_UUID = 0x001f, + L2CAP_UUID = 0x0100, +}; + +/* + * Service class identifiers of standard services and service groups + */ +enum service_class_id { + SDP_SERVER_SVCLASS_ID = 0x1000, + BROWSE_GRP_DESC_SVCLASS_ID = 0x1001, + PUBLIC_BROWSE_GROUP = 0x1002, + SERIAL_PORT_SVCLASS_ID = 0x1101, + LAN_ACCESS_SVCLASS_ID = 0x1102, + DIALUP_NET_SVCLASS_ID = 0x1103, + IRMC_SYNC_SVCLASS_ID = 0x1104, + OBEX_OBJPUSH_SVCLASS_ID = 0x1105, + OBEX_FILETRANS_SVCLASS_ID = 0x1106, + IRMC_SYNC_CMD_SVCLASS_ID = 0x1107, + HEADSET_SVCLASS_ID = 0x1108, + CORDLESS_TELEPHONY_SVCLASS_ID = 0x1109, + AUDIO_SOURCE_SVCLASS_ID = 0x110a, + AUDIO_SINK_SVCLASS_ID = 0x110b, + AV_REMOTE_TARGET_SVCLASS_ID = 0x110c, + ADVANCED_AUDIO_SVCLASS_ID = 0x110d, + AV_REMOTE_SVCLASS_ID = 0x110e, + VIDEO_CONF_SVCLASS_ID = 0x110f, + INTERCOM_SVCLASS_ID = 0x1110, + FAX_SVCLASS_ID = 0x1111, + HEADSET_AGW_SVCLASS_ID = 0x1112, + WAP_SVCLASS_ID = 0x1113, + WAP_CLIENT_SVCLASS_ID = 0x1114, + PANU_SVCLASS_ID = 0x1115, + NAP_SVCLASS_ID = 0x1116, + GN_SVCLASS_ID = 0x1117, + DIRECT_PRINTING_SVCLASS_ID = 0x1118, + REFERENCE_PRINTING_SVCLASS_ID = 0x1119, + IMAGING_SVCLASS_ID = 0x111a, + IMAGING_RESPONDER_SVCLASS_ID = 0x111b, + IMAGING_ARCHIVE_SVCLASS_ID = 0x111c, + IMAGING_REFOBJS_SVCLASS_ID = 0x111d, + HANDSFREE_SVCLASS_ID = 0x111e, + HANDSFREE_AGW_SVCLASS_ID = 0x111f, + DIRECT_PRT_REFOBJS_SVCLASS_ID = 0x1120, + REFLECTED_UI_SVCLASS_ID = 0x1121, + BASIC_PRINTING_SVCLASS_ID = 0x1122, + PRINTING_STATUS_SVCLASS_ID = 0x1123, + HID_SVCLASS_ID = 0x1124, + HCR_SVCLASS_ID = 0x1125, + HCR_PRINT_SVCLASS_ID = 0x1126, + HCR_SCAN_SVCLASS_ID = 0x1127, + CIP_SVCLASS_ID = 0x1128, + VIDEO_CONF_GW_SVCLASS_ID = 0x1129, + UDI_MT_SVCLASS_ID = 0x112a, + UDI_TA_SVCLASS_ID = 0x112b, + AV_SVCLASS_ID = 0x112c, + SAP_SVCLASS_ID = 0x112d, + PBAP_PCE_SVCLASS_ID = 0x112e, + PBAP_PSE_SVCLASS_ID = 0x112f, + PBAP_SVCLASS_ID = 0x1130, + PNP_INFO_SVCLASS_ID = 0x1200, + GENERIC_NETWORKING_SVCLASS_ID = 0x1201, + GENERIC_FILETRANS_SVCLASS_ID = 0x1202, + GENERIC_AUDIO_SVCLASS_ID = 0x1203, + GENERIC_TELEPHONY_SVCLASS_ID = 0x1204, + UPNP_SVCLASS_ID = 0x1205, + UPNP_IP_SVCLASS_ID = 0x1206, + UPNP_PAN_SVCLASS_ID = 0x1300, + UPNP_LAP_SVCLASS_ID = 0x1301, + UPNP_L2CAP_SVCLASS_ID = 0x1302, + VIDEO_SOURCE_SVCLASS_ID = 0x1303, + VIDEO_SINK_SVCLASS_ID = 0x1304, + VIDEO_DISTRIBUTION_SVCLASS_ID = 0x1305, + MDP_SVCLASS_ID = 0x1400, + MDP_SOURCE_SVCLASS_ID = 0x1401, + MDP_SINK_SVCLASS_ID = 0x1402, + APPLE_AGENT_SVCLASS_ID = 0x2112, +}; + +/* + * Standard profile descriptor identifiers; note these + * may be identical to some of the service classes defined above + */ +#define SDP_SERVER_PROFILE_ID SDP_SERVER_SVCLASS_ID +#define BROWSE_GRP_DESC_PROFILE_ID BROWSE_GRP_DESC_SVCLASS_ID +#define SERIAL_PORT_PROFILE_ID SERIAL_PORT_SVCLASS_ID +#define LAN_ACCESS_PROFILE_ID LAN_ACCESS_SVCLASS_ID +#define DIALUP_NET_PROFILE_ID DIALUP_NET_SVCLASS_ID +#define IRMC_SYNC_PROFILE_ID IRMC_SYNC_SVCLASS_ID +#define OBEX_OBJPUSH_PROFILE_ID OBEX_OBJPUSH_SVCLASS_ID +#define OBEX_FILETRANS_PROFILE_ID OBEX_FILETRANS_SVCLASS_ID +#define IRMC_SYNC_CMD_PROFILE_ID IRMC_SYNC_CMD_SVCLASS_ID +#define HEADSET_PROFILE_ID HEADSET_SVCLASS_ID +#define CORDLESS_TELEPHONY_PROFILE_ID CORDLESS_TELEPHONY_SVCLASS_ID +#define AUDIO_SOURCE_PROFILE_ID AUDIO_SOURCE_SVCLASS_ID +#define AUDIO_SINK_PROFILE_ID AUDIO_SINK_SVCLASS_ID +#define AV_REMOTE_TARGET_PROFILE_ID AV_REMOTE_TARGET_SVCLASS_ID +#define ADVANCED_AUDIO_PROFILE_ID ADVANCED_AUDIO_SVCLASS_ID +#define AV_REMOTE_PROFILE_ID AV_REMOTE_SVCLASS_ID +#define VIDEO_CONF_PROFILE_ID VIDEO_CONF_SVCLASS_ID +#define INTERCOM_PROFILE_ID INTERCOM_SVCLASS_ID +#define FAX_PROFILE_ID FAX_SVCLASS_ID +#define HEADSET_AGW_PROFILE_ID HEADSET_AGW_SVCLASS_ID +#define WAP_PROFILE_ID WAP_SVCLASS_ID +#define WAP_CLIENT_PROFILE_ID WAP_CLIENT_SVCLASS_ID +#define PANU_PROFILE_ID PANU_SVCLASS_ID +#define NAP_PROFILE_ID NAP_SVCLASS_ID +#define GN_PROFILE_ID GN_SVCLASS_ID +#define DIRECT_PRINTING_PROFILE_ID DIRECT_PRINTING_SVCLASS_ID +#define REFERENCE_PRINTING_PROFILE_ID REFERENCE_PRINTING_SVCLASS_ID +#define IMAGING_PROFILE_ID IMAGING_SVCLASS_ID +#define IMAGING_RESPONDER_PROFILE_ID IMAGING_RESPONDER_SVCLASS_ID +#define IMAGING_ARCHIVE_PROFILE_ID IMAGING_ARCHIVE_SVCLASS_ID +#define IMAGING_REFOBJS_PROFILE_ID IMAGING_REFOBJS_SVCLASS_ID +#define HANDSFREE_PROFILE_ID HANDSFREE_SVCLASS_ID +#define HANDSFREE_AGW_PROFILE_ID HANDSFREE_AGW_SVCLASS_ID +#define DIRECT_PRT_REFOBJS_PROFILE_ID DIRECT_PRT_REFOBJS_SVCLASS_ID +#define REFLECTED_UI_PROFILE_ID REFLECTED_UI_SVCLASS_ID +#define BASIC_PRINTING_PROFILE_ID BASIC_PRINTING_SVCLASS_ID +#define PRINTING_STATUS_PROFILE_ID PRINTING_STATUS_SVCLASS_ID +#define HID_PROFILE_ID HID_SVCLASS_ID +#define HCR_PROFILE_ID HCR_SCAN_SVCLASS_ID +#define HCR_PRINT_PROFILE_ID HCR_PRINT_SVCLASS_ID +#define HCR_SCAN_PROFILE_ID HCR_SCAN_SVCLASS_ID +#define CIP_PROFILE_ID CIP_SVCLASS_ID +#define VIDEO_CONF_GW_PROFILE_ID VIDEO_CONF_GW_SVCLASS_ID +#define UDI_MT_PROFILE_ID UDI_MT_SVCLASS_ID +#define UDI_TA_PROFILE_ID UDI_TA_SVCLASS_ID +#define AV_PROFILE_ID AV_SVCLASS_ID +#define SAP_PROFILE_ID SAP_SVCLASS_ID +#define PBAP_PCE_PROFILE_ID PBAP_PCE_SVCLASS_ID +#define PBAP_PSE_PROFILE_ID PBAP_PSE_SVCLASS_ID +#define PBAP_PROFILE_ID PBAP_SVCLASS_ID +#define PNP_INFO_PROFILE_ID PNP_INFO_SVCLASS_ID +#define GENERIC_NETWORKING_PROFILE_ID GENERIC_NETWORKING_SVCLASS_ID +#define GENERIC_FILETRANS_PROFILE_ID GENERIC_FILETRANS_SVCLASS_ID +#define GENERIC_AUDIO_PROFILE_ID GENERIC_AUDIO_SVCLASS_ID +#define GENERIC_TELEPHONY_PROFILE_ID GENERIC_TELEPHONY_SVCLASS_ID +#define UPNP_PROFILE_ID UPNP_SVCLASS_ID +#define UPNP_IP_PROFILE_ID UPNP_IP_SVCLASS_ID +#define UPNP_PAN_PROFILE_ID UPNP_PAN_SVCLASS_ID +#define UPNP_LAP_PROFILE_ID UPNP_LAP_SVCLASS_ID +#define UPNP_L2CAP_PROFILE_ID UPNP_L2CAP_SVCLASS_ID +#define VIDEO_SOURCE_PROFILE_ID VIDEO_SOURCE_SVCLASS_ID +#define VIDEO_SINK_PROFILE_ID VIDEO_SINK_SVCLASS_ID +#define VIDEO_DISTRIBUTION_PROFILE_ID VIDEO_DISTRIBUTION_SVCLASS_ID +#define MDP_PROFILE_ID MDP_SVCLASS_ID +#define MDP_SOURCE_PROFILE_ID MDP_SROUCE_SVCLASS_ID +#define MDP_SINK_PROFILE_ID MDP_SINK_SVCLASS_ID +#define APPLE_AGENT_PROFILE_ID APPLE_AGENT_SVCLASS_ID + +/* Data Representation */ +enum bt_sdp_data_type { + SDP_DTYPE_NIL = 0 << 3, + SDP_DTYPE_UINT = 1 << 3, + SDP_DTYPE_SINT = 2 << 3, + SDP_DTYPE_UUID = 3 << 3, + SDP_DTYPE_STRING = 4 << 3, + SDP_DTYPE_BOOL = 5 << 3, + SDP_DTYPE_SEQ = 6 << 3, + SDP_DTYPE_ALT = 7 << 3, + SDP_DTYPE_URL = 8 << 3, +}; + +enum bt_sdp_data_size { + SDP_DSIZE_1 = 0, + SDP_DSIZE_2, + SDP_DSIZE_4, + SDP_DSIZE_8, + SDP_DSIZE_16, + SDP_DSIZE_NEXT1, + SDP_DSIZE_NEXT2, + SDP_DSIZE_NEXT4, + SDP_DSIZE_MASK = SDP_DSIZE_NEXT4, +}; + +enum bt_sdp_cmd { + SDP_ERROR_RSP = 0x01, + SDP_SVC_SEARCH_REQ = 0x02, + SDP_SVC_SEARCH_RSP = 0x03, + SDP_SVC_ATTR_REQ = 0x04, + SDP_SVC_ATTR_RSP = 0x05, + SDP_SVC_SEARCH_ATTR_REQ = 0x06, + SDP_SVC_SEARCH_ATTR_RSP = 0x07, +}; + +enum bt_sdp_errorcode { + SDP_INVALID_VERSION = 0x0001, + SDP_INVALID_RECORD_HANDLE = 0x0002, + SDP_INVALID_SYNTAX = 0x0003, + SDP_INVALID_PDU_SIZE = 0x0004, + SDP_INVALID_CSTATE = 0x0005, +}; + +/* + * String identifiers are based on the SDP spec stating that + * "base attribute id of the primary (universal) language must be 0x0100" + * + * Other languages should have their own offset; e.g.: + * #define XXXLangBase yyyy + * #define AttrServiceName_XXX 0x0000+XXXLangBase + */ +#define SDP_PRIMARY_LANG_BASE 0x0100 + +enum bt_sdp_attribute_id { + SDP_ATTR_RECORD_HANDLE = 0x0000, + SDP_ATTR_SVCLASS_ID_LIST = 0x0001, + SDP_ATTR_RECORD_STATE = 0x0002, + SDP_ATTR_SERVICE_ID = 0x0003, + SDP_ATTR_PROTO_DESC_LIST = 0x0004, + SDP_ATTR_BROWSE_GRP_LIST = 0x0005, + SDP_ATTR_LANG_BASE_ATTR_ID_LIST = 0x0006, + SDP_ATTR_SVCINFO_TTL = 0x0007, + SDP_ATTR_SERVICE_AVAILABILITY = 0x0008, + SDP_ATTR_PFILE_DESC_LIST = 0x0009, + SDP_ATTR_DOC_URL = 0x000a, + SDP_ATTR_CLNT_EXEC_URL = 0x000b, + SDP_ATTR_ICON_URL = 0x000c, + SDP_ATTR_ADD_PROTO_DESC_LIST = 0x000d, + + SDP_ATTR_SVCNAME_PRIMARY = SDP_PRIMARY_LANG_BASE + 0, + SDP_ATTR_SVCDESC_PRIMARY = SDP_PRIMARY_LANG_BASE + 1, + SDP_ATTR_SVCPROV_PRIMARY = SDP_PRIMARY_LANG_BASE + 2, + + SDP_ATTR_GROUP_ID = 0x0200, + SDP_ATTR_IP_SUBNET = 0x0200, + + /* SDP */ + SDP_ATTR_VERSION_NUM_LIST = 0x0200, + SDP_ATTR_SVCDB_STATE = 0x0201, + + SDP_ATTR_SERVICE_VERSION = 0x0300, + SDP_ATTR_EXTERNAL_NETWORK = 0x0301, + SDP_ATTR_SUPPORTED_DATA_STORES_LIST = 0x0301, + SDP_ATTR_FAX_CLASS1_SUPPORT = 0x0302, + SDP_ATTR_REMOTE_AUDIO_VOLUME_CONTROL = 0x0302, + SDP_ATTR_FAX_CLASS20_SUPPORT = 0x0303, + SDP_ATTR_SUPPORTED_FORMATS_LIST = 0x0303, + SDP_ATTR_FAX_CLASS2_SUPPORT = 0x0304, + SDP_ATTR_AUDIO_FEEDBACK_SUPPORT = 0x0305, + SDP_ATTR_NETWORK_ADDRESS = 0x0306, + SDP_ATTR_WAP_GATEWAY = 0x0307, + SDP_ATTR_HOMEPAGE_URL = 0x0308, + SDP_ATTR_WAP_STACK_TYPE = 0x0309, + SDP_ATTR_SECURITY_DESC = 0x030a, + SDP_ATTR_NET_ACCESS_TYPE = 0x030b, + SDP_ATTR_MAX_NET_ACCESSRATE = 0x030c, + SDP_ATTR_IP4_SUBNET = 0x030d, + SDP_ATTR_IP6_SUBNET = 0x030e, + SDP_ATTR_SUPPORTED_CAPABILITIES = 0x0310, + SDP_ATTR_SUPPORTED_FEATURES = 0x0311, + SDP_ATTR_SUPPORTED_FUNCTIONS = 0x0312, + SDP_ATTR_TOTAL_IMAGING_DATA_CAPACITY = 0x0313, + SDP_ATTR_SUPPORTED_REPOSITORIES = 0x0314, + + /* PnP Information */ + SDP_ATTR_SPECIFICATION_ID = 0x0200, + SDP_ATTR_VENDOR_ID = 0x0201, + SDP_ATTR_PRODUCT_ID = 0x0202, + SDP_ATTR_VERSION = 0x0203, + SDP_ATTR_PRIMARY_RECORD = 0x0204, + SDP_ATTR_VENDOR_ID_SOURCE = 0x0205, + + /* BT HID */ + SDP_ATTR_DEVICE_RELEASE_NUMBER = 0x0200, + SDP_ATTR_PARSER_VERSION = 0x0201, + SDP_ATTR_DEVICE_SUBCLASS = 0x0202, + SDP_ATTR_COUNTRY_CODE = 0x0203, + SDP_ATTR_VIRTUAL_CABLE = 0x0204, + SDP_ATTR_RECONNECT_INITIATE = 0x0205, + SDP_ATTR_DESCRIPTOR_LIST = 0x0206, + SDP_ATTR_LANG_ID_BASE_LIST = 0x0207, + SDP_ATTR_SDP_DISABLE = 0x0208, + SDP_ATTR_BATTERY_POWER = 0x0209, + SDP_ATTR_REMOTE_WAKEUP = 0x020a, + SDP_ATTR_PROFILE_VERSION = 0x020b, + SDP_ATTR_SUPERVISION_TIMEOUT = 0x020c, + SDP_ATTR_NORMALLY_CONNECTABLE = 0x020d, + SDP_ATTR_BOOT_DEVICE = 0x020e, +}; diff --git a/hw/cbus.c b/hw/cbus.c new file mode 100644 index 0000000..8ae24e0 --- /dev/null +++ b/hw/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 + * + * 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 . + */ + +#include "qemu-common.h" +#include "irq.h" +#include "devices.h" +#include "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 *) qemu_mallocz(sizeof(*s)); + + s->dat_out = dat; + s->cbus.clk = qemu_allocate_irqs(cbus_clk, s, 1)[0]; + s->cbus.dat = qemu_allocate_irqs(cbus_dat, s, 1)[0]; + s->cbus.sel = qemu_allocate_irqs(cbus_sel, s, 1)[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 *) qemu_mallocz(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 *) qemu_mallocz(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/cdrom.c b/hw/cdrom.c new file mode 100644 index 0000000..3b99535 --- /dev/null +++ b/hw/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 "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 */ + cpu_to_be32wu((uint32_t *)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 { + cpu_to_be32wu((uint32_t *)q, nb_sectors); + q += 4; + } + len = q - buf; + cpu_to_be16wu((uint16_t *)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 { + cpu_to_be32wu((uint32_t *)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; + cpu_to_be16wu((uint16_t *)buf, len - 2); + return len; +} diff --git a/hw/cirrus_vga.c b/hw/cirrus_vga.c new file mode 100644 index 0000000..5f45b5d --- /dev/null +++ b/hw/cirrus_vga.c @@ -0,0 +1,3153 @@ +/* + * 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.h" +#include "pc.h" +#include "pci.h" +#include "console.h" +#include "vga_int.h" +#include "kvm.h" +#include "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 + +#define ABS(a) ((signed)(a) > 0 ? a : -a) + +#define BLTUNSAFE(s) \ + ( \ + ( /* check dst is within bounds */ \ + (s)->cirrus_blt_height * ABS((s)->cirrus_blt_dstpitch) \ + + ((s)->cirrus_blt_dstaddr & (s)->cirrus_addr_mask) > \ + (s)->vga.vram_size \ + ) || \ + ( /* check src is within bounds */ \ + (s)->cirrus_blt_height * ABS((s)->cirrus_blt_srcpitch) \ + + ((s)->cirrus_blt_srcaddr & (s)->cirrus_addr_mask) > \ + (s)->vga.vram_size \ + ) \ + ) + +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; + + int cirrus_linear_io_addr; + int cirrus_linear_bitblt_io_addr; + int cirrus_mmio_io_addr; + 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]; + uint32_t hw_cursor_x; + uint32_t hw_cursor_y; + 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; + +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 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; + off_cur &= TARGET_PAGE_MASK; + while (off_cur < off_cur_end) { + cpu_physical_memory_set_dirty(s->vga.vram_offset + off_cur); + off_cur += TARGET_PAGE_SIZE; + } + 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 (BLTUNSAFE(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 (BLTUNSAFE(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) + vga_hw_update(); + + (*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.ds, + 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 (BLTUNSAFE(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 transfered 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) { + /* Thinking about changing bank base? First, drop the dirty bitmap information + * on the current location, otherwise we lose this pointer forever */ + if (s->vga.lfb_vram_mapped) { + target_phys_addr_t base_addr = isa_mem_base + 0xa0000 + bank_index * 0x8000; + cpu_physical_sync_dirty_bitmap(base_addr, base_addr + 0x8000); + } + 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->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->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 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 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 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 + * + * assume TARGET_PAGE_SIZE >= 16 + * + ***************************************/ + +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++; + } + cpu_physical_memory_set_dirty(s->vga.vram_offset + offset); + cpu_physical_memory_set_dirty(s->vga.vram_offset + offset + 7); +} + +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; + } + cpu_physical_memory_set_dirty(s->vga.vram_offset + offset); + cpu_physical_memory_set_dirty(s->vga.vram_offset + offset + 15); +} + +/*************************************** + * + * memory access between 0xa0000-0xbffff + * + ***************************************/ + +static uint32_t cirrus_vga_mem_readb(void *opaque, target_phys_addr_t addr) +{ + CirrusVGAState *s = opaque; + unsigned bank_index; + unsigned bank_offset; + uint32_t val; + + if ((s->vga.sr[0x07] & 0x01) == 0) { + return vga_mem_readb(s, addr); + } + + addr &= 0x1ffff; + + 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 uint32_t cirrus_vga_mem_readw(void *opaque, target_phys_addr_t addr) +{ + uint32_t v; + + v = cirrus_vga_mem_readb(opaque, addr); + v |= cirrus_vga_mem_readb(opaque, addr + 1) << 8; + return v; +} + +static uint32_t cirrus_vga_mem_readl(void *opaque, target_phys_addr_t addr) +{ + uint32_t v; + + v = cirrus_vga_mem_readb(opaque, addr); + v |= cirrus_vga_mem_readb(opaque, addr + 1) << 8; + v |= cirrus_vga_mem_readb(opaque, addr + 2) << 16; + v |= cirrus_vga_mem_readb(opaque, addr + 3) << 24; + return v; +} + +static void cirrus_vga_mem_writeb(void *opaque, target_phys_addr_t addr, + uint32_t mem_value) +{ + CirrusVGAState *s = opaque; + unsigned bank_index; + unsigned bank_offset; + unsigned mode; + + if ((s->vga.sr[0x07] & 0x01) == 0) { + vga_mem_writeb(s, addr, mem_value); + return; + } + + addr &= 0x1ffff; + + 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; + cpu_physical_memory_set_dirty(s->vga.vram_offset + + bank_offset); + } 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 %02x\n", addr, + mem_value); +#endif + } +} + +static void cirrus_vga_mem_writew(void *opaque, target_phys_addr_t addr, uint32_t val) +{ + cirrus_vga_mem_writeb(opaque, addr, val & 0xff); + cirrus_vga_mem_writeb(opaque, addr + 1, (val >> 8) & 0xff); +} + +static void cirrus_vga_mem_writel(void *opaque, target_phys_addr_t addr, uint32_t val) +{ + cirrus_vga_mem_writeb(opaque, addr, val & 0xff); + cirrus_vga_mem_writeb(opaque, addr + 1, (val >> 8) & 0xff); + cirrus_vga_mem_writeb(opaque, addr + 2, (val >> 16) & 0xff); + cirrus_vga_mem_writeb(opaque, addr + 3, (val >> 24) & 0xff); +} + +static CPUReadMemoryFunc * const cirrus_vga_mem_read[3] = { + cirrus_vga_mem_readb, + cirrus_vga_mem_readw, + cirrus_vga_mem_readl, +}; + +static CPUWriteMemoryFunc * const cirrus_vga_mem_write[3] = { + cirrus_vga_mem_writeb, + cirrus_vga_mem_writew, + cirrus_vga_mem_writel, +}; + +/*************************************** + * + * 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->hw_cursor_x || + s->last_hw_cursor_y != s->hw_cursor_y) { + + invalidate_cursor1(s); + + s->last_hw_cursor_size = size; + s->last_hw_cursor_x = s->hw_cursor_x; + s->last_hw_cursor_y = s->hw_cursor_y; + /* compute the real cursor min and max y */ + cirrus_cursor_compute_yrange(s); + invalidate_cursor1(s); + } +} + +static void cirrus_cursor_draw_line(VGACommonState *s1, uint8_t *d1, int scr_y) +{ + CirrusVGAState *s = container_of(s1, CirrusVGAState, vga); + int w, h, bpp, 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->hw_cursor_y || + scr_y >= (s->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->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->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->hw_cursor_x; + if (x1 >= s->vga.last_scr_width) + return; + x2 = s->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 = s->vga.rgb_to_pixel(c6_to_8(palette[0x0 * 3]), + c6_to_8(palette[0x0 * 3 + 1]), + c6_to_8(palette[0x0 * 3 + 2])); + color1 = s->vga.rgb_to_pixel(c6_to_8(palette[0xf * 3]), + c6_to_8(palette[0xf * 3 + 1]), + c6_to_8(palette[0xf * 3 + 2])); + bpp = ((ds_get_bits_per_pixel(s->vga.ds) + 7) >> 3); + d1 += x1 * bpp; + switch(ds_get_bits_per_pixel(s->vga.ds)) { + default: + break; + case 8: + vga_draw_cursor_line_8(d1, src, poffset, w, color0, color1, 0xff); + break; + case 15: + vga_draw_cursor_line_16(d1, src, poffset, w, color0, color1, 0x7fff); + break; + case 16: + vga_draw_cursor_line_16(d1, src, poffset, w, color0, color1, 0xffff); + break; + case 32: + vga_draw_cursor_line_32(d1, src, poffset, w, color0, color1, 0xffffff); + break; + } +} + +/*************************************** + * + * LFB memory access + * + ***************************************/ + +static uint32_t cirrus_linear_readb(void *opaque, target_phys_addr_t addr) +{ + 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 uint32_t cirrus_linear_readw(void *opaque, target_phys_addr_t addr) +{ + uint32_t v; + + v = cirrus_linear_readb(opaque, addr); + v |= cirrus_linear_readb(opaque, addr + 1) << 8; + return v; +} + +static uint32_t cirrus_linear_readl(void *opaque, target_phys_addr_t addr) +{ + uint32_t v; + + v = cirrus_linear_readb(opaque, addr); + v |= cirrus_linear_readb(opaque, addr + 1) << 8; + v |= cirrus_linear_readb(opaque, addr + 2) << 16; + v |= cirrus_linear_readb(opaque, addr + 3) << 24; + return v; +} + +static void cirrus_linear_writeb(void *opaque, target_phys_addr_t addr, + uint32_t val) +{ + 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; + cpu_physical_memory_set_dirty(s->vga.vram_offset + addr); + } 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); + } + } + } +} + +static void cirrus_linear_writew(void *opaque, target_phys_addr_t addr, + uint32_t val) +{ + cirrus_linear_writeb(opaque, addr, val & 0xff); + cirrus_linear_writeb(opaque, addr + 1, (val >> 8) & 0xff); +} + +static void cirrus_linear_writel(void *opaque, target_phys_addr_t addr, + uint32_t val) +{ + cirrus_linear_writeb(opaque, addr, val & 0xff); + cirrus_linear_writeb(opaque, addr + 1, (val >> 8) & 0xff); + cirrus_linear_writeb(opaque, addr + 2, (val >> 16) & 0xff); + cirrus_linear_writeb(opaque, addr + 3, (val >> 24) & 0xff); +} + + +static CPUReadMemoryFunc * const cirrus_linear_read[3] = { + cirrus_linear_readb, + cirrus_linear_readw, + cirrus_linear_readl, +}; + +static CPUWriteMemoryFunc * const cirrus_linear_write[3] = { + cirrus_linear_writeb, + cirrus_linear_writew, + cirrus_linear_writel, +}; + +/*************************************** + * + * system to screen memory access + * + ***************************************/ + + +static uint32_t cirrus_linear_bitblt_readb(void *opaque, target_phys_addr_t addr) +{ + uint32_t ret; + + /* XXX handle bitblt */ + ret = 0xff; + return ret; +} + +static uint32_t cirrus_linear_bitblt_readw(void *opaque, target_phys_addr_t addr) +{ + uint32_t v; + + v = cirrus_linear_bitblt_readb(opaque, addr); + v |= cirrus_linear_bitblt_readb(opaque, addr + 1) << 8; + return v; +} + +static uint32_t cirrus_linear_bitblt_readl(void *opaque, target_phys_addr_t addr) +{ + uint32_t v; + + v = cirrus_linear_bitblt_readb(opaque, addr); + v |= cirrus_linear_bitblt_readb(opaque, addr + 1) << 8; + v |= cirrus_linear_bitblt_readb(opaque, addr + 2) << 16; + v |= cirrus_linear_bitblt_readb(opaque, addr + 3) << 24; + return v; +} + +static void cirrus_linear_bitblt_writeb(void *opaque, target_phys_addr_t addr, + uint32_t val) +{ + 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 void cirrus_linear_bitblt_writew(void *opaque, target_phys_addr_t addr, + uint32_t val) +{ + cirrus_linear_bitblt_writeb(opaque, addr, val & 0xff); + cirrus_linear_bitblt_writeb(opaque, addr + 1, (val >> 8) & 0xff); +} + +static void cirrus_linear_bitblt_writel(void *opaque, target_phys_addr_t addr, + uint32_t val) +{ + cirrus_linear_bitblt_writeb(opaque, addr, val & 0xff); + cirrus_linear_bitblt_writeb(opaque, addr + 1, (val >> 8) & 0xff); + cirrus_linear_bitblt_writeb(opaque, addr + 2, (val >> 16) & 0xff); + cirrus_linear_bitblt_writeb(opaque, addr + 3, (val >> 24) & 0xff); +} + + +static CPUReadMemoryFunc * const cirrus_linear_bitblt_read[3] = { + cirrus_linear_bitblt_readb, + cirrus_linear_bitblt_readw, + cirrus_linear_bitblt_readl, +}; + +static CPUWriteMemoryFunc * const cirrus_linear_bitblt_write[3] = { + cirrus_linear_bitblt_writeb, + cirrus_linear_bitblt_writew, + cirrus_linear_bitblt_writel, +}; + +static void map_linear_vram(CirrusVGAState *s) +{ + if (!s->vga.map_addr && s->vga.lfb_addr && s->vga.lfb_end) { + s->vga.map_addr = s->vga.lfb_addr; + s->vga.map_end = s->vga.lfb_end; + cpu_register_physical_memory(s->vga.map_addr, s->vga.map_end - s->vga.map_addr, s->vga.vram_offset); + } + + if (!s->vga.map_addr) + return; + + s->vga.lfb_vram_mapped = 0; + + if (!(s->cirrus_srcptr != s->cirrus_srcptr_end) + && !((s->vga.sr[0x07] & 0x01) == 0) + && !((s->vga.gr[0x0B] & 0x14) == 0x14) + && !(s->vga.gr[0x0B] & 0x02)) { + + cpu_register_physical_memory(isa_mem_base + 0xa0000, 0x8000, + (s->vga.vram_offset + s->cirrus_bank_base[0]) | IO_MEM_RAM); + cpu_register_physical_memory(isa_mem_base + 0xa8000, 0x8000, + (s->vga.vram_offset + s->cirrus_bank_base[1]) | IO_MEM_RAM); + + s->vga.lfb_vram_mapped = 1; + } + else { + cpu_register_physical_memory(isa_mem_base + 0xa0000, 0x20000, + s->vga.vga_io_memory); + } + + vga_dirty_log_start(&s->vga); +} + +static void unmap_linear_vram(CirrusVGAState *s) +{ + if (s->vga.map_addr && s->vga.lfb_addr && s->vga.lfb_end) { + s->vga.map_addr = s->vga.map_end = 0; + cpu_register_physical_memory(s->vga.lfb_addr, s->vga.vram_size, + s->cirrus_linear_io_addr); + } + cpu_register_physical_memory(isa_mem_base + 0xa0000, 0x20000, + s->vga.vga_io_memory); +} + +/* Compute the memory access functions */ +static void cirrus_update_memory_access(CirrusVGAState *s) +{ + unsigned mode; + + 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); + } + } +} + + +/* I/O ports */ + +static uint32_t cirrus_vga_ioport_read(void *opaque, uint32_t addr) +{ + CirrusVGAState *c = opaque; + VGACommonState *s = &c->vga; + int val, index; + + 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, uint32_t addr, uint32_t val) +{ + CirrusVGAState *c = opaque; + VGACommonState *s = &c->vga; + 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 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%02x\n", s->sr_index, val); +#endif + cirrus_vga_write_sr(c, val); + break; + 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%02x\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%02x\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 uint32_t cirrus_mmio_readb(void *opaque, target_phys_addr_t addr) +{ + CirrusVGAState *s = opaque; + + addr &= CIRRUS_PNPMMIO_SIZE - 1; + + if (addr >= 0x100) { + return cirrus_mmio_blt_read(s, addr - 0x100); + } else { + return cirrus_vga_ioport_read(s, addr + 0x3c0); + } +} + +static uint32_t cirrus_mmio_readw(void *opaque, target_phys_addr_t addr) +{ + uint32_t v; + + v = cirrus_mmio_readb(opaque, addr); + v |= cirrus_mmio_readb(opaque, addr + 1) << 8; + return v; +} + +static uint32_t cirrus_mmio_readl(void *opaque, target_phys_addr_t addr) +{ + uint32_t v; + + v = cirrus_mmio_readb(opaque, addr); + v |= cirrus_mmio_readb(opaque, addr + 1) << 8; + v |= cirrus_mmio_readb(opaque, addr + 2) << 16; + v |= cirrus_mmio_readb(opaque, addr + 3) << 24; + return v; +} + +static void cirrus_mmio_writeb(void *opaque, target_phys_addr_t addr, + uint32_t val) +{ + CirrusVGAState *s = opaque; + + addr &= CIRRUS_PNPMMIO_SIZE - 1; + + if (addr >= 0x100) { + cirrus_mmio_blt_write(s, addr - 0x100, val); + } else { + cirrus_vga_ioport_write(s, addr + 0x3c0, val); + } +} + +static void cirrus_mmio_writew(void *opaque, target_phys_addr_t addr, + uint32_t val) +{ + cirrus_mmio_writeb(opaque, addr, val & 0xff); + cirrus_mmio_writeb(opaque, addr + 1, (val >> 8) & 0xff); +} + +static void cirrus_mmio_writel(void *opaque, target_phys_addr_t addr, + uint32_t val) +{ + cirrus_mmio_writeb(opaque, addr, val & 0xff); + cirrus_mmio_writeb(opaque, addr + 1, (val >> 8) & 0xff); + cirrus_mmio_writeb(opaque, addr + 2, (val >> 16) & 0xff); + cirrus_mmio_writeb(opaque, addr + 3, (val >> 24) & 0xff); +} + + +static CPUReadMemoryFunc * const cirrus_mmio_read[3] = { + cirrus_mmio_readb, + cirrus_mmio_readw, + cirrus_mmio_readl, +}; + +static CPUWriteMemoryFunc * const cirrus_mmio_write[3] = { + cirrus_mmio_writeb, + cirrus_mmio_writew, + cirrus_mmio_writel, +}; + +/* 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, + .minimum_version_id_old = 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(hw_cursor_x, CirrusVGAState), + VMSTATE_UINT32(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, + .minimum_version_id_old = 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; + + /* Win2K seems to assume that the pattern buffer is at 0xff + initially ! */ + memset(s->vga.vram_ptr, 0xff, s->real_vram_size); + + s->cirrus_hidden_dac_lockindex = 5; + s->cirrus_hidden_dac_data = 0; +} + +static void cirrus_init_common(CirrusVGAState * s, int device_id, int is_pci) +{ + 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_write(0x3c0, 16, 1, cirrus_vga_ioport_write, s); + + register_ioport_write(0x3b4, 2, 1, cirrus_vga_ioport_write, s); + register_ioport_write(0x3d4, 2, 1, cirrus_vga_ioport_write, s); + register_ioport_write(0x3ba, 1, 1, cirrus_vga_ioport_write, s); + register_ioport_write(0x3da, 1, 1, cirrus_vga_ioport_write, s); + + register_ioport_read(0x3c0, 16, 1, cirrus_vga_ioport_read, s); + + register_ioport_read(0x3b4, 2, 1, cirrus_vga_ioport_read, s); + register_ioport_read(0x3d4, 2, 1, cirrus_vga_ioport_read, s); + register_ioport_read(0x3ba, 1, 1, cirrus_vga_ioport_read, s); + register_ioport_read(0x3da, 1, 1, cirrus_vga_ioport_read, s); + + s->vga.vga_io_memory = cpu_register_io_memory(cirrus_vga_mem_read, + cirrus_vga_mem_write, s, + DEVICE_LITTLE_ENDIAN); + cpu_register_physical_memory(isa_mem_base + 0x000a0000, 0x20000, + s->vga.vga_io_memory); + qemu_register_coalesced_mmio(isa_mem_base + 0x000a0000, 0x20000); + + /* I/O handler for LFB */ + s->cirrus_linear_io_addr = + cpu_register_io_memory(cirrus_linear_read, cirrus_linear_write, s, + DEVICE_LITTLE_ENDIAN); + + /* I/O handler for LFB */ + s->cirrus_linear_bitblt_io_addr = + cpu_register_io_memory(cirrus_linear_bitblt_read, + cirrus_linear_bitblt_write, s, + DEVICE_LITTLE_ENDIAN); + + /* I/O handler for memory-mapped I/O */ + s->cirrus_mmio_io_addr = + cpu_register_io_memory(cirrus_mmio_read, cirrus_mmio_write, s, + DEVICE_LITTLE_ENDIAN); + + 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); + cirrus_reset(s); +} + +/*************************************** + * + * ISA bus support + * + ***************************************/ + +void isa_cirrus_vga_init(void) +{ + CirrusVGAState *s; + + s = qemu_mallocz(sizeof(CirrusVGAState)); + + vga_common_init(&s->vga, VGA_RAM_SIZE); + cirrus_init_common(s, CIRRUS_ID_CLGD5430, 0); + s->vga.ds = graphic_console_init(s->vga.update, s->vga.invalidate, + s->vga.screen_dump, s->vga.text_update, + &s->vga); + vmstate_register(NULL, 0, &vmstate_cirrus_vga, s); + rom_add_vga(VGABIOS_CIRRUS_FILENAME); + /* XXX ISA-LFB support */ +} + +/*************************************** + * + * PCI bus support + * + ***************************************/ + +static void cirrus_pci_lfb_map(PCIDevice *d, int region_num, + pcibus_t addr, pcibus_t size, int type) +{ + CirrusVGAState *s = &DO_UPCAST(PCICirrusVGAState, dev, d)->cirrus_vga; + + /* XXX: add byte swapping apertures */ + cpu_register_physical_memory(addr, s->vga.vram_size, + s->cirrus_linear_io_addr); + cpu_register_physical_memory(addr + 0x1000000, 0x400000, + s->cirrus_linear_bitblt_io_addr); + + s->vga.map_addr = s->vga.map_end = 0; + s->vga.lfb_addr = addr & TARGET_PAGE_MASK; + s->vga.lfb_end = ((addr + VGA_RAM_SIZE) + TARGET_PAGE_SIZE - 1) & TARGET_PAGE_MASK; + /* account for overflow */ + if (s->vga.lfb_end < addr + VGA_RAM_SIZE) + s->vga.lfb_end = addr + VGA_RAM_SIZE; + + vga_dirty_log_start(&s->vga); +} + +static void cirrus_pci_mmio_map(PCIDevice *d, int region_num, + pcibus_t addr, pcibus_t size, int type) +{ + CirrusVGAState *s = &DO_UPCAST(PCICirrusVGAState, dev, d)->cirrus_vga; + + cpu_register_physical_memory(addr, CIRRUS_PNPMMIO_SIZE, + s->cirrus_mmio_io_addr); +} + +static void pci_cirrus_write_config(PCIDevice *d, + uint32_t address, uint32_t val, int len) +{ + PCICirrusVGAState *pvs = DO_UPCAST(PCICirrusVGAState, dev, d); + CirrusVGAState *s = &pvs->cirrus_vga; + + pci_default_write_config(d, address, val, len); + if (s->vga.map_addr && d->io_regions[0].addr == PCI_BAR_UNMAPPED) + s->vga.map_addr = 0; + cirrus_update_memory_access(s); +} + +static int pci_cirrus_vga_initfn(PCIDevice *dev) +{ + PCICirrusVGAState *d = DO_UPCAST(PCICirrusVGAState, dev, dev); + CirrusVGAState *s = &d->cirrus_vga; + uint8_t *pci_conf = d->dev.config; + int device_id = CIRRUS_ID_CLGD5446; + + /* setup VGA */ + vga_common_init(&s->vga, VGA_RAM_SIZE); + cirrus_init_common(s, device_id, 1); + s->vga.ds = graphic_console_init(s->vga.update, s->vga.invalidate, + s->vga.screen_dump, s->vga.text_update, + &s->vga); + + /* setup PCI */ + pci_config_set_vendor_id(pci_conf, PCI_VENDOR_ID_CIRRUS); + pci_config_set_device_id(pci_conf, device_id); + pci_config_set_class(pci_conf, PCI_CLASS_DISPLAY_VGA); + + /* 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, 0x2000000, + PCI_BASE_ADDRESS_MEM_PREFETCH, cirrus_pci_lfb_map); + if (device_id == CIRRUS_ID_CLGD5446) { + pci_register_bar(&d->dev, 1, CIRRUS_PNPMMIO_SIZE, + PCI_BASE_ADDRESS_SPACE_MEMORY, cirrus_pci_mmio_map); + } + return 0; +} + +void pci_cirrus_vga_init(PCIBus *bus) +{ + pci_create_simple(bus, -1, "cirrus-vga"); +} + +static PCIDeviceInfo cirrus_vga_info = { + .qdev.name = "cirrus-vga", + .qdev.desc = "Cirrus CLGD 54xx VGA", + .qdev.size = sizeof(PCICirrusVGAState), + .qdev.vmsd = &vmstate_pci_cirrus_vga, + .no_hotplug = 1, + .init = pci_cirrus_vga_initfn, + .romfile = VGABIOS_CIRRUS_FILENAME, + .config_write = pci_cirrus_write_config, +}; + +static void cirrus_vga_register(void) +{ + pci_qdev_register(&cirrus_vga_info); +} +device_init(cirrus_vga_register); diff --git a/hw/cirrus_vga_rop.h b/hw/cirrus_vga_rop.h new file mode 100644 index 0000000..9c7bb09 --- /dev/null +++ b/hw/cirrus_vga_rop.h @@ -0,0 +1,208 @@ +/* + * 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 (dstpitch < 0 || srcpitch < 0) { + /* is 0 valid? srcpitch == 0 could be useful */ + 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/cirrus_vga_rop2.h b/hw/cirrus_vga_rop2.h new file mode 100644 index 0000000..d28bcc6 --- /dev/null +++ b/hw/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/cris-boot.c b/hw/cris-boot.c new file mode 100644 index 0000000..2ef17f6 --- /dev/null +++ b/hw/cris-boot.c @@ -0,0 +1,97 @@ +/* + * 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.h" +#include "sysemu.h" +#include "loader.h" +#include "elf.h" +#include "cris-boot.h" + +static void main_cpu_reset(void *opaque) +{ + CPUState *env = opaque; + struct cris_load_info *li; + + li = env->load_info; + + cpu_reset(env); + + 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(CPUState *env, struct cris_load_info *li) +{ + 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, env); +} diff --git a/hw/cris-boot.h b/hw/cris-boot.h new file mode 100644 index 0000000..e9caf8d --- /dev/null +++ b/hw/cris-boot.h @@ -0,0 +1,11 @@ + +struct cris_load_info +{ + const char *image_filename; + const char *cmdline; + int image_size; + + target_phys_addr_t entry; +}; + +void cris_load_image(CPUState *env, struct cris_load_info *li); diff --git a/hw/cris_pic_cpu.c b/hw/cris_pic_cpu.c new file mode 100644 index 0000000..a92d445 --- /dev/null +++ b/hw/cris_pic_cpu.c @@ -0,0 +1,50 @@ +/* + * QEMU CRIS CPU interrupt wrapper logic. + * + * 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.h" +#include "pc.h" +#include "etraxfs.h" + +#define D(x) + +void pic_info(Monitor *mon) +{} +void irq_info(Monitor *mon) +{} + +static void cris_pic_cpu_handler(void *opaque, int irq, int level) +{ + CPUState *env = (CPUState *)opaque; + int type = irq ? CPU_INTERRUPT_NMI : CPU_INTERRUPT_HARD; + + if (level) + cpu_interrupt(env, type); + else + cpu_reset_interrupt(env, type); +} + +qemu_irq *cris_pic_init_cpu(CPUState *env) +{ + return qemu_allocate_irqs(cris_pic_cpu_handler, env, 2); +} diff --git a/hw/cs4231.c b/hw/cs4231.c new file mode 100644 index 0000000..a65b697 --- /dev/null +++ b/hw/cs4231.c @@ -0,0 +1,175 @@ +/* + * 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 "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) + +typedef struct CSState { + SysBusDevice busdev; + 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 = container_of(d, CSState, busdev.qdev); + + 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 uint32_t cs_mem_readl(void *opaque, target_phys_addr_t addr) +{ + 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_writel(void *opaque, target_phys_addr_t addr, uint32_t val) +{ + 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(&s->busdev.qdev); + } + val &= 0x7f; + s->regs[saddr] = val; + break; + default: + s->regs[saddr] = val; + break; + } +} + +static CPUReadMemoryFunc * const cs_mem_read[3] = { + cs_mem_readl, + cs_mem_readl, + cs_mem_readl, +}; + +static CPUWriteMemoryFunc * const cs_mem_write[3] = { + cs_mem_writel, + cs_mem_writel, + cs_mem_writel, +}; + +static const VMStateDescription vmstate_cs4231 = { + .name ="cs4231", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 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) +{ + int io; + CSState *s = FROM_SYSBUS(CSState, dev); + + io = cpu_register_io_memory(cs_mem_read, cs_mem_write, s, + DEVICE_NATIVE_ENDIAN); + sysbus_init_mmio(dev, CS_SIZE, io); + sysbus_init_irq(dev, &s->irq); + + return 0; +} + +static SysBusDeviceInfo cs4231_info = { + .init = cs4231_init1, + .qdev.name = "SUNW,CS4231", + .qdev.size = sizeof(CSState), + .qdev.vmsd = &vmstate_cs4231, + .qdev.reset = cs_reset, + .qdev.props = (Property[]) { + {.name = NULL} + } +}; + +static void cs4231_register_devices(void) +{ + sysbus_register_withprop(&cs4231_info); +} + +device_init(cs4231_register_devices) diff --git a/hw/cs4231a.c b/hw/cs4231a.c new file mode 100644 index 0000000..598f032 --- /dev/null +++ b/hw/cs4231a.c @@ -0,0 +1,686 @@ +/* + * 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.h" +#include "audiodev.h" +#include "audio/audio.h" +#include "isa.h" +#include "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 + +typedef struct CSState { + ISADevice dev; + QEMUSoundCard card; + 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 IO_READ_PROTO(name) \ + static uint32_t name (void *opaque, uint32_t addr) + +#define IO_WRITE_PROTO(name) \ + static void name (void *opaque, uint32_t addr, uint32_t val) + +#define GET_SADDR(addr) (addr & 3) + +#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 cs_reset (void *opaque) +{ + CSState *s = opaque; + + 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); + } +} + +IO_READ_PROTO (cs_read) +{ + CSState *s = opaque; + uint32_t saddr, iaddr, ret; + + saddr = GET_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; +} + +IO_WRITE_PROTO (cs_write) +{ + CSState *s = opaque; + uint32_t saddr, iaddr; + + saddr = GET_SADDR (addr); + + 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, + .minimum_version_id_old = 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 int cs4231a_initfn (ISADevice *dev) +{ + CSState *s = DO_UPCAST (CSState, dev, dev); + int i; + + isa_init_irq (dev, &s->pic, s->irq); + + for (i = 0; i < 4; i++) { + isa_init_ioport(dev, i); + register_ioport_write (s->port + i, 1, 1, cs_write, s); + register_ioport_read (s->port + i, 1, 1, cs_read, s); + } + + DMA_register_channel (s->dma, cs_dma_read, s); + + qemu_register_reset (cs_reset, s); + cs_reset (s); + + AUD_register_card ("cs4231a", &s->card); + return 0; +} + +int cs4231a_init (qemu_irq *pic) +{ + isa_create_simple ("cs4231a"); + return 0; +} + +static ISADeviceInfo cs4231a_info = { + .qdev.name = "cs4231a", + .qdev.desc = "Crystal Semiconductor CS4231A", + .qdev.size = sizeof (CSState), + .qdev.vmsd = &vmstate_cs4231a, + .init = cs4231a_initfn, + .qdev.props = (Property[]) { + DEFINE_PROP_HEX32 ("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_register (void) +{ + isa_qdev_register (&cs4231a_info); +} +device_init (cs4231a_register) diff --git a/hw/cuda.c b/hw/cuda.c new file mode 100644 index 0000000..e4c178d --- /dev/null +++ b/hw/cuda.c @@ -0,0 +1,769 @@ +/* + * 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.h" +#include "ppc_mac.h" +#include "qemu-timer.h" +#include "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 + +typedef struct CUDATimer { + int index; + uint16_t latch; + uint16_t counter_value; /* counter value at load time */ + int64_t load_time; + int64_t next_irq_time; + QEMUTimer *timer; +} CUDATimer; + +typedef struct CUDAState { + /* cuda registers */ + uint8_t b; /* B-side data */ + uint8_t a; /* A-side data */ + uint8_t dirb; /* B-side direction (1=output) */ + uint8_t dira; /* A-side direction (1=output) */ + uint8_t sr; /* Shift register */ + uint8_t acr; /* Auxiliary control register */ + uint8_t pcr; /* Peripheral control register */ + uint8_t ifr; /* Interrupt flag register */ + uint8_t ier; /* Interrupt enable register */ + uint8_t anh; /* A-side data, no handshake */ + + CUDATimer timers[2]; + + uint32_t tick_offset; + + uint8_t last_b; /* last value of B register */ + uint8_t last_acr; /* last value of B register */ + + 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; + +static CUDAState cuda_state; +ADBBusState adb_bus; + +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 unsigned int get_counter(CUDATimer *s) +{ + int64_t d; + unsigned int counter; + + d = muldiv64(qemu_get_clock(vm_clock) - s->load_time, + CUDA_TIMER_FREQ, get_ticks_per_sec()); + 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 = qemu_get_clock(vm_clock); + 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) { + qemu_del_timer(ti->timer); + } else { + ti->next_irq_time = get_next_irq_time(ti, current_time); + qemu_mod_timer(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, target_phys_addr_t 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, target_phys_addr_t 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_get_clock(vm_clock)); + 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_get_clock(vm_clock)); + 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_get_clock(vm_clock)); + 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_get_clock(vm_clock)); + 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(&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); + } + qemu_mod_timer(s->adb_poll_timer, + qemu_get_clock(vm_clock) + + (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) { + qemu_mod_timer(s->adb_poll_timer, + qemu_get_clock(vm_clock) + + (get_ticks_per_sec() / CUDA_ADB_POLL_FREQ)); + } else { + qemu_del_timer(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_get_clock(vm_clock) / 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_get_clock(vm_clock) / 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(&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, target_phys_addr_t addr, uint32_t value) +{ +} + +static void cuda_writel (void *opaque, target_phys_addr_t addr, uint32_t value) +{ +} + +static uint32_t cuda_readw (void *opaque, target_phys_addr_t addr) +{ + return 0; +} + +static uint32_t cuda_readl (void *opaque, target_phys_addr_t addr) +{ + return 0; +} + +static CPUWriteMemoryFunc * const cuda_write[] = { + &cuda_writeb, + &cuda_writew, + &cuda_writel, +}; + +static CPUReadMemoryFunc * const cuda_read[] = { + &cuda_readb, + &cuda_readw, + &cuda_readl, +}; + +static void cuda_save_timer(QEMUFile *f, CUDATimer *s) +{ + qemu_put_be16s(f, &s->latch); + qemu_put_be16s(f, &s->counter_value); + qemu_put_sbe64s(f, &s->load_time); + qemu_put_sbe64s(f, &s->next_irq_time); + if (s->timer) + qemu_put_timer(f, s->timer); +} + +static void cuda_save(QEMUFile *f, void *opaque) +{ + CUDAState *s = (CUDAState *)opaque; + + qemu_put_ubyte(f, s->b); + qemu_put_ubyte(f, s->a); + qemu_put_ubyte(f, s->dirb); + qemu_put_ubyte(f, s->dira); + qemu_put_ubyte(f, s->sr); + qemu_put_ubyte(f, s->acr); + qemu_put_ubyte(f, s->pcr); + qemu_put_ubyte(f, s->ifr); + qemu_put_ubyte(f, s->ier); + qemu_put_ubyte(f, s->anh); + qemu_put_sbe32s(f, &s->data_in_size); + qemu_put_sbe32s(f, &s->data_in_index); + qemu_put_sbe32s(f, &s->data_out_index); + qemu_put_ubyte(f, s->autopoll); + qemu_put_buffer(f, s->data_in, sizeof(s->data_in)); + qemu_put_buffer(f, s->data_out, sizeof(s->data_out)); + qemu_put_be32s(f, &s->tick_offset); + cuda_save_timer(f, &s->timers[0]); + cuda_save_timer(f, &s->timers[1]); +} + +static void cuda_load_timer(QEMUFile *f, CUDATimer *s) +{ + qemu_get_be16s(f, &s->latch); + qemu_get_be16s(f, &s->counter_value); + qemu_get_sbe64s(f, &s->load_time); + qemu_get_sbe64s(f, &s->next_irq_time); + if (s->timer) + qemu_get_timer(f, s->timer); +} + +static int cuda_load(QEMUFile *f, void *opaque, int version_id) +{ + CUDAState *s = (CUDAState *)opaque; + + if (version_id != 1) + return -EINVAL; + + s->b = qemu_get_ubyte(f); + s->a = qemu_get_ubyte(f); + s->dirb = qemu_get_ubyte(f); + s->dira = qemu_get_ubyte(f); + s->sr = qemu_get_ubyte(f); + s->acr = qemu_get_ubyte(f); + s->pcr = qemu_get_ubyte(f); + s->ifr = qemu_get_ubyte(f); + s->ier = qemu_get_ubyte(f); + s->anh = qemu_get_ubyte(f); + qemu_get_sbe32s(f, &s->data_in_size); + qemu_get_sbe32s(f, &s->data_in_index); + qemu_get_sbe32s(f, &s->data_out_index); + s->autopoll = qemu_get_ubyte(f); + qemu_get_buffer(f, s->data_in, sizeof(s->data_in)); + qemu_get_buffer(f, s->data_out, sizeof(s->data_out)); + qemu_get_be32s(f, &s->tick_offset); + cuda_load_timer(f, &s->timers[0]); + cuda_load_timer(f, &s->timers[1]); + + return 0; +} + +static void cuda_reset(void *opaque) +{ + CUDAState *s = opaque; + + 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); +} + +void cuda_init (int *cuda_mem_index, qemu_irq irq) +{ + struct tm tm; + CUDAState *s = &cuda_state; + + s->irq = irq; + + s->timers[0].index = 0; + s->timers[0].timer = qemu_new_timer(vm_clock, cuda_timer1, s); + + s->timers[1].index = 1; + + qemu_get_timedate(&tm, 0); + s->tick_offset = (uint32_t)mktimegm(&tm) + RTC_OFFSET; + + s->adb_poll_timer = qemu_new_timer(vm_clock, cuda_adb_poll, s); + *cuda_mem_index = cpu_register_io_memory(cuda_read, cuda_write, s, + DEVICE_NATIVE_ENDIAN); + register_savevm(NULL, "cuda", -1, 1, cuda_save, cuda_load, s); + qemu_register_reset(cuda_reset, s); +} diff --git a/hw/debugcon.c b/hw/debugcon.c new file mode 100644 index 0000000..5ee6821 --- /dev/null +++ b/hw/debugcon.c @@ -0,0 +1,107 @@ +/* + * 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.h" +#include "qemu-char.h" +#include "isa.h" +#include "pc.h" + +//#define DEBUG_DEBUGCON + +typedef struct DebugconState { + CharDriverState *chr; + uint32_t readback; +} DebugconState; + +typedef struct ISADebugconState { + ISADevice dev; + uint32_t iobase; + DebugconState state; +} ISADebugconState; + +static void debugcon_ioport_write(void *opaque, uint32_t addr, uint32_t val) +{ + DebugconState *s = opaque; + unsigned char ch = val; + +#ifdef DEBUG_DEBUGCON + printf("debugcon: write addr=0x%04x val=0x%02x\n", addr, val); +#endif + + qemu_chr_write(s->chr, &ch, 1); +} + + +static uint32_t debugcon_ioport_read(void *opaque, uint32_t addr) +{ + DebugconState *s = opaque; + +#ifdef DEBUG_DEBUGCON + printf("debugcon: read addr=0x%04x\n", addr); +#endif + + return s->readback; +} + +static void debugcon_init_core(DebugconState *s) +{ + if (!s->chr) { + fprintf(stderr, "Can't create debugcon device, empty char device\n"); + exit(1); + } + + qemu_chr_add_handlers(s->chr, NULL, NULL, NULL, s); +} + +static int debugcon_isa_initfn(ISADevice *dev) +{ + ISADebugconState *isa = DO_UPCAST(ISADebugconState, dev, dev); + DebugconState *s = &isa->state; + + debugcon_init_core(s); + register_ioport_write(isa->iobase, 1, 1, debugcon_ioport_write, s); + register_ioport_read(isa->iobase, 1, 1, debugcon_ioport_read, s); + return 0; +} + +static ISADeviceInfo debugcon_isa_info = { + .qdev.name = "isa-debugcon", + .qdev.size = sizeof(ISADebugconState), + .init = debugcon_isa_initfn, + .qdev.props = (Property[]) { + DEFINE_PROP_HEX32("iobase", ISADebugconState, iobase, 0xe9), + DEFINE_PROP_CHR("chardev", ISADebugconState, state.chr), + DEFINE_PROP_HEX32("readback", ISADebugconState, state.readback, 0xe9), + DEFINE_PROP_END_OF_LIST(), + }, +}; + +static void debugcon_register_devices(void) +{ + isa_qdev_register(&debugcon_isa_info); +} + +device_init(debugcon_register_devices) diff --git a/hw/dec_pci.c b/hw/dec_pci.c new file mode 100644 index 0000000..bf88f2a --- /dev/null +++ b/hw/dec_pci.c @@ -0,0 +1,133 @@ +/* + * 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_pci.h" +#include "sysbus.h" +#include "pci.h" +#include "pci_host.h" +#include "pci_bridge.h" +#include "pci_internals.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 + +typedef struct DECState { + SysBusDevice busdev; + PCIHostState host_state; +} DECState; + +static int dec_map_irq(PCIDevice *pci_dev, int irq_num) +{ + return irq_num; +} + +static int dec_21154_initfn(PCIDevice *dev) +{ + int rc; + + rc = pci_bridge_initfn(dev); + if (rc < 0) { + return rc; + } + + pci_config_set_vendor_id(dev->config, PCI_VENDOR_ID_DEC); + pci_config_set_device_id(dev->config, PCI_DEVICE_ID_DEC_21154); + return 0; +} + +static PCIDeviceInfo dec_21154_pci_bridge_info = { + .qdev.name = "dec-21154-p2p-bridge", + .qdev.desc = "DEC 21154 PCI-PCI bridge", + .qdev.size = sizeof(PCIBridge), + .qdev.vmsd = &vmstate_pci_device, + .qdev.reset = pci_bridge_reset, + .init = dec_21154_initfn, + .exit = pci_bridge_exitfn, + .config_write = pci_bridge_write_config, + .is_bridge = 1, +}; + +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 = DO_UPCAST(PCIBridge, dev, 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_init_device(SysBusDevice *dev) +{ + DECState *s; + int pci_mem_config, pci_mem_data; + + s = FROM_SYSBUS(DECState, dev); + + pci_mem_config = pci_host_conf_register_mmio(&s->host_state, + DEVICE_LITTLE_ENDIAN); + pci_mem_data = pci_host_data_register_mmio(&s->host_state, + DEVICE_LITTLE_ENDIAN); + sysbus_init_mmio(dev, 0x1000, pci_mem_config); + sysbus_init_mmio(dev, 0x1000, pci_mem_data); + return 0; +} + +static int dec_21154_pci_host_init(PCIDevice *d) +{ + /* PCI2PCI bridge same values as PearPC - check this */ + pci_config_set_vendor_id(d->config, PCI_VENDOR_ID_DEC); + pci_config_set_device_id(d->config, PCI_DEVICE_ID_DEC_21154); + pci_set_byte(d->config + PCI_REVISION_ID, 0x02); + pci_config_set_class(d->config, PCI_CLASS_BRIDGE_PCI); + return 0; +} + +static PCIDeviceInfo dec_21154_pci_host_info = { + .qdev.name = "dec-21154", + .qdev.size = sizeof(PCIDevice), + .init = dec_21154_pci_host_init, + .is_bridge = 1, +}; + +static void dec_register_devices(void) +{ + sysbus_register_dev("dec-21154", sizeof(DECState), + pci_dec_21154_init_device); + pci_qdev_register(&dec_21154_pci_host_info); + pci_qdev_register(&dec_21154_pci_bridge_info); +} + +device_init(dec_register_devices) diff --git a/hw/dec_pci.h b/hw/dec_pci.h new file mode 100644 index 0000000..79264ba --- /dev/null +++ b/hw/dec_pci.h @@ -0,0 +1,8 @@ +#ifndef DEC_PCI_H +#define DEC_PCI_H + +#include "qemu-common.h" + +PCIBus *pci_dec_21154_init(PCIBus *parent_bus, int devfn); + +#endif diff --git a/hw/device-hotplug.c b/hw/device-hotplug.c new file mode 100644 index 0000000..8b2ed7a --- /dev/null +++ b/hw/device-hotplug.c @@ -0,0 +1,46 @@ +/* + * QEMU device hotplug helpers + * + * 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.h" +#include "boards.h" +#include "net.h" +#include "blockdev.h" + +DriveInfo *add_init_drive(const char *optstr) +{ + DriveInfo *dinfo; + QemuOpts *opts; + + opts = drive_def(optstr); + if (!opts) + return NULL; + + dinfo = drive_init(opts, current_machine->use_scsi); + if (!dinfo) { + qemu_opts_del(opts); + return NULL; + } + + return dinfo; +} diff --git a/hw/devices.h b/hw/devices.h new file mode 100644 index 0000000..c788373 --- /dev/null +++ b/hw/devices.h @@ -0,0 +1,70 @@ +#ifndef QEMU_DEVICES_H +#define QEMU_DEVICES_H + +/* Devices that have nowhere better to go. */ + +/* smc91c111.c */ +void smc91c111_init(NICInfo *, uint32_t, qemu_irq); + +/* lan9118.c */ +void lan9118_init(NICInfo *, uint32_t, qemu_irq); + +/* tsc210x.c */ +uWireSlave *tsc2102_init(qemu_irq pint); +uWireSlave *tsc2301_init(qemu_irq penirq, qemu_irq kbirq, qemu_irq dav); +I2SCodec *tsc210x_codec(uWireSlave *chip); +uint32_t tsc210x_txrx(void *opaque, uint32_t value, int len); +void tsc210x_set_transform(uWireSlave *chip, + MouseTransformInfo *info); +void tsc210x_key_event(uWireSlave *chip, int key, int down); + +/* tsc2005.c */ +void *tsc2005_init(qemu_irq pintdav); +uint32_t tsc2005_txrx(void *opaque, uint32_t value, int len); +void tsc2005_set_transform(void *opaque, MouseTransformInfo *info); + +/* stellaris_input.c */ +void stellaris_gamepad_init(int n, qemu_irq *irq, const int *keycode); + +/* blizzard.c */ +void *s1d13745_init(qemu_irq gpio_int); +void s1d13745_write(void *opaque, int dc, uint16_t value); +void s1d13745_write_block(void *opaque, int dc, + void *buf, size_t len, int pitch); +uint16_t s1d13745_read(void *opaque, int dc); + +/* cbus.c */ +typedef struct { + qemu_irq clk; + qemu_irq dat; + qemu_irq sel; +} CBus; +CBus *cbus_init(qemu_irq dat_out); +void cbus_attach(CBus *bus, void *slave_opaque); + +void *retu_init(qemu_irq irq, int vilma); +void *tahvo_init(qemu_irq irq, int betty); + +void retu_key_event(void *retu, int state); + +/* tusb6010.c */ +typedef struct TUSBState TUSBState; +TUSBState *tusb6010_init(qemu_irq intr); +int tusb6010_sync_io(TUSBState *s); +int tusb6010_async_io(TUSBState *s); +void tusb6010_power(TUSBState *s, int on); + +/* tc6393xb.c */ +typedef struct TC6393xbState TC6393xbState; +#define TC6393XB_RAM 0x110000 /* amount of ram for Video and USB */ +TC6393xbState *tc6393xb_init(uint32_t base, qemu_irq irq); +void tc6393xb_gpio_out_set(TC6393xbState *s, int line, + qemu_irq handler); +qemu_irq *tc6393xb_gpio_in_get(TC6393xbState *s); +qemu_irq tc6393xb_l3v_get(TC6393xbState *s); + +/* sm501.c */ +void sm501_init(uint32_t base, uint32_t local_mem_bytes, qemu_irq irq, + CharDriverState *chr); + +#endif diff --git a/hw/dma.c b/hw/dma.c new file mode 100644 index 0000000..8a7302a --- /dev/null +++ b/hw/dma.c @@ -0,0 +1,555 @@ +/* + * 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.h" +#include "isa.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; +} 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 uint32_t read_chan (void *opaque, uint32_t nport) +{ + 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, uint32_t nport, uint32_t data) +{ + 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, uint32_t nport, uint32_t data) +{ + struct dma_cont *d = opaque; + int iport, ichan = 0; + + iport = (nport >> d->dshift) & 0x0f; + switch (iport) { + case 0x08: /* command */ + if ((data != 0) && (data & CMD_NOT_SUPPORTED)) { + dolog ("command %#x not supported\n", data); + return; + } + d->command = data; + break; + + case 0x09: + 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 0x0a: /* single mask */ + if (data & 4) + d->mask |= 1 << (data & 3); + else + d->mask &= ~(1 << (data & 3)); + DMA_run(); + break; + + case 0x0b: /* 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 0x0c: /* clear flip flop */ + d->flip_flop = 0; + break; + + case 0x0d: /* reset */ + d->flip_flop = 0; + d->mask = ~0; + d->status = 0; + d->command = 0; + break; + + case 0x0e: /* clear mask for all channels */ + d->mask = 0; + DMA_run(); + break; + + case 0x0f: /* 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 uint32_t read_cont (void *opaque, uint32_t nport) +{ + struct dma_cont *d = opaque; + int iport, val; + + iport = (nport >> d->dshift) & 0x0f; + switch (iport) { + case 0x08: /* status */ + val = d->status; + d->status &= 0xf0; + break; + case 0x0f: /* 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; + + 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; + } + } + } + + 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]; + target_phys_addr_t 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]; + target_phys_addr_t 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, (0x0d << d->dshift), 0); +} + +static int dma_phony_handler (void *opaque, int nchan, int dma_pos, int dma_len) +{ + dolog ("unregistered DMA channel used nchan=%d dma_pos=%d dma_len=%d\n", + nchan, dma_pos, dma_len); + return dma_pos; +} + +/* 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) +{ + static const int page_port_list[] = { 0x1, 0x2, 0x3, 0x7 }; + int i; + + d->dshift = dshift; + d->cpu_request_exit = cpu_request_exit; + for (i = 0; i < 8; i++) { + register_ioport_write (base + (i << dshift), 1, 1, write_chan, d); + register_ioport_read (base + (i << dshift), 1, 1, read_chan, d); + } + for (i = 0; i < ARRAY_SIZE (page_port_list); i++) { + register_ioport_write (page_base + page_port_list[i], 1, 1, + write_page, d); + register_ioport_read (page_base + page_port_list[i], 1, 1, + read_page, d); + if (pageh_base >= 0) { + register_ioport_write (pageh_base + page_port_list[i], 1, 1, + write_pageh, d); + register_ioport_read (pageh_base + page_port_list[i], 1, 1, + read_pageh, d); + } + } + for (i = 0; i < 8; i++) { + register_ioport_write (base + ((i + 8) << dshift), 1, 1, + write_cont, d); + register_ioport_read (base + ((i + 8) << dshift), 1, 1, + read_cont, d); + } + 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, + .minimum_version_id_old = 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, + .minimum_version_id_old = 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/dp8393x.c b/hw/dp8393x.c new file mode 100644 index 0000000..0ef8abe --- /dev/null +++ b/hw/dp8393x.c @@ -0,0 +1,914 @@ +/* + * 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 . + */ + +#include "hw.h" +#include "qemu-timer.h" +#include "net.h" +#include "mips.h" + +//#define DEBUG_SONIC + +/* Calculate CRCs properly on Rx packets */ +#define SONIC_CALCULATE_RXCRC + +#if defined(SONIC_CALCULATE_RXCRC) +/* For crc32 */ +#include +#endif + +#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 + +typedef struct dp8393xState { + /* Hardware */ + int it_shift; + qemu_irq irq; +#ifdef DEBUG_SONIC + int irq_level; +#endif + QEMUTimer *watchdog; + int64_t wt_last_update; + NICConf conf; + NICState *nic; + int mmio_index; + + /* Registers */ + uint8_t cam[16][6]; + uint16_t regs[0x40]; + + /* Temporaries */ + uint8_t tx_buffer[0x10000]; + int loopback_packet; + + /* Memory access */ + void (*memory_rw)(void *opaque, target_phys_addr_t addr, uint8_t *buf, int len, int is_write); + void* mem_opaque; +} 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 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 */ + s->memory_rw(s->mem_opaque, + (s->regs[SONIC_URRA] << 16) | s->regs[SONIC_CDP], + (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 */ + s->memory_rw(s->mem_opaque, + (s->regs[SONIC_URRA] << 16) | s->regs[SONIC_CDP], + (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 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; + s->memory_rw(s->mem_opaque, + (s->regs[SONIC_URRA] << 16) | s->regs[SONIC_RRP], + (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 do_software_reset(dp8393xState *s) +{ + qemu_del_timer(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 set_next_tick(dp8393xState *s) +{ + uint32_t ticks; + int64_t delay; + + if (s->regs[SONIC_CR] & SONIC_CR_STP) { + qemu_del_timer(s->watchdog); + return; + } + + ticks = s->regs[SONIC_WT1] << 16 | s->regs[SONIC_WT0]; + s->wt_last_update = qemu_get_clock(vm_clock); + delay = get_ticks_per_sec() * ticks / 5000000; + qemu_mod_timer(s->watchdog, s->wt_last_update + delay); +} + +static void update_wt_regs(dp8393xState *s) +{ + int64_t elapsed; + uint32_t val; + + if (s->regs[SONIC_CR] & SONIC_CR_STP) { + qemu_del_timer(s->watchdog); + return; + } + + elapsed = s->wt_last_update - qemu_get_clock(vm_clock); + 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; + set_next_tick(s); + +} + +static void do_start_timer(dp8393xState *s) +{ + s->regs[SONIC_CR] &= ~SONIC_CR_STP; + set_next_tick(s); +} + +static void do_stop_timer(dp8393xState *s) +{ + s->regs[SONIC_CR] &= ~SONIC_CR_ST; + update_wt_regs(s); +} + +static void do_receiver_enable(dp8393xState *s) +{ + s->regs[SONIC_CR] &= ~SONIC_CR_RXDIS; +} + +static void do_receiver_disable(dp8393xState *s) +{ + s->regs[SONIC_CR] &= ~SONIC_CR_RXEN; +} + +static void do_transmit_packets(dp8393xState *s) +{ + 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]; + s->memory_rw(s->mem_opaque, + ((s->regs[SONIC_UTDA] << 16) | s->regs[SONIC_TTDA]) + sizeof(uint16_t) * width, + (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; + } + s->memory_rw(s->mem_opaque, + (s->regs[SONIC_TSA1] << 16) | s->regs[SONIC_TSA0], + &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; + s->memory_rw(s->mem_opaque, + ((s->regs[SONIC_UTDA] << 16) | s->regs[SONIC_TTDA]) + sizeof(uint16_t) * (4 + 3 * i) * width, + (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 (s->nic->nc.info->can_receive(&s->nic->nc)) { + s->loopback_packet = 1; + s->nic->nc.info->receive(&s->nic->nc, s->tx_buffer, tx_len); + } + } else { + /* Transmit packet */ + qemu_send_packet(&s->nic->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; + s->memory_rw(s->mem_opaque, + (s->regs[SONIC_UTDA] << 16) | s->regs[SONIC_TTDA], + (uint8_t *)data, size, 1); + + if (!(s->regs[SONIC_CR] & SONIC_CR_HTX)) { + /* Read footer of packet */ + size = sizeof(uint16_t) * width; + s->memory_rw(s->mem_opaque, + ((s->regs[SONIC_UTDA] << 16) | s->regs[SONIC_TTDA]) + sizeof(uint16_t) * (4 + 3 * s->regs[SONIC_TFC]) * width, + (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 do_halt_transmission(dp8393xState *s) +{ + /* Nothing to do */ +} + +static void 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) + do_halt_transmission(s); + if (command & SONIC_CR_TXP) + do_transmit_packets(s); + if (command & SONIC_CR_RXDIS) + do_receiver_disable(s); + if (command & SONIC_CR_RXEN) + do_receiver_enable(s); + if (command & SONIC_CR_STP) + do_stop_timer(s); + if (command & SONIC_CR_ST) + do_start_timer(s); + if (command & SONIC_CR_RST) + do_software_reset(s); + if (command & SONIC_CR_RRRA) + do_read_rra(s); + if (command & SONIC_CR_LCAM) + do_load_cam(s); +} + +static uint16_t read_register(dp8393xState *s, int reg) +{ + uint16_t val = 0; + + switch (reg) { + /* Update data before reading it */ + case SONIC_WT0: + case SONIC_WT1: + 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 write_register(dp8393xState *s, int reg, uint16_t val) +{ + DPRINTF("write 0x%04x to reg %s\n", val, reg_names[reg]); + + switch (reg) { + /* Command register */ + case SONIC_CR: + do_command(s, val);; + 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] = val & 0xbfff; + } else { + DPRINTF("writing to DCR invalid\n"); + } + break; + case SONIC_DCR2: + if (s->regs[SONIC_CR] & SONIC_CR_RST) { + s->regs[reg] = val & 0xf017; + } else { + DPRINTF("writing to DCR2 invalid\n"); + } + break; + /* 12 lower bytes are Read Only */ + case SONIC_TCR: + s->regs[reg] = val & 0xf000; + break; + /* 9 lower bytes are Read Only */ + case SONIC_RCR: + s->regs[reg] = val & 0xffe0; + break; + /* Ignore most significant bit */ + case SONIC_IMR: + s->regs[reg] = val & 0x7fff; + dp8393x_update_irq(s); + break; + /* Clear bits by writing 1 to them */ + case SONIC_ISR: + val &= s->regs[reg]; + s->regs[reg] &= ~val; + if (val & SONIC_ISR_RBE) { + do_read_rra(s); + } + dp8393x_update_irq(s); + break; + /* Ignore least significant bit */ + case SONIC_RSA: + case SONIC_REA: + case SONIC_RRP: + case SONIC_RWP: + s->regs[reg] = val & 0xfffe; + break; + /* Invert written value for some registers */ + case SONIC_CRCT: + case SONIC_FAET: + case SONIC_MPT: + s->regs[reg] = val ^ 0xffff; + break; + /* All other registers have no special contrainst */ + default: + s->regs[reg] = val; + } + + if (reg == SONIC_WT0 || reg == SONIC_WT1) { + set_next_tick(s); + } +} + +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; + set_next_tick(s); + + /* Signal underflow */ + s->regs[SONIC_ISR] |= SONIC_ISR_TC; + dp8393x_update_irq(s); +} + +static uint32_t dp8393x_readw(void *opaque, target_phys_addr_t addr) +{ + dp8393xState *s = opaque; + int reg; + + if ((addr & ((1 << s->it_shift) - 1)) != 0) { + return 0; + } + + reg = addr >> s->it_shift; + return read_register(s, reg); +} + +static uint32_t dp8393x_readb(void *opaque, target_phys_addr_t addr) +{ + uint16_t v = dp8393x_readw(opaque, addr & ~0x1); + return (v >> (8 * (addr & 0x1))) & 0xff; +} + +static uint32_t dp8393x_readl(void *opaque, target_phys_addr_t addr) +{ + uint32_t v; + v = dp8393x_readw(opaque, addr); + v |= dp8393x_readw(opaque, addr + 2) << 16; + return v; +} + +static void dp8393x_writew(void *opaque, target_phys_addr_t addr, uint32_t val) +{ + dp8393xState *s = opaque; + int reg; + + if ((addr & ((1 << s->it_shift) - 1)) != 0) { + return; + } + + reg = addr >> s->it_shift; + + write_register(s, reg, (uint16_t)val); +} + +static void dp8393x_writeb(void *opaque, target_phys_addr_t addr, uint32_t val) +{ + uint16_t old_val = dp8393x_readw(opaque, addr & ~0x1); + + switch (addr & 3) { + case 0: + val = val | (old_val & 0xff00); + break; + case 1: + val = (val << 8) | (old_val & 0x00ff); + break; + } + dp8393x_writew(opaque, addr & ~0x1, val); +} + +static void dp8393x_writel(void *opaque, target_phys_addr_t addr, uint32_t val) +{ + dp8393x_writew(opaque, addr, val & 0xffff); + dp8393x_writew(opaque, addr + 2, (val >> 16) & 0xffff); +} + +static CPUReadMemoryFunc * const dp8393x_read[3] = { + dp8393x_readb, + dp8393x_readw, + dp8393x_readl, +}; + +static CPUWriteMemoryFunc * const dp8393x_write[3] = { + dp8393x_writeb, + dp8393x_writew, + dp8393x_writel, +}; + +static int nic_can_receive(VLANClientState *nc) +{ + dp8393xState *s = DO_UPCAST(NICState, nc, nc)->opaque; + + if (!(s->regs[SONIC_CR] & SONIC_CR_RXEN)) + return 0; + if (s->regs[SONIC_ISR] & SONIC_ISR_RBE) + return 0; + return 1; +} + +static int receive_filter(dp8393xState *s, const uint8_t * buf, int size) +{ + static const uint8_t bcast[] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; + int i; + + /* Check for runt packet (remember that checksum is not there) */ + if (size < 64 - 4) { + return (s->regs[SONIC_RCR] & SONIC_RCR_RNT) ? 0 : -1; + } + + /* 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 nic_receive(VLANClientState *nc, const uint8_t * buf, size_t size) +{ + dp8393xState *s = DO_UPCAST(NICState, nc, nc)->opaque; + 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 = 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; + s->memory_rw(s->mem_opaque, address, (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 */ +#ifdef SONIC_CALCULATE_RXCRC + checksum = cpu_to_le32(crc32(0, buf, rx_len)); +#else + checksum = 0; +#endif + + /* 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]; + s->memory_rw(s->mem_opaque, address, (uint8_t*)buf, rx_len, 1); + address += rx_len; + s->memory_rw(s->mem_opaque, address, (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; + s->memory_rw(s->mem_opaque, (s->regs[SONIC_URDA] << 16) | s->regs[SONIC_CRDA], (uint8_t *)data, size, 1); + + /* Move to next descriptor */ + size = sizeof(uint16_t) * width; + s->memory_rw(s->mem_opaque, + ((s->regs[SONIC_URDA] << 16) | s->regs[SONIC_CRDA]) + sizeof(uint16_t) * 5 * width, + (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 */ + s->memory_rw(s->mem_opaque, + ((s->regs[SONIC_URDA] << 16) | s->regs[SONIC_CRDA]) + sizeof(uint16_t) * 6 * width, + (uint8_t *)data, size, 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 */ + do_read_rra(s); + } + } + + /* Done */ + dp8393x_update_irq(s); + + return size; +} + +static void nic_reset(void *opaque) +{ + dp8393xState *s = opaque; + qemu_del_timer(s->watchdog); + + 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 void nic_cleanup(VLANClientState *nc) +{ + dp8393xState *s = DO_UPCAST(NICState, nc, nc)->opaque; + + cpu_unregister_io_memory(s->mmio_index); + + qemu_del_timer(s->watchdog); + qemu_free_timer(s->watchdog); + + qemu_free(s); +} + +static NetClientInfo net_dp83932_info = { + .type = NET_CLIENT_TYPE_NIC, + .size = sizeof(NICState), + .can_receive = nic_can_receive, + .receive = nic_receive, + .cleanup = nic_cleanup, +}; + +void dp83932_init(NICInfo *nd, target_phys_addr_t base, int it_shift, + qemu_irq irq, void* mem_opaque, + void (*memory_rw)(void *opaque, target_phys_addr_t addr, uint8_t *buf, int len, int is_write)) +{ + dp8393xState *s; + + qemu_check_nic_model(nd, "dp83932"); + + s = qemu_mallocz(sizeof(dp8393xState)); + + s->mem_opaque = mem_opaque; + s->memory_rw = memory_rw; + s->it_shift = it_shift; + s->irq = irq; + s->watchdog = qemu_new_timer(vm_clock, dp8393x_watchdog, s); + s->regs[SONIC_SR] = 0x0004; /* only revision recognized by Linux */ + + memcpy(s->conf.macaddr.a, nd->macaddr, sizeof(s->conf.macaddr)); + s->conf.vlan = nd->vlan; + s->conf.peer = nd->netdev; + + s->nic = qemu_new_nic(&net_dp83932_info, &s->conf, nd->model, nd->name, s); + + qemu_format_nic_info_str(&s->nic->nc, s->conf.macaddr.a); + qemu_register_reset(nic_reset, s); + nic_reset(s); + + s->mmio_index = cpu_register_io_memory(dp8393x_read, dp8393x_write, s, + DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(base, 0x40 << it_shift, s->mmio_index); +} diff --git a/hw/ds1225y.c b/hw/ds1225y.c new file mode 100644 index 0000000..b1c5232 --- /dev/null +++ b/hw/ds1225y.c @@ -0,0 +1,182 @@ +/* + * 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.h" +#include "mips.h" +#include "nvram.h" + +//#define DEBUG_NVRAM + +typedef struct ds1225y_t +{ + uint32_t chip_size; + QEMUFile *file; + uint8_t *contents; + uint8_t protection; +} ds1225y_t; + + +static uint32_t nvram_readb (void *opaque, target_phys_addr_t addr) +{ + ds1225y_t *s = opaque; + uint32_t val; + + val = s->contents[addr]; + +#ifdef DEBUG_NVRAM + printf("nvram: read 0x%x at " TARGET_FMT_lx "\n", val, addr); +#endif + return val; +} + +static uint32_t nvram_readw (void *opaque, target_phys_addr_t addr) +{ + uint32_t v; + v = nvram_readb(opaque, addr); + v |= nvram_readb(opaque, addr + 1) << 8; + return v; +} + +static uint32_t nvram_readl (void *opaque, target_phys_addr_t addr) +{ + uint32_t v; + v = nvram_readb(opaque, addr); + v |= nvram_readb(opaque, addr + 1) << 8; + v |= nvram_readb(opaque, addr + 2) << 16; + v |= nvram_readb(opaque, addr + 3) << 24; + return v; +} + +static void nvram_writeb (void *opaque, target_phys_addr_t addr, uint32_t val) +{ + ds1225y_t *s = opaque; + +#ifdef DEBUG_NVRAM + printf("nvram: write 0x%x at " TARGET_FMT_lx "\n", val, addr); +#endif + + s->contents[addr] = val & 0xff; + if (s->file) { + qemu_fseek(s->file, addr, SEEK_SET); + qemu_put_byte(s->file, (int)val); + qemu_fflush(s->file); + } +} + +static void nvram_writew (void *opaque, target_phys_addr_t addr, uint32_t val) +{ + nvram_writeb(opaque, addr, val & 0xff); + nvram_writeb(opaque, addr + 1, (val >> 8) & 0xff); +} + +static void nvram_writel (void *opaque, target_phys_addr_t addr, uint32_t val) +{ + nvram_writeb(opaque, addr, val & 0xff); + nvram_writeb(opaque, addr + 1, (val >> 8) & 0xff); + nvram_writeb(opaque, addr + 2, (val >> 16) & 0xff); + nvram_writeb(opaque, addr + 3, (val >> 24) & 0xff); +} + +static void nvram_writeb_protected (void *opaque, target_phys_addr_t addr, uint32_t val) +{ + ds1225y_t *s = opaque; + + if (s->protection != 7) { +#ifdef DEBUG_NVRAM + printf("nvram: prevent write of 0x%x at " TARGET_FMT_lx "\n", val, addr); +#endif + return; + } + + nvram_writeb(opaque, addr, val); +} + +static void nvram_writew_protected (void *opaque, target_phys_addr_t addr, uint32_t val) +{ + nvram_writeb_protected(opaque, addr, val & 0xff); + nvram_writeb_protected(opaque, addr + 1, (val >> 8) & 0xff); +} + +static void nvram_writel_protected (void *opaque, target_phys_addr_t addr, uint32_t val) +{ + nvram_writeb_protected(opaque, addr, val & 0xff); + nvram_writeb_protected(opaque, addr + 1, (val >> 8) & 0xff); + nvram_writeb_protected(opaque, addr + 2, (val >> 16) & 0xff); + nvram_writeb_protected(opaque, addr + 3, (val >> 24) & 0xff); +} + +static CPUReadMemoryFunc * const nvram_read[] = { + &nvram_readb, + &nvram_readw, + &nvram_readl, +}; + +static CPUWriteMemoryFunc * const nvram_write[] = { + &nvram_writeb, + &nvram_writew, + &nvram_writel, +}; + +static CPUWriteMemoryFunc * const nvram_write_protected[] = { + &nvram_writeb_protected, + &nvram_writew_protected, + &nvram_writel_protected, +}; + +/* Initialisation routine */ +void *ds1225y_init(target_phys_addr_t mem_base, const char *filename) +{ + ds1225y_t *s; + int mem_indexRW, mem_indexRP; + QEMUFile *file; + + s = qemu_mallocz(sizeof(ds1225y_t)); + s->chip_size = 0x2000; /* Fixed for ds1225y chip: 8 KiB */ + s->contents = qemu_mallocz(s->chip_size); + s->protection = 7; + + /* Read current file */ + file = qemu_fopen(filename, "rb"); + if (file) { + /* Read nvram contents */ + qemu_get_buffer(file, s->contents, s->chip_size); + qemu_fclose(file); + } + s->file = qemu_fopen(filename, "wb"); + if (s->file) { + /* Write back contents, as 'wb' mode cleaned the file */ + qemu_put_buffer(s->file, s->contents, s->chip_size); + qemu_fflush(s->file); + } + + /* Read/write memory */ + mem_indexRW = cpu_register_io_memory(nvram_read, nvram_write, s, + DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(mem_base, s->chip_size, mem_indexRW); + /* Read/write protected memory */ + mem_indexRP = cpu_register_io_memory(nvram_read, nvram_write_protected, s, + DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(mem_base + s->chip_size, s->chip_size, mem_indexRP); + return s; +} diff --git a/hw/ds1338.c b/hw/ds1338.c new file mode 100644 index 0000000..6f5ae5e --- /dev/null +++ b/hw/ds1338.c @@ -0,0 +1,132 @@ +/* + * MAXIM DS1338 I2C RTC+NVRAM + * + * Copyright (c) 2009 CodeSourcery. + * Written by Paul Brook + * + * This code is licenced under the GNU GPL v2. + */ + +#include "i2c.h" + +typedef struct { + i2c_slave i2c; + time_t offset; + struct tm now; + uint8_t nvram[56]; + int ptr; + int addr_byte; +} DS1338State; + +static void ds1338_event(i2c_slave *i2c, enum i2c_event event) +{ + DS1338State *s = FROM_I2C_SLAVE(DS1338State, i2c); + + switch (event) { + case I2C_START_RECV: + qemu_get_timedate(&s->now, s->offset); + s->nvram[0] = to_bcd(s->now.tm_sec); + s->nvram[1] = to_bcd(s->now.tm_min); + if (s->nvram[2] & 0x40) { + s->nvram[2] = (to_bcd((s->now.tm_hour % 12)) + 1) | 0x40; + if (s->now.tm_hour >= 12) { + s->nvram[2] |= 0x20; + } + } else { + s->nvram[2] = to_bcd(s->now.tm_hour); + } + s->nvram[3] = to_bcd(s->now.tm_wday) + 1; + s->nvram[4] = to_bcd(s->now.tm_mday); + s->nvram[5] = to_bcd(s->now.tm_mon) + 1; + s->nvram[6] = to_bcd(s->now.tm_year - 100); + break; + case I2C_START_SEND: + s->addr_byte = 1; + break; + default: + break; + } +} + +static int ds1338_recv(i2c_slave *i2c) +{ + DS1338State *s = FROM_I2C_SLAVE(DS1338State, i2c); + uint8_t res; + + res = s->nvram[s->ptr]; + s->ptr = (s->ptr + 1) & 0xff; + return res; +} + +static int ds1338_send(i2c_slave *i2c, uint8_t data) +{ + DS1338State *s = FROM_I2C_SLAVE(DS1338State, i2c); + if (s->addr_byte) { + s->ptr = data; + s->addr_byte = 0; + return 0; + } + s->nvram[s->ptr - 8] = data; + if (data < 8) { + qemu_get_timedate(&s->now, s->offset); + switch(data) { + case 0: + /* TODO: Implement CH (stop) bit. */ + s->now.tm_sec = from_bcd(data & 0x7f); + break; + case 1: + s->now.tm_min = from_bcd(data & 0x7f); + break; + case 2: + if (data & 0x40) { + if (data & 0x20) { + data = from_bcd(data & 0x4f) + 11; + } else { + data = from_bcd(data & 0x1f) - 1; + } + } else { + data = from_bcd(data); + } + s->now.tm_hour = data; + break; + case 3: + s->now.tm_wday = from_bcd(data & 7) - 1; + break; + case 4: + s->now.tm_mday = from_bcd(data & 0x3f); + break; + case 5: + s->now.tm_mon = from_bcd(data & 0x1f) - 1; + case 6: + s->now.tm_year = from_bcd(data) + 100; + break; + case 7: + /* Control register. Currently ignored. */ + break; + } + s->offset = qemu_timedate_diff(&s->now); + } + s->ptr = (s->ptr + 1) & 0xff; + return 0; +} + +static int ds1338_init(i2c_slave *i2c) +{ + return 0; +} + +static I2CSlaveInfo ds1338_info = { + .qdev.name = "ds1338", + .qdev.size = sizeof(DS1338State), + .init = ds1338_init, + .event = ds1338_event, + .recv = ds1338_recv, + .send = ds1338_send, +}; + +static void ds1338_register_devices(void) +{ + i2c_register_slave(&ds1338_info); +} + +device_init(ds1338_register_devices) diff --git a/hw/dummy_m68k.c b/hw/dummy_m68k.c new file mode 100644 index 0000000..61efb39 --- /dev/null +++ b/hw/dummy_m68k.c @@ -0,0 +1,80 @@ +/* + * Dummy board with just RAM and CPU for use as an ISS. + * + * Copyright (c) 2007 CodeSourcery. + * + * This code is licenced under the GPL + */ + +#include "hw.h" +#include "sysemu.h" +#include "boards.h" +#include "loader.h" +#include "elf.h" + +#define KERNEL_LOAD_ADDR 0x10000 + +/* Board init. */ + +static void dummy_m68k_init(ram_addr_t ram_size, + const char *boot_device, + const char *kernel_filename, const char *kernel_cmdline, + const char *initrd_filename, const char *cpu_model) +{ + CPUState *env; + int kernel_size; + uint64_t elf_entry; + target_phys_addr_t entry; + + if (!cpu_model) + cpu_model = "cfv4e"; + env = cpu_init(cpu_model); + if (!env) { + fprintf(stderr, "Unable to find m68k CPU definition\n"); + exit(1); + } + + /* Initialize CPU registers. */ + env->vbr = 0; + + /* RAM at address zero */ + cpu_register_physical_memory(0, ram_size, + qemu_ram_alloc(NULL, "dummy_m68k.ram", ram_size) | IO_MEM_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); + } + 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/e1000.c b/hw/e1000.c new file mode 100644 index 0000000..af101bd --- /dev/null +++ b/hw/e1000.c @@ -0,0 +1,1183 @@ +/* + * 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 . + */ + + +#include "hw.h" +#include "pci.h" +#include "net.h" +#include "net/checksum.h" +#include "loader.h" +#include "sysemu.h" + +#include "e1000_hw.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_NOTYET, +}; +#define DBGBIT(x) (1<>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), +}; + +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 +}; + +static void +ioport_map(PCIDevice *pci_dev, int region_num, pcibus_t addr, + pcibus_t size, int type) +{ + DBGOUT(IO, "e1000_ioport_map addr=0x%04"FMT_PCIBUS + " size=0x%08"FMT_PCIBUS"\n", addr, size); +} + +static void +set_interrupt_cause(E1000State *s, int index, uint32_t val) +{ + if (val) + val |= E1000_ICR_INT_ASSERTED; + s->mac_reg[ICR] = val; + s->mac_reg[ICS] = val; + qemu_set_irq(s->dev.irq[0], (s->mac_reg[IMS] & s->mac_reg[ICR]) != 0); +} + +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 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 +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]); +} + +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 + s->phy_reg[addr] = data; + } + s->mac_reg[MDIC] = val | E1000_MDIC_READY; + 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); + cpu_to_be16wu((uint16_t *)(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_cpup((uint16_t *)(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 +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 + cpu_to_be16wu((uint16_t *)(tp->data+css+2), + tp->size - css); + cpu_to_be16wu((uint16_t *)(tp->data+css+4), + be16_to_cpup((uint16_t *)(tp->data+css+4))+frames); + } else // IPv6 + cpu_to_be16wu((uint16_t *)(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; + cpu_to_be32wu((uint32_t *)(tp->data+css+4), // seq + be32_to_cpupu((uint32_t *)(tp->data+css+4))+sofar); + if (tp->paylen - sofar > tp->mss) + tp->data[css + 13] &= ~9; // PSH, FIN + } else // UDP + cpu_to_be16wu((uint16_t *)(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); + cpu_to_be16wu(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); + qemu_send_packet(&s->nic->nc, tp->vlan, tp->size + 4); + } else + qemu_send_packet(&s->nic->nc, 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) +{ + 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, hdr = 0; + uint64_t addr; + struct e1000_context_desc *xp = (struct e1000_context_desc *)dp; + struct e1000_tx *tp = &s->tx; + + 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 + 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; + cpu_to_be16wu((uint16_t *)(tp->vlan_header), + le16_to_cpup((uint16_t *)(s->mac_reg + VET))); + cpu_to_be16wu((uint16_t *)(tp->vlan_header + 2), + le16_to_cpu(dp->upper.fields.special)); + } + + addr = le64_to_cpu(dp->buffer_addr); + if (tp->tse && tp->cptse) { + hdr = tp->hdr_len; + msh = hdr + tp->mss; + do { + bytes = split_size; + if (tp->size + bytes > msh) + bytes = msh - tp->size; + cpu_physical_memory_read(addr, tp->data + tp->size, bytes); + if ((sz = tp->size + bytes) >= hdr && tp->size < hdr) + memmove(tp->header, tp->data, hdr); + tp->size = sz; + addr += bytes; + if (sz == msh) { + xmit_seg(s); + memmove(tp->data, tp->header, hdr); + tp->size = hdr; + } + } while (split_size -= bytes); + } else if (!tp->tse && tp->cptse) { + // context descriptor TSE is not set, while data descriptor TSE is set + DBGOUT(TXERR, "TCP segmentaion Error\n"); + } else { + cpu_physical_memory_read(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 < hdr)) + 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(target_phys_addr_t base, struct e1000_tx_desc *dp) +{ + 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); + cpu_physical_memory_write(base + ((char *)&dp->upper - (char *)dp), + (void *)&dp->upper, sizeof(dp->upper)); + return E1000_ICR_TXDW; +} + +static void +start_xmit(E1000State *s) +{ + target_phys_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 = ((uint64_t)s->mac_reg[TDBAH] << 32) + s->mac_reg[TDBAL] + + sizeof(struct e1000_tx_desc) * s->mac_reg[TDH]; + cpu_physical_memory_read(base, (void *)&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(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(VLANClientState *nc) +{ + E1000State *s = DO_UPCAST(NICState, nc, nc)->opaque; + uint32_t old_status = s->mac_reg[STATUS]; + + if (nc->link_down) + s->mac_reg[STATUS] &= ~E1000_STATUS_LU; + else + s->mac_reg[STATUS] |= E1000_STATUS_LU; + + if (s->mac_reg[STATUS] != old_status) + set_ics(s, 0, E1000_ICR_LSC); +} + +static int +e1000_can_receive(VLANClientState *nc) +{ + E1000State *s = DO_UPCAST(NICState, nc, nc)->opaque; + + return (s->mac_reg[RCTL] & E1000_RCTL_EN); +} + +static ssize_t +e1000_receive(VLANClientState *nc, const uint8_t *buf, size_t size) +{ + E1000State *s = DO_UPCAST(NICState, nc, nc)->opaque; + struct e1000_rx_desc desc; + target_phys_addr_t base; + unsigned int n, rdt; + uint32_t rdh_start; + uint16_t vlan_special = 0; + uint8_t vlan_status = 0, vlan_offset = 0; + uint8_t min_buf[MIN_BUF_SIZE]; + + if (!(s->mac_reg[RCTL] & E1000_RCTL_EN)) + return -1; + + /* 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); + } + + if (size > s->rxbuf_size) { + DBGOUT(RX, "packet too large for buffers (%lu > %d)\n", + (unsigned long)size, s->rxbuf_size); + return -1; + } + + if (!receive_filter(s, buf, size)) + return size; + + if (vlan_enabled(s) && is_vlan_packet(s, buf)) { + vlan_special = cpu_to_le16(be16_to_cpup((uint16_t *)(buf + 14))); + memmove((uint8_t *)buf + 4, buf, 12); + vlan_status = E1000_RXD_STAT_VP; + vlan_offset = 4; + size -= 4; + } + + rdh_start = s->mac_reg[RDH]; + do { + if (s->mac_reg[RDH] == s->mac_reg[RDT] && s->check_rxov) { + set_ics(s, 0, E1000_ICS_RXO); + return -1; + } + base = ((uint64_t)s->mac_reg[RDBAH] << 32) + s->mac_reg[RDBAL] + + sizeof(desc) * s->mac_reg[RDH]; + cpu_physical_memory_read(base, (void *)&desc, sizeof(desc)); + desc.special = vlan_special; + desc.status |= (vlan_status | E1000_RXD_STAT_DD); + if (desc.buffer_addr) { + cpu_physical_memory_write(le64_to_cpu(desc.buffer_addr), + (void *)(buf + vlan_offset), size); + desc.length = cpu_to_le16(size + fcs_len(s)); + desc.status |= E1000_RXD_STAT_EOP|E1000_RXD_STAT_IXSM; + } else { // as per intel docs; skip descriptors with null buf addr + DBGOUT(RX, "Null RX descriptor!!\n"); + } + cpu_physical_memory_write(base, (void *)&desc, sizeof(desc)); + + if (++s->mac_reg[RDH] * sizeof(desc) >= s->mac_reg[RDLEN]) + s->mac_reg[RDH] = 0; + s->check_rxov = 1; + /* 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.buffer_addr == 0); + + s->mac_reg[GPRC]++; + s->mac_reg[TPR]++; + /* TOR - Total Octets Received: + * This register includes bytes received in a packet from the field through the 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 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) +{ + s->mac_reg[index] = val; +} + +static void +set_rdt(E1000State *s, int index, uint32_t val) +{ + s->check_rxov = 0; + s->mac_reg[index] = val & 0xffff; +} + +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), + + [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, + [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_writel(void *opaque, target_phys_addr_t addr, uint32_t val) +{ + 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%04x\n", index<<2, val); + } else { + DBGOUT(UNKNOWN, "MMIO unknown write addr=0x%08x,val=0x%08x\n", + index<<2, val); + } +} + +static void +e1000_mmio_writew(void *opaque, target_phys_addr_t addr, uint32_t val) +{ + // emulate hw without byte enables: no RMW + e1000_mmio_writel(opaque, addr & ~3, + (val & 0xffff) << (8*(addr & 3))); +} + +static void +e1000_mmio_writeb(void *opaque, target_phys_addr_t addr, uint32_t val) +{ + // emulate hw without byte enables: no RMW + e1000_mmio_writel(opaque, addr & ~3, + (val & 0xff) << (8*(addr & 3))); +} + +static uint32_t +e1000_mmio_readl(void *opaque, target_phys_addr_t addr) +{ + 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 uint32_t +e1000_mmio_readb(void *opaque, target_phys_addr_t addr) +{ + return ((e1000_mmio_readl(opaque, addr & ~3)) >> + (8 * (addr & 3))) & 0xff; +} + +static uint32_t +e1000_mmio_readw(void *opaque, target_phys_addr_t addr) +{ + return ((e1000_mmio_readl(opaque, addr & ~3)) >> + (8 * (addr & 3))) & 0xffff; +} + +static bool is_version_1(void *opaque, int version_id) +{ + return version_id == 1; +} + +static const VMStateDescription vmstate_e1000 = { + .name = "e1000", + .version_id = 2, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField []) { + VMSTATE_PCI_DEVICE(dev, 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() + } +}; + +static const uint16_t e1000_eeprom_template[64] = { + 0x0000, 0x0000, 0x0000, 0x0000, 0xffff, 0x0000, 0x0000, 0x0000, + 0x3000, 0x1000, 0x6403, E1000_DEVID, 0x8086, E1000_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, +}; + +static const uint16_t phy_reg_init[] = { + [PHY_CTRL] = 0x1140, [PHY_STATUS] = 0x796d, // link initially up + [PHY_ID1] = 0x141, [PHY_ID2] = PHY_ID2_INIT, + [PHY_1000T_CTRL] = 0x0e00, [M88E1000_PHY_SPEC_CTRL] = 0x360, + [M88E1000_EXT_PHY_SPEC_CTRL] = 0x0d60, [PHY_AUTONEG_ADV] = 0xde1, + [PHY_LP_ABILITY] = 0x1e0, [PHY_1000T_STATUS] = 0x3c00, + [M88E1000_PHY_SPEC_STATUS] = 0xac00, +}; + +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, +}; + +/* PCI interface */ + +static CPUWriteMemoryFunc * const e1000_mmio_write[] = { + e1000_mmio_writeb, e1000_mmio_writew, e1000_mmio_writel +}; + +static CPUReadMemoryFunc * const e1000_mmio_read[] = { + e1000_mmio_readb, e1000_mmio_readw, e1000_mmio_readl +}; + +static void +e1000_mmio_map(PCIDevice *pci_dev, int region_num, + pcibus_t addr, pcibus_t size, int type) +{ + E1000State *d = DO_UPCAST(E1000State, dev, pci_dev); + int i; + const uint32_t excluded_regs[] = { + E1000_MDIC, E1000_ICR, E1000_ICS, E1000_IMS, + E1000_IMC, E1000_TCTL, E1000_TDT, PNPMMIO_SIZE + }; + + + DBGOUT(MMIO, "e1000_mmio_map addr=0x%08"FMT_PCIBUS" 0x%08"FMT_PCIBUS"\n", + addr, size); + + cpu_register_physical_memory(addr, PNPMMIO_SIZE, d->mmio_index); + qemu_register_coalesced_mmio(addr, excluded_regs[0]); + + for (i = 0; excluded_regs[i] != PNPMMIO_SIZE; i++) + qemu_register_coalesced_mmio(addr + excluded_regs[i] + 4, + excluded_regs[i + 1] - + excluded_regs[i] - 4); +} + +static void +e1000_cleanup(VLANClientState *nc) +{ + E1000State *s = DO_UPCAST(NICState, nc, nc)->opaque; + + s->nic = NULL; +} + +static int +pci_e1000_uninit(PCIDevice *dev) +{ + E1000State *d = DO_UPCAST(E1000State, dev, dev); + + cpu_unregister_io_memory(d->mmio_index); + qemu_del_vlan_client(&d->nic->nc); + return 0; +} + +static void e1000_reset(void *opaque) +{ + E1000State *d = opaque; + + memset(d->phy_reg, 0, sizeof d->phy_reg); + memmove(d->phy_reg, phy_reg_init, sizeof phy_reg_init); + 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); +} + +static NetClientInfo net_e1000_info = { + .type = NET_CLIENT_TYPE_NIC, + .size = sizeof(NICState), + .can_receive = e1000_can_receive, + .receive = e1000_receive, + .cleanup = e1000_cleanup, + .link_status_changed = e1000_set_link_status, +}; + +static int pci_e1000_init(PCIDevice *pci_dev) +{ + E1000State *d = DO_UPCAST(E1000State, dev, pci_dev); + uint8_t *pci_conf; + uint16_t checksum = 0; + int i; + uint8_t *macaddr; + + pci_conf = d->dev.config; + + pci_config_set_vendor_id(pci_conf, PCI_VENDOR_ID_INTEL); + pci_config_set_device_id(pci_conf, E1000_DEVID); + /* TODO: we have no capabilities, so why is this bit set? */ + pci_set_word(pci_conf + PCI_STATUS, PCI_STATUS_CAP_LIST); + pci_conf[PCI_REVISION_ID] = 0x03; + pci_config_set_class(pci_conf, PCI_CLASS_NETWORK_ETHERNET); + /* TODO: RST# value should be 0, PCI spec 6.2.4 */ + pci_conf[PCI_CACHE_LINE_SIZE] = 0x10; + + /* TODO: RST# value should be 0 if programmable, PCI spec 6.2.4 */ + pci_conf[PCI_INTERRUPT_PIN] = 1; // interrupt pin 0 + + d->mmio_index = cpu_register_io_memory(e1000_mmio_read, + e1000_mmio_write, d, DEVICE_LITTLE_ENDIAN); + + pci_register_bar(&d->dev, 0, PNPMMIO_SIZE, + PCI_BASE_ADDRESS_SPACE_MEMORY, e1000_mmio_map); + + pci_register_bar(&d->dev, 1, IOPORT_SIZE, + PCI_BASE_ADDRESS_SPACE_IO, ioport_map); + + 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]; + 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, + d->dev.qdev.info->name, d->dev.qdev.id, d); + + qemu_format_nic_info_str(&d->nic->nc, macaddr); + + add_boot_device_path(d->conf.bootindex, &pci_dev->qdev, "/ethernet-phy@0"); + + return 0; +} + +static void qdev_e1000_reset(DeviceState *dev) +{ + E1000State *d = DO_UPCAST(E1000State, dev.qdev, dev); + e1000_reset(d); +} + +static PCIDeviceInfo e1000_info = { + .qdev.name = "e1000", + .qdev.desc = "Intel Gigabit Ethernet", + .qdev.size = sizeof(E1000State), + .qdev.reset = qdev_e1000_reset, + .qdev.vmsd = &vmstate_e1000, + .init = pci_e1000_init, + .exit = pci_e1000_uninit, + .romfile = "pxe-e1000.bin", + .qdev.props = (Property[]) { + DEFINE_NIC_PROPERTIES(E1000State, conf), + DEFINE_PROP_END_OF_LIST(), + } +}; + +static void e1000_register_devices(void) +{ + pci_qdev_register(&e1000_info); +} + +device_init(e1000_register_devices) diff --git a/hw/e1000_hw.h b/hw/e1000_hw.h new file mode 100644 index 0000000..9bd8a4b --- /dev/null +++ b/hw/e1000_hw.h @@ -0,0 +1,864 @@ +/******************************************************************************* + + 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 . + + The full GNU General Public License is included in this distribution in + the file called "COPYING". + + Contact Information: + Linux NICS + e1000-devel Mailing List + 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 + +/* 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 /* Managment 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 */ + +/* 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/ecc.c b/hw/ecc.c new file mode 100644 index 0000000..a75408b --- /dev/null +++ b/hw/ecc.c @@ -0,0 +1,88 @@ +/* + * Calculate Error-correcting Codes. Used by NAND Flash controllers + * (not by NAND chips). + * + * Copyright (c) 2006 Openedhand Ltd. + * Written by Andrzej Zaborowski + * + * This code is licensed under the GNU GPL v2. + */ + +#include "hw.h" +#include "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, + .minimum_version_id_old = 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/eccmemctl.c b/hw/eccmemctl.c new file mode 100644 index 0000000..2bda87b --- /dev/null +++ b/hw/eccmemctl.c @@ -0,0 +1,332 @@ +/* + * 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 "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) + +typedef struct ECCState { + SysBusDevice busdev; + qemu_irq irq; + uint32_t regs[ECC_NREGS]; + uint8_t diag[ECC_DIAG_SIZE]; + uint32_t version; +} ECCState; + +static void ecc_mem_writel(void *opaque, target_phys_addr_t addr, uint32_t val) +{ + 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 uint32_t ecc_mem_readl(void *opaque, target_phys_addr_t addr) +{ + 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 CPUReadMemoryFunc * const ecc_mem_read[3] = { + NULL, + NULL, + ecc_mem_readl, +}; + +static CPUWriteMemoryFunc * const ecc_mem_write[3] = { + NULL, + NULL, + ecc_mem_writel, +}; + +static void ecc_diag_mem_writeb(void *opaque, target_phys_addr_t addr, + uint32_t val) +{ + ECCState *s = opaque; + + trace_ecc_diag_mem_writeb(addr, val); + s->diag[addr & ECC_DIAG_MASK] = val; +} + +static uint32_t ecc_diag_mem_readb(void *opaque, target_phys_addr_t addr) +{ + ECCState *s = opaque; + uint32_t ret = s->diag[(int)addr]; + + trace_ecc_diag_mem_readb(addr, ret); + return ret; +} + +static CPUReadMemoryFunc * const ecc_diag_mem_read[3] = { + ecc_diag_mem_readb, + NULL, + NULL, +}; + +static CPUWriteMemoryFunc * const ecc_diag_mem_write[3] = { + ecc_diag_mem_writeb, + NULL, + NULL, +}; + +static const VMStateDescription vmstate_ecc = { + .name ="ECC", + .version_id = 3, + .minimum_version_id = 3, + .minimum_version_id_old = 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 = container_of(d, ECCState, busdev.qdev); + + 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) +{ + int ecc_io_memory; + ECCState *s = FROM_SYSBUS(ECCState, dev); + + sysbus_init_irq(dev, &s->irq); + s->regs[0] = s->version; + ecc_io_memory = cpu_register_io_memory(ecc_mem_read, ecc_mem_write, s, + DEVICE_NATIVE_ENDIAN); + sysbus_init_mmio(dev, ECC_SIZE, ecc_io_memory); + + if (s->version == ECC_MCC) { // SS-600MP only + ecc_io_memory = cpu_register_io_memory(ecc_diag_mem_read, + ecc_diag_mem_write, s, + DEVICE_NATIVE_ENDIAN); + sysbus_init_mmio(dev, ECC_DIAG_SIZE, ecc_io_memory); + } + + return 0; +} + +static SysBusDeviceInfo ecc_info = { + .init = ecc_init1, + .qdev.name = "eccmemctl", + .qdev.size = sizeof(ECCState), + .qdev.vmsd = &vmstate_ecc, + .qdev.reset = ecc_reset, + .qdev.props = (Property[]) { + DEFINE_PROP_HEX32("version", ECCState, version, -1), + DEFINE_PROP_END_OF_LIST(), + } +}; + + +static void ecc_register_devices(void) +{ + sysbus_register_withprop(&ecc_info); +} + +device_init(ecc_register_devices) diff --git a/hw/eepro100.c b/hw/eepro100.c new file mode 100644 index 0000000..edf48f6 --- /dev/null +++ b/hw/eepro100.c @@ -0,0 +1,2066 @@ +/* + * QEMU i8255x (PRO100) emulation + * + * Copyright (C) 2006-2010 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 . + * + * Tested features (i82559): + * PXE boot (i386) ok + * Linux networking (i386) ok + * + * Untested: + * non-i386 platforms + * 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 /* offsetof */ +#include "hw.h" +#include "pci.h" +#include "net.h" +#include "eeprom93xx.h" +#include "sysemu.h" + +#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 BIT(n) (1 << (n)) +#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 { + PCIDeviceInfo pci; + uint32_t device; + uint16_t device_id; + uint8_t revision; + 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. */ +enum speedo_offsets { + 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. */ +}; + +/* 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; + char packet[MAX_ETH_FRAME_SIZE + 4]; +} 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]; + int mmio_index; + 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. */ + uint32_t region[3]; /* PCI region addresses */ + uint16_t mdimem[32]; + eeprom_t *eeprom; + uint32_t device; /* device variant */ + uint32_t pointer; + /* (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; + + /* Configuration bytes. */ + uint8_t configuration[22]; + + /* Data in mem is always in the byte order of the controller (le). */ + uint8_t mem[PCI_MEM_SIZE]; + /* 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, +}; + +/* XXX: optimize */ +static void stl_le_phys(target_phys_addr_t addr, uint32_t val) +{ + val = cpu_to_le32(val); + cpu_physical_memory_write(addr, (const uint8_t *)&val, sizeof(val)); +} + +#define POLYNOMIAL 0x04c11db6 + +/* From FreeBSD */ +/* XXX: optimize */ +static unsigned 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; +} + +#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")); + qemu_irq_lower(s->dev.irq[0]); + s->int_stat = 0; + } +} + +static void enable_interrupt(EEPRO100State * s) +{ + if (!s->int_stat) { + TRACE(INT, logout("interrupt enabled\n")); + qemu_irq_raise(s->dev.irq[0]); + 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 *e100_device) +{ + uint32_t device = s->device; + uint8_t *pci_conf = s->dev.config; + + TRACE(OTHER, logout("%p\n", s)); + + /* PCI Vendor ID */ + pci_config_set_vendor_id(pci_conf, PCI_VENDOR_ID_INTEL); + /* PCI Device ID */ + pci_config_set_device_id(pci_conf, e100_device->device_id); + /* PCI Status */ + pci_set_word(pci_conf + PCI_STATUS, PCI_STATUS_DEVSEL_MEDIUM | + PCI_STATUS_FAST_BACK); + /* PCI Revision ID */ + pci_config_set_revision(pci_conf, e100_device->revision); + pci_config_set_class(pci_conf, PCI_CLASS_NETWORK_ETHERNET); + /* 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 = e100_device->stats_size; + s->has_extended_tcb_support = e100_device->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: + break; + case i82559C: +#if EEPROM_SIZE > 0 + pci_set_word(pci_conf + PCI_SUBSYSTEM_VENDOR_ID, PCI_VENDOR_ID_INTEL); + pci_set_word(pci_conf + PCI_SUBSYSTEM_ID, 0x0040); +#endif + 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 (e100_device->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)); + uint32_t val = BIT(21); + memcpy(&s->mem[SCBCtrlMDI], &val, sizeof(val)); + + 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!!! + */ + cpu_physical_memory_write(s->statsaddr, + (uint8_t *) & s->statistics, s->stats_size); + stl_le_phys(s->statsaddr + 0, s->statistics.tx_good_frames); + stl_le_phys(s->statsaddr + 36, s->statistics.rx_good_frames); + stl_le_phys(s->statsaddr + 48, s->statistics.rx_resource_errors); + stl_le_phys(s->statsaddr + 60, s->statistics.rx_short_frame_errors); +#if 0 + stw_le_phys(s->statsaddr + 76, s->statistics.xmt_tco_frames); + stw_le_phys(s->statsaddr + 78, s->statistics.rcv_tco_frames); + missing("CU dump statistical counters"); +#endif +} + +static void read_cb(EEPRO100State *s) +{ + cpu_physical_memory_read(s->cb_address, (uint8_t *) &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_phys(tbd_address); + uint16_t tx_buffer_size = lduw_phys(tbd_address + 4); +#if 0 + uint16_t tx_buffer_el = lduw_phys(tbd_address + 6); +#endif + 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); + cpu_physical_memory_read(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_phys(tbd_address); + uint16_t tx_buffer_size = lduw_phys(tbd_address + 4); + uint16_t tx_buffer_el = lduw_phys(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); + cpu_physical_memory_read(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_phys(tbd_address); + uint16_t tx_buffer_size = lduw_phys(tbd_address + 4); + uint16_t tx_buffer_el = lduw_phys(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); + cpu_physical_memory_read(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(&s->nic->nc, 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]; + cpu_physical_memory_read(s->cb_address + 10 + i, multicast_addr, 6); + TRACE(OTHER, logout("multicast entry %s\n", nic_dump(multicast_addr, 6))); + unsigned mcast_idx = compute_mcast_idx(multicast_addr); + assert(mcast_idx < 64); + s->mult[mcast_idx >> 3] |= (1 << (mcast_idx & 7)); + } +} + +static void action_command(EEPRO100State *s) +{ + 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 + 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: + cpu_physical_memory_read(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: + cpu_physical_memory_read(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_phys(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 = s->pointer; + 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 = s->pointer; + TRACE(OTHER, logout("val=0x%02x (status address)\n", val)); + break; + case CU_SHOWSTATS: + /* Dump statistical counters. */ + TRACE(OTHER, logout("val=0x%02x (dump stats)\n", val)); + dump_statistics(s); + stl_le_phys(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 = s->pointer; + 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_phys(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 = s->pointer; + 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 = s->pointer; + 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; + memcpy(&val, &s->mem[SCBeeprom], sizeof(val)); + 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 unwriteable 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); +} + +static void eepro100_write_pointer(EEPRO100State * s, uint32_t val) +{ + s->pointer = le32_to_cpu(val); + TRACE(OTHER, logout("val=0x%08x\n", val)); +} + +/***************************************************************************** + * + * 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; + memcpy(&val, &s->mem[0x10], sizeof(val)); + +#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) +{ + 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"); + data = s->mdimem[reg]; + 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] = data; + } 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; + memcpy(&s->mem[0x10], &val, sizeof(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) +{ + val = le32_to_cpu(val); + 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; + cpu_physical_memory_read(address, (uint8_t *) & data, sizeof(data)); + data.st_sign = 0xffffffff; + data.st_result = 0; + cpu_physical_memory_write(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)) { + memcpy(&val, &s->mem[addr], sizeof(val)); + } + + 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 SCBpmdr: /* Power Management Driver Register */ + val = 0; + 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)) { + memcpy(&val, &s->mem[addr], sizeof(val)); + } + + 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; + 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)) { + memcpy(&val, &s->mem[addr], sizeof(val)); + } + + switch (addr) { + case SCBStatus: + TRACE(OTHER, logout("addr=%s val=0x%08x\n", regname(addr), val)); + break; + case SCBPointer: +#if 0 + val = eepro100_read_pointer(s); +#endif + 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 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)) { + memcpy(&s->mem[addr], &val, sizeof(val)); + } + + TRACE(OTHER, logout("addr=%s val=0x%02x\n", regname(addr), val)); + + switch (addr) { + case SCBStatus: + break; + case SCBAck: + eepro100_acknowledge(s); + break; + case SCBCmd: + eepro100_write_command(s, val); + break; + case SCBIntmask: + if (val & BIT(1)) { + eepro100_swi_interrupt(s); + } + eepro100_interrupt(s, 0); + break; + case SCBPort + 3: + 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: + eepro100_write_eeprom(s->eeprom, val); + 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)) { + memcpy(&s->mem[addr], &val, sizeof(val)); + } + + TRACE(OTHER, logout("addr=%s val=0x%04x\n", regname(addr), val)); + + switch (addr) { + case SCBStatus: + s->mem[SCBAck] = (val >> 8); + eepro100_acknowledge(s); + break; + case SCBCmd: + eepro100_write_command(s, val); + eepro100_write1(s, SCBIntmask, val >> 8); + break; + case SCBeeprom: + eepro100_write_eeprom(s->eeprom, val); + 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)) { + memcpy(&s->mem[addr], &val, sizeof(val)); + } + + switch (addr) { + case SCBPointer: + eepro100_write_pointer(s, val); + break; + case SCBPort: + TRACE(OTHER, logout("addr=%s val=0x%08x\n", regname(addr), val)); + eepro100_write_port(s, val); + break; + case SCBCtrlMDI: + eepro100_write_mdi(s, val); + break; + default: + logout("addr=%s val=0x%08x\n", regname(addr), val); + missing("unknown longword write"); + } +} + +/***************************************************************************** + * + * Port mapped I/O. + * + ****************************************************************************/ + +static uint32_t ioport_read1(void *opaque, uint32_t addr) +{ + EEPRO100State *s = opaque; +#if 0 + logout("addr=%s\n", regname(addr)); +#endif + return eepro100_read1(s, addr - s->region[1]); +} + +static uint32_t ioport_read2(void *opaque, uint32_t addr) +{ + EEPRO100State *s = opaque; + return eepro100_read2(s, addr - s->region[1]); +} + +static uint32_t ioport_read4(void *opaque, uint32_t addr) +{ + EEPRO100State *s = opaque; + return eepro100_read4(s, addr - s->region[1]); +} + +static void ioport_write1(void *opaque, uint32_t addr, uint32_t val) +{ + EEPRO100State *s = opaque; +#if 0 + logout("addr=%s val=0x%02x\n", regname(addr), val); +#endif + eepro100_write1(s, addr - s->region[1], val); +} + +static void ioport_write2(void *opaque, uint32_t addr, uint32_t val) +{ + EEPRO100State *s = opaque; + eepro100_write2(s, addr - s->region[1], val); +} + +static void ioport_write4(void *opaque, uint32_t addr, uint32_t val) +{ + EEPRO100State *s = opaque; + eepro100_write4(s, addr - s->region[1], val); +} + +/***********************************************************/ +/* PCI EEPRO100 definitions */ + +static void pci_map(PCIDevice * pci_dev, int region_num, + pcibus_t addr, pcibus_t size, int type) +{ + EEPRO100State *s = DO_UPCAST(EEPRO100State, dev, pci_dev); + + TRACE(OTHER, logout("region %d, addr=0x%08"FMT_PCIBUS", " + "size=0x%08"FMT_PCIBUS", type=%d\n", + region_num, addr, size, type)); + + assert(region_num == 1); + register_ioport_write(addr, size, 1, ioport_write1, s); + register_ioport_read(addr, size, 1, ioport_read1, s); + register_ioport_write(addr, size, 2, ioport_write2, s); + register_ioport_read(addr, size, 2, ioport_read2, s); + register_ioport_write(addr, size, 4, ioport_write4, s); + register_ioport_read(addr, size, 4, ioport_read4, s); + + s->region[region_num] = addr; +} + +/***************************************************************************** + * + * Memory mapped I/O. + * + ****************************************************************************/ + +static void pci_mmio_writeb(void *opaque, target_phys_addr_t addr, uint32_t val) +{ + EEPRO100State *s = opaque; +#if 0 + logout("addr=%s val=0x%02x\n", regname(addr), val); +#endif + eepro100_write1(s, addr, val); +} + +static void pci_mmio_writew(void *opaque, target_phys_addr_t addr, uint32_t val) +{ + EEPRO100State *s = opaque; +#if 0 + logout("addr=%s val=0x%02x\n", regname(addr), val); +#endif + eepro100_write2(s, addr, val); +} + +static void pci_mmio_writel(void *opaque, target_phys_addr_t addr, uint32_t val) +{ + EEPRO100State *s = opaque; +#if 0 + logout("addr=%s val=0x%02x\n", regname(addr), val); +#endif + eepro100_write4(s, addr, val); +} + +static uint32_t pci_mmio_readb(void *opaque, target_phys_addr_t addr) +{ + EEPRO100State *s = opaque; +#if 0 + logout("addr=%s\n", regname(addr)); +#endif + return eepro100_read1(s, addr); +} + +static uint32_t pci_mmio_readw(void *opaque, target_phys_addr_t addr) +{ + EEPRO100State *s = opaque; +#if 0 + logout("addr=%s\n", regname(addr)); +#endif + return eepro100_read2(s, addr); +} + +static uint32_t pci_mmio_readl(void *opaque, target_phys_addr_t addr) +{ + EEPRO100State *s = opaque; +#if 0 + logout("addr=%s\n", regname(addr)); +#endif + return eepro100_read4(s, addr); +} + +static CPUWriteMemoryFunc * const pci_mmio_write[] = { + pci_mmio_writeb, + pci_mmio_writew, + pci_mmio_writel +}; + +static CPUReadMemoryFunc * const pci_mmio_read[] = { + pci_mmio_readb, + pci_mmio_readw, + pci_mmio_readl +}; + +static void pci_mmio_map(PCIDevice * pci_dev, int region_num, + pcibus_t addr, pcibus_t size, int type) +{ + EEPRO100State *s = DO_UPCAST(EEPRO100State, dev, pci_dev); + + TRACE(OTHER, logout("region %d, addr=0x%08"FMT_PCIBUS", " + "size=0x%08"FMT_PCIBUS", type=%d\n", + region_num, addr, size, type)); + + assert(region_num == 0 || region_num == 2); + + /* Map control / status registers and flash. */ + cpu_register_physical_memory(addr, size, s->mmio_index); + s->region[region_num] = addr; +} + +static int nic_can_receive(VLANClientState *nc) +{ + EEPRO100State *s = DO_UPCAST(NICState, nc, nc)->opaque; + TRACE(RXTX, logout("%p\n", s)); + return get_ru_state(s) == ru_ready; +#if 0 + return !eepro100_buffer_full(s); +#endif +} + +static ssize_t nic_receive(VLANClientState *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 = DO_UPCAST(NICState, nc, nc)->opaque; + uint16_t rfd_status = 0xa000; + static const uint8_t broadcast_macaddr[6] = + { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; + + if (s->configuration[8] & 0x80) { + /* CSMA is disabled. */ + logout("%p received while CSMA is disabled\n", s); + return -1; + } 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++; +#if 0 + 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 = 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; + cpu_physical_memory_read(s->ru_base + s->ru_offset, (uint8_t *) & rx, + offsetof(eepro100_rx_t, packet)); + 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 (size < 64) { + rfd_status |= 0x0080; + } + 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_phys(s->ru_base + s->ru_offset + offsetof(eepro100_rx_t, status), + rfd_status); + stw_phys(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 + cpu_physical_memory_write(s->ru_base + s->ru_offset + + offsetof(eepro100_rx_t, packet), 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_suspended); + } + 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, + .minimum_version_id_old = 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(pointer, EEPRO100State), + 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 nic_cleanup(VLANClientState *nc) +{ + EEPRO100State *s = DO_UPCAST(NICState, nc, nc)->opaque; + + s->nic = NULL; +} + +static int pci_nic_uninit(PCIDevice *pci_dev) +{ + EEPRO100State *s = DO_UPCAST(EEPRO100State, dev, pci_dev); + + cpu_unregister_io_memory(s->mmio_index); + vmstate_unregister(&pci_dev->qdev, s->vmstate, s); + eeprom93xx_free(&pci_dev->qdev, s->eeprom); + qemu_del_vlan_client(&s->nic->nc); + return 0; +} + +static NetClientInfo net_eepro100_info = { + .type = NET_CLIENT_TYPE_NIC, + .size = sizeof(NICState), + .can_receive = nic_can_receive, + .receive = nic_receive, + .cleanup = nic_cleanup, +}; + +static int e100_nic_init(PCIDevice *pci_dev) +{ + EEPRO100State *s = DO_UPCAST(EEPRO100State, dev, pci_dev); + E100PCIDeviceInfo *e100_device = DO_UPCAST(E100PCIDeviceInfo, pci.qdev, + pci_dev->qdev.info); + + TRACE(OTHER, logout("\n")); + + s->device = e100_device->device; + + e100_pci_reset(s, e100_device); + + /* 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 */ + s->mmio_index = + cpu_register_io_memory(pci_mmio_read, pci_mmio_write, s, + DEVICE_NATIVE_ENDIAN); + + pci_register_bar(&s->dev, 0, PCI_MEM_SIZE, + PCI_BASE_ADDRESS_SPACE_MEMORY | + PCI_BASE_ADDRESS_MEM_PREFETCH, pci_mmio_map); + pci_register_bar(&s->dev, 1, PCI_IO_SIZE, PCI_BASE_ADDRESS_SPACE_IO, + pci_map); + pci_register_bar(&s->dev, 2, PCI_FLASH_SIZE, PCI_BASE_ADDRESS_SPACE_MEMORY, + pci_mmio_map); + + qemu_macaddr_default_if_unset(&s->conf.macaddr); + logout("macaddr: %s\n", nic_dump(&s->conf.macaddr.a[0], 6)); + assert(s->region[1] == 0); + + nic_reset(s); + + s->nic = qemu_new_nic(&net_eepro100_info, &s->conf, + pci_dev->qdev.info->name, pci_dev->qdev.id, s); + + qemu_format_nic_info_str(&s->nic->nc, s->conf.macaddr.a); + TRACE(OTHER, logout("%s\n", s->nic->nc.info_str)); + + qemu_register_reset(nic_reset, s); + + s->vmstate = qemu_malloc(sizeof(vmstate_eepro100)); + memcpy(s->vmstate, &vmstate_eepro100, sizeof(vmstate_eepro100)); + s->vmstate->name = s->nic->nc.model; + vmstate_register(&pci_dev->qdev, -1, s->vmstate, s); + + add_boot_device_path(s->conf.bootindex, &pci_dev->qdev, "/ethernet-phy@0"); + + return 0; +} + +static E100PCIDeviceInfo e100_devices[] = { + { + .pci.qdev.name = "i82550", + .pci.qdev.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, + },{ + .pci.qdev.name = "i82551", + .pci.qdev.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, + },{ + .pci.qdev.name = "i82557a", + .pci.qdev.desc = "Intel i82557A Ethernet", + .device = i82557A, + .device_id = PCI_DEVICE_ID_INTEL_82557, + .revision = 0x01, + .power_management = false, + },{ + .pci.qdev.name = "i82557b", + .pci.qdev.desc = "Intel i82557B Ethernet", + .device = i82557B, + .device_id = PCI_DEVICE_ID_INTEL_82557, + .revision = 0x02, + .power_management = false, + },{ + .pci.qdev.name = "i82557c", + .pci.qdev.desc = "Intel i82557C Ethernet", + .device = i82557C, + .device_id = PCI_DEVICE_ID_INTEL_82557, + .revision = 0x03, + .power_management = false, + },{ + .pci.qdev.name = "i82558a", + .pci.qdev.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, + },{ + .pci.qdev.name = "i82558b", + .pci.qdev.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, + },{ + .pci.qdev.name = "i82559a", + .pci.qdev.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, + },{ + .pci.qdev.name = "i82559b", + .pci.qdev.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, + },{ + .pci.qdev.name = "i82559c", + .pci.qdev.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, + .stats_size = 80, + .has_extended_tcb_support = true, + .power_management = true, + },{ + .pci.qdev.name = "i82559er", + .pci.qdev.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, + },{ + .pci.qdev.name = "i82562", + .pci.qdev.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. */ + .pci.qdev.name = "i82801", + .pci.qdev.desc = "Intel i82801 Ethernet", + .device = i82801, + .device_id = 0x2449, + .revision = 0x03, + .stats_size = 80, + .has_extended_tcb_support = true, + .power_management = true, + } +}; + +static Property e100_properties[] = { + DEFINE_NIC_PROPERTIES(EEPRO100State, conf), + DEFINE_PROP_END_OF_LIST(), +}; + +static void eepro100_register_devices(void) +{ + size_t i; + for (i = 0; i < ARRAY_SIZE(e100_devices); i++) { + PCIDeviceInfo *pci_dev = &e100_devices[i].pci; + /* We use the same rom file for all device ids. + QEMU fixes the device id during rom load. */ + pci_dev->romfile = "gpxe-eepro100-80861209.rom"; + pci_dev->init = e100_nic_init; + pci_dev->exit = pci_nic_uninit; + pci_dev->qdev.props = e100_properties; + pci_dev->qdev.size = sizeof(EEPRO100State); + pci_qdev_register(pci_dev); + } +} + +device_init(eepro100_register_devices) diff --git a/hw/eeprom93xx.c b/hw/eeprom93xx.c new file mode 100644 index 0000000..660b28f --- /dev/null +++ b/hw/eeprom93xx.c @@ -0,0 +1,337 @@ +/* + * 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 . + */ + +/* 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.h" +#include "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 writeable; + + 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, + .minimum_version_id_old = OLD_EEPROM_VERSION, + .fields = (VMStateField []) { + VMSTATE_UINT8(tick, eeprom_t), + VMSTATE_UINT8(address, eeprom_t), + VMSTATE_UINT8(command, eeprom_t), + VMSTATE_UINT8(writeable, 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->writeable) { + 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->writeable = 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->writeable = 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 *)qemu_mallocz(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); + qemu_free(eeprom); +} + +uint16_t *eeprom93xx_data(eeprom_t *eeprom) +{ + /* Get EEPROM data array. */ + return &eeprom->contents[0]; +} + +/* eof */ diff --git a/hw/eeprom93xx.h b/hw/eeprom93xx.h new file mode 100644 index 0000000..8ba0e28 --- /dev/null +++ b/hw/eeprom93xx.h @@ -0,0 +1,40 @@ +/* + * 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 . + */ + +#ifndef EEPROM93XX_H +#define EEPROM93XX_H + +typedef struct _eeprom_t eeprom_t; + +/* Create a new EEPROM with (nwords * 2) bytes. */ +eeprom_t *eeprom93xx_new(DeviceState *dev, uint16_t nwords); + +/* Destroy an existing EEPROM. */ +void eeprom93xx_free(DeviceState *dev, eeprom_t *eeprom); + +/* Read from the EEPROM. */ +uint16_t eeprom93xx_read(eeprom_t *eeprom); + +/* Write to the EEPROM. */ +void eeprom93xx_write(eeprom_t *eeprom, int eecs, int eesk, int eedi); + +/* Get EEPROM data array. */ +uint16_t *eeprom93xx_data(eeprom_t *eeprom); + +#endif /* EEPROM93XX_H */ diff --git a/hw/elf_ops.h b/hw/elf_ops.h new file mode 100644 index 0000000..0bd7235 --- /dev/null +++ b/hw/elf_ops.h @@ -0,0 +1,297 @@ +static void glue(bswap_ehdr, SZ)(struct elfhdr *ehdr) +{ + bswap16s(&ehdr->e_type); /* Object file type */ + bswap16s(&ehdr->e_machine); /* Architecture */ + bswap32s(&ehdr->e_version); /* Object file version */ + bswapSZs(&ehdr->e_entry); /* Entry point virtual address */ + bswapSZs(&ehdr->e_phoff); /* Program header table file offset */ + bswapSZs(&ehdr->e_shoff); /* Section header table file offset */ + bswap32s(&ehdr->e_flags); /* Processor-specific flags */ + bswap16s(&ehdr->e_ehsize); /* ELF header size in bytes */ + bswap16s(&ehdr->e_phentsize); /* Program header table entry size */ + bswap16s(&ehdr->e_phnum); /* Program header table entry count */ + bswap16s(&ehdr->e_shentsize); /* Section header table entry size */ + bswap16s(&ehdr->e_shnum); /* Section header table entry count */ + bswap16s(&ehdr->e_shstrndx); /* Section header string table index */ +} + +static void glue(bswap_phdr, SZ)(struct elf_phdr *phdr) +{ + bswap32s(&phdr->p_type); /* Segment type */ + bswapSZs(&phdr->p_offset); /* Segment file offset */ + bswapSZs(&phdr->p_vaddr); /* Segment virtual address */ + bswapSZs(&phdr->p_paddr); /* Segment physical address */ + bswapSZs(&phdr->p_filesz); /* Segment size in file */ + bswapSZs(&phdr->p_memsz); /* Segment size in memory */ + bswap32s(&phdr->p_flags); /* Segment flags */ + bswapSZs(&phdr->p_align); /* Segment alignment */ +} + +static void glue(bswap_shdr, SZ)(struct elf_shdr *shdr) +{ + bswap32s(&shdr->sh_name); + bswap32s(&shdr->sh_type); + bswapSZs(&shdr->sh_flags); + bswapSZs(&shdr->sh_addr); + bswapSZs(&shdr->sh_offset); + bswapSZs(&shdr->sh_size); + bswap32s(&shdr->sh_link); + bswap32s(&shdr->sh_info); + bswapSZs(&shdr->sh_addralign); + bswapSZs(&shdr->sh_entsize); +} + +static void glue(bswap_sym, SZ)(struct elf_sym *sym) +{ + bswap32s(&sym->st_name); + bswapSZs(&sym->st_value); + bswapSZs(&sym->st_size); + bswap16s(&sym->st_shndx); +} + +static struct elf_shdr *glue(find_section, SZ)(struct elf_shdr *shdr_table, + int n, int type) +{ + int i; + for(i=0;ist_value < sym->st_value) { + result = -1; + } else if (key->st_value >= sym->st_value + sym->st_size) { + result = 1; + } + return result; +} + +static const char *glue(lookup_symbol, SZ)(struct syminfo *s, + target_phys_addr_t orig_addr) +{ + struct elf_sym *syms = glue(s->disas_symtab.elf, SZ); + struct elf_sym key; + struct elf_sym *sym; + + key.st_value = orig_addr; + + sym = bsearch(&key, syms, s->disas_num_syms, sizeof(*syms), glue(symfind, SZ)); + if (sym != NULL) { + return s->disas_strtab + sym->st_name; + } + + return ""; +} + +static int glue(symcmp, SZ)(const void *s0, const void *s1) +{ + struct elf_sym *sym0 = (struct elf_sym *)s0; + struct elf_sym *sym1 = (struct elf_sym *)s1; + return (sym0->st_value < sym1->st_value) + ? -1 + : ((sym0->st_value > sym1->st_value) ? 1 : 0); +} + +static int glue(load_symbols, SZ)(struct elfhdr *ehdr, int fd, int must_swab, + int clear_lsb) +{ + struct elf_shdr *symtab, *strtab, *shdr_table = NULL; + struct elf_sym *syms = NULL; + struct syminfo *s; + int nsyms, i; + char *str = NULL; + + shdr_table = load_at(fd, ehdr->e_shoff, + sizeof(struct elf_shdr) * ehdr->e_shnum); + if (!shdr_table) + return -1; + + if (must_swab) { + for (i = 0; i < ehdr->e_shnum; i++) { + glue(bswap_shdr, SZ)(shdr_table + i); + } + } + + symtab = glue(find_section, SZ)(shdr_table, ehdr->e_shnum, SHT_SYMTAB); + if (!symtab) + goto fail; + syms = load_at(fd, symtab->sh_offset, symtab->sh_size); + if (!syms) + goto fail; + + nsyms = symtab->sh_size / sizeof(struct elf_sym); + + i = 0; + while (i < nsyms) { + if (must_swab) + glue(bswap_sym, SZ)(&syms[i]); + /* We are only interested in function symbols. + Throw everything else away. */ + if (syms[i].st_shndx == SHN_UNDEF || + syms[i].st_shndx >= SHN_LORESERVE || + ELF_ST_TYPE(syms[i].st_info) != STT_FUNC) { + nsyms--; + if (i < nsyms) { + syms[i] = syms[nsyms]; + } + continue; + } + if (clear_lsb) { + /* The bottom address bit marks a Thumb or MIPS16 symbol. */ + syms[i].st_value &= ~(glue(glue(Elf, SZ), _Addr))1; + } + i++; + } + if (nsyms) { + syms = qemu_realloc(syms, nsyms * sizeof(*syms)); + + qsort(syms, nsyms, sizeof(*syms), glue(symcmp, SZ)); + for (i = 0; i < nsyms - 1; i++) { + if (syms[i].st_size == 0) { + syms[i].st_size = syms[i + 1].st_value - syms[i].st_value; + } + } + } else { + qemu_free(syms); + syms = NULL; + } + + /* String table */ + if (symtab->sh_link >= ehdr->e_shnum) + goto fail; + strtab = &shdr_table[symtab->sh_link]; + + str = load_at(fd, strtab->sh_offset, strtab->sh_size); + if (!str) + goto fail; + + /* Commit */ + s = qemu_mallocz(sizeof(*s)); + s->lookup_symbol = glue(lookup_symbol, SZ); + glue(s->disas_symtab.elf, SZ) = syms; + s->disas_num_syms = nsyms; + s->disas_strtab = str; + s->next = syminfos; + syminfos = s; + qemu_free(shdr_table); + return 0; + fail: + qemu_free(syms); + qemu_free(str); + qemu_free(shdr_table); + return -1; +} + +static int glue(load_elf, SZ)(const char *name, int fd, + uint64_t (*translate_fn)(void *, uint64_t), + void *translate_opaque, + int must_swab, uint64_t *pentry, + uint64_t *lowaddr, uint64_t *highaddr, + int elf_machine, int clear_lsb) +{ + struct elfhdr ehdr; + struct elf_phdr *phdr = NULL, *ph; + int size, i, total_size; + elf_word mem_size; + uint64_t addr, low = (uint64_t)-1, high = 0; + uint8_t *data = NULL; + char label[128]; + + if (read(fd, &ehdr, sizeof(ehdr)) != sizeof(ehdr)) + goto fail; + if (must_swab) { + glue(bswap_ehdr, SZ)(&ehdr); + } + + switch (elf_machine) { + case EM_PPC64: + if (EM_PPC64 != ehdr.e_machine) + if (EM_PPC != ehdr.e_machine) + goto fail; + break; + case EM_X86_64: + if (EM_X86_64 != ehdr.e_machine) + if (EM_386 != ehdr.e_machine) + goto fail; + break; + case EM_MICROBLAZE: + if (EM_MICROBLAZE != ehdr.e_machine) + if (EM_MICROBLAZE_OLD != ehdr.e_machine) + goto fail; + break; + default: + if (elf_machine != ehdr.e_machine) + goto fail; + } + + if (pentry) + *pentry = (uint64_t)(elf_sword)ehdr.e_entry; + + glue(load_symbols, SZ)(&ehdr, fd, must_swab, clear_lsb); + + size = ehdr.e_phnum * sizeof(phdr[0]); + lseek(fd, ehdr.e_phoff, SEEK_SET); + phdr = qemu_mallocz(size); + if (!phdr) + goto fail; + if (read(fd, phdr, size) != size) + goto fail; + if (must_swab) { + for(i = 0; i < ehdr.e_phnum; i++) { + ph = &phdr[i]; + glue(bswap_phdr, SZ)(ph); + } + } + + total_size = 0; + for(i = 0; i < ehdr.e_phnum; i++) { + ph = &phdr[i]; + if (ph->p_type == PT_LOAD) { + mem_size = ph->p_memsz; + /* XXX: avoid allocating */ + data = qemu_mallocz(mem_size); + if (ph->p_filesz > 0) { + if (lseek(fd, ph->p_offset, SEEK_SET) < 0) + goto fail; + if (read(fd, data, ph->p_filesz) != ph->p_filesz) + goto fail; + } + /* address_offset is hack for kernel images that are + linked at the wrong physical address. */ + if (translate_fn) { + addr = translate_fn(translate_opaque, ph->p_paddr); + } else { + addr = ph->p_paddr; + } + + snprintf(label, sizeof(label), "phdr #%d: %s", i, name); + rom_add_blob_fixed(label, data, mem_size, addr); + + total_size += mem_size; + if (addr < low) + low = addr; + if ((addr + mem_size) > high) + high = addr + mem_size; + + qemu_free(data); + data = NULL; + } + } + qemu_free(phdr); + if (lowaddr) + *lowaddr = (uint64_t)(elf_sword)low; + if (highaddr) + *highaddr = (uint64_t)(elf_sword)high; + return total_size; + fail: + qemu_free(data); + qemu_free(phdr); + return -1; +} diff --git a/hw/empty_slot.c b/hw/empty_slot.c new file mode 100644 index 0000000..664b8d9 --- /dev/null +++ b/hw/empty_slot.c @@ -0,0 +1,93 @@ +/* + * 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.h" +#include "sysbus.h" +#include "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 + +typedef struct EmptySlot { + SysBusDevice busdev; + uint64_t size; +} EmptySlot; + +static uint32_t empty_slot_readl(void *opaque, target_phys_addr_t addr) +{ + DPRINTF("read from " TARGET_FMT_plx "\n", addr); + return 0; +} + +static void empty_slot_writel(void *opaque, target_phys_addr_t addr, + uint32_t val) +{ + DPRINTF("write 0x%x to " TARGET_FMT_plx "\n", val, addr); +} + +CPUReadMemoryFunc * const empty_slot_read[3] = { + empty_slot_readl, + empty_slot_readl, + empty_slot_readl, +}; + +static CPUWriteMemoryFunc * const empty_slot_write[3] = { + empty_slot_writel, + empty_slot_writel, + empty_slot_writel, +}; + +void empty_slot_init(target_phys_addr_t addr, uint64_t slot_size) +{ + DeviceState *dev; + SysBusDevice *s; + EmptySlot *e; + + dev = qdev_create(NULL, "empty_slot"); + s = sysbus_from_qdev(dev); + e = FROM_SYSBUS(EmptySlot, s); + e->size = slot_size; + + qdev_init_nofail(dev); + + sysbus_mmio_map(s, 0, addr); +} + +static int empty_slot_init1(SysBusDevice *dev) +{ + EmptySlot *s = FROM_SYSBUS(EmptySlot, dev); + ram_addr_t empty_slot_offset; + + empty_slot_offset = cpu_register_io_memory(empty_slot_read, + empty_slot_write, s, + DEVICE_NATIVE_ENDIAN); + sysbus_init_mmio(dev, s->size, empty_slot_offset | IO_MEM_RAM); + return 0; +} + +static SysBusDeviceInfo empty_slot_info = { + .init = empty_slot_init1, + .qdev.name = "empty_slot", + .qdev.size = sizeof(EmptySlot), +}; + +static void empty_slot_register_devices(void) +{ + sysbus_register_withprop(&empty_slot_info); +} + +device_init(empty_slot_register_devices); diff --git a/hw/empty_slot.h b/hw/empty_slot.h new file mode 100644 index 0000000..78dc91d --- /dev/null +++ b/hw/empty_slot.h @@ -0,0 +1,2 @@ +/* empty_slot.c */ +void empty_slot_init(target_phys_addr_t addr, uint64_t slot_size); diff --git a/hw/es1370.c b/hw/es1370.c new file mode 100644 index 0000000..40cb48c --- /dev/null +++ b/hw/es1370.c @@ -0,0 +1,1053 @@ +/* + * 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.h" +#include "audiodev.h" +#include "audio/audio.h" +#include "pci.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; + 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; + } + qemu_set_irq (s->dev.irq[0], !!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; + } + } + qemu_irq_lower (s->dev.irq[0]); +} + +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 %d, 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 %d 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 %d 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 %d 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 transfered = 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; + + cpu_physical_memory_write (addr, tmpbuf, acquired); + + temp -= acquired; + addr += acquired; + transfered += acquired; + } + } + else { + SWVoiceOut *voice = s->dac_voice[index]; + + while (temp) { + int copied, to_copy; + + to_copy = audio_MIN ((size_t) temp, sizeof (tmpbuf)); + cpu_physical_memory_read (addr, tmpbuf, to_copy); + copied = AUD_write (voice, tmpbuf, to_copy); + if (!copied) + break; + temp -= copied; + addr += copied; + transfered += copied; + } + } + + if (csc_bytes == transfered) { + *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 - transfered - 1) >> d->shift) << 16); + } + + cnt += (transfered + 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 = (transfered + 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 void es1370_map (PCIDevice *pci_dev, int region_num, + pcibus_t addr, pcibus_t size, int type) +{ + ES1370State *s = DO_UPCAST (ES1370State, dev, pci_dev); + + (void) region_num; + (void) size; + (void) type; + + register_ioport_write (addr, 0x40 * 4, 1, es1370_writeb, s); + register_ioport_write (addr, 0x40 * 2, 2, es1370_writew, s); + register_ioport_write (addr, 0x40, 4, es1370_writel, s); + + register_ioport_read (addr, 0x40 * 4, 1, es1370_readb, s); + register_ioport_read (addr, 0x40 * 2, 2, es1370_readw, s); + register_ioport_read (addr, 0x40, 4, es1370_readl, s); +} + +static const VMStateDescription vmstate_es1370_channel = { + .name = "es1370_channel", + .version_id = 2, + .minimum_version_id = 2, + .minimum_version_id_old = 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, + .minimum_version_id_old = 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 int es1370_initfn (PCIDevice *dev) +{ + ES1370State *s = DO_UPCAST (ES1370State, dev, dev); + uint8_t *c = s->dev.config; + + pci_config_set_vendor_id (c, PCI_VENDOR_ID_ENSONIQ); + pci_config_set_device_id (c, PCI_DEVICE_ID_ENSONIQ_ES1370); + c[PCI_STATUS + 1] = PCI_STATUS_DEVSEL_SLOW >> 8; + pci_config_set_class (c, PCI_CLASS_MULTIMEDIA_AUDIO); + +#if 1 + c[PCI_SUBSYSTEM_VENDOR_ID] = 0x42; + c[PCI_SUBSYSTEM_VENDOR_ID + 1] = 0x49; + c[PCI_SUBSYSTEM_ID] = 0x4c; + c[PCI_SUBSYSTEM_ID + 1] = 0x4c; +#else + c[PCI_SUBSYSTEM_VENDOR_ID] = 0x74; + c[PCI_SUBSYSTEM_VENDOR_ID + 1] = 0x12; + c[PCI_SUBSYSTEM_ID] = 0x71; + c[PCI_SUBSYSTEM_ID + 1] = 0x13; + c[PCI_CAPABILITY_LIST] = 0xdc; + c[PCI_INTERRUPT_LINE] = 10; + c[0xdc] = 0x00; +#endif + + /* TODO: RST# value should be 0. */ + c[PCI_INTERRUPT_PIN] = 1; + c[PCI_MIN_GNT] = 0x0c; + c[PCI_MAX_LAT] = 0x80; + + pci_register_bar (&s->dev, 0, 256, PCI_BASE_ADDRESS_SPACE_IO, es1370_map); + qemu_register_reset (es1370_on_reset, s); + + AUD_register_card ("es1370", &s->card); + es1370_reset (s); + return 0; +} + +int es1370_init (PCIBus *bus) +{ + pci_create_simple (bus, -1, "ES1370"); + return 0; +} + +static PCIDeviceInfo es1370_info = { + .qdev.name = "ES1370", + .qdev.desc = "ENSONIQ AudioPCI ES1370", + .qdev.size = sizeof (ES1370State), + .qdev.vmsd = &vmstate_es1370, + .init = es1370_initfn, +}; + +static void es1370_register (void) +{ + pci_qdev_register (&es1370_info); +} +device_init (es1370_register); + diff --git a/hw/escc.c b/hw/escc.c new file mode 100644 index 0000000..f6fd919 --- /dev/null +++ b/hw/escc.c @@ -0,0 +1,961 @@ +/* + * 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.h" +#include "sysbus.h" +#include "escc.h" +#include "qemu-char.h" +#include "console.h" + +/* debug serial */ +//#define DEBUG_SERIAL + +/* debug keyboard */ +//#define DEBUG_KBD + +/* debug mouse */ +//#define DEBUG_MOUSE + +/* + * 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 + */ + +#ifdef DEBUG_SERIAL +#define SER_DPRINTF(fmt, ...) \ + do { printf("SER: " fmt , ## __VA_ARGS__); } while (0) +#else +#define SER_DPRINTF(fmt, ...) +#endif +#ifdef DEBUG_KBD +#define KBD_DPRINTF(fmt, ...) \ + do { printf("KBD: " fmt , ## __VA_ARGS__); } while (0) +#else +#define KBD_DPRINTF(fmt, ...) +#endif +#ifdef DEBUG_MOUSE +#define MS_DPRINTF(fmt, ...) \ + do { printf("MSC: " fmt , ## __VA_ARGS__); } while (0) +#else +#define MS_DPRINTF(fmt, ...) +#endif + +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 reg; + uint32_t rxint, txint, rxint_under_svc, txint_under_svc; + ChnID chn; // this channel, A (base+4) or B (base+0) + ChnType type; + struct ChannelState *otherchn; + uint8_t rx, tx, 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; +} ChannelState; + +struct SerialState { + SysBusDevice busdev; + struct ChannelState chn[2]; + uint32_t it_shift; + int mmio_index; + uint32_t disabled; + uint32_t frequency; +}; + +#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; + + SER_DPRINTF("channel %c put: 0x%02x\n", 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--; + } + SER_DPRINTF("channel %c get 0x%02x\n", 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); + + SER_DPRINTF("IRQ = %d\n", 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) +{ + SerialState *s = container_of(d, SerialState, busdev.qdev); + + 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; + SER_DPRINTF("channel %c: speed=%d parity=%c data=%d stop=%d\n", CHN_C(s), + speed, parity, data_bits, stop_bits); + qemu_chr_ioctl(s->chr, CHR_IOCTL_SERIAL_SET_PARAMS, &ssp); +} + +static void escc_mem_writeb(void *opaque, target_phys_addr_t addr, uint32_t val) +{ + SerialState *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: + SER_DPRINTF("Write channel %c, reg[%d] = %2.2x\n", 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(&serial->busdev.qdev); + return; + } + break; + default: + break; + } + if (s->reg == 0) + s->reg = newreg; + else + s->reg = 0; + break; + case SERIAL_DATA: + SER_DPRINTF("Write channel %c, ch %d\n", CHN_C(s), val); + s->tx = val; + if (s->wregs[W_TXCTRL2] & TXCTRL2_TXEN) { // tx enabled + if (s->chr) + qemu_chr_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 uint32_t escc_mem_readb(void *opaque, target_phys_addr_t addr) +{ + SerialState *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: + SER_DPRINTF("Read channel %c, reg[%d] = %2.2x\n", 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; + SER_DPRINTF("Read channel %c, ch %d\n", CHN_C(s), ret); + if (s->chr) + qemu_chr_accept_input(s->chr); + return ret; + default: + break; + } + return 0; +} + +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) +{ + SER_DPRINTF("channel %c put ch %d\n", 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 CPUReadMemoryFunc * const escc_mem_read[3] = { + escc_mem_readb, + NULL, + NULL, +}; + +static CPUWriteMemoryFunc * const escc_mem_write[3] = { + escc_mem_writeb, + NULL, + NULL, +}; + +static const VMStateDescription vmstate_escc_chn = { + .name ="escc_chn", + .version_id = 2, + .minimum_version_id = 1, + .minimum_version_id_old = 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, + .minimum_version_id_old = 1, + .fields = (VMStateField []) { + VMSTATE_STRUCT_ARRAY(chn, SerialState, 2, 2, vmstate_escc_chn, + ChannelState), + VMSTATE_END_OF_LIST() + } +}; + +int escc_init(target_phys_addr_t base, qemu_irq irqA, qemu_irq irqB, + CharDriverState *chrA, CharDriverState *chrB, + int clock, int it_shift) +{ + DeviceState *dev; + SysBusDevice *s; + SerialState *d; + + dev = qdev_create(NULL, "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 = sysbus_from_qdev(dev); + sysbus_connect_irq(s, 0, irqB); + sysbus_connect_irq(s, 1, irqA); + if (base) { + sysbus_mmio_map(s, 0, base); + } + + d = FROM_SYSBUS(SerialState, s); + return d->mmio_index; +} + +static const uint8_t keycodes[128] = { + 127, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 43, 53, + 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 89, 76, 77, 78, + 79, 80, 81, 82, 83, 84, 85, 86, 87, 42, 99, 88, 100, 101, 102, 103, + 104, 105, 106, 107, 108, 109, 110, 47, 19, 121, 119, 5, 6, 8, 10, 12, + 14, 16, 17, 18, 7, 98, 23, 68, 69, 70, 71, 91, 92, 93, 125, 112, + 113, 114, 94, 50, 0, 0, 124, 9, 11, 0, 0, 0, 0, 0, 0, 0, + 90, 0, 46, 22, 13, 111, 52, 20, 96, 24, 28, 74, 27, 123, 44, 66, + 0, 45, 2, 4, 48, 0, 0, 21, 0, 0, 0, 0, 0, 120, 122, 67, +}; + +static const uint8_t e0_keycodes[128] = { + 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, 90, 76, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 109, 0, 0, 13, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 68, 69, 70, 0, 91, 0, 93, 0, 112, + 113, 114, 94, 50, 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, + 1, 3, 25, 26, 49, 52, 72, 73, 97, 99, 111, 118, 120, 122, 67, 0, +}; + +static void sunkbd_event(void *opaque, int ch) +{ + ChannelState *s = opaque; + int release = ch & 0x80; + + KBD_DPRINTF("Untranslated keycode %2.2x (%s)\n", ch, release? "release" : + "press"); + switch (ch) { + case 58: // Caps lock press + s->caps_lock_mode ^= 1; + if (s->caps_lock_mode == 2) + return; // Drop second press + break; + case 69: // Num lock press + s->num_lock_mode ^= 1; + if (s->num_lock_mode == 2) + return; // Drop second press + break; + case 186: // Caps lock release + s->caps_lock_mode ^= 2; + if (s->caps_lock_mode == 3) + return; // Drop first release + break; + case 197: // Num lock release + s->num_lock_mode ^= 2; + if (s->num_lock_mode == 3) + return; // Drop first release + break; + case 0xe0: + s->e0_mode = 1; + return; + default: + break; + } + if (s->e0_mode) { + s->e0_mode = 0; + ch = e0_keycodes[ch & 0x7f]; + } else { + ch = keycodes[ch & 0x7f]; + } + KBD_DPRINTF("Translated keycode %2.2x\n", ch); + put_queue(s, ch | release); +} + +static void handle_kbd_command(ChannelState *s, int val) +{ + KBD_DPRINTF("Command %d\n", 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, 0); // XXX, layout? + break; + default: + break; + } +} + +static void sunmouse_event(void *opaque, + int dx, int dy, int dz, int buttons_state) +{ + ChannelState *s = opaque; + int ch; + + MS_DPRINTF("dx=%d dy=%d buttons=%01x\n", 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(target_phys_addr_t base, qemu_irq irq, + int disabled, int clock, int it_shift) +{ + DeviceState *dev; + SysBusDevice *s; + + dev = qdev_create(NULL, "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 = sysbus_from_qdev(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) +{ + SerialState *s = FROM_SYSBUS(SerialState, dev); + int io; + 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]; + + io = cpu_register_io_memory(escc_mem_read, escc_mem_write, s, + DEVICE_NATIVE_ENDIAN); + sysbus_init_mmio(dev, ESCC_SIZE << s->it_shift, io); + s->mmio_index = io; + + 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) { + qemu_add_kbd_event_handler(sunkbd_event, &s->chn[1]); + } + + return 0; +} + +static SysBusDeviceInfo escc_info = { + .init = escc_init1, + .qdev.name = "escc", + .qdev.size = sizeof(SerialState), + .qdev.vmsd = &vmstate_escc, + .qdev.reset = escc_reset, + .qdev.props = (Property[]) { + DEFINE_PROP_UINT32("frequency", SerialState, frequency, 0), + DEFINE_PROP_UINT32("it_shift", SerialState, it_shift, 0), + DEFINE_PROP_UINT32("disabled", SerialState, disabled, 0), + DEFINE_PROP_UINT32("disabled", SerialState, disabled, 0), + DEFINE_PROP_UINT32("chnBtype", SerialState, chn[0].type, 0), + DEFINE_PROP_UINT32("chnAtype", SerialState, chn[1].type, 0), + DEFINE_PROP_CHR("chrB", SerialState, chn[0].chr), + DEFINE_PROP_CHR("chrA", SerialState, chn[1].chr), + DEFINE_PROP_END_OF_LIST(), + } +}; + +static void escc_register_devices(void) +{ + sysbus_register_withprop(&escc_info); +} + +device_init(escc_register_devices) diff --git a/hw/escc.h b/hw/escc.h new file mode 100644 index 0000000..015b9d0 --- /dev/null +++ b/hw/escc.h @@ -0,0 +1,8 @@ +/* escc.c */ +#define ESCC_SIZE 4 +int escc_init(target_phys_addr_t base, qemu_irq irqA, qemu_irq irqB, + CharDriverState *chrA, CharDriverState *chrB, + int clock, int it_shift); + +void slavio_serial_ms_kbd_init(target_phys_addr_t base, qemu_irq irq, + int disabled, int clock, int it_shift); diff --git a/hw/esp.c b/hw/esp.c new file mode 100644 index 0000000..fa9d2a2 --- /dev/null +++ b/hw/esp.c @@ -0,0 +1,751 @@ +/* + * QEMU ESP/NCR53C9x emulation + * + * Copyright (c) 2005-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 "sysbus.h" +#include "scsi.h" +#include "esp.h" + +/* debug ESP card */ +//#define DEBUG_ESP + +/* + * 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 + */ + +#ifdef DEBUG_ESP +#define DPRINTF(fmt, ...) \ + do { printf("ESP: " fmt , ## __VA_ARGS__); } while (0) +#else +#define DPRINTF(fmt, ...) do {} while (0) +#endif + +#define ESP_ERROR(fmt, ...) \ + do { printf("ESP ERROR: %s: " fmt, __func__ , ## __VA_ARGS__); } while (0) + +#define ESP_REGS 16 +#define TI_BUFSZ 16 + +typedef struct ESPState ESPState; + +struct ESPState { + SysBusDevice busdev; + uint32_t it_shift; + qemu_irq irq; + uint8_t rregs[ESP_REGS]; + uint8_t wregs[ESP_REGS]; + int32_t ti_size; + uint32_t ti_rptr, ti_wptr; + uint8_t ti_buf[TI_BUFSZ]; + uint32_t sense; + uint32_t dma; + SCSIBus bus; + SCSIDevice *current_dev; + uint8_t cmdbuf[TI_BUFSZ]; + uint32_t cmdlen; + uint32_t do_cmd; + + /* The amount of data left in the current DMA transfer. */ + uint32_t dma_left; + /* The size of the current DMA transfer. Zero if no transfer is in + progress. */ + uint32_t dma_counter; + uint8_t *async_buf; + uint32_t async_len; + + ESPDMAMemoryReadWriteFunc dma_memory_read; + ESPDMAMemoryReadWriteFunc dma_memory_write; + void *dma_opaque; + int dma_enabled; + void (*dma_cb)(ESPState *s); +}; + +#define ESP_TCLO 0x0 +#define ESP_TCMID 0x1 +#define ESP_FIFO 0x2 +#define ESP_CMD 0x3 +#define ESP_RSTAT 0x4 +#define ESP_WBUSID 0x4 +#define ESP_RINTR 0x5 +#define ESP_WSEL 0x5 +#define ESP_RSEQ 0x6 +#define ESP_WSYNTP 0x6 +#define ESP_RFLAGS 0x7 +#define ESP_WSYNO 0x7 +#define ESP_CFG1 0x8 +#define ESP_RRES1 0x9 +#define ESP_WCCF 0x9 +#define ESP_RRES2 0xa +#define ESP_WTEST 0xa +#define ESP_CFG2 0xb +#define ESP_CFG3 0xc +#define ESP_RES3 0xd +#define ESP_TCHI 0xe +#define ESP_RES4 0xf + +#define CMD_DMA 0x80 +#define CMD_CMD 0x7f + +#define CMD_NOP 0x00 +#define CMD_FLUSH 0x01 +#define CMD_RESET 0x02 +#define CMD_BUSRESET 0x03 +#define CMD_TI 0x10 +#define CMD_ICCS 0x11 +#define CMD_MSGACC 0x12 +#define CMD_PAD 0x18 +#define CMD_SATN 0x1a +#define CMD_SEL 0x41 +#define CMD_SELATN 0x42 +#define CMD_SELATNS 0x43 +#define CMD_ENSEL 0x44 + +#define STAT_DO 0x00 +#define STAT_DI 0x01 +#define STAT_CD 0x02 +#define STAT_ST 0x03 +#define STAT_MO 0x06 +#define STAT_MI 0x07 +#define STAT_PIO_MASK 0x06 + +#define STAT_TC 0x10 +#define STAT_PE 0x20 +#define STAT_GE 0x40 +#define STAT_INT 0x80 + +#define BUSID_DID 0x07 + +#define INTR_FC 0x08 +#define INTR_BS 0x10 +#define INTR_DC 0x20 +#define INTR_RST 0x80 + +#define SEQ_0 0x0 +#define SEQ_CD 0x4 + +#define CFG1_RESREPT 0x40 + +#define TCHI_FAS100A 0x4 + +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); + DPRINTF("Raise IRQ\n"); + } +} + +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); + DPRINTF("Lower IRQ\n"); + } +} + +static void esp_dma_enable(void *opaque, int irq, int level) +{ + DeviceState *d = opaque; + ESPState *s = container_of(d, ESPState, busdev.qdev); + + if (level) { + s->dma_enabled = 1; + DPRINTF("Raise enable\n"); + if (s->dma_cb) { + s->dma_cb(s); + s->dma_cb = NULL; + } + } else { + DPRINTF("Lower enable\n"); + s->dma_enabled = 0; + } +} + +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] | (s->rregs[ESP_TCMID] << 8); + s->dma_memory_read(s->dma_opaque, buf, dmalen); + } else { + dmalen = s->ti_size; + memcpy(buf, s->ti_buf, dmalen); + buf[0] = 0; + } + DPRINTF("get_cmd: len %d target %d\n", dmalen, target); + + s->ti_size = 0; + s->ti_rptr = 0; + s->ti_wptr = 0; + + if (s->current_dev) { + /* Started a new command before the old one finished. Cancel it. */ + s->current_dev->info->cancel_io(s->current_dev, 0); + s->async_len = 0; + } + + if (target >= ESP_MAX_DEVS || !s->bus.devs[target]) { + // 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; + } + s->current_dev = s->bus.devs[target]; + return dmalen; +} + +static void do_busid_cmd(ESPState *s, uint8_t *buf, uint8_t busid) +{ + int32_t datalen; + int lun; + + DPRINTF("do_busid_cmd: busid 0x%x\n", busid); + lun = busid & 7; + datalen = s->current_dev->info->send_command(s->current_dev, 0, buf, lun); + 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; + s->current_dev->info->read_data(s->current_dev, 0); + } else { + s->rregs[ESP_RSTAT] |= STAT_DO; + s->current_dev->info->write_data(s->current_dev, 0); + } + } + 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_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_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_enabled) { + s->dma_cb = handle_satn_stop; + return; + } + s->cmdlen = get_cmd(s, s->cmdbuf); + if (s->cmdlen) { + DPRINTF("Set ATN & Stop: cmdlen %d\n", 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) +{ + DPRINTF("Transfer status (sense=%d)\n", s->sense); + s->ti_buf[0] = s->sense; + 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; + 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) { + DPRINTF("command len %d + %d\n", 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) { + if (to_device) { + // ti_size is negative + s->current_dev->info->write_data(s->current_dev, 0); + } else { + s->current_dev->info->read_data(s->current_dev, 0); + /* 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 (s->dma_left == 0 && s->ti_size > 0) { + esp_dma_done(s); + } + } + } else { + /* Partially filled a scsi buffer. Complete immediately. */ + esp_dma_done(s); + } +} + +static void esp_command_complete(SCSIBus *bus, int reason, uint32_t tag, + uint32_t arg) +{ + ESPState *s = DO_UPCAST(ESPState, busdev.qdev, bus->qbus.parent); + + if (reason == SCSI_REASON_DONE) { + DPRINTF("SCSI Command complete\n"); + if (s->ti_size != 0) + DPRINTF("SCSI command completed unexpectedly\n"); + s->ti_size = 0; + s->dma_left = 0; + s->async_len = 0; + if (arg) + DPRINTF("Command failed\n"); + s->sense = arg; + s->rregs[ESP_RSTAT] = STAT_ST; + esp_dma_done(s); + s->current_dev = NULL; + } else { + DPRINTF("transfer %d/%d\n", s->dma_left, s->ti_size); + s->async_len = arg; + s->async_buf = s->current_dev->info->get_buf(s->current_dev, 0); + 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; + + dmalen = s->rregs[ESP_TCLO] | (s->rregs[ESP_TCMID] << 8); + 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; + DPRINTF("Transfer Information len %d\n", minlen); + if (s->dma) { + s->dma_left = minlen; + s->rregs[ESP_RSTAT] &= ~STAT_TC; + esp_do_dma(s); + } else if (s->do_cmd) { + DPRINTF("command len %d\n", s->cmdlen); + s->ti_size = 0; + s->cmdlen = 0; + s->do_cmd = 0; + do_cmd(s, s->cmdbuf); + return; + } +} + +static void esp_hard_reset(DeviceState *d) +{ + ESPState *s = container_of(d, ESPState, busdev.qdev); + + memset(s->rregs, 0, ESP_REGS); + memset(s->wregs, 0, ESP_REGS); + s->rregs[ESP_TCHI] = TCHI_FAS100A; // Indicate fas100a + 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(DeviceState *d) +{ + ESPState *s = container_of(d, ESPState, busdev.qdev); + + qemu_irq_lower(s->irq); + esp_hard_reset(d); +} + +static void parent_esp_reset(void *opaque, int irq, int level) +{ + if (level) { + esp_soft_reset(opaque); + } +} + +static void esp_gpio_demux(void *opaque, int irq, int level) +{ + switch (irq) { + case 0: + parent_esp_reset(opaque, irq, level); + break; + case 1: + esp_dma_enable(opaque, irq, level); + break; + } +} + +static uint32_t esp_mem_readb(void *opaque, target_phys_addr_t addr) +{ + ESPState *s = opaque; + uint32_t saddr, old_val; + + saddr = addr >> s->it_shift; + DPRINTF("read reg[%d]: 0x%2.2x\n", 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. */ + ESP_ERROR("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; + default: + break; + } + return s->rregs[saddr]; +} + +static void esp_mem_writeb(void *opaque, target_phys_addr_t addr, uint32_t val) +{ + ESPState *s = opaque; + uint32_t saddr; + + saddr = addr >> s->it_shift; + DPRINTF("write reg[%d]: 0x%2.2x -> 0x%2.2x\n", saddr, s->wregs[saddr], + val); + switch (saddr) { + 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) { + ESP_ERROR("fifo overrun\n"); + } 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]; + } else { + s->dma = 0; + } + switch(val & CMD_CMD) { + case CMD_NOP: + DPRINTF("NOP (%2.2x)\n", val); + break; + case CMD_FLUSH: + DPRINTF("Flush FIFO (%2.2x)\n", 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: + DPRINTF("Chip reset (%2.2x)\n", val); + esp_soft_reset(&s->busdev.qdev); + break; + case CMD_BUSRESET: + DPRINTF("Bus reset (%2.2x)\n", 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: + DPRINTF("Initiator Command Complete Sequence (%2.2x)\n", val); + write_response(s); + s->rregs[ESP_RINTR] = INTR_FC; + s->rregs[ESP_RSTAT] |= STAT_MI; + break; + case CMD_MSGACC: + DPRINTF("Message Accepted (%2.2x)\n", 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: + DPRINTF("Transfer padding (%2.2x)\n", val); + s->rregs[ESP_RSTAT] = STAT_TC; + s->rregs[ESP_RINTR] = INTR_FC; + s->rregs[ESP_RSEQ] = 0; + break; + case CMD_SATN: + DPRINTF("Set ATN (%2.2x)\n", val); + break; + case CMD_SEL: + DPRINTF("Select without ATN (%2.2x)\n", val); + handle_s_without_atn(s); + break; + case CMD_SELATN: + DPRINTF("Select with ATN (%2.2x)\n", val); + handle_satn(s); + break; + case CMD_SELATNS: + DPRINTF("Select with ATN & stop (%2.2x)\n", val); + handle_satn_stop(s); + break; + case CMD_ENSEL: + DPRINTF("Enable selection (%2.2x)\n", val); + s->rregs[ESP_RINTR] = 0; + break; + default: + ESP_ERROR("Unhandled ESP command (%2.2x)\n", val); + break; + } + break; + case ESP_WBUSID ... ESP_WSYNO: + break; + case ESP_CFG1: + s->rregs[saddr] = val; + break; + case ESP_WCCF ... ESP_WTEST: + break; + case ESP_CFG2 ... ESP_RES4: + s->rregs[saddr] = val; + break; + default: + ESP_ERROR("invalid write of 0x%02x at [0x%x]\n", val, saddr); + return; + } + s->wregs[saddr] = val; +} + +static CPUReadMemoryFunc * const esp_mem_read[3] = { + esp_mem_readb, + NULL, + NULL, +}; + +static CPUWriteMemoryFunc * const esp_mem_write[3] = { + esp_mem_writeb, + NULL, + esp_mem_writeb, +}; + +static const VMStateDescription vmstate_esp = { + .name ="esp", + .version_id = 3, + .minimum_version_id = 3, + .minimum_version_id_old = 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(sense, 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() + } +}; + +void esp_init(target_phys_addr_t 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; + ESPState *esp; + + dev = qdev_create(NULL, "esp"); + esp = DO_UPCAST(ESPState, busdev.qdev, dev); + esp->dma_memory_read = dma_memory_read; + esp->dma_memory_write = dma_memory_write; + esp->dma_opaque = dma_opaque; + esp->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 = sysbus_from_qdev(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 int esp_init1(SysBusDevice *dev) +{ + ESPState *s = FROM_SYSBUS(ESPState, dev); + int esp_io_memory; + + sysbus_init_irq(dev, &s->irq); + assert(s->it_shift != -1); + + esp_io_memory = cpu_register_io_memory(esp_mem_read, esp_mem_write, s, + DEVICE_NATIVE_ENDIAN); + sysbus_init_mmio(dev, ESP_REGS << s->it_shift, esp_io_memory); + + qdev_init_gpio_in(&dev->qdev, esp_gpio_demux, 2); + + scsi_bus_new(&s->bus, &dev->qdev, 0, ESP_MAX_DEVS, esp_command_complete); + return scsi_bus_legacy_handle_cmdline(&s->bus); +} + +static SysBusDeviceInfo esp_info = { + .init = esp_init1, + .qdev.name = "esp", + .qdev.size = sizeof(ESPState), + .qdev.vmsd = &vmstate_esp, + .qdev.reset = esp_hard_reset, + .qdev.props = (Property[]) { + {.name = NULL} + } +}; + +static void esp_register_devices(void) +{ + sysbus_register_withprop(&esp_info); +} + +device_init(esp_register_devices) diff --git a/hw/esp.h b/hw/esp.h new file mode 100644 index 0000000..62bfd4d --- /dev/null +++ b/hw/esp.h @@ -0,0 +1,13 @@ +#ifndef QEMU_HW_ESP_H +#define QEMU_HW_ESP_H + +/* esp.c */ +#define ESP_MAX_DEVS 7 +typedef void (*ESPDMAMemoryReadWriteFunc)(void *opaque, uint8_t *buf, int len); +void esp_init(target_phys_addr_t 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); + +#endif diff --git a/hw/etraxfs.c b/hw/etraxfs.c new file mode 100644 index 0000000..5ee5f97 --- /dev/null +++ b/hw/etraxfs.c @@ -0,0 +1,160 @@ +/* + * 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 "sysbus.h" +#include "boards.h" +#include "sysemu.h" +#include "net.h" +#include "flash.h" +#include "etraxfs.h" +#include "loader.h" +#include "elf.h" +#include "cris-boot.h" +#include "blockdev.h" + +#define FLASH_SIZE 0x2000000 +#define INTMEM_SIZE (128 * 1024) + +static struct cris_load_info li; + +static void flash_cpu_reset(void *opaque) +{ + CPUState *env = opaque; + cpu_reset(env); +} + +static +void bareetraxfs_init (ram_addr_t ram_size, + const char *boot_device, + const char *kernel_filename, const char *kernel_cmdline, + const char *initrd_filename, const char *cpu_model) +{ + DeviceState *dev; + SysBusDevice *s; + CPUState *env; + qemu_irq irq[30], nmi[2], *cpu_irq; + void *etraxfs_dmac; + struct etraxfs_dma_client *eth[2] = {NULL, NULL}; + DriveInfo *dinfo; + int i; + ram_addr_t phys_ram; + ram_addr_t phys_flash; + ram_addr_t phys_intmem; + + /* init CPUs */ + if (cpu_model == NULL) { + cpu_model = "crisv32"; + } + env = cpu_init(cpu_model); + + /* allocate RAM */ + phys_ram = qemu_ram_alloc(NULL, "etraxfs.ram", ram_size); + cpu_register_physical_memory(0x40000000, ram_size, phys_ram | IO_MEM_RAM); + + /* The ETRAX-FS has 128Kb on chip ram, the docs refer to it as the + internal memory. */ + phys_intmem = qemu_ram_alloc(NULL, "etraxfs.chipram", INTMEM_SIZE); + cpu_register_physical_memory(0x38000000, INTMEM_SIZE, + phys_intmem | IO_MEM_RAM); + + + phys_flash = qemu_ram_alloc(NULL, "etraxfs.flash", FLASH_SIZE); + dinfo = drive_get(IF_PFLASH, 0, 0); + pflash_cfi02_register(0x0, phys_flash, + dinfo ? dinfo->bdrv : NULL, (64 * 1024), + FLASH_SIZE >> 16, + 1, 2, 0x0000, 0x0000, 0x0000, 0x0000, + 0x555, 0x2aa, 0); + cpu_irq = cris_pic_init_cpu(env); + 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 = sysbus_from_qdev(dev); + sysbus_mmio_map(s, 0, 0x3001c000); + sysbus_connect_irq(s, 0, cpu_irq[0]); + sysbus_connect_irq(s, 1, cpu_irq[1]); + 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. */ + eth[0] = etraxfs_eth_init(&nd_table[0], 0x30034000, 1); + if (nb_nics > 1) + eth[1] = etraxfs_eth_init(&nd_table[1], 0x30036000, 2); + + /* The DMA Connector block is missing, hardwire things for now. */ + etraxfs_dmac_connect_client(etraxfs_dmac, 0, eth[0]); + etraxfs_dmac_connect_client(etraxfs_dmac, 1, eth[0] + 1); + if (eth[1]) { + etraxfs_dmac_connect_client(etraxfs_dmac, 6, eth[1]); + etraxfs_dmac_connect_client(etraxfs_dmac, 7, eth[1] + 1); + } + + /* 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(env, &li); + } else { + if (!dinfo) { + fprintf(stderr, + "Provide a kernel image or a flash image to boot from.\n"); + exit(1); + } + + /* Nothing more to do for flash images, those boot from addr 0. */ + qemu_register_reset(flash_cpu_reset, env); + } +} + +static QEMUMachine bareetraxfs_machine = { + .name = "bareetraxfs", + .desc = "Bare ETRAX FS board", + .init = bareetraxfs_init, + .is_default = 1, +}; + +static void bareetraxfs_machine_init(void) +{ + qemu_register_machine(&bareetraxfs_machine); +} + +machine_init(bareetraxfs_machine_init); diff --git a/hw/etraxfs.h b/hw/etraxfs.h new file mode 100644 index 0000000..01fb9d3 --- /dev/null +++ b/hw/etraxfs.h @@ -0,0 +1,28 @@ +/* + * QEMU ETRAX System Emulator + * + * 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 "etraxfs_dma.h" + +qemu_irq *cris_pic_init_cpu(CPUState *env); +void *etraxfs_eth_init(NICInfo *nd, target_phys_addr_t base, int phyaddr); diff --git a/hw/etraxfs_dma.c b/hw/etraxfs_dma.c new file mode 100644 index 0000000..c205ec1 --- /dev/null +++ b/hw/etraxfs_dma.c @@ -0,0 +1,756 @@ +/* + * 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 +#include +#include "hw.h" +#include "qemu-common.h" +#include "sysemu.h" + +#include "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; + + /* Controll registers. */ + uint32_t regs[DMA_REG_MAX]; +}; + +struct fs_dma_ctrl +{ + int map; + 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(target_phys_addr_t 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) +{ + target_phys_addr_t 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) +{ + target_phys_addr_t 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) +{ + target_phys_addr_t 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) +{ + target_phys_addr_t 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) +{ + target_phys_addr_t 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]; + + if (ctrl->channels[c].eol) + return 0; + + do { + 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)); + + 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); + + D(printf("channel %d pushes %x %u bytes\n", c, + saved_data_buf, len)); + + if (ctrl->channels[c].client->client.push) + ctrl->channels[c].client->client.push( + ctrl->channels[c].client->client.opaque, + buf, len); + 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) { + /* TODO: signal eop to the client. */ + D(printf("signal eop\n")); + } + if (ctrl->channels[c].current_d.intr) { + /* TODO: signal eop to the client. */ + /* 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, target_phys_addr_t addr) +{ + hw_error("Unsupported short raccess. reg=" TARGET_FMT_plx "\n", addr); + return 0; +} + +static uint32_t +dma_readl (void *opaque, target_phys_addr_t addr) +{ + struct fs_dma_ctrl *ctrl = opaque; + int c; + uint32_t r = 0; + + /* 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, target_phys_addr_t 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] & 1) != 3) { + 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_writel (void *opaque, target_phys_addr_t addr, uint32_t value) +{ + struct fs_dma_ctrl *ctrl = opaque; + int c; + + /* 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 CPUReadMemoryFunc * const dma_read[] = { + &dma_rinvalid, + &dma_rinvalid, + &dma_readl, +}; + +static CPUWriteMemoryFunc * const dma_write[] = { + &dma_winvalid, + &dma_winvalid, + &dma_writel, +}; + +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 (vm_running) + p = etraxfs_dmac_run(etraxfs_dmac); + + if (p) + qemu_bh_schedule_idle(etraxfs_dmac->bh); +} + +void *etraxfs_dmac_init(target_phys_addr_t base, int nr_channels) +{ + struct fs_dma_ctrl *ctrl = NULL; + + ctrl = qemu_mallocz(sizeof *ctrl); + + ctrl->bh = qemu_bh_new(DMA_run, ctrl); + + ctrl->nr_channels = nr_channels; + ctrl->channels = qemu_mallocz(sizeof ctrl->channels[0] * nr_channels); + + ctrl->map = cpu_register_io_memory(dma_read, dma_write, ctrl, DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(base, nr_channels * 0x2000, ctrl->map); + return ctrl; +} diff --git a/hw/etraxfs_dma.h b/hw/etraxfs_dma.h new file mode 100644 index 0000000..96408ab --- /dev/null +++ b/hw/etraxfs_dma.h @@ -0,0 +1,22 @@ +struct etraxfs_dma_client +{ + /* DMA controller. */ + int channel; + void *ctrl; + + /* client. */ + struct + { + int (*push)(void *opaque, unsigned char *buf, int len); + void (*pull)(void *opaque); + void *opaque; + } client; +}; + +void *etraxfs_dmac_init(target_phys_addr_t base, int nr_channels); +void etraxfs_dmac_connect(void *opaque, int channel, qemu_irq *line, + int input); +void etraxfs_dmac_connect_client(void *opaque, int c, + struct etraxfs_dma_client *cl); +int etraxfs_dmac_input(struct etraxfs_dma_client *client, + void *buf, int len, int eop); diff --git a/hw/etraxfs_eth.c b/hw/etraxfs_eth.c new file mode 100644 index 0000000..6aa4007 --- /dev/null +++ b/hw/etraxfs_eth.c @@ -0,0 +1,613 @@ +/* + * 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 +#include "hw.h" +#include "net.h" +#include "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 + +struct fs_eth +{ + NICState *nic; + NICConf conf; + int ethregs; + + /* Two addrs in the filter. */ + uint8_t macaddr[2][6]; + uint32_t regs[FS_ETH_MAX_REGS]; + + struct etraxfs_dma_client *dma_out; + struct etraxfs_dma_client *dma_in; + + /* MDIO bus. */ + struct qemu_mdio mdio_bus; + unsigned int phyaddr; + int duplex_mismatch; + + /* PHY. */ + struct qemu_phy phy; +}; + +static void eth_validate_duplex(struct fs_eth *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 uint32_t eth_readl (void *opaque, target_phys_addr_t addr) +{ + struct fs_eth *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(struct fs_eth *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_writel (void *opaque, target_phys_addr_t addr, uint32_t value) +{ + struct fs_eth *eth = opaque; + + 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(ð->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(struct fs_eth *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 int eth_can_receive(VLANClientState *nc) +{ + return 1; +} + +static ssize_t eth_receive(VLANClientState *nc, const uint8_t *buf, size_t size) +{ + unsigned char sa_bcast[6] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; + struct fs_eth *eth = DO_UPCAST(NICState, nc, nc)->opaque; + 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) +{ + struct fs_eth *eth = opaque; + + D(printf("%s buf=%p len=%d\n", __func__, buf, len)); + qemu_send_packet(ð->nic->nc, buf, len); + return len; +} + +static void eth_set_link(VLANClientState *nc) +{ + struct fs_eth *eth = DO_UPCAST(NICState, nc, nc)->opaque; + D(printf("%s %d\n", __func__, nc->link_down)); + eth->phy.link = !nc->link_down; +} + +static CPUReadMemoryFunc * const eth_read[] = { + NULL, NULL, + ð_readl, +}; + +static CPUWriteMemoryFunc * const eth_write[] = { + NULL, NULL, + ð_writel, +}; + +static void eth_cleanup(VLANClientState *nc) +{ + struct fs_eth *eth = DO_UPCAST(NICState, nc, nc)->opaque; + + cpu_unregister_io_memory(eth->ethregs); + + qemu_free(eth->dma_out); + qemu_free(eth); +} + +static NetClientInfo net_etraxfs_info = { + .type = NET_CLIENT_TYPE_NIC, + .size = sizeof(NICState), + .can_receive = eth_can_receive, + .receive = eth_receive, + .cleanup = eth_cleanup, + .link_status_changed = eth_set_link, +}; + +void *etraxfs_eth_init(NICInfo *nd, target_phys_addr_t base, int phyaddr) +{ + struct etraxfs_dma_client *dma = NULL; + struct fs_eth *eth = NULL; + + qemu_check_nic_model(nd, "fseth"); + + dma = qemu_mallocz(sizeof *dma * 2); + eth = qemu_mallocz(sizeof *eth); + + dma[0].client.push = eth_tx_push; + dma[0].client.opaque = eth; + dma[1].client.opaque = eth; + dma[1].client.pull = NULL; + + eth->dma_out = dma; + eth->dma_in = dma + 1; + + /* Connect the phy. */ + eth->phyaddr = phyaddr & 0x1f; + tdk_init(ð->phy); + mdio_attach(ð->mdio_bus, ð->phy, eth->phyaddr); + + eth->ethregs = cpu_register_io_memory(eth_read, eth_write, eth, + DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory (base, 0x5c, eth->ethregs); + + memcpy(eth->conf.macaddr.a, nd->macaddr, sizeof(nd->macaddr)); + eth->conf.vlan = nd->vlan; + eth->conf.peer = nd->netdev; + + eth->nic = qemu_new_nic(&net_etraxfs_info, ð->conf, + nd->model, nd->name, eth); + + return dma; +} diff --git a/hw/etraxfs_pic.c b/hw/etraxfs_pic.c new file mode 100644 index 0000000..4feffda --- /dev/null +++ b/hw/etraxfs_pic.c @@ -0,0 +1,169 @@ +/* + * 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 "sysbus.h" +#include "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 + +struct etrax_pic +{ + SysBusDevice busdev; + 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 teh 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 uint32_t pic_readl (void *opaque, target_phys_addr_t addr) +{ + 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_writel (void *opaque, target_phys_addr_t addr, uint32_t value) +{ + 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 CPUReadMemoryFunc * const pic_read[] = { + NULL, NULL, + &pic_readl, +}; + +static CPUWriteMemoryFunc * const pic_write[] = { + NULL, NULL, + &pic_writel, +}; + +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) + return nmi_handler(opaque, irq, level); + + 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 *dev) +{ + struct etrax_pic *s = FROM_SYSBUS(typeof (*s), dev); + int intr_vect_regs; + + qdev_init_gpio_in(&dev->qdev, irq_handler, 32); + sysbus_init_irq(dev, &s->parent_irq); + sysbus_init_irq(dev, &s->parent_nmi); + + intr_vect_regs = cpu_register_io_memory(pic_read, pic_write, s, + DEVICE_NATIVE_ENDIAN); + sysbus_init_mmio(dev, R_MAX * 4, intr_vect_regs); + return 0; +} + +static SysBusDeviceInfo etraxfs_pic_info = { + .init = etraxfs_pic_init, + .qdev.name = "etraxfs,pic", + .qdev.size = sizeof(struct etrax_pic), + .qdev.props = (Property[]) { + DEFINE_PROP_PTR("interrupt_vector", struct etrax_pic, interrupt_vector), + DEFINE_PROP_END_OF_LIST(), + } +}; + +static void etraxfs_pic_register(void) +{ + sysbus_register_withprop(&etraxfs_pic_info); +} + +device_init(etraxfs_pic_register) diff --git a/hw/etraxfs_ser.c b/hw/etraxfs_ser.c new file mode 100644 index 0000000..2787ebd --- /dev/null +++ b/hw/etraxfs_ser.c @@ -0,0 +1,220 @@ +/* + * 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 "sysbus.h" +#include "qemu-char.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 + +struct etrax_serial +{ + SysBusDevice busdev; + 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]; +}; + +static void ser_update_irq(struct etrax_serial *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 uint32_t ser_readl (void *opaque, target_phys_addr_t addr) +{ + struct etrax_serial *s = opaque; + D(CPUState *env = s->env); + 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(printf ("%s " TARGET_FMT_plx "=%x\n", __func__, addr, r)); + break; + } + return r; +} + +static void +ser_writel (void *opaque, target_phys_addr_t addr, uint32_t value) +{ + struct etrax_serial *s = opaque; + unsigned char ch = value; + D(CPUState *env = s->env); + + D(printf ("%s " TARGET_FMT_plx "=%x\n", __func__, addr, value)); + addr >>= 2; + switch (addr) + { + case RW_DOUT: + qemu_chr_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(printf("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 CPUReadMemoryFunc * const ser_read[] = { + NULL, NULL, + &ser_readl, +}; + +static CPUWriteMemoryFunc * const ser_write[] = { + NULL, NULL, + &ser_writel, +}; + +static void serial_receive(void *opaque, const uint8_t *buf, int size) +{ + struct etrax_serial *s = opaque; + int i; + + /* Got a byte. */ + if (s->rx_fifo_len >= 16) { + printf("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) +{ + struct etrax_serial *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 int etraxfs_ser_init(SysBusDevice *dev) +{ + struct etrax_serial *s = FROM_SYSBUS(typeof (*s), dev); + int ser_regs; + + /* transmitter begins ready and idle. */ + s->regs[RS_STAT_DIN] |= (1 << STAT_TR_RDY); + s->regs[RS_STAT_DIN] |= (1 << STAT_TR_IDLE); + + sysbus_init_irq(dev, &s->irq); + ser_regs = cpu_register_io_memory(ser_read, ser_write, s, + DEVICE_NATIVE_ENDIAN); + sysbus_init_mmio(dev, R_MAX * 4, ser_regs); + s->chr = qdev_init_chardev(&dev->qdev); + if (s->chr) + qemu_chr_add_handlers(s->chr, + serial_can_receive, serial_receive, + serial_event, s); + return 0; +} + +static void etraxfs_serial_register(void) +{ + sysbus_register_dev("etraxfs,serial", sizeof (struct etrax_serial), + etraxfs_ser_init); +} + +device_init(etraxfs_serial_register) diff --git a/hw/etraxfs_timer.c b/hw/etraxfs_timer.c new file mode 100644 index 0000000..133741b --- /dev/null +++ b/hw/etraxfs_timer.c @@ -0,0 +1,336 @@ +/* + * 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 "sysbus.h" +#include "sysemu.h" +#include "qemu-timer.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 + +struct etrax_timer { + SysBusDevice busdev; + 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; +}; + +static uint32_t timer_readl (void *opaque, target_phys_addr_t addr) +{ + struct etrax_timer *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_get_clock(vm_clock) / 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(struct etrax_timer *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(struct etrax_timer *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) +{ + struct etrax_timer *t = opaque; + t->r_intr |= 1; + timer_update_irq(t); +} + +static void timer1_hit(void *opaque) +{ + struct etrax_timer *t = opaque; + t->r_intr |= 2; + timer_update_irq(t); +} + +static void watchdog_hit(void *opaque) +{ + struct etrax_timer *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(struct etrax_timer *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_writel (void *opaque, target_phys_addr_t addr, uint32_t value) +{ + struct etrax_timer *t = opaque; + + 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 CPUReadMemoryFunc * const timer_read[] = { + NULL, NULL, + &timer_readl, +}; + +static CPUWriteMemoryFunc * const timer_write[] = { + NULL, NULL, + &timer_writel, +}; + +static void etraxfs_timer_reset(void *opaque) +{ + struct etrax_timer *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) +{ + struct etrax_timer *t = FROM_SYSBUS(typeof (*t), dev); + int timer_regs; + + 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); + + timer_regs = cpu_register_io_memory(timer_read, timer_write, t, + DEVICE_NATIVE_ENDIAN); + sysbus_init_mmio(dev, 0x5c, timer_regs); + + qemu_register_reset(etraxfs_timer_reset, t); + return 0; +} + +static void etraxfs_timer_register(void) +{ + sysbus_register_dev("etraxfs,timer", sizeof (struct etrax_timer), + etraxfs_timer_init); +} + +device_init(etraxfs_timer_register) diff --git a/hw/event_notifier.c b/hw/event_notifier.c new file mode 100644 index 0000000..13f3656 --- /dev/null +++ b/hw/event_notifier.c @@ -0,0 +1,62 @@ +/* + * event notifier support + * + * Copyright Red Hat, Inc. 2010 + * + * Authors: + * Michael S. Tsirkin + * + * This work is licensed under the terms of the GNU GPL, version 2. See + * the COPYING file in the top-level directory. + */ + +#include "hw.h" +#include "event_notifier.h" +#ifdef CONFIG_EVENTFD +#include +#endif + +int event_notifier_init(EventNotifier *e, int active) +{ +#ifdef CONFIG_EVENTFD + int fd = eventfd(!!active, EFD_NONBLOCK | EFD_CLOEXEC); + if (fd < 0) + return -errno; + e->fd = fd; + return 0; +#else + return -ENOSYS; +#endif +} + +void event_notifier_cleanup(EventNotifier *e) +{ + close(e->fd); +} + +int event_notifier_get_fd(EventNotifier *e) +{ + return e->fd; +} + +int event_notifier_test_and_clear(EventNotifier *e) +{ + uint64_t value; + int r = read(e->fd, &value, sizeof(value)); + return r == sizeof(value); +} + +int event_notifier_test(EventNotifier *e) +{ + uint64_t value; + int r = read(e->fd, &value, sizeof(value)); + if (r == sizeof(value)) { + /* restore previous value. */ + int s = write(e->fd, &value, sizeof(value)); + /* never blocks because we use EFD_SEMAPHORE. + * If we didn't we'd get EAGAIN on overflow + * and we'd have to write code to ignore it. */ + assert(s == sizeof(value)); + } + return r == sizeof(value); +} diff --git a/hw/event_notifier.h b/hw/event_notifier.h new file mode 100644 index 0000000..24117ea --- /dev/null +++ b/hw/event_notifier.h @@ -0,0 +1,16 @@ +#ifndef QEMU_EVENT_NOTIFIER_H +#define QEMU_EVENT_NOTIFIER_H + +#include "qemu-common.h" + +struct EventNotifier { + int fd; +}; + +int event_notifier_init(EventNotifier *, int active); +void event_notifier_cleanup(EventNotifier *); +int event_notifier_get_fd(EventNotifier *); +int event_notifier_test_and_clear(EventNotifier *); +int event_notifier_test(EventNotifier *); + +#endif diff --git a/hw/fdc.c b/hw/fdc.c new file mode 100644 index 0000000..4bbcc47 --- /dev/null +++ b/hw/fdc.c @@ -0,0 +1,2110 @@ +/* + * 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.h" +#include "fdc.h" +#include "qemu-error.h" +#include "qemu-timer.h" +#include "isa.h" +#include "sysbus.h" +#include "qdev-addr.h" +#include "blockdev.h" +#include "sysemu.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 + +#define FLOPPY_ERROR(fmt, ...) \ + do { printf("FLOPPY ERROR: %s: " fmt, __func__ , ## __VA_ARGS__); } while (0) + +/********************************************************/ +/* Floppy drive emulation */ + +#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 */ + +/* Floppy disk drive emulation */ +typedef enum FDiskType { + FDRIVE_DISK_288 = 0x01, /* 2.88 MB disk */ + FDRIVE_DISK_144 = 0x02, /* 1.44 MB disk */ + FDRIVE_DISK_720 = 0x03, /* 720 kB disk */ + FDRIVE_DISK_USER = 0x04, /* User defined geometry */ + FDRIVE_DISK_NONE = 0x05, /* No disk */ +} FDiskType; + +typedef enum FDriveType { + FDRIVE_DRV_144 = 0x00, /* 1.44 MB 3"5 drive */ + FDRIVE_DRV_288 = 0x01, /* 2.88 MB 3"5 drive */ + FDRIVE_DRV_120 = 0x02, /* 1.2 MB 5"25 drive */ + FDRIVE_DRV_NONE = 0x03, /* No drive connected */ +} FDriveType; + +typedef enum FDiskFlags { + FDISK_DBL_SIDES = 0x01, +} FDiskFlags; + +typedef struct FDrive { + BlockDriverState *bs; + /* 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 */ +} 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; +} + +static int fd_sector_calc(uint8_t head, uint8_t track, uint8_t sect, + uint8_t last_sect) +{ + return (((track * 2) + 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); +} + +/* 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); + ret = 0; + if (sector != fd_sector(drv)) { +#if 0 + if (!enable_seek) { + FLOPPY_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) + ret = 1; + drv->track = track; + drv->sect = sect; + } + + return ret; +} + +/* Set drive back to track 0 */ +static void fd_recalibrate(FDrive *drv) +{ + FLOPPY_DPRINTF("recalibrate\n"); + drv->head = 0; + drv->track = 0; + drv->sect = 1; +} + +/* Recognize floppy formats */ +typedef struct FDFormat { + FDriveType drive; + FDiskType disk; + uint8_t last_sect; + uint8_t max_track; + uint8_t max_head; + const char *str; +} FDFormat; + +static const FDFormat fd_formats[] = { + /* First entry is default format */ + /* 1.44 MB 3"1/2 floppy disks */ + { FDRIVE_DRV_144, FDRIVE_DISK_144, 18, 80, 1, "1.44 MB 3\"1/2", }, + { FDRIVE_DRV_144, FDRIVE_DISK_144, 20, 80, 1, "1.6 MB 3\"1/2", }, + { FDRIVE_DRV_144, FDRIVE_DISK_144, 21, 80, 1, "1.68 MB 3\"1/2", }, + { FDRIVE_DRV_144, FDRIVE_DISK_144, 21, 82, 1, "1.72 MB 3\"1/2", }, + { FDRIVE_DRV_144, FDRIVE_DISK_144, 21, 83, 1, "1.74 MB 3\"1/2", }, + { FDRIVE_DRV_144, FDRIVE_DISK_144, 22, 80, 1, "1.76 MB 3\"1/2", }, + { FDRIVE_DRV_144, FDRIVE_DISK_144, 23, 80, 1, "1.84 MB 3\"1/2", }, + { FDRIVE_DRV_144, FDRIVE_DISK_144, 24, 80, 1, "1.92 MB 3\"1/2", }, + /* 2.88 MB 3"1/2 floppy disks */ + { FDRIVE_DRV_288, FDRIVE_DISK_288, 36, 80, 1, "2.88 MB 3\"1/2", }, + { FDRIVE_DRV_288, FDRIVE_DISK_288, 39, 80, 1, "3.12 MB 3\"1/2", }, + { FDRIVE_DRV_288, FDRIVE_DISK_288, 40, 80, 1, "3.2 MB 3\"1/2", }, + { FDRIVE_DRV_288, FDRIVE_DISK_288, 44, 80, 1, "3.52 MB 3\"1/2", }, + { FDRIVE_DRV_288, FDRIVE_DISK_288, 48, 80, 1, "3.84 MB 3\"1/2", }, + /* 720 kB 3"1/2 floppy disks */ + { FDRIVE_DRV_144, FDRIVE_DISK_720, 9, 80, 1, "720 kB 3\"1/2", }, + { FDRIVE_DRV_144, FDRIVE_DISK_720, 10, 80, 1, "800 kB 3\"1/2", }, + { FDRIVE_DRV_144, FDRIVE_DISK_720, 10, 82, 1, "820 kB 3\"1/2", }, + { FDRIVE_DRV_144, FDRIVE_DISK_720, 10, 83, 1, "830 kB 3\"1/2", }, + { FDRIVE_DRV_144, FDRIVE_DISK_720, 13, 80, 1, "1.04 MB 3\"1/2", }, + { FDRIVE_DRV_144, FDRIVE_DISK_720, 14, 80, 1, "1.12 MB 3\"1/2", }, + /* 1.2 MB 5"1/4 floppy disks */ + { FDRIVE_DRV_120, FDRIVE_DISK_288, 15, 80, 1, "1.2 kB 5\"1/4", }, + { FDRIVE_DRV_120, FDRIVE_DISK_288, 18, 80, 1, "1.44 MB 5\"1/4", }, + { FDRIVE_DRV_120, FDRIVE_DISK_288, 18, 82, 1, "1.48 MB 5\"1/4", }, + { FDRIVE_DRV_120, FDRIVE_DISK_288, 18, 83, 1, "1.49 MB 5\"1/4", }, + { FDRIVE_DRV_120, FDRIVE_DISK_288, 20, 80, 1, "1.6 MB 5\"1/4", }, + /* 720 kB 5"1/4 floppy disks */ + { FDRIVE_DRV_120, FDRIVE_DISK_288, 9, 80, 1, "720 kB 5\"1/4", }, + { FDRIVE_DRV_120, FDRIVE_DISK_288, 11, 80, 1, "880 kB 5\"1/4", }, + /* 360 kB 5"1/4 floppy disks */ + { FDRIVE_DRV_120, FDRIVE_DISK_288, 9, 40, 1, "360 kB 5\"1/4", }, + { FDRIVE_DRV_120, FDRIVE_DISK_288, 9, 40, 0, "180 kB 5\"1/4", }, + { FDRIVE_DRV_120, FDRIVE_DISK_288, 10, 41, 1, "410 kB 5\"1/4", }, + { FDRIVE_DRV_120, FDRIVE_DISK_288, 10, 42, 1, "420 kB 5\"1/4", }, + /* 320 kB 5"1/4 floppy disks */ + { FDRIVE_DRV_120, FDRIVE_DISK_288, 8, 40, 1, "320 kB 5\"1/4", }, + { FDRIVE_DRV_120, FDRIVE_DISK_288, 8, 40, 0, "160 kB 5\"1/4", }, + /* 360 kB must match 5"1/4 better than 3"1/2... */ + { FDRIVE_DRV_144, FDRIVE_DISK_720, 9, 80, 0, "360 kB 3\"1/2", }, + /* end */ + { FDRIVE_DRV_NONE, FDRIVE_DISK_NONE, -1, -1, 0, NULL, }, +}; + +/* Revalidate a disk drive after a disk change */ +static void fd_revalidate(FDrive *drv) +{ + const FDFormat *parse; + uint64_t nb_sectors, size; + int i, first_match, match; + int nb_heads, max_track, last_sect, ro; + + FLOPPY_DPRINTF("revalidate\n"); + if (drv->bs != NULL && bdrv_is_inserted(drv->bs)) { + ro = bdrv_is_read_only(drv->bs); + bdrv_get_geometry_hint(drv->bs, &nb_heads, &max_track, &last_sect); + if (nb_heads != 0 && max_track != 0 && last_sect != 0) { + FLOPPY_DPRINTF("User defined disk (%d %d %d)", + nb_heads - 1, max_track, last_sect); + } else { + bdrv_get_geometry(drv->bs, &nb_sectors); + match = -1; + first_match = -1; + for (i = 0;; i++) { + parse = &fd_formats[i]; + if (parse->drive == FDRIVE_DRV_NONE) + break; + if (drv->drive == parse->drive || + drv->drive == 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; + drv->drive = parse->drive; + FLOPPY_DPRINTF("%s floppy disk (%d h %d t %d s) %s\n", parse->str, + 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; + } else { + FLOPPY_DPRINTF("No disk in drive\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_reset_fifo(FDCtrl *fdctrl); +static int fdctrl_transfer_handler (void *opaque, int nchan, + int dma_pos, int dma_len); +static void fdctrl_raise_irq(FDCtrl *fdctrl, uint8_t status0); + +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); + +enum { + FD_DIR_WRITE = 0, + FD_DIR_READ = 1, + FD_DIR_SCANE = 2, + FD_DIR_SCANL = 3, + FD_DIR_SCANH = 4, +}; + +enum { + FD_STATE_MULTI = 0x01, /* multi track flag */ + FD_STATE_FORMAT = 0x02, /* format flag */ + FD_STATE_SEEK = 0x04, /* seek 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, +}; + +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_EQPMT = 0x10, + FD_SR0_SEEK = 0x20, + FD_SR0_ABNTERM = 0x40, + FD_SR0_INVCMD = 0x80, + FD_SR0_RDYCHG = 0xc0, +}; + +enum { + 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, +}; + +#define FD_MULTI_TRACK(state) ((state) & FD_STATE_MULTI) +#define FD_DID_SEEK(state) ((state) & FD_STATE_SEEK) +#define FD_FORMAT_CMD(state) ((state) & FD_STATE_FORMAT) + +struct FDCtrl { + /* Controller's identification */ + uint8_t version; + /* HW */ + qemu_irq irq; + int dma_chann; + /* Controller state */ + QEMUTimer *result_timer; + 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 */ + /* Timers state */ + uint8_t timer0; + uint8_t timer1; + /* precompensation */ + uint8_t precomp_trk; + uint8_t config; + uint8_t lock; + /* Power down config (also with status regB access mode */ + uint8_t pwrd; + /* Sun4m quirks? */ + int sun4m; + /* Floppy drives */ + uint8_t num_floppies; + FDrive drives[MAX_FD]; + int reset_sensei; +}; + +typedef struct FDCtrlSysBus { + SysBusDevice busdev; + struct FDCtrl state; +} FDCtrlSysBus; + +typedef struct FDCtrlISABus { + ISADevice busdev; + 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; + + 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); + + 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; + default: + break; + } +} + +static uint32_t fdctrl_read_port (void *opaque, uint32_t reg) +{ + return fdctrl_read(opaque, reg & 7); +} + +static void fdctrl_write_port (void *opaque, uint32_t reg, uint32_t value) +{ + fdctrl_write(opaque, reg & 7, value); +} + +static uint32_t fdctrl_read_mem (void *opaque, target_phys_addr_t reg) +{ + return fdctrl_read(opaque, (uint32_t)reg); +} + +static void fdctrl_write_mem (void *opaque, + target_phys_addr_t reg, uint32_t value) +{ + fdctrl_write(opaque, (uint32_t)reg, value); +} + +static CPUReadMemoryFunc * const fdctrl_mem_read[3] = { + fdctrl_read_mem, + fdctrl_read_mem, + fdctrl_read_mem, +}; + +static CPUWriteMemoryFunc * const fdctrl_mem_write[3] = { + fdctrl_write_mem, + fdctrl_write_mem, + fdctrl_write_mem, +}; + +static CPUReadMemoryFunc * const fdctrl_mem_read_strict[3] = { + fdctrl_read_mem, + NULL, + NULL, +}; + +static CPUWriteMemoryFunc * const fdctrl_mem_write_strict[3] = { + fdctrl_write_mem, + NULL, + NULL, +}; + +static const VMStateDescription vmstate_fdrive = { + .name = "fdrive", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField []) { + VMSTATE_UINT8(head, FDrive), + VMSTATE_UINT8(track, FDrive), + VMSTATE_UINT8(sect, FDrive), + VMSTATE_END_OF_LIST() + } +}; + +static void fdc_pre_save(void *opaque) +{ + FDCtrl *s = opaque; + + s->dor_vmstate = s->dor | GET_CUR_DRV(s); +} + +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; + return 0; +} + +static const VMStateDescription vmstate_fdc = { + .name = "fdc", + .version_id = 2, + .minimum_version_id = 2, + .minimum_version_id_old = 2, + .pre_save = fdc_pre_save, + .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() + } +}; + +static void fdctrl_external_reset_sysbus(DeviceState *d) +{ + FDCtrlSysBus *sys = container_of(d, FDCtrlSysBus, busdev.qdev); + FDCtrl *s = &sys->state; + + fdctrl_reset(s, 0); +} + +static void fdctrl_external_reset_isa(DeviceState *d) +{ + FDCtrlISABus *isa = container_of(d, FDCtrlISABus, busdev.qdev); + 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"); + } +} + +/* XXX: may change if moved to bdrv */ +int fdctrl_get_drive_type(FDCtrl *fdctrl, int drive_num) +{ + return fdctrl->drives[drive_num].drive; +} + +/* Change IRQ state */ +static void fdctrl_reset_irq(FDCtrl *fdctrl) +{ + 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, uint8_t status0) +{ + /* Sparc mutation */ + if (fdctrl->sun4m && (fdctrl->msr & FD_MSR_CMDBUSY)) { + /* XXX: not sure */ + fdctrl->msr &= ~FD_MSR_CMDBUSY; + fdctrl->msr |= FD_MSR_RQM | FD_MSR_DIO; + fdctrl->status0 = status0; + return; + } + if (!(fdctrl->sra & FD_SRA_INTPEND)) { + qemu_set_irq(fdctrl->irq, 1); + fdctrl->sra |= FD_SRA_INTPEND; + } + fdctrl->reset_sensei = 0; + fdctrl->status0 = status0; + 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].bs) + 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; + /* 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_reset_fifo(fdctrl); + if (do_irq) { + fdctrl_raise_irq(fdctrl, FD_SR0_RDYCHG); + 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; + + /* Sparc mutation */ + if (fdctrl->sun4m) { + retval |= FD_MSR_DIO; + fdctrl_reset_irq(fdctrl); + }; + + 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; +} + +static int fdctrl_media_changed(FDrive *drv) +{ + int ret; + + if (!drv->bs) + return 0; + ret = bdrv_media_changed(drv->bs); + if (ret) { + fd_revalidate(drv); + } + return ret; +} + +/* Digital input register : 0x07 (read-only) */ +static uint32_t fdctrl_read_dir(FDCtrl *fdctrl) +{ + uint32_t retval = 0; + + if (fdctrl_media_changed(drv0(fdctrl)) + || fdctrl_media_changed(drv1(fdctrl)) +#if MAX_FD == 4 + || fdctrl_media_changed(drv2(fdctrl)) + || fdctrl_media_changed(drv3(fdctrl)) +#endif + ) + retval |= FD_DIR_DSKCHG; + if (retval != 0) { + FLOPPY_DPRINTF("Floppy digital input register: 0x%02x\n", retval); + } + + return retval; +} + +/* FIFO state control */ +static void fdctrl_reset_fifo(FDCtrl *fdctrl) +{ + fdctrl->data_dir = FD_DIR_WRITE; + fdctrl->data_pos = 0; + fdctrl->msr &= ~(FD_MSR_CMDBUSY | FD_MSR_DIO); +} + +/* Set FIFO status for the host to read */ +static void fdctrl_set_fifo(FDCtrl *fdctrl, int fifo_len, int do_irq) +{ + 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; + if (do_irq) + fdctrl_raise_irq(fdctrl, 0x00); +} + +/* Set an error: unimplemented/unknown command */ +static void fdctrl_unimplemented(FDCtrl *fdctrl, int direction) +{ + FLOPPY_ERROR("unimplemented command 0x%02x\n", fdctrl->fifo[0]); + fdctrl->fifo[0] = FD_SR0_INVCMD; + fdctrl_set_fifo(fdctrl, 1, 0); +} + +/* Seek to next sector */ +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 */ + if (cur_drv->sect >= cur_drv->last_sect || + cur_drv->sect == fdctrl->eot) { + cur_drv->sect = 1; + if (FD_MULTI_TRACK(fdctrl->data_state)) { + if (cur_drv->head == 0 && + (cur_drv->flags & FDISK_DBL_SIDES) != 0) { + cur_drv->head = 1; + } else { + cur_drv->head = 0; + cur_drv->track++; + if ((cur_drv->flags & FDISK_DBL_SIDES) == 0) + return 0; + } + } else { + cur_drv->track++; + return 0; + } + FLOPPY_DPRINTF("seek to next track (%d %02x %02x => %d)\n", + cur_drv->head, cur_drv->track, + cur_drv->sect, fd_sector(cur_drv)); + } else { + cur_drv->sect++; + } + return 1; +} + +/* 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); + FLOPPY_DPRINTF("transfer status: %02x %02x %02x (%02x)\n", + status0, status1, status2, + status0 | (cur_drv->head << 2) | GET_CUR_DRV(fdctrl)); + fdctrl->fifo[0] = status0 | (cur_drv->head << 2) | GET_CUR_DRV(fdctrl); + 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_set_fifo(fdctrl, 7, 1); +} + +/* 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; + int did_seek = 0; + + 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)); + 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: + did_seek = 1; + break; + default: + break; + } + + /* Set the FIFO state */ + fdctrl->data_dir = direction; + fdctrl->data_pos = 0; + fdctrl->msr |= FD_MSR_CMDBUSY; + if (fdctrl->fifo[0] & 0x80) + fdctrl->data_state |= FD_STATE_MULTI; + else + fdctrl->data_state &= ~FD_STATE_MULTI; + if (did_seek) + fdctrl->data_state |= FD_STATE_SEEK; + else + fdctrl->data_state &= ~FD_STATE_SEEK; + if (fdctrl->fifo[5] == 00) { + 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)) { + /* No access is allowed until DMA transfer has completed */ + fdctrl->msr &= ~FD_MSR_RQM; + /* Now, we just have to wait for the DMA controller to + * recall us... + */ + DMA_hold_DREQ(fdctrl->dma_chann); + DMA_schedule(fdctrl->dma_chann); + return; + } else { + FLOPPY_ERROR("dma_mode=%d direction=%d\n", dma_mode, direction); + } + } + FLOPPY_DPRINTF("start non-DMA transfer\n"); + fdctrl->msr |= FD_MSR_NONDMA; + if (direction != FD_DIR_WRITE) + fdctrl->msr |= FD_MSR_DIO; + /* IO based transfer: calculate len */ + fdctrl_raise_irq(fdctrl, 0x00); + + return; +} + +/* Prepare a transfer of deleted data */ +static void fdctrl_start_transfer_del(FDCtrl *fdctrl, int direction) +{ + FLOPPY_ERROR("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->bs == 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 (bdrv_read(cur_drv->bs, 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 */ + DMA_read_memory (nchan, fdctrl->fifo + rel_pos, + fdctrl->data_pos, len); + if (bdrv_write(cur_drv->bs, fd_sector(cur_drv), + fdctrl->fifo, 1) < 0) { + FLOPPY_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; + 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; + if (FD_DID_SEEK(fdctrl->data_state)) + status0 |= FD_SR0_SEEK; + 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; + int pos; + + cur_drv = get_cur_drv(fdctrl); + fdctrl->dsr &= ~FD_DSR_PWRDOWN; + if (!(fdctrl->msr & FD_MSR_RQM) || !(fdctrl->msr & FD_MSR_DIO)) { + FLOPPY_ERROR("controller not ready for reading\n"); + return 0; + } + pos = fdctrl->data_pos; + if (fdctrl->msr & FD_MSR_NONDMA) { + pos %= FD_SECTOR_LEN; + 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 (bdrv_read(cur_drv->bs, 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); + } + } + } + retval = fdctrl->fifo[pos]; + if (++fdctrl->data_pos == fdctrl->data_len) { + fdctrl->data_pos = 0; + /* Switch from transfer mode to status mode + * then from status mode to command mode + */ + if (fdctrl->msr & FD_MSR_NONDMA) { + fdctrl_stop_transfer(fdctrl, FD_SR0_SEEK, 0x00, 0x00); + } else { + fdctrl_reset_fifo(fdctrl); + fdctrl_reset_irq(fdctrl); + } + } + 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)); + 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->data_state |= FD_STATE_SEEK; + break; + default: + break; + } + memset(fdctrl->fifo, 0, FD_SECTOR_LEN); + if (cur_drv->bs == NULL || + bdrv_write(cur_drv->bs, fd_sector(cur_drv), fdctrl->fifo, 1) < 0) { + FLOPPY_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 */ + if (FD_DID_SEEK(fdctrl->data_state)) + fdctrl_stop_transfer(fdctrl, FD_SR0_SEEK, 0x00, 0x00); + else + 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_set_fifo(fdctrl, 1, fdctrl->lock); +} + +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_set_fifo(fdctrl, 10, 0); +} + +static void fdctrl_handle_version(FDCtrl *fdctrl, int direction) +{ + /* Controller's version */ + fdctrl->fifo[0] = fdctrl->version; + fdctrl_set_fifo(fdctrl, 1, 1); +} + +static void fdctrl_handle_partid(FDCtrl *fdctrl, int direction) +{ + fdctrl->fifo[0] = 0x41; /* Stepping 1 */ + fdctrl_set_fifo(fdctrl, 1, 0); +} + +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_reset_fifo(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_set_fifo(fdctrl, 15, 1); +} + +static void fdctrl_handle_readid(FDCtrl *fdctrl, int direction) +{ + FDrive *cur_drv = get_cur_drv(fdctrl); + + /* XXX: should set main status register to busy */ + cur_drv->head = (fdctrl->fifo[1] >> 2) & 1; + qemu_mod_timer(fdctrl->result_timer, + qemu_get_clock(vm_clock) + (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; + fdctrl->data_state &= ~FD_STATE_SEEK; + 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_reset_fifo(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_set_fifo(fdctrl, 1, 0); +} + +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_reset_fifo(fdctrl); + /* Raise Interrupt */ + fdctrl_raise_irq(fdctrl, FD_SR0_SEEK); +} + +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 { + /* XXX: status0 handling is broken for read/write + commands, so we do this hack. It should be suppressed + ASAP */ + fdctrl->fifo[0] = + FD_SR0_SEEK | (cur_drv->head << 2) | GET_CUR_DRV(fdctrl); + } + + fdctrl->fifo[1] = cur_drv->track; + fdctrl_set_fifo(fdctrl, 2, 0); + 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_reset_fifo(fdctrl); + if (fdctrl->fifo[2] > cur_drv->max_track) { + fdctrl_raise_irq(fdctrl, FD_SR0_ABNTERM | FD_SR0_SEEK); + } else { + cur_drv->track = fdctrl->fifo[2]; + /* Raise Interrupt */ + fdctrl_raise_irq(fdctrl, FD_SR0_SEEK); + } +} + +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_reset_fifo(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_reset_fifo(fdctrl); +} + +static void fdctrl_handle_powerdown_mode(FDCtrl *fdctrl, int direction) +{ + fdctrl->pwrd = fdctrl->fifo[1]; + fdctrl->fifo[0] = fdctrl->fifo[1]; + fdctrl_set_fifo(fdctrl, 1, 1); +} + +static void fdctrl_handle_option(FDCtrl *fdctrl, int direction) +{ + /* No result back */ + fdctrl_reset_fifo(fdctrl); +} + +static void fdctrl_handle_drive_specification_command(FDCtrl *fdctrl, int direction) +{ + FDrive *cur_drv = get_cur_drv(fdctrl); + + if (fdctrl->fifo[fdctrl->data_pos - 1] & 0x80) { + /* Command parameters done */ + if (fdctrl->fifo[fdctrl->data_pos - 1] & 0x40) { + fdctrl->fifo[0] = fdctrl->fifo[1]; + fdctrl->fifo[2] = 0; + fdctrl->fifo[3] = 0; + fdctrl_set_fifo(fdctrl, 4, 1); + } else { + fdctrl_reset_fifo(fdctrl); + } + } else if (fdctrl->data_len > 7) { + /* ERROR */ + fdctrl->fifo[0] = 0x80 | + (cur_drv->head << 2) | GET_CUR_DRV(fdctrl); + fdctrl_set_fifo(fdctrl, 1, 1); + } +} + +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 >= cur_drv->max_track) { + cur_drv->track = cur_drv->max_track - 1; + } else { + cur_drv->track += fdctrl->fifo[2]; + } + fdctrl_reset_fifo(fdctrl); + /* Raise Interrupt */ + fdctrl_raise_irq(fdctrl, FD_SR0_SEEK); +} + +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->track = 0; + } else { + cur_drv->track -= fdctrl->fifo[2]; + } + fdctrl_reset_fifo(fdctrl); + /* Raise Interrupt */ + fdctrl_raise_irq(fdctrl, FD_SR0_SEEK); +} + +static const struct { + uint8_t value; + uint8_t mask; + const char* name; + int parameters; + void (*handler)(FDCtrl *fdctrl, int direction); + int direction; +} 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_unimplemented }, + { 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 void fdctrl_write_data(FDCtrl *fdctrl, uint32_t value) +{ + FDrive *cur_drv; + int 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_ERROR("controller not ready for writing\n"); + return; + } + fdctrl->dsr &= ~FD_DSR_PWRDOWN; + /* Is it write command time ? */ + if (fdctrl->msr & FD_MSR_NONDMA) { + /* FIFO data write */ + pos = fdctrl->data_pos++; + pos %= FD_SECTOR_LEN; + fdctrl->fifo[pos] = value; + if (pos == FD_SECTOR_LEN - 1 || + fdctrl->data_pos == fdctrl->data_len) { + cur_drv = get_cur_drv(fdctrl); + if (bdrv_write(cur_drv->bs, fd_sector(cur_drv), fdctrl->fifo, 1) < 0) { + FLOPPY_ERROR("writing sector %d\n", fd_sector(cur_drv)); + return; + } + if (!fdctrl_seek_to_next_sect(fdctrl, cur_drv)) { + FLOPPY_DPRINTF("error seeking to next sector %d\n", + fd_sector(cur_drv)); + return; + } + } + /* Switch from transfer mode to status mode + * then from status mode to command mode + */ + if (fdctrl->data_pos == fdctrl->data_len) + fdctrl_stop_transfer(fdctrl, FD_SR0_SEEK, 0x00, 0x00); + return; + } + if (fdctrl->data_pos == 0) { + /* Command */ + pos = command_to_handler[value & 0xff]; + FLOPPY_DPRINTF("%s command\n", handlers[pos].name); + fdctrl->data_len = handlers[pos].parameters + 1; + } + + FLOPPY_DPRINTF("%s: %02x\n", __func__, value); + fdctrl->fifo[fdctrl->data_pos++] = value; + if (fdctrl->data_pos == fdctrl->data_len) { + /* We now have all parameters + * and will be able to treat the command + */ + if (fdctrl->data_state & FD_STATE_FORMAT) { + fdctrl_format_sector(fdctrl); + return; + } + + pos = command_to_handler[fdctrl->fifo[0] & 0xff]; + FLOPPY_DPRINTF("treat %s command\n", handlers[pos].name); + (*handlers[pos].handler)(fdctrl, handlers[pos].direction); + } +} + +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; + } + fdctrl_stop_transfer(fdctrl, 0x00, 0x00, 0x00); +} + +/* Init functions */ +static int fdctrl_connect_drives(FDCtrl *fdctrl) +{ + unsigned int i; + FDrive *drive; + + for (i = 0; i < MAX_FD; i++) { + drive = &fdctrl->drives[i]; + + if (drive->bs) { + if (bdrv_get_on_error(drive->bs, 0) != BLOCK_ERR_STOP_ENOSPC) { + error_report("fdc doesn't support drive option werror"); + return -1; + } + if (bdrv_get_on_error(drive->bs, 1) != BLOCK_ERR_REPORT) { + error_report("fdc doesn't support drive option rerror"); + return -1; + } + } + + fd_init(drive); + fd_revalidate(drive); + if (drive->bs) { + bdrv_set_removable(drive->bs, 1); + } + } + return 0; +} + +FDCtrl *fdctrl_init_isa(DriveInfo **fds) +{ + ISADevice *dev; + + dev = isa_create("isa-fdc"); + if (fds[0]) { + qdev_prop_set_drive_nofail(&dev->qdev, "driveA", fds[0]->bdrv); + } + if (fds[1]) { + qdev_prop_set_drive_nofail(&dev->qdev, "driveB", fds[1]->bdrv); + } + qdev_init_nofail(&dev->qdev); + return &(DO_UPCAST(FDCtrlISABus, busdev, dev)->state); +} + +FDCtrl *fdctrl_init_sysbus(qemu_irq irq, int dma_chann, + target_phys_addr_t mmio_base, DriveInfo **fds) +{ + FDCtrl *fdctrl; + DeviceState *dev; + FDCtrlSysBus *sys; + + dev = qdev_create(NULL, "sysbus-fdc"); + sys = DO_UPCAST(FDCtrlSysBus, busdev.qdev, dev); + fdctrl = &sys->state; + fdctrl->dma_chann = dma_chann; /* FIXME */ + if (fds[0]) { + qdev_prop_set_drive_nofail(dev, "driveA", fds[0]->bdrv); + } + if (fds[1]) { + qdev_prop_set_drive_nofail(dev, "driveB", fds[1]->bdrv); + } + qdev_init_nofail(dev); + sysbus_connect_irq(&sys->busdev, 0, irq); + sysbus_mmio_map(&sys->busdev, 0, mmio_base); + + return fdctrl; +} + +FDCtrl *sun4m_fdctrl_init(qemu_irq irq, target_phys_addr_t io_base, + DriveInfo **fds, qemu_irq *fdc_tc) +{ + DeviceState *dev; + FDCtrlSysBus *sys; + FDCtrl *fdctrl; + + dev = qdev_create(NULL, "SUNW,fdtwo"); + if (fds[0]) { + qdev_prop_set_drive_nofail(dev, "drive", fds[0]->bdrv); + } + qdev_init_nofail(dev); + sys = DO_UPCAST(FDCtrlSysBus, busdev.qdev, dev); + fdctrl = &sys->state; + sysbus_connect_irq(&sys->busdev, 0, irq); + sysbus_mmio_map(&sys->busdev, 0, io_base); + *fdc_tc = qdev_get_gpio_in(dev, 0); + + return fdctrl; +} + +static int fdctrl_init_common(FDCtrl *fdctrl) +{ + 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 = qemu_new_timer(vm_clock, + 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); + return fdctrl_connect_drives(fdctrl); +} + +static int isabus_fdc_init1(ISADevice *dev) +{ + FDCtrlISABus *isa = DO_UPCAST(FDCtrlISABus, busdev, dev); + FDCtrl *fdctrl = &isa->state; + int iobase = 0x3f0; + int isairq = 6; + int dma_chann = 2; + int ret; + + register_ioport_read(iobase + 0x01, 5, 1, + &fdctrl_read_port, fdctrl); + register_ioport_read(iobase + 0x07, 1, 1, + &fdctrl_read_port, fdctrl); + register_ioport_write(iobase + 0x01, 5, 1, + &fdctrl_write_port, fdctrl); + register_ioport_write(iobase + 0x07, 1, 1, + &fdctrl_write_port, fdctrl); + isa_init_ioport_range(dev, iobase, 6); + isa_init_ioport(dev, iobase + 7); + + isa_init_irq(&isa->busdev, &fdctrl->irq, isairq); + fdctrl->dma_chann = dma_chann; + + qdev_set_legacy_instance_id(&dev->qdev, iobase, 2); + ret = fdctrl_init_common(fdctrl); + + add_boot_device_path(isa->bootindexA, &dev->qdev, "/floppy@0"); + add_boot_device_path(isa->bootindexB, &dev->qdev, "/floppy@1"); + + return ret; +} + +static int sysbus_fdc_init1(SysBusDevice *dev) +{ + FDCtrlSysBus *sys = DO_UPCAST(FDCtrlSysBus, busdev, dev); + FDCtrl *fdctrl = &sys->state; + int io; + int ret; + + io = cpu_register_io_memory(fdctrl_mem_read, fdctrl_mem_write, fdctrl, + DEVICE_NATIVE_ENDIAN); + sysbus_init_mmio(dev, 0x08, io); + sysbus_init_irq(dev, &fdctrl->irq); + qdev_init_gpio_in(&dev->qdev, fdctrl_handle_tc, 1); + fdctrl->dma_chann = -1; + + qdev_set_legacy_instance_id(&dev->qdev, io, 2); + ret = fdctrl_init_common(fdctrl); + + return ret; +} + +static int sun4m_fdc_init1(SysBusDevice *dev) +{ + FDCtrl *fdctrl = &(FROM_SYSBUS(FDCtrlSysBus, dev)->state); + int io; + + io = cpu_register_io_memory(fdctrl_mem_read_strict, + fdctrl_mem_write_strict, fdctrl, + DEVICE_NATIVE_ENDIAN); + sysbus_init_mmio(dev, 0x08, io); + sysbus_init_irq(dev, &fdctrl->irq); + qdev_init_gpio_in(&dev->qdev, fdctrl_handle_tc, 1); + + fdctrl->sun4m = 1; + qdev_set_legacy_instance_id(&dev->qdev, io, 2); + return fdctrl_init_common(fdctrl); +} + +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 ISADeviceInfo isa_fdc_info = { + .init = isabus_fdc_init1, + .qdev.name = "isa-fdc", + .qdev.fw_name = "fdc", + .qdev.size = sizeof(FDCtrlISABus), + .qdev.no_user = 1, + .qdev.vmsd = &vmstate_isa_fdc, + .qdev.reset = fdctrl_external_reset_isa, + .qdev.props = (Property[]) { + DEFINE_PROP_DRIVE("driveA", FDCtrlISABus, state.drives[0].bs), + DEFINE_PROP_DRIVE("driveB", FDCtrlISABus, state.drives[1].bs), + DEFINE_PROP_INT32("bootindexA", FDCtrlISABus, bootindexA, -1), + DEFINE_PROP_INT32("bootindexB", FDCtrlISABus, bootindexB, -1), + DEFINE_PROP_END_OF_LIST(), + }, +}; + +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 SysBusDeviceInfo sysbus_fdc_info = { + .init = sysbus_fdc_init1, + .qdev.name = "sysbus-fdc", + .qdev.size = sizeof(FDCtrlSysBus), + .qdev.vmsd = &vmstate_sysbus_fdc, + .qdev.reset = fdctrl_external_reset_sysbus, + .qdev.props = (Property[]) { + DEFINE_PROP_DRIVE("driveA", FDCtrlSysBus, state.drives[0].bs), + DEFINE_PROP_DRIVE("driveB", FDCtrlSysBus, state.drives[1].bs), + DEFINE_PROP_END_OF_LIST(), + }, +}; + +static SysBusDeviceInfo sun4m_fdc_info = { + .init = sun4m_fdc_init1, + .qdev.name = "SUNW,fdtwo", + .qdev.size = sizeof(FDCtrlSysBus), + .qdev.vmsd = &vmstate_sysbus_fdc, + .qdev.reset = fdctrl_external_reset_sysbus, + .qdev.props = (Property[]) { + DEFINE_PROP_DRIVE("drive", FDCtrlSysBus, state.drives[0].bs), + DEFINE_PROP_END_OF_LIST(), + }, +}; + +static void fdc_register_devices(void) +{ + isa_qdev_register(&isa_fdc_info); + sysbus_register_withprop(&sysbus_fdc_info); + sysbus_register_withprop(&sun4m_fdc_info); +} + +device_init(fdc_register_devices) diff --git a/hw/fdc.h b/hw/fdc.h new file mode 100644 index 0000000..242730a --- /dev/null +++ b/hw/fdc.h @@ -0,0 +1,16 @@ +#ifndef HW_FDC_H +#define HW_FDC_H + +/* fdc.c */ +#define MAX_FD 2 + +typedef struct FDCtrl FDCtrl; + +FDCtrl *fdctrl_init_isa(DriveInfo **fds); +FDCtrl *fdctrl_init_sysbus(qemu_irq irq, int dma_chann, + target_phys_addr_t mmio_base, DriveInfo **fds); +FDCtrl *sun4m_fdctrl_init(qemu_irq irq, target_phys_addr_t io_base, + DriveInfo **fds, qemu_irq *fdc_tc); +int fdctrl_get_drive_type(FDCtrl *fdctrl, int drive_num); + +#endif diff --git a/hw/file-op-9p.h b/hw/file-op-9p.h new file mode 100644 index 0000000..126e60e --- /dev/null +++ b/hw/file-op-9p.h @@ -0,0 +1,107 @@ +/* + * Virtio 9p + * + * Copyright IBM, Corp. 2010 + * + * Authors: + * Aneesh Kumar K.V + * + * This work is licensed under the terms of the GNU GPL, version 2. See + * the COPYING file in the top-level directory. + * + */ +#ifndef _FILEOP_H +#define _FILEOP_H +#include +#include +#include +#include +#include +#include +#include +#define SM_LOCAL_MODE_BITS 0600 +#define SM_LOCAL_DIR_MODE_BITS 0700 + +typedef enum +{ + /* + * Server will try to set uid/gid. + * On failure ignore the error. + */ + SM_NONE = 0, + /* + * uid/gid set on fileserver files + */ + SM_PASSTHROUGH = 1, + /* + * uid/gid part of xattr + */ + SM_MAPPED, +} SecModel; + +typedef struct FsCred +{ + uid_t fc_uid; + gid_t fc_gid; + mode_t fc_mode; + dev_t fc_rdev; +} FsCred; + +struct xattr_operations; + +typedef struct FsContext +{ + char *fs_root; + SecModel fs_sm; + uid_t uid; + struct xattr_operations **xops; +} FsContext; + +void cred_init(FsCred *); + +typedef struct FileOperations +{ + int (*lstat)(FsContext *, const char *, struct stat *); + ssize_t (*readlink)(FsContext *, const char *, char *, size_t); + int (*chmod)(FsContext *, const char *, FsCred *); + int (*chown)(FsContext *, const char *, FsCred *); + int (*mknod)(FsContext *, const char *, FsCred *); + int (*utimensat)(FsContext *, const char *, const struct timespec *); + int (*remove)(FsContext *, const char *); + int (*symlink)(FsContext *, const char *, const char *, FsCred *); + int (*link)(FsContext *, const char *, const char *); + int (*setuid)(FsContext *, uid_t); + int (*close)(FsContext *, int); + int (*closedir)(FsContext *, DIR *); + DIR *(*opendir)(FsContext *, const char *); + int (*open)(FsContext *, const char *, int); + int (*open2)(FsContext *, const char *, int, FsCred *); + void (*rewinddir)(FsContext *, DIR *); + off_t (*telldir)(FsContext *, DIR *); + struct dirent *(*readdir)(FsContext *, DIR *); + void (*seekdir)(FsContext *, DIR *, off_t); + ssize_t (*preadv)(FsContext *, int, const struct iovec *, int, off_t); + ssize_t (*pwritev)(FsContext *, int, const struct iovec *, int, off_t); + int (*mkdir)(FsContext *, const char *, FsCred *); + int (*fstat)(FsContext *, int, struct stat *); + int (*rename)(FsContext *, const char *, const char *); + int (*truncate)(FsContext *, const char *, off_t); + int (*fsync)(FsContext *, int, int); + int (*statfs)(FsContext *s, const char *path, struct statfs *stbuf); + ssize_t (*lgetxattr)(FsContext *, const char *, + const char *, void *, size_t); + ssize_t (*llistxattr)(FsContext *, const char *, void *, size_t); + int (*lsetxattr)(FsContext *, const char *, + const char *, void *, size_t, int); + int (*lremovexattr)(FsContext *, const char *, const char *); + void *opaque; +} FileOperations; + +static inline const char *rpath(FsContext *ctx, const char *path) +{ + /* FIXME: so wrong... */ + static char buffer[4096]; + snprintf(buffer, sizeof(buffer), "%s/%s", ctx->fs_root, path); + return buffer; +} +#endif diff --git a/hw/firmware_abi.h b/hw/firmware_abi.h new file mode 100644 index 0000000..5e6e5d4 --- /dev/null +++ b/hw/firmware_abi.h @@ -0,0 +1,73 @@ +#ifndef FIRMWARE_ABI_H +#define FIRMWARE_ABI_H + +/* OpenBIOS NVRAM partition */ +struct OpenBIOS_nvpart_v1 { + uint8_t signature; + uint8_t checksum; + uint16_t len; // BE, length divided by 16 + char name[12]; +}; + +#define OPENBIOS_PART_SYSTEM 0x70 +#define OPENBIOS_PART_FREE 0x7f + +static inline void +OpenBIOS_finish_partition(struct OpenBIOS_nvpart_v1 *header, uint32_t size) +{ + unsigned int i, sum; + uint8_t *tmpptr; + + // Length divided by 16 + header->len = cpu_to_be16(size >> 4); + + // Checksum + tmpptr = (uint8_t *)header; + sum = *tmpptr; + for (i = 0; i < 14; i++) { + sum += tmpptr[2 + i]; + sum = (sum + ((sum & 0xff00) >> 8)) & 0xff; + } + header->checksum = sum & 0xff; +} + +static inline uint32_t +OpenBIOS_set_var(uint8_t *nvram, uint32_t addr, const char *str) +{ + uint32_t len; + + len = strlen(str) + 1; + memcpy(&nvram[addr], str, len); + + return addr + len; +} + +/* Sun IDPROM structure at the end of NVRAM */ +/* from http://www.squirrel.com/squirrel/sun-nvram-hostid.faq.html */ +struct Sun_nvram { + uint8_t type; /* always 01 */ + uint8_t machine_id; /* first byte of host id (machine type) */ + uint8_t macaddr[6]; /* 6 byte ethernet address (first 3 bytes 08, 00, 20) */ + uint8_t date[4]; /* date of manufacture */ + uint8_t hostid[3]; /* remaining 3 bytes of host id (serial number) */ + uint8_t checksum; /* bitwise xor of previous bytes */ +}; + +static inline void +Sun_init_header(struct Sun_nvram *header, const uint8_t *macaddr, int machine_id) +{ + uint8_t tmp, *tmpptr; + unsigned int i; + + header->type = 1; + header->machine_id = machine_id & 0xff; + memcpy(&header->macaddr, macaddr, 6); + /* Calculate checksum */ + tmp = 0; + tmpptr = (uint8_t *)header; + for (i = 0; i < 15; i++) + tmp ^= tmpptr[i]; + + header->checksum = tmp; +} +#endif /* FIRMWARE_ABI_H */ diff --git a/hw/flash.h b/hw/flash.h new file mode 100644 index 0000000..d7d103e --- /dev/null +++ b/hw/flash.h @@ -0,0 +1,54 @@ +/* NOR flash devices */ +typedef struct pflash_t pflash_t; + +/* pflash_cfi01.c */ +pflash_t *pflash_cfi01_register(target_phys_addr_t base, ram_addr_t off, + BlockDriverState *bs, + uint32_t sector_len, int nb_blocs, int width, + uint16_t id0, uint16_t id1, + uint16_t id2, uint16_t id3, int be); + +/* pflash_cfi02.c */ +pflash_t *pflash_cfi02_register(target_phys_addr_t base, ram_addr_t off, + BlockDriverState *bs, 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); + +/* nand.c */ +typedef struct NANDFlashState NANDFlashState; +NANDFlashState *nand_init(int manf_id, int chip_id); +void nand_done(NANDFlashState *s); +void nand_setpins(NANDFlashState *s, + int cle, int ale, int ce, int wp, int gnd); +void nand_getpins(NANDFlashState *s, int *rb); +void nand_setio(NANDFlashState *s, uint8_t value); +uint8_t nand_getio(NANDFlashState *s); + +#define NAND_MFR_TOSHIBA 0x98 +#define NAND_MFR_SAMSUNG 0xec +#define NAND_MFR_FUJITSU 0x04 +#define NAND_MFR_NATIONAL 0x8f +#define NAND_MFR_RENESAS 0x07 +#define NAND_MFR_STMICRO 0x20 +#define NAND_MFR_HYNIX 0xad +#define NAND_MFR_MICRON 0x2c + +/* onenand.c */ +void onenand_base_update(void *opaque, target_phys_addr_t new); +void onenand_base_unmap(void *opaque); +void *onenand_init(uint32_t id, int regshift, qemu_irq irq); +void *onenand_raw_otp(void *opaque); + +/* ecc.c */ +typedef struct { + uint8_t cp; /* Column parity */ + uint16_t lp[2]; /* Line parity */ + uint16_t count; +} ECCState; + +uint8_t ecc_digest(ECCState *s, uint8_t sample); +void ecc_reset(ECCState *s); +extern VMStateDescription vmstate_ecc_state; diff --git a/hw/fmopl.c b/hw/fmopl.c new file mode 100644 index 0000000..3df1806 --- /dev/null +++ b/hw/fmopl.c @@ -0,0 +1,1391 @@ +/* +** +** 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 . + */ + +#define INLINE static inline +#define HAS_YM3812 1 + +#include +#include +#include +#include +#include +//#include "driver.h" /* use M.A.M.E. */ +#include "fmopl.h" + +#ifndef PI +#define PI 3.14159265358979323846 +#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<=LOG_LEVEL ) logerror x +#define LOG(n,x) + +/* --------------------- subroutines --------------------- */ + +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 */ +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 */ +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 */ +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 ----- */ +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 ----- */ +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]<evc = EG_DST; + SLOT->eve = EG_DED; + SLOT->evs = SLOT->evsr; + } +} + +/* ---------- calcrate Envelope Generator & Phase Generator ---------- */ +/* return : envelope output */ +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 algorythm connection */ +static void set_algorythm( OPL_CH *CH) +{ + INT32 *carrier = &outd[0]; + CH->connect1 = CH->CON ? carrier : &feedback2; + CH->connect2 = carrier; +} + +/* ---------- frequency counter for operater update ---------- */ +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 */ +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 */ +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 */ +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 */ +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 ---------- */ +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 rythm block ---------- */ +#define WHITE_NOISE_db 6.0 +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<AR_TABLE[i] = rate / ARRATE; + OPL->DR_TABLE[i] = rate / DRRATE; + } + for (i = 60;i < 76;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<AR_TABLE[i]) * (1000.0 / OPL->rate), + ((double)(EG_ENT<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< 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 ) 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; iSLOT[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_initalize(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<rate * 3.7 * ((double)OPL->clock/3600000) : 0; + OPL->vibIncr = OPL->rate ? (double)VIB_ENT*(1<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:controll */ + 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;cmax_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 controll : 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->rythm^v; + OPL->ams_table = &AMS_TABLE[v&0x80 ? AMS_ENT : 0]; + OPL->vib_table = &VIB_TABLE[v&0x40 ? VIB_ENT : 0]; + OPL->rythm = v&0x3f; + if(OPL->rythm&0x20) + { +#if 0 + usrintf_showmessage("OPL Rythm 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_algorythm(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 rythm = OPL->rythm&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]; + /* rythm 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 = rythm ? &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(rythm) + 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_chipamsCnt; + UINT32 vibCnt = OPL->vibCnt; + UINT8 rythm = OPL->rythm&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]; + /* rythm 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 = rythm ? &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(rythm) + 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<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_initalize(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_chipaddress,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 controll */ + 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/fmopl.h b/hw/fmopl.h new file mode 100644 index 0000000..a01ff90 --- /dev/null +++ b/hw/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 */ + /* Rythm sention */ + UINT8 rythm; /* Rythm 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/framebuffer.c b/hw/framebuffer.c new file mode 100644 index 0000000..24cdf25 --- /dev/null +++ b/hw/framebuffer.c @@ -0,0 +1,116 @@ +/* + * Framebuffer device helper routines + * + * Copyright (c) 2009 CodeSourcery + * Written by Paul Brook + * + * This code is licensed under the GNU GPLv2. + */ + +/* 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.h" +#include "console.h" +#include "framebuffer.h" + +/* Render an image from a shared memory framebuffer. */ + +void framebuffer_update_display( + DisplayState *ds, + target_phys_addr_t base, + int cols, /* Width in pixels. */ + int rows, /* Leight 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 */) +{ + target_phys_addr_t src_len; + uint8_t *dest; + uint8_t *src; + uint8_t *src_base; + int first, last = 0; + int dirty; + int i; + ram_addr_t addr; + ram_addr_t pd; + ram_addr_t pd2; + + i = *first_row; + *first_row = -1; + src_len = src_width * rows; + + cpu_physical_sync_dirty_bitmap(base, base + src_len); + pd = cpu_get_physical_page_desc(base); + pd2 = cpu_get_physical_page_desc(base + src_len - 1); + /* We should reall check that this is a continuous ram region. + Instead we just check that the first and last pages are + both ram, and the right distance apart. */ + if ((pd & ~TARGET_PAGE_MASK) > IO_MEM_ROM + || (pd2 & ~TARGET_PAGE_MASK) > IO_MEM_ROM) { + return; + } + pd = (pd & TARGET_PAGE_MASK) + (base & ~TARGET_PAGE_MASK); + if (((pd + src_len - 1) & TARGET_PAGE_MASK) != (pd2 & TARGET_PAGE_MASK)) { + return; + } + + src_base = cpu_physical_memory_map(base, &src_len, 0); + /* If we can't map the framebuffer then bail. We could try harder, + but it's not really worth it as dirty flag tracking will probably + already have failed above. */ + if (!src_base) + return; + if (src_len != src_width * rows) { + cpu_physical_memory_unmap(src_base, src_len, 0, 0); + return; + } + src = src_base; + dest = ds_get_data(ds); + if (dest_col_pitch < 0) + dest -= dest_col_pitch * (cols - 1); + first = -1; + addr = pd; + + addr += i * src_width; + src += i * src_width; + dest += i * dest_row_pitch; + + for (; i < rows; i++) { + target_phys_addr_t dirty_offset; + dirty = 0; + dirty_offset = 0; + while (addr + dirty_offset < TARGET_PAGE_ALIGN(addr + src_width)) { + dirty |= cpu_physical_memory_get_dirty(addr + dirty_offset, + VGA_DIRTY_FLAG); + dirty_offset += TARGET_PAGE_SIZE; + } + + 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; + } + cpu_physical_memory_unmap(src_base, src_len, 0, 0); + if (first < 0) { + return; + } + cpu_physical_memory_reset_dirty(pd, pd + src_len, VGA_DIRTY_FLAG); + *first_row = first; + *last_row = last; + return; +} diff --git a/hw/framebuffer.h b/hw/framebuffer.h new file mode 100644 index 0000000..a3a2146 --- /dev/null +++ b/hw/framebuffer.h @@ -0,0 +1,22 @@ +#ifndef QEMU_FRAMEBUFFER_H +#define QEMU_FRAMEBUFFER_H + +/* Framebuffer device helper routines. */ + +typedef void (*drawfn)(void *, uint8_t *, const uint8_t *, int, int); + +void framebuffer_update_display( + DisplayState *ds, + target_phys_addr_t base, + 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/fw_cfg.c b/hw/fw_cfg.c new file mode 100644 index 0000000..85c8c3c --- /dev/null +++ b/hw/fw_cfg.c @@ -0,0 +1,407 @@ +/* + * 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.h" +#include "sysemu.h" +#include "isa.h" +#include "fw_cfg.h" +#include "sysbus.h" + +/* debug firmware config */ +//#define DEBUG_FW_CFG + +#ifdef DEBUG_FW_CFG +#define FW_CFG_DPRINTF(fmt, ...) \ + do { printf("FW_CFG: " fmt , ## __VA_ARGS__); } while (0) +#else +#define FW_CFG_DPRINTF(fmt, ...) +#endif + +#define FW_CFG_SIZE 2 + +typedef struct FWCfgEntry { + uint32_t len; + uint8_t *data; + void *callback_opaque; + FWCfgCallback callback; +} FWCfgEntry; + +struct FWCfgState { + SysBusDevice busdev; + uint32_t ctl_iobase, data_iobase; + FWCfgEntry entries[2][FW_CFG_MAX_ENTRY]; + FWCfgFiles *files; + uint16_t cur_entry; + uint32_t cur_offset; + Notifier machine_ready; +}; + +static void fw_cfg_write(FWCfgState *s, uint8_t value) +{ + int arch = !!(s->cur_entry & FW_CFG_ARCH_LOCAL); + FWCfgEntry *e = &s->entries[arch][s->cur_entry & FW_CFG_ENTRY_MASK]; + + FW_CFG_DPRINTF("write %d\n", value); + + if (s->cur_entry & FW_CFG_WRITE_CHANNEL && s->cur_offset < e->len) { + e->data[s->cur_offset++] = value; + if (s->cur_offset == e->len) { + e->callback(e->callback_opaque, e->data); + s->cur_offset = 0; + } + } +} + +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; + } + + FW_CFG_DPRINTF("select key %d (%sfound)\n", key, ret ? "" : "not "); + + 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 + ret = e->data[s->cur_offset++]; + + FW_CFG_DPRINTF("read %d\n", ret); + + return ret; +} + +static uint32_t fw_cfg_io_readb(void *opaque, uint32_t addr) +{ + return fw_cfg_read(opaque); +} + +static void fw_cfg_io_writeb(void *opaque, uint32_t addr, uint32_t value) +{ + fw_cfg_write(opaque, (uint8_t)value); +} + +static void fw_cfg_io_writew(void *opaque, uint32_t addr, uint32_t value) +{ + fw_cfg_select(opaque, (uint16_t)value); +} + +static uint32_t fw_cfg_mem_readb(void *opaque, target_phys_addr_t addr) +{ + return fw_cfg_read(opaque); +} + +static void fw_cfg_mem_writeb(void *opaque, target_phys_addr_t addr, + uint32_t value) +{ + fw_cfg_write(opaque, (uint8_t)value); +} + +static void fw_cfg_mem_writew(void *opaque, target_phys_addr_t addr, + uint32_t value) +{ + fw_cfg_select(opaque, (uint16_t)value); +} + +static CPUReadMemoryFunc * const fw_cfg_ctl_mem_read[3] = { + NULL, + NULL, + NULL, +}; + +static CPUWriteMemoryFunc * const fw_cfg_ctl_mem_write[3] = { + NULL, + fw_cfg_mem_writew, + NULL, +}; + +static CPUReadMemoryFunc * const fw_cfg_data_mem_read[3] = { + fw_cfg_mem_readb, + NULL, + NULL, +}; + +static CPUWriteMemoryFunc * const fw_cfg_data_mem_write[3] = { + fw_cfg_mem_writeb, + NULL, + NULL, +}; + +static void fw_cfg_reset(DeviceState *d) +{ + FWCfgState *s = DO_UPCAST(FWCfgState, busdev.qdev, 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, + .minimum_version_id_old = 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() + } +}; + +int fw_cfg_add_bytes(FWCfgState *s, uint16_t key, uint8_t *data, uint32_t len) +{ + int arch = !!(key & FW_CFG_ARCH_LOCAL); + + key &= FW_CFG_ENTRY_MASK; + + if (key >= FW_CFG_MAX_ENTRY) + return 0; + + s->entries[arch][key].data = data; + s->entries[arch][key].len = len; + + return 1; +} + +int fw_cfg_add_i16(FWCfgState *s, uint16_t key, uint16_t value) +{ + uint16_t *copy; + + copy = qemu_malloc(sizeof(value)); + *copy = cpu_to_le16(value); + return fw_cfg_add_bytes(s, key, (uint8_t *)copy, sizeof(value)); +} + +int fw_cfg_add_i32(FWCfgState *s, uint16_t key, uint32_t value) +{ + uint32_t *copy; + + copy = qemu_malloc(sizeof(value)); + *copy = cpu_to_le32(value); + return fw_cfg_add_bytes(s, key, (uint8_t *)copy, sizeof(value)); +} + +int fw_cfg_add_i64(FWCfgState *s, uint16_t key, uint64_t value) +{ + uint64_t *copy; + + copy = qemu_malloc(sizeof(value)); + *copy = cpu_to_le64(value); + return fw_cfg_add_bytes(s, key, (uint8_t *)copy, sizeof(value)); +} + +int fw_cfg_add_callback(FWCfgState *s, uint16_t key, FWCfgCallback callback, + void *callback_opaque, uint8_t *data, size_t len) +{ + int arch = !!(key & FW_CFG_ARCH_LOCAL); + + if (!(key & FW_CFG_WRITE_CHANNEL)) + return 0; + + key &= FW_CFG_ENTRY_MASK; + + if (key >= FW_CFG_MAX_ENTRY || len > 65535) + return 0; + + s->entries[arch][key].data = data; + s->entries[arch][key].len = len; + s->entries[arch][key].callback_opaque = callback_opaque; + s->entries[arch][key].callback = callback; + + return 1; +} + +int fw_cfg_add_file(FWCfgState *s, const char *filename, uint8_t *data, + uint32_t len) +{ + int i, index; + + if (!s->files) { + int dsize = sizeof(uint32_t) + sizeof(FWCfgFile) * FW_CFG_FILE_SLOTS; + s->files = qemu_mallocz(dsize); + fw_cfg_add_bytes(s, FW_CFG_FILE_DIR, (uint8_t*)s->files, dsize); + } + + index = be32_to_cpu(s->files->count); + if (index == FW_CFG_FILE_SLOTS) { + fprintf(stderr, "fw_cfg: out of file slots\n"); + return 0; + } + + fw_cfg_add_bytes(s, FW_CFG_FILE_FIRST + index, data, len); + + 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) { + FW_CFG_DPRINTF("%s: skip duplicate: %s\n", __FUNCTION__, + s->files->f[index].name); + return 1; + } + } + + s->files->f[index].size = cpu_to_be32(len); + s->files->f[index].select = cpu_to_be16(FW_CFG_FILE_FIRST + index); + FW_CFG_DPRINTF("%s: #%d: %s (%d bytes)\n", __FUNCTION__, + index, s->files->f[index].name, len); + + s->files->count = cpu_to_be32(index+1); + return 1; +} + +static void fw_cfg_machine_ready(struct Notifier* n) +{ + uint32_t len; + FWCfgState *s = container_of(n, FWCfgState, machine_ready); + char *bootindex = get_boot_devices_list(&len); + + fw_cfg_add_file(s, "bootorder", (uint8_t*)bootindex, len); +} + +FWCfgState *fw_cfg_init(uint32_t ctl_port, uint32_t data_port, + target_phys_addr_t ctl_addr, target_phys_addr_t data_addr) +{ + DeviceState *dev; + SysBusDevice *d; + FWCfgState *s; + + dev = qdev_create(NULL, "fw_cfg"); + qdev_prop_set_uint32(dev, "ctl_iobase", ctl_port); + qdev_prop_set_uint32(dev, "data_iobase", data_port); + qdev_init_nofail(dev); + d = sysbus_from_qdev(dev); + + s = DO_UPCAST(FWCfgState, busdev.qdev, dev); + + if (ctl_addr) { + sysbus_mmio_map(d, 0, ctl_addr); + } + if (data_addr) { + sysbus_mmio_map(d, 1, data_addr); + } + fw_cfg_add_bytes(s, FW_CFG_SIGNATURE, (uint8_t *)"QEMU", 4); + 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_MAX_CPUS, (uint16_t)max_cpus); + fw_cfg_add_i16(s, FW_CFG_BOOT_MENU, (uint16_t)boot_menu); + + + s->machine_ready.notify = fw_cfg_machine_ready; + qemu_add_machine_init_done_notifier(&s->machine_ready); + + return s; +} + +static int fw_cfg_init1(SysBusDevice *dev) +{ + FWCfgState *s = FROM_SYSBUS(FWCfgState, dev); + int io_ctl_memory, io_data_memory; + + io_ctl_memory = cpu_register_io_memory(fw_cfg_ctl_mem_read, + fw_cfg_ctl_mem_write, s, + DEVICE_NATIVE_ENDIAN); + sysbus_init_mmio(dev, FW_CFG_SIZE, io_ctl_memory); + + io_data_memory = cpu_register_io_memory(fw_cfg_data_mem_read, + fw_cfg_data_mem_write, s, + DEVICE_NATIVE_ENDIAN); + sysbus_init_mmio(dev, FW_CFG_SIZE, io_data_memory); + + if (s->ctl_iobase) { + register_ioport_write(s->ctl_iobase, 2, 2, fw_cfg_io_writew, s); + } + if (s->data_iobase) { + register_ioport_read(s->data_iobase, 1, 1, fw_cfg_io_readb, s); + register_ioport_write(s->data_iobase, 1, 1, fw_cfg_io_writeb, s); + } + return 0; +} + +static SysBusDeviceInfo fw_cfg_info = { + .init = fw_cfg_init1, + .qdev.name = "fw_cfg", + .qdev.size = sizeof(FWCfgState), + .qdev.vmsd = &vmstate_fw_cfg, + .qdev.reset = fw_cfg_reset, + .qdev.no_user = 1, + .qdev.props = (Property[]) { + DEFINE_PROP_HEX32("ctl_iobase", FWCfgState, ctl_iobase, -1), + DEFINE_PROP_HEX32("data_iobase", FWCfgState, data_iobase, -1), + DEFINE_PROP_END_OF_LIST(), + }, +}; + +static void fw_cfg_register_devices(void) +{ + sysbus_register_withprop(&fw_cfg_info); +} + +device_init(fw_cfg_register_devices) diff --git a/hw/fw_cfg.h b/hw/fw_cfg.h new file mode 100644 index 0000000..856bf91 --- /dev/null +++ b/hw/fw_cfg.h @@ -0,0 +1,70 @@ +#ifndef FW_CFG_H +#define FW_CFG_H + +#define FW_CFG_SIGNATURE 0x00 +#define FW_CFG_ID 0x01 +#define FW_CFG_UUID 0x02 +#define FW_CFG_RAM_SIZE 0x03 +#define FW_CFG_NOGRAPHIC 0x04 +#define FW_CFG_NB_CPUS 0x05 +#define FW_CFG_MACHINE_ID 0x06 +#define FW_CFG_KERNEL_ADDR 0x07 +#define FW_CFG_KERNEL_SIZE 0x08 +#define FW_CFG_KERNEL_CMDLINE 0x09 +#define FW_CFG_INITRD_ADDR 0x0a +#define FW_CFG_INITRD_SIZE 0x0b +#define FW_CFG_BOOT_DEVICE 0x0c +#define FW_CFG_NUMA 0x0d +#define FW_CFG_BOOT_MENU 0x0e +#define FW_CFG_MAX_CPUS 0x0f +#define FW_CFG_KERNEL_ENTRY 0x10 +#define FW_CFG_KERNEL_DATA 0x11 +#define FW_CFG_INITRD_DATA 0x12 +#define FW_CFG_CMDLINE_ADDR 0x13 +#define FW_CFG_CMDLINE_SIZE 0x14 +#define FW_CFG_CMDLINE_DATA 0x15 +#define FW_CFG_SETUP_ADDR 0x16 +#define FW_CFG_SETUP_SIZE 0x17 +#define FW_CFG_SETUP_DATA 0x18 +#define FW_CFG_FILE_DIR 0x19 + +#define FW_CFG_FILE_FIRST 0x20 +#define FW_CFG_FILE_SLOTS 0x10 +#define FW_CFG_MAX_ENTRY (FW_CFG_FILE_FIRST+FW_CFG_FILE_SLOTS) + +#define FW_CFG_WRITE_CHANNEL 0x4000 +#define FW_CFG_ARCH_LOCAL 0x8000 +#define FW_CFG_ENTRY_MASK ~(FW_CFG_WRITE_CHANNEL | FW_CFG_ARCH_LOCAL) + +#define FW_CFG_INVALID 0xffff + +#ifndef NO_QEMU_PROTOS +typedef struct FWCfgFile { + uint32_t size; /* file size */ + uint16_t select; /* write this to 0x510 to read it */ + uint16_t reserved; + char name[56]; +} FWCfgFile; + +typedef struct FWCfgFiles { + uint32_t count; + FWCfgFile f[]; +} FWCfgFiles; + +typedef void (*FWCfgCallback)(void *opaque, uint8_t *data); + +typedef struct FWCfgState FWCfgState; +int fw_cfg_add_bytes(FWCfgState *s, uint16_t key, uint8_t *data, uint32_t len); +int fw_cfg_add_i16(FWCfgState *s, uint16_t key, uint16_t value); +int fw_cfg_add_i32(FWCfgState *s, uint16_t key, uint32_t value); +int fw_cfg_add_i64(FWCfgState *s, uint16_t key, uint64_t value); +int fw_cfg_add_callback(FWCfgState *s, uint16_t key, FWCfgCallback callback, + void *callback_opaque, uint8_t *data, size_t len); +int fw_cfg_add_file(FWCfgState *s, const char *filename, uint8_t *data, + uint32_t len); +FWCfgState *fw_cfg_init(uint32_t ctl_port, uint32_t data_port, + target_phys_addr_t crl_addr, target_phys_addr_t data_addr); + +#endif /* NO_QEMU_PROTOS */ + +#endif diff --git a/hw/g364fb.c b/hw/g364fb.c new file mode 100644 index 0000000..a41e988 --- /dev/null +++ b/hw/g364fb.c @@ -0,0 +1,615 @@ +/* + * QEMU G364 framebuffer Emulator. + * + * Copyright (c) 2007-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 . + */ + +#include "hw.h" +#include "mips.h" +#include "console.h" +#include "pixel_ops.h" + +//#define DEBUG_G364 + +#ifdef DEBUG_G364 +#define DPRINTF(fmt, ...) \ +do { printf("g364: " fmt , ## __VA_ARGS__); } while (0) +#else +#define DPRINTF(fmt, ...) do {} while (0) +#endif +#define BADF(fmt, ...) \ +do { fprintf(stderr, "g364 ERROR: " fmt , ## __VA_ARGS__);} while (0) + +typedef struct G364State { + /* hardware */ + uint8_t *vram; + ram_addr_t vram_offset; + int vram_size; + qemu_irq irq; + /* 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 */ + DisplayState *ds; + int depth; + int blanked; +} G364State; + +#define REG_ID 0x000000 +#define REG_BOOT 0x080000 +#define REG_DISPLAY 0x080118 +#define REG_VDISPLAY 0x080150 +#define REG_CTLA 0x080300 +#define REG_TOP 0x080400 +#define REG_CURS_PAL 0x080508 +#define REG_CURS_POS 0x080638 +#define REG_CLR_PAL 0x080800 +#define REG_CURS_PAT 0x081000 +#define REG_RESET 0x180000 + +#define CTLA_FORCE_BLANK 0x00000400 +#define CTLA_NO_CURSOR 0x00800000 + +static inline int check_dirty(ram_addr_t page) +{ + return cpu_physical_memory_get_dirty(page, VGA_DIRTY_FLAG); +} + +static inline void reset_dirty(G364State *s, + ram_addr_t page_min, ram_addr_t page_max) +{ + cpu_physical_memory_reset_dirty(page_min, page_max + TARGET_PAGE_SIZE - 1, + VGA_DIRTY_FLAG); +} + +static void g364fb_draw_graphic8(G364State *s) +{ + 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 (ds_get_bits_per_pixel(s->ds)) { + 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: + BADF("unknown host depth %d\n", ds_get_bits_per_pixel(s->ds)); + return; + } + + page = s->vram_offset; + 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 = ds_get_data(s->ds); + while (y < s->height) { + if (check_dirty(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 < TARGET_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 + ds_get_linesize(s->ds); + 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_update(s->ds, xmin, ymin, xmax - xmin + 1, ymax - ymin + 1); + xmin = s->width; + xmax = 0; + ymin = s->height; + ymax = 0; + } + x += TARGET_PAGE_SIZE; + dy = x / s->width; + x = x % s->width; + y += dy; + vram += TARGET_PAGE_SIZE; + data_display += dy * ds_get_linesize(s->ds); + dd = data_display + x * w; + } + page += TARGET_PAGE_SIZE; + } + +done: + if (page_min != (ram_addr_t)-1) { + dpy_update(s->ds, xmin, ymin, xmax - xmin + 1, ymax - ymin + 1); + reset_dirty(s, page_min, page_max); + } +} + +static void g364fb_draw_blank(G364State *s) +{ + int i, w; + uint8_t *d; + + if (s->blanked) { + /* Screen is already blank. No need to redraw it */ + return; + } + + w = s->width * ((ds_get_bits_per_pixel(s->ds) + 7) >> 3); + d = ds_get_data(s->ds); + for (i = 0; i < s->height; i++) { + memset(d, 0, w); + d += ds_get_linesize(s->ds); + } + + dpy_update(s->ds, 0, 0, s->width, s->height); + s->blanked = 1; +} + +static void g364fb_update_display(void *opaque) +{ + G364State *s = opaque; + + if (s->width == 0 || s->height == 0) + return; + + if (s->width != ds_get_width(s->ds) || s->height != ds_get_height(s->ds)) { + qemu_console_resize(s->ds, s->width, s->height); + } + + if (s->ctla & CTLA_FORCE_BLANK) { + g364fb_draw_blank(s); + } else if (s->depth == 8) { + g364fb_draw_graphic8(s); + } else { + BADF("unknown guest depth %d\n", s->depth); + } + + qemu_irq_raise(s->irq); +} + +static inline void g364fb_invalidate_display(void *opaque) +{ + G364State *s = opaque; + int i; + + s->blanked = 0; + for (i = 0; i < s->vram_size; i += TARGET_PAGE_SIZE) { + cpu_physical_memory_set_dirty(s->vram_offset + i); + } +} + +static void g364fb_reset(void *opaque) +{ + G364State *s = opaque; + 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(opaque); +} + +static void g364fb_screen_dump(void *opaque, const char *filename) +{ + G364State *s = opaque; + int y, x; + uint8_t index; + uint8_t *data_buffer; + FILE *f; + + if (s->depth != 8) { + BADF("unknown guest depth %d\n", s->depth); + return; + } + + f = fopen(filename, "wb"); + if (!f) + return; + + if (s->ctla & CTLA_FORCE_BLANK) { + /* blank screen */ + fprintf(f, "P4\n%d %d\n", + s->width, s->height); + for (y = 0; y < s->height; y++) + for (x = 0; x < s->width; x++) + fputc(0, f); + } else { + data_buffer = s->vram + s->top_of_screen; + fprintf(f, "P6\n%d %d\n%d\n", + s->width, s->height, 255); + for (y = 0; y < s->height; y++) + for (x = 0; x < s->width; x++, data_buffer++) { + index = *data_buffer; + fputc(s->color_palette[index][0], f); + fputc(s->color_palette[index][1], f); + fputc(s->color_palette[index][2], f); + } + } + + fclose(f); +} + +/* called for accesses to io ports */ +static uint32_t g364fb_ctrl_readl(void *opaque, target_phys_addr_t addr) +{ + 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_ID: + val = 0x10; /* Mips G364 */ + break; + case REG_DISPLAY: + val = s->width / 4; + break; + case REG_VDISPLAY: + val = s->height * 2; + break; + case REG_CTLA: + val = s->ctla; + break; + default: + { + BADF("invalid read at [" TARGET_FMT_plx "]\n", addr); + val = 0; + break; + } + } + } + + DPRINTF("read 0x%08x at [" TARGET_FMT_plx "]\n", val, addr); + + return val; +} + +static uint32_t g364fb_ctrl_readw(void *opaque, target_phys_addr_t addr) +{ + uint32_t v = g364fb_ctrl_readl(opaque, addr & ~0x3); + if (addr & 0x2) + return v >> 16; + else + return v & 0xffff; +} + +static uint32_t g364fb_ctrl_readb(void *opaque, target_phys_addr_t addr) +{ + uint32_t v = g364fb_ctrl_readl(opaque, addr & ~0x3); + return (v >> (8 * (addr & 0x3))) & 0xff; +} + +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) +{ + int ymin, ymax, start, end, i; + + /* invalidate only near the cursor */ + ymin = s->cursor_position & 0xfff; + ymax = MIN(s->height, ymin + 64); + start = ymin * ds_get_linesize(s->ds); + end = (ymax + 1) * ds_get_linesize(s->ds); + + for (i = start; i < end; i += TARGET_PAGE_SIZE) { + cpu_physical_memory_set_dirty(s->vram_offset + i); + } +} + +static void g364fb_ctrl_writel(void *opaque, target_phys_addr_t addr, uint32_t val) +{ + G364State *s = opaque; + + DPRINTF("write 0x%08x at [" TARGET_FMT_plx "]\n", val, addr); + + 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_ID: /* Card identifier; read-only */ + case REG_BOOT: /* Boot timing */ + case 0x80108: /* Line timing: half sync */ + case 0x80110: /* Line timing: back porch */ + case 0x80120: /* Line timing: short display */ + case 0x80128: /* Frame timing: broad pulse */ + case 0x80130: /* Frame timing: v sync */ + case 0x80138: /* Frame timing: v preequalise */ + case 0x80140: /* Frame timing: v postequalise */ + case 0x80148: /* Frame timing: v blank */ + case 0x80158: /* Line timing: line time */ + case 0x80160: /* Frame store: line start */ + case 0x80168: /* vram cycle: mem init */ + case 0x80170: /* vram cycle: transfer delay */ + case 0x80200: /* 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: + BADF("invalid write of 0x%08x at [" TARGET_FMT_plx "]\n", val, addr); + break; + } + } + qemu_irq_lower(s->irq); +} + +static void g364fb_ctrl_writew(void *opaque, target_phys_addr_t addr, uint32_t val) +{ + uint32_t old_val = g364fb_ctrl_readl(opaque, addr & ~0x3); + + if (addr & 0x2) + val = (val << 16) | (old_val & 0x0000ffff); + else + val = val | (old_val & 0xffff0000); + g364fb_ctrl_writel(opaque, addr & ~0x3, val); +} + +static void g364fb_ctrl_writeb(void *opaque, target_phys_addr_t addr, uint32_t val) +{ + uint32_t old_val = g364fb_ctrl_readl(opaque, addr & ~0x3); + + switch (addr & 3) { + case 0: + val = val | (old_val & 0xffffff00); + break; + case 1: + val = (val << 8) | (old_val & 0xffff00ff); + break; + case 2: + val = (val << 16) | (old_val & 0xff00ffff); + break; + case 3: + val = (val << 24) | (old_val & 0x00ffffff); + break; + } + g364fb_ctrl_writel(opaque, addr & ~0x3, val); +} + +static CPUReadMemoryFunc * const g364fb_ctrl_read[3] = { + g364fb_ctrl_readb, + g364fb_ctrl_readw, + g364fb_ctrl_readl, +}; + +static CPUWriteMemoryFunc * const g364fb_ctrl_write[3] = { + g364fb_ctrl_writeb, + g364fb_ctrl_writew, + g364fb_ctrl_writel, +}; + +static int g364fb_load(QEMUFile *f, void *opaque, int version_id) +{ + G364State *s = opaque; + unsigned int i, vram_size; + + if (version_id != 1) + return -EINVAL; + + vram_size = qemu_get_be32(f); + if (vram_size < s->vram_size) + return -EINVAL; + qemu_get_buffer(f, s->vram, s->vram_size); + for (i = 0; i < 256; i++) + qemu_get_buffer(f, s->color_palette[i], 3); + for (i = 0; i < 3; i++) + qemu_get_buffer(f, s->cursor_palette[i], 3); + qemu_get_buffer(f, (uint8_t *)s->cursor, sizeof(s->cursor)); + s->cursor_position = qemu_get_be32(f); + s->ctla = qemu_get_be32(f); + s->top_of_screen = qemu_get_be32(f); + s->width = qemu_get_be32(f); + s->height = qemu_get_be32(f); + + /* force refresh */ + g364fb_update_depth(s); + g364fb_invalidate_display(s); + + return 0; +} + +static void g364fb_save(QEMUFile *f, void *opaque) +{ + G364State *s = opaque; + int i; + + qemu_put_be32(f, s->vram_size); + qemu_put_buffer(f, s->vram, s->vram_size); + for (i = 0; i < 256; i++) + qemu_put_buffer(f, s->color_palette[i], 3); + for (i = 0; i < 3; i++) + qemu_put_buffer(f, s->cursor_palette[i], 3); + qemu_put_buffer(f, (uint8_t *)s->cursor, sizeof(s->cursor)); + qemu_put_be32(f, s->cursor_position); + qemu_put_be32(f, s->ctla); + qemu_put_be32(f, s->top_of_screen); + qemu_put_be32(f, s->width); + qemu_put_be32(f, s->height); +} + +int g364fb_mm_init(target_phys_addr_t vram_base, + target_phys_addr_t ctrl_base, int it_shift, + qemu_irq irq) +{ + G364State *s; + int io_ctrl; + + s = qemu_mallocz(sizeof(G364State)); + + s->vram_size = 8 * 1024 * 1024; + s->vram_offset = qemu_ram_alloc(NULL, "g364fb.vram", s->vram_size); + s->vram = qemu_get_ram_ptr(s->vram_offset); + s->irq = irq; + + qemu_register_reset(g364fb_reset, s); + register_savevm(NULL, "g364fb", 0, 1, g364fb_save, g364fb_load, s); + g364fb_reset(s); + + s->ds = graphic_console_init(g364fb_update_display, + g364fb_invalidate_display, + g364fb_screen_dump, NULL, s); + + cpu_register_physical_memory(vram_base, s->vram_size, s->vram_offset); + + io_ctrl = cpu_register_io_memory(g364fb_ctrl_read, g364fb_ctrl_write, s, + DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(ctrl_base, 0x200000, io_ctrl); + + return 0; +} diff --git a/hw/grackle_pci.c b/hw/grackle_pci.c new file mode 100644 index 0000000..bd3d6b0 --- /dev/null +++ b/hw/grackle_pci.c @@ -0,0 +1,147 @@ +/* + * 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 "sysbus.h" +#include "ppc_mac.h" +#include "pci.h" +#include "pci_host.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 + +typedef struct GrackleState { + SysBusDevice busdev; + PCIHostState host_state; +} 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); +} + +static void pci_grackle_save(QEMUFile* f, void *opaque) +{ + PCIDevice *d = opaque; + + pci_device_save(d, f); +} + +static int pci_grackle_load(QEMUFile* f, void *opaque, int version_id) +{ + PCIDevice *d = opaque; + + if (version_id != 1) + return -EINVAL; + + return pci_device_load(d, f); +} + +static void pci_grackle_reset(void *opaque) +{ +} + +PCIBus *pci_grackle_init(uint32_t base, qemu_irq *pic) +{ + DeviceState *dev; + SysBusDevice *s; + GrackleState *d; + + dev = qdev_create(NULL, "grackle"); + qdev_init_nofail(dev); + s = sysbus_from_qdev(dev); + d = FROM_SYSBUS(GrackleState, s); + d->host_state.bus = pci_register_bus(&d->busdev.qdev, "pci", + pci_grackle_set_irq, + pci_grackle_map_irq, + pic, 0, 4); + + pci_create_simple(d->host_state.bus, 0, "grackle"); + + sysbus_mmio_map(s, 0, base); + sysbus_mmio_map(s, 1, base + 0x00200000); + + return d->host_state.bus; +} + +static int pci_grackle_init_device(SysBusDevice *dev) +{ + GrackleState *s; + int pci_mem_config, pci_mem_data; + + s = FROM_SYSBUS(GrackleState, dev); + + pci_mem_config = pci_host_conf_register_mmio(&s->host_state, + DEVICE_LITTLE_ENDIAN); + pci_mem_data = pci_host_data_register_mmio(&s->host_state, + DEVICE_LITTLE_ENDIAN); + sysbus_init_mmio(dev, 0x1000, pci_mem_config); + sysbus_init_mmio(dev, 0x1000, pci_mem_data); + + register_savevm(&dev->qdev, "grackle", 0, 1, pci_grackle_save, + pci_grackle_load, &s->host_state); + qemu_register_reset(pci_grackle_reset, &s->host_state); + return 0; +} + +static int grackle_pci_host_init(PCIDevice *d) +{ + pci_config_set_vendor_id(d->config, PCI_VENDOR_ID_MOTOROLA); + pci_config_set_device_id(d->config, PCI_DEVICE_ID_MOTOROLA_MPC106); + d->config[0x08] = 0x00; // revision + d->config[0x09] = 0x01; + pci_config_set_class(d->config, PCI_CLASS_BRIDGE_HOST); + return 0; +} + +static PCIDeviceInfo grackle_pci_host_info = { + .qdev.name = "grackle", + .qdev.size = sizeof(PCIDevice), + .init = grackle_pci_host_init, +}; + +static void grackle_register_devices(void) +{ + sysbus_register_dev("grackle", sizeof(GrackleState), + pci_grackle_init_device); + pci_qdev_register(&grackle_pci_host_info); +} + +device_init(grackle_register_devices) diff --git a/hw/grlib.h b/hw/grlib.h new file mode 100644 index 0000000..fdf4b11 --- /dev/null +++ b/hw/grlib.h @@ -0,0 +1,126 @@ +/* + * QEMU GRLIB Components + * + * 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. + */ + +#ifndef _GRLIB_H_ +#define _GRLIB_H_ + +#include "qdev.h" +#include "sysbus.h" + +/* Emulation of GrLib device is base on the GRLIB IP Core User's Manual: + * http://www.gaisler.com/products/grlib/grip.pdf + */ + +/* IRQMP */ + +typedef void (*set_pil_in_fn) (void *opaque, uint32_t pil_in); + +void grlib_irqmp_set_irq(void *opaque, int irq, int level); + +void grlib_irqmp_ack(DeviceState *dev, int intno); + +static inline +DeviceState *grlib_irqmp_create(target_phys_addr_t base, + CPUState *env, + qemu_irq **cpu_irqs, + uint32_t nr_irqs, + set_pil_in_fn set_pil_in) +{ + DeviceState *dev; + + assert(cpu_irqs != NULL); + + dev = qdev_create(NULL, "grlib,irqmp"); + qdev_prop_set_ptr(dev, "set_pil_in", set_pil_in); + qdev_prop_set_ptr(dev, "set_pil_in_opaque", env); + + if (qdev_init(dev)) { + return NULL; + } + + env->irq_manager = dev; + + sysbus_mmio_map(sysbus_from_qdev(dev), 0, base); + + *cpu_irqs = qemu_allocate_irqs(grlib_irqmp_set_irq, + dev, + nr_irqs); + + return dev; +} + +/* GPTimer */ + +static inline +DeviceState *grlib_gptimer_create(target_phys_addr_t base, + uint32_t nr_timers, + uint32_t freq, + qemu_irq *cpu_irqs, + int base_irq) +{ + DeviceState *dev; + int i; + + dev = qdev_create(NULL, "grlib,gptimer"); + qdev_prop_set_uint32(dev, "nr-timers", nr_timers); + qdev_prop_set_uint32(dev, "frequency", freq); + qdev_prop_set_uint32(dev, "irq-line", base_irq); + + if (qdev_init(dev)) { + return NULL; + } + + sysbus_mmio_map(sysbus_from_qdev(dev), 0, base); + + for (i = 0; i < nr_timers; i++) { + sysbus_connect_irq(sysbus_from_qdev(dev), i, cpu_irqs[base_irq + i]); + } + + return dev; +} + +/* APB UART */ + +static inline +DeviceState *grlib_apbuart_create(target_phys_addr_t base, + CharDriverState *serial, + qemu_irq irq) +{ + DeviceState *dev; + + dev = qdev_create(NULL, "grlib,apbuart"); + qdev_prop_set_chr(dev, "chrdev", serial); + + if (qdev_init(dev)) { + return NULL; + } + + sysbus_mmio_map(sysbus_from_qdev(dev), 0, base); + + sysbus_connect_irq(sysbus_from_qdev(dev), 0, irq); + + return dev; +} + +#endif /* ! _GRLIB_H_ */ diff --git a/hw/grlib_apbuart.c b/hw/grlib_apbuart.c new file mode 100644 index 0000000..101b150 --- /dev/null +++ b/hw/grlib_apbuart.c @@ -0,0 +1,187 @@ +/* + * 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 "sysbus.h" +#include "qemu-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 */ + +typedef struct UART { + SysBusDevice busdev; + + qemu_irq irq; + + CharDriverState *chr; + + /* registers */ + uint32_t receive; + uint32_t status; + uint32_t control; +} UART; + +static int grlib_apbuart_can_receive(void *opaque) +{ + UART *uart = opaque; + + return !!(uart->status & UART_DATA_READY); +} + +static void grlib_apbuart_receive(void *opaque, const uint8_t *buf, int size) +{ + UART *uart = opaque; + + uart->receive = *buf; + 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 void +grlib_apbuart_writel(void *opaque, target_phys_addr_t addr, uint32_t value) +{ + UART *uart = opaque; + unsigned char c = 0; + + addr &= 0xff; + + /* Unit registers */ + switch (addr) { + case DATA_OFFSET: + c = value & 0xFF; + qemu_chr_write(uart->chr, &c, 1); + return; + + case STATUS_OFFSET: + /* Read Only */ + return; + + case CONTROL_OFFSET: + /* Not supported */ + return; + + case SCALER_OFFSET: + /* Not supported */ + return; + + default: + break; + } + + trace_grlib_apbuart_unknown_register("write", addr); +} + +static CPUReadMemoryFunc * const grlib_apbuart_read[] = { + NULL, NULL, NULL, +}; + +static CPUWriteMemoryFunc * const grlib_apbuart_write[] = { + NULL, NULL, grlib_apbuart_writel, +}; + +static int grlib_apbuart_init(SysBusDevice *dev) +{ + UART *uart = FROM_SYSBUS(typeof(*uart), dev); + int uart_regs = 0; + + qemu_chr_add_handlers(uart->chr, + grlib_apbuart_can_receive, + grlib_apbuart_receive, + grlib_apbuart_event, + uart); + + sysbus_init_irq(dev, &uart->irq); + + uart_regs = cpu_register_io_memory(grlib_apbuart_read, + grlib_apbuart_write, + uart, DEVICE_NATIVE_ENDIAN); + if (uart_regs < 0) { + return -1; + } + + sysbus_init_mmio(dev, UART_REG_SIZE, uart_regs); + + return 0; +} + +static SysBusDeviceInfo grlib_gptimer_info = { + .init = grlib_apbuart_init, + .qdev.name = "grlib,apbuart", + .qdev.size = sizeof(UART), + .qdev.props = (Property[]) { + DEFINE_PROP_CHR("chrdev", UART, chr), + DEFINE_PROP_END_OF_LIST() + } +}; + +static void grlib_gptimer_register(void) +{ + sysbus_register_withprop(&grlib_gptimer_info); +} + +device_init(grlib_gptimer_register) diff --git a/hw/grlib_gptimer.c b/hw/grlib_gptimer.c new file mode 100644 index 0000000..596a900 --- /dev/null +++ b/hw/grlib_gptimer.c @@ -0,0 +1,395 @@ +/* + * 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 "sysbus.h" +#include "qemu-timer.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 + +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 busdev; + + 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 + 1); + + ptimer_set_count(timer->ptimer, 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 uint32_t grlib_gptimer_readl(void *opaque, target_phys_addr_t addr) +{ + GPTimerUnit *unit = opaque; + target_phys_addr_t timer_addr; + int id; + uint32_t value = 0; + + addr &= 0xff; + + /* Unit registers */ + switch (addr) { + case SCALER_OFFSET: + trace_grlib_gptimer_readl(-1, "scaler:", unit->scaler); + return unit->scaler; + + case SCALER_RELOAD_OFFSET: + trace_grlib_gptimer_readl(-1, "reload:", unit->reload); + return unit->reload; + + case CONFIG_OFFSET: + trace_grlib_gptimer_readl(-1, "config:", 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, "counter value:", value); + return value; + + case COUNTER_RELOAD_OFFSET: + value = unit->timers[id].reload; + trace_grlib_gptimer_readl(id, "reload value:", value); + return value; + + case CONFIG_OFFSET: + trace_grlib_gptimer_readl(id, "scaler value:", + unit->timers[id].config); + return unit->timers[id].config; + + default: + break; + } + + } + + trace_grlib_gptimer_unknown_register("read", addr); + return 0; +} + +static void +grlib_gptimer_writel(void *opaque, target_phys_addr_t addr, uint32_t value) +{ + GPTimerUnit *unit = opaque; + target_phys_addr_t 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, "scaler:", unit->scaler); + return; + + case SCALER_RELOAD_OFFSET: + value &= 0xFFFF; /* clean up the value */ + unit->reload = value; + trace_grlib_gptimer_writel(-1, "reload:", unit->reload); + grlib_gptimer_set_scaler(unit, value); + return; + + case CONFIG_OFFSET: + /* Read Only (disable timer freeze not supported) */ + trace_grlib_gptimer_writel(-1, "config (Read Only):", 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, "counter:", value); + unit->timers[id].counter = value; + grlib_gptimer_enable(&unit->timers[id]); + return; + + case COUNTER_RELOAD_OFFSET: + trace_grlib_gptimer_writel(id, "reload:", value); + unit->timers[id].reload = value; + return; + + case CONFIG_OFFSET: + trace_grlib_gptimer_writel(id, "config:", 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_unknown_register("write", addr); +} + +static CPUReadMemoryFunc * const grlib_gptimer_read[] = { + NULL, NULL, grlib_gptimer_readl, +}; + +static CPUWriteMemoryFunc * const grlib_gptimer_write[] = { + NULL, NULL, grlib_gptimer_writel, +}; + +static void grlib_gptimer_reset(DeviceState *d) +{ + GPTimerUnit *unit = container_of(d, GPTimerUnit, busdev.qdev); + int i = 0; + + assert(unit != NULL); + + unit->scaler = 0; + unit->reload = 0; + unit->config = 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 = FROM_SYSBUS(typeof(*unit), dev); + unsigned int i; + int timer_regs; + + assert(unit->nr_timers > 0); + assert(unit->nr_timers <= GPTIMER_MAX_TIMERS); + + unit->timers = qemu_mallocz(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); + } + + timer_regs = cpu_register_io_memory(grlib_gptimer_read, + grlib_gptimer_write, + unit, DEVICE_NATIVE_ENDIAN); + if (timer_regs < 0) { + return -1; + } + + sysbus_init_mmio(dev, UNIT_REG_SIZE + GPTIMER_REG_SIZE * unit->nr_timers, + timer_regs); + return 0; +} + +static SysBusDeviceInfo grlib_gptimer_info = { + .init = grlib_gptimer_init, + .qdev.name = "grlib,gptimer", + .qdev.reset = grlib_gptimer_reset, + .qdev.size = sizeof(GPTimerUnit), + .qdev.props = (Property[]) { + 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_register(void) +{ + sysbus_register_withprop(&grlib_gptimer_info); +} + +device_init(grlib_gptimer_register) diff --git a/hw/grlib_irqmp.c b/hw/grlib_irqmp.c new file mode 100644 index 0000000..f47c491 --- /dev/null +++ b/hw/grlib_irqmp.c @@ -0,0 +1,376 @@ +/* + * 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 "sysbus.h" +#include "cpu.h" + +#include "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 + +typedef struct IRQMPState IRQMPState; + +typedef struct IRQMP { + SysBusDevice busdev; + + 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) +{ + SysBusDevice *sdev; + IRQMP *irqmp; + IRQMPState *state; + uint32_t mask; + + assert(dev != NULL); + + sdev = sysbus_from_qdev(dev); + assert(sdev != NULL); + + irqmp = FROM_SYSBUS(typeof(*irqmp), sdev); + assert(irqmp != NULL); + + 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; + IRQMPState *s; + int i = 0; + + assert(opaque != NULL); + + irqmp = FROM_SYSBUS(typeof(*irqmp), sysbus_from_qdev(opaque)); + assert(irqmp != NULL); + + 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 uint32_t grlib_irqmp_readl(void *opaque, target_phys_addr_t addr) +{ + 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_unknown_register("read", addr); + return 0; +} + +static void +grlib_irqmp_writel(void *opaque, target_phys_addr_t addr, uint32_t value) +{ + 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_unknown_register("write", addr); +} + +static CPUReadMemoryFunc * const grlib_irqmp_read[] = { + NULL, NULL, &grlib_irqmp_readl, +}; + +static CPUWriteMemoryFunc * const grlib_irqmp_write[] = { + NULL, NULL, &grlib_irqmp_writel, +}; + +static void grlib_irqmp_reset(DeviceState *d) +{ + IRQMP *irqmp = container_of(d, IRQMP, busdev.qdev); + assert(irqmp != NULL); + assert(irqmp->state != NULL); + + memset(irqmp->state, 0, sizeof *irqmp->state); + irqmp->state->parent = irqmp; +} + +static int grlib_irqmp_init(SysBusDevice *dev) +{ + IRQMP *irqmp = FROM_SYSBUS(typeof(*irqmp), dev); + int irqmp_regs; + + assert(irqmp != NULL); + + /* Check parameters */ + if (irqmp->set_pil_in == NULL) { + return -1; + } + + irqmp_regs = cpu_register_io_memory(grlib_irqmp_read, + grlib_irqmp_write, + irqmp, DEVICE_NATIVE_ENDIAN); + + irqmp->state = qemu_mallocz(sizeof *irqmp->state); + + if (irqmp_regs < 0) { + return -1; + } + + sysbus_init_mmio(dev, IRQMP_REG_SIZE, irqmp_regs); + + return 0; +} + +static SysBusDeviceInfo grlib_irqmp_info = { + .init = grlib_irqmp_init, + .qdev.name = "grlib,irqmp", + .qdev.reset = grlib_irqmp_reset, + .qdev.size = sizeof(IRQMP), + .qdev.props = (Property[]) { + 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_register(void) +{ + sysbus_register_withprop(&grlib_irqmp_info); +} + +device_init(grlib_irqmp_register) diff --git a/hw/gt64xxx.c b/hw/gt64xxx.c new file mode 100644 index 0000000..923073b --- /dev/null +++ b/hw/gt64xxx.c @@ -0,0 +1,1174 @@ +/* + * 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.h" +#include "mips.h" +#include "pci.h" +#include "pci_host.h" +#include "pc.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) \ + target_phys_addr_t regname ##_start; \ + target_phys_addr_t regname ##_length; \ + int regname ##_handle + +typedef struct GT64120State { + SysBusDevice busdev; + PCIHostState pci; + uint32_t regs[GT_REGS]; + PCI_MAPPING_ENTRY(PCI0IO); + PCI_MAPPING_ENTRY(ISD); +} 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 (target_phys_addr_t *start, + target_phys_addr_t *length) +{ + target_phys_addr_t begin = *start; + target_phys_addr_t 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) +{ + target_phys_addr_t start = s->regs[GT_ISD] << 21; + target_phys_addr_t length = 0x1000; + + if (s->ISD_length) + cpu_register_physical_memory(s->ISD_start, s->ISD_length, + IO_MEM_UNASSIGNED); + check_reserved_space(&start, &length); + length = 0x1000; + /* Map new address */ + DPRINTF("ISD: "TARGET_FMT_plx"@"TARGET_FMT_plx" -> "TARGET_FMT_plx"@"TARGET_FMT_plx", %x\n", s->ISD_length, s->ISD_start, + length, start, s->ISD_handle); + s->ISD_start = start; + s->ISD_length = length; + cpu_register_physical_memory(s->ISD_start, s->ISD_length, s->ISD_handle); +} + +static void gt64120_pci_mapping(GT64120State *s) +{ + /* Update IO mapping */ + if ((s->regs[GT_PCI0IOLD] & 0x7f) <= s->regs[GT_PCI0IOHD]) + { + /* Unmap old IO address */ + if (s->PCI0IO_length) + { + cpu_register_physical_memory(s->PCI0IO_start, s->PCI0IO_length, IO_MEM_UNASSIGNED); + } + /* 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; + isa_mem_base = s->PCI0IO_start; + isa_mmio_init(s->PCI0IO_start, s->PCI0IO_length); + } +} + +static void gt64120_writel (void *opaque, target_phys_addr_t addr, + uint32_t val) +{ + GT64120State *s = opaque; + 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; + break; + case GT_PCI0M1LD: + s->regs[GT_PCI0M1LD] = val & 0x00007fff; + s->regs[GT_PCI0M1REMAP] = val & 0x000007ff; + 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_PCI0IOHD: + s->regs[saddr] = val & 0x0000007f; + gt64120_pci_mapping(s); + break; + case GT_PCI0M0HD: + case GT_PCI0M1HD: + 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: + s->pci.config_reg = val & 0x80fffffc; + break; + case GT_PCI0_CFGDATA: + if (!(s->regs[GT_PCI0_CMD] & 1) && (s->pci.config_reg & 0x00fff800)) + val = bswap32(val); + if (s->pci.config_reg & (1u << 31)) + pci_data_write(s->pci.bus, s->pci.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 %x\n", val); + break; + case GT_INTRMASK: + s->regs[saddr] = val & 0x3c3ffffe; + DPRINTF("INTRMASK %x\n", val); + break; + case GT_PCI0_ICMASK: + s->regs[saddr] = val & 0x03fffffe; + DPRINTF("ICMASK %x\n", val); + break; + case GT_PCI0_SERR0MASK: + s->regs[saddr] = val & 0x0000003f; + 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: + /* 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 uint32_t gt64120_readl (void *opaque, + target_phys_addr_t addr) +{ + GT64120State *s = opaque; + 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 = s->pci.config_reg; + break; + case GT_PCI0_CFGDATA: + if (!(s->pci.config_reg & (1 << 31))) + val = 0xffffffff; + else + val = pci_data_read(s->pci.bus, s->pci.config_reg, 4); + if (!(s->regs[GT_PCI0_CMD] & 1) && (s->pci.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 CPUWriteMemoryFunc * const gt64120_write[] = { + >64120_writel, + >64120_writel, + >64120_writel, +}; + +static CPUReadMemoryFunc * const gt64120_read[] = { + >64120_readl, + >64120_readl, + >64120_readl, +}; + +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); +} + +static void gt64120_save(QEMUFile* f, void *opaque) +{ + PCIDevice *d = opaque; + pci_device_save(d, f); +} + +static int gt64120_load(QEMUFile* f, void *opaque, int version_id) +{ + PCIDevice *d = opaque; + int ret; + + if (version_id != 1) + return -EINVAL; + ret = pci_device_load(d, f); + if (ret < 0) + return ret; + return 0; +} + +PCIBus *gt64120_register(qemu_irq *pic) +{ + SysBusDevice *s; + GT64120State *d; + DeviceState *dev; + + dev = qdev_create(NULL, "gt64120"); + qdev_init_nofail(dev); + s = sysbus_from_qdev(dev); + d = FROM_SYSBUS(GT64120State, s); + d->pci.bus = pci_register_bus(&d->busdev.qdev, "pci", + gt64120_pci_set_irq, gt64120_pci_map_irq, + pic, PCI_DEVFN(18, 0), 4); + d->ISD_handle = cpu_register_io_memory(gt64120_read, gt64120_write, d, + DEVICE_NATIVE_ENDIAN); + + pci_create_simple(d->pci.bus, PCI_DEVFN(0, 0), "gt64120_pci"); + return d->pci.bus; +} + +static int gt64120_init(SysBusDevice *dev) +{ + GT64120State *s; + + s = FROM_SYSBUS(GT64120State, dev); + + /* FIXME: This value is computed from registers during reset, but some + devices (e.g. VGA card) need to know it when they are registered. + This also mean that changing the register to change the mapping + does not fully work. */ + isa_mem_base = 0x10000000; + qemu_register_reset(gt64120_reset, s); + register_savevm(&dev->qdev, "GT64120 PCI Bus", 0, 1, + gt64120_save, gt64120_load, &s->pci); + return 0; +} + +static int gt64120_pci_init(PCIDevice *d) +{ + /* FIXME: Malta specific hw assumptions ahead */ + pci_config_set_vendor_id(d->config, PCI_VENDOR_ID_MARVELL); + pci_config_set_device_id(d->config, PCI_DEVICE_ID_MARVELL_GT6412X); + pci_set_word(d->config + PCI_COMMAND, 0); + pci_set_word(d->config + PCI_STATUS, + PCI_STATUS_FAST_BACK | PCI_STATUS_DEVSEL_MEDIUM); + pci_set_byte(d->config + PCI_CLASS_REVISION, 0x10); + pci_config_set_prog_interface(d->config, 0); + pci_config_set_class(d->config, PCI_CLASS_BRIDGE_HOST); + 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 PCIDeviceInfo gt64120_pci_info = { + .qdev.name = "gt64120_pci", + .qdev.size = sizeof(PCIDevice), + .init = gt64120_pci_init, +}; + +static void gt64120_pci_register_devices(void) +{ + sysbus_register_dev("gt64120", sizeof(GT64120State), + gt64120_init); + pci_qdev_register(>64120_pci_info); +} + +device_init(gt64120_pci_register_devices) diff --git a/hw/gumstix.c b/hw/gumstix.c new file mode 100644 index 0000000..ee63f63 --- /dev/null +++ b/hw/gumstix.c @@ -0,0 +1,141 @@ +/* + * Gumstix Platforms + * + * Copyright (c) 2007 by Thorsten Zitterell + * + * Code based on spitz platform by Andrzej Zaborowski + * + * This code is licensed under the GNU GPL v2. + */ + +/* + * 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.h" +#include "pxa.h" +#include "net.h" +#include "flash.h" +#include "sysemu.h" +#include "devices.h" +#include "boards.h" +#include "blockdev.h" + +static const int sector_len = 128 * 1024; + +static void connex_init(ram_addr_t ram_size, + const char *boot_device, + const char *kernel_filename, const char *kernel_cmdline, + const char *initrd_filename, const char *cpu_model) +{ + PXA2xxState *cpu; + DriveInfo *dinfo; + int be; + + uint32_t connex_rom = 0x01000000; + uint32_t connex_ram = 0x04000000; + + cpu = pxa255_init(connex_ram); + + dinfo = drive_get(IF_PFLASH, 0, 0); + if (!dinfo) { + 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, qemu_ram_alloc(NULL, "connext.rom", + connex_rom), + dinfo->bdrv, 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(ram_addr_t ram_size, + const char *boot_device, + const char *kernel_filename, const char *kernel_cmdline, + const char *initrd_filename, const char *cpu_model) +{ + PXA2xxState *cpu; + DriveInfo *dinfo; + int be; + + uint32_t verdex_rom = 0x02000000; + uint32_t verdex_ram = 0x10000000; + + cpu = pxa270_init(verdex_ram, cpu_model ?: "pxa270-c0"); + + dinfo = drive_get(IF_PFLASH, 0, 0); + if (!dinfo) { + 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, qemu_ram_alloc(NULL, "verdex.rom", + verdex_rom), + dinfo->bdrv, 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/gus.c b/hw/gus.c new file mode 100644 index 0000000..ff9e7c7 --- /dev/null +++ b/hw/gus.c @@ -0,0 +1,322 @@ +/* + * 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.h" +#include "audiodev.h" +#include "audio/audio.h" +#include "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) + +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_READ_PROTO (gus_readw) +{ + GUSState *s = opaque; + + return gus_read (&s->emu, nport, 2); +} + +IO_WRITE_PROTO (gus_writeb) +{ + GUSState *s = opaque; + + gus_write (&s->emu, nport, 1, val); +} + +IO_WRITE_PROTO (gus_writew) +{ + GUSState *s = opaque; + + gus_write (&s->emu, nport, 2, 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 (0 == ((mode >> 4) & 1)) { + DMA_release_DREQ (s->emu.gusdma); + } + return dma_len; +} + +static const VMStateDescription vmstate_gus = { + .name = "gus", + .version_id = 2, + .minimum_version_id = 2, + .minimum_version_id_old = 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 int gus_initfn (ISADevice *dev) +{ + GUSState *s = DO_UPCAST(GUSState, dev, 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); + return -1; + } + + s->shift = 2; + s->samples = AUD_get_buffer_size_out (s->voice) >> s->shift; + s->mixbuf = qemu_mallocz (s->samples << s->shift); + + register_ioport_write (s->port, 1, 1, gus_writeb, s); + register_ioport_write (s->port, 1, 2, gus_writew, s); + isa_init_ioport_range(dev, s->port, 2); + + register_ioport_read ((s->port + 0x100) & 0xf00, 1, 1, gus_readb, s); + register_ioport_read ((s->port + 0x100) & 0xf00, 1, 2, gus_readw, s); + isa_init_ioport_range(dev, (s->port + 0x100) & 0xf00, 2); + + register_ioport_write (s->port + 6, 10, 1, gus_writeb, s); + register_ioport_write (s->port + 6, 10, 2, gus_writew, s); + register_ioport_read (s->port + 6, 10, 1, gus_readb, s); + register_ioport_read (s->port + 6, 10, 2, gus_readw, s); + isa_init_ioport_range(dev, s->port + 6, 10); + + + register_ioport_write (s->port + 0x100, 8, 1, gus_writeb, s); + register_ioport_write (s->port + 0x100, 8, 2, gus_writew, s); + register_ioport_read (s->port + 0x100, 8, 1, gus_readb, s); + register_ioport_read (s->port + 0x100, 8, 2, gus_readw, s); + isa_init_ioport_range(dev, s->port + 0x100, 8); + + 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 (dev, &s->pic, s->emu.gusirq); + + AUD_set_active_out (s->voice, 1); + + return 0; +} + +int GUS_init (qemu_irq *pic) +{ + isa_create_simple ("gus"); + return 0; +} + +static ISADeviceInfo gus_info = { + .qdev.name = "gus", + .qdev.desc = "Gravis Ultrasound GF1", + .qdev.size = sizeof (GUSState), + .qdev.vmsd = &vmstate_gus, + .init = gus_initfn, + .qdev.props = (Property[]) { + DEFINE_PROP_UINT32 ("freq", GUSState, freq, 44100), + DEFINE_PROP_HEX32 ("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_register (void) +{ + isa_qdev_register (&gus_info); +} +device_init (gus_register) diff --git a/hw/gusemu.h b/hw/gusemu.h new file mode 100644 index 0000000..5093767 --- /dev/null +++ b/hw/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 /* doesnt 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 + 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 transfering 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/gusemu_hal.c b/hw/gusemu_hal.c new file mode 100644 index 0000000..c6f9537 --- /dev/null +++ b/hw/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 transfered 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 adress */ + } + + 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/gusemu_mixer.c b/hw/gusemu_mixer.c new file mode 100644 index 0000000..6d8d9ce --- /dev/null +++ b/hw/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/gustate.h b/hw/gustate.h new file mode 100644 index 0000000..ece903a --- /dev/null +++ b/hw/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/hda-audio.c b/hw/hda-audio.c new file mode 100644 index 0000000..c699d6f --- /dev/null +++ b/hw/hda-audio.c @@ -0,0 +1,926 @@ +/* + * Copyright (C) 2010 Red Hat, Inc. + * + * written by Gerd Hoffmann + * + * 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 . + */ + +#include "hw.h" +#include "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_ID_OUTPUT ((QEMU_HDA_ID_VENDOR << 16) | 0x10) +#define QEMU_HDA_ID_DUPLEX ((QEMU_HDA_ID_VENDOR << 16) | 0x20) + +#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 + +#ifdef CONFIG_MIXEMU +#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_AMP_CAPS QEMU_HDA_AMP_NONE +#endif + +/* common: audio output widget */ +static const desc_param common_params_audio_dac[] = { + { + .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: pin widget (line-out) */ +static const desc_param common_params_audio_lineout[] = { + { + .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, + }, +}; + +/* output: root node */ +static const desc_param output_params_root[] = { + { + .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 output_params_audio_func[] = { + { + .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 output_nodes[] = { + { + .nid = AC_NODE_ROOT, + .name = "root", + .params = output_params_root, + .nparams = ARRAY_SIZE(output_params_root), + },{ + .nid = 1, + .name = "func", + .params = output_params_audio_func, + .nparams = ARRAY_SIZE(output_params_audio_func), + },{ + .nid = 2, + .name = "dac", + .params = common_params_audio_dac, + .nparams = ARRAY_SIZE(common_params_audio_dac), + .stindex = 0, + },{ + .nid = 3, + .name = "out", + .params = common_params_audio_lineout, + .nparams = ARRAY_SIZE(common_params_audio_lineout), + .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 output = { + .name = "output", + .iid = QEMU_HDA_ID_OUTPUT, + .nodes = output_nodes, + .nnodes = ARRAY_SIZE(output_nodes), +}; + +/* duplex: root node */ +static const desc_param duplex_params_root[] = { + { + .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 input widget */ +static const desc_param duplex_params_audio_adc[] = { + { + .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, + }, +}; + +/* duplex: pin widget (line-in) */ +static const desc_param duplex_params_audio_linein[] = { + { + .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, + }, +}; + +/* duplex: audio function */ +static const desc_param duplex_params_audio_func[] = { + { + .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 duplex_nodes[] = { + { + .nid = AC_NODE_ROOT, + .name = "root", + .params = duplex_params_root, + .nparams = ARRAY_SIZE(duplex_params_root), + },{ + .nid = 1, + .name = "func", + .params = duplex_params_audio_func, + .nparams = ARRAY_SIZE(duplex_params_audio_func), + },{ + .nid = 2, + .name = "dac", + .params = common_params_audio_dac, + .nparams = ARRAY_SIZE(common_params_audio_dac), + .stindex = 0, + },{ + .nid = 3, + .name = "out", + .params = common_params_audio_lineout, + .nparams = ARRAY_SIZE(common_params_audio_lineout), + .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 = duplex_params_audio_adc, + .nparams = ARRAY_SIZE(duplex_params_audio_adc), + .stindex = 1, + .conn = (uint32_t[]) { 5 }, + },{ + .nid = 5, + .name = "in", + .params = duplex_params_audio_linein, + .nparams = ARRAY_SIZE(duplex_params_audio_linein), + .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 duplex = { + .name = "duplex", + .iid = QEMU_HDA_ID_DUPLEX, + .nodes = duplex_nodes, + .nnodes = ARRAY_SIZE(duplex_nodes), +}; + +/* -------------------------------------------------------------------------- */ + +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; +}; + +struct HDAAudioState { + HDACodecDevice hda; + const char *name; + + QEMUSoundCard card; + const desc_codec *desc; + HDAAudioStream st[4]; + bool running[16]; + + /* properties */ + uint32_t debug; +}; + +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->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 = DO_UPCAST(HDAAudioState, hda, 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[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) +{ + HDAAudioState *a = DO_UPCAST(HDAAudioState, hda, hda); + int s; + + a->running[stnr] = running; + for (s = 0; s < ARRAY_SIZE(a->st); s++) { + if (a->st[s].node == NULL) { + 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 = DO_UPCAST(HDAAudioState, hda, hda); + HDAAudioStream *st; + const desc_node *node; + const desc_param *param; + uint32_t i, type; + + a->desc = desc; + a->name = a->hda.qdev.info->name; + 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 (NULL == param) + 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 = DO_UPCAST(HDAAudioState, hda, 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__); + 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[st->stream]); + } + return 0; +} + +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 = 1, + .post_load = hda_audio_post_load, + .fields = (VMStateField []) { + VMSTATE_STRUCT_ARRAY(st, HDAAudioState, 4, 0, + vmstate_hda_audio_stream, + HDAAudioStream), + VMSTATE_BOOL_ARRAY(running, HDAAudioState, 16), + VMSTATE_END_OF_LIST() + } +}; + +static Property hda_audio_properties[] = { + DEFINE_PROP_UINT32("debug", HDAAudioState, debug, 0), + DEFINE_PROP_END_OF_LIST(), +}; + +static int hda_audio_init_output(HDACodecDevice *hda) +{ + return hda_audio_init(hda, &output); +} + +static int hda_audio_init_duplex(HDACodecDevice *hda) +{ + return hda_audio_init(hda, &duplex); +} + +static HDACodecDeviceInfo hda_audio_info_output = { + .qdev.name = "hda-output", + .qdev.desc = "HDA Audio Codec, output-only", + .qdev.size = sizeof(HDAAudioState), + .qdev.vmsd = &vmstate_hda_audio, + .qdev.props = hda_audio_properties, + .init = hda_audio_init_output, + .exit = hda_audio_exit, + .command = hda_audio_command, + .stream = hda_audio_stream, +}; + +static HDACodecDeviceInfo hda_audio_info_duplex = { + .qdev.name = "hda-duplex", + .qdev.desc = "HDA Audio Codec, duplex", + .qdev.size = sizeof(HDAAudioState), + .qdev.vmsd = &vmstate_hda_audio, + .qdev.props = hda_audio_properties, + .init = hda_audio_init_duplex, + .exit = hda_audio_exit, + .command = hda_audio_command, + .stream = hda_audio_stream, +}; + +static void hda_audio_register(void) +{ + hda_codec_register(&hda_audio_info_output); + hda_codec_register(&hda_audio_info_duplex); +} +device_init(hda_audio_register); diff --git a/hw/heathrow_pic.c b/hw/heathrow_pic.c new file mode 100644 index 0000000..b19b754 --- /dev/null +++ b/hw/heathrow_pic.c @@ -0,0 +1,230 @@ +/* + * 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.h" +#include "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 { + 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_writel (void *opaque, target_phys_addr_t addr, uint32_t value) +{ + 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 uint32_t pic_readl (void *opaque, target_phys_addr_t addr) +{ + 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 CPUWriteMemoryFunc * const pic_write[] = { + &pic_writel, + &pic_writel, + &pic_writel, +}; + +static CPUReadMemoryFunc * const pic_read[] = { + &pic_readl, + &pic_readl, + &pic_readl, +}; + + +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 void heathrow_pic_save_one(QEMUFile *f, HeathrowPIC *s) +{ + qemu_put_be32s(f, &s->events); + qemu_put_be32s(f, &s->mask); + qemu_put_be32s(f, &s->levels); + qemu_put_be32s(f, &s->level_triggered); +} + +static void heathrow_pic_save(QEMUFile *f, void *opaque) +{ + HeathrowPICS *s = (HeathrowPICS *)opaque; + + heathrow_pic_save_one(f, &s->pics[0]); + heathrow_pic_save_one(f, &s->pics[1]); +} + +static void heathrow_pic_load_one(QEMUFile *f, HeathrowPIC *s) +{ + qemu_get_be32s(f, &s->events); + qemu_get_be32s(f, &s->mask); + qemu_get_be32s(f, &s->levels); + qemu_get_be32s(f, &s->level_triggered); +} + +static int heathrow_pic_load(QEMUFile *f, void *opaque, int version_id) +{ + HeathrowPICS *s = (HeathrowPICS *)opaque; + + if (version_id != 1) + return -EINVAL; + + heathrow_pic_load_one(f, &s->pics[0]); + heathrow_pic_load_one(f, &s->pics[1]); + + return 0; +} + +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(int *pmem_index, + int nb_cpus, qemu_irq **irqs) +{ + HeathrowPICS *s; + + s = qemu_mallocz(sizeof(HeathrowPICS)); + /* only 1 CPU */ + s->irqs = irqs[0]; + *pmem_index = cpu_register_io_memory(pic_read, pic_write, s, + DEVICE_LITTLE_ENDIAN); + + register_savevm(NULL, "heathrow_pic", -1, 1, heathrow_pic_save, + heathrow_pic_load, s); + qemu_register_reset(heathrow_pic_reset, s); + return qemu_allocate_irqs(heathrow_pic_set_irq, s, 64); +} diff --git a/hw/hpet.c b/hw/hpet.c new file mode 100644 index 0000000..c12064d --- /dev/null +++ b/hw/hpet.c @@ -0,0 +1,747 @@ +/* + * High Precisition Event Timer emulation + * + * Copyright (c) 2007 Alexander Graf + * Copyright (c) 2008 IBM Corporation + * + * Authors: Beth Kon + * + * 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 . + * + * ***************************************************************** + * + * This driver attempts to emulate an HPET device in software. + */ + +#include "hw.h" +#include "pc.h" +#include "console.h" +#include "qemu-timer.h" +#include "hpet_emul.h" +#include "sysbus.h" +#include "mc146818rtc.h" + +//#define HPET_DEBUG +#ifdef HPET_DEBUG +#define DPRINTF printf +#else +#define DPRINTF(...) +#endif + +#define HPET_MSI_SUPPORT 0 + +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 { + SysBusDevice busdev; + uint64_t hpet_offset; + qemu_irq irqs[HPET_NUM_IRQ_ROUTES]; + uint32_t flags; + uint8_t rtc_irq_level; + uint8_t num_timers; + 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; + +struct hpet_fw_config hpet_cfg = {.count = UINT8_MAX}; + +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_get_clock(vm_clock) + 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)0; + return (uint64_t)diff; + } else { + uint64_t diff, cmp; + + cmp = t->cmp; + diff = cmp - current; + diff = (int64_t)diff > 0 ? diff : (uint64_t)0; + 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)) { + qemu_irq_lower(s->irqs[route]); + } + } else if (timer_fsb_route(timer)) { + stl_phys(timer->fsb >> 32, timer->fsb & 0xffffffff); + } else if (timer->config & HPET_TN_TYPE_LEVEL) { + s->isr |= mask; + 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 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_get_clock(vm_clock); + + /* 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 const VMStateDescription vmstate_hpet_timer = { + .name = "hpet_timer", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 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(qemu_timer, HPETTimer), + VMSTATE_END_OF_LIST() + } +}; + +static const VMStateDescription vmstate_hpet = { + .name = "hpet", + .version_id = 2, + .minimum_version_id = 1, + .minimum_version_id_old = 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_STRUCT_VARRAY_UINT8(timer, HPETState, num_timers, 0, + vmstate_hpet_timer, HPETTimer), + VMSTATE_END_OF_LIST() + } +}; + +/* + * 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); + qemu_mod_timer(t->qemu_timer, + qemu_get_clock(vm_clock) + (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); + qemu_mod_timer(t->qemu_timer, qemu_get_clock(vm_clock) + + (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; + } + } + qemu_mod_timer(t->qemu_timer, + qemu_get_clock(vm_clock) + (int64_t)ticks_to_ns(diff)); +} + +static void hpet_del_timer(HPETTimer *t) +{ + qemu_del_timer(t->qemu_timer); + update_irq(t, 0); +} + +#ifdef HPET_DEBUG +static uint32_t hpet_ram_readb(void *opaque, target_phys_addr_t addr) +{ + printf("qemu: hpet_read b at %" PRIx64 "\n", addr); + return 0; +} + +static uint32_t hpet_ram_readw(void *opaque, target_phys_addr_t addr) +{ + printf("qemu: hpet_read w at %" PRIx64 "\n", addr); + return 0; +} +#endif + +static uint32_t hpet_ram_readl(void *opaque, target_phys_addr_t addr) +{ + 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; +} + +#ifdef HPET_DEBUG +static void hpet_ram_writeb(void *opaque, target_phys_addr_t addr, + uint32_t value) +{ + printf("qemu: invalid hpet_write b at %" PRIx64 " = %#x\n", + addr, value); +} + +static void hpet_ram_writew(void *opaque, target_phys_addr_t addr, + uint32_t value) +{ + printf("qemu: invalid hpet_write w at %" PRIx64 " = %#x\n", + addr, value); +} +#endif + +static void hpet_ram_writel(void *opaque, target_phys_addr_t addr, + uint32_t value) +{ + 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_readl(opaque, addr); + 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_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_get_clock(vm_clock); + 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 are disabled when HPET is in legacy mode */ + if (activating_bit(old_val, new_val, HPET_CFG_LEGACY)) { + hpet_pit_disable(); + qemu_irq_lower(s->irqs[RTC_ISA_IRQ]); + } else if (deactivating_bit(old_val, new_val, HPET_CFG_LEGACY)) { + hpet_pit_enable(); + 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 CPUReadMemoryFunc * const hpet_ram_read[] = { +#ifdef HPET_DEBUG + hpet_ram_readb, + hpet_ram_readw, +#else + NULL, + NULL, +#endif + hpet_ram_readl, +}; + +static CPUWriteMemoryFunc * const hpet_ram_write[] = { +#ifdef HPET_DEBUG + hpet_ram_writeb, + hpet_ram_writew, +#else + NULL, + NULL, +#endif + hpet_ram_writel, +}; + +static void hpet_reset(DeviceState *d) +{ + HPETState *s = FROM_SYSBUS(HPETState, sysbus_from_qdev(d)); + int i; + static int count = 0; + + 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 inti2 */ + timer->config |= 0x00000004ULL << 32; + timer->period = 0ULL; + timer->wrap_flag = 0; + } + + s->hpet_counter = 0ULL; + s->hpet_offset = 0ULL; + s->config = 0ULL; + if (count > 0) { + /* we don't enable pit when hpet_reset is first called (by hpet_init) + * because hpet is taking over for pit here. On subsequent invocations, + * hpet_reset is called due to system reset. At this point control must + * be returned to pit until SW reenables hpet. + */ + hpet_pit_enable(); + } + hpet_cfg.hpet[s->hpet_id].event_timer_block_id = (uint32_t)s->capability; + hpet_cfg.hpet[s->hpet_id].address = sysbus_from_qdev(d)->mmio[0].addr; + count = 1; +} + +static void hpet_handle_rtc_irq(void *opaque, int n, int level) +{ + HPETState *s = FROM_SYSBUS(HPETState, opaque); + + s->rtc_irq_level = level; + if (!hpet_in_legacy_mode(s)) { + qemu_set_irq(s->irqs[RTC_ISA_IRQ], level); + } +} + +static int hpet_init(SysBusDevice *dev) +{ + HPETState *s = FROM_SYSBUS(HPETState, dev); + int i, iomemtype; + HPETTimer *timer; + + if (hpet_cfg.count == UINT8_MAX) { + /* first instance */ + hpet_cfg.count = 0; + } + + if (hpet_cfg.count == 8) { + fprintf(stderr, "Only 8 instances of HPET is allowed\n"); + return -1; + } + + s->hpet_id = hpet_cfg.count++; + + for (i = 0; i < HPET_NUM_IRQ_ROUTES; i++) { + sysbus_init_irq(dev, &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 = qemu_new_timer(vm_clock, 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->qdev, hpet_handle_rtc_irq, 1); + + /* HPET Area */ + iomemtype = cpu_register_io_memory(hpet_ram_read, + hpet_ram_write, s, + DEVICE_NATIVE_ENDIAN); + sysbus_init_mmio(dev, 0x400, iomemtype); + return 0; +} + +static SysBusDeviceInfo hpet_device_info = { + .qdev.name = "hpet", + .qdev.size = sizeof(HPETState), + .qdev.no_user = 1, + .qdev.vmsd = &vmstate_hpet, + .qdev.reset = hpet_reset, + .init = hpet_init, + .qdev.props = (Property[]) { + DEFINE_PROP_UINT8("timers", HPETState, num_timers, HPET_MIN_TIMERS), + DEFINE_PROP_BIT("msi", HPETState, flags, HPET_MSI_SUPPORT, false), + DEFINE_PROP_END_OF_LIST(), + }, +}; + +static void hpet_register_device(void) +{ + sysbus_register_withprop(&hpet_device_info); +} + +device_init(hpet_register_device) diff --git a/hw/hpet_emul.h b/hw/hpet_emul.h new file mode 100644 index 0000000..8bf312a --- /dev/null +++ b/hw/hpet_emul.h @@ -0,0 +1,71 @@ +/* + * QEMU Emulated HPET support + * + * Copyright IBM, Corp. 2008 + * + * Authors: + * Beth Kon + * + * This work is licensed under the terms of the GNU GPL, version 2. See + * the COPYING file in the top-level directory. + * + */ +#ifndef QEMU_HPET_EMUL_H +#define QEMU_HPET_EMUL_H + +#define HPET_BASE 0xfed00000 +#define HPET_CLK_PERIOD 10000000ULL /* 10000000 femtoseconds == 10ns*/ + +#define FS_PER_NS 1000000 +#define HPET_MIN_TIMERS 3 +#define HPET_MAX_TIMERS 32 + +#define HPET_NUM_IRQ_ROUTES 32 + +#define HPET_CFG_ENABLE 0x001 +#define HPET_CFG_LEGACY 0x002 + +#define HPET_ID 0x000 +#define HPET_PERIOD 0x004 +#define HPET_CFG 0x010 +#define HPET_STATUS 0x020 +#define HPET_COUNTER 0x0f0 +#define HPET_TN_CFG 0x000 +#define HPET_TN_CMP 0x008 +#define HPET_TN_ROUTE 0x010 +#define HPET_CFG_WRITE_MASK 0x3 + +#define HPET_ID_NUM_TIM_SHIFT 8 +#define HPET_ID_NUM_TIM_MASK 0x1f00 + +#define HPET_TN_TYPE_LEVEL 0x002 +#define HPET_TN_ENABLE 0x004 +#define HPET_TN_PERIODIC 0x008 +#define HPET_TN_PERIODIC_CAP 0x010 +#define HPET_TN_SIZE_CAP 0x020 +#define HPET_TN_SETVAL 0x040 +#define HPET_TN_32BIT 0x100 +#define HPET_TN_INT_ROUTE_MASK 0x3e00 +#define HPET_TN_FSB_ENABLE 0x4000 +#define HPET_TN_FSB_CAP 0x8000 +#define HPET_TN_CFG_WRITE_MASK 0x7f4e +#define HPET_TN_INT_ROUTE_SHIFT 9 +#define HPET_TN_INT_ROUTE_CAP_SHIFT 32 +#define HPET_TN_CFG_BITS_READONLY_OR_RESERVED 0xffff80b1U + +struct hpet_fw_entry +{ + uint32_t event_timer_block_id; + uint64_t address; + uint16_t min_tick; + uint8_t page_prot; +} __attribute__ ((packed)); + +struct hpet_fw_config +{ + uint8_t count; + struct hpet_fw_entry hpet[8]; +} __attribute__ ((packed)); + +extern struct hpet_fw_config hpet_cfg; +#endif diff --git a/hw/hw.h b/hw/hw.h new file mode 100644 index 0000000..5e24329 --- /dev/null +++ b/hw/hw.h @@ -0,0 +1,824 @@ +/* Declarations for use by hardware emulation. */ +#ifndef QEMU_HW_H +#define QEMU_HW_H + +#include "qemu-common.h" + +#if defined(TARGET_PHYS_ADDR_BITS) && !defined(NEED_CPU_H) +#include "cpu-common.h" +#endif + +#include "ioport.h" +#include "irq.h" + +/* VM Load/Save */ + +/* This function writes a chunk of data to a file at the given position. + * The pos argument can be ignored if the file is only being used for + * streaming. The handler should try to write all of the data it can. + */ +typedef int (QEMUFilePutBufferFunc)(void *opaque, const uint8_t *buf, + int64_t pos, int size); + +/* Read a chunk of data from a file at the given position. The pos argument + * can be ignored if the file is only be used for streaming. The number of + * bytes actually read should be returned. + */ +typedef int (QEMUFileGetBufferFunc)(void *opaque, uint8_t *buf, + int64_t pos, int size); + +/* Close a file and return an error code */ +typedef int (QEMUFileCloseFunc)(void *opaque); + +/* Called to determine if the file has exceeded it's bandwidth allocation. The + * bandwidth capping is a soft limit, not a hard limit. + */ +typedef int (QEMUFileRateLimit)(void *opaque); + +/* Called to change the current bandwidth allocation. This function must return + * the new actual bandwidth. It should be new_rate if everything goes ok, and + * the old rate otherwise + */ +typedef int64_t (QEMUFileSetRateLimit)(void *opaque, int64_t new_rate); +typedef int64_t (QEMUFileGetRateLimit)(void *opaque); + +QEMUFile *qemu_fopen_ops(void *opaque, QEMUFilePutBufferFunc *put_buffer, + QEMUFileGetBufferFunc *get_buffer, + QEMUFileCloseFunc *close, + QEMUFileRateLimit *rate_limit, + QEMUFileSetRateLimit *set_rate_limit, + QEMUFileGetRateLimit *get_rate_limit); +QEMUFile *qemu_fopen(const char *filename, const char *mode); +QEMUFile *qemu_fdopen(int fd, const char *mode); +QEMUFile *qemu_fopen_socket(int fd); +QEMUFile *qemu_popen(FILE *popen_file, const char *mode); +QEMUFile *qemu_popen_cmd(const char *command, const char *mode); +int qemu_stdio_fd(QEMUFile *f); +void qemu_fflush(QEMUFile *f); +int qemu_fclose(QEMUFile *f); +void qemu_put_buffer(QEMUFile *f, const uint8_t *buf, int size); +void qemu_put_byte(QEMUFile *f, int v); + +static inline void qemu_put_ubyte(QEMUFile *f, unsigned int v) +{ + qemu_put_byte(f, (int)v); +} + +#define qemu_put_sbyte qemu_put_byte + +void qemu_put_be16(QEMUFile *f, unsigned int v); +void qemu_put_be32(QEMUFile *f, unsigned int v); +void qemu_put_be64(QEMUFile *f, uint64_t v); +int qemu_get_buffer(QEMUFile *f, uint8_t *buf, int size); +int qemu_get_byte(QEMUFile *f); + +static inline unsigned int qemu_get_ubyte(QEMUFile *f) +{ + return (unsigned int)qemu_get_byte(f); +} + +#define qemu_get_sbyte qemu_get_byte + +unsigned int qemu_get_be16(QEMUFile *f); +unsigned int qemu_get_be32(QEMUFile *f); +uint64_t qemu_get_be64(QEMUFile *f); +int qemu_file_rate_limit(QEMUFile *f); +int64_t qemu_file_set_rate_limit(QEMUFile *f, int64_t new_rate); +int64_t qemu_file_get_rate_limit(QEMUFile *f); +int qemu_file_has_error(QEMUFile *f); +void qemu_file_set_error(QEMUFile *f); + +/* Try to send any outstanding data. This function is useful when output is + * halted due to rate limiting or EAGAIN errors occur as it can be used to + * resume output. */ +void qemu_file_put_notify(QEMUFile *f); + +static inline void qemu_put_be64s(QEMUFile *f, const uint64_t *pv) +{ + qemu_put_be64(f, *pv); +} + +static inline void qemu_put_be32s(QEMUFile *f, const uint32_t *pv) +{ + qemu_put_be32(f, *pv); +} + +static inline void qemu_put_be16s(QEMUFile *f, const uint16_t *pv) +{ + qemu_put_be16(f, *pv); +} + +static inline void qemu_put_8s(QEMUFile *f, const uint8_t *pv) +{ + qemu_put_byte(f, *pv); +} + +static inline void qemu_get_be64s(QEMUFile *f, uint64_t *pv) +{ + *pv = qemu_get_be64(f); +} + +static inline void qemu_get_be32s(QEMUFile *f, uint32_t *pv) +{ + *pv = qemu_get_be32(f); +} + +static inline void qemu_get_be16s(QEMUFile *f, uint16_t *pv) +{ + *pv = qemu_get_be16(f); +} + +static inline void qemu_get_8s(QEMUFile *f, uint8_t *pv) +{ + *pv = qemu_get_byte(f); +} + +// Signed versions for type safety +static inline void qemu_put_sbuffer(QEMUFile *f, const int8_t *buf, int size) +{ + qemu_put_buffer(f, (const uint8_t *)buf, size); +} + +static inline void qemu_put_sbe16(QEMUFile *f, int v) +{ + qemu_put_be16(f, (unsigned int)v); +} + +static inline void qemu_put_sbe32(QEMUFile *f, int v) +{ + qemu_put_be32(f, (unsigned int)v); +} + +static inline void qemu_put_sbe64(QEMUFile *f, int64_t v) +{ + qemu_put_be64(f, (uint64_t)v); +} + +static inline size_t qemu_get_sbuffer(QEMUFile *f, int8_t *buf, int size) +{ + return qemu_get_buffer(f, (uint8_t *)buf, size); +} + +static inline int qemu_get_sbe16(QEMUFile *f) +{ + return (int)qemu_get_be16(f); +} + +static inline int qemu_get_sbe32(QEMUFile *f) +{ + return (int)qemu_get_be32(f); +} + +static inline int64_t qemu_get_sbe64(QEMUFile *f) +{ + return (int64_t)qemu_get_be64(f); +} + +static inline void qemu_put_s8s(QEMUFile *f, const int8_t *pv) +{ + qemu_put_8s(f, (const uint8_t *)pv); +} + +static inline void qemu_put_sbe16s(QEMUFile *f, const int16_t *pv) +{ + qemu_put_be16s(f, (const uint16_t *)pv); +} + +static inline void qemu_put_sbe32s(QEMUFile *f, const int32_t *pv) +{ + qemu_put_be32s(f, (const uint32_t *)pv); +} + +static inline void qemu_put_sbe64s(QEMUFile *f, const int64_t *pv) +{ + qemu_put_be64s(f, (const uint64_t *)pv); +} + +static inline void qemu_get_s8s(QEMUFile *f, int8_t *pv) +{ + qemu_get_8s(f, (uint8_t *)pv); +} + +static inline void qemu_get_sbe16s(QEMUFile *f, int16_t *pv) +{ + qemu_get_be16s(f, (uint16_t *)pv); +} + +static inline void qemu_get_sbe32s(QEMUFile *f, int32_t *pv) +{ + qemu_get_be32s(f, (uint32_t *)pv); +} + +static inline void qemu_get_sbe64s(QEMUFile *f, int64_t *pv) +{ + qemu_get_be64s(f, (uint64_t *)pv); +} + +#ifdef NEED_CPU_H +#if TARGET_LONG_BITS == 64 +#define qemu_put_betl qemu_put_be64 +#define qemu_get_betl qemu_get_be64 +#define qemu_put_betls qemu_put_be64s +#define qemu_get_betls qemu_get_be64s +#define qemu_put_sbetl qemu_put_sbe64 +#define qemu_get_sbetl qemu_get_sbe64 +#define qemu_put_sbetls qemu_put_sbe64s +#define qemu_get_sbetls qemu_get_sbe64s +#else +#define qemu_put_betl qemu_put_be32 +#define qemu_get_betl qemu_get_be32 +#define qemu_put_betls qemu_put_be32s +#define qemu_get_betls qemu_get_be32s +#define qemu_put_sbetl qemu_put_sbe32 +#define qemu_get_sbetl qemu_get_sbe32 +#define qemu_put_sbetls qemu_put_sbe32s +#define qemu_get_sbetls qemu_get_sbe32s +#endif +#endif + +int64_t qemu_ftell(QEMUFile *f); +int64_t qemu_fseek(QEMUFile *f, int64_t pos, int whence); + +typedef void SaveSetParamsHandler(int blk_enable, int shared, void * opaque); +typedef void SaveStateHandler(QEMUFile *f, void *opaque); +typedef int SaveLiveStateHandler(Monitor *mon, QEMUFile *f, int stage, + void *opaque); +typedef int LoadStateHandler(QEMUFile *f, void *opaque, int version_id); + +int register_savevm(DeviceState *dev, + const char *idstr, + int instance_id, + int version_id, + SaveStateHandler *save_state, + LoadStateHandler *load_state, + void *opaque); + +int register_savevm_live(DeviceState *dev, + const char *idstr, + int instance_id, + int version_id, + SaveSetParamsHandler *set_params, + SaveLiveStateHandler *save_live_state, + SaveStateHandler *save_state, + LoadStateHandler *load_state, + void *opaque); + +void unregister_savevm(DeviceState *dev, const char *idstr, void *opaque); +void register_device_unmigratable(DeviceState *dev, const char *idstr, + void *opaque); + +typedef void QEMUResetHandler(void *opaque); + +void qemu_register_reset(QEMUResetHandler *func, void *opaque); +void qemu_unregister_reset(QEMUResetHandler *func, void *opaque); + +/* handler to set the boot_device order for a specific type of QEMUMachine */ +/* return 0 if success */ +typedef int QEMUBootSetHandler(void *opaque, const char *boot_devices); +void qemu_register_boot_set(QEMUBootSetHandler *func, void *opaque); +int qemu_boot_set(const char *boot_devices); + +typedef struct VMStateInfo VMStateInfo; +typedef struct VMStateDescription VMStateDescription; + +struct VMStateInfo { + const char *name; + int (*get)(QEMUFile *f, void *pv, size_t size); + void (*put)(QEMUFile *f, void *pv, size_t size); +}; + +enum VMStateFlags { + VMS_SINGLE = 0x001, + VMS_POINTER = 0x002, + VMS_ARRAY = 0x004, + VMS_STRUCT = 0x008, + VMS_VARRAY_INT32 = 0x010, /* Array with size in int32_t field*/ + VMS_BUFFER = 0x020, /* static sized buffer */ + VMS_ARRAY_OF_POINTER = 0x040, + VMS_VARRAY_UINT16 = 0x080, /* Array with size in uint16_t field */ + VMS_VBUFFER = 0x100, /* Buffer with size in int32_t field */ + VMS_MULTIPLY = 0x200, /* multiply "size" field by field_size */ +}; + +typedef struct { + const char *name; + size_t offset; + size_t size; + size_t start; + int num; + size_t num_offset; + size_t size_offset; + const VMStateInfo *info; + enum VMStateFlags flags; + const VMStateDescription *vmsd; + int version_id; + bool (*field_exists)(void *opaque, int version_id); +} VMStateField; + +typedef struct VMStateSubsection { + const VMStateDescription *vmsd; + bool (*needed)(void *opaque); +} VMStateSubsection; + +struct VMStateDescription { + const char *name; + int version_id; + int minimum_version_id; + int minimum_version_id_old; + LoadStateHandler *load_state_old; + int (*pre_load)(void *opaque); + int (*post_load)(void *opaque, int version_id); + void (*pre_save)(void *opaque); + VMStateField *fields; + const VMStateSubsection *subsections; +}; + +extern const VMStateInfo vmstate_info_bool; + +extern const VMStateInfo vmstate_info_int8; +extern const VMStateInfo vmstate_info_int16; +extern const VMStateInfo vmstate_info_int32; +extern const VMStateInfo vmstate_info_int64; + +extern const VMStateInfo vmstate_info_uint8_equal; +extern const VMStateInfo vmstate_info_uint16_equal; +extern const VMStateInfo vmstate_info_int32_equal; +extern const VMStateInfo vmstate_info_int32_le; + +extern const VMStateInfo vmstate_info_uint8; +extern const VMStateInfo vmstate_info_uint16; +extern const VMStateInfo vmstate_info_uint32; +extern const VMStateInfo vmstate_info_uint64; + +extern const VMStateInfo vmstate_info_timer; +extern const VMStateInfo vmstate_info_ptimer; +extern const VMStateInfo vmstate_info_buffer; +extern const VMStateInfo vmstate_info_unused_buffer; + +#define type_check_array(t1,t2,n) ((t1(*)[n])0 - (t2*)0) +#define type_check_pointer(t1,t2) ((t1**)0 - (t2*)0) + +#define vmstate_offset_value(_state, _field, _type) \ + (offsetof(_state, _field) + \ + type_check(_type, typeof_field(_state, _field))) + +#define vmstate_offset_pointer(_state, _field, _type) \ + (offsetof(_state, _field) + \ + type_check_pointer(_type, typeof_field(_state, _field))) + +#define vmstate_offset_array(_state, _field, _type, _num) \ + (offsetof(_state, _field) + \ + type_check_array(_type, typeof_field(_state, _field), _num)) + +#define vmstate_offset_sub_array(_state, _field, _type, _start) \ + (offsetof(_state, _field[_start])) + +#define vmstate_offset_buffer(_state, _field) \ + vmstate_offset_array(_state, _field, uint8_t, \ + sizeof(typeof_field(_state, _field))) + +#define VMSTATE_SINGLE_TEST(_field, _state, _test, _version, _info, _type) { \ + .name = (stringify(_field)), \ + .version_id = (_version), \ + .field_exists = (_test), \ + .size = sizeof(_type), \ + .info = &(_info), \ + .flags = VMS_SINGLE, \ + .offset = vmstate_offset_value(_state, _field, _type), \ +} + +#define VMSTATE_POINTER(_field, _state, _version, _info, _type) { \ + .name = (stringify(_field)), \ + .version_id = (_version), \ + .info = &(_info), \ + .size = sizeof(_type), \ + .flags = VMS_SINGLE|VMS_POINTER, \ + .offset = vmstate_offset_value(_state, _field, _type), \ +} + +#define VMSTATE_ARRAY(_field, _state, _num, _version, _info, _type) {\ + .name = (stringify(_field)), \ + .version_id = (_version), \ + .num = (_num), \ + .info = &(_info), \ + .size = sizeof(_type), \ + .flags = VMS_ARRAY, \ + .offset = vmstate_offset_array(_state, _field, _type, _num), \ +} + +#define VMSTATE_ARRAY_TEST(_field, _state, _num, _test, _info, _type) {\ + .name = (stringify(_field)), \ + .field_exists = (_test), \ + .num = (_num), \ + .info = &(_info), \ + .size = sizeof(_type), \ + .flags = VMS_ARRAY, \ + .offset = vmstate_offset_array(_state, _field, _type, _num),\ +} + +#define VMSTATE_SUB_ARRAY(_field, _state, _start, _num, _version, _info, _type) { \ + .name = (stringify(_field)), \ + .version_id = (_version), \ + .num = (_num), \ + .info = &(_info), \ + .size = sizeof(_type), \ + .flags = VMS_ARRAY, \ + .offset = vmstate_offset_sub_array(_state, _field, _type, _start), \ +} + +#define VMSTATE_VARRAY_INT32(_field, _state, _field_num, _version, _info, _type) {\ + .name = (stringify(_field)), \ + .version_id = (_version), \ + .num_offset = vmstate_offset_value(_state, _field_num, int32_t), \ + .info = &(_info), \ + .size = sizeof(_type), \ + .flags = VMS_VARRAY_INT32|VMS_POINTER, \ + .offset = vmstate_offset_pointer(_state, _field, _type), \ +} + +#define VMSTATE_VARRAY_UINT16_UNSAFE(_field, _state, _field_num, _version, _info, _type) {\ + .name = (stringify(_field)), \ + .version_id = (_version), \ + .num_offset = vmstate_offset_value(_state, _field_num, uint16_t),\ + .info = &(_info), \ + .size = sizeof(_type), \ + .flags = VMS_VARRAY_UINT16, \ + .offset = offsetof(_state, _field), \ +} + +#define VMSTATE_STRUCT_TEST(_field, _state, _test, _version, _vmsd, _type) { \ + .name = (stringify(_field)), \ + .version_id = (_version), \ + .field_exists = (_test), \ + .vmsd = &(_vmsd), \ + .size = sizeof(_type), \ + .flags = VMS_STRUCT, \ + .offset = vmstate_offset_value(_state, _field, _type), \ +} + +#define VMSTATE_STRUCT_POINTER_TEST(_field, _state, _test, _vmsd, _type) { \ + .name = (stringify(_field)), \ + .field_exists = (_test), \ + .vmsd = &(_vmsd), \ + .size = sizeof(_type), \ + .flags = VMS_STRUCT|VMS_POINTER, \ + .offset = vmstate_offset_value(_state, _field, _type), \ +} + +#define VMSTATE_ARRAY_OF_POINTER(_field, _state, _num, _version, _info, _type) {\ + .name = (stringify(_field)), \ + .version_id = (_version), \ + .num = (_num), \ + .info = &(_info), \ + .size = sizeof(_type), \ + .flags = VMS_ARRAY|VMS_ARRAY_OF_POINTER, \ + .offset = vmstate_offset_array(_state, _field, _type, _num), \ +} + +#define VMSTATE_STRUCT_ARRAY(_field, _state, _num, _version, _vmsd, _type) { \ + .name = (stringify(_field)), \ + .num = (_num), \ + .version_id = (_version), \ + .vmsd = &(_vmsd), \ + .size = sizeof(_type), \ + .flags = VMS_STRUCT|VMS_ARRAY, \ + .offset = vmstate_offset_array(_state, _field, _type, _num), \ +} + +#define VMSTATE_STRUCT_VARRAY_UINT8(_field, _state, _field_num, _version, _vmsd, _type) { \ + .name = (stringify(_field)), \ + .num_offset = vmstate_offset_value(_state, _field_num, uint8_t), \ + .version_id = (_version), \ + .vmsd = &(_vmsd), \ + .size = sizeof(_type), \ + .flags = VMS_STRUCT|VMS_VARRAY_INT32, \ + .offset = offsetof(_state, _field), \ +} + +#define VMSTATE_STATIC_BUFFER(_field, _state, _version, _test, _start, _size) { \ + .name = (stringify(_field)), \ + .version_id = (_version), \ + .field_exists = (_test), \ + .size = (_size - _start), \ + .info = &vmstate_info_buffer, \ + .flags = VMS_BUFFER, \ + .offset = vmstate_offset_buffer(_state, _field) + _start, \ +} + +#define VMSTATE_BUFFER_MULTIPLY(_field, _state, _version, _test, _start, _field_size, _multiply) { \ + .name = (stringify(_field)), \ + .version_id = (_version), \ + .field_exists = (_test), \ + .size_offset = vmstate_offset_value(_state, _field_size, uint32_t),\ + .size = (_multiply), \ + .info = &vmstate_info_buffer, \ + .flags = VMS_VBUFFER|VMS_MULTIPLY, \ + .offset = offsetof(_state, _field), \ + .start = (_start), \ +} + +#define VMSTATE_VBUFFER(_field, _state, _version, _test, _start, _field_size) { \ + .name = (stringify(_field)), \ + .version_id = (_version), \ + .field_exists = (_test), \ + .size_offset = vmstate_offset_value(_state, _field_size, int32_t),\ + .info = &vmstate_info_buffer, \ + .flags = VMS_VBUFFER|VMS_POINTER, \ + .offset = offsetof(_state, _field), \ + .start = (_start), \ +} + +#define VMSTATE_VBUFFER_UINT32(_field, _state, _version, _test, _start, _field_size) { \ + .name = (stringify(_field)), \ + .version_id = (_version), \ + .field_exists = (_test), \ + .size_offset = vmstate_offset_value(_state, _field_size, uint32_t),\ + .info = &vmstate_info_buffer, \ + .flags = VMS_VBUFFER|VMS_POINTER, \ + .offset = offsetof(_state, _field), \ + .start = (_start), \ +} + +#define VMSTATE_BUFFER_UNSAFE_INFO(_field, _state, _version, _info, _size) { \ + .name = (stringify(_field)), \ + .version_id = (_version), \ + .size = (_size), \ + .info = &(_info), \ + .flags = VMS_BUFFER, \ + .offset = offsetof(_state, _field), \ +} + +#define VMSTATE_UNUSED_BUFFER(_test, _version, _size) { \ + .name = "unused", \ + .field_exists = (_test), \ + .version_id = (_version), \ + .size = (_size), \ + .info = &vmstate_info_unused_buffer, \ + .flags = VMS_BUFFER, \ +} +extern const VMStateDescription vmstate_pci_device; + +#define VMSTATE_PCI_DEVICE(_field, _state) { \ + .name = (stringify(_field)), \ + .size = sizeof(PCIDevice), \ + .vmsd = &vmstate_pci_device, \ + .flags = VMS_STRUCT, \ + .offset = vmstate_offset_value(_state, _field, PCIDevice), \ +} + +extern const VMStateDescription vmstate_pcie_device; + +#define VMSTATE_PCIE_DEVICE(_field, _state) { \ + .name = (stringify(_field)), \ + .version_id = 2, \ + .size = sizeof(PCIDevice), \ + .vmsd = &vmstate_pcie_device, \ + .flags = VMS_STRUCT, \ + .offset = vmstate_offset_value(_state, _field, PCIDevice), \ +} + +extern const VMStateDescription vmstate_i2c_slave; + +#define VMSTATE_I2C_SLAVE(_field, _state) { \ + .name = (stringify(_field)), \ + .size = sizeof(i2c_slave), \ + .vmsd = &vmstate_i2c_slave, \ + .flags = VMS_STRUCT, \ + .offset = vmstate_offset_value(_state, _field, i2c_slave), \ +} + +extern const VMStateDescription vmstate_usb_device; + +#define VMSTATE_USB_DEVICE(_field, _state) { \ + .name = (stringify(_field)), \ + .size = sizeof(USBDevice), \ + .vmsd = &vmstate_usb_device, \ + .flags = VMS_STRUCT, \ + .offset = vmstate_offset_value(_state, _field, USBDevice), \ +} + +#define vmstate_offset_macaddr(_state, _field) \ + vmstate_offset_array(_state, _field.a, uint8_t, \ + sizeof(typeof_field(_state, _field))) + +#define VMSTATE_MACADDR(_field, _state) { \ + .name = (stringify(_field)), \ + .size = sizeof(MACAddr), \ + .info = &vmstate_info_buffer, \ + .flags = VMS_BUFFER, \ + .offset = vmstate_offset_macaddr(_state, _field), \ +} + +/* _f : field name + _f_n : num of elements field_name + _n : num of elements + _s : struct state name + _v : version +*/ + +#define VMSTATE_SINGLE(_field, _state, _version, _info, _type) \ + VMSTATE_SINGLE_TEST(_field, _state, NULL, _version, _info, _type) + +#define VMSTATE_STRUCT(_field, _state, _version, _vmsd, _type) \ + VMSTATE_STRUCT_TEST(_field, _state, NULL, _version, _vmsd, _type) + +#define VMSTATE_STRUCT_POINTER(_field, _state, _vmsd, _type) \ + VMSTATE_STRUCT_POINTER_TEST(_field, _state, NULL, _vmsd, _type) + +#define VMSTATE_BOOL_V(_f, _s, _v) \ + VMSTATE_SINGLE(_f, _s, _v, vmstate_info_bool, bool) + +#define VMSTATE_INT8_V(_f, _s, _v) \ + VMSTATE_SINGLE(_f, _s, _v, vmstate_info_int8, int8_t) +#define VMSTATE_INT16_V(_f, _s, _v) \ + VMSTATE_SINGLE(_f, _s, _v, vmstate_info_int16, int16_t) +#define VMSTATE_INT32_V(_f, _s, _v) \ + VMSTATE_SINGLE(_f, _s, _v, vmstate_info_int32, int32_t) +#define VMSTATE_INT64_V(_f, _s, _v) \ + VMSTATE_SINGLE(_f, _s, _v, vmstate_info_int64, int64_t) + +#define VMSTATE_UINT8_V(_f, _s, _v) \ + VMSTATE_SINGLE(_f, _s, _v, vmstate_info_uint8, uint8_t) +#define VMSTATE_UINT16_V(_f, _s, _v) \ + VMSTATE_SINGLE(_f, _s, _v, vmstate_info_uint16, uint16_t) +#define VMSTATE_UINT32_V(_f, _s, _v) \ + VMSTATE_SINGLE(_f, _s, _v, vmstate_info_uint32, uint32_t) +#define VMSTATE_UINT64_V(_f, _s, _v) \ + VMSTATE_SINGLE(_f, _s, _v, vmstate_info_uint64, uint64_t) + +#define VMSTATE_BOOL(_f, _s) \ + VMSTATE_BOOL_V(_f, _s, 0) + +#define VMSTATE_INT8(_f, _s) \ + VMSTATE_INT8_V(_f, _s, 0) +#define VMSTATE_INT16(_f, _s) \ + VMSTATE_INT16_V(_f, _s, 0) +#define VMSTATE_INT32(_f, _s) \ + VMSTATE_INT32_V(_f, _s, 0) +#define VMSTATE_INT64(_f, _s) \ + VMSTATE_INT64_V(_f, _s, 0) + +#define VMSTATE_UINT8(_f, _s) \ + VMSTATE_UINT8_V(_f, _s, 0) +#define VMSTATE_UINT16(_f, _s) \ + VMSTATE_UINT16_V(_f, _s, 0) +#define VMSTATE_UINT32(_f, _s) \ + VMSTATE_UINT32_V(_f, _s, 0) +#define VMSTATE_UINT64(_f, _s) \ + VMSTATE_UINT64_V(_f, _s, 0) + +#define VMSTATE_UINT8_EQUAL(_f, _s) \ + VMSTATE_SINGLE(_f, _s, 0, vmstate_info_uint8_equal, uint8_t) + +#define VMSTATE_UINT16_EQUAL(_f, _s) \ + VMSTATE_SINGLE(_f, _s, 0, vmstate_info_uint16_equal, uint16_t) + +#define VMSTATE_UINT16_EQUAL_V(_f, _s, _v) \ + VMSTATE_SINGLE(_f, _s, _v, vmstate_info_uint16_equal, uint16_t) + +#define VMSTATE_INT32_EQUAL(_f, _s) \ + VMSTATE_SINGLE(_f, _s, 0, vmstate_info_int32_equal, int32_t) + +#define VMSTATE_INT32_LE(_f, _s) \ + VMSTATE_SINGLE(_f, _s, 0, vmstate_info_int32_le, int32_t) + +#define VMSTATE_UINT16_TEST(_f, _s, _t) \ + VMSTATE_SINGLE_TEST(_f, _s, _t, 0, vmstate_info_uint16, uint16_t) + +#define VMSTATE_UINT32_TEST(_f, _s, _t) \ + VMSTATE_SINGLE_TEST(_f, _s, _t, 0, vmstate_info_uint32, uint32_t) + +#define VMSTATE_TIMER_V(_f, _s, _v) \ + VMSTATE_POINTER(_f, _s, _v, vmstate_info_timer, QEMUTimer *) + +#define VMSTATE_TIMER(_f, _s) \ + VMSTATE_TIMER_V(_f, _s, 0) + +#define VMSTATE_TIMER_ARRAY(_f, _s, _n) \ + VMSTATE_ARRAY_OF_POINTER(_f, _s, _n, 0, vmstate_info_timer, QEMUTimer *) + +#define VMSTATE_PTIMER_V(_f, _s, _v) \ + VMSTATE_POINTER(_f, _s, _v, vmstate_info_ptimer, ptimer_state *) + +#define VMSTATE_PTIMER(_f, _s) \ + VMSTATE_PTIMER_V(_f, _s, 0) + +#define VMSTATE_BOOL_ARRAY_V(_f, _s, _n, _v) \ + VMSTATE_ARRAY(_f, _s, _n, _v, vmstate_info_bool, bool) + +#define VMSTATE_BOOL_ARRAY(_f, _s, _n) \ + VMSTATE_BOOL_ARRAY_V(_f, _s, _n, 0) + +#define VMSTATE_UINT16_ARRAY_V(_f, _s, _n, _v) \ + VMSTATE_ARRAY(_f, _s, _n, _v, vmstate_info_uint16, uint16_t) + +#define VMSTATE_UINT16_ARRAY(_f, _s, _n) \ + VMSTATE_UINT16_ARRAY_V(_f, _s, _n, 0) + +#define VMSTATE_UINT8_ARRAY_V(_f, _s, _n, _v) \ + VMSTATE_ARRAY(_f, _s, _n, _v, vmstate_info_uint8, uint8_t) + +#define VMSTATE_UINT8_ARRAY(_f, _s, _n) \ + VMSTATE_UINT8_ARRAY_V(_f, _s, _n, 0) + +#define VMSTATE_UINT32_ARRAY_V(_f, _s, _n, _v) \ + VMSTATE_ARRAY(_f, _s, _n, _v, vmstate_info_uint32, uint32_t) + +#define VMSTATE_UINT32_ARRAY(_f, _s, _n) \ + VMSTATE_UINT32_ARRAY_V(_f, _s, _n, 0) + +#define VMSTATE_UINT64_ARRAY_V(_f, _s, _n, _v) \ + VMSTATE_ARRAY(_f, _s, _n, _v, vmstate_info_uint64, uint64_t) + +#define VMSTATE_UINT64_ARRAY(_f, _s, _n) \ + VMSTATE_UINT64_ARRAY_V(_f, _s, _n, 0) + +#define VMSTATE_INT16_ARRAY_V(_f, _s, _n, _v) \ + VMSTATE_ARRAY(_f, _s, _n, _v, vmstate_info_int16, int16_t) + +#define VMSTATE_INT16_ARRAY(_f, _s, _n) \ + VMSTATE_INT16_ARRAY_V(_f, _s, _n, 0) + +#define VMSTATE_INT32_ARRAY_V(_f, _s, _n, _v) \ + VMSTATE_ARRAY(_f, _s, _n, _v, vmstate_info_int32, int32_t) + +#define VMSTATE_INT32_ARRAY(_f, _s, _n) \ + VMSTATE_INT32_ARRAY_V(_f, _s, _n, 0) + +#define VMSTATE_UINT32_SUB_ARRAY(_f, _s, _start, _num) \ + VMSTATE_SUB_ARRAY(_f, _s, _start, _num, 0, vmstate_info_uint32, uint32_t) + +#define VMSTATE_UINT32_ARRAY(_f, _s, _n) \ + VMSTATE_UINT32_ARRAY_V(_f, _s, _n, 0) + +#define VMSTATE_BUFFER_V(_f, _s, _v) \ + VMSTATE_STATIC_BUFFER(_f, _s, _v, NULL, 0, sizeof(typeof_field(_s, _f))) + +#define VMSTATE_BUFFER(_f, _s) \ + VMSTATE_BUFFER_V(_f, _s, 0) + +#define VMSTATE_PARTIAL_BUFFER(_f, _s, _size) \ + VMSTATE_STATIC_BUFFER(_f, _s, 0, NULL, 0, _size) + +#define VMSTATE_BUFFER_START_MIDDLE(_f, _s, _start) \ + VMSTATE_STATIC_BUFFER(_f, _s, 0, NULL, _start, sizeof(typeof_field(_s, _f))) + +#define VMSTATE_PARTIAL_VBUFFER(_f, _s, _size) \ + VMSTATE_VBUFFER(_f, _s, 0, NULL, 0, _size) + +#define VMSTATE_PARTIAL_VBUFFER_UINT32(_f, _s, _size) \ + VMSTATE_VBUFFER_UINT32(_f, _s, 0, NULL, 0, _size) + +#define VMSTATE_SUB_VBUFFER(_f, _s, _start, _size) \ + VMSTATE_VBUFFER(_f, _s, 0, NULL, _start, _size) + +#define VMSTATE_BUFFER_TEST(_f, _s, _test) \ + VMSTATE_STATIC_BUFFER(_f, _s, 0, _test, 0, sizeof(typeof_field(_s, _f))) + +#define VMSTATE_BUFFER_UNSAFE(_field, _state, _version, _size) \ + VMSTATE_BUFFER_UNSAFE_INFO(_field, _state, _version, vmstate_info_buffer, _size) + +#define VMSTATE_UNUSED_V(_v, _size) \ + VMSTATE_UNUSED_BUFFER(NULL, _v, _size) + +#define VMSTATE_UNUSED(_size) \ + VMSTATE_UNUSED_V(0, _size) + +#define VMSTATE_UNUSED_TEST(_test, _size) \ + VMSTATE_UNUSED_BUFFER(_test, 0, _size) + +#ifdef NEED_CPU_H +#if TARGET_LONG_BITS == 64 +#define VMSTATE_UINTTL_V(_f, _s, _v) \ + VMSTATE_UINT64_V(_f, _s, _v) +#define VMSTATE_UINTTL_ARRAY_V(_f, _s, _n, _v) \ + VMSTATE_UINT64_ARRAY_V(_f, _s, _n, _v) +#else +#define VMSTATE_UINTTL_V(_f, _s, _v) \ + VMSTATE_UINT32_V(_f, _s, _v) +#define VMSTATE_UINTTL_ARRAY_V(_f, _s, _n, _v) \ + VMSTATE_UINT32_ARRAY_V(_f, _s, _n, _v) +#endif +#define VMSTATE_UINTTL(_f, _s) \ + VMSTATE_UINTTL_V(_f, _s, 0) +#define VMSTATE_UINTTL_ARRAY(_f, _s, _n) \ + VMSTATE_UINTTL_ARRAY_V(_f, _s, _n, 0) + +#endif + +#define VMSTATE_END_OF_LIST() \ + {} + +int vmstate_load_state(QEMUFile *f, const VMStateDescription *vmsd, + void *opaque, int version_id); +void vmstate_save_state(QEMUFile *f, const VMStateDescription *vmsd, + void *opaque); +int vmstate_register(DeviceState *dev, int instance_id, + const VMStateDescription *vmsd, void *base); +int vmstate_register_with_alias_id(DeviceState *dev, int instance_id, + const VMStateDescription *vmsd, + void *base, int alias_id, + int required_for_version); +void vmstate_unregister(DeviceState *dev, const VMStateDescription *vmsd, + void *opaque); +#endif diff --git a/hw/i2c.c b/hw/i2c.c new file mode 100644 index 0000000..f80d12d --- /dev/null +++ b/hw/i2c.c @@ -0,0 +1,196 @@ +/* + * QEMU I2C bus interface. + * + * Copyright (c) 2007 CodeSourcery. + * Written by Paul Brook + * + * This code is licenced under the LGPL. + */ + +#include "i2c.h" + +struct i2c_bus +{ + BusState qbus; + i2c_slave *current_dev; + i2c_slave *dev; + uint8_t saved_address; +}; + +static struct BusInfo i2c_bus_info = { + .name = "I2C", + .size = sizeof(i2c_bus), + .props = (Property[]) { + DEFINE_PROP_UINT8("address", struct i2c_slave, address, 0), + DEFINE_PROP_END_OF_LIST(), + } +}; + +static void i2c_bus_pre_save(void *opaque) +{ + i2c_bus *bus = opaque; + + bus->saved_address = bus->current_dev ? bus->current_dev->address : -1; +} + +static int i2c_bus_post_load(void *opaque, int version_id) +{ + i2c_bus *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, + .minimum_version_id_old = 1, + .pre_save = i2c_bus_pre_save, + .post_load = i2c_bus_post_load, + .fields = (VMStateField []) { + VMSTATE_UINT8(saved_address, i2c_bus), + VMSTATE_END_OF_LIST() + } +}; + +/* Create a new I2C bus. */ +i2c_bus *i2c_init_bus(DeviceState *parent, const char *name) +{ + i2c_bus *bus; + + bus = FROM_QBUS(i2c_bus, qbus_create(&i2c_bus_info, parent, name)); + vmstate_register(NULL, -1, &vmstate_i2c_bus, bus); + return bus; +} + +void i2c_set_slave_address(i2c_slave *dev, uint8_t address) +{ + dev->address = address; +} + +/* Return nonzero if bus is busy. */ +int i2c_bus_busy(i2c_bus *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(i2c_bus *bus, uint8_t address, int recv) +{ + DeviceState *qdev; + i2c_slave *slave = NULL; + + QLIST_FOREACH(qdev, &bus->qbus.children, sibling) { + i2c_slave *candidate = I2C_SLAVE_FROM_QDEV(qdev); + if (candidate->address == address) { + slave = candidate; + break; + } + } + + if (!slave) + return 1; + + /* If the bus is already busy, assume this is a repeated + start condition. */ + bus->current_dev = slave; + slave->info->event(slave, recv ? I2C_START_RECV : I2C_START_SEND); + return 0; +} + +void i2c_end_transfer(i2c_bus *bus) +{ + i2c_slave *dev = bus->current_dev; + + if (!dev) + return; + + dev->info->event(dev, I2C_FINISH); + + bus->current_dev = NULL; +} + +int i2c_send(i2c_bus *bus, uint8_t data) +{ + i2c_slave *dev = bus->current_dev; + + if (!dev) + return -1; + + return dev->info->send(dev, data); +} + +int i2c_recv(i2c_bus *bus) +{ + i2c_slave *dev = bus->current_dev; + + if (!dev) + return -1; + + return dev->info->recv(dev); +} + +void i2c_nack(i2c_bus *bus) +{ + i2c_slave *dev = bus->current_dev; + + if (!dev) + return; + + dev->info->event(dev, I2C_NACK); +} + +static int i2c_slave_post_load(void *opaque, int version_id) +{ + i2c_slave *dev = opaque; + i2c_bus *bus; + bus = FROM_QBUS(i2c_bus, qdev_get_parent_bus(&dev->qdev)); + if (bus->saved_address == dev->address) { + bus->current_dev = dev; + } + return 0; +} + +const VMStateDescription vmstate_i2c_slave = { + .name = "i2c_slave", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .post_load = i2c_slave_post_load, + .fields = (VMStateField []) { + VMSTATE_UINT8(address, i2c_slave), + VMSTATE_END_OF_LIST() + } +}; + +static int i2c_slave_qdev_init(DeviceState *dev, DeviceInfo *base) +{ + I2CSlaveInfo *info = container_of(base, I2CSlaveInfo, qdev); + i2c_slave *s = I2C_SLAVE_FROM_QDEV(dev); + + s->info = info; + + return info->init(s); +} + +void i2c_register_slave(I2CSlaveInfo *info) +{ + assert(info->qdev.size >= sizeof(i2c_slave)); + info->qdev.init = i2c_slave_qdev_init; + info->qdev.bus_info = &i2c_bus_info; + qdev_register(&info->qdev); +} + +DeviceState *i2c_create_slave(i2c_bus *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; +} diff --git a/hw/i2c.h b/hw/i2c.h new file mode 100644 index 0000000..83fd917 --- /dev/null +++ b/hw/i2c.h @@ -0,0 +1,82 @@ +#ifndef QEMU_I2C_H +#define QEMU_I2C_H + +#include "qdev.h" + +/* The QEMU I2C implementation only supports simple transfers that complete + immediately. It does not support slave devices that need to be able to + defer their response (eg. CPU slave interfaces where the data is supplied + by the device driver in response to an interrupt). */ + +enum i2c_event { + I2C_START_RECV, + I2C_START_SEND, + I2C_FINISH, + I2C_NACK /* Masker NACKed a receive byte. */ +}; + +/* Master to slave. */ +typedef int (*i2c_send_cb)(i2c_slave *s, uint8_t data); +/* Slave to master. */ +typedef int (*i2c_recv_cb)(i2c_slave *s); +/* Notify the slave of a bus state change. */ +typedef void (*i2c_event_cb)(i2c_slave *s, enum i2c_event event); + +typedef int (*i2c_slave_initfn)(i2c_slave *dev); + +typedef struct { + DeviceInfo qdev; + + /* Callbacks provided by the device. */ + i2c_slave_initfn init; + i2c_event_cb event; + i2c_recv_cb recv; + i2c_send_cb send; +} I2CSlaveInfo; + +struct i2c_slave +{ + DeviceState qdev; + I2CSlaveInfo *info; + + /* Remaining fields for internal use by the I2C code. */ + uint8_t address; +}; + +i2c_bus *i2c_init_bus(DeviceState *parent, const char *name); +void i2c_set_slave_address(i2c_slave *dev, uint8_t address); +int i2c_bus_busy(i2c_bus *bus); +int i2c_start_transfer(i2c_bus *bus, uint8_t address, int recv); +void i2c_end_transfer(i2c_bus *bus); +void i2c_nack(i2c_bus *bus); +int i2c_send(i2c_bus *bus, uint8_t data); +int i2c_recv(i2c_bus *bus); + +#define I2C_SLAVE_FROM_QDEV(dev) DO_UPCAST(i2c_slave, qdev, dev) +#define FROM_I2C_SLAVE(type, dev) DO_UPCAST(type, i2c, dev) + +void i2c_register_slave(I2CSlaveInfo *type); + +DeviceState *i2c_create_slave(i2c_bus *bus, const char *name, uint8_t addr); + +/* max7310.c */ +void max7310_reset(i2c_slave *i2c); +qemu_irq *max7310_gpio_in_get(i2c_slave *i2c); +void max7310_gpio_out_set(i2c_slave *i2c, int line, qemu_irq handler); + +/* wm8750.c */ +void wm8750_data_req_set(DeviceState *dev, + void (*data_req)(void *, int, int), void *opaque); +void wm8750_dac_dat(void *opaque, uint32_t sample); +uint32_t wm8750_adc_dat(void *opaque); +void *wm8750_dac_buffer(void *opaque, int samples); +void wm8750_dac_commit(void *opaque); +void wm8750_set_bclk_in(void *opaque, int new_hz); + +/* tmp105.c */ +void tmp105_set(i2c_slave *i2c, int temp); + +/* lm832x.c */ +void lm832x_key_event(i2c_slave *i2c, int key, int state); + +#endif diff --git a/hw/i8254.c b/hw/i8254.c new file mode 100644 index 0000000..06b225c --- /dev/null +++ b/hw/i8254.c @@ -0,0 +1,517 @@ +/* + * 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.h" +#include "pc.h" +#include "isa.h" +#include "qemu-timer.h" + +//#define DEBUG_PIT + +#define RW_STATE_LSB 1 +#define RW_STATE_MSB 2 +#define RW_STATE_WORD0 3 +#define RW_STATE_WORD1 4 + +typedef struct PITChannelState { + int count; /* can be 65536 */ + uint16_t latched_count; + uint8_t count_latched; + uint8_t status_latched; + uint8_t status; + uint8_t read_state; + uint8_t write_state; + uint8_t write_latch; + uint8_t rw_mode; + uint8_t mode; + uint8_t bcd; /* not supported */ + uint8_t gate; /* timer start */ + int64_t count_load_time; + /* irq handling */ + int64_t next_transition_time; + QEMUTimer *irq_timer; + qemu_irq irq; +} PITChannelState; + +struct PITState { + PITChannelState channels[3]; +}; + +static PITState pit_state; + +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_get_clock(vm_clock) - 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; +} + +/* get pit output bit */ +static int pit_get_out1(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; +} + +int pit_get_out(PITState *pit, int channel, int64_t current_time) +{ + PITChannelState *s = &pit->channels[channel]; + return pit_get_out1(s, current_time); +} + +/* return -1 if no transition will occur. */ +static 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; +} + +/* val must be 0 or 1 */ +void pit_set_gate(PITState *pit, int channel, int val) +{ + PITChannelState *s = &pit->channels[channel]; + + switch(s->mode) { + default: + case 0: + case 4: + /* XXX: just disable/enable counting */ + break; + case 1: + case 5: + if (s->gate < val) { + /* restart counting on rising edge */ + s->count_load_time = qemu_get_clock(vm_clock); + pit_irq_timer_update(s, s->count_load_time); + } + break; + case 2: + case 3: + if (s->gate < val) { + /* restart counting on rising edge */ + s->count_load_time = qemu_get_clock(vm_clock); + pit_irq_timer_update(s, s->count_load_time); + } + /* XXX: disable/enable counting */ + break; + } + s->gate = val; +} + +int pit_get_gate(PITState *pit, int channel) +{ + PITChannelState *s = &pit->channels[channel]; + return s->gate; +} + +int pit_get_initial_count(PITState *pit, int channel) +{ + PITChannelState *s = &pit->channels[channel]; + return s->count; +} + +int pit_get_mode(PITState *pit, int channel) +{ + PITChannelState *s = &pit->channels[channel]; + return s->mode; +} + +static inline void pit_load_count(PITChannelState *s, int val) +{ + if (val == 0) + val = 0x10000; + s->count_load_time = qemu_get_clock(vm_clock); + 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, uint32_t addr, uint32_t val) +{ + PITState *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_out1(s, qemu_get_clock(vm_clock)) << 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 uint32_t pit_ioport_read(void *opaque, uint32_t addr) +{ + PITState *pit = opaque; + int ret, count; + PITChannelState *s; + + addr &= 3; + 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) + return; + expire_time = pit_get_next_transition_time(s, current_time); + irq_level = pit_get_out1(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) + qemu_mod_timer(s->irq_timer, expire_time); + else + qemu_del_timer(s->irq_timer); +} + +static void pit_irq_timer(void *opaque) +{ + PITChannelState *s = opaque; + + pit_irq_timer_update(s, s->next_transition_time); +} + +static const VMStateDescription vmstate_pit_channel = { + .name = "pit channel", + .version_id = 2, + .minimum_version_id = 2, + .minimum_version_id_old = 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) +{ + PITState *pit = opaque; + 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); + if (s->irq_timer) { + s->next_transition_time=qemu_get_be64(f); + qemu_get_timer(f, s->irq_timer); + } + } + return 0; +} + +static const VMStateDescription vmstate_pit = { + .name = "i8254", + .version_id = 2, + .minimum_version_id = 2, + .minimum_version_id_old = 1, + .load_state_old = pit_load_old, + .fields = (VMStateField []) { + VMSTATE_STRUCT_ARRAY(channels, PITState, 3, 2, vmstate_pit_channel, PITChannelState), + VMSTATE_TIMER(channels[0].irq_timer, PITState), + VMSTATE_END_OF_LIST() + } +}; + +static void pit_reset(void *opaque) +{ + PITState *pit = opaque; + PITChannelState *s; + int i; + + for(i = 0;i < 3; i++) { + s = &pit->channels[i]; + s->mode = 3; + s->gate = (i != 2); + pit_load_count(s, 0); + } +} + +/* When HPET is operating in legacy mode, i8254 timer0 is disabled */ +void hpet_pit_disable(void) { + PITChannelState *s; + s = &pit_state.channels[0]; + if (s->irq_timer) + qemu_del_timer(s->irq_timer); +} + +/* When HPET is reset or leaving legacy mode, it must reenable i8254 + * timer 0 + */ + +void hpet_pit_enable(void) +{ + PITState *pit = &pit_state; + PITChannelState *s; + s = &pit->channels[0]; + s->mode = 3; + s->gate = 1; + pit_load_count(s, 0); +} + +PITState *pit_init(int base, qemu_irq irq) +{ + PITState *pit = &pit_state; + PITChannelState *s; + + s = &pit->channels[0]; + /* the timer 0 is connected to an IRQ */ + s->irq_timer = qemu_new_timer(vm_clock, pit_irq_timer, s); + s->irq = irq; + + vmstate_register(NULL, base, &vmstate_pit, pit); + qemu_register_reset(pit_reset, pit); + register_ioport_write(base, 4, 1, pit_ioport_write, pit); + register_ioport_read(base, 3, 1, pit_ioport_read, pit); + + return pit; +} diff --git a/hw/i8259.c b/hw/i8259.c new file mode 100644 index 0000000..a8dbee6 --- /dev/null +++ b/hw/i8259.c @@ -0,0 +1,539 @@ +/* + * 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.h" +#include "pc.h" +#include "isa.h" +#include "monitor.h" +#include "qemu-timer.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 + +typedef struct PicState { + uint8_t last_irr; /* edge detection */ + uint8_t irr; /* interrupt request register */ + uint8_t imr; /* interrupt mask register */ + uint8_t isr; /* interrupt service register */ + uint8_t priority_add; /* highest irq priority */ + uint8_t irq_base; + uint8_t read_reg_select; + uint8_t poll; + uint8_t special_mask; + uint8_t init_state; + uint8_t auto_eoi; + uint8_t rotate_on_auto_eoi; + uint8_t special_fully_nested_mode; + uint8_t init4; /* true if 4 byte init */ + uint8_t single_mode; /* true if slave pic is not initialized */ + uint8_t elcr; /* PIIX edge/trigger selection*/ + uint8_t elcr_mask; + PicState2 *pics_state; +} PicState; + +struct PicState2 { + /* 0 is master pic, 1 is slave pic */ + /* XXX: better separation between the two pics */ + PicState pics[2]; + qemu_irq parent_irq; + void *irq_request_opaque; +}; + +#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 +PicState2 *isa_pic; + +/* set irq level. If an edge is detected, then the IRR is set to 1 */ +static inline void pic_set_irq1(PicState *s, int irq, int level) +{ + int mask; + mask = 1 << irq; + 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; + } + } +} + +/* return the highest priority found in mask (highest = smallest + number). Return 8 if no irq */ +static inline int get_priority(PicState *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(PicState *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 == &s->pics_state->pics[0]) + 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; + } +} + +/* raise irq to CPU if necessary. must be called every time the active + irq may change */ +/* XXX: should not export it, but it is needed for an APIC kludge */ +void pic_update_irq(PicState2 *s) +{ + int irq2, irq; + + /* first look at slave pic */ + irq2 = pic_get_irq(&s->pics[1]); + if (irq2 >= 0) { + /* if irq request by slave pic, signal master PIC */ + pic_set_irq1(&s->pics[0], 2, 1); + pic_set_irq1(&s->pics[0], 2, 0); + } + /* look at requested irq */ + irq = pic_get_irq(&s->pics[0]); + if (irq >= 0) { +#if defined(DEBUG_PIC) + { + int i; + for(i = 0; i < 2; i++) { + printf("pic%d: imr=%x irr=%x padd=%d\n", + i, s->pics[i].imr, s->pics[i].irr, + s->pics[i].priority_add); + + } + } + printf("pic: cpu_interrupt\n"); +#endif + qemu_irq_raise(s->parent_irq); + } + +/* all targets should do this rather than acking the IRQ in the cpu */ +#if defined(TARGET_MIPS) || defined(TARGET_PPC) || defined(TARGET_ALPHA) + else { + qemu_irq_lower(s->parent_irq); + } +#endif +} + +#ifdef DEBUG_IRQ_LATENCY +int64_t irq_time[16]; +#endif + +static void i8259_set_irq(void *opaque, int irq, int level) +{ + PicState2 *s = opaque; + +#if defined(DEBUG_PIC) || defined(DEBUG_IRQ_COUNT) + if (level != irq_level[irq]) { + DPRINTF("i8259_set_irq: irq=%d level=%d\n", irq, level); + irq_level[irq] = level; +#ifdef DEBUG_IRQ_COUNT + if (level == 1) + irq_count[irq]++; +#endif + } +#endif +#ifdef DEBUG_IRQ_LATENCY + if (level) { + irq_time[irq] = qemu_get_clock(vm_clock); + } +#endif + pic_set_irq1(&s->pics[irq >> 3], irq & 7, level); + pic_update_irq(s); +} + +/* acknowledge interrupt 'irq' */ +static inline void pic_intack(PicState *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); +} + +int pic_read_irq(PicState2 *s) +{ + int irq, irq2, intno; + + irq = pic_get_irq(&s->pics[0]); + if (irq >= 0) { + pic_intack(&s->pics[0], irq); + if (irq == 2) { + irq2 = pic_get_irq(&s->pics[1]); + if (irq2 >= 0) { + pic_intack(&s->pics[1], irq2); + } else { + /* spurious IRQ on slave controller */ + irq2 = 7; + } + intno = s->pics[1].irq_base + irq2; +#if defined(DEBUG_PIC) || defined(DEBUG_IRQ_LATENCY) + irq = irq2 + 8; +#endif + } else { + intno = s->pics[0].irq_base + irq; + } + } else { + /* spurious IRQ on host controller */ + irq = 7; + intno = s->pics[0].irq_base + irq; + } + pic_update_irq(s); + +#ifdef DEBUG_IRQ_LATENCY + printf("IRQ%d latency=%0.3fus\n", + irq, + (double)(qemu_get_clock(vm_clock) - + irq_time[irq]) * 1000000.0 / get_ticks_per_sec()); +#endif + DPRINTF("pic_interrupt: irq=%d\n", irq); + return intno; +} + +static void pic_reset(void *opaque) +{ + PicState *s = opaque; + + s->last_irr = 0; + s->irr = 0; + 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_ioport_write(void *opaque, uint32_t addr, uint32_t val) +{ + PicState *s = opaque; + int priority, cmd, irq; + + DPRINTF("write: addr=0x%02x val=0x%02x\n", addr, val); + addr &= 1; + if (addr == 0) { + if (val & 0x10) { + /* init */ + pic_reset(s); + /* deassert a pending interrupt */ + qemu_irq_lower(s->pics_state->parent_irq); + s->init_state = 1; + s->init4 = val & 1; + s->single_mode = val & 2; + if (val & 0x08) + hw_error("level sensitive irq not supported"); + } 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->pics_state); + } + break; + case 3: + irq = val & 7; + s->isr &= ~(1 << irq); + pic_update_irq(s->pics_state); + break; + case 6: + s->priority_add = (val + 1) & 7; + pic_update_irq(s->pics_state); + break; + case 7: + irq = val & 7; + s->isr &= ~(1 << irq); + s->priority_add = (irq + 1) & 7; + pic_update_irq(s->pics_state); + break; + default: + /* no operation */ + break; + } + } + } else { + switch(s->init_state) { + case 0: + /* normal mode */ + s->imr = val; + pic_update_irq(s->pics_state); + 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 uint32_t pic_poll_read (PicState *s, uint32_t addr1) +{ + int ret; + + ret = pic_get_irq(s); + if (ret >= 0) { + if (addr1 >> 7) { + s->pics_state->pics[0].isr &= ~(1 << 2); + s->pics_state->pics[0].irr &= ~(1 << 2); + } + s->irr &= ~(1 << ret); + s->isr &= ~(1 << ret); + if (addr1 >> 7 || ret != 2) + pic_update_irq(s->pics_state); + } else { + ret = 0x07; + pic_update_irq(s->pics_state); + } + + return ret; +} + +static uint32_t pic_ioport_read(void *opaque, uint32_t addr1) +{ + PicState *s = opaque; + unsigned int addr; + int ret; + + addr = addr1; + addr &= 1; + if (s->poll) { + ret = pic_poll_read(s, addr1); + 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%02x val=0x%02x\n", addr1, ret); + return ret; +} + +/* memory mapped interrupt status */ +/* XXX: may be the same than pic_read_irq() */ +uint32_t pic_intack_read(PicState2 *s) +{ + int ret; + + ret = pic_poll_read(&s->pics[0], 0x00); + if (ret == 2) + ret = pic_poll_read(&s->pics[1], 0x80) + 8; + /* Prepare for ISR read */ + s->pics[0].read_reg_select = 1; + + return ret; +} + +static void elcr_ioport_write(void *opaque, uint32_t addr, uint32_t val) +{ + PicState *s = opaque; + s->elcr = val & s->elcr_mask; +} + +static uint32_t elcr_ioport_read(void *opaque, uint32_t addr1) +{ + PicState *s = opaque; + return s->elcr; +} + +static const VMStateDescription vmstate_pic = { + .name = "i8259", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField []) { + VMSTATE_UINT8(last_irr, PicState), + VMSTATE_UINT8(irr, PicState), + VMSTATE_UINT8(imr, PicState), + VMSTATE_UINT8(isr, PicState), + VMSTATE_UINT8(priority_add, PicState), + VMSTATE_UINT8(irq_base, PicState), + VMSTATE_UINT8(read_reg_select, PicState), + VMSTATE_UINT8(poll, PicState), + VMSTATE_UINT8(special_mask, PicState), + VMSTATE_UINT8(init_state, PicState), + VMSTATE_UINT8(auto_eoi, PicState), + VMSTATE_UINT8(rotate_on_auto_eoi, PicState), + VMSTATE_UINT8(special_fully_nested_mode, PicState), + VMSTATE_UINT8(init4, PicState), + VMSTATE_UINT8(single_mode, PicState), + VMSTATE_UINT8(elcr, PicState), + VMSTATE_END_OF_LIST() + } +}; + +/* XXX: add generic master/slave system */ +static void pic_init1(int io_addr, int elcr_addr, PicState *s) +{ + register_ioport_write(io_addr, 2, 1, pic_ioport_write, s); + register_ioport_read(io_addr, 2, 1, pic_ioport_read, s); + if (elcr_addr >= 0) { + register_ioport_write(elcr_addr, 1, 1, elcr_ioport_write, s); + register_ioport_read(elcr_addr, 1, 1, elcr_ioport_read, s); + } + vmstate_register(NULL, io_addr, &vmstate_pic, s); + qemu_register_reset(pic_reset, s); +} + +void pic_info(Monitor *mon) +{ + int i; + PicState *s; + + if (!isa_pic) + return; + + for(i=0;i<2;i++) { + s = &isa_pic->pics[i]; + 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 irq_info(Monitor *mon) +{ +#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(qemu_irq parent_irq) +{ + PicState2 *s; + + s = qemu_mallocz(sizeof(PicState2)); + pic_init1(0x20, 0x4d0, &s->pics[0]); + pic_init1(0xa0, 0x4d1, &s->pics[1]); + s->pics[0].elcr_mask = 0xf8; + s->pics[1].elcr_mask = 0xde; + s->parent_irq = parent_irq; + s->pics[0].pics_state = s; + s->pics[1].pics_state = s; + isa_pic = s; + return qemu_allocate_irqs(i8259_set_irq, s, 16); +} diff --git a/hw/ide.h b/hw/ide.h new file mode 100644 index 0000000..73fb550 --- /dev/null +++ b/hw/ide.h @@ -0,0 +1,31 @@ +#ifndef HW_IDE_H +#define HW_IDE_H + +#include "isa.h" +#include "pci.h" + +#define MAX_IDE_DEVS 2 + +/* ide-isa.c */ +ISADevice *isa_ide_init(int iobase, int iobase2, int isairq, + DriveInfo *hd0, DriveInfo *hd1); + +/* ide-pci.c */ +void pci_cmd646_ide_init(PCIBus *bus, DriveInfo **hd_table, + int secondary_ide_enabled); +PCIDevice *pci_piix3_ide_init(PCIBus *bus, DriveInfo **hd_table, int devfn); +PCIDevice *pci_piix4_ide_init(PCIBus *bus, DriveInfo **hd_table, int devfn); +void vt82c686b_ide_init(PCIBus *bus, DriveInfo **hd_table, int devfn); + +/* ide-macio.c */ +int pmac_ide_init (DriveInfo **hd_table, qemu_irq irq, + void *dbdma, int channel, qemu_irq dma_irq); + +/* ide-mmio.c */ +void mmio_ide_init (target_phys_addr_t membase, target_phys_addr_t membase2, + qemu_irq irq, int shift, + DriveInfo *hd0, DriveInfo *hd1); + +void ide_get_bs(BlockDriverState *bs[], BusState *qbus); + +#endif /* HW_IDE_H */ diff --git a/hw/ide/ahci.c b/hw/ide/ahci.c new file mode 100644 index 0000000..98bdf70 --- /dev/null +++ b/hw/ide/ahci.c @@ -0,0 +1,1152 @@ +/* + * QEMU AHCI Emulation + * + * Copyright (c) 2010 qiaochong@loongson.cn + * Copyright (c) 2010 Roland Elek + * Copyright (c) 2010 Sebastian Herbszt + * Copyright (c) 2010 Alexander Graf + * + * 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 . + * + */ + +#include +#include +#include +#include + +#include "monitor.h" +#include "dma.h" +#include "cpu-common.h" +#include "internal.h" +#include +#include + +/* #define DEBUG_AHCI */ + +#ifdef DEBUG_AHCI +#define DPRINTF(port, fmt, ...) \ +do { fprintf(stderr, "ahci: %s: [%d] ", __FUNCTION__, port); \ + fprintf(stderr, fmt, ## __VA_ARGS__); } while (0) +#else +#define DPRINTF(port, fmt, ...) do {} while(0) +#endif + +static void check_cmd(AHCIState *s, int port); +static int handle_cmd(AHCIState *s,int port,int 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 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 = ((uint16_t)s->dev[port].port.ifs[0].error << 8) | + s->dev[port].port.ifs[0].status; + break; + case PORT_SIG: + val = pr->sig; + break; + case PORT_SCR_STAT: + if (s->dev[port].port.ifs[0].bs) { + 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: + pr->scr_act &= ~s->dev[port].finished; + s->dev[port].finished = 0; + 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) +{ + struct AHCIPCIState *d = container_of(s, AHCIPCIState, ahci); + + DPRINTF(0, "raise irq\n"); + + if (msi_enabled(&d->card)) { + msi_notify(&d->card, 0); + } else { + qemu_irq_raise(s->irq); + } +} + +static void ahci_irq_lower(AHCIState *s, AHCIDevice *dev) +{ + struct AHCIPCIState *d = container_of(s, AHCIPCIState, ahci); + + DPRINTF(0, "lower irq\n"); + + if (!msi_enabled(&d->card)) { + qemu_irq_lower(s->irq); + } +} + +static void ahci_check_irq(AHCIState *s) +{ + int i; + + DPRINTF(-1, "check irq %#x\n", s->control_regs.irqstatus); + + 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(uint8_t **ptr, uint64_t addr, uint32_t wanted) +{ + target_phys_addr_t len = wanted; + + if (*ptr) { + cpu_physical_memory_unmap(*ptr, len, 1, len); + } + + *ptr = cpu_physical_memory_map(addr, &len, 1); + if (len < wanted) { + cpu_physical_memory_unmap(*ptr, len, 1, len); + *ptr = NULL; + } +} + +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; + map_page(&s->dev[port].lst, + ((uint64_t)pr->lst_addr_hi << 32) | pr->lst_addr, 1024); + s->dev[port].cur_cmd = NULL; + break; + case PORT_LST_ADDR_HI: + pr->lst_addr_hi = val; + map_page(&s->dev[port].lst, + ((uint64_t)pr->lst_addr_hi << 32) | pr->lst_addr, 1024); + s->dev[port].cur_cmd = NULL; + break; + case PORT_FIS_ADDR: + pr->fis_addr = val; + map_page(&s->dev[port].res_fis, + ((uint64_t)pr->fis_addr_hi << 32) | pr->fis_addr, 256); + break; + case PORT_FIS_ADDR_HI: + pr->fis_addr_hi = val; + map_page(&s->dev[port].res_fis, + ((uint64_t)pr->fis_addr_hi << 32) | pr->fis_addr, 256); + break; + case PORT_IRQ_STAT: + pr->irq_stat &= ~val; + break; + case PORT_IRQ_MASK: + pr->irq_mask = val & 0xfdc000ff; + ahci_check_irq(s); + break; + case PORT_CMD: + pr->cmd = val & ~(PORT_CMD_LIST_ON | PORT_CMD_FIS_ON); + + if (pr->cmd & PORT_CMD_START) { + pr->cmd |= PORT_CMD_LIST_ON; + } + + if (pr->cmd & PORT_CMD_FIS_RX) { + pr->cmd |= PORT_CMD_FIS_ON; + } + + /* 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 = 1; + } + + check_cmd(s, port); + break; + case PORT_TFDATA: + s->dev[port].port.ifs[0].error = (val >> 8) & 0xff; + s->dev[port].port.ifs[0].status = val & 0xff; + break; + case PORT_SIG: + pr->sig = val; + break; + case PORT_SCR_STAT: + pr->scr_stat = val; + 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 uint32_t ahci_mem_readl(void *ptr, target_phys_addr_t addr) +{ + AHCIState *s = ptr; + uint32_t val = 0; + + addr = addr & 0xfff; + 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; +} + + + +static void ahci_mem_writel(void *ptr, target_phys_addr_t addr, uint32_t val) +{ + AHCIState *s = ptr; + addr = addr & 0xfff; + + /* 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%08X\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(container_of(s, AHCIPCIState, ahci)); + } 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 CPUReadMemoryFunc * const ahci_readfn[3]={ + ahci_mem_readl, + ahci_mem_readl, + ahci_mem_readl +}; + +static CPUWriteMemoryFunc * const ahci_writefn[3]={ + ahci_mem_writel, + ahci_mem_writel, + ahci_mem_writel +}; + +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 uint32_t read_from_sglist(uint8_t *buffer, uint32_t len, + QEMUSGList *sglist) +{ + uint32_t i = 0; + uint32_t total = 0, once; + ScatterGatherEntry *cur_prd; + uint32_t sgcount; + + cur_prd = sglist->sg; + sgcount = sglist->nsg; + for (i = 0; len && sgcount; i++) { + once = MIN(cur_prd->len, len); + cpu_physical_memory_read(cur_prd->base, buffer, once); + cur_prd++; + sgcount--; + len -= once; + buffer += once; + total += once; + } + + return total; +} + +static uint32_t write_to_sglist(uint8_t *buffer, uint32_t len, + QEMUSGList *sglist) +{ + uint32_t i = 0; + uint32_t total = 0, once; + ScatterGatherEntry *cur_prd; + uint32_t sgcount; + + DPRINTF(-1, "total: 0x%x bytes\n", len); + + cur_prd = sglist->sg; + sgcount = sglist->nsg; + for (i = 0; len && sgcount; i++) { + once = MIN(cur_prd->len, len); + DPRINTF(-1, "write 0x%x bytes to 0x%lx\n", once, (long)cur_prd->base); + cpu_physical_memory_write(cur_prd->base, buffer, once); + cur_prd++; + sgcount--; + len -= once; + buffer += once; + total += once; + } + + return total; +} + +static void check_cmd(AHCIState *s, int port) +{ + AHCIPortRegs *pr = &s->dev[port].port_regs; + int slot; + + if ((pr->cmd & PORT_CMD_START) && pr->cmd_issue) { + for (slot = 0; (slot < 32) && pr->cmd_issue; slot++) { + if ((pr->cmd_issue & (1 << slot)) && + !handle_cmd(s, port, slot)) { + pr->cmd_issue &= ~(1 << 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[0x20]; + 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->irq_stat = 0; + pr->irq_mask = 0; + pr->scr_stat = 0; + pr->scr_ctl = 0; + pr->scr_err = 0; + pr->scr_act = 0; + d->busy_slot = -1; + d->init_d2h_sent = 0; + + ide_state = &s->dev[port].port.ifs[0]; + if (!ide_state->bs) { + return; + } + + /* reset ncq queue */ + for (i = 0; i < AHCI_MAX_CMDS; i++) { + NCQTransferState *ncq_tfs = &s->dev[port].ncq_tfs[i]; + if (!ncq_tfs->used) { + continue; + } + + if (ncq_tfs->aiocb) { + bdrv_aio_cancel(ncq_tfs->aiocb); + ncq_tfs->aiocb = NULL; + } + + qemu_sglist_destroy(&ncq_tfs->sglist); + ncq_tfs->used = 0; + } + + s->dev[port].port_state = STATE_RUN; + if (!ide_state->bs) { + s->dev[port].port_regs.sig = 0; + ide_state->status = SEEK_STAT | WRERR_STAT; + } else if (ide_state->drive_kind == IDE_CD) { + s->dev[port].port_regs.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 { + s->dev[port].port_regs.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) +{ +#ifdef 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 void ahci_write_fis_sdb(AHCIState *s, int port, uint32_t finished) +{ + AHCIPortRegs *pr = &s->dev[port].port_regs; + IDEState *ide_state; + uint8_t *sdb_fis; + + if (!s->dev[port].res_fis || + !(pr->cmd & PORT_CMD_FIS_RX)) { + return; + } + + sdb_fis = &s->dev[port].res_fis[RES_FIS_SDBFIS]; + ide_state = &s->dev[port].port.ifs[0]; + + /* clear memory */ + *(uint32_t*)sdb_fis = 0; + + /* write values */ + sdb_fis[0] = ide_state->error; + sdb_fis[2] = ide_state->status & 0x77; + s->dev[port].finished |= finished; + *(uint32_t*)(sdb_fis + 4) = cpu_to_le32(s->dev[port].finished); + + ahci_trigger_irq(s, &s->dev[port], PORT_IRQ_STAT_SDBS); +} + +static void ahci_write_fis_d2h(AHCIDevice *ad, uint8_t *cmd_fis) +{ + AHCIPortRegs *pr = &ad->port_regs; + uint8_t *d2h_fis; + int i; + target_phys_addr_t cmd_len = 0x80; + int cmd_mapped = 0; + + if (!ad->res_fis || !(pr->cmd & PORT_CMD_FIS_RX)) { + return; + } + + if (!cmd_fis) { + /* map cmd_fis */ + uint64_t tbl_addr = le64_to_cpu(ad->cur_cmd->tbl_addr); + cmd_fis = cpu_physical_memory_map(tbl_addr, &cmd_len, 0); + cmd_mapped = 1; + } + + d2h_fis = &ad->res_fis[RES_FIS_RFIS]; + + d2h_fis[0] = 0x34; + d2h_fis[1] = (ad->hba->control_regs.irqstatus ? (1 << 6) : 0); + d2h_fis[2] = ad->port.ifs[0].status; + d2h_fis[3] = ad->port.ifs[0].error; + + d2h_fis[4] = cmd_fis[4]; + d2h_fis[5] = cmd_fis[5]; + d2h_fis[6] = cmd_fis[6]; + d2h_fis[7] = cmd_fis[7]; + d2h_fis[8] = cmd_fis[8]; + d2h_fis[9] = cmd_fis[9]; + d2h_fis[10] = cmd_fis[10]; + d2h_fis[11] = cmd_fis[11]; + d2h_fis[12] = cmd_fis[12]; + d2h_fis[13] = cmd_fis[13]; + for (i = 14; i < 0x20; i++) { + d2h_fis[i] = 0; + } + + if (d2h_fis[2] & ERR_STAT) { + ahci_trigger_irq(ad->hba, ad, PORT_IRQ_STAT_TFES); + } + + ahci_trigger_irq(ad->hba, ad, PORT_IRQ_D2H_REG_FIS); + + if (cmd_mapped) { + cpu_physical_memory_unmap(cmd_fis, cmd_len, 0, cmd_len); + } +} + +static int ahci_populate_sglist(AHCIDevice *ad, QEMUSGList *sglist) +{ + AHCICmdHdr *cmd = ad->cur_cmd; + uint32_t opts = le32_to_cpu(cmd->opts); + uint64_t prdt_addr = le64_to_cpu(cmd->tbl_addr) + 0x80; + int sglist_alloc_hint = opts >> AHCI_CMD_HDR_PRDT_LEN; + target_phys_addr_t prdt_len = (sglist_alloc_hint * sizeof(AHCI_SG)); + target_phys_addr_t real_prdt_len = prdt_len; + uint8_t *prdt; + int i; + int r = 0; + + if (!sglist_alloc_hint) { + DPRINTF(ad->port_no, "no sg list given by guest: 0x%08x\n", opts); + return -1; + } + + /* map PRDT */ + if (!(prdt = cpu_physical_memory_map(prdt_addr, &prdt_len, 0))){ + 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 (sglist_alloc_hint > 0) { + AHCI_SG *tbl = (AHCI_SG *)prdt; + + qemu_sglist_init(sglist, sglist_alloc_hint); + for (i = 0; i < sglist_alloc_hint; i++) { + /* flags_size is zero-based */ + qemu_sglist_add(sglist, le64_to_cpu(tbl[i].addr), + le32_to_cpu(tbl[i].flags_size) + 1); + } + } + +out: + cpu_physical_memory_unmap(prdt, prdt_len, 0, prdt_len); + return r; +} + +static void ncq_cb(void *opaque, int ret) +{ + NCQTransferState *ncq_tfs = (NCQTransferState *)opaque; + IDEState *ide_state = &ncq_tfs->drive->port.ifs[0]; + + /* Clear bit for this tag in SActive */ + ncq_tfs->drive->port_regs.scr_act &= ~(1 << ncq_tfs->tag); + + if (ret < 0) { + /* error */ + ide_state->error = ABRT_ERR; + ide_state->status = READY_STAT | ERR_STAT; + ncq_tfs->drive->port_regs.scr_err |= (1 << ncq_tfs->tag); + } else { + ide_state->status = READY_STAT | SEEK_STAT; + } + + ahci_write_fis_sdb(ncq_tfs->drive->hba, ncq_tfs->drive->port_no, + (1 << ncq_tfs->tag)); + + DPRINTF(ncq_tfs->drive->port_no, "NCQ transfer tag %d finished\n", + ncq_tfs->tag); + + qemu_sglist_destroy(&ncq_tfs->sglist); + ncq_tfs->used = 0; +} + +static void process_ncq_command(AHCIState *s, int port, uint8_t *cmd_fis, + int slot) +{ + NCQFrame *ncq_fis = (NCQFrame*)cmd_fis; + uint8_t tag = ncq_fis->tag >> 3; + NCQTransferState *ncq_tfs = &s->dev[port].ncq_tfs[tag]; + + 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 = &s->dev[port]; + ncq_tfs->slot = slot; + 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; + + /* Note: We calculate the sector count, but don't currently rely on it. + * The total size of the DMA buffer tells us the transfer size instead. */ + ncq_tfs->sector_count = ((uint16_t)ncq_fis->sector_count_high << 8) | + ncq_fis->sector_count_low; + + DPRINTF(port, "NCQ transfer LBA from %ld to %ld, drive max %ld\n", + ncq_tfs->lba, ncq_tfs->lba + ncq_tfs->sector_count - 2, + s->dev[port].port.ifs[0].nb_sectors - 1); + + ahci_populate_sglist(&s->dev[port], &ncq_tfs->sglist); + ncq_tfs->tag = tag; + + switch(ncq_fis->command) { + case READ_FPDMA_QUEUED: + DPRINTF(port, "NCQ reading %d sectors from LBA %ld, tag %d\n", + ncq_tfs->sector_count-1, ncq_tfs->lba, ncq_tfs->tag); + ncq_tfs->is_read = 1; + + DPRINTF(port, "tag %d aio read %ld\n", ncq_tfs->tag, ncq_tfs->lba); + ncq_tfs->aiocb = dma_bdrv_read(ncq_tfs->drive->port.ifs[0].bs, + &ncq_tfs->sglist, ncq_tfs->lba, + ncq_cb, ncq_tfs); + break; + case WRITE_FPDMA_QUEUED: + DPRINTF(port, "NCQ writing %d sectors to LBA %ld, tag %d\n", + ncq_tfs->sector_count-1, ncq_tfs->lba, ncq_tfs->tag); + ncq_tfs->is_read = 0; + + DPRINTF(port, "tag %d aio write %ld\n", ncq_tfs->tag, ncq_tfs->lba); + ncq_tfs->aiocb = dma_bdrv_write(ncq_tfs->drive->port.ifs[0].bs, + &ncq_tfs->sglist, ncq_tfs->lba, + ncq_cb, ncq_tfs); + break; + default: + DPRINTF(port, "error: tried to process non-NCQ command as NCQ\n"); + qemu_sglist_destroy(&ncq_tfs->sglist); + break; + } +} + +static int handle_cmd(AHCIState *s, int port, int slot) +{ + IDEState *ide_state; + uint32_t opts; + uint64_t tbl_addr; + AHCICmdHdr *cmd; + uint8_t *cmd_fis; + target_phys_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; + } + + cmd = &((AHCICmdHdr *)s->dev[port].lst)[slot]; + + if (!s->dev[port].lst) { + DPRINTF(port, "error: lst not given but cmd handled"); + return -1; + } + + /* remember current slot handle for later */ + s->dev[port].cur_cmd = cmd; + + opts = le32_to_cpu(cmd->opts); + tbl_addr = le64_to_cpu(cmd->tbl_addr); + + cmd_len = 0x80; + cmd_fis = cpu_physical_memory_map(tbl_addr, &cmd_len, 1); + + if (!cmd_fis) { + DPRINTF(port, "error: guest passed us an invalid cmd fis\n"); + return -1; + } + + /* The device we are working for */ + ide_state = &s->dev[port].port.ifs[0]; + + if (!ide_state->bs) { + DPRINTF(port, "error: guest accessed unused port"); + goto out; + } + + debug_print_fis(cmd_fis, 0x90); + //debug_print_fis(cmd_fis, (opts & AHCI_CMD_HDR_CMD_FIS_LEN) * 4); + + switch (cmd_fis[0]) { + case SATA_FIS_TYPE_REGISTER_H2D: + 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]); + goto out; + break; + } + + switch (cmd_fis[1]) { + case SATA_FIS_REG_H2D_UPDATE_COMMAND_REGISTER: + break; + case 0: + 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]); + goto out; + break; + } + + 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; + } + + if (cmd_fis[1] == SATA_FIS_REG_H2D_UPDATE_COMMAND_REGISTER) { + + /* Check for NCQ command */ + if ((cmd_fis[2] == READ_FPDMA_QUEUED) || + (cmd_fis[2] == WRITE_FPDMA_QUEUED)) { + process_ncq_command(s, port, cmd_fis, slot); + goto out; + } + + /* Decompose the FIS */ + ide_state->nsector = (int64_t)((cmd_fis[13] << 8) | cmd_fis[12]); + ide_state->feature = cmd_fis[3]; + if (!ide_state->nsector) { + ide_state->nsector = 256; + } + + if (ide_state->drive_kind != IDE_CD) { + ide_set_sector(ide_state, (cmd_fis[6] << 16) | (cmd_fis[5] << 8) | + cmd_fis[4]); + } + + /* 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); + ide_state->lcyl = 0x14; + ide_state->hcyl = 0xeb; + debug_print_fis(ide_state->io_buffer, 0x10); + ide_state->feature = IDE_FEATURE_DMA; + s->dev[port].done_atapi_packet = 0; + /* 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]); + + if (s->dev[port].port.ifs[0].status & READY_STAT) { + ahci_write_fis_d2h(&s->dev[port], cmd_fis); + } + } + +out: + cpu_physical_memory_unmap(cmd_fis, cmd_len, 1, 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 int 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 */ + uint32_t opts = le32_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 = 1; + goto out; + } + + if (!ahci_populate_sglist(ad, &s->sg)) { + 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 (is_write && has_sglist && (s->data_ptr < s->data_end)) { + read_from_sglist(s->data_ptr, size, &s->sg); + } + + if (!is_write && has_sglist && (s->data_ptr < s->data_end)) { + write_to_sglist(s->data_ptr, size, &s->sg); + } + + /* update number of transferred bytes */ + ad->cur_cmd->status = cpu_to_le32(le32_to_cpu(ad->cur_cmd->status) + size); + +out: + /* declare that we processed everything */ + s->data_ptr = s->data_end; + + if (has_sglist) { + qemu_sglist_destroy(&s->sg); + } + + s->end_transfer_func(s); + + if (!(s->status & DRQ_STAT)) { + /* done with DMA */ + ahci_trigger_irq(ad->hba, ad, PORT_IRQ_STAT_DSS); + } + + return 0; +} + +static void ahci_start_dma(IDEDMA *dma, IDEState *s, + BlockDriverCompletionFunc *dma_cb) +{ + AHCIDevice *ad = DO_UPCAST(AHCIDevice, dma, dma); + + DPRINTF(ad->port_no, "\n"); + ad->dma_cb = dma_cb; + ad->dma_status |= BM_STATUS_DMAING; + dma_cb(s, 0); +} + +static int ahci_dma_prepare_buf(IDEDMA *dma, int is_write) +{ + AHCIDevice *ad = DO_UPCAST(AHCIDevice, dma, dma); + IDEState *s = &ad->port.ifs[0]; + int i; + + ahci_populate_sglist(ad, &s->sg); + + s->io_buffer_size = 0; + for (i = 0; i < s->sg.nsg; i++) { + s->io_buffer_size += s->sg.sg[i].len; + } + + DPRINTF(ad->port_no, "len=%#x\n", s->io_buffer_size); + return s->io_buffer_size != 0; +} + +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)) { + return 0; + } + + if (is_write) { + write_to_sglist(p, l, &s->sg); + } else { + read_from_sglist(p, l, &s->sg); + } + + /* update number of transferred bytes */ + ad->cur_cmd->status = cpu_to_le32(le32_to_cpu(ad->cur_cmd->status) + l); + s->io_buffer_index += l; + + DPRINTF(ad->port_no, "len=%#x\n", l); + + return 1; +} + +static int ahci_dma_set_unit(IDEDMA *dma, int unit) +{ + /* only a single unit per link */ + return 0; +} + +static int ahci_dma_add_status(IDEDMA *dma, int status) +{ + AHCIDevice *ad = DO_UPCAST(AHCIDevice, dma, dma); + ad->dma_status |= status; + DPRINTF(ad->port_no, "set status: %x\n", status); + + if (status & BM_STATUS_INT) { + ahci_trigger_irq(ad->hba, ad, PORT_IRQ_STAT_DSS); + } + + return 0; +} + +static int ahci_dma_set_inactive(IDEDMA *dma) +{ + AHCIDevice *ad = DO_UPCAST(AHCIDevice, dma, dma); + + DPRINTF(ad->port_no, "dma done\n"); + + /* update d2h status */ + ahci_write_fis_d2h(ad, NULL); + + ad->dma_cb = NULL; + + /* 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); + + return 0; +} + +static void ahci_irq_set(void *opaque, int n, int level) +{ +} + +static void ahci_dma_restart_cb(void *opaque, int running, int reason) +{ +} + +static int ahci_dma_reset(IDEDMA *dma) +{ + return 0; +} + +static const IDEDMAOps ahci_dma_ops = { + .start_dma = ahci_start_dma, + .start_transfer = ahci_start_transfer, + .prepare_buf = ahci_dma_prepare_buf, + .rw_buf = ahci_dma_rw_buf, + .set_unit = ahci_dma_set_unit, + .add_status = ahci_dma_add_status, + .set_inactive = ahci_dma_set_inactive, + .restart_cb = ahci_dma_restart_cb, + .reset = ahci_dma_reset, +}; + +void ahci_init(AHCIState *s, DeviceState *qdev, int ports) +{ + qemu_irq *irqs; + int i; + + s->ports = ports; + s->dev = qemu_mallocz(sizeof(AHCIDevice) * ports); + ahci_reg_init(s); + s->mem = cpu_register_io_memory(ahci_readfn, ahci_writefn, s, + DEVICE_LITTLE_ENDIAN); + 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, qdev, i); + 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; + ad->port_regs.cmd = PORT_CMD_SPIN_UP | PORT_CMD_POWER_ON; + } +} + +void ahci_uninit(AHCIState *s) +{ + qemu_free(s->dev); +} + +void ahci_pci_map(PCIDevice *pci_dev, int region_num, + pcibus_t addr, pcibus_t size, int type) +{ + struct AHCIPCIState *d = (struct AHCIPCIState *)pci_dev; + AHCIState *s = &d->ahci; + + cpu_register_physical_memory(addr, size, s->mem); +} + +void ahci_reset(void *opaque) +{ + struct AHCIPCIState *d = opaque; + int i; + + d->ahci.control_regs.irqstatus = 0; + d->ahci.control_regs.ghc = 0; + + for (i = 0; i < d->ahci.ports; i++) { + ahci_reset_port(&d->ahci, i); + } +} diff --git a/hw/ide/ahci.h b/hw/ide/ahci.h new file mode 100644 index 0000000..a4560c4 --- /dev/null +++ b/hw/ide/ahci.h @@ -0,0 +1,333 @@ +/* + * QEMU AHCI Emulation + * + * Copyright (c) 2010 qiaochong@loongson.cn + * Copyright (c) 2010 Roland Elek + * Copyright (c) 2010 Sebastian Herbszt + * Copyright (c) 2010 Alexander Graf + * + * 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 . + * + */ + +#ifndef HW_IDE_AHCI_H +#define HW_IDE_AHCI_H + +#define AHCI_PCI_BAR 5 +#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 (1 << 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 (1 << 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 (1 << 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 (1 << 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 (0xf << 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_IRQ_STAT_DHRS (1 << 0) /* Device to Host Register FIS */ +#define PORT_IRQ_STAT_PSS (1 << 1) /* PIO Setup FIS */ +#define PORT_IRQ_STAT_DSS (1 << 2) /* DMA Setup FIS */ +#define PORT_IRQ_STAT_SDBS (1 << 3) /* Set Device Bits */ +#define PORT_IRQ_STAT_UFS (1 << 4) /* Unknown FIS */ +#define PORT_IRQ_STAT_DPS (1 << 5) /* Descriptor Processed */ +#define PORT_IRQ_STAT_PCS (1 << 6) /* Port Connect Change Status */ +#define PORT_IRQ_STAT_DMPS (1 << 7) /* Device Mechanical Presence + Status */ +#define PORT_IRQ_STAT_PRCS (1 << 22) /* File Ready Status */ +#define PORT_IRQ_STAT_IPMS (1 << 23) /* Incorrect Port Multiplier + Status */ +#define PORT_IRQ_STAT_OFS (1 << 24) /* Overflow Status */ +#define PORT_IRQ_STAT_INFS (1 << 26) /* Interface Non-Fatal Error + Status */ +#define PORT_IRQ_STAT_IFS (1 << 27) /* Interface Fatal Error */ +#define PORT_IRQ_STAT_HBDS (1 << 28) /* Host Bus Data Error Status */ +#define PORT_IRQ_STAT_HBFS (1 << 29) /* Host Bus Fatal Error Status */ +#define PORT_IRQ_STAT_TFES (1 << 30) /* Task File Error Status */ +#define PORT_IRQ_STAT_CPDS (1 << 31) /* Code Port Detect Status */ + +/* 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 AHCI_CMD_HDR_CMD_FIS_LEN 0x1f +#define AHCI_CMD_HDR_PRDT_LEN 16 + +#define SATA_SIGNATURE_CDROM 0xeb140000 +#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 IDE_FEATURE_DMA 1 + +#define READ_FPDMA_QUEUED 0x60 +#define WRITE_FPDMA_QUEUED 0x61 + +#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 + +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 { + uint32_t opts; + uint32_t status; + uint64_t tbl_addr; + uint32_t reserved[4]; +} __attribute__ ((packed)) AHCICmdHdr; + +typedef struct AHCI_SG { + uint64_t addr; + uint32_t reserved; + uint32_t flags_size; +} __attribute__ ((packed)) AHCI_SG; + +typedef struct AHCIDevice AHCIDevice; + +typedef struct NCQTransferState { + AHCIDevice *drive; + BlockDriverAIOCB *aiocb; + QEMUSGList sglist; + int is_read; + uint16_t sector_count; + uint64_t lba; + uint8_t tag; + int slot; + int used; +} 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; + int dma_status; + int done_atapi_packet; + int busy_slot; + int init_d2h_sent; + BlockDriverCompletionFunc *dma_cb; + AHCICmdHdr *cur_cmd; + NCQTransferState ncq_tfs[AHCI_MAX_CMDS]; +}; + +typedef struct AHCIState { + AHCIDevice *dev; + AHCIControlRegs control_regs; + int mem; + int ports; + qemu_irq irq; +} AHCIState; + +typedef struct AHCIPCIState { + PCIDevice card; + AHCIState ahci; +} AHCIPCIState; + +typedef struct NCQFrame { + uint8_t fis_type; + uint8_t c; + uint8_t command; + uint8_t sector_count_low; + uint8_t lba0; + uint8_t lba1; + uint8_t lba2; + uint8_t fua; + uint8_t lba3; + uint8_t lba4; + uint8_t lba5; + uint8_t sector_count_high; + uint8_t tag; + uint8_t reserved5; + uint8_t reserved6; + uint8_t control; + uint8_t reserved7; + uint8_t reserved8; + uint8_t reserved9; + uint8_t reserved10; +} __attribute__ ((packed)) NCQFrame; + +void ahci_init(AHCIState *s, DeviceState *qdev, int ports); +void ahci_uninit(AHCIState *s); + +void ahci_pci_map(PCIDevice *pci_dev, int region_num, + pcibus_t addr, pcibus_t size, int type); + +void ahci_reset(void *opaque); + +#endif /* HW_IDE_AHCI_H */ diff --git a/hw/ide/cmd646.c b/hw/ide/cmd646.c new file mode 100644 index 0000000..5d5464a --- /dev/null +++ b/hw/ide/cmd646.c @@ -0,0 +1,298 @@ +/* + * 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 +#include +#include +#include +#include "block.h" +#include "block_int.h" +#include "sysemu.h" +#include "dma.h" + +#include + +/* CMD646 specific */ +#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(PCIIDEState *d); + +static void ide_map(PCIDevice *pci_dev, int region_num, + pcibus_t addr, pcibus_t size, int type) +{ + PCIIDEState *d = DO_UPCAST(PCIIDEState, dev, pci_dev); + IDEBus *bus; + + if (region_num <= 3) { + bus = &d->bus[(region_num >> 1)]; + if (region_num & 1) { + register_ioport_read(addr + 2, 1, 1, ide_status_read, bus); + register_ioport_write(addr + 2, 1, 1, ide_cmd_write, bus); + } else { + register_ioport_write(addr, 8, 1, ide_ioport_write, bus); + register_ioport_read(addr, 8, 1, ide_ioport_read, bus); + + /* data ports */ + register_ioport_write(addr, 2, 2, ide_data_writew, bus); + register_ioport_read(addr, 2, 2, ide_data_readw, bus); + register_ioport_write(addr, 4, 4, ide_data_writel, bus); + register_ioport_read(addr, 4, 4, ide_data_readl, bus); + } + } +} + +static uint32_t bmdma_readb_common(PCIIDEState *pci_dev, BMDMAState *bm, + uint32_t addr) +{ + uint32_t val; + + switch(addr & 3) { + case 0: + val = bm->cmd; + break; + case 1: + val = pci_dev->dev.config[MRDMODE]; + break; + case 2: + val = bm->status; + break; + case 3: + if (bm == &pci_dev->bmdma[0]) { + val = pci_dev->dev.config[UDIDETCR0]; + } else { + val = pci_dev->dev.config[UDIDETCR1]; + } + break; + default: + val = 0xff; + break; + } +#ifdef DEBUG_IDE + printf("bmdma: readb 0x%02x : 0x%02x\n", addr, val); +#endif + return val; +} + +static uint32_t bmdma_readb_0(void *opaque, uint32_t addr) +{ + PCIIDEState *pci_dev = opaque; + BMDMAState *bm = &pci_dev->bmdma[0]; + + return bmdma_readb_common(pci_dev, bm, addr); +} + +static uint32_t bmdma_readb_1(void *opaque, uint32_t addr) +{ + PCIIDEState *pci_dev = opaque; + BMDMAState *bm = &pci_dev->bmdma[1]; + + return bmdma_readb_common(pci_dev, bm, addr); +} + +static void bmdma_writeb_common(PCIIDEState *pci_dev, BMDMAState *bm, + uint32_t addr, uint32_t val) +{ +#ifdef DEBUG_IDE + printf("bmdma: writeb 0x%02x : 0x%02x\n", addr, val); +#endif + switch(addr & 3) { + case 0: + bmdma_cmd_writeb(bm, addr, val); + break; + case 1: + pci_dev->dev.config[MRDMODE] = + (pci_dev->dev.config[MRDMODE] & ~0x30) | (val & 0x30); + cmd646_update_irq(pci_dev); + break; + case 2: + bm->status = (val & 0x60) | (bm->status & 1) | (bm->status & ~val & 0x06); + break; + case 3: + if (bm == &pci_dev->bmdma[0]) + pci_dev->dev.config[UDIDETCR0] = val; + else + pci_dev->dev.config[UDIDETCR1] = val; + break; + } +} + +static void bmdma_writeb_0(void *opaque, uint32_t addr, uint32_t val) +{ + PCIIDEState *pci_dev = opaque; + BMDMAState *bm = &pci_dev->bmdma[0]; + + bmdma_writeb_common(pci_dev, bm, addr, val); +} + +static void bmdma_writeb_1(void *opaque, uint32_t addr, uint32_t val) +{ + PCIIDEState *pci_dev = opaque; + BMDMAState *bm = &pci_dev->bmdma[1]; + + bmdma_writeb_common(pci_dev, bm, addr, val); +} + +static void bmdma_map(PCIDevice *pci_dev, int region_num, + pcibus_t addr, pcibus_t size, int type) +{ + PCIIDEState *d = DO_UPCAST(PCIIDEState, dev, pci_dev); + int i; + + for(i = 0;i < 2; i++) { + BMDMAState *bm = &d->bmdma[i]; + + if (i == 0) { + register_ioport_write(addr, 4, 1, bmdma_writeb_0, d); + register_ioport_read(addr, 4, 1, bmdma_readb_0, d); + } else { + register_ioport_write(addr, 4, 1, bmdma_writeb_1, d); + register_ioport_read(addr, 4, 1, bmdma_readb_1, d); + } + + iorange_init(&bm->addr_ioport, &bmdma_addr_ioport_ops, addr + 4, 4); + ioport_register(&bm->addr_ioport); + addr += 8; + } +} + +/* XXX: call it also when the MRDMODE is changed from the PCI config + registers */ +static void cmd646_update_irq(PCIIDEState *d) +{ + int pci_level; + pci_level = ((d->dev.config[MRDMODE] & MRDMODE_INTR_CH0) && + !(d->dev.config[MRDMODE] & MRDMODE_BLK_CH0)) || + ((d->dev.config[MRDMODE] & MRDMODE_INTR_CH1) && + !(d->dev.config[MRDMODE] & MRDMODE_BLK_CH1)); + qemu_set_irq(d->dev.irq[0], 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; + int irq_mask; + + irq_mask = MRDMODE_INTR_CH0 << channel; + if (level) + d->dev.config[MRDMODE] |= irq_mask; + else + d->dev.config[MRDMODE] &= ~irq_mask; + cmd646_update_irq(d); +} + +static void cmd646_reset(void *opaque) +{ + PCIIDEState *d = opaque; + unsigned int i; + + for (i = 0; i < 2; i++) { + ide_bus_reset(&d->bus[i]); + } +} + +/* CMD646 PCI IDE controller */ +static int pci_cmd646_ide_initfn(PCIDevice *dev) +{ + PCIIDEState *d = DO_UPCAST(PCIIDEState, dev, dev); + uint8_t *pci_conf = d->dev.config; + qemu_irq *irq; + int i; + + pci_config_set_vendor_id(pci_conf, PCI_VENDOR_ID_CMD); + pci_config_set_device_id(pci_conf, PCI_DEVICE_ID_CMD_646); + + pci_conf[PCI_REVISION_ID] = 0x07; // IDE controller revision + pci_conf[PCI_CLASS_PROG] = 0x8f; + + pci_config_set_class(pci_conf, PCI_CLASS_STORAGE_IDE); + + pci_conf[0x51] = 0x04; // enable IDE0 + if (d->secondary) { + /* XXX: if not enabled, really disable the seconday IDE controller */ + pci_conf[0x51] |= 0x08; /* enable IDE1 */ + } + + pci_register_bar(dev, 0, 0x8, PCI_BASE_ADDRESS_SPACE_IO, ide_map); + pci_register_bar(dev, 1, 0x4, PCI_BASE_ADDRESS_SPACE_IO, ide_map); + pci_register_bar(dev, 2, 0x8, PCI_BASE_ADDRESS_SPACE_IO, ide_map); + pci_register_bar(dev, 3, 0x4, PCI_BASE_ADDRESS_SPACE_IO, ide_map); + pci_register_bar(dev, 4, 0x10, PCI_BASE_ADDRESS_SPACE_IO, bmdma_map); + + /* 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], &d->dev.qdev, i); + ide_init2(&d->bus[i], irq[i]); + + bmdma_init(&d->bus[i], &d->bmdma[i]); + d->bmdma[i].bus = &d->bus[i]; + qemu_add_vm_change_state_handler(d->bus[i].dma->ops->restart_cb, + &d->bmdma[i].dma); + } + + vmstate_register(&dev->qdev, 0, &vmstate_ide_pci, d); + qemu_register_reset(cmd646_reset, d); + return 0; +} + +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 PCIDeviceInfo cmd646_ide_info[] = { + { + .qdev.name = "cmd646-ide", + .qdev.size = sizeof(PCIIDEState), + .init = pci_cmd646_ide_initfn, + .qdev.props = (Property[]) { + DEFINE_PROP_UINT32("secondary", PCIIDEState, secondary, 0), + DEFINE_PROP_END_OF_LIST(), + }, + },{ + /* end of list */ + } +}; + +static void cmd646_ide_register(void) +{ + pci_qdev_register_many(cmd646_ide_info); +} +device_init(cmd646_ide_register); diff --git a/hw/ide/core.c b/hw/ide/core.c new file mode 100644 index 0000000..dd63664 --- /dev/null +++ b/hw/ide/core.c @@ -0,0 +1,2813 @@ +/* + * 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 +#include +#include +#include +#include "qemu-error.h" +#include "qemu-timer.h" +#include "sysemu.h" +#include "dma.h" +#include "blockdev.h" + +#include + +static const int smart_attributes[][5] = { + /* id, flags, val, wrst, thrsh */ + { 0x01, 0x03, 0x64, 0x64, 0x06}, /* raw read */ + { 0x03, 0x03, 0x64, 0x64, 0x46}, /* spin up */ + { 0x04, 0x02, 0x64, 0x64, 0x14}, /* start stop count */ + { 0x05, 0x03, 0x64, 0x64, 0x36}, /* remapped sectors */ + { 0x00, 0x00, 0x00, 0x00, 0x00} +}; + +/* XXX: DVDs that could fit on a CD will be reported as a CD */ +static inline int media_present(IDEState *s) +{ + return (s->nb_sectors > 0); +} + +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 ide_atapi_cmd_read_dma_cb(void *opaque, int ret); +static int ide_handle_rw_error(IDEState *s, int error, int op); + +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 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 void put_le16(uint16_t *p, unsigned int v) +{ + *p = cpu_to_le16(v); +} + +static void ide_identify(IDEState *s) +{ + uint16_t *p; + unsigned int oldsize; + IDEDevice *dev; + + if (s->identify_set) { + memcpy(s->io_buffer, s->identify_data, sizeof(s->identify_data)); + return; + } + + memset(s->io_buffer, 0, 512); + p = (uint16_t *)s->io_buffer; + 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), "QEMU HARDDISK", 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); + put_le16(p + 60, s->nb_sectors); + put_le16(p + 61, s->nb_sectors >> 16); + 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 (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, 1=SMART self test, 0=SMART error logging */ + put_le16(p + 84, (1 << 14) | 0); + /* 14 = NOP supported, 5=WCACHE enabled, 0=SMART feature set enabled */ + if (bdrv_enable_write_cache(s->bs)) + 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 << 14) | (1 << 13) | (1 <<12) | (1 << 10)); + /* 14=set to 1, 1=smart self test, 0=smart error logging */ + 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); + 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); + dev = s->unit ? s->bus->slave : s->bus->master; + if (dev && dev->conf.physical_block_size) + put_le16(p + 106, 0x6000 | get_physical_block_exp(&dev->conf)); + + memcpy(s->identify_data, p, sizeof(s->identify_data)); + s->identify_set = 1; +} + +static void ide_atapi_identify(IDEState *s) +{ + uint16_t *p; + + if (s->identify_set) { + memcpy(s->io_buffer, s->identify_data, sizeof(s->identify_data)); + return; + } + + memset(s->io_buffer, 0, 512); + p = (uint16_t *)s->io_buffer; + /* 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), "QEMU DVD-ROM", 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 */ +#ifdef USE_DMA_CDROM + put_le16(p + 88, 0x3f | (1 << 13)); /* udma5 set and supported */ +#endif + memcpy(s->identify_data, p, sizeof(s->identify_data)); + s->identify_set = 1; +} + +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 */ + put_le16(p + 7, s->nb_sectors >> 16); /* Sectors per card */ + put_le16(p + 8, s->nb_sectors); /* Sectors per card */ + 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), "QEMU MICRODRIVE", 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); + put_le16(p + 60, s->nb_sectors); /* Total LBA sectors */ + put_le16(p + 61, s->nb_sectors >> 16); /* Total LBA sectors */ + 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 */ + + 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->bs) { + s->lcyl = 0; + s->hcyl = 0; + } else { + s->lcyl = 0xff; + s->hcyl = 0xff; + } +} + +static inline void ide_abort_command(IDEState *s) +{ + s->status = READY_STAT | ERR_STAT; + s->error = ABRT_ERR; +} + +/* prepare data transfer and tell what to do after */ +static 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; + } + s->bus->dma->ops->start_transfer(s->bus->dma); +} + +static 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; +} + +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); +} + +void ide_sector_read(IDEState *s) +{ + int64_t sector_num; + int ret, 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) { + /* no more sector to read from disk */ + ide_transfer_stop(s); + } else { +#if defined(DEBUG_IDE) + printf("read sector=%" PRId64 "\n", sector_num); +#endif + if (n > s->req_nb_sectors) + n = s->req_nb_sectors; + ret = bdrv_read(s->bs, sector_num, s->io_buffer, n); + if (ret != 0) { + if (ide_handle_rw_error(s, -ret, + BM_STATUS_PIO_RETRY | BM_STATUS_RETRY_READ)) + { + return; + } + } + ide_transfer_start(s, s->io_buffer, 512 * n, ide_sector_read); + ide_set_irq(s->bus); + ide_set_sector(s, sector_num + n); + s->nsector -= n; + } +} + +static void dma_buf_commit(IDEState *s, int is_write) +{ + qemu_sglist_destroy(&s->sg); +} + +static void ide_set_inactive(IDEState *s) +{ + s->bus->dma->aiocb = NULL; + s->bus->dma->ops->set_inactive(s->bus->dma); +} + +void ide_dma_error(IDEState *s) +{ + ide_transfer_stop(s); + s->error = ABRT_ERR; + s->status = READY_STAT | ERR_STAT; + ide_set_inactive(s); + s->bus->dma->ops->add_status(s->bus->dma, BM_STATUS_INT); + ide_set_irq(s->bus); +} + +static int ide_handle_rw_error(IDEState *s, int error, int op) +{ + int is_read = (op & BM_STATUS_RETRY_READ); + BlockErrorAction action = bdrv_get_on_error(s->bs, is_read); + + if (action == BLOCK_ERR_IGNORE) { + bdrv_mon_event(s->bs, BDRV_ACTION_IGNORE, is_read); + return 0; + } + + if ((error == ENOSPC && action == BLOCK_ERR_STOP_ENOSPC) + || action == BLOCK_ERR_STOP_ANY) { + s->bus->dma->ops->set_unit(s->bus->dma, s->unit); + s->bus->dma->ops->add_status(s->bus->dma, op); + bdrv_mon_event(s->bs, BDRV_ACTION_STOP, is_read); + vm_stop(0); + } else { + if (op & BM_STATUS_DMA_RETRY) { + dma_buf_commit(s, 0); + ide_dma_error(s); + } else { + ide_rw_error(s); + } + bdrv_mon_event(s->bs, BDRV_ACTION_REPORT, is_read); + } + + return 1; +} + +void ide_dma_cb(void *opaque, int ret) +{ + IDEState *s = opaque; + int n; + int64_t sector_num; + +handle_rw_error: + if (ret < 0) { + int op = BM_STATUS_DMA_RETRY; + + if (s->is_read) + op |= BM_STATUS_RETRY_READ; + if (ide_handle_rw_error(s, -ret, op)) { + return; + } + } + + n = s->io_buffer_size >> 9; + sector_num = ide_get_sector(s); + if (n > 0) { + dma_buf_commit(s, s->is_read); + 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->is_read) == 0) + goto eot; + +#ifdef DEBUG_AIO + printf("ide_dma_cb: sector_num=%" PRId64 " n=%d, is_read=%d\n", + sector_num, n, s->is_read); +#endif + + if (s->is_read) { + s->bus->dma->aiocb = dma_bdrv_read(s->bs, &s->sg, sector_num, + ide_dma_cb, s); + } else { + s->bus->dma->aiocb = dma_bdrv_write(s->bs, &s->sg, sector_num, + ide_dma_cb, s); + } + + if (!s->bus->dma->aiocb) { + ret = -1; + goto handle_rw_error; + } + return; + +eot: + s->bus->dma->ops->add_status(s->bus->dma, BM_STATUS_INT); + ide_set_inactive(s); +} + +static void ide_sector_start_dma(IDEState *s, int is_read) +{ + s->status = READY_STAT | SEEK_STAT | DRQ_STAT | BUSY_STAT; + s->io_buffer_index = 0; + s->io_buffer_size = 0; + s->is_read = is_read; + s->bus->dma->ops->start_dma(s->bus->dma, s, ide_dma_cb); +} + +static void ide_sector_write_timer_cb(void *opaque) +{ + IDEState *s = opaque; + ide_set_irq(s->bus); +} + +void ide_sector_write(IDEState *s) +{ + int64_t sector_num; + int ret, n, n1; + + s->status = READY_STAT | SEEK_STAT; + sector_num = ide_get_sector(s); +#if defined(DEBUG_IDE) + printf("write sector=%" PRId64 "\n", sector_num); +#endif + n = s->nsector; + if (n > s->req_nb_sectors) + n = s->req_nb_sectors; + ret = bdrv_write(s->bs, sector_num, s->io_buffer, n); + + if (ret != 0) { + if (ide_handle_rw_error(s, -ret, BM_STATUS_PIO_RETRY)) + return; + } + + s->nsector -= n; + if (s->nsector == 0) { + /* no more sectors to write */ + ide_transfer_stop(s); + } else { + n1 = s->nsector; + if (n1 > s->req_nb_sectors) + n1 = s->req_nb_sectors; + ide_transfer_start(s, s->io_buffer, 512 * n1, ide_sector_write); + } + ide_set_sector(s, sector_num + n); + + 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. */ + qemu_mod_timer(s->sector_write_timer, + qemu_get_clock(vm_clock) + (get_ticks_per_sec() / 1000)); + } else { + ide_set_irq(s->bus); + } +} + +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_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_set_irq(s->bus); +} + +static void ide_atapi_cmd_check_status(IDEState *s) +{ +#ifdef DEBUG_IDE_ATAPI + printf("atapi_cmd_check_status\n"); +#endif + s->error = MC_ERR | (SENSE_UNIT_ATTENTION << 4); + s->status = ERR_STAT; + s->nsector = 0; + ide_set_irq(s->bus); +} + +static void ide_flush_cb(void *opaque, int ret) +{ + IDEState *s = opaque; + + if (ret < 0) { + /* XXX: What sector number to set here? */ + if (ide_handle_rw_error(s, -ret, BM_STATUS_RETRY_FLUSH)) { + return; + } + } + + s->status = READY_STAT | SEEK_STAT; + ide_set_irq(s->bus); +} + +void ide_flush_cache(IDEState *s) +{ + BlockDriverAIOCB *acb; + + if (s->bs == NULL) { + ide_flush_cb(s, 0); + return; + } + + acb = bdrv_aio_flush(s->bs, ide_flush_cb, s); + if (acb == NULL) { + ide_flush_cb(s, -EIO); + } +} + +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 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(BlockDriverState *bs, int lba, uint8_t *buf, + int sector_size) +{ + int ret; + + switch(sector_size) { + case 2048: + ret = bdrv_read(bs, (int64_t)lba << 2, buf, 4); + break; + case 2352: + ret = bdrv_read(bs, (int64_t)lba << 2, buf + 16, 4); + if (ret < 0) + return ret; + cd_data_to_raw(buf, lba); + break; + default: + ret = -EIO; + break; + } + return ret; +} + +void ide_atapi_io_error(IDEState *s, int ret) +{ + /* XXX: handle more errors */ + if (ret == -ENOMEDIUM) { + ide_atapi_cmd_error(s, SENSE_NOT_READY, + ASC_MEDIUM_NOT_PRESENT); + } else { + ide_atapi_cmd_error(s, SENSE_ILLEGAL_REQUEST, + ASC_LOGICAL_BLOCK_OOR); + } +} + +/* The whole ATAPI transfer logic is handled in this function */ +static 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_transfer_stop(s); + s->status = READY_STAT | SEEK_STAT; + s->nsector = (s->nsector & ~7) | ATAPI_INT_REASON_IO | ATAPI_INT_REASON_CD; + 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->bs, s->lba, s->io_buffer, s->cd_sector_size); + if (ret < 0) { + ide_transfer_stop(s); + 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; + s->io_buffer_index = 0; + + if (s->atapi_dma) { + s->status = READY_STAT | SEEK_STAT | DRQ_STAT; + s->bus->dma->ops->start_dma(s->bus->dma, s, + ide_atapi_cmd_read_dma_cb); + } else { + s->status = READY_STAT | SEEK_STAT; + 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); +} + +/* 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); + eot: + s->bus->dma->ops->add_status(s->bus->dma, BM_STATUS_INT); + ide_set_inactive(s); + return; + } + + 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 = bdrv_aio_readv(s->bs, (int64_t)s->lba << 2, + &s->bus->dma->qiov, n * 4, + ide_atapi_cmd_read_dma_cb, s); + if (!s->bus->dma->aiocb) { + /* Note: media not present is the most likely case */ + ide_atapi_cmd_error(s, SENSE_NOT_READY, + ASC_MEDIUM_NOT_PRESENT); + goto eot; + } +} + +/* 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_index = 0; + s->io_buffer_size = 0; + s->cd_sector_size = sector_size; + + /* XXX: check if BUSY_STAT should be set */ + s->status = READY_STAT | SEEK_STAT | DRQ_STAT | BUSY_STAT; + s->bus->dma->ops->start_dma(s->bus->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); + } +} + +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; + + bdrv_get_geometry(s->bs, &total_sectors); + total_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 */ + cpu_to_be16wu((uint16_t *)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 */ + cpu_to_be16wu((uint16_t *)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 */ + cpu_to_be16wu((uint16_t *)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 */ + cpu_to_be16wu((uint16_t *)(buf + 6), 2048 + 4); + + buf[8] = 0x01; /* Copyright info */ + buf[9] = 0x40; /* Not writable, is readable */ + cpu_to_be16wu((uint16_t *)(buf + 10), 4 + 4); + + buf[12] = 0x03; /* BCA info */ + buf[13] = 0x40; /* Not writable, is readable */ + cpu_to_be16wu((uint16_t *)(buf + 14), 188 + 4); + + buf[16] = 0x04; /* Manufacturing info */ + buf[17] = 0x40; /* Not writable, is readable */ + cpu_to_be16wu((uint16_t *)(buf + 18), 2048 + 4); + + /* Size of buffer, not including 2 byte size field */ + cpu_to_be16wu((uint16_t *)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 void ide_atapi_cmd(IDEState *s) +{ + const uint8_t *packet; + uint8_t *buf; + int max_len; + + packet = s->io_buffer; + 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", packet[i]); + } + printf("\n"); + } +#endif + /* If there's a UNIT_ATTENTION condition pending, only + REQUEST_SENSE and INQUIRY commands are allowed to complete. */ + if (s->sense_key == SENSE_UNIT_ATTENTION && + s->io_buffer[0] != GPCMD_REQUEST_SENSE && + s->io_buffer[0] != GPCMD_INQUIRY) { + ide_atapi_cmd_check_status(s); + return; + } + switch(s->io_buffer[0]) { + case GPCMD_TEST_UNIT_READY: + if (bdrv_is_inserted(s->bs) && !s->cdrom_changed) { + ide_atapi_cmd_ok(s); + } else { + s->cdrom_changed = 0; + ide_atapi_cmd_error(s, SENSE_NOT_READY, + ASC_MEDIUM_NOT_PRESENT); + } + break; + case GPCMD_MODE_SENSE_6: + case GPCMD_MODE_SENSE_10: + { + int action, code; + if (packet[0] == GPCMD_MODE_SENSE_10) + max_len = ube16_to_cpu(packet + 7); + else + max_len = packet[4]; + action = packet[2] >> 6; + code = packet[2] & 0x3f; + switch(action) { + case 0: /* current values */ + switch(code) { + case GPMODE_R_W_ERROR_PAGE: /* error recovery */ + cpu_to_ube16(&buf[0], 16 + 6); + buf[2] = 0x70; + buf[3] = 0; + buf[4] = 0; + buf[5] = 0; + buf[6] = 0; + buf[7] = 0; + + buf[8] = 0x01; + buf[9] = 0x06; + 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 GPMODE_AUDIO_CTL_PAGE: + cpu_to_ube16(&buf[0], 24 + 6); + buf[2] = 0x70; + buf[3] = 0; + buf[4] = 0; + buf[5] = 0; + buf[6] = 0; + buf[7] = 0; + + /* 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 GPMODE_CAPABILITIES_PAGE: + cpu_to_ube16(&buf[0], 28 + 6); + buf[2] = 0x70; + buf[3] = 0; + buf[4] = 0; + buf[5] = 0; + buf[6] = 0; + buf[7] = 0; + + buf[8] = 0x2a; + buf[9] = 0x12; + buf[10] = 0x00; + 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 (bdrv_is_locked(s->bs)) + buf[6] |= 1 << 1; + buf[15] = 0x00; + cpu_to_ube16(&buf[16], 706); + buf[18] = 0; + buf[19] = 2; + cpu_to_ube16(&buf[20], 512); + cpu_to_ube16(&buf[22], 706); + buf[24] = 0; + buf[25] = 0; + buf[26] = 0; + buf[27] = 0; + ide_atapi_cmd_reply(s, 28, 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, SENSE_ILLEGAL_REQUEST, + ASC_SAVING_PARAMETERS_NOT_SUPPORTED); + break; + } + } + break; + case GPCMD_REQUEST_SENSE: + max_len = packet[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 == SENSE_UNIT_ATTENTION) + s->sense_key = SENSE_NONE; + ide_atapi_cmd_reply(s, 18, max_len); + break; + case GPCMD_PREVENT_ALLOW_MEDIUM_REMOVAL: + if (bdrv_is_inserted(s->bs)) { + bdrv_set_locked(s->bs, packet[4] & 1); + ide_atapi_cmd_ok(s); + } else { + ide_atapi_cmd_error(s, SENSE_NOT_READY, + ASC_MEDIUM_NOT_PRESENT); + } + break; + case GPCMD_READ_10: + case GPCMD_READ_12: + { + int nb_sectors, lba; + + if (packet[0] == GPCMD_READ_10) + nb_sectors = ube16_to_cpu(packet + 7); + else + nb_sectors = ube32_to_cpu(packet + 6); + lba = ube32_to_cpu(packet + 2); + if (nb_sectors == 0) { + ide_atapi_cmd_ok(s); + break; + } + ide_atapi_cmd_read(s, lba, nb_sectors, 2048); + } + break; + case GPCMD_READ_CD: + { + int nb_sectors, lba, transfer_request; + + nb_sectors = (packet[6] << 16) | (packet[7] << 8) | packet[8]; + lba = ube32_to_cpu(packet + 2); + if (nb_sectors == 0) { + ide_atapi_cmd_ok(s); + break; + } + transfer_request = packet[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, SENSE_ILLEGAL_REQUEST, + ASC_INV_FIELD_IN_CMD_PACKET); + break; + } + } + break; + case GPCMD_SEEK: + { + unsigned int lba; + uint64_t total_sectors; + + bdrv_get_geometry(s->bs, &total_sectors); + total_sectors >>= 2; + if (total_sectors == 0) { + ide_atapi_cmd_error(s, SENSE_NOT_READY, + ASC_MEDIUM_NOT_PRESENT); + break; + } + lba = ube32_to_cpu(packet + 2); + if (lba >= total_sectors) { + ide_atapi_cmd_error(s, SENSE_ILLEGAL_REQUEST, + ASC_LOGICAL_BLOCK_OOR); + break; + } + ide_atapi_cmd_ok(s); + } + break; + case GPCMD_START_STOP_UNIT: + { + int start, eject, err = 0; + start = packet[4] & 1; + eject = (packet[4] >> 1) & 1; + + if (eject) { + err = bdrv_eject(s->bs, !start); + } + + switch (err) { + case 0: + ide_atapi_cmd_ok(s); + break; + case -EBUSY: + ide_atapi_cmd_error(s, SENSE_NOT_READY, + ASC_MEDIA_REMOVAL_PREVENTED); + break; + default: + ide_atapi_cmd_error(s, SENSE_NOT_READY, + ASC_MEDIUM_NOT_PRESENT); + break; + } + } + break; + case GPCMD_MECHANISM_STATUS: + { + max_len = ube16_to_cpu(packet + 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); + } + break; + case GPCMD_READ_TOC_PMA_ATIP: + { + int format, msf, start_track, len; + uint64_t total_sectors; + + bdrv_get_geometry(s->bs, &total_sectors); + total_sectors >>= 2; + if (total_sectors == 0) { + ide_atapi_cmd_error(s, SENSE_NOT_READY, + ASC_MEDIUM_NOT_PRESENT); + break; + } + max_len = ube16_to_cpu(packet + 7); + format = packet[9] >> 6; + msf = (packet[1] >> 1) & 1; + start_track = packet[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, SENSE_ILLEGAL_REQUEST, + ASC_INV_FIELD_IN_CMD_PACKET); + break; + } + } + break; + case GPCMD_READ_CDVD_CAPACITY: + { + uint64_t total_sectors; + + bdrv_get_geometry(s->bs, &total_sectors); + total_sectors >>= 2; + if (total_sectors == 0) { + ide_atapi_cmd_error(s, SENSE_NOT_READY, + ASC_MEDIUM_NOT_PRESENT); + break; + } + /* 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); + } + break; + case GPCMD_READ_DVD_STRUCTURE: + { + int media = packet[1]; + int format = packet[7]; + int ret; + + max_len = ube16_to_cpu(packet + 8); + + if (format < 0xff) { + if (media_is_cd(s)) { + ide_atapi_cmd_error(s, SENSE_ILLEGAL_REQUEST, + ASC_INCOMPATIBLE_FORMAT); + break; + } else if (!media_present(s)) { + ide_atapi_cmd_error(s, SENSE_ILLEGAL_REQUEST, + ASC_INV_FIELD_IN_CMD_PACKET); + break; + } + } + + 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, packet, buf); + + if (ret < 0) + ide_atapi_cmd_error(s, SENSE_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, SENSE_ILLEGAL_REQUEST, + ASC_INV_FIELD_IN_CMD_PACKET); + break; + } + } + break; + case GPCMD_SET_SPEED: + ide_atapi_cmd_ok(s); + break; + case GPCMD_INQUIRY: + max_len = packet[4]; + buf[0] = 0x05; /* CD-ROM */ + buf[1] = 0x80; /* removable */ + buf[2] = 0x00; /* ISO */ + buf[3] = 0x21; /* ATAPI-2 (XXX: put ATAPI-4 ?) */ + buf[4] = 31; /* additional length */ + 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); + ide_atapi_cmd_reply(s, 36, max_len); + break; + case GPCMD_GET_CONFIGURATION: + { + uint32_t len; + uint8_t index = 0; + + /* only feature 0 is supported */ + if (packet[2] != 0 || packet[3] != 0) { + ide_atapi_cmd_error(s, SENSE_ILLEGAL_REQUEST, + ASC_INV_FIELD_IN_CMD_PACKET); + break; + } + + /* XXX: could result in alignment problems in some architectures */ + max_len = ube16_to_cpu(packet + 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); + break; + } + case GPCMD_GET_EVENT_STATUS_NOTIFICATION: + max_len = ube16_to_cpu(packet + 7); + + if (packet[1] & 0x01) { /* polling */ + /* We don't support any event class (yet). */ + cpu_to_ube16(buf, 0x00); /* No event descriptor returned */ + buf[2] = 0x80; /* No Event Available (NEA) */ + buf[3] = 0x00; /* Empty supported event classes */ + ide_atapi_cmd_reply(s, 4, max_len); + } else { /* asynchronous mode */ + /* Only polling is supported, asynchronous mode is not. */ + ide_atapi_cmd_error(s, SENSE_ILLEGAL_REQUEST, + ASC_INV_FIELD_IN_CMD_PACKET); + } + break; + default: + ide_atapi_cmd_error(s, SENSE_ILLEGAL_REQUEST, + ASC_ILLEGAL_OPCODE); + break; + } +} + +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 cdrom_change_cb(void *opaque, int reason) +{ + IDEState *s = opaque; + uint64_t nb_sectors; + + if (!(reason & CHANGE_MEDIA)) { + return; + } + + bdrv_get_geometry(s->bs, &nb_sectors); + s->nb_sectors = nb_sectors; + + s->sense_key = SENSE_UNIT_ATTENTION; + s->asc = ASC_MEDIUM_MAY_HAVE_CHANGED; + s->cdrom_changed = 1; + 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; + } +} + + +void ide_exec_cmd(IDEBus *bus, uint32_t val) +{ + IDEState *s; + int n; + int lba48 = 0; + +#if defined(DEBUG_IDE) + printf("ide: CMD=%02x\n", val); +#endif + s = idebus_active_if(bus); + /* ignore commands to non existant slave */ + if (s != bus->ifs && !s->bs) + 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; + + switch(val) { + case WIN_IDENTIFY: + if (s->bs && 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); + } else { + if (s->drive_kind == IDE_CD) { + ide_set_signature(s); + } + ide_abort_command(s); + } + ide_set_irq(s->bus); + break; + case WIN_SPECIFY: + case WIN_RECAL: + s->error = 0; + s->status = READY_STAT | SEEK_STAT; + ide_set_irq(s->bus); + break; + case WIN_SETMULT: + if (s->drive_kind == IDE_CFATA && s->nsector == 0) { + /* Disable Read and Write Multiple */ + s->mult_sectors = 0; + s->status = READY_STAT | SEEK_STAT; + } 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; + s->status = READY_STAT | SEEK_STAT; + } + ide_set_irq(s->bus); + break; + case WIN_VERIFY_EXT: + lba48 = 1; + case WIN_VERIFY: + case WIN_VERIFY_ONCE: + /* do sector number check ? */ + ide_cmd_lba48_transform(s, lba48); + s->status = READY_STAT | SEEK_STAT; + ide_set_irq(s->bus); + break; + case WIN_READ_EXT: + lba48 = 1; + case WIN_READ: + case WIN_READ_ONCE: + if (!s->bs) + goto abort_cmd; + ide_cmd_lba48_transform(s, lba48); + s->req_nb_sectors = 1; + ide_sector_read(s); + break; + case WIN_WRITE_EXT: + lba48 = 1; + case WIN_WRITE: + case WIN_WRITE_ONCE: + case CFA_WRITE_SECT_WO_ERASE: + case WIN_WRITE_VERIFY: + ide_cmd_lba48_transform(s, lba48); + s->error = 0; + s->status = SEEK_STAT | READY_STAT; + s->req_nb_sectors = 1; + ide_transfer_start(s, s->io_buffer, 512, ide_sector_write); + s->media_changed = 1; + break; + case WIN_MULTREAD_EXT: + lba48 = 1; + case WIN_MULTREAD: + if (!s->mult_sectors) + goto abort_cmd; + ide_cmd_lba48_transform(s, lba48); + s->req_nb_sectors = s->mult_sectors; + ide_sector_read(s); + break; + case WIN_MULTWRITE_EXT: + lba48 = 1; + case WIN_MULTWRITE: + case CFA_WRITE_MULTI_WO_ERASE: + if (!s->mult_sectors) + goto abort_cmd; + ide_cmd_lba48_transform(s, lba48); + s->error = 0; + s->status = SEEK_STAT | READY_STAT; + s->req_nb_sectors = s->mult_sectors; + n = s->nsector; + if (n > s->req_nb_sectors) + n = s->req_nb_sectors; + ide_transfer_start(s, s->io_buffer, 512 * n, ide_sector_write); + s->media_changed = 1; + break; + case WIN_READDMA_EXT: + lba48 = 1; + case WIN_READDMA: + case WIN_READDMA_ONCE: + if (!s->bs) + goto abort_cmd; + ide_cmd_lba48_transform(s, lba48); + ide_sector_start_dma(s, 1); + break; + case WIN_WRITEDMA_EXT: + lba48 = 1; + case WIN_WRITEDMA: + case WIN_WRITEDMA_ONCE: + if (!s->bs) + goto abort_cmd; + ide_cmd_lba48_transform(s, lba48); + ide_sector_start_dma(s, 0); + s->media_changed = 1; + break; + case WIN_READ_NATIVE_MAX_EXT: + lba48 = 1; + case WIN_READ_NATIVE_MAX: + ide_cmd_lba48_transform(s, lba48); + ide_set_sector(s, s->nb_sectors - 1); + s->status = READY_STAT | SEEK_STAT; + ide_set_irq(s->bus); + break; + case WIN_CHECKPOWERMODE1: + case WIN_CHECKPOWERMODE2: + s->nsector = 0xff; /* device active or idle */ + s->status = READY_STAT | SEEK_STAT; + ide_set_irq(s->bus); + break; + case WIN_SETFEATURES: + if (!s->bs) + goto abort_cmd; + /* XXX: valid for CDROM ? */ + switch(s->feature) { + case 0xcc: /* reverting to power-on defaults enable */ + case 0x66: /* reverting to power-on defaults disable */ + case 0x02: /* write cache enable */ + case 0x82: /* write cache 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 */ + s->status = READY_STAT | SEEK_STAT; + ide_set_irq(s->bus); + break; + case 0x03: { /* set transfer mode */ + uint8_t val = s->nsector & 0x07; + uint16_t *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; + } + s->status = READY_STAT | SEEK_STAT; + ide_set_irq(s->bus); + break; + } + default: + goto abort_cmd; + } + break; + case WIN_FLUSH_CACHE: + case WIN_FLUSH_CACHE_EXT: + ide_flush_cache(s); + break; + case WIN_STANDBY: + case WIN_STANDBY2: + case WIN_STANDBYNOW1: + case WIN_STANDBYNOW2: + case WIN_IDLEIMMEDIATE: + case CFA_IDLEIMMEDIATE: + case WIN_SETIDLE1: + case WIN_SETIDLE2: + case WIN_SLEEPNOW1: + case WIN_SLEEPNOW2: + s->status = READY_STAT; + ide_set_irq(s->bus); + break; + case WIN_SEEK: + if(s->drive_kind == IDE_CD) + goto abort_cmd; + /* XXX: Check that seek is within bounds */ + s->status = READY_STAT | SEEK_STAT; + ide_set_irq(s->bus); + break; + /* ATAPI commands */ + case WIN_PIDENTIFY: + if (s->drive_kind == IDE_CD) { + ide_atapi_identify(s); + s->status = READY_STAT | SEEK_STAT; + ide_transfer_start(s, s->io_buffer, 512, ide_transfer_stop); + } else { + ide_abort_command(s); + } + ide_set_irq(s->bus); + break; + case WIN_DIAGNOSE: + 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. */ + else + s->status = READY_STAT | SEEK_STAT; + s->error = 0x01; /* Device 0 passed, Device 1 passed or not + * present. + */ + ide_set_irq(s->bus); + break; + case WIN_SRST: + if (s->drive_kind != IDE_CD) + goto abort_cmd; + ide_set_signature(s); + s->status = 0x00; /* NOTE: READY is _not_ set */ + s->error = 0x01; + break; + case WIN_PACKETCMD: + if (s->drive_kind != IDE_CD) + goto abort_cmd; + /* overlapping commands not supported */ + if (s->feature & 0x02) + goto abort_cmd; + 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); + break; + /* CF-ATA commands */ + case CFA_REQ_EXT_ERROR_CODE: + if (s->drive_kind != IDE_CFATA) + goto abort_cmd; + s->error = 0x09; /* miscellaneous error */ + s->status = READY_STAT | SEEK_STAT; + ide_set_irq(s->bus); + break; + case CFA_ERASE_SECTORS: + case CFA_WEAR_LEVEL: + if (s->drive_kind != IDE_CFATA) + goto abort_cmd; + if (val == CFA_WEAR_LEVEL) + s->nsector = 0; + if (val == CFA_ERASE_SECTORS) + s->media_changed = 1; + s->error = 0x00; + s->status = READY_STAT | SEEK_STAT; + ide_set_irq(s->bus); + break; + case CFA_TRANSLATE_SECTOR: + if (s->drive_kind != IDE_CFATA) + goto abort_cmd; + s->error = 0x00; + 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); + break; + case CFA_ACCESS_METADATA_STORAGE: + if (s->drive_kind != IDE_CFATA) + goto abort_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: + goto abort_cmd; + } + ide_transfer_start(s, s->io_buffer, 0x200, ide_transfer_stop); + s->status = 0x00; /* NOTE: READY is _not_ set */ + ide_set_irq(s->bus); + break; + case IBM_SENSE_CONDITION: + if (s->drive_kind != IDE_CFATA) + goto abort_cmd; + switch (s->feature) { + case 0x01: /* sense temperature in device */ + s->nsector = 0x50; /* +20 C */ + break; + default: + goto abort_cmd; + } + s->status = READY_STAT | SEEK_STAT; + ide_set_irq(s->bus); + break; + + case WIN_SMART: + if (s->drive_kind == IDE_CD) + goto abort_cmd; + 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; + s->status = READY_STAT | SEEK_STAT; + ide_set_irq(s->bus); + break; + case SMART_ENABLE: + s->smart_enabled = 1; + s->status = READY_STAT | SEEK_STAT; + ide_set_irq(s->bus); + break; + 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; + } + s->status = READY_STAT | SEEK_STAT; + ide_set_irq(s->bus); + break; + case SMART_STATUS: + if (!s->smart_errors) { + s->hcyl = 0xc2; + s->lcyl = 0x4f; + } else { + s->hcyl = 0x2c; + s->lcyl = 0xf4; + } + s->status = READY_STAT | SEEK_STAT; + ide_set_irq(s->bus); + break; + case SMART_READ_THRESH: + memset(s->io_buffer, 0, 0x200); + s->io_buffer[0] = 0x01; /* smart struct version */ + for (n=0; n<30; n++) { + if (smart_attributes[n][0] == 0) + break; + s->io_buffer[2+0+(n*12)] = smart_attributes[n][0]; + s->io_buffer[2+1+(n*12)] = smart_attributes[n][4]; + } + for (n=0; n<511; n++) /* checksum */ + 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); + break; + case SMART_READ_DATA: + memset(s->io_buffer, 0, 0x200); + s->io_buffer[0] = 0x01; /* smart struct version */ + for (n=0; n<30; n++) { + if (smart_attributes[n][0] == 0) + break; + s->io_buffer[2+0+(n*12)] = smart_attributes[n][0]; + s->io_buffer[2+1+(n*12)] = smart_attributes[n][1]; + s->io_buffer[2+3+(n*12)] = smart_attributes[n][2]; + s->io_buffer[2+4+(n*12)] = smart_attributes[n][3]; + } + 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); + break; + 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); + break; + 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 = 0; + 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 */ + s->status = READY_STAT | SEEK_STAT; + ide_set_irq(s->bus); + break; + default: + goto abort_cmd; + } + break; + default: + goto abort_cmd; + } + break; + default: + abort_cmd: + ide_abort_command(s); + ide_set_irq(s->bus); + break; + } +} + +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].bs && !bus->ifs[1].bs) || + (s != bus->ifs && !s->bs)) + ret = 0; + else if (!hob) + ret = s->error; + else + ret = s->hob_feature; + break; + case 2: + if (!bus->ifs[0].bs && !bus->ifs[1].bs) + ret = 0; + else if (!hob) + ret = s->nsector & 0xff; + else + ret = s->hob_nsector; + break; + case 3: + if (!bus->ifs[0].bs && !bus->ifs[1].bs) + ret = 0; + else if (!hob) + ret = s->sector; + else + ret = s->hob_sector; + break; + case 4: + if (!bus->ifs[0].bs && !bus->ifs[1].bs) + ret = 0; + else if (!hob) + ret = s->lcyl; + else + ret = s->hob_lcyl; + break; + case 5: + if (!bus->ifs[0].bs && !bus->ifs[1].bs) + ret = 0; + else if (!hob) + ret = s->hcyl; + else + ret = s->hob_hcyl; + break; + case 6: + if (!bus->ifs[0].bs && !bus->ifs[1].bs) + ret = 0; + else + ret = s->select; + break; + default: + case 7: + if ((!bus->ifs[0].bs && !bus->ifs[1].bs) || + (s != bus->ifs && !s->bs)) + 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].bs && !bus->ifs[1].bs) || + (s != bus->ifs && !s->bs)) + 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; +} + +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 */ + if (!(s->status & DRQ_STAT)) + return; + + p = s->data_ptr; + *(uint16_t *)p = le16_to_cpu(val); + p += 2; + s->data_ptr = p; + if (p >= s->data_end) + 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 */ + if (!(s->status & DRQ_STAT)) + return 0; + + p = s->data_ptr; + ret = cpu_to_le16(*(uint16_t *)p); + p += 2; + s->data_ptr = p; + if (p >= s->data_end) + 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 */ + if (!(s->status & DRQ_STAT)) + return; + + p = s->data_ptr; + *(uint32_t *)p = le32_to_cpu(val); + p += 4; + s->data_ptr = p; + if (p >= s->data_end) + 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 */ + if (!(s->status & DRQ_STAT)) + return 0; + + p = s->data_ptr; + ret = cpu_to_le32(*(uint32_t *)p); + p += 4; + s->data_ptr = p; + if (p >= s->data_end) + 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->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; + /* 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 + bdrv_aio_cancel(bus->dma->aiocb); + bus->dma->aiocb = NULL; + } + + /* reset dma provider too */ + bus->dma->ops->reset(bus->dma); +} + +int ide_init_drive(IDEState *s, BlockDriverState *bs, + const char *version, const char *serial) +{ + int cylinders, heads, secs; + uint64_t nb_sectors; + + s->bs = bs; + bdrv_get_geometry(bs, &nb_sectors); + bdrv_guess_geometry(bs, &cylinders, &heads, &secs); + if (cylinders < 1 || cylinders > 16383) { + error_report("cyls must be between 1 and 16383"); + return -1; + } + if (heads < 1 || heads > 16) { + error_report("heads must be between 1 and 16"); + return -1; + } + if (secs < 1 || secs > 63) { + error_report("secs must be between 1 and 63"); + return -1; + } + s->cylinders = cylinders; + s->heads = heads; + s->sectors = secs; + s->nb_sectors = nb_sectors; + /* 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 (bdrv_get_type_hint(bs) == BDRV_TYPE_CDROM) { + s->drive_kind = IDE_CD; + bdrv_set_change_cb(bs, cdrom_change_cb, s); + bs->buffer_alignment = 2048; + } else { + if (!bdrv_is_inserted(s->bs)) { + error_report("Device needs media, but drive is empty"); + return -1; + } + if (bdrv_is_read_only(bs)) { + error_report("Can't use a read-only drive"); + return -1; + } + } + if (serial) { + strncpy(s->drive_serial_str, serial, sizeof(s->drive_serial_str)); + } else { + snprintf(s->drive_serial_str, sizeof(s->drive_serial_str), + "QM%05d", s->drive_serial); + } + if (version) { + pstrcpy(s->version, sizeof(s->version), version); + } else { + pstrcpy(s->version, sizeof(s->version), QEMU_VERSION); + } + + ide_reset(s); + bdrv_set_removable(bs, s->drive_kind == IDE_CD); + 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 = qemu_memalign(2048, IDE_DMA_BUF_SECTORS*512 + 4); + s->io_buffer_total_len = IDE_DMA_BUF_SECTORS*512 + 4; + s->smart_selftest_data = qemu_blockalign(s->bs, 512); + s->sector_write_timer = qemu_new_timer(vm_clock, + ide_sector_write_timer_cb, s); +} + +static void ide_nop_start(IDEDMA *dma, IDEState *s, + BlockDriverCompletionFunc *cb) +{ +} + +static int ide_nop(IDEDMA *dma) +{ + return 0; +} + +static int ide_nop_int(IDEDMA *dma, int x) +{ + return 0; +} + +static void ide_nop_restart(void *opaque, int x, int y) +{ +} + +static const IDEDMAOps ide_dma_nop_ops = { + .start_dma = ide_nop_start, + .start_transfer = ide_nop, + .prepare_buf = ide_nop_int, + .rw_buf = ide_nop_int, + .set_unit = ide_nop_int, + .add_status = ide_nop_int, + .set_inactive = ide_nop, + .restart_cb = ide_nop_restart, + .reset = ide_nop, +}; + +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; +} + +/* TODO convert users to qdev and remove */ +void ide_init2_with_non_qdev_drives(IDEBus *bus, DriveInfo *hd0, + DriveInfo *hd1, qemu_irq irq) +{ + int i; + DriveInfo *dinfo; + + for(i = 0; i < 2; i++) { + dinfo = i == 0 ? hd0 : hd1; + ide_init1(bus, i); + if (dinfo) { + if (ide_init_drive(&bus->ifs[i], dinfo->bdrv, NULL, + *dinfo->serial ? dinfo->serial : NULL) < 0) { + error_report("Can't set up IDE drive %s", dinfo->id); + exit(1); + } + } else { + ide_reset(&bus->ifs[i]); + } + } + bus->irq = irq; + bus->dma = &ide_dma_nop; +} + +void ide_init_ioport(IDEBus *bus, int iobase, int iobase2) +{ + register_ioport_write(iobase, 8, 1, ide_ioport_write, bus); + register_ioport_read(iobase, 8, 1, ide_ioport_read, bus); + if (iobase2) { + register_ioport_read(iobase2, 1, 1, ide_status_read, bus); + register_ioport_write(iobase2, 1, 1, ide_cmd_write, bus); + } + + /* data ports */ + register_ioport_write(iobase, 2, 2, ide_data_writew, bus); + register_ioport_read(iobase, 2, 2, ide_data_readw, bus); + register_ioport_write(iobase, 4, 4, ide_data_writel, bus); + register_ioport_read(iobase, 4, 4, ide_data_readl, bus); +} + +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 (version_id < 3) { + if (s->sense_key == SENSE_UNIT_ATTENTION && + s->asc == ASC_MEDIUM_MAY_HAVE_CHANGED) { + s->cdrom_changed = 1; + } + } + 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; + + 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; +} + +const VMStateDescription vmstate_ide_drive_pio_state = { + .name = "ide_drive/pio_state", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .pre_save = ide_drive_pio_pre_save, + .post_load = ide_drive_pio_post_load, + .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, + .minimum_version_id_old = 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 = (VMStateSubsection []) { + { + .vmsd = &vmstate_ide_drive_pio_state, + .needed = ide_drive_pio_state_needed, + }, { + /* empty */ + } + } +}; + +const VMStateDescription vmstate_ide_bus = { + .name = "ide_bus", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField []) { + VMSTATE_UINT8(cmd, IDEBus), + VMSTATE_UINT8(unit, IDEBus), + VMSTATE_END_OF_LIST() + } +}; diff --git a/hw/ide/ich.c b/hw/ide/ich.c new file mode 100644 index 0000000..f242d7a --- /dev/null +++ b/hw/ide/ich.c @@ -0,0 +1,148 @@ +/* + * QEMU ICH Emulation + * + * Copyright (c) 2010 Sebastian Herbszt + * Copyright (c) 2010 Alexander Graf + * + * 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 . + * + * + * 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- SERR- + * 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 +#include +#include +#include +#include +#include "block.h" +#include "block_int.h" +#include "sysemu.h" +#include "dma.h" + +#include +#include + +static int pci_ich9_ahci_init(PCIDevice *dev) +{ + struct AHCIPCIState *d; + d = DO_UPCAST(struct AHCIPCIState, card, dev); + + pci_config_set_vendor_id(d->card.config, PCI_VENDOR_ID_INTEL); + pci_config_set_device_id(d->card.config, PCI_DEVICE_ID_INTEL_82801IR); + + pci_config_set_class(d->card.config, PCI_CLASS_STORAGE_SATA); + pci_config_set_revision(d->card.config, 0x02); + pci_config_set_prog_interface(d->card.config, AHCI_PROGMODE_MAJOR_REV_1); + + d->card.config[PCI_CACHE_LINE_SIZE] = 0x08; /* Cache line size */ + d->card.config[PCI_LATENCY_TIMER] = 0x00; /* Latency timer */ + pci_config_set_interrupt_pin(d->card.config, 1); + + /* XXX Software should program this register */ + d->card.config[0x90] = 1 << 6; /* Address Map Register - AHCI mode */ + + qemu_register_reset(ahci_reset, d); + + /* XXX BAR size should be 1k, but that breaks, so bump it to 4k for now */ + pci_register_bar(&d->card, 5, 0x1000, PCI_BASE_ADDRESS_SPACE_MEMORY, + ahci_pci_map); + + msi_init(dev, 0x50, 1, true, false); + + ahci_init(&d->ahci, &dev->qdev, 6); + d->ahci.irq = d->card.irq[0]; + + return 0; +} + +static int pci_ich9_uninit(PCIDevice *dev) +{ + struct AHCIPCIState *d; + d = DO_UPCAST(struct AHCIPCIState, card, dev); + + if (msi_enabled(dev)) { + msi_uninit(dev); + } + + qemu_unregister_reset(ahci_reset, d); + ahci_uninit(&d->ahci); + + return 0; +} + +static void pci_ich9_write_config(PCIDevice *pci, uint32_t addr, + uint32_t val, int len) +{ + pci_default_write_config(pci, addr, val, len); + msi_write_config(pci, addr, val, len); +} + +static PCIDeviceInfo ich_ahci_info[] = { + { + .qdev.name = "ich9-ahci", + .qdev.alias = "ahci", + .qdev.size = sizeof(AHCIPCIState), + .init = pci_ich9_ahci_init, + .exit = pci_ich9_uninit, + .config_write = pci_ich9_write_config, + },{ + /* end of list */ + } +}; + +static void ich_ahci_register(void) +{ + pci_qdev_register_many(ich_ahci_info); +} +device_init(ich_ahci_register); diff --git a/hw/ide/internal.h b/hw/ide/internal.h new file mode 100644 index 0000000..d533fb6 --- /dev/null +++ b/hw/ide/internal.h @@ -0,0 +1,571 @@ +#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 +#include "block_int.h" +#include "iorange.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 IDEDeviceInfo IDEDeviceInfo; +typedef struct IDEState IDEState; +typedef struct IDEDMA IDEDMA; +typedef struct IDEDMAOps IDEDMAOps; + +/* 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 + +/* ATA/ATAPI Commands pre T13 Spec */ +#define WIN_NOP 0x00 +/* + * 0x01->0x02 Reserved + */ +#define CFA_REQ_EXT_ERROR_CODE 0x03 /* CFA Request Extended Error Code */ +/* + * 0x04->0x07 Reserved + */ +#define WIN_SRST 0x08 /* ATAPI soft reset command */ +#define WIN_DEVICE_RESET 0x08 +/* + * 0x09->0x0F Reserved + */ +#define WIN_RECAL 0x10 +#define WIN_RESTORE WIN_RECAL +/* + * 0x10->0x1F Reserved + */ +#define WIN_READ 0x20 /* 28-Bit */ +#define WIN_READ_ONCE 0x21 /* 28-Bit without retries */ +#define WIN_READ_LONG 0x22 /* 28-Bit */ +#define WIN_READ_LONG_ONCE 0x23 /* 28-Bit without retries */ +#define WIN_READ_EXT 0x24 /* 48-Bit */ +#define WIN_READDMA_EXT 0x25 /* 48-Bit */ +#define WIN_READDMA_QUEUED_EXT 0x26 /* 48-Bit */ +#define WIN_READ_NATIVE_MAX_EXT 0x27 /* 48-Bit */ +/* + * 0x28 + */ +#define WIN_MULTREAD_EXT 0x29 /* 48-Bit */ +/* + * 0x2A->0x2F Reserved + */ +#define WIN_WRITE 0x30 /* 28-Bit */ +#define WIN_WRITE_ONCE 0x31 /* 28-Bit without retries */ +#define WIN_WRITE_LONG 0x32 /* 28-Bit */ +#define WIN_WRITE_LONG_ONCE 0x33 /* 28-Bit without retries */ +#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 */ +#define CFA_WRITE_SECT_WO_ERASE 0x38 /* CFA Write Sectors without erase */ +#define WIN_MULTWRITE_EXT 0x39 /* 48-Bit */ +/* + * 0x3A->0x3B Reserved + */ +#define WIN_WRITE_VERIFY 0x3C /* 28-Bit */ +/* + * 0x3D->0x3F Reserved + */ +#define WIN_VERIFY 0x40 /* 28-Bit - Read Verify Sectors */ +#define WIN_VERIFY_ONCE 0x41 /* 28-Bit - without retries */ +#define WIN_VERIFY_EXT 0x42 /* 48-Bit */ +/* + * 0x43->0x4F Reserved + */ +#define WIN_FORMAT 0x50 +/* + * 0x51->0x5F Reserved + */ +#define WIN_INIT 0x60 +/* + * 0x61->0x5F Reserved + */ +#define WIN_SEEK 0x70 /* 0x70-0x7F Reserved */ +#define CFA_TRANSLATE_SECTOR 0x87 /* CFA Translate Sector */ +#define WIN_DIAGNOSE 0x90 +#define WIN_SPECIFY 0x91 /* set drive geometry translation */ +#define WIN_DOWNLOAD_MICROCODE 0x92 +#define WIN_STANDBYNOW2 0x94 +#define CFA_IDLEIMMEDIATE 0x95 /* force drive to become "ready" */ +#define WIN_STANDBY2 0x96 +#define WIN_SETIDLE2 0x97 +#define WIN_CHECKPOWERMODE2 0x98 +#define WIN_SLEEPNOW2 0x99 +/* + * 0x9A VENDOR + */ +#define WIN_PACKETCMD 0xA0 /* Send a packet command. */ +#define WIN_PIDENTIFY 0xA1 /* identify ATAPI device */ +#define WIN_QUEUED_SERVICE 0xA2 +#define WIN_SMART 0xB0 /* self-monitoring and reporting */ +#define CFA_ACCESS_METADATA_STORAGE 0xB8 +#define CFA_ERASE_SECTORS 0xC0 /* microdrives implement as NOP */ +#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 */ +#define WIN_READDMA 0xC8 /* read sectors using DMA transfers */ +#define WIN_READDMA_ONCE 0xC9 /* 28-Bit - without retries */ +#define WIN_WRITEDMA 0xCA /* write sectors using DMA transfers */ +#define WIN_WRITEDMA_ONCE 0xCB /* 28-Bit - without retries */ +#define WIN_WRITEDMA_QUEUED 0xCC /* write sectors using Queued DMA transfers */ +#define CFA_WRITE_MULTI_WO_ERASE 0xCD /* CFA Write multiple without erase */ +#define WIN_GETMEDIASTATUS 0xDA +#define WIN_ACKMEDIACHANGE 0xDB /* ATA-1, ATA-2 vendor */ +#define WIN_POSTBOOT 0xDC +#define WIN_PREBOOT 0xDD +#define WIN_DOORLOCK 0xDE /* lock door on removable drives */ +#define WIN_DOORUNLOCK 0xDF /* unlock door on removable drives */ +#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 */ +#define WIN_WRITE_SAME 0xE9 /* read ata-2 to use */ + /* SET_FEATURES 0x22 or 0xDD */ +#define WIN_FLUSH_CACHE_EXT 0xEA /* 48-Bit */ +#define WIN_IDENTIFY 0xEC /* ask drive to identify itself */ +#define WIN_MEDIAEJECT 0xED +#define WIN_IDENTIFY_DMA 0xEE /* same as WIN_IDENTIFY, but DMA */ +#define WIN_SETFEATURES 0xEF /* set special drive features */ +#define EXABYTE_ENABLE_NEST 0xF0 +#define IBM_SENSE_CONDITION 0xF0 /* measure disk temperature */ +#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 */ +#define WIN_SECURITY_DISABLE 0xF6 +#define WIN_READ_NATIVE_MAX 0xF8 /* return the native maximum address */ +#define WIN_SET_MAX 0xF9 +#define DISABLE_SEAGATE 0xFB + +/* set to 1 set disable mult support */ +#define MAX_MULT_SECTORS 16 + +#define IDE_DMA_BUF_SECTORS 256 + +#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 + +/* Mode page codes for mode sense/set */ +#define GPMODE_R_W_ERROR_PAGE 0x01 +#define GPMODE_WRITE_PARMS_PAGE 0x05 +#define GPMODE_AUDIO_CTL_PAGE 0x0e +#define GPMODE_POWER_PAGE 0x1a +#define GPMODE_FAULT_FAIL_PAGE 0x1c +#define GPMODE_TO_PROTECT_PAGE 0x1d +#define GPMODE_CAPABILITIES_PAGE 0x2a +#define GPMODE_ALL_PAGES 0x3f +/* Not in Mt. Fuji, but in ATAPI 2.6 -- depricated now in favor + * of MODE_SENSE_POWER_PAGE */ +#define GPMODE_CDROM_PAGE 0x0d + +/* + * Based on values from but extending CD_MINS + * to the maximum common size allowed by the Orange's Book ATIP + * + * 90 and 99 min CDs are also available but using them as the + * upper limit reduces the effectiveness of the heuristic to + * detect DVDs burned to less than 25% of their maximum capacity + */ + +/* Some generally useful CD-ROM information */ +#define CD_MINS 80 /* max. minutes per CD */ +#define CD_SECS 60 /* seconds per minute */ +#define CD_FRAMES 75 /* frames per second */ +#define CD_FRAMESIZE 2048 /* bytes per frame, "cooked" mode */ +#define CD_MAX_BYTES (CD_MINS * CD_SECS * CD_FRAMES * CD_FRAMESIZE) +#define CD_MAX_SECTORS (CD_MAX_BYTES / 512) + +/* + * The MMC values are not IDE specific and might need to be moved + * to a common header if they are also needed for the SCSI emulation + */ + +/* Profile list from MMC-6 revision 1 table 91 */ +#define MMC_PROFILE_NONE 0x0000 +#define MMC_PROFILE_CD_ROM 0x0008 +#define MMC_PROFILE_CD_R 0x0009 +#define MMC_PROFILE_CD_RW 0x000A +#define MMC_PROFILE_DVD_ROM 0x0010 +#define MMC_PROFILE_DVD_R_SR 0x0011 +#define MMC_PROFILE_DVD_RAM 0x0012 +#define MMC_PROFILE_DVD_RW_RO 0x0013 +#define MMC_PROFILE_DVD_RW_SR 0x0014 +#define MMC_PROFILE_DVD_R_DL_SR 0x0015 +#define MMC_PROFILE_DVD_R_DL_JR 0x0016 +#define MMC_PROFILE_DVD_RW_DL 0x0017 +#define MMC_PROFILE_DVD_DDR 0x0018 +#define MMC_PROFILE_DVD_PLUS_RW 0x001A +#define MMC_PROFILE_DVD_PLUS_R 0x001B +#define MMC_PROFILE_DVD_PLUS_RW_DL 0x002A +#define MMC_PROFILE_DVD_PLUS_R_DL 0x002B +#define MMC_PROFILE_BD_ROM 0x0040 +#define MMC_PROFILE_BD_R_SRM 0x0041 +#define MMC_PROFILE_BD_R_RRM 0x0042 +#define MMC_PROFILE_BD_RE 0x0043 +#define MMC_PROFILE_HDDVD_ROM 0x0050 +#define MMC_PROFILE_HDDVD_R 0x0051 +#define MMC_PROFILE_HDDVD_RAM 0x0052 +#define MMC_PROFILE_HDDVD_RW 0x0053 +#define MMC_PROFILE_HDDVD_R_DL 0x0058 +#define MMC_PROFILE_HDDVD_RW_DL 0x005A +#define MMC_PROFILE_INVALID 0xFFFF + +#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_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_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 SENSE_NONE 0 +#define SENSE_NOT_READY 2 +#define SENSE_ILLEGAL_REQUEST 5 +#define SENSE_UNIT_ATTENTION 6 + +#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 *, BlockDriverCompletionFunc *); +typedef int DMAFunc(IDEDMA *); +typedef int DMAIntFunc(IDEDMA *, int); +typedef void DMARestartFunc(void *, int, int); + +/* NOTE: IDEState represents in fact one drive */ +struct IDEState { + IDEBus *bus; + uint8_t unit; + /* ide config */ + IDEDriveKind drive_kind; + int cylinders, heads, sectors; + int64_t nb_sectors; + int mult_sectors; + int identify_set; + uint8_t identify_data[512]; + int drive_serial; + char drive_serial_str[21]; + /* 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; + BlockDriverState *bs; + char version[9]; + /* ATAPI specific */ + uint8_t sense_key; + uint8_t asc; + uint8_t cdrom_changed; + int packet_transfer_size; + int elementary_transfer_size; + int io_buffer_index; + int lba; + int cd_sector_size; + int atapi_dma; /* true if dma is requested for the packet cmd */ + /* ATA DMA state */ + int 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; + int cur_io_buffer_offset; + int 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; + int is_read; + /* 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; + DMAFunc *start_transfer; + DMAIntFunc *prepare_buf; + DMAIntFunc *rw_buf; + DMAIntFunc *set_unit; + DMAIntFunc *add_status; + DMAFunc *set_inactive; + DMARestartFunc *restart_cb; + DMAFunc *reset; +}; + +struct IDEDMA { + const struct IDEDMAOps *ops; + struct iovec iov; + QEMUIOVector qiov; + BlockDriverAIOCB *aiocb; +}; + +struct IDEBus { + BusState qbus; + IDEDevice *master; + IDEDevice *slave; + IDEState ifs[2]; + int bus_id; + IDEDMA *dma; + uint8_t unit; + uint8_t cmd; + qemu_irq irq; +}; + +struct IDEDevice { + DeviceState qdev; + uint32_t unit; + BlockConf conf; + char *version; + char *serial; +}; + +typedef int (*ide_qdev_initfn)(IDEDevice *dev); +struct IDEDeviceInfo { + DeviceInfo qdev; + ide_qdev_initfn init; +}; + +#define BM_STATUS_DMAING 0x01 +#define BM_STATUS_ERROR 0x02 +#define BM_STATUS_INT 0x04 +#define BM_STATUS_DMA_RETRY 0x08 +#define BM_STATUS_PIO_RETRY 0x10 +#define BM_STATUS_RETRY_READ 0x20 +#define BM_STATUS_RETRY_FLUSH 0x40 + +#define BM_CMD_START 0x01 +#define BM_CMD_READ 0x08 + +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) + +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_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_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, BlockDriverState *bs, + const char *version, const char *serial); +void ide_init2(IDEBus *bus, qemu_irq irq); +void ide_init2_with_non_qdev_drives(IDEBus *bus, DriveInfo *hd0, + DriveInfo *hd1, qemu_irq irq); +void ide_init_ioport(IDEBus *bus, int iobase, int iobase2); + +void ide_exec_cmd(IDEBus *bus, uint32_t val); +void ide_dma_cb(void *opaque, int ret); +void ide_sector_write(IDEState *s); +void ide_sector_read(IDEState *s); +void ide_flush_cache(IDEState *s); + +/* hw/ide/qdev.c */ +void ide_bus_new(IDEBus *idebus, DeviceState *dev, int bus_id); +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 0000000..8c59c5a --- /dev/null +++ b/hw/ide/isa.c @@ -0,0 +1,120 @@ +/* + * 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 +#include +#include +#include "block.h" +#include "block_int.h" +#include "sysemu.h" +#include "dma.h" + +#include + +/***********************************************************/ +/* ISA IDE definitions */ + +typedef struct ISAIDEState { + ISADevice dev; + IDEBus bus; + uint32_t iobase; + uint32_t iobase2; + uint32_t isairq; + qemu_irq irq; +} ISAIDEState; + +static void isa_ide_reset(DeviceState *d) +{ + ISAIDEState *s = container_of(d, ISAIDEState, dev.qdev); + + ide_bus_reset(&s->bus); +} + +static const VMStateDescription vmstate_ide_isa = { + .name = "isa-ide", + .version_id = 3, + .minimum_version_id = 0, + .minimum_version_id_old = 0, + .fields = (VMStateField []) { + VMSTATE_IDE_BUS(bus, ISAIDEState), + VMSTATE_IDE_DRIVES(bus.ifs, ISAIDEState), + VMSTATE_END_OF_LIST() + } +}; + +static int isa_ide_initfn(ISADevice *dev) +{ + ISAIDEState *s = DO_UPCAST(ISAIDEState, dev, dev); + + ide_bus_new(&s->bus, &s->dev.qdev, 0); + ide_init_ioport(&s->bus, s->iobase, s->iobase2); + isa_init_irq(dev, &s->irq, s->isairq); + isa_init_ioport_range(dev, s->iobase, 8); + isa_init_ioport(dev, s->iobase2); + ide_init2(&s->bus, s->irq); + vmstate_register(&dev->qdev, 0, &vmstate_ide_isa, s); + return 0; +}; + +ISADevice *isa_ide_init(int iobase, int iobase2, int isairq, + DriveInfo *hd0, DriveInfo *hd1) +{ + ISADevice *dev; + ISAIDEState *s; + + dev = isa_create("isa-ide"); + qdev_prop_set_uint32(&dev->qdev, "iobase", iobase); + qdev_prop_set_uint32(&dev->qdev, "iobase2", iobase2); + qdev_prop_set_uint32(&dev->qdev, "irq", isairq); + if (qdev_init(&dev->qdev) < 0) + return NULL; + + s = DO_UPCAST(ISAIDEState, dev, dev); + if (hd0) + ide_create_drive(&s->bus, 0, hd0); + if (hd1) + ide_create_drive(&s->bus, 1, hd1); + return dev; +} + +static ISADeviceInfo isa_ide_info = { + .qdev.name = "isa-ide", + .qdev.fw_name = "ide", + .qdev.size = sizeof(ISAIDEState), + .init = isa_ide_initfn, + .qdev.reset = isa_ide_reset, + .qdev.props = (Property[]) { + DEFINE_PROP_HEX32("iobase", ISAIDEState, iobase, 0x1f0), + DEFINE_PROP_HEX32("iobase2", ISAIDEState, iobase2, 0x3f6), + DEFINE_PROP_UINT32("irq", ISAIDEState, isairq, 14), + DEFINE_PROP_END_OF_LIST(), + }, +}; + +static void isa_ide_register_devices(void) +{ + isa_qdev_register(&isa_ide_info); +} + +device_init(isa_ide_register_devices) diff --git a/hw/ide/macio.c b/hw/ide/macio.c new file mode 100644 index 0000000..c1b4caa --- /dev/null +++ b/hw/ide/macio.c @@ -0,0 +1,329 @@ +/* + * 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 +#include +#include +#include "block.h" +#include "block_int.h" +#include "sysemu.h" +#include "dma.h" + +#include + +/***********************************************************/ +/* MacIO based PowerPC IDE */ + +typedef struct MACIOIDEState { + IDEBus bus; + BlockDriverAIOCB *aiocb; +} MACIOIDEState; + +#define MACIO_PAGE_SIZE 4096 + +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); + + if (ret < 0) { + m->aiocb = NULL; + qemu_sglist_destroy(&s->sg); + ide_atapi_io_error(s, ret); + io->dma_end(opaque); + return; + } + + if (s->io_buffer_size > 0) { + m->aiocb = NULL; + qemu_sglist_destroy(&s->sg); + + s->packet_transfer_size -= s->io_buffer_size; + + s->io_buffer_index += s->io_buffer_size; + s->lba += s->io_buffer_index >> 11; + s->io_buffer_index &= 0x7ff; + } + + if (s->packet_transfer_size <= 0) + ide_atapi_cmd_ok(s); + + if (io->len == 0) { + io->dma_end(opaque); + return; + } + + /* launch next transfer */ + + s->io_buffer_size = io->len; + + qemu_sglist_init(&s->sg, io->len / MACIO_PAGE_SIZE + 1); + qemu_sglist_add(&s->sg, io->addr, io->len); + io->addr += io->len; + io->len = 0; + + m->aiocb = dma_bdrv_read(s->bs, &s->sg, + (int64_t)(s->lba << 2) + (s->io_buffer_index >> 9), + pmac_ide_atapi_transfer_cb, io); + if (!m->aiocb) { + qemu_sglist_destroy(&s->sg); + /* Note: media not present is the most likely case */ + ide_atapi_cmd_error(s, SENSE_NOT_READY, + ASC_MEDIUM_NOT_PRESENT); + 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); + int n; + int64_t sector_num; + + if (ret < 0) { + m->aiocb = NULL; + qemu_sglist_destroy(&s->sg); + ide_dma_error(s); + io->dma_end(io); + return; + } + + sector_num = ide_get_sector(s); + if (s->io_buffer_size > 0) { + m->aiocb = NULL; + qemu_sglist_destroy(&s->sg); + n = (s->io_buffer_size + 0x1ff) >> 9; + 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); + } + + /* end of DMA ? */ + + if (io->len == 0) { + io->dma_end(io); + return; + } + + /* launch next transfer */ + + s->io_buffer_index = 0; + s->io_buffer_size = io->len; + + qemu_sglist_init(&s->sg, io->len / MACIO_PAGE_SIZE + 1); + qemu_sglist_add(&s->sg, io->addr, io->len); + io->addr += io->len; + io->len = 0; + + if (s->is_read) + m->aiocb = dma_bdrv_read(s->bs, &s->sg, sector_num, + pmac_ide_transfer_cb, io); + else + m->aiocb = dma_bdrv_write(s->bs, &s->sg, sector_num, + pmac_ide_transfer_cb, io); + if (!m->aiocb) + pmac_ide_transfer_cb(io, -1); +} + +static void pmac_ide_transfer(DBDMA_io *io) +{ + MACIOIDEState *m = io->opaque; + IDEState *s = idebus_active_if(&m->bus); + + s->io_buffer_size = 0; + if (s->drive_kind == IDE_CD) { + pmac_ide_atapi_transfer_cb(io, 0); + return; + } + + pmac_ide_transfer_cb(io, 0); +} + +static void pmac_ide_flush(DBDMA_io *io) +{ + MACIOIDEState *m = io->opaque; + + if (m->aiocb) + qemu_aio_flush(); +} + +/* PowerMac IDE memory IO */ +static void pmac_ide_writeb (void *opaque, + target_phys_addr_t 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,target_phys_addr_t 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, + target_phys_addr_t 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,target_phys_addr_t 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, + target_phys_addr_t 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,target_phys_addr_t 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 CPUWriteMemoryFunc * const pmac_ide_write[] = { + pmac_ide_writeb, + pmac_ide_writew, + pmac_ide_writel, +}; + +static CPUReadMemoryFunc * const pmac_ide_read[] = { + pmac_ide_readb, + pmac_ide_readw, + pmac_ide_readl, +}; + +static const VMStateDescription vmstate_pmac = { + .name = "ide", + .version_id = 3, + .minimum_version_id = 0, + .minimum_version_id_old = 0, + .fields = (VMStateField []) { + VMSTATE_IDE_BUS(bus, MACIOIDEState), + VMSTATE_IDE_DRIVES(bus.ifs, MACIOIDEState), + VMSTATE_END_OF_LIST() + } +}; + +static void pmac_ide_reset(void *opaque) +{ + MACIOIDEState *d = opaque; + + ide_bus_reset(&d->bus); +} + +/* hd_table must contain 4 block drivers */ +/* PowerMac uses memory mapped registers, not I/O. Return the memory + I/O index to access the ide. */ +int pmac_ide_init (DriveInfo **hd_table, qemu_irq irq, + void *dbdma, int channel, qemu_irq dma_irq) +{ + MACIOIDEState *d; + int pmac_ide_memory; + + d = qemu_mallocz(sizeof(MACIOIDEState)); + ide_init2_with_non_qdev_drives(&d->bus, hd_table[0], hd_table[1], irq); + + if (dbdma) + DBDMA_register_channel(dbdma, channel, dma_irq, pmac_ide_transfer, pmac_ide_flush, d); + + pmac_ide_memory = cpu_register_io_memory(pmac_ide_read, + pmac_ide_write, d, + DEVICE_NATIVE_ENDIAN); + vmstate_register(NULL, 0, &vmstate_pmac, d); + qemu_register_reset(pmac_ide_reset, d); + + return pmac_ide_memory; +} diff --git a/hw/ide/microdrive.c b/hw/ide/microdrive.c new file mode 100644 index 0000000..2ceeb87 --- /dev/null +++ b/hw/ide/microdrive.c @@ -0,0 +1,551 @@ +/* + * 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 +#include +#include +#include "block.h" +#include "block_int.h" +#include "sysemu.h" +#include "dma.h" + +#include + +/***********************************************************/ +/* CF-ATA Microdrive */ + +#define METADATA_SIZE 0x20 + +/* DSCM-1XXXX Microdrive hard disk with CF+ II / PCMCIA interface. */ +typedef struct { + IDEBus bus; + PCMCIACardState card; + 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) +{ + if (!s->card.slot) + return; + + qemu_set_irq(s->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(MicroDriveState *s) +{ + 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(void *opaque, uint32_t at) +{ + MicroDriveState *s = opaque; + if (at < s->attr_base) { + if (at < s->card.cis_len) + return s->card.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(void *opaque, uint32_t at, uint8_t value) +{ + MicroDriveState *s = opaque; + at -= s->attr_base; + + switch (at) { + case 0x00: /* Configuration Option Register */ + s->opt = value & 0xcf; + if (value & OPT_SRESET) + md_reset(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(void *opaque, uint32_t at) +{ + MicroDriveState *s = opaque; + 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->bs) + 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(void *opaque, uint32_t at, uint16_t value) +{ + MicroDriveState *s = opaque; + 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) + md_reset(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, + .minimum_version_id_old = 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 */ +}; + +static int dscm1xxxx_attach(void *opaque) +{ + MicroDriveState *md = opaque; + md->card.attr_read = md_attr_read; + md->card.attr_write = md_attr_write; + md->card.common_read = md_common_read; + md->card.common_write = md_common_write; + md->card.io_read = md_common_read; + md->card.io_write = md_common_write; + + md->attr_base = md->card.cis[0x74] | (md->card.cis[0x76] << 8); + md->io_base = 0x0; + + md_reset(md); + md_interrupt_update(md); + + md->card.slot->card_string = "DSCM-1xxxx Hitachi Microdrive"; + return 0; +} + +static int dscm1xxxx_detach(void *opaque) +{ + MicroDriveState *md = opaque; + md_reset(md); + return 0; +} + +PCMCIACardState *dscm1xxxx_init(DriveInfo *bdrv) +{ + MicroDriveState *md = (MicroDriveState *) qemu_mallocz(sizeof(MicroDriveState)); + md->card.state = md; + md->card.attach = dscm1xxxx_attach; + md->card.detach = dscm1xxxx_detach; + md->card.cis = dscm1xxxx_cis; + md->card.cis_len = sizeof(dscm1xxxx_cis); + + ide_init2_with_non_qdev_drives(&md->bus, bdrv, NULL, + qemu_allocate_irqs(md_set_irq, md, 1)[0]); + md->bus.ifs[0].drive_kind = IDE_CFATA; + md->bus.ifs[0].mdata_size = METADATA_SIZE; + md->bus.ifs[0].mdata_storage = (uint8_t *) qemu_mallocz(METADATA_SIZE); + + vmstate_register(NULL, -1, &vmstate_microdrive, md); + + return &md->card; +} diff --git a/hw/ide/mmio.c b/hw/ide/mmio.c new file mode 100644 index 0000000..82b24b6 --- /dev/null +++ b/hw/ide/mmio.c @@ -0,0 +1,141 @@ +/* + * 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 +#include "block.h" +#include "block_int.h" +#include "sysemu.h" +#include "dma.h" + +#include + +/***********************************************************/ +/* 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. + */ + +typedef struct { + IDEBus bus; + int shift; +} MMIOState; + +static void mmio_ide_reset(void *opaque) +{ + MMIOState *s = opaque; + + ide_bus_reset(&s->bus); +} + +static uint32_t mmio_ide_read (void *opaque, target_phys_addr_t addr) +{ + 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, target_phys_addr_t addr, + uint32_t val) +{ + 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 CPUReadMemoryFunc * const mmio_ide_reads[] = { + mmio_ide_read, + mmio_ide_read, + mmio_ide_read, +}; + +static CPUWriteMemoryFunc * const mmio_ide_writes[] = { + mmio_ide_write, + mmio_ide_write, + mmio_ide_write, +}; + +static uint32_t mmio_ide_status_read (void *opaque, target_phys_addr_t addr) +{ + MMIOState *s= opaque; + return ide_status_read(&s->bus, 0); +} + +static void mmio_ide_cmd_write (void *opaque, target_phys_addr_t addr, + uint32_t val) +{ + MMIOState *s = opaque; + ide_cmd_write(&s->bus, 0, val); +} + +static CPUReadMemoryFunc * const mmio_ide_status[] = { + mmio_ide_status_read, + mmio_ide_status_read, + mmio_ide_status_read, +}; + +static CPUWriteMemoryFunc * const mmio_ide_cmd[] = { + mmio_ide_cmd_write, + mmio_ide_cmd_write, + mmio_ide_cmd_write, +}; + +static const VMStateDescription vmstate_ide_mmio = { + .name = "mmio-ide", + .version_id = 3, + .minimum_version_id = 0, + .minimum_version_id_old = 0, + .fields = (VMStateField []) { + VMSTATE_IDE_BUS(bus, MMIOState), + VMSTATE_IDE_DRIVES(bus.ifs, MMIOState), + VMSTATE_END_OF_LIST() + } +}; + +void mmio_ide_init (target_phys_addr_t membase, target_phys_addr_t membase2, + qemu_irq irq, int shift, + DriveInfo *hd0, DriveInfo *hd1) +{ + MMIOState *s = qemu_mallocz(sizeof(MMIOState)); + int mem1, mem2; + + ide_init2_with_non_qdev_drives(&s->bus, hd0, hd1, irq); + + s->shift = shift; + + mem1 = cpu_register_io_memory(mmio_ide_reads, mmio_ide_writes, s, + DEVICE_NATIVE_ENDIAN); + mem2 = cpu_register_io_memory(mmio_ide_status, mmio_ide_cmd, s, + DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(membase, 16 << shift, mem1); + cpu_register_physical_memory(membase2, 2 << shift, mem2); + vmstate_register(NULL, 0, &vmstate_ide_mmio, s); + qemu_register_reset(mmio_ide_reset, s); +} + diff --git a/hw/ide/pci.c b/hw/ide/pci.c new file mode 100644 index 0000000..35168cb --- /dev/null +++ b/hw/ide/pci.c @@ -0,0 +1,467 @@ +/* + * 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 +#include +#include +#include +#include "block.h" +#include "block_int.h" +#include "sysemu.h" +#include "dma.h" + +#include + +#define BMDMA_PAGE_SIZE 4096 + +static void bmdma_start_dma(IDEDMA *dma, IDEState *s, + BlockDriverCompletionFunc *dma_cb) +{ + BMDMAState *bm = DO_UPCAST(BMDMAState, dma, dma); + + bm->unit = s->unit; + bm->dma_cb = dma_cb; + bm->cur_prd_last = 0; + bm->cur_prd_addr = 0; + bm->cur_prd_len = 0; + bm->sector_num = ide_get_sector(s); + bm->nsector = s->nsector; + + if (bm->status & BM_STATUS_DMAING) { + bm->dma_cb(bmdma_active_if(bm), 0); + } +} + +/* return 0 if buffer completed */ +static int bmdma_prepare_buf(IDEDMA *dma, int is_write) +{ + BMDMAState *bm = DO_UPCAST(BMDMAState, dma, dma); + IDEState *s = bmdma_active_if(bm); + struct { + uint32_t addr; + uint32_t size; + } prd; + int l, len; + + qemu_sglist_init(&s->sg, 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->io_buffer_size != 0; + cpu_physical_memory_read(bm->cur_addr, (uint8_t *)&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) { + qemu_sglist_add(&s->sg, bm->cur_prd_addr, l); + bm->cur_prd_addr += l; + bm->cur_prd_len -= l; + s->io_buffer_size += l; + } + } + 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); + 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; + cpu_physical_memory_read(bm->cur_addr, (uint8_t *)&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) { + cpu_physical_memory_write(bm->cur_prd_addr, + s->io_buffer + s->io_buffer_index, l); + } else { + cpu_physical_memory_read(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 int bmdma_set_unit(IDEDMA *dma, int unit) +{ + BMDMAState *bm = DO_UPCAST(BMDMAState, dma, dma); + bm->unit = unit; + + return 0; +} + +static int bmdma_add_status(IDEDMA *dma, int status) +{ + BMDMAState *bm = DO_UPCAST(BMDMAState, dma, dma); + bm->status |= status; + + return 0; +} + +static int bmdma_set_inactive(IDEDMA *dma) +{ + BMDMAState *bm = DO_UPCAST(BMDMAState, dma, dma); + + bm->status &= ~BM_STATUS_DMAING; + bm->dma_cb = NULL; + bm->unit = -1; + + return 0; +} + +static void bmdma_restart_dma(BMDMAState *bm, int is_read) +{ + IDEState *s = bmdma_active_if(bm); + + ide_set_sector(s, bm->sector_num); + s->io_buffer_index = 0; + s->io_buffer_size = 0; + s->nsector = bm->nsector; + s->is_read = is_read; + bm->cur_addr = bm->addr; + bm->dma_cb = ide_dma_cb; + bmdma_start_dma(&bm->dma, s, bm->dma_cb); +} + +static void bmdma_restart_bh(void *opaque) +{ + BMDMAState *bm = opaque; + int is_read; + + qemu_bh_delete(bm->bh); + bm->bh = NULL; + + is_read = !!(bm->status & BM_STATUS_RETRY_READ); + + if (bm->status & BM_STATUS_DMA_RETRY) { + bm->status &= ~(BM_STATUS_DMA_RETRY | BM_STATUS_RETRY_READ); + bmdma_restart_dma(bm, is_read); + } else if (bm->status & BM_STATUS_PIO_RETRY) { + bm->status &= ~(BM_STATUS_PIO_RETRY | BM_STATUS_RETRY_READ); + if (is_read) { + ide_sector_read(bmdma_active_if(bm)); + } else { + ide_sector_write(bmdma_active_if(bm)); + } + } else if (bm->status & BM_STATUS_RETRY_FLUSH) { + ide_flush_cache(bmdma_active_if(bm)); + } +} + +static void bmdma_restart_cb(void *opaque, int running, int reason) +{ + IDEDMA *dma = opaque; + BMDMAState *bm = DO_UPCAST(BMDMAState, dma, dma); + + if (!running) + return; + + if (!bm->bh) { + bm->bh = qemu_bh_new(bmdma_restart_bh, &bm->dma); + qemu_bh_schedule(bm->bh); + } +} + +static void bmdma_cancel(BMDMAState *bm) +{ + if (bm->status & BM_STATUS_DMAING) { + /* cancel DMA request */ + bmdma_set_inactive(&bm->dma); + } +} + +static int 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; + bm->sector_num = 0; + bm->nsector = 0; + + return 0; +} + +static int bmdma_start_transfer(IDEDMA *dma) +{ + return 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(void *opaque, uint32_t addr, uint32_t val) +{ + BMDMAState *bm = opaque; +#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) { + qemu_aio_flush(); +#ifdef DEBUG_IDE + if (bm->bus->dma->aiocb) + printf("ide_dma_cancel: aiocb still pending"); + if (bm->status & BM_STATUS_DMAING) + printf("ide_dma_cancel: BM_STATUS_DMAING still pending"); +#endif + } + } 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 void bmdma_addr_read(IORange *ioport, uint64_t addr, + unsigned width, uint64_t *data) +{ + BMDMAState *bm = container_of(ioport, BMDMAState, addr_ioport); + uint32_t mask = (1ULL << (width * 8)) - 1; + + *data = (bm->addr >> (addr * 8)) & mask; +#ifdef DEBUG_IDE + printf("%s: 0x%08x\n", __func__, (unsigned)*data); +#endif +} + +static void bmdma_addr_write(IORange *ioport, uint64_t addr, + unsigned width, uint64_t data) +{ + BMDMAState *bm = container_of(ioport, BMDMAState, addr_ioport); + 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; +} + +const IORangeOps bmdma_addr_ioport_ops = { + .read = bmdma_addr_read, + .write = bmdma_addr_write, +}; + +static bool ide_bmdma_current_needed(void *opaque) +{ + BMDMAState *bm = opaque; + + return (bm->cur_prd_len != 0); +} + +static const VMStateDescription vmstate_bmdma_current = { + .name = "ide bmdma_current", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .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 = { + .name = "ide bmdma", + .version_id = 3, + .minimum_version_id = 0, + .minimum_version_id_old = 0, + .fields = (VMStateField []) { + VMSTATE_UINT8(cmd, BMDMAState), + VMSTATE_UINT8(status, BMDMAState), + VMSTATE_UINT32(addr, BMDMAState), + VMSTATE_INT64(sector_num, BMDMAState), + VMSTATE_UINT32(nsector, BMDMAState), + VMSTATE_UINT8(unit, BMDMAState), + VMSTATE_END_OF_LIST() + }, + .subsections = (VMStateSubsection []) { + { + .vmsd = &vmstate_bmdma_current, + .needed = ide_bmdma_current_needed, + }, { + /* empty */ + } + } +}; + +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].unit &= 1; + } + return 0; +} + +const VMStateDescription vmstate_ide_pci = { + .name = "ide", + .version_id = 3, + .minimum_version_id = 0, + .minimum_version_id_old = 0, + .post_load = ide_pci_post_load, + .fields = (VMStateField []) { + VMSTATE_PCI_DEVICE(dev, 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 = DO_UPCAST(PCIIDEState, dev, 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, + .start_transfer = bmdma_start_transfer, + .prepare_buf = bmdma_prepare_buf, + .rw_buf = bmdma_rw_buf, + .set_unit = bmdma_set_unit, + .add_status = bmdma_add_status, + .set_inactive = bmdma_set_inactive, + .restart_cb = bmdma_restart_cb, + .reset = bmdma_reset, +}; + +void bmdma_init(IDEBus *bus, BMDMAState *bm) +{ + qemu_irq *irq; + + if (bus->dma == &bm->dma) { + return; + } + + bm->dma.ops = &bmdma_ops; + bus->dma = &bm->dma; + bm->irq = bus->irq; + irq = qemu_allocate_irqs(bmdma_irq, bm, 1); + bus->irq = *irq; +} diff --git a/hw/ide/pci.h b/hw/ide/pci.h new file mode 100644 index 0000000..cd72cba --- /dev/null +++ b/hw/ide/pci.h @@ -0,0 +1,48 @@ +#ifndef HW_IDE_PCI_H +#define HW_IDE_PCI_H + +#include + +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; + uint8_t unit; + BlockDriverCompletionFunc *dma_cb; + int64_t sector_num; + uint32_t nsector; + IORange addr_ioport; + QEMUBH *bh; + qemu_irq irq; +} BMDMAState; + +typedef struct PCIIDEState { + PCIDevice dev; + IDEBus bus[2]; + BMDMAState bmdma[2]; + uint32_t secondary; /* used only for cmd646 */ +} PCIIDEState; + + +static inline IDEState *bmdma_active_if(BMDMAState *bmdma) +{ + assert(bmdma->unit != (uint8_t)-1); + return bmdma->bus->ifs + bmdma->unit; +} + + +void bmdma_init(IDEBus *bus, BMDMAState *bm); +void bmdma_cmd_writeb(void *opaque, uint32_t addr, uint32_t val); +extern const IORangeOps 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 0000000..c349644 --- /dev/null +++ b/hw/ide/piix.c @@ -0,0 +1,214 @@ +/* + * 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 +#include +#include +#include +#include "block.h" +#include "block_int.h" +#include "sysemu.h" +#include "dma.h" + +#include + +static uint32_t bmdma_readb(void *opaque, uint32_t addr) +{ + BMDMAState *bm = opaque; + uint32_t val; + + 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_writeb(void *opaque, uint32_t addr, uint32_t val) +{ + BMDMAState *bm = opaque; +#ifdef DEBUG_IDE + printf("bmdma: writeb 0x%02x : 0x%02x\n", addr, val); +#endif + switch(addr & 3) { + case 2: + bm->status = (val & 0x60) | (bm->status & 1) | (bm->status & ~val & 0x06); + break; + } +} + +static void bmdma_map(PCIDevice *pci_dev, int region_num, + pcibus_t addr, pcibus_t size, int type) +{ + PCIIDEState *d = DO_UPCAST(PCIIDEState, dev, pci_dev); + int i; + + for(i = 0;i < 2; i++) { + BMDMAState *bm = &d->bmdma[i]; + + register_ioport_write(addr, 1, 1, bmdma_cmd_writeb, bm); + + register_ioport_write(addr + 1, 3, 1, bmdma_writeb, bm); + register_ioport_read(addr, 4, 1, bmdma_readb, bm); + + iorange_init(&bm->addr_ioport, &bmdma_addr_ioport_ops, addr + 4, 4); + ioport_register(&bm->addr_ioport); + addr += 8; + } +} + +static void piix3_reset(void *opaque) +{ + PCIIDEState *d = opaque; + uint8_t *pci_conf = d->dev.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) { + int i; + struct { + int iobase; + int iobase2; + int isairq; + } port_info[] = { + {0x1f0, 0x3f6, 14}, + {0x170, 0x376, 15}, + }; + + for (i = 0; i < 2; i++) { + ide_bus_new(&d->bus[i], &d->dev.qdev, i); + ide_init_ioport(&d->bus[i], port_info[i].iobase, port_info[i].iobase2); + ide_init2(&d->bus[i], isa_get_irq(port_info[i].isairq)); + + bmdma_init(&d->bus[i], &d->bmdma[i]); + d->bmdma[i].bus = &d->bus[i]; + qemu_add_vm_change_state_handler(d->bus[i].dma->ops->restart_cb, + &d->bmdma[i].dma); + } +} + +static int pci_piix_ide_initfn(PCIIDEState *d) +{ + uint8_t *pci_conf = d->dev.config; + + pci_conf[PCI_CLASS_PROG] = 0x80; // legacy ATA mode + pci_config_set_class(pci_conf, PCI_CLASS_STORAGE_IDE); + + qemu_register_reset(piix3_reset, d); + + pci_register_bar(&d->dev, 4, 0x10, PCI_BASE_ADDRESS_SPACE_IO, bmdma_map); + + vmstate_register(&d->dev.qdev, 0, &vmstate_ide_pci, d); + + pci_piix_init_ports(d); + + return 0; +} + +static int pci_piix3_ide_initfn(PCIDevice *dev) +{ + PCIIDEState *d = DO_UPCAST(PCIIDEState, dev, dev); + + pci_config_set_vendor_id(d->dev.config, PCI_VENDOR_ID_INTEL); + pci_config_set_device_id(d->dev.config, PCI_DEVICE_ID_INTEL_82371SB_1); + return pci_piix_ide_initfn(d); +} + +static int pci_piix4_ide_initfn(PCIDevice *dev) +{ + PCIIDEState *d = DO_UPCAST(PCIIDEState, dev, dev); + + pci_config_set_vendor_id(d->dev.config, PCI_VENDOR_ID_INTEL); + pci_config_set_device_id(d->dev.config, PCI_DEVICE_ID_INTEL_82371AB); + return pci_piix_ide_initfn(d); +} + +/* 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 PCIDeviceInfo piix_ide_info[] = { + { + .qdev.name = "piix3-ide", + .qdev.size = sizeof(PCIIDEState), + .qdev.no_user = 1, + .no_hotplug = 1, + .init = pci_piix3_ide_initfn, + },{ + .qdev.name = "piix4-ide", + .qdev.size = sizeof(PCIIDEState), + .qdev.no_user = 1, + .no_hotplug = 1, + .init = pci_piix4_ide_initfn, + },{ + /* end of list */ + } +}; + +static void piix_ide_register(void) +{ + pci_qdev_register_many(piix_ide_info); +} +device_init(piix_ide_register); diff --git a/hw/ide/qdev.c b/hw/ide/qdev.c new file mode 100644 index 0000000..2bb5c27 --- /dev/null +++ b/hw/ide/qdev.c @@ -0,0 +1,172 @@ +/* + * ide bus support for qdev. + * + * Copyright (c) 2009 Gerd Hoffmann + * + * 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 . + */ +#include +#include "dma.h" +#include "qemu-error.h" +#include +#include "blockdev.h" +#include "sysemu.h" + +/* --------------------------------- */ + +static char *idebus_get_fw_dev_path(DeviceState *dev); + +static struct BusInfo ide_bus_info = { + .name = "IDE", + .size = sizeof(IDEBus), + .get_fw_dev_path = idebus_get_fw_dev_path, +}; + +void ide_bus_new(IDEBus *idebus, DeviceState *dev, int bus_id) +{ + qbus_create_inplace(&idebus->qbus, &ide_bus_info, dev, NULL); + idebus->bus_id = bus_id; +} + +static char *idebus_get_fw_dev_path(DeviceState *dev) +{ + char path[30]; + + snprintf(path, sizeof(path), "%s@%d", qdev_fw_name(dev), + ((IDEBus*)dev->parent_bus)->bus_id); + + return strdup(path); +} + +static int ide_qdev_init(DeviceState *qdev, DeviceInfo *base) +{ + IDEDevice *dev = DO_UPCAST(IDEDevice, qdev, qdev); + IDEDeviceInfo *info = DO_UPCAST(IDEDeviceInfo, qdev, base); + IDEBus *bus = DO_UPCAST(IDEBus, qbus, qdev->parent_bus); + + if (!dev->conf.bs) { + error_report("No drive specified"); + goto err; + } + if (dev->unit == -1) { + dev->unit = bus->master ? 1 : 0; + } + 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 info->init(dev); + +err: + return -1; +} + +static void ide_qdev_register(IDEDeviceInfo *info) +{ + info->qdev.init = ide_qdev_init; + info->qdev.bus_info = &ide_bus_info; + qdev_register(&info->qdev); +} + +IDEDevice *ide_create_drive(IDEBus *bus, int unit, DriveInfo *drive) +{ + DeviceState *dev; + + dev = qdev_create(&bus->qbus, "ide-drive"); + qdev_prop_set_uint32(dev, "unit", unit); + qdev_prop_set_drive_nofail(dev, "drive", drive->bdrv); + qdev_init_nofail(dev); + return DO_UPCAST(IDEDevice, qdev, dev); +} + +void ide_get_bs(BlockDriverState *bs[], BusState *qbus) +{ + IDEBus *bus = DO_UPCAST(IDEBus, qbus, qbus); + bs[0] = bus->master ? bus->master->conf.bs : NULL; + bs[1] = bus->slave ? bus->slave->conf.bs : NULL; +} + +/* --------------------------------- */ + +typedef struct IDEDrive { + IDEDevice dev; +} IDEDrive; + +static int ide_drive_initfn(IDEDevice *dev) +{ + IDEBus *bus = DO_UPCAST(IDEBus, qbus, dev->qdev.parent_bus); + IDEState *s = bus->ifs + dev->unit; + const char *serial; + DriveInfo *dinfo; + + serial = dev->serial; + if (!serial) { + /* try to fall back to value set with legacy -drive serial=... */ + dinfo = drive_get_by_blockdev(dev->conf.bs); + if (*dinfo->serial) { + serial = dinfo->serial; + } + } + + if (ide_init_drive(s, dev->conf.bs, dev->version, serial) < 0) { + return -1; + } + + if (!dev->version) { + dev->version = qemu_strdup(s->version); + } + if (!dev->serial) { + dev->serial = qemu_strdup(s->drive_serial_str); + } + + add_boot_device_path(dev->conf.bootindex, &dev->qdev, + dev->unit ? "/disk@1" : "/disk@0"); + + return 0; +} + +static IDEDeviceInfo ide_drive_info = { + .qdev.name = "ide-drive", + .qdev.fw_name = "drive", + .qdev.size = sizeof(IDEDrive), + .init = ide_drive_initfn, + .qdev.props = (Property[]) { + DEFINE_PROP_UINT32("unit", IDEDrive, dev.unit, -1), + DEFINE_BLOCK_PROPERTIES(IDEDrive, dev.conf), + DEFINE_PROP_STRING("ver", IDEDrive, dev.version), + DEFINE_PROP_STRING("serial", IDEDrive, dev.serial), + DEFINE_PROP_END_OF_LIST(), + } +}; + +static void ide_drive_register(void) +{ + ide_qdev_register(&ide_drive_info); +} +device_init(ide_drive_register); diff --git a/hw/ide/via.c b/hw/ide/via.c new file mode 100644 index 0000000..04f3290 --- /dev/null +++ b/hw/ide/via.c @@ -0,0 +1,200 @@ +/* + * QEMU IDE Emulation: PCI VIA82C686B support. + * + * Copyright (c) 2003 Fabrice Bellard + * Copyright (c) 2006 Openedhand Ltd. + * Copyright (c) 2010 Huacai Chen + * + * 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 +#include +#include +#include +#include "block.h" +#include "block_int.h" +#include "sysemu.h" +#include "dma.h" + +#include + +static uint32_t bmdma_readb(void *opaque, uint32_t addr) +{ + BMDMAState *bm = opaque; + uint32_t val; + + 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_writeb(void *opaque, uint32_t addr, uint32_t val) +{ + BMDMAState *bm = opaque; +#ifdef DEBUG_IDE + printf("bmdma: writeb 0x%02x : 0x%02x\n", addr, val); +#endif + switch (addr & 3) { + case 2: + bm->status = (val & 0x60) | (bm->status & 1) | (bm->status & ~val & 0x06); + break; + default:; + } +} + +static void bmdma_map(PCIDevice *pci_dev, int region_num, + pcibus_t addr, pcibus_t size, int type) +{ + PCIIDEState *d = DO_UPCAST(PCIIDEState, dev, pci_dev); + int i; + + for(i = 0;i < 2; i++) { + BMDMAState *bm = &d->bmdma[i]; + + register_ioport_write(addr, 1, 1, bmdma_cmd_writeb, bm); + + register_ioport_write(addr + 1, 3, 1, bmdma_writeb, bm); + register_ioport_read(addr, 4, 1, bmdma_readb, bm); + + iorange_init(&bm->addr_ioport, &bmdma_addr_ioport_ops, addr + 4, 4); + ioport_register(&bm->addr_ioport); + addr += 8; + } +} + +static void via_reset(void *opaque) +{ + PCIIDEState *d = opaque; + uint8_t *pci_conf = d->dev.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) { + int i; + struct { + int iobase; + int iobase2; + int isairq; + } port_info[] = { + {0x1f0, 0x3f6, 14}, + {0x170, 0x376, 15}, + }; + + for (i = 0; i < 2; i++) { + ide_bus_new(&d->bus[i], &d->dev.qdev, i); + ide_init_ioport(&d->bus[i], port_info[i].iobase, port_info[i].iobase2); + ide_init2(&d->bus[i], isa_get_irq(port_info[i].isairq)); + + bmdma_init(&d->bus[i], &d->bmdma[i]); + d->bmdma[i].bus = &d->bus[i]; + qemu_add_vm_change_state_handler(d->bus[i].dma->ops->restart_cb, + &d->bmdma[i].dma); + } +} + +/* via ide func */ +static int vt82c686b_ide_initfn(PCIDevice *dev) +{ + PCIIDEState *d = DO_UPCAST(PCIIDEState, dev, dev);; + uint8_t *pci_conf = d->dev.config; + + pci_config_set_vendor_id(pci_conf, PCI_VENDOR_ID_VIA); + pci_config_set_device_id(pci_conf, PCI_DEVICE_ID_VIA_IDE); + pci_config_set_class(pci_conf, PCI_CLASS_STORAGE_IDE); + pci_config_set_prog_interface(pci_conf, 0x8a); /* legacy ATA mode */ + pci_config_set_revision(pci_conf,0x06); /* Revision 0.6 */ + pci_set_long(pci_conf + PCI_CAPABILITY_LIST, 0x000000c0); + + qemu_register_reset(via_reset, d); + pci_register_bar(&d->dev, 4, 0x10, + PCI_BASE_ADDRESS_SPACE_IO, bmdma_map); + + vmstate_register(&dev->qdev, 0, &vmstate_ide_pci, d); + + vt82c686b_init_ports(d); + + return 0; +} + +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 PCIDeviceInfo via_ide_info = { + .qdev.name = "via-ide", + .qdev.size = sizeof(PCIIDEState), + .qdev.no_user = 1, + .init = vt82c686b_ide_initfn, +}; + +static void via_ide_register(void) +{ + pci_qdev_register(&via_ide_info); +} +device_init(via_ide_register); diff --git a/hw/integratorcp.c b/hw/integratorcp.c new file mode 100644 index 0000000..b049940 --- /dev/null +++ b/hw/integratorcp.c @@ -0,0 +1,545 @@ +/* + * ARM Integrator CP System emulation. + * + * Copyright (c) 2005-2007 CodeSourcery. + * Written by Paul Brook + * + * This code is licenced under the GPL + */ + +#include "sysbus.h" +#include "primecell.h" +#include "devices.h" +#include "sysemu.h" +#include "boards.h" +#include "arm-misc.h" +#include "net.h" + +typedef struct { + SysBusDevice busdev; + uint32_t memsz; + uint32_t flash_offset; + 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 int_level; + uint32_t irq_enabled; + uint32_t fiq_enabled; +} integratorcm_state; + +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 uint32_t integratorcm_read(void *opaque, target_phys_addr_t offset) +{ + integratorcm_state *s = (integratorcm_state *)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_REFCT */ + /* ??? High frequency timer. */ + hw_error("integratorcm_read: CM_REFCT"); + 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(integratorcm_state *s, int flash) +{ + if (flash) { + cpu_register_physical_memory(0, 0x100000, IO_MEM_RAM); + } else { + cpu_register_physical_memory(0, 0x100000, s->flash_offset | IO_MEM_RAM); + } + //??? tlb_flush (cpu_single_env, 1); +} + +static void integratorcm_set_ctrl(integratorcm_state *s, uint32_t value) +{ + if (value & 8) { + hw_error("Board reset\n"); + } + if ((s->cm_init ^ value) & 4) { + integratorcm_do_remap(s, (value & 4) == 0); + } + if ((s->cm_init ^ value) & 1) { + printf("Green LED %s\n", (value & 1) ? "on" : "off"); + } + s->cm_init = (s->cm_init & ~ 5) | (value ^ 5); +} + +static void integratorcm_update(integratorcm_state *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, target_phys_addr_t offset, + uint32_t value) +{ + integratorcm_state *s = (integratorcm_state *)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 CPUReadMemoryFunc * const integratorcm_readfn[] = { + integratorcm_read, + integratorcm_read, + integratorcm_read +}; + +static CPUWriteMemoryFunc * const integratorcm_writefn[] = { + integratorcm_write, + integratorcm_write, + integratorcm_write +}; + +static int integratorcm_init(SysBusDevice *dev) +{ + int iomemtype; + integratorcm_state *s = FROM_SYSBUS(integratorcm_state, 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->flash_offset = qemu_ram_alloc(NULL, "integrator.flash", 0x100000); + + iomemtype = cpu_register_io_memory(integratorcm_readfn, + integratorcm_writefn, s, + DEVICE_NATIVE_ENDIAN); + sysbus_init_mmio(dev, 0x00800000, iomemtype); + integratorcm_do_remap(s, 1); + /* ??? Save/restore. */ + return 0; +} + +/* Integrator/CP hardware emulation. */ +/* Primary interrupt controller. */ + +typedef struct icp_pic_state +{ + SysBusDevice busdev; + 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 uint32_t icp_pic_read(void *opaque, target_phys_addr_t offset) +{ + 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, target_phys_addr_t offset, + uint32_t value) +{ + 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 CPUReadMemoryFunc * const icp_pic_readfn[] = { + icp_pic_read, + icp_pic_read, + icp_pic_read +}; + +static CPUWriteMemoryFunc * const icp_pic_writefn[] = { + icp_pic_write, + icp_pic_write, + icp_pic_write +}; + +static int icp_pic_init(SysBusDevice *dev) +{ + icp_pic_state *s = FROM_SYSBUS(icp_pic_state, dev); + int iomemtype; + + qdev_init_gpio_in(&dev->qdev, icp_pic_set_irq, 32); + sysbus_init_irq(dev, &s->parent_irq); + sysbus_init_irq(dev, &s->parent_fiq); + iomemtype = cpu_register_io_memory(icp_pic_readfn, + icp_pic_writefn, s, + DEVICE_NATIVE_ENDIAN); + sysbus_init_mmio(dev, 0x00800000, iomemtype); + return 0; +} + +/* CP control registers. */ +static uint32_t icp_control_read(void *opaque, target_phys_addr_t offset) +{ + switch (offset >> 2) { + case 0: /* CP_IDFIELD */ + return 0x41034003; + case 1: /* CP_FLASHPROG */ + return 0; + case 2: /* CP_INTREG */ + return 0; + 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, target_phys_addr_t offset, + uint32_t value) +{ + switch (offset >> 2) { + case 1: /* CP_FLASHPROG */ + case 2: /* CP_INTREG */ + case 3: /* CP_DECODE */ + /* Nothing interesting implemented yet. */ + break; + default: + hw_error("icp_control_write: Bad offset %x\n", (int)offset); + } +} +static CPUReadMemoryFunc * const icp_control_readfn[] = { + icp_control_read, + icp_control_read, + icp_control_read +}; + +static CPUWriteMemoryFunc * const icp_control_writefn[] = { + icp_control_write, + icp_control_write, + icp_control_write +}; + +static void icp_control_init(uint32_t base) +{ + int iomemtype; + + iomemtype = cpu_register_io_memory(icp_control_readfn, + icp_control_writefn, NULL, + DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(base, 0x00800000, iomemtype); + /* ??? Save/restore. */ +} + + +/* Board init. */ + +static struct arm_boot_info integrator_binfo = { + .loader_start = 0x0, + .board_id = 0x113, +}; + +static void integratorcp_init(ram_addr_t ram_size, + const char *boot_device, + const char *kernel_filename, const char *kernel_cmdline, + const char *initrd_filename, const char *cpu_model) +{ + CPUState *env; + ram_addr_t ram_offset; + qemu_irq pic[32]; + qemu_irq *cpu_pic; + DeviceState *dev; + int i; + + if (!cpu_model) + cpu_model = "arm926"; + env = cpu_init(cpu_model); + if (!env) { + fprintf(stderr, "Unable to find CPU definition\n"); + exit(1); + } + ram_offset = qemu_ram_alloc(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*/ + cpu_register_physical_memory(0, ram_size, ram_offset | IO_MEM_RAM); + /* And again at address 0x80000000 */ + cpu_register_physical_memory(0x80000000, ram_size, ram_offset | IO_MEM_RAM); + + dev = qdev_create(NULL, "integrator_core"); + qdev_prop_set_uint32(dev, "memsz", ram_size >> 20); + qdev_init_nofail(dev); + sysbus_mmio_map((SysBusDevice *)dev, 0, 0x10000000); + + cpu_pic = arm_pic_init_cpu(env); + dev = sysbus_create_varargs("integrator_pic", 0x14000000, + cpu_pic[ARM_PIC_CPU_IRQ], + cpu_pic[ARM_PIC_CPU_FIQ], NULL); + for (i = 0; i < 32; i++) { + pic[i] = qdev_get_gpio_in(dev, i); + } + sysbus_create_simple("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_control_init(0xcb000000); + sysbus_create_simple("pl050_keyboard", 0x18000000, pic[3]); + sysbus_create_simple("pl050_mouse", 0x19000000, pic[4]); + sysbus_create_varargs("pl181", 0x1c000000, pic[23], pic[24], NULL); + if (nd_table[0].vlan) + 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(env, &integrator_binfo); +} + +static QEMUMachine integratorcp_machine = { + .name = "integratorcp", + .desc = "ARM Integrator/CP (ARM926EJ-S)", + .init = integratorcp_init, + .is_default = 1, +}; + +static void integratorcp_machine_init(void) +{ + qemu_register_machine(&integratorcp_machine); +} + +machine_init(integratorcp_machine_init); + +static SysBusDeviceInfo core_info = { + .init = integratorcm_init, + .qdev.name = "integrator_core", + .qdev.size = sizeof(integratorcm_state), + .qdev.props = (Property[]) { + DEFINE_PROP_UINT32("memsz", integratorcm_state, memsz, 0), + DEFINE_PROP_END_OF_LIST(), + } +}; + +static void integratorcp_register_devices(void) +{ + sysbus_register_dev("integrator_pic", sizeof(icp_pic_state), icp_pic_init); + sysbus_register_withprop(&core_info); +} + +device_init(integratorcp_register_devices) diff --git a/hw/intel-hda-defs.h b/hw/intel-hda-defs.h new file mode 100644 index 0000000..2e37e5b --- /dev/null +++ b/hw/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/intel-hda.c b/hw/intel-hda.c new file mode 100644 index 0000000..b2b6708 --- /dev/null +++ b/hw/intel-hda.c @@ -0,0 +1,1308 @@ +/* + * Copyright (C) 2010 Red Hat, Inc. + * + * written by Gerd Hoffmann + * + * 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 . + */ + +#include "hw.h" +#include "pci.h" +#include "msi.h" +#include "qemu-timer.h" +#include "audiodev.h" +#include "intel-hda.h" +#include "intel-hda-defs.h" + +/* --------------------------------------------------------------------- */ +/* hda bus */ + +static struct BusInfo hda_codec_bus_info = { + .name = "HDA", + .size = sizeof(HDACodecBus), + .props = (Property[]) { + DEFINE_PROP_UINT32("cad", HDACodecDevice, cad, -1), + DEFINE_PROP_END_OF_LIST() + } +}; + +void hda_codec_bus_init(DeviceState *dev, HDACodecBus *bus, + hda_codec_response_func response, + hda_codec_xfer_func xfer) +{ + qbus_create_inplace(&bus->qbus, &hda_codec_bus_info, dev, NULL); + bus->response = response; + bus->xfer = xfer; +} + +static int hda_codec_dev_init(DeviceState *qdev, DeviceInfo *base) +{ + HDACodecBus *bus = DO_UPCAST(HDACodecBus, qbus, qdev->parent_bus); + HDACodecDevice *dev = DO_UPCAST(HDACodecDevice, qdev, qdev); + HDACodecDeviceInfo *info = DO_UPCAST(HDACodecDeviceInfo, qdev, base); + + dev->info = info; + if (dev->cad == -1) { + dev->cad = bus->next_cad; + } + if (dev->cad >= 15) { + return -1; + } + bus->next_cad = dev->cad + 1; + return info->init(dev); +} + +static int hda_codec_dev_exit(DeviceState *qdev) +{ + HDACodecDevice *dev = DO_UPCAST(HDACodecDevice, qdev, qdev); + + if (dev->info->exit) { + dev->info->exit(dev); + } + return 0; +} + +void hda_codec_register(HDACodecDeviceInfo *info) +{ + info->qdev.init = hda_codec_dev_init; + info->qdev.exit = hda_codec_dev_exit; + info->qdev.bus_info = &hda_codec_bus_info; + qdev_register(&info->qdev); +} + +HDACodecDevice *hda_codec_find(HDACodecBus *bus, uint32_t cad) +{ + DeviceState *qdev; + HDACodecDevice *cdev; + + QLIST_FOREACH(qdev, &bus->qbus.children, sibling) { + 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 */ + int mmio_addr; + 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; +}; + +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 target_phys_addr_t intel_hda_addr(uint32_t lbase, uint32_t ubase) +{ + target_phys_addr_t addr; + +#if TARGET_PHYS_ADDR_BITS == 32 + addr = lbase; +#else + addr = ubase; + addr <<= 32; + addr |= lbase; +#endif + return addr; +} + +static void stl_phys_le(target_phys_addr_t addr, uint32_t value) +{ + uint32_t value_le = cpu_to_le32(value); + cpu_physical_memory_write(addr, (uint8_t*)(&value_le), sizeof(value_le)); +} + +static uint32_t ldl_phys_le(target_phys_addr_t addr) +{ + uint32_t value_le; + cpu_physical_memory_read(addr, (uint8_t*)(&value_le), sizeof(value_le)); + return le32_to_cpu(value_le); +} + +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 |= (1 << 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 & (1 << 31) && d->int_ctl & (1 << 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 { + qemu_set_irq(d->pci.irq[0], level); + } +} + +static int intel_hda_send_command(IntelHDAState *d, uint32_t verb) +{ + uint32_t cad, nid, data; + HDACodecDevice *codec; + + 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; + } + codec->info->command(codec, nid, data); + return 0; +} + +static void intel_hda_corb_run(IntelHDAState *d) +{ + target_phys_addr_t 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_phys_le(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); + target_phys_addr_t 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_phys_le(addr + 8*wp, response); + stl_phys_le(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); + IntelHDAStream *st = NULL; + target_phys_addr_t addr; + uint32_t s, copy, left; + bool irq = false; + + for (s = 0; s < ARRAY_SIZE(d->st); s++) { + if (stnr == ((d->st[s].ctl >> 20) & 0x0f)) { + st = d->st + s; + break; + } + } + if (st == NULL) { + 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); + + cpu_physical_memory_rw(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) { + addr = intel_hda_addr(d->dp_lbase & ~0x01, d->dp_ubase); + stl_phys_le(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) +{ + target_phys_addr_t addr; + uint8_t buf[16]; + uint32_t i; + + addr = intel_hda_addr(st->bdlp_lbase, st->bdlp_ubase); + st->bentries = st->lvi +1; + qemu_free(st->bpl); + st->bpl = qemu_malloc(sizeof(bpl) * st->bentries); + for (i = 0; i < st->bentries; i++, addr += 16) { + cpu_physical_memory_read(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) +{ + DeviceState *qdev; + HDACodecDevice *cdev; + + QLIST_FOREACH(qdev, &d->codecs.qbus.children, sibling) { + cdev = DO_UPCAST(HDACodecDevice, qdev, qdev); + if (cdev->info->stream) { + cdev->info->stream(cdev, stream, running); + } + } +} + +/* --------------------------------------------------------------------- */ + +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(&d->pci.qdev); + } +} + +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_get_clock_ns(vm_clock) - 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) +{ + IntelHDAStream *st = d->st + reg->stream; + + if (st->ctl & 0x01) { + /* reset */ + dprint(d, 1, "st #%d: reset\n", reg->stream); + st->ctl = 0; + } + 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); + } else { + /* stop */ + dprint(d, 1, "st #%d: stop %d\n", reg->stream, stnr); + intel_hda_notify_codecs(d, stnr, false); + } + } + 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, \ + }, \ + [ 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, target_phys_addr_t addr) +{ + const IntelHDAReg *reg; + + if (addr >= sizeof(regtab)/sizeof(regtab[0])) { + 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 < sizeof(regtab)/sizeof(regtab[0]); 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, target_phys_addr_t 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, target_phys_addr_t 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, target_phys_addr_t 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, target_phys_addr_t 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, target_phys_addr_t 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, target_phys_addr_t addr) +{ + IntelHDAState *d = opaque; + const IntelHDAReg *reg = intel_hda_reg_find(d, addr); + + return intel_hda_reg_read(d, reg, 0xffffffff); +} + +static CPUReadMemoryFunc * const intel_hda_mmio_read[3] = { + intel_hda_mmio_readb, + intel_hda_mmio_readw, + intel_hda_mmio_readl, +}; + +static CPUWriteMemoryFunc * const intel_hda_mmio_write[3] = { + intel_hda_mmio_writeb, + intel_hda_mmio_writew, + intel_hda_mmio_writel, +}; + +static void intel_hda_map(PCIDevice *pci, int region_num, + pcibus_t addr, pcibus_t size, int type) +{ + IntelHDAState *d = DO_UPCAST(IntelHDAState, pci, pci); + + cpu_register_physical_memory(addr, 0x4000, d->mmio_addr); +} + +/* --------------------------------------------------------------------- */ + +static void intel_hda_reset(DeviceState *dev) +{ + IntelHDAState *d = DO_UPCAST(IntelHDAState, pci.qdev, dev); + DeviceState *qdev; + HDACodecDevice *cdev; + + intel_hda_regs_reset(d); + d->wall_base_ns = qemu_get_clock(vm_clock); + + /* reset codecs */ + QLIST_FOREACH(qdev, &d->codecs.qbus.children, sibling) { + cdev = DO_UPCAST(HDACodecDevice, qdev, qdev); + if (qdev->info->reset) { + qdev->info->reset(qdev); + } + d->state_sts |= (1 << cdev->cad); + } + intel_hda_update_irq(d); +} + +static int intel_hda_init(PCIDevice *pci) +{ + IntelHDAState *d = DO_UPCAST(IntelHDAState, pci, pci); + uint8_t *conf = d->pci.config; + + d->name = d->pci.qdev.info->name; + + pci_config_set_vendor_id(conf, PCI_VENDOR_ID_INTEL); + pci_config_set_device_id(conf, 0x2668); + pci_config_set_revision(conf, 1); + pci_config_set_class(conf, PCI_CLASS_MULTIMEDIA_HD_AUDIO); + 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; + + d->mmio_addr = cpu_register_io_memory(intel_hda_mmio_read, + intel_hda_mmio_write, d, + DEVICE_NATIVE_ENDIAN); + pci_register_bar(&d->pci, 0, 0x4000, PCI_BASE_ADDRESS_SPACE_MEMORY, + intel_hda_map); + if (d->msi) { + msi_init(&d->pci, 0x50, 1, true, false); + } + + hda_codec_bus_init(&d->pci.qdev, &d->codecs, + intel_hda_response, intel_hda_xfer); + + return 0; +} + +static int intel_hda_exit(PCIDevice *pci) +{ + IntelHDAState *d = DO_UPCAST(IntelHDAState, pci, pci); + + if (d->msi) { + msi_uninit(&d->pci); + } + cpu_unregister_io_memory(d->mmio_addr); + return 0; +} + +static void intel_hda_write_config(PCIDevice *pci, uint32_t addr, + uint32_t val, int len) +{ + IntelHDAState *d = DO_UPCAST(IntelHDAState, pci, pci); + + pci_default_write_config(pci, addr, val, len); + if (d->msi) { + msi_write_config(pci, addr, val, len); + } +} + +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 PCIDeviceInfo intel_hda_info = { + .qdev.name = "intel-hda", + .qdev.desc = "Intel HD Audio Controller", + .qdev.size = sizeof(IntelHDAState), + .qdev.vmsd = &vmstate_intel_hda, + .qdev.reset = intel_hda_reset, + .init = intel_hda_init, + .exit = intel_hda_exit, + .config_write = intel_hda_write_config, + .qdev.props = (Property[]) { + DEFINE_PROP_UINT32("debug", IntelHDAState, debug, 0), + DEFINE_PROP_UINT32("msi", IntelHDAState, msi, 1), + DEFINE_PROP_END_OF_LIST(), + } +}; + +static void intel_hda_register(void) +{ + pci_qdev_register(&intel_hda_info); +} +device_init(intel_hda_register); + +/* + * create intel hda controller with codec attached to it, + * so '-soundhw hda' works. + */ +int intel_hda_and_codec_init(PCIBus *bus) +{ + PCIDevice *controller; + BusState *hdabus; + DeviceState *codec; + + controller = pci_create_simple(bus, -1, "intel-hda"); + hdabus = QLIST_FIRST(&controller->qdev.child_bus); + codec = qdev_create(hdabus, "hda-duplex"); + qdev_init_nofail(codec); + return 0; +} + diff --git a/hw/intel-hda.h b/hw/intel-hda.h new file mode 100644 index 0000000..4e44e38 --- /dev/null +++ b/hw/intel-hda.h @@ -0,0 +1,62 @@ +#ifndef HW_INTEL_HDA_H +#define HW_INTEL_HDA_H + +#include "qdev.h" + +/* --------------------------------------------------------------------- */ +/* hda bus */ + +typedef struct HDACodecBus HDACodecBus; +typedef struct HDACodecDevice HDACodecDevice; +typedef struct HDACodecDeviceInfo HDACodecDeviceInfo; + +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; +}; + +struct HDACodecDevice { + DeviceState qdev; + HDACodecDeviceInfo *info; + uint32_t cad; /* codec address */ +}; + +struct HDACodecDeviceInfo { + DeviceInfo qdev; + 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); +}; + +void hda_codec_bus_init(DeviceState *dev, HDACodecBus *bus, + hda_codec_response_func response, + hda_codec_xfer_func xfer); +void hda_codec_register(HDACodecDeviceInfo *info); +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/ioapic.c b/hw/ioapic.c new file mode 100644 index 0000000..569327d --- /dev/null +++ b/hw/ioapic.c @@ -0,0 +1,358 @@ +/* + * ioapic.c IOAPIC emulation logic + * + * Copyright (c) 2004-2005 Fabrice Bellard + * + * Split the ioapic logic from apic.c + * Xiantao Zhang + * + * 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 . + */ + +#include "hw.h" +#include "pc.h" +#include "apic.h" +#include "ioapic.h" +#include "qemu-timer.h" +#include "host-utils.h" +#include "sysbus.h" + +//#define DEBUG_IOAPIC + +#ifdef DEBUG_IOAPIC +#define DPRINTF(fmt, ...) \ + do { printf("ioapic: " fmt , ## __VA_ARGS__); } while (0) +#else +#define DPRINTF(fmt, ...) +#endif + +#define MAX_IOAPICS 1 + +#define IOAPIC_VERSION 0x11 + +#define IOAPIC_LVT_DEST_SHIFT 56 +#define IOAPIC_LVT_MASKED_SHIFT 16 +#define IOAPIC_LVT_TRIGGER_MODE_SHIFT 15 +#define IOAPIC_LVT_REMOTE_IRR_SHIFT 14 +#define IOAPIC_LVT_POLARITY_SHIFT 13 +#define IOAPIC_LVT_DELIV_STATUS_SHIFT 12 +#define IOAPIC_LVT_DEST_MODE_SHIFT 11 +#define IOAPIC_LVT_DELIV_MODE_SHIFT 8 + +#define IOAPIC_LVT_MASKED (1 << IOAPIC_LVT_MASKED_SHIFT) +#define IOAPIC_LVT_REMOTE_IRR (1 << IOAPIC_LVT_REMOTE_IRR_SHIFT) + +#define IOAPIC_TRIGGER_EDGE 0 +#define IOAPIC_TRIGGER_LEVEL 1 + +/*io{apic,sapic} delivery mode*/ +#define IOAPIC_DM_FIXED 0x0 +#define IOAPIC_DM_LOWEST_PRIORITY 0x1 +#define IOAPIC_DM_PMI 0x2 +#define IOAPIC_DM_NMI 0x4 +#define IOAPIC_DM_INIT 0x5 +#define IOAPIC_DM_SIPI 0x6 +#define IOAPIC_DM_EXTINT 0x7 +#define IOAPIC_DM_MASK 0x7 + +#define IOAPIC_VECTOR_MASK 0xff + +#define IOAPIC_IOREGSEL 0x00 +#define IOAPIC_IOWIN 0x10 + +#define IOAPIC_REG_ID 0x00 +#define IOAPIC_REG_VER 0x01 +#define IOAPIC_REG_ARB 0x02 +#define IOAPIC_REG_REDTBL_BASE 0x10 +#define IOAPIC_ID 0x00 + +#define IOAPIC_ID_SHIFT 24 +#define IOAPIC_ID_MASK 0xf + +#define IOAPIC_VER_ENTRIES_SHIFT 16 + +typedef struct IOAPICState IOAPICState; + +struct IOAPICState { + SysBusDevice busdev; + uint8_t id; + uint8_t ioregsel; + uint32_t irr; + uint64_t ioredtbl[IOAPIC_NUM_PINS]; +}; + +static IOAPICState *ioapics[MAX_IOAPICS]; + +static void ioapic_service(IOAPICState *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; + uint8_t polarity; + + 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; + polarity = (entry >> IOAPIC_LVT_POLARITY_SHIFT) & 1; + 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, polarity, trig_mode); + } + } + } +} + +static void ioapic_set_irq(void *opaque, int vector, int level) +{ + IOAPICState *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 { + /* edge triggered */ + if (level) { + s->irr |= mask; + ioapic_service(s); + } + } + } +} + +void ioapic_eoi_broadcast(int vector) +{ + IOAPICState *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 uint32_t ioapic_mem_readl(void *opaque, target_phys_addr_t addr) +{ + IOAPICState *s = opaque; + int index; + uint32_t val = 0; + + switch (addr & 0xff) { + case IOAPIC_IOREGSEL: + val = s->ioregsel; + break; + case IOAPIC_IOWIN: + 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_writel(void *opaque, target_phys_addr_t addr, uint32_t val) +{ + IOAPICState *s = opaque; + int index; + + switch (addr & 0xff) { + case IOAPIC_IOREGSEL: + s->ioregsel = val; + break; + case IOAPIC_IOWIN: + DPRINTF("write: %08x = %08x\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 int ioapic_post_load(void *opaque, int version_id) +{ + IOAPICState *s = opaque; + + if (version_id == 1) { + /* set sane value */ + s->irr = 0; + } + return 0; +} + +static const VMStateDescription vmstate_ioapic = { + .name = "ioapic", + .version_id = 3, + .post_load = ioapic_post_load, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT8(id, IOAPICState), + VMSTATE_UINT8(ioregsel, IOAPICState), + VMSTATE_UNUSED_V(2, 8), /* to account for qemu-kvm's v2 format */ + VMSTATE_UINT32_V(irr, IOAPICState, 2), + VMSTATE_UINT64_ARRAY(ioredtbl, IOAPICState, IOAPIC_NUM_PINS), + VMSTATE_END_OF_LIST() + } +}; + +static void ioapic_reset(DeviceState *d) +{ + IOAPICState *s = DO_UPCAST(IOAPICState, busdev.qdev, d); + 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 CPUReadMemoryFunc * const ioapic_mem_read[3] = { + ioapic_mem_readl, + ioapic_mem_readl, + ioapic_mem_readl, +}; + +static CPUWriteMemoryFunc * const ioapic_mem_write[3] = { + ioapic_mem_writel, + ioapic_mem_writel, + ioapic_mem_writel, +}; + +static int ioapic_init1(SysBusDevice *dev) +{ + IOAPICState *s = FROM_SYSBUS(IOAPICState, dev); + int io_memory; + static int ioapic_no; + + if (ioapic_no >= MAX_IOAPICS) { + return -1; + } + + io_memory = cpu_register_io_memory(ioapic_mem_read, + ioapic_mem_write, s, + DEVICE_NATIVE_ENDIAN); + sysbus_init_mmio(dev, 0x1000, io_memory); + + qdev_init_gpio_in(&dev->qdev, ioapic_set_irq, IOAPIC_NUM_PINS); + + ioapics[ioapic_no++] = s; + + return 0; +} + +static SysBusDeviceInfo ioapic_info = { + .init = ioapic_init1, + .qdev.name = "ioapic", + .qdev.size = sizeof(IOAPICState), + .qdev.vmsd = &vmstate_ioapic, + .qdev.reset = ioapic_reset, + .qdev.no_user = 1, +}; + +static void ioapic_register_devices(void) +{ + sysbus_register_withprop(&ioapic_info); +} + +device_init(ioapic_register_devices) diff --git a/hw/ioapic.h b/hw/ioapic.h new file mode 100644 index 0000000..cb2642a --- /dev/null +++ b/hw/ioapic.h @@ -0,0 +1,20 @@ +/* + * ioapic.c IOAPIC emulation logic + * + * 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 . + */ + +void ioapic_eoi_broadcast(int vector); diff --git a/hw/ioh3420.c b/hw/ioh3420.c new file mode 100644 index 0000000..95adf09 --- /dev/null +++ b/hw/ioh3420.c @@ -0,0 +1,246 @@ +/* + * ioh3420.c + * Intel X58 north bridge IOH + * PCI Express root port device id 3420 + * + * Copyright (c) 2010 Isaku Yamahata + * 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 . + */ + +#include "pci_ids.h" +#include "msi.h" +#include "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); + msi_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 = DO_UPCAST(PCIDevice, qdev, qdev); + msi_reset(d); + ioh3420_aer_vector_update(d); + pcie_cap_root_reset(d); + pcie_cap_deverr_reset(d); + pcie_cap_slot_reset(d); + pcie_aer_root_reset(d); + pci_bridge_reset(qdev); + pci_bridge_disable_base_limit(d); +} + +static int ioh3420_initfn(PCIDevice *d) +{ + PCIBridge* br = DO_UPCAST(PCIBridge, dev, d); + PCIEPort *p = DO_UPCAST(PCIEPort, br, br); + PCIESlot *s = DO_UPCAST(PCIESlot, port, p); + int rc; + int tmp; + + rc = pci_bridge_initfn(d); + if (rc < 0) { + return rc; + } + + d->config[PCI_REVISION_ID] = PCI_DEVICE_ID_IOH_REV; + pcie_port_init_reg(d); + + pci_config_set_vendor_id(d->config, PCI_VENDOR_ID_INTEL); + pci_config_set_device_id(d->config, PCI_DEVICE_ID_IOH_EPORT); + + 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_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; + return rc; + } + 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: + tmp = pci_bridge_exitfn(d); + assert(!tmp); + return rc; +} + +static int ioh3420_exitfn(PCIDevice *d) +{ + PCIBridge* br = DO_UPCAST(PCIBridge, dev, d); + PCIEPort *p = DO_UPCAST(PCIEPort, br, br); + PCIESlot *s = DO_UPCAST(PCIESlot, port, p); + + pcie_aer_exit(d); + pcie_chassis_del_slot(s); + pcie_cap_exit(d); + msi_uninit(d); + return pci_bridge_exitfn(d); +} + +PCIESlot *ioh3420_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, "ioh3420"); + if (!d) { + return NULL; + } + br = DO_UPCAST(PCIBridge, dev, d); + + qdev = &br->dev.qdev; + 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 DO_UPCAST(PCIESlot, port, DO_UPCAST(PCIEPort, br, br)); +} + +static const VMStateDescription vmstate_ioh3420 = { + .name = "ioh-3240-express-root-port", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .post_load = pcie_cap_slot_post_load, + .fields = (VMStateField[]) { + VMSTATE_PCIE_DEVICE(port.br.dev, PCIESlot), + VMSTATE_STRUCT(port.br.dev.exp.aer_log, PCIESlot, 0, + vmstate_pcie_aer_log, PCIEAERLog), + VMSTATE_END_OF_LIST() + } +}; + +static PCIDeviceInfo ioh3420_info = { + .qdev.name = "ioh3420", + .qdev.desc = "Intel IOH device id 3420 PCIE Root Port", + .qdev.size = sizeof(PCIESlot), + .qdev.reset = ioh3420_reset, + .qdev.vmsd = &vmstate_ioh3420, + + .is_express = 1, + .is_bridge = 1, + .config_write = ioh3420_write_config, + .init = ioh3420_initfn, + .exit = ioh3420_exitfn, + + .qdev.props = (Property[]) { + DEFINE_PROP_UINT8("port", PCIESlot, port.port, 0), + DEFINE_PROP_UINT8("chassis", PCIESlot, chassis, 0), + DEFINE_PROP_UINT16("slot", PCIESlot, slot, 0), + DEFINE_PROP_UINT16("aer_log_max", PCIESlot, + port.br.dev.exp.aer_log.log_max, + PCIE_AER_LOG_MAX_DEFAULT), + DEFINE_PROP_END_OF_LIST(), + } +}; + +static void ioh3420_register(void) +{ + pci_qdev_register(&ioh3420_info); +} + +device_init(ioh3420_register); + +/* + * Local variables: + * c-indent-level: 4 + * c-basic-offset: 4 + * tab-width: 8 + * indent-tab-mode: nil + * End: + */ diff --git a/hw/ioh3420.h b/hw/ioh3420.h new file mode 100644 index 0000000..68c523a --- /dev/null +++ b/hw/ioh3420.h @@ -0,0 +1,10 @@ +#ifndef QEMU_IOH3420_H +#define QEMU_IOH3420_H + +#include "pcie_port.h" + +PCIESlot *ioh3420_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_IOH3420_H */ diff --git a/hw/irq.c b/hw/irq.c new file mode 100644 index 0000000..7703f62 --- /dev/null +++ b/hw/irq.c @@ -0,0 +1,77 @@ +/* + * 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 "irq.h" + +struct IRQState { + 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_allocate_irqs(qemu_irq_handler handler, void *opaque, int n) +{ + qemu_irq *s; + struct IRQState *p; + int i; + + s = (qemu_irq *)qemu_mallocz(sizeof(qemu_irq) * n); + p = (struct IRQState *)qemu_mallocz(sizeof(struct IRQState) * n); + for (i = 0; i < n; i++) { + p->handler = handler; + p->opaque = opaque; + p->n = i; + s[i] = p; + p++; + } + return s; +} + +void qemu_free_irqs(qemu_irq *s) +{ + qemu_free(s[0]); + qemu_free(s); +} + +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_irqs(qemu_notirq, irq, 1)[0]; +} diff --git a/hw/irq.h b/hw/irq.h new file mode 100644 index 0000000..5daae44 --- /dev/null +++ b/hw/irq.h @@ -0,0 +1,35 @@ +#ifndef QEMU_IRQ_H +#define QEMU_IRQ_H + +/* Generic IRQ/GPIO pin infrastructure. */ + +/* FIXME: Rmove one of these. */ +typedef void (*qemu_irq_handler)(void *opaque, int n, int level); +typedef void SetIRQFunc(void *opaque, int irq_num, int level); + +void qemu_set_irq(qemu_irq irq, int level); + +static inline void qemu_irq_raise(qemu_irq irq) +{ + qemu_set_irq(irq, 1); +} + +static inline void qemu_irq_lower(qemu_irq irq) +{ + qemu_set_irq(irq, 0); +} + +static inline void qemu_irq_pulse(qemu_irq irq) +{ + qemu_set_irq(irq, 1); + qemu_set_irq(irq, 0); +} + +/* Returns an array of N IRQs. */ +qemu_irq *qemu_allocate_irqs(qemu_irq_handler handler, void *opaque, int n); +void qemu_free_irqs(qemu_irq *s); + +/* Returns a new IRQ with opposite polarity. */ +qemu_irq qemu_irq_invert(qemu_irq irq); + +#endif diff --git a/hw/isa-bus.c b/hw/isa-bus.c new file mode 100644 index 0000000..9e33e71 --- /dev/null +++ b/hw/isa-bus.c @@ -0,0 +1,194 @@ +/* + * isa bus support for qdev. + * + * Copyright (c) 2009 Gerd Hoffmann + * + * 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 . + */ +#include "hw.h" +#include "sysemu.h" +#include "monitor.h" +#include "sysbus.h" +#include "isa.h" + +struct ISABus { + BusState qbus; + qemu_irq *irqs; +}; +static ISABus *isabus; +target_phys_addr_t isa_mem_base = 0; + +static void isabus_dev_print(Monitor *mon, DeviceState *dev, int indent); +static char *isabus_get_fw_dev_path(DeviceState *dev); + +static struct BusInfo isa_bus_info = { + .name = "ISA", + .size = sizeof(ISABus), + .print_dev = isabus_dev_print, + .get_fw_dev_path = isabus_get_fw_dev_path, +}; + +ISABus *isa_bus_new(DeviceState *dev) +{ + if (isabus) { + fprintf(stderr, "Can't create a second ISA bus\n"); + return NULL; + } + if (NULL == dev) { + dev = qdev_create(NULL, "isabus-bridge"); + qdev_init_nofail(dev); + } + + isabus = FROM_QBUS(ISABus, qbus_create(&isa_bus_info, dev, NULL)); + return isabus; +} + +void isa_bus_irqs(qemu_irq *irqs) +{ + isabus->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(int isairq) +{ + 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(isairq); + dev->nirqs++; +} + +static void isa_init_ioport_one(ISADevice *dev, uint16_t ioport) +{ + assert(dev->nioports < ARRAY_SIZE(dev->ioports)); + dev->ioports[dev->nioports++] = ioport; +} + +static int isa_cmp_ports(const void *p1, const void *p2) +{ + return *(uint16_t*)p1 - *(uint16_t*)p2; +} + +void isa_init_ioport_range(ISADevice *dev, uint16_t start, uint16_t length) +{ + int i; + for (i = start; i < start + length; i++) { + isa_init_ioport_one(dev, i); + } + qsort(dev->ioports, dev->nioports, sizeof(dev->ioports[0]), isa_cmp_ports); +} + +void isa_init_ioport(ISADevice *dev, uint16_t ioport) +{ + isa_init_ioport_range(dev, ioport, 1); +} + +static int isa_qdev_init(DeviceState *qdev, DeviceInfo *base) +{ + ISADevice *dev = DO_UPCAST(ISADevice, qdev, qdev); + ISADeviceInfo *info = DO_UPCAST(ISADeviceInfo, qdev, base); + + dev->isairq[0] = -1; + dev->isairq[1] = -1; + + return info->init(dev); +} + +void isa_qdev_register(ISADeviceInfo *info) +{ + info->qdev.init = isa_qdev_init; + info->qdev.bus_info = &isa_bus_info; + qdev_register(&info->qdev); +} + +ISADevice *isa_create(const char *name) +{ + DeviceState *dev; + + if (!isabus) { + hw_error("Tried to create isa device %s with no isa bus present.", + name); + } + dev = qdev_create(&isabus->qbus, name); + return DO_UPCAST(ISADevice, qdev, dev); +} + +ISADevice *isa_create_simple(const char *name) +{ + ISADevice *dev; + + dev = isa_create(name); + qdev_init_nofail(&dev->qdev); + return dev; +} + +static void isabus_dev_print(Monitor *mon, DeviceState *dev, int indent) +{ + ISADevice *d = DO_UPCAST(ISADevice, qdev, 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 int isabus_bridge_init(SysBusDevice *dev) +{ + /* nothing */ + return 0; +} + +static SysBusDeviceInfo isabus_bridge_info = { + .init = isabus_bridge_init, + .qdev.name = "isabus-bridge", + .qdev.fw_name = "isa", + .qdev.size = sizeof(SysBusDevice), + .qdev.no_user = 1, +}; + +static void isabus_register_devices(void) +{ + sysbus_register_withprop(&isabus_bridge_info); +} + +static char *isabus_get_fw_dev_path(DeviceState *dev) +{ + ISADevice *d = (ISADevice*)dev; + char path[40]; + int off; + + off = snprintf(path, sizeof(path), "%s", qdev_fw_name(dev)); + if (d->nioports) { + snprintf(path + off, sizeof(path) - off, "@%04x", d->ioports[0]); + } + + return strdup(path); +} + +device_init(isabus_register_devices) diff --git a/hw/isa.h b/hw/isa.h new file mode 100644 index 0000000..f1335ff --- /dev/null +++ b/hw/isa.h @@ -0,0 +1,52 @@ +#ifndef HW_ISA_H +#define HW_ISA_H + +/* ISA bus */ + +#include "ioport.h" +#include "qdev.h" + +typedef struct ISABus ISABus; +typedef struct ISADevice ISADevice; +typedef struct ISADeviceInfo ISADeviceInfo; + +struct ISADevice { + DeviceState qdev; + uint32_t isairq[2]; + int nirqs; + uint16_t ioports[32]; + int nioports; +}; + +typedef int (*isa_qdev_initfn)(ISADevice *dev); +struct ISADeviceInfo { + DeviceInfo qdev; + isa_qdev_initfn init; +}; + +ISABus *isa_bus_new(DeviceState *dev); +void isa_bus_irqs(qemu_irq *irqs); +qemu_irq isa_get_irq(int isairq); +void isa_init_irq(ISADevice *dev, qemu_irq *p, int isairq); +void isa_init_ioport(ISADevice *dev, uint16_t ioport); +void isa_init_ioport_range(ISADevice *dev, uint16_t start, uint16_t length); +void isa_qdev_register(ISADeviceInfo *info); +ISADevice *isa_create(const char *name); +ISADevice *isa_create_simple(const char *name); + +extern target_phys_addr_t isa_mem_base; + +void isa_mmio_init(target_phys_addr_t base, target_phys_addr_t size); + +/* dma.c */ +int DMA_get_channel_mode (int nchan); +int DMA_read_memory (int nchan, void *buf, int pos, int size); +int DMA_write_memory (int nchan, void *buf, int pos, int size); +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); +#endif diff --git a/hw/isa_mmio.c b/hw/isa_mmio.c new file mode 100644 index 0000000..ca957fb --- /dev/null +++ b/hw/isa_mmio.c @@ -0,0 +1,82 @@ +/* + * Memory mapped access to ISA IO space. + * + * 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.h" +#include "isa.h" + +static void isa_mmio_writeb (void *opaque, target_phys_addr_t addr, + uint32_t val) +{ + cpu_outb(addr & IOPORTS_MASK, val); +} + +static void isa_mmio_writew(void *opaque, target_phys_addr_t addr, + uint32_t val) +{ + cpu_outw(addr & IOPORTS_MASK, val); +} + +static void isa_mmio_writel(void *opaque, target_phys_addr_t addr, + uint32_t val) +{ + cpu_outl(addr & IOPORTS_MASK, val); +} + +static uint32_t isa_mmio_readb (void *opaque, target_phys_addr_t addr) +{ + return cpu_inb(addr & IOPORTS_MASK); +} + +static uint32_t isa_mmio_readw(void *opaque, target_phys_addr_t addr) +{ + return cpu_inw(addr & IOPORTS_MASK); +} + +static uint32_t isa_mmio_readl(void *opaque, target_phys_addr_t addr) +{ + return cpu_inl(addr & IOPORTS_MASK); +} + +static CPUWriteMemoryFunc * const isa_mmio_write[] = { + &isa_mmio_writeb, + &isa_mmio_writew, + &isa_mmio_writel, +}; + +static CPUReadMemoryFunc * const isa_mmio_read[] = { + &isa_mmio_readb, + &isa_mmio_readw, + &isa_mmio_readl, +}; + +void isa_mmio_init(target_phys_addr_t base, target_phys_addr_t size) +{ + int isa_mmio_iomemtype; + + isa_mmio_iomemtype = cpu_register_io_memory(isa_mmio_read, + isa_mmio_write, + NULL, + DEVICE_LITTLE_ENDIAN); + cpu_register_physical_memory(base, size, isa_mmio_iomemtype); +} diff --git a/hw/ivshmem.c b/hw/ivshmem.c new file mode 100644 index 0000000..7b19a81 --- /dev/null +++ b/hw/ivshmem.c @@ -0,0 +1,829 @@ +/* + * Inter-VM Shared Memory PCI device. + * + * Author: + * Cam Macdonell + * + * 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. + */ +#include "hw.h" +#include "pc.h" +#include "pci.h" +#include "msix.h" +#include "kvm.h" + +#include +#include + +#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 + +typedef struct Peer { + int nb_eventfds; + int *eventfds; +} Peer; + +typedef struct EventfdEntry { + PCIDevice *pdev; + int vector; +} EventfdEntry; + +typedef struct IVShmemState { + PCIDevice dev; + uint32_t intrmask; + uint32_t intrstatus; + uint32_t doorbell; + + CharDriverState **eventfd_chr; + CharDriverState *server_chr; + int ivshmem_mmio_io_addr; + + pcibus_t mmio_addr; + pcibus_t shm_pci_addr; + uint64_t ivshmem_offset; + uint64_t ivshmem_size; /* size of shared memory region */ + 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; + + 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; +} + +static void ivshmem_map(PCIDevice *pci_dev, int region_num, + pcibus_t addr, pcibus_t size, int type) +{ + IVShmemState *s = DO_UPCAST(IVShmemState, dev, pci_dev); + + s->shm_pci_addr = addr; + + if (s->ivshmem_offset > 0) { + cpu_register_physical_memory(s->shm_pci_addr, s->ivshmem_size, + s->ivshmem_offset); + } + + IVSHMEM_DPRINTF("guest pci addr = %" FMT_PCIBUS ", guest h/w addr = %" + PRIu64 ", size = %" FMT_PCIBUS "\n", addr, s->ivshmem_offset, size); + +} + +/* accessing registers - based on rtl8139 */ +static void ivshmem_update_irq(IVShmemState *s, int val) +{ + 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); + } + + qemu_set_irq(s->dev.irq[0], (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); + return; +} + +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_writew(void *opaque, target_phys_addr_t addr, + uint32_t val) +{ + + IVSHMEM_DPRINTF("We shouldn't be writing words\n"); +} + +static void ivshmem_io_writel(void *opaque, target_phys_addr_t addr, + uint32_t val) +{ + IVShmemState *s = opaque; + + uint64_t write_one = 1; + 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("Writing %" PRId64 " to VM %d on vector %d\n", + write_one, dest, vector); + if (write(s->peers[dest].eventfds[vector], + &(write_one), 8) != 8) { + IVSHMEM_DPRINTF("error writing to eventfd\n"); + } + } + break; + default: + IVSHMEM_DPRINTF("Invalid VM Doorbell VM %d\n", dest); + } +} + +static void ivshmem_io_writeb(void *opaque, target_phys_addr_t addr, + uint32_t val) +{ + IVSHMEM_DPRINTF("We shouldn't be writing bytes\n"); +} + +static uint32_t ivshmem_io_readw(void *opaque, target_phys_addr_t addr) +{ + + IVSHMEM_DPRINTF("We shouldn't be reading words\n"); + return 0; +} + +static uint32_t ivshmem_io_readl(void *opaque, target_phys_addr_t addr) +{ + + 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 uint32_t ivshmem_io_readb(void *opaque, target_phys_addr_t addr) +{ + IVSHMEM_DPRINTF("We shouldn't be reading bytes\n"); + + return 0; +} + +static CPUReadMemoryFunc * const ivshmem_mmio_read[3] = { + ivshmem_io_readb, + ivshmem_io_readw, + ivshmem_io_readl, +}; + +static CPUWriteMemoryFunc * const ivshmem_mmio_write[3] = { + ivshmem_io_writeb, + ivshmem_io_writew, + ivshmem_io_writel, +}; + +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, int eventfd, + int vector) +{ + /* create a event character device based on the passed eventfd */ + IVShmemState *s = opaque; + CharDriverState * chr; + + chr = qemu_chr_open_eventfd(eventfd); + + if (chr == NULL) { + fprintf(stderr, "creating eventfd for eventfd %d failed\n", eventfd); + exit(-1); + } + + /* if MSI is supported we need multiple interrupts */ + if (ivshmem_has_feature(s, IVSHMEM_MSI)) { + s->eventfd_table[vector].pdev = &s->dev; + 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; + + fstat(fd, &buf); + + if (s->ivshmem_size > buf.st_size) { + fprintf(stderr, + "IVSHMEM ERROR: Requested memory size greater" + " than shared object size (%" PRIu64 " > %" PRIu64")\n", + 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); + + s->ivshmem_offset = qemu_ram_alloc_from_ptr(&s->dev.qdev, "ivshmem.bar2", + s->ivshmem_size, ptr); + + /* region for shared memory */ + pci_register_bar(&s->dev, 2, s->ivshmem_size, + PCI_BASE_ADDRESS_SPACE_MEMORY, ivshmem_map); +} + +static void close_guest_eventfds(IVShmemState *s, int posn) +{ + int i, guest_curr_max; + + guest_curr_max = s->peers[posn].nb_eventfds; + + for (i = 0; i < guest_curr_max; i++) { + kvm_set_ioeventfd_mmio_long(s->peers[posn].eventfds[i], + s->mmio_addr + DOORBELL, (posn << 16) | i, 0); + close(s->peers[posn].eventfds[i]); + } + + qemu_free(s->peers[posn].eventfds); + s->peers[posn].nb_eventfds = 0; +} + +static void setup_ioeventfds(IVShmemState *s) { + + int i, j; + + for (i = 0; i <= s->max_peer; i++) { + for (j = 0; j < s->peers[i].nb_eventfds; j++) { + kvm_set_ioeventfd_mmio_long(s->peers[i].eventfds[j], + s->mmio_addr + DOORBELL, (i << 16) | j, 1); + } + } +} + +/* this function increase the dynamic storage need to store data about other + * guests */ +static void increase_dynamic_storage(IVShmemState *s, int new_min_size) { + + int j, old_nb_alloc; + + old_nb_alloc = s->nb_peers; + + while (new_min_size >= s->nb_peers) + s->nb_peers = s->nb_peers * 2; + + IVSHMEM_DPRINTF("bumping storage to %d guests\n", s->nb_peers); + s->peers = qemu_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; + } +} + +static void ivshmem_read(void *opaque, const uint8_t * buf, int flags) +{ + IVShmemState *s = opaque; + int incoming_fd, tmp_fd; + int guest_max_eventfd; + long incoming_posn; + + memcpy(&incoming_posn, buf, sizeof(long)); + /* pick off s->server_chr->msgfd and store it, posn should accompany msg */ + tmp_fd = qemu_chr_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) { + increase_dynamic_storage(s, incoming_posn); + } + + 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) { + fprintf(stderr, "could not allocate file descriptor %s\n", + strerror(errno)); + 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); + s->ivshmem_offset = qemu_ram_alloc_from_ptr(&s->dev.qdev, + "ivshmem.bar2", s->ivshmem_size, map_ptr); + + IVSHMEM_DPRINTF("guest pci addr = %" FMT_PCIBUS ", guest h/w addr = %" + PRIu64 ", size = %" PRIu64 "\n", s->shm_pci_addr, + s->ivshmem_offset, s->ivshmem_size); + + if (s->shm_pci_addr > 0) { + /* map memory into BAR2 */ + cpu_register_physical_memory(s->shm_pci_addr, s->ivshmem_size, + s->ivshmem_offset); + } + + /* 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 = (int *) qemu_malloc(s->vectors * + sizeof(int)); + } + + /* this is an eventfd for a particular guest VM */ + IVSHMEM_DPRINTF("eventfds[%ld][%d] = %d\n", incoming_posn, + guest_max_eventfd, incoming_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)) { + if (kvm_set_ioeventfd_mmio_long(incoming_fd, s->mmio_addr + DOORBELL, + (incoming_posn << 16) | guest_max_eventfd, 1) < 0) { + fprintf(stderr, "ivshmem: ioeventfd not available\n"); + } + } + + return; +} + +static void ivshmem_reset(DeviceState *d) +{ + IVShmemState *s = DO_UPCAST(IVShmemState, dev.qdev, d); + + s->intrstatus = 0; + return; +} + +static void ivshmem_mmio_map(PCIDevice *pci_dev, int region_num, + pcibus_t addr, pcibus_t size, int type) +{ + IVShmemState *s = DO_UPCAST(IVShmemState, dev, pci_dev); + + s->mmio_addr = addr; + cpu_register_physical_memory(addr + 0, IVSHMEM_REG_BAR_SIZE, + s->ivshmem_mmio_io_addr); + + if (ivshmem_has_feature(s, IVSHMEM_IOEVENTFD)) { + setup_ioeventfds(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: + fprintf(stderr, "qemu: invalid ram size: %s\n", s->sizearg); + exit(1); + } + + /* BARs must be a power of 2 */ + if (!is_power_of_two(value)) { + fprintf(stderr, "ivshmem: size must be power of 2\n"); + exit(1); + } + + return value; +} + +static void ivshmem_setup_msi(IVShmemState * s) { + + int i; + + /* allocate the MSI-X vectors */ + + if (!msix_init(&s->dev, s->vectors, 1, 0)) { + pci_register_bar(&s->dev, 1, + msix_bar_size(&s->dev), + PCI_BASE_ADDRESS_SPACE_MEMORY, + msix_mmio_map); + IVSHMEM_DPRINTF("msix initialized (%d vectors)\n", s->vectors); + } else { + IVSHMEM_DPRINTF("msix initialization failed\n"); + exit(1); + } + + /* 'activate' the vectors */ + for (i = 0; i < s->vectors; i++) { + msix_vector_use(&s->dev, i); + } + + /* allocate Qemu char devices for receiving interrupts */ + s->eventfd_table = qemu_mallocz(s->vectors * sizeof(EventfdEntry)); +} + +static void ivshmem_save(QEMUFile* f, void *opaque) +{ + IVShmemState *proxy = opaque; + + IVSHMEM_DPRINTF("ivshmem_save\n"); + pci_device_save(&proxy->dev, f); + + if (ivshmem_has_feature(proxy, IVSHMEM_MSI)) { + msix_save(&proxy->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; + int ret, i; + + if (version_id > 0) { + return -EINVAL; + } + + if (proxy->role_val == IVSHMEM_PEER) { + fprintf(stderr, "ivshmem: 'peer' devices are not migratable\n"); + return -EINVAL; + } + + ret = pci_device_load(&proxy->dev, f); + if (ret) { + return ret; + } + + if (ivshmem_has_feature(proxy, IVSHMEM_MSI)) { + msix_load(&proxy->dev, f); + for (i = 0; i < proxy->vectors; i++) { + msix_vector_use(&proxy->dev, i); + } + } else { + proxy->intrstatus = qemu_get_be32(f); + proxy->intrmask = qemu_get_be32(f); + } + + return 0; +} + +static int pci_ivshmem_init(PCIDevice *dev) +{ + IVShmemState *s = DO_UPCAST(IVShmemState, dev, 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); + } + + register_savevm(&s->dev.qdev, "ivshmem", 0, 0, ivshmem_save, ivshmem_load, + dev); + + /* IRQFD requires MSI */ + if (ivshmem_has_feature(s, IVSHMEM_IOEVENTFD) && + !ivshmem_has_feature(s, IVSHMEM_MSI)) { + fprintf(stderr, "ivshmem: ioeventfd/irqfd requires MSI\n"); + 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 { + fprintf(stderr, "ivshmem: 'role' must be 'peer' or 'master'\n"); + exit(1); + } + } else { + s->role_val = IVSHMEM_MASTER; /* default */ + } + + if (s->role_val == IVSHMEM_PEER) { + register_device_unmigratable(&s->dev.qdev, "ivshmem", s); + } + + pci_conf = s->dev.config; + pci_config_set_vendor_id(pci_conf, PCI_VENDOR_ID_REDHAT_QUMRANET); + pci_conf[0x02] = 0x10; + pci_conf[0x03] = 0x11; + pci_conf[PCI_COMMAND] = PCI_COMMAND_IO | PCI_COMMAND_MEMORY; + pci_config_set_class(pci_conf, PCI_CLASS_MEMORY_RAM); + pci_conf[PCI_HEADER_TYPE] = PCI_HEADER_TYPE_NORMAL; + + pci_config_set_interrupt_pin(pci_conf, 1); + + s->shm_pci_addr = 0; + s->ivshmem_offset = 0; + s->shm_fd = 0; + + s->ivshmem_mmio_io_addr = cpu_register_io_memory(ivshmem_mmio_read, + ivshmem_mmio_write, s, DEVICE_NATIVE_ENDIAN); + /* region for registers*/ + pci_register_bar(&s->dev, 0, IVSHMEM_REG_BAR_SIZE, + PCI_BASE_ADDRESS_SPACE_MEMORY, ivshmem_mmio_map); + + 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) { + fprintf(stderr, "WARNING: do not specify both 'chardev' " + "and 'shm' with ivshmem\n"); + } + + 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 = qemu_mallocz(s->nb_peers * sizeof(Peer)); + + pci_register_bar(&s->dev, 2, s->ivshmem_size, + PCI_BASE_ADDRESS_SPACE_MEMORY, ivshmem_map); + + s->eventfd_chr = qemu_mallocz(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) { + fprintf(stderr, "Must specify 'chardev' or 'shm' to ivshmem\n"); + } + + 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) { + fprintf(stderr, "ivshmem: could not truncate shared file\n"); + } + + } else if ((fd = shm_open(s->shmobj, O_CREAT|O_RDWR, + S_IRWXU|S_IRWXG|S_IRWXO)) < 0) { + fprintf(stderr, "ivshmem: could not open shared file\n"); + exit(-1); + + } + + if (check_shm_size(s, fd) == -1) { + exit(-1); + } + + create_shared_memory_BAR(s, fd); + + } + + return 0; +} + +static int pci_ivshmem_uninit(PCIDevice *dev) +{ + IVShmemState *s = DO_UPCAST(IVShmemState, dev, dev); + + cpu_unregister_io_memory(s->ivshmem_mmio_io_addr); + unregister_savevm(&dev->qdev, "ivshmem", s); + + return 0; +} + +static PCIDeviceInfo ivshmem_info = { + .qdev.name = "ivshmem", + .qdev.size = sizeof(IVShmemState), + .qdev.reset = ivshmem_reset, + .init = pci_ivshmem_init, + .exit = pci_ivshmem_uninit, + .qdev.props = (Property[]) { + 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_END_OF_LIST(), + } +}; + +static void ivshmem_register_devices(void) +{ + pci_qdev_register(&ivshmem_info); +} + +device_init(ivshmem_register_devices) diff --git a/hw/jazz_led.c b/hw/jazz_led.c new file mode 100644 index 0000000..1dc22cf --- /dev/null +++ b/hw/jazz_led.c @@ -0,0 +1,328 @@ +/* + * QEMU JAZZ LED emulator. + * + * 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.h" +#include "mips.h" +#include "console.h" +#include "pixel_ops.h" + +//#define DEBUG_LED + +#ifdef DEBUG_LED +#define DPRINTF(fmt, ...) \ +do { printf("jazz led: " fmt , ## __VA_ARGS__); } while (0) +#else +#define DPRINTF(fmt, ...) do {} while (0) +#endif +#define BADF(fmt, ...) \ +do { fprintf(stderr, "jazz led ERROR: " fmt , ## __VA_ARGS__);} while (0) + +typedef enum { + REDRAW_NONE = 0, REDRAW_SEGMENTS = 1, REDRAW_BACKGROUND = 2, +} screen_state_t; + +typedef struct LedState { + uint8_t segments; + DisplayState *ds; + screen_state_t state; +} LedState; + +static uint32_t led_readb(void *opaque, target_phys_addr_t addr) +{ + LedState *s = opaque; + uint32_t val; + + switch (addr) { + case 0: + val = s->segments; + break; + default: + BADF("invalid read at [" TARGET_FMT_plx "]\n", addr); + val = 0; + } + + DPRINTF("read addr=" TARGET_FMT_plx " val=0x%02x\n", addr, val); + + return val; +} + +static uint32_t led_readw(void *opaque, target_phys_addr_t addr) +{ + uint32_t v; +#ifdef TARGET_WORDS_BIGENDIAN + v = led_readb(opaque, addr) << 8; + v |= led_readb(opaque, addr + 1); +#else + v = led_readb(opaque, addr); + v |= led_readb(opaque, addr + 1) << 8; +#endif + return v; +} + +static uint32_t led_readl(void *opaque, target_phys_addr_t addr) +{ + uint32_t v; +#ifdef TARGET_WORDS_BIGENDIAN + v = led_readb(opaque, addr) << 24; + v |= led_readb(opaque, addr + 1) << 16; + v |= led_readb(opaque, addr + 2) << 8; + v |= led_readb(opaque, addr + 3); +#else + v = led_readb(opaque, addr); + v |= led_readb(opaque, addr + 1) << 8; + v |= led_readb(opaque, addr + 2) << 16; + v |= led_readb(opaque, addr + 3) << 24; +#endif + return v; +} + +static void led_writeb(void *opaque, target_phys_addr_t addr, uint32_t val) +{ + LedState *s = opaque; + + DPRINTF("write addr=" TARGET_FMT_plx " val=0x%02x\n", addr, val); + + switch (addr) { + case 0: + s->segments = val; + s->state |= REDRAW_SEGMENTS; + break; + default: + BADF("invalid write of 0x%08x at [" TARGET_FMT_plx "]\n", val, addr); + break; + } +} + +static void led_writew(void *opaque, target_phys_addr_t addr, uint32_t val) +{ +#ifdef TARGET_WORDS_BIGENDIAN + led_writeb(opaque, addr, (val >> 8) & 0xff); + led_writeb(opaque, addr + 1, val & 0xff); +#else + led_writeb(opaque, addr, val & 0xff); + led_writeb(opaque, addr + 1, (val >> 8) & 0xff); +#endif +} + +static void led_writel(void *opaque, target_phys_addr_t addr, uint32_t val) +{ +#ifdef TARGET_WORDS_BIGENDIAN + led_writeb(opaque, addr, (val >> 24) & 0xff); + led_writeb(opaque, addr + 1, (val >> 16) & 0xff); + led_writeb(opaque, addr + 2, (val >> 8) & 0xff); + led_writeb(opaque, addr + 3, val & 0xff); +#else + led_writeb(opaque, addr, val & 0xff); + led_writeb(opaque, addr + 1, (val >> 8) & 0xff); + led_writeb(opaque, addr + 2, (val >> 16) & 0xff); + led_writeb(opaque, addr + 3, (val >> 24) & 0xff); +#endif +} + +static CPUReadMemoryFunc * const led_read[3] = { + led_readb, + led_readw, + led_readl, +}; + +static CPUWriteMemoryFunc * const led_write[3] = { + led_writeb, + led_writew, + led_writel, +}; + +/***********************************************************/ +/* jazz_led display */ + +static void draw_horizontal_line(DisplayState *ds, int posy, int posx1, int posx2, uint32_t color) +{ + uint8_t *d; + int x, bpp; + + bpp = (ds_get_bits_per_pixel(ds) + 7) >> 3; + d = ds_get_data(ds) + ds_get_linesize(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(DisplayState *ds, int posx, int posy1, int posy2, uint32_t color) +{ + uint8_t *d; + int y, bpp; + + bpp = (ds_get_bits_per_pixel(ds) + 7) >> 3; + d = ds_get_data(ds) + ds_get_linesize(ds) * posy1 + bpp * posx; + switch(bpp) { + case 1: + for (y = posy1; y <= posy2; y++) { + *((uint8_t *)d) = color; + d += ds_get_linesize(ds); + } + break; + case 2: + for (y = posy1; y <= posy2; y++) { + *((uint16_t *)d) = color; + d += ds_get_linesize(ds); + } + break; + case 4: + for (y = posy1; y <= posy2; y++) { + *((uint32_t *)d) = color; + d += ds_get_linesize(ds); + } + break; + } +} + +static void jazz_led_update_display(void *opaque) +{ + LedState *s = opaque; + DisplayState *ds = s->ds; + uint8_t *d1; + uint32_t color_segment, color_led; + int y, bpp; + + if (s->state & REDRAW_BACKGROUND) { + /* clear screen */ + bpp = (ds_get_bits_per_pixel(ds) + 7) >> 3; + d1 = ds_get_data(ds); + for (y = 0; y < ds_get_height(ds); y++) { + memset(d1, 0x00, ds_get_width(ds) * bpp); + d1 += ds_get_linesize(ds); + } + } + + if (s->state & REDRAW_SEGMENTS) { + /* set colors according to bpp */ + switch (ds_get_bits_per_pixel(ds)) { + 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); + 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(ds, 40, 10, 40, (s->segments & 0x02) ? color_segment : 0); + draw_vertical_line(ds, 10, 10, 40, (s->segments & 0x04) ? color_segment : 0); + draw_vertical_line(ds, 10, 40, 70, (s->segments & 0x08) ? color_segment : 0); + draw_horizontal_line(ds, 70, 10, 40, (s->segments & 0x10) ? color_segment : 0); + draw_vertical_line(ds, 40, 40, 70, (s->segments & 0x20) ? color_segment : 0); + draw_vertical_line(ds, 40, 10, 40, (s->segments & 0x40) ? color_segment : 0); + draw_horizontal_line(ds, 10, 10, 40, (s->segments & 0x80) ? color_segment : 0); + + /* display led */ + if (!(s->segments & 0x01)) + color_led = 0; /* black */ + draw_horizontal_line(ds, 68, 50, 50, color_led); + draw_horizontal_line(ds, 69, 49, 51, color_led); + draw_horizontal_line(ds, 70, 48, 52, color_led); + draw_horizontal_line(ds, 71, 49, 51, color_led); + draw_horizontal_line(ds, 72, 50, 50, color_led); + } + + s->state = REDRAW_NONE; + dpy_update(ds, 0, 0, ds_get_width(ds), ds_get_height(ds)); +} + +static void jazz_led_invalidate_display(void *opaque) +{ + LedState *s = opaque; + s->state |= REDRAW_SEGMENTS | REDRAW_BACKGROUND; +} + +static void jazz_led_screen_dump(void *opaque, const char *filename) +{ + printf("jazz_led_screen_dump() not implemented\n"); +} + +static void jazz_led_text_update(void *opaque, console_ch_t *chardata) +{ + LedState *s = opaque; + char buf[2]; + + dpy_cursor(s->ds, -1, -1); + qemu_console_resize(s->ds, 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_update(s->ds, 0, 0, 2, 1); +} + +void jazz_led_init(target_phys_addr_t base) +{ + LedState *s; + int io; + + s = qemu_mallocz(sizeof(LedState)); + + s->state = REDRAW_SEGMENTS | REDRAW_BACKGROUND; + + io = cpu_register_io_memory(led_read, led_write, s, + DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(base, 1, io); + + s->ds = graphic_console_init(jazz_led_update_display, + jazz_led_invalidate_display, + jazz_led_screen_dump, + jazz_led_text_update, s); + qemu_console_resize(s->ds, 60, 80); +} diff --git a/hw/lan9118.c b/hw/lan9118.c new file mode 100644 index 0000000..4ce9eb3 --- /dev/null +++ b/hw/lan9118.c @@ -0,0 +1,1190 @@ +/* + * SMSC LAN9118 Ethernet interface emulation + * + * Copyright (c) 2009 CodeSourcery, LLC. + * Written by Paul Brook + * + * This code is licenced under the GNU GPL v2 + */ + +#include "sysbus.h" +#include "net.h" +#include "devices.h" +#include "sysemu.h" +/* For crc32 */ +#include + +//#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 { + enum tx_state state; + uint32_t cmd_a; + uint32_t cmd_b; + int buffer_size; + int offset; + int pad; + int fifo_used; + int len; + uint8_t data[2048]; +} LAN9118Packet; + +typedef struct { + SysBusDevice busdev; + NICState *nic; + NICConf conf; + qemu_irq irq; + int mmio_index; + 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; + + int eeprom_writable; + uint8_t eeprom[128]; + + int tx_fifo_size; + LAN9118Packet *txp; + LAN9118Packet tx_packet; + + int tx_status_fifo_used; + int tx_status_fifo_head; + uint32_t tx_status_fifo[512]; + + int rx_status_fifo_size; + int rx_status_fifo_used; + int rx_status_fifo_head; + uint32_t rx_status_fifo[896]; + int rx_fifo_size; + int rx_fifo_used; + int rx_fifo_head; + uint32_t rx_fifo[3360]; + int rx_packet_size_head; + int rx_packet_size_tail; + int rx_packet_size[1024]; + + int rxp_offset; + int rxp_size; + int rxp_pad; +} lan9118_state; + +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; + } + qemu_set_irq(s->irq, level); +} + +static void lan9118_mac_changed(lan9118_state *s) +{ + qemu_format_nic_info_str(&s->nic->nc, 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 (s->nic->nc.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(VLANClientState *nc) +{ + phy_update_link(DO_UPCAST(NICState, nc, nc)->opaque); +} + +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 = FROM_SYSBUS(lan9118_state, sysbus_from_qdev(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 = 0x00050000; + 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_get_clock(vm_clock) / 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; + + phy_reset(s); + + s->eeprom_writable = 0; + lan9118_reload_eeprom(s); +} + +static int lan9118_can_receive(VLANClientState *nc) +{ + return 1; +} + +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 = (crc32(~0, addr, 6) >> 26); + if (hash & 0x20) { + return (s->mac_hashh >> (hash & 0x1f)) & 1; + } else { + return (s->mac_hashl >> (hash & 0x1f)) & 1; + } + } +} + +static ssize_t lan9118_receive(VLANClientState *nc, const uint8_t *buf, + size_t size) +{ + lan9118_state *s = DO_UPCAST(NICState, nc, nc)->opaque; + 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(&s->nic->nc, s->txp->data, s->txp->len); + } else { + qemu_send_packet(&s->nic->nc, 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; + break; + case TX_B: + if (s->txp->cmd_a & 0x2000) { + /* First segment */ + s->txp->cmd_b = val; + s->txp->fifo_used++; + s->txp->buffer_size = s->txp->cmd_a & 0x7ff; + s->txp->offset = (s->txp->cmd_a >> 16) & 0x1f; + /* 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 = 4; + 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 advertisment */ + 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 imediately. */ + if (val & 0x1000) { + s->phy_status |= 0x0020; + } + break; + case 4: /* Auto-neg advertisment */ + 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"); + } + 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, target_phys_addr_t offset, + uint32_t val) +{ + 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. */ + s->irq_cfg = (s->irq_cfg & IRQ_INT) | (val & IRQ_EN); + 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(&s->busdev.qdev); + } else { + s->hw_cfg = val & 0x003f300; + } + 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, val); + break; + } + lan9118_update(s); +} + +static uint32_t lan9118_readl(void *opaque, target_phys_addr_t offset) +{ + 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 | 0x4; + 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_get_clock(vm_clock) / 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 CPUReadMemoryFunc * const lan9118_readfn[] = { + lan9118_readl, + lan9118_readl, + lan9118_readl +}; + +static CPUWriteMemoryFunc * const lan9118_writefn[] = { + lan9118_writel, + lan9118_writel, + lan9118_writel +}; + +static void lan9118_cleanup(VLANClientState *nc) +{ + lan9118_state *s = DO_UPCAST(NICState, nc, nc)->opaque; + + s->nic = NULL; +} + +static NetClientInfo net_lan9118_info = { + .type = NET_CLIENT_TYPE_NIC, + .size = sizeof(NICState), + .can_receive = lan9118_can_receive, + .receive = lan9118_receive, + .cleanup = lan9118_cleanup, + .link_status_changed = lan9118_set_link, +}; + +static int lan9118_init1(SysBusDevice *dev) +{ + lan9118_state *s = FROM_SYSBUS(lan9118_state, dev); + QEMUBH *bh; + int i; + + s->mmio_index = cpu_register_io_memory(lan9118_readfn, + lan9118_writefn, s, + DEVICE_NATIVE_ENDIAN); + sysbus_init_mmio(dev, 0x100, s->mmio_index); + sysbus_init_irq(dev, &s->irq); + qemu_macaddr_default_if_unset(&s->conf.macaddr); + + s->nic = qemu_new_nic(&net_lan9118_info, &s->conf, + dev->qdev.info->name, dev->qdev.id, s); + qemu_format_nic_info_str(&s->nic->nc, 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); + + /* ??? Save/restore. */ + return 0; +} + +static SysBusDeviceInfo lan9118_info = { + .init = lan9118_init1, + .qdev.name = "lan9118", + .qdev.size = sizeof(lan9118_state), + .qdev.reset = lan9118_reset, + .qdev.props = (Property[]) { + DEFINE_NIC_PROPERTIES(lan9118_state, conf), + DEFINE_PROP_END_OF_LIST(), + } +}; + +static void lan9118_register_devices(void) +{ + sysbus_register_withprop(&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, "lan9118"); + qdev_set_nic_properties(dev, nd); + qdev_init_nofail(dev); + s = sysbus_from_qdev(dev); + sysbus_mmio_map(s, 0, base); + sysbus_connect_irq(s, 0, irq); +} + +device_init(lan9118_register_devices) diff --git a/hw/lance.c b/hw/lance.c new file mode 100644 index 0000000..ddb1cbb --- /dev/null +++ b/hw/lance.c @@ -0,0 +1,160 @@ +/* + * 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 "sysbus.h" +#include "net.h" +#include "qemu-timer.h" +#include "qemu_socket.h" +#include "sun4m.h" +#include "pcnet.h" +#include "trace.h" + +typedef struct { + SysBusDevice busdev; + 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_writew(void *opaque, target_phys_addr_t addr, + uint32_t val) +{ + SysBusPCNetState *d = opaque; + + trace_lance_mem_writew(addr, val & 0xffff); + pcnet_ioport_writew(&d->state, addr, val & 0xffff); +} + +static uint32_t lance_mem_readw(void *opaque, target_phys_addr_t addr) +{ + SysBusPCNetState *d = opaque; + uint32_t val; + + val = pcnet_ioport_readw(&d->state, addr); + trace_lance_mem_readw(addr, val & 0xffff); + return val & 0xffff; +} + +static CPUReadMemoryFunc * const lance_mem_read[3] = { + NULL, + lance_mem_readw, + NULL, +}; + +static CPUWriteMemoryFunc * const lance_mem_write[3] = { + NULL, + lance_mem_writew, + NULL, +}; + +static void lance_cleanup(VLANClientState *nc) +{ + PCNetState *d = DO_UPCAST(NICState, nc, nc)->opaque; + + pcnet_common_cleanup(d); +} + +static NetClientInfo net_lance_info = { + .type = NET_CLIENT_TYPE_NIC, + .size = sizeof(NICState), + .can_receive = pcnet_can_receive, + .receive = pcnet_receive, + .cleanup = lance_cleanup, +}; + +static const VMStateDescription vmstate_lance = { + .name = "pcnet", + .version_id = 3, + .minimum_version_id = 2, + .minimum_version_id_old = 2, + .fields = (VMStateField []) { + VMSTATE_STRUCT(state, SysBusPCNetState, 0, vmstate_pcnet, PCNetState), + VMSTATE_END_OF_LIST() + } +}; + +static int lance_init(SysBusDevice *dev) +{ + SysBusPCNetState *d = FROM_SYSBUS(SysBusPCNetState, dev); + PCNetState *s = &d->state; + + s->mmio_index = + cpu_register_io_memory(lance_mem_read, lance_mem_write, d, + DEVICE_NATIVE_ENDIAN); + + qdev_init_gpio_in(&dev->qdev, parent_lance_reset, 1); + + sysbus_init_mmio(dev, 4, s->mmio_index); + + sysbus_init_irq(dev, &s->irq); + + s->phys_mem_read = ledma_memory_read; + s->phys_mem_write = ledma_memory_write; + return pcnet_common_init(&dev->qdev, s, &net_lance_info); +} + +static void lance_reset(DeviceState *dev) +{ + SysBusPCNetState *d = DO_UPCAST(SysBusPCNetState, busdev.qdev, dev); + + pcnet_h_reset(&d->state); +} + +static SysBusDeviceInfo lance_info = { + .init = lance_init, + .qdev.name = "lance", + .qdev.fw_name = "ethernet", + .qdev.size = sizeof(SysBusPCNetState), + .qdev.reset = lance_reset, + .qdev.vmsd = &vmstate_lance, + .qdev.props = (Property[]) { + DEFINE_PROP_PTR("dma", SysBusPCNetState, state.dma_opaque), + DEFINE_NIC_PROPERTIES(SysBusPCNetState, state.conf), + DEFINE_PROP_END_OF_LIST(), + } +}; + +static void lance_register_devices(void) +{ + sysbus_register_withprop(&lance_info); +} +device_init(lance_register_devices) diff --git a/hw/leon3.c b/hw/leon3.c new file mode 100644 index 0000000..919f49f --- /dev/null +++ b/hw/leon3.c @@ -0,0 +1,217 @@ +/* + * 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.h" +#include "qemu-timer.h" +#include "qemu-char.h" +#include "sysemu.h" +#include "boards.h" +#include "loader.h" +#include "elf.h" +#include "trace.h" + +#include "grlib.h" + +/* Default system clock. */ +#define CPU_CLK (40 * 1000 * 1000) + +#define PROM_FILENAME "u-boot.bin" + +#define MAX_PILS 16 + +typedef struct ResetData { + CPUState *env; + uint32_t entry; /* save kernel entry in case of reset */ +} ResetData; + +static void main_cpu_reset(void *opaque) +{ + ResetData *s = (ResetData *)opaque; + CPUState *env = s->env; + + cpu_reset(env); + + env->halted = 0; + env->pc = s->entry; + env->npc = s->entry + 4; +} + +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) +{ + CPUState *env = (CPUState *)opaque; + + 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) { + trace_leon3_set_irq(i); + cpu_interrupt(env, CPU_INTERRUPT_HARD); + } + break; + } + } + } else if (!env->pil_in && (env->interrupt_index & ~15) == TT_EXTINT) { + trace_leon3_reset_irq(env->interrupt_index & 15); + env->interrupt_index = 0; + cpu_reset_interrupt(env, CPU_INTERRUPT_HARD); + } +} + +static void leon3_generic_hw_init(ram_addr_t ram_size, + const char *boot_device, + const char *kernel_filename, + const char *kernel_cmdline, + const char *initrd_filename, + const char *cpu_model) +{ + CPUState *env; + ram_addr_t ram_offset, prom_offset; + 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"; + } + + env = cpu_init(cpu_model); + if (!env) { + fprintf(stderr, "qemu: Unable to find Sparc CPU definition\n"); + exit(1); + } + + cpu_sparc_set_id(env, 0); + + /* Reset data */ + reset_info = qemu_mallocz(sizeof(ResetData)); + reset_info->env = env; + 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); + } + + ram_offset = qemu_ram_alloc(NULL, "leon3.ram", ram_size); + cpu_register_physical_memory(0x40000000, ram_size, ram_offset | IO_MEM_RAM); + + /* Allocate BIOS */ + prom_size = 8 * 1024 * 1024; /* 8Mb */ + prom_offset = qemu_ram_alloc(NULL, "Leon3.bios", prom_size); + cpu_register_physical_memory(0x00000000, prom_size, + prom_offset | IO_MEM_ROM); + + /* 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) { + fprintf(stderr, "Can't read bios image %s\n", filename); + exit(1); + } + + /* 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]); + } +} + +QEMUMachine leon3_generic_machine = { + .name = "leon3_generic", + .desc = "Leon-3 generic", + .init = leon3_generic_hw_init, + .use_scsi = 0, +}; + +static void leon3_machine_init(void) +{ + qemu_register_machine(&leon3_generic_machine); +} + +machine_init(leon3_machine_init); diff --git a/hw/lm832x.c b/hw/lm832x.c new file mode 100644 index 0000000..ce7dcac --- /dev/null +++ b/hw/lm832x.c @@ -0,0 +1,512 @@ +/* + * National Semiconductor LM8322/8323 GPIO keyboard & PWM chips. + * + * Copyright (C) 2008 Nokia Corporation + * Written by Andrzej Zaborowski + * + * 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 . + */ + +#include "hw.h" +#include "i2c.h" +#include "qemu-timer.h" +#include "console.h" + +typedef struct { + i2c_slave i2c; + 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; + } + + qemu_del_timer(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(i2c_slave *i2c, enum i2c_event event) +{ + LM823KbdState *s = FROM_I2C_SLAVE(LM823KbdState, 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(i2c_slave *i2c) +{ + LM823KbdState *s = FROM_I2C_SLAVE(LM823KbdState, i2c); + + return lm_kbd_read(s, s->reg, s->i2c_cycle ++); +} + +static int lm_i2c_tx(i2c_slave *i2c, uint8_t data) +{ + LM823KbdState *s = (LM823KbdState *) 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, + .minimum_version_id_old = 0, + .post_load = lm_kbd_post_load, + .fields = (VMStateField []) { + VMSTATE_I2C_SLAVE(i2c, 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_ARRAY(pwm.tm, LM823KbdState, 3), + VMSTATE_END_OF_LIST() + } +}; + + +static int lm8323_init(i2c_slave *i2c) +{ + LM823KbdState *s = FROM_I2C_SLAVE(LM823KbdState, i2c); + + s->model = 0x8323; + s->pwm.tm[0] = qemu_new_timer(vm_clock, lm_kbd_pwm0_tick, s); + s->pwm.tm[1] = qemu_new_timer(vm_clock, lm_kbd_pwm1_tick, s); + s->pwm.tm[2] = qemu_new_timer(vm_clock, lm_kbd_pwm2_tick, s); + qdev_init_gpio_out(&i2c->qdev, &s->nirq, 1); + + lm_kbd_reset(s); + + qemu_register_reset((void *) lm_kbd_reset, s); + return 0; +} + +void lm832x_key_event(struct i2c_slave *i2c, int key, int state) +{ + LM823KbdState *s = (LM823KbdState *) i2c; + + 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 I2CSlaveInfo lm8323_info = { + .qdev.name = "lm8323", + .qdev.size = sizeof(LM823KbdState), + .qdev.vmsd = &vmstate_lm_kbd, + .init = lm8323_init, + .event = lm_i2c_event, + .recv = lm_i2c_rx, + .send = lm_i2c_tx +}; + +static void lm832x_register_devices(void) +{ + i2c_register_slave(&lm8323_info); +} + +device_init(lm832x_register_devices) diff --git a/hw/loader.c b/hw/loader.c new file mode 100644 index 0000000..35d792e --- /dev/null +++ b/hw/loader.c @@ -0,0 +1,795 @@ +/* + * 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 . + */ + +#include "hw.h" +#include "disas.h" +#include "monitor.h" +#include "sysemu.h" +#include "uboot_image.h" +#include "loader.h" +#include "fw_cfg.h" + +#include + +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); + lseek(fd, 0, SEEK_SET); + if (read(fd, addr, size) != size) { + close(fd); + return -1; + } + close(fd); + return size; +} + +/* read()-like version */ +int read_targphys(const char *name, + int fd, target_phys_addr_t dst_addr, size_t nbytes) +{ + uint8_t *buf; + size_t did; + + buf = qemu_malloc(nbytes); + did = read(fd, buf, nbytes); + if (did > 0) + rom_add_blob_fixed("read", buf, did, dst_addr); + qemu_free(buf); + return did; +} + +/* return the size or -1 if error */ +int load_image_targphys(const char *filename, + target_phys_addr_t addr, int max_sz) +{ + int size; + + size = get_image_size(filename); + if (size > 0) + rom_add_file_fixed(filename, addr, -1); + return size; +} + +void pstrcpy_targphys(const char *name, target_phys_addr_t 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, target_phys_addr_t addr, int max_sz, + int bswap_needed, target_phys_addr_t target_page_size) +{ + int fd, 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, int offset, int size) +{ + void *ptr; + if (lseek(fd, offset, SEEK_SET) < 0) + return NULL; + ptr = qemu_malloc(size); + if (read(fd, ptr, size) != size) { + qemu_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 "elf_ops.h" + +#undef elfhdr +#undef elf_phdr +#undef elf_shdr +#undef elf_sym +#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_word uint64_t +#define elf_sword int64_t +#define bswapSZs bswap64s +#define SZ 64 +#include "elf_ops.h" + +/* 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; + 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) + 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]) { + 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); + } + + close(fd); + return ret; + + fail: + close(fd); + return -1; +} + +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 = qemu_malloc(size); + + return (p); +} + +static void zfree(void *x, void *addr) +{ + qemu_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 maximum in uboot, so if a uImage overflows this, it would + * overflow on real hardware too. */ +#define UBOOT_MAX_GUNZIP_BYTES 0x800000 + +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. */ +int load_uimage(const char *filename, target_phys_addr_t *ep, + target_phys_addr_t *loadaddr, int *is_linux) +{ + int fd; + int size; + uboot_image_header_t h; + uboot_image_header_t *hdr = &h; + uint8_t *data = NULL; + int ret = -1; + + 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; + + /* TODO: Implement other image types. */ + if (hdr->ih_type != IH_TYPE_KERNEL) { + fprintf(stderr, "Can only load u-boot image type \"kernel\"\n"); + goto out; + } + + switch (hdr->ih_comp) { + case IH_COMP_NONE: + case IH_COMP_GZIP: + break; + default: + fprintf(stderr, + "Unable to load u-boot images with compression type %d\n", + hdr->ih_comp); + goto out; + } + + /* TODO: Check CPU type. */ + if (is_linux) { + if (hdr->ih_os == IH_OS_LINUX) + *is_linux = 1; + else + *is_linux = 0; + } + + *ep = hdr->ih_ep; + data = qemu_malloc(hdr->ih_size); + + if (read(fd, data, hdr->ih_size) != hdr->ih_size) { + fprintf(stderr, "Error reading file\n"); + goto out; + } + + if (hdr->ih_comp == IH_COMP_GZIP) { + uint8_t *compressed_data; + size_t max_bytes; + ssize_t bytes; + + compressed_data = data; + max_bytes = UBOOT_MAX_GUNZIP_BYTES; + data = qemu_malloc(max_bytes); + + bytes = gunzip(data, max_bytes, compressed_data, hdr->ih_size); + qemu_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, hdr->ih_load); + + if (loadaddr) + *loadaddr = hdr->ih_load; + + ret = hdr->ih_size; + +out: + if (data) + qemu_free(data); + close(fd); + return ret; +} + +/* + * 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; + size_t romsize; + uint8_t *data; + int isrom; + char *fw_dir; + char *fw_file; + + target_phys_addr_t 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); +} + +int rom_add_file(const char *file, const char *fw_dir, + target_phys_addr_t addr, int32_t bootindex) +{ + Rom *rom; + int rc, fd = -1; + char devpath[100]; + + rom = qemu_mallocz(sizeof(*rom)); + rom->name = qemu_strdup(file); + rom->path = qemu_find_file(QEMU_FILE_TYPE_BIOS, rom->name); + if (rom->path == NULL) { + rom->path = qemu_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 = qemu_strdup(fw_dir); + rom->fw_file = qemu_strdup(file); + } + rom->addr = addr; + rom->romsize = lseek(fd, 0, SEEK_END); + rom->data = qemu_mallocz(rom->romsize); + lseek(fd, 0, SEEK_SET); + rc = read(fd, rom->data, rom->romsize); + if (rc != rom->romsize) { + fprintf(stderr, "rom: file %-20s: read error: rc=%d (expected %zd)\n", + rom->name, rc, rom->romsize); + goto err; + } + close(fd); + rom_insert(rom); + if (rom->fw_file && fw_cfg) { + const char *basename; + char fw_file_name[56]; + + 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); + fw_cfg_add_file(fw_cfg, fw_file_name, rom->data, rom->romsize); + snprintf(devpath, sizeof(devpath), "/rom@%s", fw_file_name); + } 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); + qemu_free(rom->data); + qemu_free(rom->path); + qemu_free(rom->name); + qemu_free(rom); + return -1; +} + +int rom_add_blob(const char *name, const void *blob, size_t len, + target_phys_addr_t addr) +{ + Rom *rom; + + rom = qemu_mallocz(sizeof(*rom)); + rom->name = qemu_strdup(name); + rom->addr = addr; + rom->romsize = len; + rom->data = qemu_mallocz(rom->romsize); + memcpy(rom->data, blob, len); + rom_insert(rom); + return 0; +} + +int rom_add_vga(const char *file) +{ + return rom_add_file(file, "vgaroms", 0, -1); +} + +int rom_add_option(const char *file, int32_t bootindex) +{ + return rom_add_file(file, "genroms", 0, bootindex); +} + +static void rom_reset(void *unused) +{ + Rom *rom; + + QTAILQ_FOREACH(rom, &roms, next) { + if (rom->fw_file) { + continue; + } + if (rom->data == NULL) { + continue; + } + cpu_physical_memory_write_rom(rom->addr, rom->data, rom->romsize); + if (rom->isrom) { + /* rom needs to be written only once */ + qemu_free(rom->data); + rom->data = NULL; + } + } +} + +int rom_load_all(void) +{ + target_phys_addr_t addr = 0; + int memtype; + 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; + memtype = cpu_get_physical_page_desc(rom->addr) & (3 << IO_MEM_SHIFT); + if (memtype == IO_MEM_ROM) + rom->isrom = 1; + } + qemu_register_reset(rom_reset, NULL); + roms_loaded = 1; + return 0; +} + +void rom_set_fw(void *f) +{ + fw_cfg = f; +} + +static Rom *find_rom(target_phys_addr_t addr) +{ + Rom *rom; + + QTAILQ_FOREACH(rom, &roms, next) { + if (rom->fw_file) { + 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, target_phys_addr_t addr, size_t size) +{ + target_phys_addr_t 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->addr + rom->romsize < addr) { + continue; + } + if (rom->addr > end) { + break; + } + if (!rom->data) { + continue; + } + + d = dest + (rom->addr - addr); + s = rom->data; + l = rom->romsize; + + if ((d + l) > (dest + size)) { + l = dest - d; + } + + memcpy(d, s, l); + } + + return (d + l) - dest; +} + +void *rom_ptr(target_phys_addr_t addr) +{ + Rom *rom; + + rom = find_rom(addr); + if (!rom || !rom->data) + return NULL; + return rom->data + (addr - rom->addr); +} + +void do_info_roms(Monitor *mon) +{ + Rom *rom; + + QTAILQ_FOREACH(rom, &roms, next) { + 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/loader.h b/hw/loader.h new file mode 100644 index 0000000..fc6bdff --- /dev/null +++ b/hw/loader.h @@ -0,0 +1,48 @@ +#ifndef LOADER_H +#define LOADER_H + +/* loader.c */ +int get_image_size(const char *filename); +int load_image(const char *filename, uint8_t *addr); /* deprecated */ +int load_image_targphys(const char *filename, target_phys_addr_t, int max_sz); +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 load_aout(const char *filename, target_phys_addr_t addr, int max_sz, + int bswap_needed, target_phys_addr_t target_page_size); +int load_uimage(const char *filename, target_phys_addr_t *ep, + target_phys_addr_t *loadaddr, int *is_linux); + +int read_targphys(const char *name, + int fd, target_phys_addr_t dst_addr, size_t nbytes); +void pstrcpy_targphys(const char *name, + target_phys_addr_t dest, int buf_size, + const char *source); + + +int rom_add_file(const char *file, const char *fw_dir, + target_phys_addr_t addr, int32_t bootindex); +int rom_add_blob(const char *name, const void *blob, size_t len, + target_phys_addr_t addr); +int rom_load_all(void); +void rom_set_fw(void *f); +int rom_copy(uint8_t *dest, target_phys_addr_t addr, size_t size); +void *rom_ptr(target_phys_addr_t addr); +void do_info_roms(Monitor *mon); + +#define rom_add_file_fixed(_f, _a, _i) \ + rom_add_file(_f, NULL, _a, _i) +#define rom_add_blob_fixed(_f, _b, _l, _a) \ + rom_add_blob(_f, _b, _l, _a) + +#define PC_ROM_MIN_VGA 0xc0000 +#define PC_ROM_MIN_OPTION 0xc8000 +#define PC_ROM_MAX 0xe0000 +#define PC_ROM_ALIGN 0x800 +#define PC_ROM_SIZE (PC_ROM_MAX - PC_ROM_MIN_VGA) + +int rom_add_vga(const char *file); +int rom_add_option(const char *file, int32_t bootindex); + +#endif diff --git a/hw/lsi53c895a.c b/hw/lsi53c895a.c new file mode 100644 index 0000000..e4b51a8 --- /dev/null +++ b/hw/lsi53c895a.c @@ -0,0 +1,2276 @@ +/* + * QEMU LSI53C895A SCSI Host Bus Adapter emulation + * + * Copyright (c) 2006 CodeSourcery. + * Written by Paul Brook + * + * This code is licenced under the LGPL. + */ + +/* ??? Need to check if the {read,write}[wl] routines work properly on + big-endian targets. */ + +#include + +#include "hw.h" +#include "pci.h" +#include "scsi.h" +#include "block_int.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 { + 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 { + PCIDevice dev; + int mmio_io_addr; + int ram_io_addr; + uint32_t script_ram_base; + + int carry; /* ??? Should this be an a visible register somewhere? */ + int sense; + /* 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; + + /* Script ram is stored as 32-bit words in host byteorder. */ + uint32_t script_ram[2048]; +} LSIState; + +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) +{ + lsi_request *p; + + 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; + while (!QTAILQ_EMPTY(&s->queue)) { + p = QTAILQ_FIRST(&s->queue); + QTAILQ_REMOVE(&s->queue, p, next); + qemu_free(p); + } + if (s->current) { + qemu_free(s->current); + s->current = NULL; + } +} + +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; + + /* Optimize reading from SCRIPTS RAM. */ + if ((addr & 0xffffe000) == s->script_ram_base) { + return s->script_ram[(addr & 0x1fff) >> 2]; + } + cpu_physical_memory_read(addr, (uint8_t *)&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) +{ + 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; + } + qemu_set_irq(s->dev.irq[0], 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) +{ + uint32_t count, id; + target_phys_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; + } + + id = (s->current->tag >> 8) & 0xf; + dev = s->bus.devs[id]; + if (!dev) { + lsi_bad_selection(s, id); + return; + } + + 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" TARGET_FMT_plx " len=%d\n", addr, count); + s->csbc += count; + s->dnad += count; + s->dbc -= count; + + if (s->current->dma_buf == NULL) { + s->current->dma_buf = dev->info->get_buf(dev, s->current->tag); + } + + /* ??? Set SFBR to first data byte. */ + if (out) { + cpu_physical_memory_read(addr, s->current->dma_buf, count); + } else { + cpu_physical_memory_write(addr, s->current->dma_buf, count); + } + s->current->dma_len -= count; + if (s->current->dma_len == 0) { + s->current->dma_buf = NULL; + if (out) { + /* Write the data. */ + dev->info->write_data(dev, s->current->tag); + } else { + /* Request any remaining data. */ + dev->info->read_data(dev, s->current->tag); + } + } 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); + } +} + +/* 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_tag(LSIState *s, uint32_t tag, uint32_t arg) +{ + lsi_request *p; + + QTAILQ_FOREACH(p, &s->queue, next) { + if (p->tag == tag) { + if (p->pending) { + BADF("Multiple IO pending for tag %d\n", tag); + } + p->pending = arg; + /* 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", tag); + p->pending = arg; + return 1; + } + } + } + BADF("IO with unknown tag %d\n", tag); + return 1; +} + +/* Callback to indicate that the SCSI layer has completed a transfer. */ +static void lsi_command_complete(SCSIBus *bus, int reason, uint32_t tag, + uint32_t arg) +{ + LSIState *s = DO_UPCAST(LSIState, dev.qdev, bus->qbus.parent); + int out; + + out = (s->sstat1 & PHASE_MASK) == PHASE_DO; + if (reason == SCSI_REASON_DONE) { + DPRINTF("Command complete sense=%d\n", (int)arg); + s->sense = arg; + 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); + } + + qemu_free(s->current); + s->current = NULL; + + lsi_resume_script(s); + return; + } + + if (s->waiting == 1 || !s->current || tag != s->current->tag || + (lsi_irq_on_rsl(s) && !(s->scntl1 & LSI_SCNTL1_CON))) { + if (lsi_queue_tag(s, tag, arg)) + return; + } + + /* host adapter (re)connected */ + DPRINTF("Data ready tag=0x%x len=%d\n", tag, arg); + s->current->dma_len = arg; + s->command_complete = 1; + if (!s->waiting) + return; + 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; + cpu_physical_memory_read(s->dnad, buf, s->dbc); + s->sfbr = buf[0]; + s->command_complete = 0; + + id = (s->select_tag >> 8) & 0xf; + dev = s->bus.devs[id]; + if (!dev) { + lsi_bad_selection(s, id); + return; + } + + assert(s->current == NULL); + s->current = qemu_mallocz(sizeof(lsi_request)); + s->current->tag = s->select_tag; + + n = dev->info->send_command(dev, s->current->tag, buf, s->current_lun); + if (n > 0) { + lsi_set_phase(s, PHASE_DI); + dev->info->read_data(dev, s->current->tag); + } else if (n < 0) { + lsi_set_phase(s, PHASE_DO); + dev->info->write_data(dev, s->current->tag); + } + + 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 sense; + DPRINTF("Get status len=%d sense=%d\n", s->dbc, s->sense); + if (s->dbc != 1) + BADF("Bad Status move\n"); + s->dbc = 1; + sense = s->sense; + s->sfbr = sense; + cpu_physical_memory_write(s->dnad, &sense, 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; + cpu_physical_memory_write(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; + cpu_physical_memory_read(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; + SCSIDevice *current_dev; + lsi_request *p, *p_next; + int id; + + if (s->current) { + current_tag = s->current->tag; + } else { + current_tag = s->select_tag; + } + id = (current_tag >> 8) & 0xf; + current_dev = s->bus.devs[id]; + + 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); + current_dev->info->cancel_io(current_dev, current_tag); + 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 */ + current_dev->info->cancel_io(current_dev, current_tag); + + /* 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: */ + id = current_tag & 0x0000ff00; + QTAILQ_FOREACH_SAFE(p, &s->queue, next, p_next) { + if ((p->tag & 0x0000ff00) == id) { + current_dev->info->cancel_io(current_dev, p->tag); + QTAILQ_REMOVE(&s->queue, p, next); + } + } + + 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; +} + +/* Sign extend a 24-bit value. */ +static inline int32_t sxt24(int32_t n) +{ + return (n << 8) >> 8; +} + +#define LSI_BUF_SIZE 4096 +static void lsi_memcpy(LSIState *s, uint32_t dest, uint32_t src, int count) +{ + 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; + cpu_physical_memory_read(src, buf, n); + cpu_physical_memory_write(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) +{ + 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 = sxt24(addr); + cpu_physical_memory_read(s->dsa + offset, (uint8_t *)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 + sxt24(insn)); + } else { + id = insn; + } + id = (id >> 16) & 0xf; + if (insn & (1 << 26)) { + addr = s->dsp + sxt24(addr); + } + 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 (id >= LSI_MAX_DEVS || !s->bus.devs[id]) { + 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 + sxt24(addr); + } + switch ((insn >> 27) & 7) { + case 0: /* Jump */ + DPRINTF("Jump to 0x%08x\n", 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 + sxt24(addr); + } + n = (insn & 7); + reg = (insn >> 16) & 0xff; + if (insn & (1 << 24)) { + cpu_physical_memory_read(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); + } + cpu_physical_memory_write(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 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 | 0x80; + 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; + 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)) { + DeviceState *dev; + int id; + + for (id = 0; id < s->bus.ndev; id++) { + if (s->bus.devs[id]) { + dev = &s->bus.devs[id]->qdev; + dev->info->reset(dev); + } + } + 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 ((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) { + lsi_soft_reset(s); + } + break; + case 0x16: /* MBOX0 */ + s->mbox0 = val; + break; + case 0x17: /* MBOX1 */ + s->mbox1 = val; + 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] &= ~(0xff << shift); + s->scratch[n] |= (val & 0xff) << shift; + } else { + BADF("Unhandled writeb 0x%x = 0x%x\n", offset, val); + } + } +#undef CASE_SET_REG24 +#undef CASE_SET_REG32 +} + +static void lsi_mmio_writeb(void *opaque, target_phys_addr_t addr, uint32_t val) +{ + LSIState *s = opaque; + + lsi_reg_writeb(s, addr & 0xff, val); +} + +static void lsi_mmio_writew(void *opaque, target_phys_addr_t addr, uint32_t val) +{ + LSIState *s = opaque; + + addr &= 0xff; + lsi_reg_writeb(s, addr, val & 0xff); + lsi_reg_writeb(s, addr + 1, (val >> 8) & 0xff); +} + +static void lsi_mmio_writel(void *opaque, target_phys_addr_t addr, uint32_t val) +{ + LSIState *s = opaque; + + addr &= 0xff; + lsi_reg_writeb(s, addr, val & 0xff); + lsi_reg_writeb(s, addr + 1, (val >> 8) & 0xff); + lsi_reg_writeb(s, addr + 2, (val >> 16) & 0xff); + lsi_reg_writeb(s, addr + 3, (val >> 24) & 0xff); +} + +static uint32_t lsi_mmio_readb(void *opaque, target_phys_addr_t addr) +{ + LSIState *s = opaque; + + return lsi_reg_readb(s, addr & 0xff); +} + +static uint32_t lsi_mmio_readw(void *opaque, target_phys_addr_t addr) +{ + LSIState *s = opaque; + uint32_t val; + + addr &= 0xff; + val = lsi_reg_readb(s, addr); + val |= lsi_reg_readb(s, addr + 1) << 8; + return val; +} + +static uint32_t lsi_mmio_readl(void *opaque, target_phys_addr_t addr) +{ + LSIState *s = opaque; + uint32_t val; + addr &= 0xff; + val = lsi_reg_readb(s, addr); + val |= lsi_reg_readb(s, addr + 1) << 8; + val |= lsi_reg_readb(s, addr + 2) << 16; + val |= lsi_reg_readb(s, addr + 3) << 24; + return val; +} + +static CPUReadMemoryFunc * const lsi_mmio_readfn[3] = { + lsi_mmio_readb, + lsi_mmio_readw, + lsi_mmio_readl, +}; + +static CPUWriteMemoryFunc * const lsi_mmio_writefn[3] = { + lsi_mmio_writeb, + lsi_mmio_writew, + lsi_mmio_writel, +}; + +static void lsi_ram_writeb(void *opaque, target_phys_addr_t addr, uint32_t val) +{ + LSIState *s = opaque; + uint32_t newval; + int shift; + + addr &= 0x1fff; + newval = s->script_ram[addr >> 2]; + shift = (addr & 3) * 8; + newval &= ~(0xff << shift); + newval |= val << shift; + s->script_ram[addr >> 2] = newval; +} + +static void lsi_ram_writew(void *opaque, target_phys_addr_t addr, uint32_t val) +{ + LSIState *s = opaque; + uint32_t newval; + + addr &= 0x1fff; + newval = s->script_ram[addr >> 2]; + if (addr & 2) { + newval = (newval & 0xffff) | (val << 16); + } else { + newval = (newval & 0xffff0000) | val; + } + s->script_ram[addr >> 2] = newval; +} + + +static void lsi_ram_writel(void *opaque, target_phys_addr_t addr, uint32_t val) +{ + LSIState *s = opaque; + + addr &= 0x1fff; + s->script_ram[addr >> 2] = val; +} + +static uint32_t lsi_ram_readb(void *opaque, target_phys_addr_t addr) +{ + LSIState *s = opaque; + uint32_t val; + + addr &= 0x1fff; + val = s->script_ram[addr >> 2]; + val >>= (addr & 3) * 8; + return val & 0xff; +} + +static uint32_t lsi_ram_readw(void *opaque, target_phys_addr_t addr) +{ + LSIState *s = opaque; + uint32_t val; + + addr &= 0x1fff; + val = s->script_ram[addr >> 2]; + if (addr & 2) + val >>= 16; + return val; +} + +static uint32_t lsi_ram_readl(void *opaque, target_phys_addr_t addr) +{ + LSIState *s = opaque; + + addr &= 0x1fff; + return s->script_ram[addr >> 2]; +} + +static CPUReadMemoryFunc * const lsi_ram_readfn[3] = { + lsi_ram_readb, + lsi_ram_readw, + lsi_ram_readl, +}; + +static CPUWriteMemoryFunc * const lsi_ram_writefn[3] = { + lsi_ram_writeb, + lsi_ram_writew, + lsi_ram_writel, +}; + +static uint32_t lsi_io_readb(void *opaque, uint32_t addr) +{ + LSIState *s = opaque; + return lsi_reg_readb(s, addr & 0xff); +} + +static uint32_t lsi_io_readw(void *opaque, uint32_t addr) +{ + LSIState *s = opaque; + uint32_t val; + addr &= 0xff; + val = lsi_reg_readb(s, addr); + val |= lsi_reg_readb(s, addr + 1) << 8; + return val; +} + +static uint32_t lsi_io_readl(void *opaque, uint32_t addr) +{ + LSIState *s = opaque; + uint32_t val; + addr &= 0xff; + val = lsi_reg_readb(s, addr); + val |= lsi_reg_readb(s, addr + 1) << 8; + val |= lsi_reg_readb(s, addr + 2) << 16; + val |= lsi_reg_readb(s, addr + 3) << 24; + return val; +} + +static void lsi_io_writeb(void *opaque, uint32_t addr, uint32_t val) +{ + LSIState *s = opaque; + lsi_reg_writeb(s, addr & 0xff, val); +} + +static void lsi_io_writew(void *opaque, uint32_t addr, uint32_t val) +{ + LSIState *s = opaque; + addr &= 0xff; + lsi_reg_writeb(s, addr, val & 0xff); + lsi_reg_writeb(s, addr + 1, (val >> 8) & 0xff); +} + +static void lsi_io_writel(void *opaque, uint32_t addr, uint32_t val) +{ + LSIState *s = opaque; + addr &= 0xff; + lsi_reg_writeb(s, addr, val & 0xff); + lsi_reg_writeb(s, addr + 1, (val >> 8) & 0xff); + lsi_reg_writeb(s, addr + 2, (val >> 16) & 0xff); + lsi_reg_writeb(s, addr + 3, (val >> 24) & 0xff); +} + +static void lsi_io_mapfunc(PCIDevice *pci_dev, int region_num, + pcibus_t addr, pcibus_t size, int type) +{ + LSIState *s = DO_UPCAST(LSIState, dev, pci_dev); + + DPRINTF("Mapping IO at %08"FMT_PCIBUS"\n", addr); + + register_ioport_write(addr, 256, 1, lsi_io_writeb, s); + register_ioport_read(addr, 256, 1, lsi_io_readb, s); + register_ioport_write(addr, 256, 2, lsi_io_writew, s); + register_ioport_read(addr, 256, 2, lsi_io_readw, s); + register_ioport_write(addr, 256, 4, lsi_io_writel, s); + register_ioport_read(addr, 256, 4, lsi_io_readl, s); +} + +static void lsi_ram_mapfunc(PCIDevice *pci_dev, int region_num, + pcibus_t addr, pcibus_t size, int type) +{ + LSIState *s = DO_UPCAST(LSIState, dev, pci_dev); + + DPRINTF("Mapping ram at %08"FMT_PCIBUS"\n", addr); + s->script_ram_base = addr; + cpu_register_physical_memory(addr + 0, 0x2000, s->ram_io_addr); +} + +static void lsi_mmio_mapfunc(PCIDevice *pci_dev, int region_num, + pcibus_t addr, pcibus_t size, int type) +{ + LSIState *s = DO_UPCAST(LSIState, dev, pci_dev); + + DPRINTF("Mapping registers at %08"FMT_PCIBUS"\n", addr); + cpu_register_physical_memory(addr + 0, 0x400, s->mmio_io_addr); +} + +static void lsi_scsi_reset(DeviceState *dev) +{ + LSIState *s = DO_UPCAST(LSIState, dev.qdev, 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, + .minimum_version_id_old = 0, + .pre_save = lsi_pre_save, + .fields = (VMStateField []) { + VMSTATE_PCI_DEVICE(dev, LSIState), + + VMSTATE_INT32(carry, LSIState), + VMSTATE_INT32(sense, 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 int lsi_scsi_uninit(PCIDevice *d) +{ + LSIState *s = DO_UPCAST(LSIState, dev, d); + + cpu_unregister_io_memory(s->mmio_io_addr); + cpu_unregister_io_memory(s->ram_io_addr); + + return 0; +} + +static int lsi_scsi_init(PCIDevice *dev) +{ + LSIState *s = DO_UPCAST(LSIState, dev, dev); + uint8_t *pci_conf; + + pci_conf = s->dev.config; + + /* PCI Vendor ID (word) */ + pci_config_set_vendor_id(pci_conf, PCI_VENDOR_ID_LSI_LOGIC); + /* PCI device ID (word) */ + pci_config_set_device_id(pci_conf, PCI_DEVICE_ID_LSI_53C895A); + /* PCI base class code */ + pci_config_set_class(pci_conf, PCI_CLASS_STORAGE_SCSI); + /* PCI subsystem ID */ + pci_conf[PCI_SUBSYSTEM_ID] = 0x00; + pci_conf[PCI_SUBSYSTEM_ID + 1] = 0x10; + /* PCI latency timer = 255 */ + pci_conf[PCI_LATENCY_TIMER] = 0xff; + /* TODO: RST# value should be 0 */ + /* Interrupt pin 1 */ + pci_conf[PCI_INTERRUPT_PIN] = 0x01; + + s->mmio_io_addr = cpu_register_io_memory(lsi_mmio_readfn, + lsi_mmio_writefn, s, + DEVICE_NATIVE_ENDIAN); + s->ram_io_addr = cpu_register_io_memory(lsi_ram_readfn, + lsi_ram_writefn, s, + DEVICE_NATIVE_ENDIAN); + + pci_register_bar(&s->dev, 0, 256, + PCI_BASE_ADDRESS_SPACE_IO, lsi_io_mapfunc); + pci_register_bar(&s->dev, 1, 0x400, + PCI_BASE_ADDRESS_SPACE_MEMORY, lsi_mmio_mapfunc); + pci_register_bar(&s->dev, 2, 0x2000, + PCI_BASE_ADDRESS_SPACE_MEMORY, lsi_ram_mapfunc); + QTAILQ_INIT(&s->queue); + + scsi_bus_new(&s->bus, &dev->qdev, 1, LSI_MAX_DEVS, lsi_command_complete); + if (!dev->qdev.hotplugged) { + return scsi_bus_legacy_handle_cmdline(&s->bus); + } + return 0; +} + +static PCIDeviceInfo lsi_info = { + .qdev.name = "lsi53c895a", + .qdev.alias = "lsi", + .qdev.size = sizeof(LSIState), + .qdev.reset = lsi_scsi_reset, + .qdev.vmsd = &vmstate_lsi_scsi, + .init = lsi_scsi_init, + .exit = lsi_scsi_uninit, +}; + +static void lsi53c895a_register_devices(void) +{ + pci_qdev_register(&lsi_info); +} + +device_init(lsi53c895a_register_devices); diff --git a/hw/m48t59.c b/hw/m48t59.c new file mode 100644 index 0000000..2020487 --- /dev/null +++ b/hw/m48t59.c @@ -0,0 +1,762 @@ +/* + * QEMU M48T59 and M48T08 NVRAM emulation for PPC PREP and Sparc platforms + * + * Copyright (c) 2003-2005, 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.h" +#include "nvram.h" +#include "qemu-timer.h" +#include "sysemu.h" +#include "sysbus.h" +#include "isa.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 + +/* + * 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. + */ + +/* + * 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 + */ + +struct M48t59State { + /* Model parameters */ + uint32_t type; // 2 = m48t02, 8 = m48t08, 59 = m48t59 + /* Hardware parameters */ + qemu_irq IRQ; + uint32_t io_base; + uint32_t size; + /* RTC management */ + time_t time_offset; + time_t stop_time; + /* Alarm & watchdog */ + struct tm alarm; + struct QEMUTimer *alrm_timer; + struct QEMUTimer *wd_timer; + /* NVRAM storage */ + uint8_t lock; + uint16_t addr; + uint8_t *buffer; +}; + +typedef struct M48t59ISAState { + ISADevice busdev; + M48t59State state; +} M48t59ISAState; + +typedef struct M48t59SysBusState { + SysBusDevice busdev; + M48t59State state; +} M48t59SysBusState; + +/* 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; + } + qemu_mod_timer(NVRAM->alrm_timer, qemu_get_clock(vm_clock) + + next_time * 1000); + qemu_set_irq(NVRAM->IRQ, 0); +} + +static void set_alarm(M48t59State *NVRAM) +{ + int diff; + if (NVRAM->alrm_timer != NULL) { + qemu_del_timer(NVRAM->alrm_timer); + diff = qemu_timedate_diff(&NVRAM->alarm) - NVRAM->time_offset; + if (diff > 0) + qemu_mod_timer(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) { + qemu_del_timer(NVRAM->wd_timer); + if (value != 0) { + interval = (1 << (2 * (value & 0x03))) * ((value >> 2) & 0x1F); + qemu_mod_timer(NVRAM->wd_timer, ((uint64_t)time(NULL) * 1000) + + ((interval * 1000) >> 4)); + } + } +} + +/* Direct access to NVRAM */ +void m48t59_write (void *opaque, uint32_t addr, uint32_t val) +{ + M48t59State *NVRAM = opaque; + 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->type == 2 && addr < 0x7f8) || + (NVRAM->type == 8 && addr < 0x1ff8) || + (NVRAM->type == 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 & 0x1F); + 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 */ + tmp = from_bcd(val & 0x1F); + 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); + if (NVRAM->type == 8) + tm.tm_year = from_bcd(val) + 68; // Base year is 1968 + else + tm.tm_year = from_bcd(val); + 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; + } +} + +uint32_t m48t59_read (void *opaque, uint32_t addr) +{ + M48t59State *NVRAM = opaque; + struct tm tm; + uint32_t retval = 0xFF; + + /* check for NVRAM access */ + if ((NVRAM->type == 2 && addr < 0x078f) || + (NVRAM->type == 8 && addr < 0x1ff8) || + (NVRAM->type == 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); + if (NVRAM->type == 8) + retval = to_bcd(tm.tm_year - 68); // Base year is 1968 + else + retval = to_bcd(tm.tm_year); + 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; +} + +void m48t59_set_addr (void *opaque, uint32_t addr) +{ + M48t59State *NVRAM = opaque; + + NVRAM->addr = addr; +} + +void m48t59_toggle_lock (void *opaque, int lock) +{ + M48t59State *NVRAM = opaque; + + NVRAM->lock ^= 1 << lock; +} + +/* IO access to NVRAM */ +static void NVRAM_writeb (void *opaque, uint32_t addr, uint32_t val) +{ + M48t59State *NVRAM = opaque; + + addr -= NVRAM->io_base; + 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, val, NVRAM->addr); + NVRAM->addr = 0x0000; + break; + default: + break; + } +} + +static uint32_t NVRAM_readb (void *opaque, uint32_t addr) +{ + M48t59State *NVRAM = opaque; + uint32_t retval; + + addr -= NVRAM->io_base; + 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, target_phys_addr_t addr, uint32_t value) +{ + M48t59State *NVRAM = opaque; + + m48t59_write(NVRAM, addr, value & 0xff); +} + +static void nvram_writew (void *opaque, target_phys_addr_t 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, target_phys_addr_t 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, target_phys_addr_t addr) +{ + M48t59State *NVRAM = opaque; + uint32_t retval; + + retval = m48t59_read(NVRAM, addr); + return retval; +} + +static uint32_t nvram_readw (void *opaque, target_phys_addr_t 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, target_phys_addr_t 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 CPUWriteMemoryFunc * const nvram_write[] = { + &nvram_writeb, + &nvram_writew, + &nvram_writel, +}; + +static CPUReadMemoryFunc * const nvram_read[] = { + &nvram_readb, + &nvram_readw, + &nvram_readl, +}; + +static void m48t59_save(QEMUFile *f, void *opaque) +{ + M48t59State *s = opaque; + + qemu_put_8s(f, &s->lock); + qemu_put_be16s(f, &s->addr); + qemu_put_buffer(f, s->buffer, s->size); +} + +static int m48t59_load(QEMUFile *f, void *opaque, int version_id) +{ + M48t59State *s = opaque; + + if (version_id != 1) + return -EINVAL; + + qemu_get_8s(f, &s->lock); + qemu_get_be16s(f, &s->addr); + qemu_get_buffer(f, s->buffer, s->size); + + return 0; +} + +static void m48t59_reset_common(M48t59State *NVRAM) +{ + NVRAM->addr = 0; + NVRAM->lock = 0; + if (NVRAM->alrm_timer != NULL) + qemu_del_timer(NVRAM->alrm_timer); + + if (NVRAM->wd_timer != NULL) + qemu_del_timer(NVRAM->wd_timer); +} + +static void m48t59_reset_isa(DeviceState *d) +{ + M48t59ISAState *isa = container_of(d, M48t59ISAState, busdev.qdev); + M48t59State *NVRAM = &isa->state; + + m48t59_reset_common(NVRAM); +} + +static void m48t59_reset_sysbus(DeviceState *d) +{ + M48t59SysBusState *sys = container_of(d, M48t59SysBusState, busdev.qdev); + M48t59State *NVRAM = &sys->state; + + m48t59_reset_common(NVRAM); +} + +/* Initialisation routine */ +M48t59State *m48t59_init(qemu_irq IRQ, target_phys_addr_t mem_base, + uint32_t io_base, uint16_t size, int type) +{ + DeviceState *dev; + SysBusDevice *s; + M48t59SysBusState *d; + M48t59State *state; + + dev = qdev_create(NULL, "m48t59"); + qdev_prop_set_uint32(dev, "type", type); + qdev_prop_set_uint32(dev, "size", size); + qdev_prop_set_uint32(dev, "io_base", io_base); + qdev_init_nofail(dev); + s = sysbus_from_qdev(dev); + d = FROM_SYSBUS(M48t59SysBusState, s); + state = &d->state; + sysbus_connect_irq(s, 0, IRQ); + if (io_base != 0) { + register_ioport_read(io_base, 0x04, 1, NVRAM_readb, state); + register_ioport_write(io_base, 0x04, 1, NVRAM_writeb, state); + } + if (mem_base != 0) { + sysbus_mmio_map(s, 0, mem_base); + } + + return state; +} + +M48t59State *m48t59_init_isa(uint32_t io_base, uint16_t size, int type) +{ + M48t59ISAState *d; + ISADevice *dev; + M48t59State *s; + + dev = isa_create("m48t59_isa"); + qdev_prop_set_uint32(&dev->qdev, "type", type); + qdev_prop_set_uint32(&dev->qdev, "size", size); + qdev_prop_set_uint32(&dev->qdev, "io_base", io_base); + qdev_init_nofail(&dev->qdev); + d = DO_UPCAST(M48t59ISAState, busdev, dev); + s = &d->state; + + if (io_base != 0) { + register_ioport_read(io_base, 0x04, 1, NVRAM_readb, s); + register_ioport_write(io_base, 0x04, 1, NVRAM_writeb, s); + isa_init_ioport_range(dev, io_base, 4); + } + + return s; +} + +static void m48t59_init_common(M48t59State *s) +{ + s->buffer = qemu_mallocz(s->size); + if (s->type == 59) { + s->alrm_timer = qemu_new_timer(vm_clock, &alarm_cb, s); + s->wd_timer = qemu_new_timer(vm_clock, &watchdog_cb, s); + } + qemu_get_timedate(&s->alarm, 0); + + register_savevm(NULL, "m48t59", -1, 1, m48t59_save, m48t59_load, s); +} + +static int m48t59_init_isa1(ISADevice *dev) +{ + M48t59ISAState *d = DO_UPCAST(M48t59ISAState, busdev, dev); + M48t59State *s = &d->state; + + isa_init_irq(dev, &s->IRQ, 8); + m48t59_init_common(s); + + return 0; +} + +static int m48t59_init1(SysBusDevice *dev) +{ + M48t59SysBusState *d = FROM_SYSBUS(M48t59SysBusState, dev); + M48t59State *s = &d->state; + int mem_index; + + sysbus_init_irq(dev, &s->IRQ); + + mem_index = cpu_register_io_memory(nvram_read, nvram_write, s, + DEVICE_NATIVE_ENDIAN); + sysbus_init_mmio(dev, s->size, mem_index); + m48t59_init_common(s); + + return 0; +} + +static ISADeviceInfo m48t59_isa_info = { + .init = m48t59_init_isa1, + .qdev.name = "m48t59_isa", + .qdev.size = sizeof(M48t59ISAState), + .qdev.reset = m48t59_reset_isa, + .qdev.no_user = 1, + .qdev.props = (Property[]) { + DEFINE_PROP_UINT32("size", M48t59ISAState, state.size, -1), + DEFINE_PROP_UINT32("type", M48t59ISAState, state.type, -1), + DEFINE_PROP_HEX32( "io_base", M48t59ISAState, state.io_base, 0), + DEFINE_PROP_END_OF_LIST(), + } +}; + +static SysBusDeviceInfo m48t59_info = { + .init = m48t59_init1, + .qdev.name = "m48t59", + .qdev.size = sizeof(M48t59SysBusState), + .qdev.reset = m48t59_reset_sysbus, + .qdev.props = (Property[]) { + DEFINE_PROP_UINT32("size", M48t59SysBusState, state.size, -1), + DEFINE_PROP_UINT32("type", M48t59SysBusState, state.type, -1), + DEFINE_PROP_HEX32( "io_base", M48t59SysBusState, state.io_base, 0), + DEFINE_PROP_END_OF_LIST(), + } +}; + +static void m48t59_register_devices(void) +{ + sysbus_register_withprop(&m48t59_info); + isa_qdev_register(&m48t59_isa_info); +} + +device_init(m48t59_register_devices) diff --git a/hw/mac_dbdma.c b/hw/mac_dbdma.c new file mode 100644 index 0000000..5680fa9 --- /dev/null +++ b/hw/mac_dbdma.c @@ -0,0 +1,852 @@ +/* + * 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.h" +#include "isa.h" +#include "mac_dbdma.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 + +/* + */ + +/* + * DBDMA control/status registers. All little-endian. + */ + +#define DBDMA_CONTROL 0x00 +#define DBDMA_STATUS 0x01 +#define DBDMA_CMDPTR_HI 0x02 +#define DBDMA_CMDPTR_LO 0x03 +#define DBDMA_INTR_SEL 0x04 +#define DBDMA_BRANCH_SEL 0x05 +#define DBDMA_WAIT_SEL 0x06 +#define DBDMA_XFER_MODE 0x07 +#define DBDMA_DATA2PTR_HI 0x08 +#define DBDMA_DATA2PTR_LO 0x09 +#define DBDMA_RES1 0x0A +#define DBDMA_ADDRESS_HI 0x0B +#define DBDMA_BRANCH_ADDR_HI 0x0C +#define DBDMA_RES2 0x0D +#define DBDMA_RES3 0x0E +#define DBDMA_RES4 0x0F + +#define DBDMA_REGS 16 +#define DBDMA_SIZE (DBDMA_REGS * sizeof(uint32_t)) + +#define DBDMA_CHANNEL_SHIFT 7 +#define DBDMA_CHANNEL_SIZE (1 << DBDMA_CHANNEL_SHIFT) + +#define DBDMA_CHANNELS (0x1000 >> DBDMA_CHANNEL_SHIFT) + +/* Bits in control and status registers */ + +#define RUN 0x8000 +#define PAUSE 0x4000 +#define FLUSH 0x2000 +#define WAKE 0x1000 +#define DEAD 0x0800 +#define ACTIVE 0x0400 +#define BT 0x0100 +#define DEVSTAT 0x00ff + +/* + * DBDMA command structure. These fields are all little-endian! + */ + +typedef struct dbdma_cmd { + uint16_t req_count; /* requested byte transfer count */ + uint16_t command; /* command word (has bit-fields) */ + uint32_t phy_addr; /* physical data address */ + uint32_t cmd_dep; /* command-dependent field */ + uint16_t res_count; /* residual count after completion */ + uint16_t xfer_status; /* transfer status */ +} dbdma_cmd; + +/* DBDMA command values in command field */ + +#define COMMAND_MASK 0xf000 +#define OUTPUT_MORE 0x0000 /* transfer memory data to stream */ +#define OUTPUT_LAST 0x1000 /* ditto followed by end marker */ +#define INPUT_MORE 0x2000 /* transfer stream data to memory */ +#define INPUT_LAST 0x3000 /* ditto, expect end marker */ +#define STORE_WORD 0x4000 /* write word (4 bytes) to device reg */ +#define LOAD_WORD 0x5000 /* read word (4 bytes) from device reg */ +#define DBDMA_NOP 0x6000 /* do nothing */ +#define DBDMA_STOP 0x7000 /* suspend processing */ + +/* Key values in command field */ + +#define KEY_MASK 0x0700 +#define KEY_STREAM0 0x0000 /* usual data stream */ +#define KEY_STREAM1 0x0100 /* control/status stream */ +#define KEY_STREAM2 0x0200 /* device-dependent stream */ +#define KEY_STREAM3 0x0300 /* device-dependent stream */ +#define KEY_STREAM4 0x0400 /* reserved */ +#define KEY_REGS 0x0500 /* device register space */ +#define KEY_SYSTEM 0x0600 /* system memory-mapped space */ +#define KEY_DEVICE 0x0700 /* device memory-mapped space */ + +/* Interrupt control values in command field */ + +#define INTR_MASK 0x0030 +#define INTR_NEVER 0x0000 /* don't interrupt */ +#define INTR_IFSET 0x0010 /* intr if condition bit is 1 */ +#define INTR_IFCLR 0x0020 /* intr if condition bit is 0 */ +#define INTR_ALWAYS 0x0030 /* always interrupt */ + +/* Branch control values in command field */ + +#define BR_MASK 0x000c +#define BR_NEVER 0x0000 /* don't branch */ +#define BR_IFSET 0x0004 /* branch if condition bit is 1 */ +#define BR_IFCLR 0x0008 /* branch if condition bit is 0 */ +#define BR_ALWAYS 0x000c /* always branch */ + +/* Wait control values in command field */ + +#define WAIT_MASK 0x0003 +#define WAIT_NEVER 0x0000 /* don't wait */ +#define WAIT_IFSET 0x0001 /* wait if condition bit is 1 */ +#define WAIT_IFCLR 0x0002 /* wait if condition bit is 0 */ +#define WAIT_ALWAYS 0x0003 /* always wait */ + +typedef struct DBDMA_channel { + int channel; + uint32_t regs[DBDMA_REGS]; + qemu_irq irq; + DBDMA_io io; + DBDMA_rw rw; + DBDMA_flush flush; + dbdma_cmd current; + int processing; +} DBDMA_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], + (uint8_t*)&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], + (uint8_t*)&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("conditional_interrupt\n"); + + 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); + 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); + return; + case INTR_IFCLR: /* intr if condition bit is 0 */ + if (!cond) + qemu_irq_raise(ch->irq); + 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 QEMUBH *dbdma_bh; +static void channel_run(DBDMA_channel *ch); + +static void dbdma_end(DBDMA_io *io) +{ + DBDMA_channel *ch = io->channel; + dbdma_cmd *current = &ch->current; + + 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: + ch->processing = 0; + 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->processing = 1; + 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 + */ + + 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->processing = 1; + 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, (uint8_t*)&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: + qemu_bh_schedule(dbdma_bh); +} + +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, (uint8_t*)&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: + qemu_bh_schedule(dbdma_bh); +} + +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: + qemu_bh_schedule(dbdma_bh); +} + +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 (DBDMA_channel *ch) +{ + int channel; + + for (channel = 0; channel < DBDMA_CHANNELS; channel++, ch++) { + uint32_t status = ch->regs[DBDMA_STATUS]; + if (!ch->processing && (status & RUN) && (status & ACTIVE)) + channel_run(ch); + } +} + +static void DBDMA_run_bh(void *opaque) +{ + DBDMA_channel *ch = opaque; + + DBDMA_DPRINTF("DBDMA_run_bh\n"); + + DBDMA_run(ch); +} + +void DBDMA_register_channel(void *dbdma, int nchan, qemu_irq irq, + DBDMA_rw rw, DBDMA_flush flush, + void *opaque) +{ + DBDMA_channel *ch = ( DBDMA_channel *)dbdma + 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; +} + +void DBDMA_schedule(void) +{ + qemu_notify_event(); +} + +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); + } + + DBDMA_DPRINTF(" status 0x%08x\n", status); + + ch->regs[DBDMA_STATUS] = status; + + if (status & ACTIVE) + qemu_bh_schedule(dbdma_bh); + if ((status & FLUSH) && ch->flush) + ch->flush(&ch->io); +} + +static void dbdma_writel (void *opaque, + target_phys_addr_t addr, uint32_t value) +{ + int channel = addr >> DBDMA_CHANNEL_SHIFT; + DBDMA_channel *ch = (DBDMA_channel *)opaque + channel; + int reg = (addr - (channel << DBDMA_CHANNEL_SHIFT)) >> 2; + + DBDMA_DPRINTF("writel 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); + + /* cmdptr cannot be modified if channel is RUN or ACTIVE */ + + if (reg == DBDMA_CMDPTR_LO && + (ch->regs[DBDMA_STATUS] & (RUN | 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 uint32_t dbdma_readl (void *opaque, target_phys_addr_t addr) +{ + uint32_t value; + int channel = addr >> DBDMA_CHANNEL_SHIFT; + DBDMA_channel *ch = (DBDMA_channel *)opaque + 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 CPUWriteMemoryFunc * const dbdma_write[] = { + NULL, + NULL, + dbdma_writel, +}; + +static CPUReadMemoryFunc * const dbdma_read[] = { + NULL, + NULL, + dbdma_readl, +}; + +static void dbdma_save(QEMUFile *f, void *opaque) +{ + DBDMA_channel *s = opaque; + unsigned int i, j; + + for (i = 0; i < DBDMA_CHANNELS; i++) + for (j = 0; j < DBDMA_REGS; j++) + qemu_put_be32s(f, &s[i].regs[j]); +} + +static int dbdma_load(QEMUFile *f, void *opaque, int version_id) +{ + DBDMA_channel *s = opaque; + unsigned int i, j; + + if (version_id != 2) + return -EINVAL; + + for (i = 0; i < DBDMA_CHANNELS; i++) + for (j = 0; j < DBDMA_REGS; j++) + qemu_get_be32s(f, &s[i].regs[j]); + + return 0; +} + +static void dbdma_reset(void *opaque) +{ + DBDMA_channel *s = opaque; + int i; + + for (i = 0; i < DBDMA_CHANNELS; i++) + memset(s[i].regs, 0, DBDMA_SIZE); +} + +void* DBDMA_init (int *dbdma_mem_index) +{ + DBDMA_channel *s; + + s = qemu_mallocz(sizeof(DBDMA_channel) * DBDMA_CHANNELS); + + *dbdma_mem_index = cpu_register_io_memory(dbdma_read, dbdma_write, s, + DEVICE_LITTLE_ENDIAN); + register_savevm(NULL, "dbdma", -1, 1, dbdma_save, dbdma_load, s); + qemu_register_reset(dbdma_reset, s); + + dbdma_bh = qemu_bh_new(DBDMA_run_bh, s); + + return s; +} diff --git a/hw/mac_dbdma.h b/hw/mac_dbdma.h new file mode 100644 index 0000000..d236c5b --- /dev/null +++ b/hw/mac_dbdma.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2009 Laurent Vivier + * + * 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. + */ + +typedef struct DBDMA_io DBDMA_io; + +typedef void (*DBDMA_flush)(DBDMA_io *io); +typedef void (*DBDMA_rw)(DBDMA_io *io); +typedef void (*DBDMA_end)(DBDMA_io *io); +struct DBDMA_io { + void *opaque; + void *channel; + target_phys_addr_t addr; + int len; + int is_last; + int is_dma_out; + DBDMA_end dma_end; +}; + + +void DBDMA_register_channel(void *dbdma, int nchan, qemu_irq irq, + DBDMA_rw rw, DBDMA_flush flush, + void *opaque); +void DBDMA_schedule(void); +void* DBDMA_init (int *dbdma_mem_index); diff --git a/hw/mac_nvram.c b/hw/mac_nvram.c new file mode 100644 index 0000000..c2a2fc2 --- /dev/null +++ b/hw/mac_nvram.c @@ -0,0 +1,195 @@ +/* + * 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.h" +#include "firmware_abi.h" +#include "sysemu.h" +#include "ppc_mac.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 + +struct MacIONVRAMState { + target_phys_addr_t size; + int mem_index; + unsigned int it_shift; + uint8_t *data; +}; + +#define DEF_SYSTEM_SIZE 0xc10 + +/* Direct access to NVRAM */ +uint32_t macio_nvram_read (void *opaque, uint32_t addr) +{ + MacIONVRAMState *s = opaque; + uint32_t ret; + + if (addr < s->size) + ret = s->data[addr]; + else + ret = -1; + NVR_DPRINTF("read addr %04x val %x\n", addr, ret); + + return ret; +} + +void macio_nvram_write (void *opaque, uint32_t addr, uint32_t val) +{ + MacIONVRAMState *s = opaque; + + NVR_DPRINTF("write addr %04x val %x\n", addr, val); + if (addr < s->size) + s->data[addr] = val; +} + +/* macio style NVRAM device */ +static void macio_nvram_writeb (void *opaque, + target_phys_addr_t addr, uint32_t value) +{ + MacIONVRAMState *s = opaque; + + addr = (addr >> s->it_shift) & (s->size - 1); + s->data[addr] = value; + NVR_DPRINTF("writeb addr %04x val %x\n", (int)addr, value); +} + +static uint32_t macio_nvram_readb (void *opaque, target_phys_addr_t addr) +{ + 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 CPUWriteMemoryFunc * const nvram_write[] = { + &macio_nvram_writeb, + &macio_nvram_writeb, + &macio_nvram_writeb, +}; + +static CPUReadMemoryFunc * const nvram_read[] = { + &macio_nvram_readb, + &macio_nvram_readb, + &macio_nvram_readb, +}; + +static void macio_nvram_save(QEMUFile *f, void *opaque) +{ + MacIONVRAMState *s = (MacIONVRAMState *)opaque; + + qemu_put_buffer(f, s->data, s->size); +} + +static int macio_nvram_load(QEMUFile *f, void *opaque, int version_id) +{ + MacIONVRAMState *s = (MacIONVRAMState *)opaque; + + if (version_id != 1) + return -EINVAL; + + qemu_get_buffer(f, s->data, s->size); + + return 0; +} + +static void macio_nvram_reset(void *opaque) +{ +} + +MacIONVRAMState *macio_nvram_init (int *mem_index, target_phys_addr_t size, + unsigned int it_shift) +{ + MacIONVRAMState *s; + + s = qemu_mallocz(sizeof(MacIONVRAMState)); + s->data = qemu_mallocz(size); + s->size = size; + s->it_shift = it_shift; + + s->mem_index = cpu_register_io_memory(nvram_read, nvram_write, s, + DEVICE_NATIVE_ENDIAN); + *mem_index = s->mem_index; + register_savevm(NULL, "macio_nvram", -1, 1, macio_nvram_save, + macio_nvram_load, s); + qemu_register_reset(macio_nvram_reset, s); + + return s; +} + +void macio_nvram_map (void *opaque, target_phys_addr_t mem_base) +{ + MacIONVRAMState *s; + + s = opaque; + cpu_register_physical_memory(mem_base, s->size << s->it_shift, + s->mem_index); +} + +/* Set up a system OpenBIOS NVRAM partition */ +void pmac_format_nvram_partition (MacIONVRAMState *nvr, int len) +{ + unsigned int i; + uint32_t start = 0, end; + struct OpenBIOS_nvpart_v1 *part_header; + + // OpenBIOS nvram variables + // Variable partition + part_header = (struct OpenBIOS_nvpart_v1 *)nvr->data; + 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); +} diff --git a/hw/macio.c b/hw/macio.c new file mode 100644 index 0000000..789ca55 --- /dev/null +++ b/hw/macio.c @@ -0,0 +1,118 @@ +/* + * 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.h" +#include "ppc_mac.h" +#include "pci.h" +#include "escc.h" + +typedef struct macio_state_t macio_state_t; +struct macio_state_t { + int is_oldworld; + int pic_mem_index; + int dbdma_mem_index; + int cuda_mem_index; + int escc_mem_index; + void *nvram; + int nb_ide; + int ide_mem_index[4]; +}; + +static void macio_map (PCIDevice *pci_dev, int region_num, + pcibus_t addr, pcibus_t size, int type) +{ + macio_state_t *macio_state; + int i; + + macio_state = (macio_state_t *)(pci_dev + 1); + if (macio_state->pic_mem_index >= 0) { + if (macio_state->is_oldworld) { + /* Heathrow PIC */ + cpu_register_physical_memory(addr + 0x00000, 0x1000, + macio_state->pic_mem_index); + } else { + /* OpenPIC */ + cpu_register_physical_memory(addr + 0x40000, 0x40000, + macio_state->pic_mem_index); + } + } + if (macio_state->dbdma_mem_index >= 0) { + cpu_register_physical_memory(addr + 0x08000, 0x1000, + macio_state->dbdma_mem_index); + } + if (macio_state->escc_mem_index >= 0) { + cpu_register_physical_memory(addr + 0x13000, ESCC_SIZE << 4, + macio_state->escc_mem_index); + } + if (macio_state->cuda_mem_index >= 0) { + cpu_register_physical_memory(addr + 0x16000, 0x2000, + macio_state->cuda_mem_index); + } + for (i = 0; i < macio_state->nb_ide; i++) { + if (macio_state->ide_mem_index[i] >= 0) { + cpu_register_physical_memory(addr + 0x1f000 + (i * 0x1000), 0x1000, + macio_state->ide_mem_index[i]); + } + } + if (macio_state->nvram != NULL) + macio_nvram_map(macio_state->nvram, addr + 0x60000); +} + +void macio_init (PCIBus *bus, int device_id, int is_oldworld, int pic_mem_index, + int dbdma_mem_index, int cuda_mem_index, void *nvram, + int nb_ide, int *ide_mem_index, int escc_mem_index) +{ + PCIDevice *d; + macio_state_t *macio_state; + int i; + + d = pci_register_device(bus, "macio", + sizeof(PCIDevice) + sizeof(macio_state_t), + -1, NULL, NULL); + macio_state = (macio_state_t *)(d + 1); + macio_state->is_oldworld = is_oldworld; + macio_state->pic_mem_index = pic_mem_index; + macio_state->dbdma_mem_index = dbdma_mem_index; + macio_state->cuda_mem_index = cuda_mem_index; + macio_state->escc_mem_index = escc_mem_index; + macio_state->nvram = nvram; + if (nb_ide > 4) + nb_ide = 4; + macio_state->nb_ide = nb_ide; + for (i = 0; i < nb_ide; i++) + macio_state->ide_mem_index[i] = ide_mem_index[i]; + for (; i < 4; i++) + macio_state->ide_mem_index[i] = -1; + /* Note: this code is strongly inspirated from the corresponding code + in PearPC */ + + pci_config_set_vendor_id(d->config, PCI_VENDOR_ID_APPLE); + pci_config_set_device_id(d->config, device_id); + pci_config_set_class(d->config, PCI_CLASS_OTHERS << 8); + + d->config[0x3d] = 0x01; // interrupt on pin 1 + + pci_register_bar(d, 0, 0x80000, + PCI_BASE_ADDRESS_SPACE_MEMORY, macio_map); +} diff --git a/hw/mainstone.c b/hw/mainstone.c new file mode 100644 index 0000000..58e3f86 --- /dev/null +++ b/hw/mainstone.c @@ -0,0 +1,158 @@ +/* + * PXA270-based Intel Mainstone platforms. + * + * Copyright (c) 2007 by Armin Kuster or + * + * + * Code based on spitz platform by Andrzej Zaborowski + * + * This code is licensed under the GNU GPL v2. + */ +#include "hw.h" +#include "pxa.h" +#include "arm-misc.h" +#include "net.h" +#include "devices.h" +#include "boards.h" +#include "mainstone.h" +#include "sysemu.h" +#include "flash.h" +#include "blockdev.h" + +static 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 */ + [0x39] = {5,2}, /* space */ + [0x39] = {5,3}, /* space */ + [0x1c] = {5,5}, /* enter */ + [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(ram_addr_t ram_size, + const char *kernel_filename, + const char *kernel_cmdline, const char *initrd_filename, + const char *cpu_model, enum mainstone_model_e model, int arm_id) +{ + uint32_t sector_len = 256 * 1024; + target_phys_addr_t mainstone_flash_base[] = { MST_FLASH_0, MST_FLASH_1 }; + PXA2xxState *cpu; + qemu_irq *mst_irq; + DriveInfo *dinfo; + int i; + int be; + + if (!cpu_model) + cpu_model = "pxa270-c5"; + + /* Setup CPU & memory */ + cpu = pxa270_init(mainstone_binfo.ram_size, cpu_model); + cpu_register_physical_memory(0, MAINSTONE_ROM, + qemu_ram_alloc(NULL, "mainstone.rom", + MAINSTONE_ROM) | IO_MEM_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) { + fprintf(stderr, "Two flash images must be given with the " + "'pflash' parameter\n"); + exit(1); + } + + if (!pflash_cfi01_register(mainstone_flash_base[i], + qemu_ram_alloc(NULL, i ? "mainstone.flash1" : + "mainstone.flash0", + MAINSTONE_FLASH), + dinfo->bdrv, sector_len, + MAINSTONE_FLASH / sector_len, 4, 0, 0, 0, 0, + be)) { + fprintf(stderr, "qemu: Error registering flash memory.\n"); + exit(1); + } + } + + mst_irq = mst_irq_init(cpu, MST_FPGA_PHYS, PXA2XX_PIC_GPIO_0); + + /* setup keypad */ + printf("map addr %p\n", &map); + pxa27x_register_keypad(cpu->kp, map, 0xe0); + + /* MMC/SD host */ + pxa2xx_mmci_handlers(cpu->mmc, NULL, mst_irq[MMC_IRQ]); + + smc91c111_init(&nd_table[0], MST_ETH_PHYS, mst_irq[ETHERNET_IRQ]); + + mainstone_binfo.kernel_filename = kernel_filename; + mainstone_binfo.kernel_cmdline = kernel_cmdline; + mainstone_binfo.initrd_filename = initrd_filename; + mainstone_binfo.board_id = arm_id; + arm_load_kernel(cpu->env, &mainstone_binfo); +} + +static void mainstone_init(ram_addr_t ram_size, + const char *boot_device, + const char *kernel_filename, const char *kernel_cmdline, + const char *initrd_filename, const char *cpu_model) +{ + mainstone_common_init(ram_size, kernel_filename, + kernel_cmdline, initrd_filename, cpu_model, 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/mainstone.h b/hw/mainstone.h new file mode 100644 index 0000000..9618c06 --- /dev/null +++ b/hw/mainstone.h @@ -0,0 +1,38 @@ +/* + * PXA270-based Intel Mainstone platforms. + * + * Copyright (c) 2007 by Armin Kuster or + * + * + * This code is licensed under the GNU GPL v2. + */ + +#ifndef __MAINSTONE_H__ +#define __MAINSTONE_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 + +extern qemu_irq +*mst_irq_init(PXA2xxState *cpu, uint32_t base, int irq); + +#endif /* __MAINSTONE_H__ */ diff --git a/hw/marvell_88w8618_audio.c b/hw/marvell_88w8618_audio.c new file mode 100644 index 0000000..3eff925 --- /dev/null +++ b/hw/marvell_88w8618_audio.c @@ -0,0 +1,299 @@ +/* + * Marvell 88w8618 audio emulation extracted from + * Marvell MV88w8618 / Freecom MusicPal emulation. + * + * Copyright (c) 2008 Jan Kiszka + * + * This code is licenced under the GNU GPL v2. + */ +#include "sysbus.h" +#include "hw.h" +#include "i2c.h" +#include "sysbus.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) + +typedef struct mv88w8618_audio_state { + SysBusDevice busdev; + 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; + DeviceState *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, (void *)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 uint32_t mv88w8618_audio_read(void *opaque, target_phys_addr_t offset) +{ + 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, target_phys_addr_t offset, + uint32_t value) +{ + 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 = FROM_SYSBUS(mv88w8618_audio_state, + sysbus_from_qdev(d)); + + s->playback_mode = 0; + s->status = 0; + s->irq_enable = 0; + s->clock_div = 0; + s->threshold = 0; + s->phys_buf = 0; +} + +static CPUReadMemoryFunc * const mv88w8618_audio_readfn[] = { + mv88w8618_audio_read, + mv88w8618_audio_read, + mv88w8618_audio_read +}; + +static CPUWriteMemoryFunc * const mv88w8618_audio_writefn[] = { + mv88w8618_audio_write, + mv88w8618_audio_write, + mv88w8618_audio_write +}; + +static int mv88w8618_audio_init(SysBusDevice *dev) +{ + mv88w8618_audio_state *s = FROM_SYSBUS(mv88w8618_audio_state, dev); + int iomemtype; + + sysbus_init_irq(dev, &s->irq); + + wm8750_data_req_set(s->wm, mv88w8618_audio_callback, s); + + iomemtype = cpu_register_io_memory(mv88w8618_audio_readfn, + mv88w8618_audio_writefn, s, + DEVICE_NATIVE_ENDIAN); + sysbus_init_mmio(dev, MP_AUDIO_SIZE, iomemtype); + + return 0; +} + +static const VMStateDescription mv88w8618_audio_vmsd = { + .name = "mv88w8618_audio", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 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 SysBusDeviceInfo mv88w8618_audio_info = { + .init = mv88w8618_audio_init, + .qdev.name = "mv88w8618_audio", + .qdev.size = sizeof(mv88w8618_audio_state), + .qdev.reset = mv88w8618_audio_reset, + .qdev.vmsd = &mv88w8618_audio_vmsd, + .qdev.props = (Property[]) { + { + .name = "wm8750", + .info = &qdev_prop_ptr, + .offset = offsetof(mv88w8618_audio_state, wm), + }, + {/* end of list */} + } +}; + +static void mv88w8618_register_devices(void) +{ + sysbus_register_withprop(&mv88w8618_audio_info); +} + +device_init(mv88w8618_register_devices) diff --git a/hw/max111x.c b/hw/max111x.c new file mode 100644 index 0000000..2844665 --- /dev/null +++ b/hw/max111x.c @@ -0,0 +1,188 @@ +/* + * Maxim MAX1110/1111 ADC chip emulation. + * + * Copyright (c) 2006 Openedhand Ltd. + * Written by Andrzej Zaborowski + * + * This code is licensed under the GNU GPLv2. + */ + +#include "ssi.h" + +typedef struct { + SSISlave ssidev; + qemu_irq interrupt; + uint8_t tb1, rb2, rb3; + int cycle; + + int input[8]; + int inputs, com; +} MAX111xState; + +/* 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 = FROM_SSI_SLAVE(MAX111xState, dev); + max111x_write(s, value); + return max111x_read(s); +} + +static void max111x_save(QEMUFile *f, void *opaque) +{ + MAX111xState *s = (MAX111xState *) opaque; + int i; + + qemu_put_8s(f, &s->tb1); + qemu_put_8s(f, &s->rb2); + qemu_put_8s(f, &s->rb3); + qemu_put_be32(f, s->inputs); + qemu_put_be32(f, s->com); + for (i = 0; i < s->inputs; i ++) + qemu_put_byte(f, s->input[i]); +} + +static int max111x_load(QEMUFile *f, void *opaque, int version_id) +{ + MAX111xState *s = (MAX111xState *) opaque; + int i; + + qemu_get_8s(f, &s->tb1); + qemu_get_8s(f, &s->rb2); + qemu_get_8s(f, &s->rb3); + if (s->inputs != qemu_get_be32(f)) + return -EINVAL; + s->com = qemu_get_be32(f); + for (i = 0; i < s->inputs; i ++) + s->input[i] = qemu_get_byte(f); + + return 0; +} + +static int max111x_init(SSISlave *dev, int inputs) +{ + MAX111xState *s = FROM_SSI_SLAVE(MAX111xState, dev); + + qdev_init_gpio_out(&dev->qdev, &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; + + register_savevm(&dev->qdev, "max111x", -1, 0, + max111x_save, max111x_load, 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 = FROM_SSI_SLAVE(MAX111xState, SSI_SLAVE_FROM_QDEV(dev)); + assert(line >= 0 && line < s->inputs); + s->input[line] = value; +} + +static SSISlaveInfo max1110_info = { + .qdev.name = "max1110", + .qdev.size = sizeof(MAX111xState), + .init = max1110_init, + .transfer = max111x_transfer +}; + +static SSISlaveInfo max1111_info = { + .qdev.name = "max1111", + .qdev.size = sizeof(MAX111xState), + .init = max1111_init, + .transfer = max111x_transfer +}; + +static void max111x_register_devices(void) +{ + ssi_register_slave(&max1110_info); + ssi_register_slave(&max1111_info); +} + +device_init(max111x_register_devices) diff --git a/hw/max7310.c b/hw/max7310.c new file mode 100644 index 0000000..c302eb6 --- /dev/null +++ b/hw/max7310.c @@ -0,0 +1,220 @@ +/* + * MAX7310 8-port GPIO expansion chip. + * + * Copyright (c) 2006 Openedhand Ltd. + * Written by Andrzej Zaborowski + * + * This file is licensed under GNU GPL. + */ + +#include "i2c.h" + +typedef struct { + i2c_slave i2c; + 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; + +void max7310_reset(i2c_slave *i2c) +{ + MAX7310State *s = (MAX7310State *) i2c; + s->level &= s->direction; + s->direction = 0xff; + s->polarity = 0xf0; + s->status = 0x01; + s->command = 0x00; +} + +static int max7310_rx(i2c_slave *i2c) +{ + MAX7310State *s = (MAX7310State *) 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(i2c_slave *i2c, uint8_t data) +{ + MAX7310State *s = (MAX7310State *) 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 = ffs(diff) - 1; + 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(i2c_slave *i2c, enum i2c_event event) +{ + MAX7310State *s = (MAX7310State *) 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, + .minimum_version_id_old = 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(i2c, 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(i2c_slave *i2c) +{ + MAX7310State *s = FROM_I2C_SLAVE(MAX7310State, i2c); + + s->gpio_in = qemu_allocate_irqs(max7310_gpio_set, s, + ARRAY_SIZE(s->handler)); + + max7310_reset(&s->i2c); + + return 0; +} + +qemu_irq *max7310_gpio_in_get(i2c_slave *i2c) +{ + MAX7310State *s = (MAX7310State *) i2c; + return s->gpio_in; +} + +void max7310_gpio_out_set(i2c_slave *i2c, int line, qemu_irq handler) +{ + MAX7310State *s = (MAX7310State *) i2c; + if (line >= ARRAY_SIZE(s->handler) || line < 0) + hw_error("bad GPIO line"); + + s->handler[line] = handler; +} + +static I2CSlaveInfo max7310_info = { + .qdev.name = "max7310", + .qdev.size = sizeof(MAX7310State), + .qdev.vmsd = &vmstate_max7310, + .init = max7310_init, + .event = max7310_event, + .recv = max7310_rx, + .send = max7310_tx +}; + +static void max7310_register_devices(void) +{ + i2c_register_slave(&max7310_info); +} + +device_init(max7310_register_devices) diff --git a/hw/mc146818rtc.c b/hw/mc146818rtc.c new file mode 100644 index 0000000..a1b0e31 --- /dev/null +++ b/hw/mc146818rtc.c @@ -0,0 +1,657 @@ +/* + * 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.h" +#include "qemu-timer.h" +#include "sysemu.h" +#include "pc.h" +#include "apic.h" +#include "isa.h" +#include "mc146818rtc.h" + +//#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 RTC_REINJECT_ON_ACK_COUNT 20 + +#define RTC_SECONDS 0 +#define RTC_SECONDS_ALARM 1 +#define RTC_MINUTES 2 +#define RTC_MINUTES_ALARM 3 +#define RTC_HOURS 4 +#define RTC_HOURS_ALARM 5 +#define RTC_ALARM_DONT_CARE 0xC0 + +#define RTC_DAY_OF_WEEK 6 +#define RTC_DAY_OF_MONTH 7 +#define RTC_MONTH 8 +#define RTC_YEAR 9 + +#define RTC_REG_A 10 +#define RTC_REG_B 11 +#define RTC_REG_C 12 +#define RTC_REG_D 13 + +#define REG_A_UIP 0x80 + +#define REG_B_SET 0x80 +#define REG_B_PIE 0x40 +#define REG_B_AIE 0x20 +#define REG_B_UIE 0x10 +#define REG_B_SQWE 0x08 +#define REG_B_DM 0x04 +#define REG_B_24H 0x02 + +#define REG_C_UF 0x10 +#define REG_C_IRQF 0x80 +#define REG_C_PF 0x40 +#define REG_C_AF 0x20 + +typedef struct RTCState { + ISADevice dev; + uint8_t cmos_data[128]; + uint8_t cmos_index; + struct tm current_tm; + int32_t base_year; + qemu_irq irq; + qemu_irq sqw_irq; + int it_shift; + /* periodic timer */ + QEMUTimer *periodic_timer; + int64_t next_periodic_time; + /* second update */ + int64_t next_second_time; + uint16_t irq_reinject_on_ack_count; + uint32_t irq_coalesced; + uint32_t period; + QEMUTimer *coalesced_timer; + QEMUTimer *second_timer; + QEMUTimer *second_timer2; +} RTCState; + +static void rtc_set_time(RTCState *s); +static void rtc_copy_date(RTCState *s); + +#ifdef TARGET_I386 +static void rtc_coalesced_timer_update(RTCState *s) +{ + if (s->irq_coalesced == 0) { + qemu_del_timer(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_get_clock(rtc_clock) + + muldiv64(s->period / c, get_ticks_per_sec(), 32768); + qemu_mod_timer(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 + +static void rtc_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) + || ((s->cmos_data[RTC_REG_B] & REG_B_SQWE) && s->sqw_irq))) { + 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, 32768, get_ticks_per_sec()); + next_irq_clock = (cur_clock & ~(period - 1)) + period; + s->next_periodic_time = + muldiv64(next_irq_clock, get_ticks_per_sec(), 32768) + 1; + qemu_mod_timer(s->periodic_timer, s->next_periodic_time); + } else { +#ifdef TARGET_I386 + s->irq_coalesced = 0; +#endif + qemu_del_timer(s->periodic_timer); + } +} + +static void rtc_periodic_timer(void *opaque) +{ + RTCState *s = opaque; + + rtc_timer_update(s, s->next_periodic_time); + if (s->cmos_data[RTC_REG_B] & REG_B_PIE) { + s->cmos_data[RTC_REG_C] |= 0xc0; +#ifdef TARGET_I386 + if(rtc_td_hack) { + 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); + } + if (s->cmos_data[RTC_REG_B] & REG_B_SQWE) { + /* Not square wave at all but we don't want 2048Hz interrupts! + Must be seen as a pulse. */ + qemu_irq_raise(s->sqw_irq); + } +} + +static void cmos_ioport_write(void *opaque, uint32_t addr, uint32_t data) +{ + RTCState *s = opaque; + + if ((addr & 1) == 0) { + s->cmos_index = data & 0x7f; + } else { + CMOS_DPRINTF("cmos: write index=0x%02x val=0x%02x\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; + break; + 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 (!(s->cmos_data[RTC_REG_B] & REG_B_SET)) { + rtc_set_time(s); + } + break; + case RTC_REG_A: + /* UIP bit is read only */ + s->cmos_data[RTC_REG_A] = (data & ~REG_A_UIP) | + (s->cmos_data[RTC_REG_A] & REG_A_UIP); + rtc_timer_update(s, qemu_get_clock(rtc_clock)); + break; + case RTC_REG_B: + if (data & REG_B_SET) { + /* 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) { + rtc_set_time(s); + } + } + if (((s->cmos_data[RTC_REG_B] ^ data) & (REG_B_DM | REG_B_24H)) && + !(data & REG_B_SET)) { + /* If the time format has changed and not in set mode, + update the registers immediately. */ + s->cmos_data[RTC_REG_B] = data; + rtc_copy_date(s); + } else { + s->cmos_data[RTC_REG_B] = data; + } + rtc_timer_update(s, qemu_get_clock(rtc_clock)); + 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 (s->cmos_data[RTC_REG_B] & REG_B_DM) { + return a; + } else { + return ((a >> 4) * 10) + (a & 0x0f); + } +} + +static void rtc_set_time(RTCState *s) +{ + struct tm *tm = &s->current_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) && + (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 - 1900; + + rtc_change_mon_event(tm); +} + +static void rtc_copy_date(RTCState *s) +{ + const struct tm *tm = &s->current_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 */ + s->cmos_data[RTC_HOURS] = rtc_to_bcd(s, tm->tm_hour % 12); + 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 - s->base_year) % 100; + if (year < 0) + year += 100; + s->cmos_data[RTC_YEAR] = rtc_to_bcd(s, year); +} + +/* 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++; + } + } + } + } + } +} + + +static void rtc_update_second(void *opaque) +{ + RTCState *s = opaque; + int64_t delay; + + /* if the oscillator is not in normal operation, we do not update */ + if ((s->cmos_data[RTC_REG_A] & 0x70) != 0x20) { + s->next_second_time += get_ticks_per_sec(); + qemu_mod_timer(s->second_timer, s->next_second_time); + } else { + rtc_next_second(&s->current_tm); + + if (!(s->cmos_data[RTC_REG_B] & REG_B_SET)) { + /* update in progress bit */ + s->cmos_data[RTC_REG_A] |= REG_A_UIP; + } + /* should be 244 us = 8 / 32768 seconds, but currently the + timers do not have the necessary resolution. */ + delay = (get_ticks_per_sec() * 1) / 100; + if (delay < 1) + delay = 1; + qemu_mod_timer(s->second_timer2, + s->next_second_time + delay); + } +} + +static void rtc_update_second2(void *opaque) +{ + RTCState *s = opaque; + + if (!(s->cmos_data[RTC_REG_B] & REG_B_SET)) { + rtc_copy_date(s); + } + + /* check alarm */ + if (s->cmos_data[RTC_REG_B] & REG_B_AIE) { + if (((s->cmos_data[RTC_SECONDS_ALARM] & 0xc0) == 0xc0 || + rtc_from_bcd(s, s->cmos_data[RTC_SECONDS_ALARM]) == s->current_tm.tm_sec) && + ((s->cmos_data[RTC_MINUTES_ALARM] & 0xc0) == 0xc0 || + rtc_from_bcd(s, s->cmos_data[RTC_MINUTES_ALARM]) == s->current_tm.tm_min) && + ((s->cmos_data[RTC_HOURS_ALARM] & 0xc0) == 0xc0 || + rtc_from_bcd(s, s->cmos_data[RTC_HOURS_ALARM]) == s->current_tm.tm_hour)) { + + s->cmos_data[RTC_REG_C] |= 0xa0; + qemu_irq_raise(s->irq); + } + } + + /* update ended interrupt */ + s->cmos_data[RTC_REG_C] |= REG_C_UF; + if (s->cmos_data[RTC_REG_B] & REG_B_UIE) { + s->cmos_data[RTC_REG_C] |= REG_C_IRQF; + qemu_irq_raise(s->irq); + } + + /* clear update in progress bit */ + s->cmos_data[RTC_REG_A] &= ~REG_A_UIP; + + s->next_second_time += get_ticks_per_sec(); + qemu_mod_timer(s->second_timer, s->next_second_time); +} + +static uint32_t cmos_ioport_read(void *opaque, uint32_t addr) +{ + RTCState *s = opaque; + int ret; + if ((addr & 1) == 0) { + return 0xff; + } else { + switch(s->cmos_index) { + 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: + ret = s->cmos_data[s->cmos_index]; + break; + case RTC_REG_A: + ret = s->cmos_data[s->cmos_index]; + break; + case RTC_REG_C: + ret = s->cmos_data[s->cmos_index]; + qemu_irq_lower(s->irq); +#ifdef TARGET_I386 + if(s->irq_coalesced && + s->irq_reinject_on_ack_count < RTC_REINJECT_ON_ACK_COUNT) { + s->irq_reinject_on_ack_count++; + 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); + } + break; + } +#endif + + s->cmos_data[RTC_REG_C] = 0x00; + 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 = DO_UPCAST(RTCState, dev, dev); + if (addr >= 0 && addr <= 127) + s->cmos_data[addr] = val; +} + +void rtc_set_date(ISADevice *dev, const struct tm *tm) +{ + RTCState *s = DO_UPCAST(RTCState, dev, dev); + s->current_tm = *tm; + rtc_copy_date(s); +} + +/* PC cmos mappings */ +#define REG_IBM_CENTURY_BYTE 0x32 +#define REG_IBM_PS2_CENTURY_BYTE 0x37 + +static void rtc_set_date_from_host(ISADevice *dev) +{ + RTCState *s = DO_UPCAST(RTCState, dev, dev); + struct tm tm; + int val; + + /* set the CMOS date */ + qemu_get_timedate(&tm, 0); + rtc_set_date(dev, &tm); + + val = rtc_to_bcd(s, (tm.tm_year / 100) + 19); + rtc_set_memory(dev, REG_IBM_CENTURY_BYTE, val); + rtc_set_memory(dev, REG_IBM_PS2_CENTURY_BYTE, val); +} + +static int rtc_post_load(void *opaque, int version_id) +{ +#ifdef TARGET_I386 + RTCState *s = opaque; + + if (version_id >= 2) { + if (rtc_td_hack) { + rtc_coalesced_timer_update(s); + } + } +#endif + return 0; +} + +static const VMStateDescription vmstate_rtc = { + .name = "mc146818rtc", + .version_id = 2, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .post_load = rtc_post_load, + .fields = (VMStateField []) { + VMSTATE_BUFFER(cmos_data, RTCState), + VMSTATE_UINT8(cmos_index, RTCState), + VMSTATE_INT32(current_tm.tm_sec, RTCState), + VMSTATE_INT32(current_tm.tm_min, RTCState), + VMSTATE_INT32(current_tm.tm_hour, RTCState), + VMSTATE_INT32(current_tm.tm_wday, RTCState), + VMSTATE_INT32(current_tm.tm_mday, RTCState), + VMSTATE_INT32(current_tm.tm_mon, RTCState), + VMSTATE_INT32(current_tm.tm_year, RTCState), + VMSTATE_TIMER(periodic_timer, RTCState), + VMSTATE_INT64(next_periodic_time, RTCState), + VMSTATE_INT64(next_second_time, RTCState), + VMSTATE_TIMER(second_timer, RTCState), + VMSTATE_TIMER(second_timer2, RTCState), + VMSTATE_UINT32_V(irq_coalesced, RTCState, 2), + VMSTATE_UINT32_V(period, RTCState, 2), + VMSTATE_END_OF_LIST() + } +}; + +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); + + qemu_irq_lower(s->irq); + +#ifdef TARGET_I386 + if (rtc_td_hack) + s->irq_coalesced = 0; +#endif +} + +static int rtc_initfn(ISADevice *dev) +{ + RTCState *s = DO_UPCAST(RTCState, dev, 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; + + rtc_set_date_from_host(dev); + + s->periodic_timer = qemu_new_timer(rtc_clock, rtc_periodic_timer, s); +#ifdef TARGET_I386 + if (rtc_td_hack) + s->coalesced_timer = + qemu_new_timer(rtc_clock, rtc_coalesced_timer, s); +#endif + s->second_timer = qemu_new_timer(rtc_clock, rtc_update_second, s); + s->second_timer2 = qemu_new_timer(rtc_clock, rtc_update_second2, s); + + s->next_second_time = + qemu_get_clock(rtc_clock) + (get_ticks_per_sec() * 99) / 100; + qemu_mod_timer(s->second_timer2, s->next_second_time); + + register_ioport_write(base, 2, 1, cmos_ioport_write, s); + register_ioport_read(base, 2, 1, cmos_ioport_read, s); + isa_init_ioport_range(dev, base, 2); + + qdev_set_legacy_instance_id(&dev->qdev, base, 2); + qemu_register_reset(rtc_reset, s); + return 0; +} + +ISADevice *rtc_init(int base_year, qemu_irq intercept_irq) +{ + ISADevice *dev; + RTCState *s; + + dev = isa_create("mc146818rtc"); + s = DO_UPCAST(RTCState, dev, dev); + qdev_prop_set_int32(&dev->qdev, "base_year", base_year); + qdev_init_nofail(&dev->qdev); + if (intercept_irq) { + s->irq = intercept_irq; + } else { + isa_init_irq(dev, &s->irq, RTC_ISA_IRQ); + } + return dev; +} + +static ISADeviceInfo mc146818rtc_info = { + .qdev.name = "mc146818rtc", + .qdev.size = sizeof(RTCState), + .qdev.no_user = 1, + .qdev.vmsd = &vmstate_rtc, + .init = rtc_initfn, + .qdev.props = (Property[]) { + DEFINE_PROP_INT32("base_year", RTCState, base_year, 1980), + DEFINE_PROP_END_OF_LIST(), + } +}; + +static void mc146818rtc_register(void) +{ + isa_qdev_register(&mc146818rtc_info); +} +device_init(mc146818rtc_register) diff --git a/hw/mc146818rtc.h b/hw/mc146818rtc.h new file mode 100644 index 0000000..575968c --- /dev/null +++ b/hw/mc146818rtc.h @@ -0,0 +1,12 @@ +#ifndef MC146818RTC_H +#define MC146818RTC_H + +#include "isa.h" + +#define RTC_ISA_IRQ 8 + +ISADevice *rtc_init(int base_year, qemu_irq intercept_irq); +void rtc_set_memory(ISADevice *dev, int addr, int val); +void rtc_set_date(ISADevice *dev, const struct tm *tm); + +#endif /* !MC146818RTC_H */ diff --git a/hw/mcf.h b/hw/mcf.h new file mode 100644 index 0000000..91f2821 --- /dev/null +++ b/hw/mcf.h @@ -0,0 +1,21 @@ +#ifndef HW_MCF_H +#define HW_MCF_H +/* Motorola ColdFire device prototypes. */ + +/* mcf_uart.c */ +uint32_t mcf_uart_read(void *opaque, target_phys_addr_t addr); +void mcf_uart_write(void *opaque, target_phys_addr_t addr, uint32_t val); +void *mcf_uart_init(qemu_irq irq, CharDriverState *chr); +void mcf_uart_mm_init(target_phys_addr_t base, qemu_irq irq, + CharDriverState *chr); + +/* mcf_intc.c */ +qemu_irq *mcf_intc_init(target_phys_addr_t base, CPUState *env); + +/* mcf_fec.c */ +void mcf_fec_init(NICInfo *nd, target_phys_addr_t base, qemu_irq *irq); + +/* mcf5206.c */ +qemu_irq *mcf5206_init(uint32_t base, CPUState *env); + +#endif diff --git a/hw/mcf5206.c b/hw/mcf5206.c new file mode 100644 index 0000000..2a618d4 --- /dev/null +++ b/hw/mcf5206.c @@ -0,0 +1,541 @@ +/* + * Motorola ColdFire MCF5206 SoC embedded peripheral emulation. + * + * Copyright (c) 2007 CodeSourcery. + * + * This code is licenced under the GPL + */ +#include "hw.h" +#include "mcf.h" +#include "qemu-timer.h" +#include "sysemu.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 *)qemu_mallocz(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 { + CPUState *env; + 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->env, 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 uint32_t m5206_mbar_read(m5206_mbar_state *s, uint32_t offset) +{ + 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); + } else if (offset >= 0x180 && offset < 0x1a0) { + return mcf_uart_read(s->uart[1], offset - 0x180); + } + 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, + uint32_t value) +{ + 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); + return; + } else if (offset >= 0x180 && offset < 0x1a0) { + mcf_uart_write(s->uart[1], offset - 0x180, value); + 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 int 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, target_phys_addr_t offset); +static uint32_t m5206_mbar_readl(void *opaque, target_phys_addr_t offset); + +static uint32_t m5206_mbar_readb(void *opaque, target_phys_addr_t 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); +} + +static uint32_t m5206_mbar_readw(void *opaque, target_phys_addr_t 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); +} + +static uint32_t m5206_mbar_readl(void *opaque, target_phys_addr_t 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); +} + +static void m5206_mbar_writew(void *opaque, target_phys_addr_t offset, + uint32_t value); +static void m5206_mbar_writel(void *opaque, target_phys_addr_t offset, + uint32_t value); + +static void m5206_mbar_writeb(void *opaque, target_phys_addr_t 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); +} + +static void m5206_mbar_writew(void *opaque, target_phys_addr_t 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); +} + +static void m5206_mbar_writel(void *opaque, target_phys_addr_t 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); +} + +static CPUReadMemoryFunc * const m5206_mbar_readfn[] = { + m5206_mbar_readb, + m5206_mbar_readw, + m5206_mbar_readl +}; + +static CPUWriteMemoryFunc * const m5206_mbar_writefn[] = { + m5206_mbar_writeb, + m5206_mbar_writew, + m5206_mbar_writel +}; + +qemu_irq *mcf5206_init(uint32_t base, CPUState *env) +{ + m5206_mbar_state *s; + qemu_irq *pic; + int iomemtype; + + s = (m5206_mbar_state *)qemu_mallocz(sizeof(m5206_mbar_state)); + iomemtype = cpu_register_io_memory(m5206_mbar_readfn, + m5206_mbar_writefn, s, + DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(base, 0x00001000, iomemtype); + + 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->env = env; + + m5206_mbar_reset(s); + return pic; +} diff --git a/hw/mcf5208.c b/hw/mcf5208.c new file mode 100644 index 0000000..17a692d --- /dev/null +++ b/hw/mcf5208.c @@ -0,0 +1,306 @@ +/* + * Motorola ColdFire MCF5208 SoC emulation. + * + * Copyright (c) 2007 CodeSourcery. + * + * This code is licenced under the GPL + */ +#include "hw.h" +#include "mcf.h" +#include "qemu-timer.h" +#include "sysemu.h" +#include "net.h" +#include "boards.h" +#include "loader.h" +#include "elf.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 { + 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, target_phys_addr_t offset, + uint32_t value) +{ + 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 uint32_t m5208_timer_read(void *opaque, target_phys_addr_t addr) +{ + 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 CPUReadMemoryFunc * const m5208_timer_readfn[] = { + m5208_timer_read, + m5208_timer_read, + m5208_timer_read +}; + +static CPUWriteMemoryFunc * const m5208_timer_writefn[] = { + m5208_timer_write, + m5208_timer_write, + m5208_timer_write +}; + +static uint32_t m5208_sys_read(void *opaque, target_phys_addr_t addr) +{ + 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, target_phys_addr_t addr, + uint32_t value) +{ + hw_error("m5208_sys_write: Bad offset 0x%x\n", (int)addr); +} + +static CPUReadMemoryFunc * const m5208_sys_readfn[] = { + m5208_sys_read, + m5208_sys_read, + m5208_sys_read +}; + +static CPUWriteMemoryFunc * const m5208_sys_writefn[] = { + m5208_sys_write, + m5208_sys_write, + m5208_sys_write +}; + +static void mcf5208_sys_init(qemu_irq *pic) +{ + int iomemtype; + m5208_timer_state *s; + QEMUBH *bh; + int i; + + iomemtype = cpu_register_io_memory(m5208_sys_readfn, + m5208_sys_writefn, NULL, + DEVICE_NATIVE_ENDIAN); + /* SDRAMC. */ + cpu_register_physical_memory(0xfc0a8000, 0x00004000, iomemtype); + /* Timers. */ + for (i = 0; i < 2; i++) { + s = (m5208_timer_state *)qemu_mallocz(sizeof(m5208_timer_state)); + bh = qemu_bh_new(m5208_timer_trigger, s); + s->timer = ptimer_init(bh); + iomemtype = cpu_register_io_memory(m5208_timer_readfn, + m5208_timer_writefn, s, + DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(0xfc080000 + 0x4000 * i, 0x00004000, + iomemtype); + s->irq = pic[4 + i]; + } +} + +static void mcf5208evb_init(ram_addr_t ram_size, + const char *boot_device, + const char *kernel_filename, const char *kernel_cmdline, + const char *initrd_filename, const char *cpu_model) +{ + CPUState *env; + int kernel_size; + uint64_t elf_entry; + target_phys_addr_t entry; + qemu_irq *pic; + + if (!cpu_model) + cpu_model = "m5208"; + env = cpu_init(cpu_model); + if (!env) { + fprintf(stderr, "Unable to find m68k CPU definition\n"); + exit(1); + } + + /* Initialize CPU registers. */ + env->vbr = 0; + /* TODO: Configure BARs. */ + + /* DRAM at 0x40000000 */ + cpu_register_physical_memory(0x40000000, ram_size, + qemu_ram_alloc(NULL, "mcf5208.ram", ram_size) | IO_MEM_RAM); + + /* Internal SRAM. */ + cpu_register_physical_memory(0x80000000, 16384, + qemu_ram_alloc(NULL, "mcf5208.sram", 16384) | IO_MEM_RAM); + + /* Internal peripherals. */ + pic = mcf_intc_init(0xfc048000, env); + + mcf_uart_mm_init(0xfc060000, pic[26], serial_hds[0]); + mcf_uart_mm_init(0xfc064000, pic[27], serial_hds[1]); + mcf_uart_mm_init(0xfc068000, pic[28], serial_hds[2]); + + mcf5208_sys_init(pic); + + if (nb_nics > 1) { + fprintf(stderr, "Too many NICs\n"); + exit(1); + } + if (nd_table[0].vlan) + mcf_fec_init(&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) { + 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); + } + 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/mcf_fec.c b/hw/mcf_fec.c new file mode 100644 index 0000000..21035da --- /dev/null +++ b/hw/mcf_fec.c @@ -0,0 +1,481 @@ +/* + * ColdFire Fast Ethernet Controller emulation. + * + * Copyright (c) 2007 CodeSourcery. + * + * This code is licenced under the GPL + */ +#include "hw.h" +#include "net.h" +#include "mcf.h" +/* For crc32 */ +#include + +//#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 { + qemu_irq *irq; + int mmio_index; + 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, (uint8_t *)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, (uint8_t *)&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(&s->nic->nc, 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) +{ + 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) + DPRINTF("RX buffer full\n"); +} + +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; +} + +static uint32_t mcf_fec_read(void *opaque, target_phys_addr_t addr) +{ + 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 s->mmfr; + 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, target_phys_addr_t addr, uint32_t value) +{ + 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: + /* TODO: Implement MII. */ + s->mmfr = value; + 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_can_receive(VLANClientState *nc) +{ + mcf_fec_state *s = DO_UPCAST(NICState, nc, nc)->opaque; + return s->rx_enabled; +} + +static ssize_t mcf_fec_receive(VLANClientState *nc, const uint8_t *buf, size_t size) +{ + mcf_fec_state *s = DO_UPCAST(NICState, nc, nc)->opaque; + 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; + + DPRINTF("do_rx len %d\n", size); + if (!s->rx_enabled) { + fprintf(stderr, "mcf_fec_receive: Unexpected packet\n"); + } + /* 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; + } + addr = s->rx_descriptor; + while (size > 0) { + mcf_fec_read_bd(&bd, addr); + if ((bd.flags & FEC_BD_E) == 0) { + /* No descriptors available. Bail out. */ + /* FIXME: This is wrong. We should probably either save the + remainder for when more RX buffers are available, or + flag an error. */ + fprintf(stderr, "mcf_fec: Lost end of frame\n"); + break; + } + 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 size; +} + +static CPUReadMemoryFunc * const mcf_fec_readfn[] = { + mcf_fec_read, + mcf_fec_read, + mcf_fec_read +}; + +static CPUWriteMemoryFunc * const mcf_fec_writefn[] = { + mcf_fec_write, + mcf_fec_write, + mcf_fec_write +}; + +static void mcf_fec_cleanup(VLANClientState *nc) +{ + mcf_fec_state *s = DO_UPCAST(NICState, nc, nc)->opaque; + + cpu_unregister_io_memory(s->mmio_index); + + qemu_free(s); +} + +static NetClientInfo net_mcf_fec_info = { + .type = NET_CLIENT_TYPE_NIC, + .size = sizeof(NICState), + .can_receive = mcf_fec_can_receive, + .receive = mcf_fec_receive, + .cleanup = mcf_fec_cleanup, +}; + +void mcf_fec_init(NICInfo *nd, target_phys_addr_t base, qemu_irq *irq) +{ + mcf_fec_state *s; + + qemu_check_nic_model(nd, "mcf_fec"); + + s = (mcf_fec_state *)qemu_mallocz(sizeof(mcf_fec_state)); + s->irq = irq; + s->mmio_index = cpu_register_io_memory(mcf_fec_readfn, + mcf_fec_writefn, s, + DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(base, 0x400, s->mmio_index); + + memcpy(s->conf.macaddr.a, nd->macaddr, sizeof(nd->macaddr)); + s->conf.vlan = nd->vlan; + s->conf.peer = nd->netdev; + + s->nic = qemu_new_nic(&net_mcf_fec_info, &s->conf, nd->model, nd->name, s); + + qemu_format_nic_info_str(&s->nic->nc, s->conf.macaddr.a); +} diff --git a/hw/mcf_intc.c b/hw/mcf_intc.c new file mode 100644 index 0000000..ac04295 --- /dev/null +++ b/hw/mcf_intc.c @@ -0,0 +1,157 @@ +/* + * ColdFire Interrupt Controller emulation. + * + * Copyright (c) 2007 CodeSourcery. + * + * This code is licenced under the GPL + */ +#include "hw.h" +#include "mcf.h" + +typedef struct { + uint64_t ipr; + uint64_t imr; + uint64_t ifr; + uint64_t enabled; + uint8_t icr[64]; + CPUState *env; + 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->env, best_level, s->active_vector); +} + +static uint32_t mcf_intc_read(void *opaque, target_phys_addr_t addr) +{ + 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, target_phys_addr_t addr, uint32_t val) +{ + 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; + 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 CPUReadMemoryFunc * const mcf_intc_readfn[] = { + mcf_intc_read, + mcf_intc_read, + mcf_intc_read +}; + +static CPUWriteMemoryFunc * const mcf_intc_writefn[] = { + mcf_intc_write, + mcf_intc_write, + mcf_intc_write +}; + +qemu_irq *mcf_intc_init(target_phys_addr_t base, CPUState *env) +{ + mcf_intc_state *s; + int iomemtype; + + s = qemu_mallocz(sizeof(mcf_intc_state)); + s->env = env; + mcf_intc_reset(s); + + iomemtype = cpu_register_io_memory(mcf_intc_readfn, + mcf_intc_writefn, s, + DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(base, 0x100, iomemtype); + + return qemu_allocate_irqs(mcf_intc_set_irq, s, 64); +} diff --git a/hw/mcf_uart.c b/hw/mcf_uart.c new file mode 100644 index 0000000..db57096 --- /dev/null +++ b/hw/mcf_uart.c @@ -0,0 +1,310 @@ +/* + * ColdFire UART emulation. + * + * Copyright (c) 2007 CodeSourcery. + * + * This code is licenced under the GPL + */ +#include "hw.h" +#include "mcf.h" +#include "qemu-char.h" + +typedef struct { + 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); +} + +uint32_t mcf_uart_read(void *opaque, target_phys_addr_t addr) +{ + 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_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, target_phys_addr_t addr, uint32_t val) +{ + 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 = qemu_mallocz(sizeof(mcf_uart_state)); + s->chr = chr; + s->irq = irq; + if (chr) { + qemu_chr_add_handlers(chr, mcf_uart_can_receive, mcf_uart_receive, + mcf_uart_event, s); + } + mcf_uart_reset(s); + return s; +} + + +static CPUReadMemoryFunc * const mcf_uart_readfn[] = { + mcf_uart_read, + mcf_uart_read, + mcf_uart_read +}; + +static CPUWriteMemoryFunc * const mcf_uart_writefn[] = { + mcf_uart_write, + mcf_uart_write, + mcf_uart_write +}; + +void mcf_uart_mm_init(target_phys_addr_t base, qemu_irq irq, + CharDriverState *chr) +{ + mcf_uart_state *s; + int iomemtype; + + s = mcf_uart_init(irq, chr); + iomemtype = cpu_register_io_memory(mcf_uart_readfn, + mcf_uart_writefn, s, + DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(base, 0x40, iomemtype); +} diff --git a/hw/microblaze_pic_cpu.c b/hw/microblaze_pic_cpu.c new file mode 100644 index 0000000..7c59382 --- /dev/null +++ b/hw/microblaze_pic_cpu.c @@ -0,0 +1,50 @@ +/* + * QEMU MicroBlaze CPU interrupt wrapper logic. + * + * 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.h" +#include "pc.h" + +#define D(x) + +void pic_info(Monitor *mon) +{} +void irq_info(Monitor *mon) +{} + +static void microblaze_pic_cpu_handler(void *opaque, int irq, int level) +{ + CPUState *env = (CPUState *)opaque; + int type = irq ? CPU_INTERRUPT_NMI : CPU_INTERRUPT_HARD; + + if (level) + cpu_interrupt(env, type); + else + cpu_reset_interrupt(env, type); +} + +qemu_irq *microblaze_pic_init_cpu(CPUState *env); +qemu_irq *microblaze_pic_init_cpu(CPUState *env) +{ + return qemu_allocate_irqs(microblaze_pic_cpu_handler, env, 2); +} diff --git a/hw/mips-bios.h b/hw/mips-bios.h new file mode 100644 index 0000000..b4b88ac --- /dev/null +++ b/hw/mips-bios.h @@ -0,0 +1,8 @@ +#include "cpu.h" + +#define BIOS_SIZE (4 * 1024 * 1024) +#ifdef TARGET_WORDS_BIGENDIAN +#define BIOS_FILENAME "mips_bios.bin" +#else +#define BIOS_FILENAME "mipsel_bios.bin" +#endif diff --git a/hw/mips.h b/hw/mips.h new file mode 100644 index 0000000..73aa8f8 --- /dev/null +++ b/hw/mips.h @@ -0,0 +1,40 @@ +#ifndef HW_MIPS_H +#define HW_MIPS_H +/* Definitions for mips board emulation. */ + +/* gt64xxx.c */ +PCIBus *gt64120_register(qemu_irq *pic); + +/* bonito.c */ +PCIBus *bonito_init(qemu_irq *pic); + +/* ds1225y.c */ +void *ds1225y_init(target_phys_addr_t mem_base, const char *filename); +void ds1225y_set_protection(void *opaque, int protection); + +/* g364fb.c */ +int g364fb_mm_init(target_phys_addr_t vram_base, + target_phys_addr_t ctrl_base, int it_shift, + qemu_irq irq); + +/* mipsnet.c */ +void mipsnet_init(int base, qemu_irq irq, NICInfo *nd); + +/* jazz_led.c */ +void jazz_led_init(target_phys_addr_t base); + +/* rc4030.c */ +typedef struct rc4030DMAState *rc4030_dma; +void rc4030_dma_memory_rw(void *opaque, target_phys_addr_t addr, uint8_t *buf, int len, int is_write); +void rc4030_dma_read(void *dma, uint8_t *buf, int len); +void rc4030_dma_write(void *dma, uint8_t *buf, int len); + +void *rc4030_init(qemu_irq timer, qemu_irq jazz_bus, + qemu_irq **irqs, rc4030_dma **dmas); + +/* dp8393x.c */ +void dp83932_init(NICInfo *nd, target_phys_addr_t base, int it_shift, + qemu_irq irq, void* mem_opaque, + void (*memory_rw)(void *opaque, target_phys_addr_t addr, uint8_t *buf, int len, int is_write)); + +#endif diff --git a/hw/mips_addr.c b/hw/mips_addr.c new file mode 100644 index 0000000..aa1c7d8 --- /dev/null +++ b/hw/mips_addr.c @@ -0,0 +1,34 @@ +/* + * 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.h" +#include "mips_cpudevs.h" + +uint64_t cpu_mips_kseg0_to_phys(void *opaque, uint64_t addr) +{ + return addr & 0x7fffffffll; +} + +uint64_t cpu_mips_phys_to_kseg0(void *opaque, uint64_t addr) +{ + return addr | ~0x7fffffffll; +} diff --git a/hw/mips_cpudevs.h b/hw/mips_cpudevs.h new file mode 100644 index 0000000..db82b41 --- /dev/null +++ b/hw/mips_cpudevs.h @@ -0,0 +1,15 @@ +#ifndef HW_MIPS_CPUDEVS_H +#define HW_MIPS_CPUDEVS_H +/* Definitions for MIPS CPU internal devices. */ + +/* mips_addr.c */ +uint64_t cpu_mips_kseg0_to_phys(void *opaque, uint64_t addr); +uint64_t cpu_mips_phys_to_kseg0(void *opaque, uint64_t addr); + +/* mips_int.c */ +void cpu_mips_irq_init_cpu(CPUState *env); + +/* mips_timer.c */ +void cpu_mips_clock_init(CPUState *); + +#endif diff --git a/hw/mips_fulong2e.c b/hw/mips_fulong2e.c new file mode 100644 index 0000000..3b0abdb --- /dev/null +++ b/hw/mips_fulong2e.c @@ -0,0 +1,408 @@ +/* + * 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. + */ + +/* + * 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.h" +#include "pc.h" +#include "fdc.h" +#include "net.h" +#include "boards.h" +#include "smbus.h" +#include "block.h" +#include "flash.h" +#include "mips.h" +#include "mips_cpudevs.h" +#include "pci.h" +#include "usb-uhci.h" +#include "qemu-char.h" +#include "sysemu.h" +#include "audio/audio.h" +#include "qemu-log.h" +#include "loader.h" +#include "mips-bios.h" +#include "ide.h" +#include "elf.h" +#include "vt82c686.h" +#include "mc146818rtc.h" +#include "blockdev.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 PITState *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 (CPUState *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 + ~TARGET_PAGE_MASK) & TARGET_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 = qemu_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)); + + return kernel_entry; +} + +static void write_bootloader (CPUState *env, uint8_t *base, int64_t kernel_addr) +{ + uint32_t *p; + + /* Small bootloader */ + p = (uint32_t *) base; + + stl_raw(p++, 0x0bf00010); /* j 0x1fc00040 */ + stl_raw(p++, 0x00000000); /* nop */ + + /* Second part of the bootloader */ + p = (uint32_t *) (base + 0x040); + + stl_raw(p++, 0x3c040000); /* lui a0, 0 */ + stl_raw(p++, 0x34840002); /* ori a0, a0, 2 */ + stl_raw(p++, 0x3c050000 | ((ENVP_ADDR >> 16) & 0xffff)); /* lui a1, high(ENVP_ADDR) */ + stl_raw(p++, 0x34a50000 | (ENVP_ADDR & 0xffff)); /* ori a1, a0, low(ENVP_ADDR) */ + stl_raw(p++, 0x3c060000 | (((ENVP_ADDR + 8) >> 16) & 0xffff)); /* lui a2, high(ENVP_ADDR + 8) */ + stl_raw(p++, 0x34c60000 | ((ENVP_ADDR + 8) & 0xffff)); /* ori a2, a2, low(ENVP_ADDR + 8) */ + stl_raw(p++, 0x3c070000 | (loaderparams.ram_size >> 16)); /* lui a3, high(env->ram_size) */ + stl_raw(p++, 0x34e70000 | (loaderparams.ram_size & 0xffff)); /* ori a3, a3, low(env->ram_size) */ + stl_raw(p++, 0x3c1f0000 | ((kernel_addr >> 16) & 0xffff)); /* lui ra, high(kernel_addr) */; + stl_raw(p++, 0x37ff0000 | (kernel_addr & 0xffff)); /* ori ra, ra, low(kernel_addr) */ + stl_raw(p++, 0x03e00008); /* jr ra */ + stl_raw(p++, 0x00000000); /* nop */ +} + + +static void main_cpu_reset(void *opaque) +{ + CPUState *env = opaque; + + cpu_reset(env); + /* TODO: 2E reset stuff */ + if (loaderparams.kernel_filename) { + env->CP0_Status &= ~((1 << CP0St_BEV) | (1 << CP0St_ERL)); + } +} + +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 (void) +{ + 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, "rtl8139", default_devaddr); + } +} + +static void cpu_request_exit(void *opaque, int irq, int level) +{ + CPUState *env = cpu_single_env; + + if (env && level) { + cpu_exit(env); + } +} + +static void mips_fulong2e_init(ram_addr_t ram_size, const char *boot_device, + const char *kernel_filename, const char *kernel_cmdline, + const char *initrd_filename, const char *cpu_model) +{ + char *filename; + unsigned long ram_offset, bios_offset; + long bios_size; + int64_t kernel_entry; + qemu_irq *i8259; + qemu_irq *cpu_exit_irq; + int via_devfn; + PCIBus *pci_bus; + uint8_t *eeprom_buf; + i2c_bus *smbus; + int i; + DriveInfo *hd[MAX_IDE_BUS * MAX_IDE_DEVS]; + DeviceState *eeprom; + CPUState *env; + + /* init CPUs */ + if (cpu_model == NULL) { + cpu_model = "Loongson-2E"; + } + env = cpu_init(cpu_model); + if (!env) { + fprintf(stderr, "Unable to find CPU definition\n"); + exit(1); + } + + register_savevm(NULL, "cpu", 0, 3, cpu_save, cpu_load, env); + qemu_register_reset(main_cpu_reset, env); + + /* fulong 2e has 256M ram. */ + ram_size = 256 * 1024 * 1024; + + /* fulong 2e has a 1M flash.Winbond W39L040AP70Z */ + bios_size = 1024 * 1024; + + /* allocate RAM */ + ram_offset = qemu_ram_alloc(NULL, "fulong2e.ram", ram_size); + bios_offset = qemu_ram_alloc(NULL, "fulong2e.bios", bios_size); + + cpu_register_physical_memory(0, ram_size, ram_offset); + cpu_register_physical_memory(0x1fc00000LL, + bios_size, bios_offset | IO_MEM_ROM); + + /* 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, qemu_get_ram_ptr(bios_offset), 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); + qemu_free(filename); + } else { + bios_size = -1; + } + + if ((bios_size < 0 || bios_size > BIOS_SIZE) && !kernel_filename) { + fprintf(stderr, "qemu: Could not load MIPS bios '%s'\n", bios_name); + exit(1); + } + } + + /* Init internal devices */ + cpu_mips_irq_init_cpu(env); + cpu_mips_clock_init(env); + + /* Interrupt controller */ + /* The 8259 -> IP5 */ + i8259 = i8259_init(env->irq[5]); + + /* North bridge, Bonito --> IP2 */ + pci_bus = bonito_init((qemu_irq *)&(env->irq[2])); + + /* South bridge */ + if (drive_get_max_bus(IF_IDE) >= MAX_IDE_BUS) { + fprintf(stderr, "qemu: too many IDE bus\n"); + exit(1); + } + + for(i = 0; i < MAX_IDE_BUS * MAX_IDE_DEVS; i++) { + hd[i] = drive_get(IF_IDE, i / MAX_IDE_DEVS, i % MAX_IDE_DEVS); + } + + via_devfn = vt82c686b_init(pci_bus, PCI_DEVFN(FULONG2E_VIA_SLOT, 0)); + if (via_devfn < 0) { + fprintf(stderr, "vt82c686b_init error \n"); + exit(1); + } + + isa_bus_irqs(i8259); + vt82c686b_ide_init(pci_bus, hd, PCI_DEVFN(FULONG2E_VIA_SLOT, 1)); + usb_uhci_vt82c686b_init(pci_bus, PCI_DEVFN(FULONG2E_VIA_SLOT, 2)); + usb_uhci_vt82c686b_init(pci_bus, PCI_DEVFN(FULONG2E_VIA_SLOT, 3)); + + smbus = vt82c686b_pm_init(pci_bus, PCI_DEVFN(FULONG2E_VIA_SLOT, 4), + 0xeee1, NULL); + eeprom_buf = qemu_mallocz(8 * 256); /* XXX: make this persistent */ + memcpy(eeprom_buf, eeprom_spd, sizeof(eeprom_spd)); + /* TODO: Populate SPD eeprom data. */ + eeprom = qdev_create((BusState *)smbus, "smbus-eeprom"); + qdev_prop_set_uint8(eeprom, "address", 0x50); + qdev_prop_set_ptr(eeprom, "data", eeprom_buf); + qdev_init_nofail(eeprom); + + /* init other devices */ + pit = pit_init(0x40, isa_get_irq(0)); + cpu_exit_irq = qemu_allocate_irqs(cpu_request_exit, NULL, 1); + DMA_init(0, cpu_exit_irq); + + /* Super I/O */ + isa_create_simple("i8042"); + + rtc_init(2000, NULL); + + for(i = 0; i < MAX_SERIAL_PORTS; i++) { + if (serial_hds[i]) { + serial_isa_init(i, serial_hds[i]); + } + } + + if (parallel_hds[0]) { + parallel_init(0, parallel_hds[0]); + } + + /* Sound card */ + audio_init(pci_bus); + /* Network card */ + network_init(); +} + +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_int.c b/hw/mips_int.c new file mode 100644 index 0000000..477f6ab --- /dev/null +++ b/hw/mips_int.c @@ -0,0 +1,65 @@ +/* + * 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.h" +#include "mips_cpudevs.h" +#include "cpu.h" + +static void cpu_mips_irq_request(void *opaque, int irq, int level) +{ + CPUState *env = (CPUState *)opaque; + + if (irq < 0 || irq > 7) + return; + + if (level) { + env->CP0_Cause |= 1 << (irq + CP0Ca_IP); + } else { + env->CP0_Cause &= ~(1 << (irq + CP0Ca_IP)); + } + + if (env->CP0_Cause & CP0Ca_IP_mask) { + cpu_interrupt(env, CPU_INTERRUPT_HARD); + } else { + cpu_reset_interrupt(env, CPU_INTERRUPT_HARD); + } +} + +void cpu_mips_irq_init_cpu(CPUState *env) +{ + qemu_irq *qi; + int i; + + qi = qemu_allocate_irqs(cpu_mips_irq_request, env, 8); + for (i = 0; i < 8; i++) { + env->irq[i] = qi[i]; + } +} + +void cpu_mips_soft_irq(CPUState *env, int irq, int level) +{ + if (irq < 0 || irq > 2) { + return; + } + + qemu_set_irq(env->irq[irq], level); +} diff --git a/hw/mips_jazz.c b/hw/mips_jazz.c new file mode 100644 index 0000000..85eba5a --- /dev/null +++ b/hw/mips_jazz.c @@ -0,0 +1,314 @@ +/* + * 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.h" +#include "mips.h" +#include "mips_cpudevs.h" +#include "pc.h" +#include "isa.h" +#include "fdc.h" +#include "sysemu.h" +#include "arch_init.h" +#include "boards.h" +#include "net.h" +#include "esp.h" +#include "mips-bios.h" +#include "loader.h" +#include "mc146818rtc.h" +#include "blockdev.h" + +enum jazz_model_e +{ + JAZZ_MAGNUM, + JAZZ_PICA61, +}; + +static void main_cpu_reset(void *opaque) +{ + CPUState *env = opaque; + cpu_reset(env); +} + +static uint32_t rtc_readb(void *opaque, target_phys_addr_t addr) +{ + return cpu_inw(0x71); +} + +static void rtc_writeb(void *opaque, target_phys_addr_t addr, uint32_t val) +{ + cpu_outw(0x71, val & 0xff); +} + +static CPUReadMemoryFunc * const rtc_read[3] = { + rtc_readb, + rtc_readb, + rtc_readb, +}; + +static CPUWriteMemoryFunc * const rtc_write[3] = { + rtc_writeb, + rtc_writeb, + rtc_writeb, +}; + +static void dma_dummy_writeb(void *opaque, target_phys_addr_t addr, uint32_t val) +{ + /* Nothing to do. That is only to ensure that + * the current DMA acknowledge cycle is completed. */ +} + +static CPUReadMemoryFunc * const dma_dummy_read[3] = { + NULL, + NULL, + NULL, +}; + +static CPUWriteMemoryFunc * const dma_dummy_write[3] = { + dma_dummy_writeb, + dma_dummy_writeb, + dma_dummy_writeb, +}; + +#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 *env = cpu_single_env; + + if (env && level) { + cpu_exit(env); + } +} + +static +void mips_jazz_init (ram_addr_t ram_size, + const char *cpu_model, + enum jazz_model_e jazz_model) +{ + char *filename; + int bios_size, n; + CPUState *env; + qemu_irq *rc4030, *i8259; + rc4030_dma *dmas; + void* rc4030_opaque; + int s_rtc, s_dma_dummy; + NICInfo *nd; + PITState *pit; + DriveInfo *fds[MAX_FD]; + qemu_irq esp_reset, dma_enable; + qemu_irq *cpu_exit_irq; + ram_addr_t ram_offset; + ram_addr_t bios_offset; + + /* init CPUs */ + if (cpu_model == NULL) { +#ifdef TARGET_MIPS64 + cpu_model = "R4000"; +#else + /* FIXME: All wrong, this maybe should be R3000 for the older JAZZs. */ + cpu_model = "24Kf"; +#endif + } + env = cpu_init(cpu_model); + if (!env) { + fprintf(stderr, "Unable to find CPU definition\n"); + exit(1); + } + qemu_register_reset(main_cpu_reset, env); + + /* allocate RAM */ + ram_offset = qemu_ram_alloc(NULL, "mips_jazz.ram", ram_size); + cpu_register_physical_memory(0, ram_size, ram_offset | IO_MEM_RAM); + + bios_offset = qemu_ram_alloc(NULL, "mips_jazz.bios", MAGNUM_BIOS_SIZE); + cpu_register_physical_memory(0x1fc00000LL, + MAGNUM_BIOS_SIZE, bios_offset | IO_MEM_ROM); + cpu_register_physical_memory(0xfff00000LL, + MAGNUM_BIOS_SIZE, bios_offset | IO_MEM_ROM); + + /* 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); + qemu_free(filename); + } else { + bios_size = -1; + } + if (bios_size < 0 || bios_size > MAGNUM_BIOS_SIZE) { + fprintf(stderr, "qemu: Could not load MIPS bios '%s'\n", + bios_name); + exit(1); + } + + /* Init CPU internal devices */ + cpu_mips_irq_init_cpu(env); + cpu_mips_clock_init(env); + + /* Chipset */ + rc4030_opaque = rc4030_init(env->irq[6], env->irq[3], &rc4030, &dmas); + s_dma_dummy = cpu_register_io_memory(dma_dummy_read, dma_dummy_write, NULL, + DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(0x8000d000, 0x00001000, s_dma_dummy); + + /* ISA devices */ + i8259 = i8259_init(env->irq[4]); + isa_bus_new(NULL); + isa_bus_irqs(i8259); + cpu_exit_irq = qemu_allocate_irqs(cpu_request_exit, NULL, 1); + DMA_init(0, cpu_exit_irq); + pit = pit_init(0x40, i8259[0]); + pcspk_init(pit); + + /* ISA IO space at 0x90000000 */ + isa_mmio_init(0x90000000, 0x01000000); + isa_mem_base = 0x11000000; + + /* Video card */ + switch (jazz_model) { + case JAZZ_MAGNUM: + g364fb_mm_init(0x40000000, 0x60000000, 0, rc4030[3]); + break; + case JAZZ_PICA61: + isa_vga_mm_init(0x40000000, 0x60000000, 0); + break; + default: + break; + } + + /* Network controller */ + for (n = 0; n < nb_nics; n++) { + nd = &nd_table[n]; + if (!nd->model) + nd->model = qemu_strdup("dp83932"); + if (strcmp(nd->model, "dp83932") == 0) { + dp83932_init(nd, 0x80001000, 2, rc4030[4], + rc4030_opaque, rc4030_dma_memory_rw); + break; + } else if (strcmp(nd->model, "?") == 0) { + 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], + 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(rc4030[1], 0, 0x80003000, fds); + + /* Real time clock */ + rtc_init(1980, NULL); + s_rtc = cpu_register_io_memory(rtc_read, rtc_write, NULL, + DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(0x80004000, 0x00001000, s_rtc); + + /* Keyboard (i8042) */ + i8042_mm_init(rc4030[6], rc4030[7], 0x80005000, 0x1000, 0x1); + + /* Serial ports */ + if (serial_hds[0]) { +#ifdef TARGET_WORDS_BIGENDIAN + serial_mm_init(0x80006000, 0, rc4030[8], 8000000/16, serial_hds[0], 1, 1); +#else + serial_mm_init(0x80006000, 0, rc4030[8], 8000000/16, serial_hds[0], 1, 0); +#endif + } + if (serial_hds[1]) { +#ifdef TARGET_WORDS_BIGENDIAN + serial_mm_init(0x80007000, 0, rc4030[9], 8000000/16, serial_hds[1], 1, 1); +#else + serial_mm_init(0x80007000, 0, rc4030[9], 8000000/16, serial_hds[1], 1, 0); +#endif + } + + /* Parallel port */ + if (parallel_hds[0]) + parallel_mm_init(0x80008000, 0, rc4030[0], parallel_hds[0]); + + /* Sound card */ + /* FIXME: missing Jazz sound at 0x8000c000, rc4030[2] */ + audio_init(i8259, NULL); + + /* NVRAM: Unprotected at 0x9000, Protected at 0xa000, Read only at 0xb000 */ + ds1225y_init(0x80009000, "nvram"); + + /* LED indicator */ + jazz_led_init(0x8000f000); +} + +static +void mips_magnum_init (ram_addr_t ram_size, + const char *boot_device, + const char *kernel_filename, const char *kernel_cmdline, + const char *initrd_filename, const char *cpu_model) +{ + mips_jazz_init(ram_size, cpu_model, JAZZ_MAGNUM); +} + +static +void mips_pica61_init (ram_addr_t ram_size, + const char *boot_device, + const char *kernel_filename, const char *kernel_cmdline, + const char *initrd_filename, const char *cpu_model) +{ + mips_jazz_init(ram_size, cpu_model, JAZZ_PICA61); +} + +static QEMUMachine mips_magnum_machine = { + .name = "magnum", + .desc = "MIPS Magnum", + .init = mips_magnum_init, + .use_scsi = 1, +}; + +static QEMUMachine mips_pica61_machine = { + .name = "pica61", + .desc = "Acer Pica 61", + .init = mips_pica61_init, + .use_scsi = 1, +}; + +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_malta.c b/hw/mips_malta.c new file mode 100644 index 0000000..7211d98 --- /dev/null +++ b/hw/mips_malta.c @@ -0,0 +1,978 @@ +/* + * 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.h" +#include "pc.h" +#include "fdc.h" +#include "net.h" +#include "boards.h" +#include "smbus.h" +#include "block.h" +#include "flash.h" +#include "mips.h" +#include "mips_cpudevs.h" +#include "pci.h" +#include "usb-uhci.h" +#include "vmware_vga.h" +#include "qemu-char.h" +#include "sysemu.h" +#include "arch_init.h" +#include "boards.h" +#include "qemu-log.h" +#include "mips-bios.h" +#include "ide.h" +#include "loader.h" +#include "elf.h" +#include "mc146818rtc.h" +#include "blockdev.h" + +//#define DEBUG_BOARD_INIT + +#define ENVP_ADDR 0x80002000l +#define ENVP_NB_ENTRIES 16 +#define ENVP_ENTRY_SIZE 256 + +#define MAX_IDE_BUS 2 + +typedef struct { + 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; + +static PITState *pit; + +static struct _loaderparams { + int ram_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_printf(s->display, "\e[H\n\n|\e[32m%-8.8s\e[00m|\r\n", leds_text); + qemu_chr_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 eeprom = { + .contents = { + /* 00000000: */ 0x80,0x08,0x04,0x0D,0x0A,0x01,0x40,0x00, + /* 00000008: */ 0x01,0x75,0x54,0x00,0x82,0x08,0x00,0x01, + /* 00000010: */ 0x8F,0x04,0x02,0x01,0x01,0x00,0x0E,0x00, + /* 00000018: */ 0x00,0x00,0x00,0x14,0x0F,0x14,0x2D,0x40, + /* 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 uint8_t eeprom24c0x_read(void) +{ + 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(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 uint32_t malta_fpga_readl(void *opaque, target_phys_addr_t addr) +{ + 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()); + 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_writel(void *opaque, target_phys_addr_t addr, + uint32_t val) +{ + MaltaFPGAState *s = opaque; + uint32_t saddr; + + saddr = (addr & 0xfffff); + + switch (saddr) { + + /* SWITCH Register */ + case 0x00200: + break; + + /* JMPRS Register */ + case 0x00210: + break; + + /* LEDBAR Register */ + /* XXX: implement a 8-LED array */ + case 0x00408: + s->leds = val & 0xff; + break; + + /* ASCIIWORD Register */ + case 0x00410: + snprintf(s->display_text, 9, "%08X", 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(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 CPUReadMemoryFunc * const malta_fpga_read[] = { + malta_fpga_readl, + malta_fpga_readl, + malta_fpga_readl +}; + +static CPUWriteMemoryFunc * const malta_fpga_write[] = { + malta_fpga_writel, + malta_fpga_writel, + malta_fpga_writel +}; + +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_printf(chr, "\e[HMalta LEDBAR\r\n"); + qemu_chr_printf(chr, "+--------+\r\n"); + qemu_chr_printf(chr, "+ +\r\n"); + qemu_chr_printf(chr, "+--------+\r\n"); + qemu_chr_printf(chr, "\n"); + qemu_chr_printf(chr, "Malta ASCII\r\n"); + qemu_chr_printf(chr, "+--------+\r\n"); + qemu_chr_printf(chr, "+ +\r\n"); + qemu_chr_printf(chr, "+--------+\r\n"); +} + +static MaltaFPGAState *malta_fpga_init(target_phys_addr_t base, qemu_irq uart_irq, CharDriverState *uart_chr) +{ + MaltaFPGAState *s; + int malta; + + s = (MaltaFPGAState *)qemu_mallocz(sizeof(MaltaFPGAState)); + + malta = cpu_register_io_memory(malta_fpga_read, + malta_fpga_write, s, + DEVICE_NATIVE_ENDIAN); + + cpu_register_physical_memory(base, 0x900, malta); + /* 0xa00 is less than a page, so will still get the right offsets. */ + cpu_register_physical_memory(base + 0xa00, 0x100000 - 0xa00, malta); + + s->display = qemu_chr_open("fpga", "vc:320x200", malta_fpga_led_init); + +#ifdef TARGET_WORDS_BIGENDIAN + s->uart = serial_mm_init(base + 0x900, 3, uart_irq, 230400, uart_chr, 1, 1); +#else + s->uart = serial_mm_init(base + 0x900, 3, uart_irq, 230400, uart_chr, 1, 0); +#endif + + malta_fpga_reset(s); + qemu_register_reset(malta_fpga_reset, s); + + return s; +} + +/* Network support */ +static void network_init(void) +{ + 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, "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 (CPUState *env, uint8_t *base, + int64_t kernel_entry) +{ + uint32_t *p; + + /* Small bootloader */ + p = (uint32_t *)base; + stl_raw(p++, 0x0bf00160); /* j 0x1fc00580 */ + stl_raw(p++, 0x00000000); /* nop */ + + /* YAMON service vector */ + stl_raw(base + 0x500, 0xbfc00580); /* start: */ + stl_raw(base + 0x504, 0xbfc0083c); /* print_count: */ + stl_raw(base + 0x520, 0xbfc00580); /* start: */ + stl_raw(base + 0x52c, 0xbfc00800); /* flush_cache: */ + stl_raw(base + 0x534, 0xbfc00808); /* print: */ + stl_raw(base + 0x538, 0xbfc00800); /* reg_cpu_isr: */ + stl_raw(base + 0x53c, 0xbfc00800); /* unred_cpu_isr: */ + stl_raw(base + 0x540, 0xbfc00800); /* reg_ic_isr: */ + stl_raw(base + 0x544, 0xbfc00800); /* unred_ic_isr: */ + stl_raw(base + 0x548, 0xbfc00800); /* reg_esr: */ + stl_raw(base + 0x54c, 0xbfc00800); /* unreg_esr: */ + stl_raw(base + 0x550, 0xbfc00800); /* getchar: */ + stl_raw(base + 0x554, 0xbfc00800); /* syscon_read: */ + + + /* Second part of the bootloader */ + p = (uint32_t *) (base + 0x580); + stl_raw(p++, 0x24040002); /* addiu a0, zero, 2 */ + stl_raw(p++, 0x3c1d0000 | (((ENVP_ADDR - 64) >> 16) & 0xffff)); /* lui sp, high(ENVP_ADDR) */ + stl_raw(p++, 0x37bd0000 | ((ENVP_ADDR - 64) & 0xffff)); /* ori sp, sp, low(ENVP_ADDR) */ + stl_raw(p++, 0x3c050000 | ((ENVP_ADDR >> 16) & 0xffff)); /* lui a1, high(ENVP_ADDR) */ + stl_raw(p++, 0x34a50000 | (ENVP_ADDR & 0xffff)); /* ori a1, a1, low(ENVP_ADDR) */ + stl_raw(p++, 0x3c060000 | (((ENVP_ADDR + 8) >> 16) & 0xffff)); /* lui a2, high(ENVP_ADDR + 8) */ + stl_raw(p++, 0x34c60000 | ((ENVP_ADDR + 8) & 0xffff)); /* ori a2, a2, low(ENVP_ADDR + 8) */ + stl_raw(p++, 0x3c070000 | (loaderparams.ram_size >> 16)); /* lui a3, high(ram_size) */ + stl_raw(p++, 0x34e70000 | (loaderparams.ram_size & 0xffff)); /* ori a3, a3, low(ram_size) */ + + /* Load BAR registers as done by YAMON */ + stl_raw(p++, 0x3c09b400); /* lui t1, 0xb400 */ + +#ifdef TARGET_WORDS_BIGENDIAN + stl_raw(p++, 0x3c08df00); /* lui t0, 0xdf00 */ +#else + stl_raw(p++, 0x340800df); /* ori t0, r0, 0x00df */ +#endif + stl_raw(p++, 0xad280068); /* sw t0, 0x0068(t1) */ + + stl_raw(p++, 0x3c09bbe0); /* lui t1, 0xbbe0 */ + +#ifdef TARGET_WORDS_BIGENDIAN + stl_raw(p++, 0x3c08c000); /* lui t0, 0xc000 */ +#else + stl_raw(p++, 0x340800c0); /* ori t0, r0, 0x00c0 */ +#endif + stl_raw(p++, 0xad280048); /* sw t0, 0x0048(t1) */ +#ifdef TARGET_WORDS_BIGENDIAN + stl_raw(p++, 0x3c084000); /* lui t0, 0x4000 */ +#else + stl_raw(p++, 0x34080040); /* ori t0, r0, 0x0040 */ +#endif + stl_raw(p++, 0xad280050); /* sw t0, 0x0050(t1) */ + +#ifdef TARGET_WORDS_BIGENDIAN + stl_raw(p++, 0x3c088000); /* lui t0, 0x8000 */ +#else + stl_raw(p++, 0x34080080); /* ori t0, r0, 0x0080 */ +#endif + stl_raw(p++, 0xad280058); /* sw t0, 0x0058(t1) */ +#ifdef TARGET_WORDS_BIGENDIAN + stl_raw(p++, 0x3c083f00); /* lui t0, 0x3f00 */ +#else + stl_raw(p++, 0x3408003f); /* ori t0, r0, 0x003f */ +#endif + stl_raw(p++, 0xad280060); /* sw t0, 0x0060(t1) */ + +#ifdef TARGET_WORDS_BIGENDIAN + stl_raw(p++, 0x3c08c100); /* lui t0, 0xc100 */ +#else + stl_raw(p++, 0x340800c1); /* ori t0, r0, 0x00c1 */ +#endif + stl_raw(p++, 0xad280080); /* sw t0, 0x0080(t1) */ +#ifdef TARGET_WORDS_BIGENDIAN + stl_raw(p++, 0x3c085e00); /* lui t0, 0x5e00 */ +#else + stl_raw(p++, 0x3408005e); /* ori t0, r0, 0x005e */ +#endif + stl_raw(p++, 0xad280088); /* sw t0, 0x0088(t1) */ + + /* Jump to kernel code */ + stl_raw(p++, 0x3c1f0000 | ((kernel_entry >> 16) & 0xffff)); /* lui ra, high(kernel_entry) */ + stl_raw(p++, 0x37ff0000 | (kernel_entry & 0xffff)); /* ori ra, ra, low(kernel_entry) */ + stl_raw(p++, 0x03e00008); /* jr ra */ + stl_raw(p++, 0x00000000); /* nop */ + + /* YAMON subroutines */ + p = (uint32_t *) (base + 0x800); + stl_raw(p++, 0x03e00008); /* jr ra */ + stl_raw(p++, 0x24020000); /* li v0,0 */ + /* 808 YAMON print */ + stl_raw(p++, 0x03e06821); /* move t5,ra */ + stl_raw(p++, 0x00805821); /* move t3,a0 */ + stl_raw(p++, 0x00a05021); /* move t2,a1 */ + stl_raw(p++, 0x91440000); /* lbu a0,0(t2) */ + stl_raw(p++, 0x254a0001); /* addiu t2,t2,1 */ + stl_raw(p++, 0x10800005); /* beqz a0,834 */ + stl_raw(p++, 0x00000000); /* nop */ + stl_raw(p++, 0x0ff0021c); /* jal 870 */ + stl_raw(p++, 0x00000000); /* nop */ + stl_raw(p++, 0x08000205); /* j 814 */ + stl_raw(p++, 0x00000000); /* nop */ + stl_raw(p++, 0x01a00008); /* jr t5 */ + stl_raw(p++, 0x01602021); /* move a0,t3 */ + /* 0x83c YAMON print_count */ + stl_raw(p++, 0x03e06821); /* move t5,ra */ + stl_raw(p++, 0x00805821); /* move t3,a0 */ + stl_raw(p++, 0x00a05021); /* move t2,a1 */ + stl_raw(p++, 0x00c06021); /* move t4,a2 */ + stl_raw(p++, 0x91440000); /* lbu a0,0(t2) */ + stl_raw(p++, 0x0ff0021c); /* jal 870 */ + stl_raw(p++, 0x00000000); /* nop */ + stl_raw(p++, 0x254a0001); /* addiu t2,t2,1 */ + stl_raw(p++, 0x258cffff); /* addiu t4,t4,-1 */ + stl_raw(p++, 0x1580fffa); /* bnez t4,84c */ + stl_raw(p++, 0x00000000); /* nop */ + stl_raw(p++, 0x01a00008); /* jr t5 */ + stl_raw(p++, 0x01602021); /* move a0,t3 */ + /* 0x870 */ + stl_raw(p++, 0x3c08b800); /* lui t0,0xb400 */ + stl_raw(p++, 0x350803f8); /* ori t0,t0,0x3f8 */ + stl_raw(p++, 0x91090005); /* lbu t1,5(t0) */ + stl_raw(p++, 0x00000000); /* nop */ + stl_raw(p++, 0x31290040); /* andi t1,t1,0x40 */ + stl_raw(p++, 0x1120fffc); /* beqz t1,878 */ + stl_raw(p++, 0x00000000); /* nop */ + stl_raw(p++, 0x03e00008); /* jr ra */ + stl_raw(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; + +#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); + } + + /* 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 + ~TARGET_PAGE_MASK) & TARGET_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 = qemu_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", + cpu_mips_phys_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++, "%i", 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)); + + return kernel_entry; +} + +static void main_cpu_reset(void *opaque) +{ + CPUState *env = opaque; + cpu_reset(env); + + /* 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_BEV) | (1 << CP0St_ERL)); + } +} + +static void cpu_request_exit(void *opaque, int irq, int level) +{ + CPUState *env = cpu_single_env; + + if (env && level) { + cpu_exit(env); + } +} + +static +void mips_malta_init (ram_addr_t ram_size, + const char *boot_device, + const char *kernel_filename, const char *kernel_cmdline, + const char *initrd_filename, const char *cpu_model) +{ + char *filename; + ram_addr_t ram_offset; + ram_addr_t bios_offset; + target_long bios_size; + int64_t kernel_entry; + PCIBus *pci_bus; + CPUState *env; + qemu_irq *i8259; + qemu_irq *cpu_exit_irq; + int piix4_devfn; + uint8_t *eeprom_buf; + i2c_bus *smbus; + int i; + DriveInfo *dinfo; + DriveInfo *hd[MAX_IDE_BUS * MAX_IDE_DEVS]; + DriveInfo *fd[MAX_FD]; + int fl_idx = 0; + int fl_sectors = 0; + int be; + + /* 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_open(label, "null", NULL); + } + } + + /* init CPUs */ + if (cpu_model == NULL) { +#ifdef TARGET_MIPS64 + cpu_model = "20Kc"; +#else + cpu_model = "24Kf"; +#endif + } + env = cpu_init(cpu_model); + if (!env) { + fprintf(stderr, "Unable to find CPU definition\n"); + exit(1); + } + qemu_register_reset(main_cpu_reset, env); + + /* 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); + } + ram_offset = qemu_ram_alloc(NULL, "mips_malta.ram", ram_size); + bios_offset = qemu_ram_alloc(NULL, "mips_malta.bios", BIOS_SIZE); + + + cpu_register_physical_memory(0, ram_size, ram_offset | IO_MEM_RAM); + + /* Map the bios at two physical locations, as on the real board. */ + cpu_register_physical_memory(0x1e000000LL, + BIOS_SIZE, bios_offset | IO_MEM_ROM); + cpu_register_physical_memory(0x1fc00000LL, + BIOS_SIZE, bios_offset | IO_MEM_ROM); + +#ifdef TARGET_WORDS_BIGENDIAN + be = 1; +#else + be = 0; +#endif + /* FPGA */ + malta_fpga_init(0x1f000000LL, env->irq[2], serial_hds[2]); + + /* Load firmware in flash / BIOS unless we boot directly into a kernel. */ + if (kernel_filename) { + /* Write a small bootloader to the flash location. */ + loaderparams.ram_size = ram_size; + loaderparams.kernel_filename = kernel_filename; + loaderparams.kernel_cmdline = kernel_cmdline; + loaderparams.initrd_filename = initrd_filename; + kernel_entry = load_kernel(); + write_bootloader(env, qemu_get_ram_ptr(bios_offset), kernel_entry); + } else { + dinfo = drive_get(IF_PFLASH, 0, fl_idx); + if (dinfo) { + /* Load firmware from flash. */ + bios_size = 0x400000; + fl_sectors = bios_size >> 16; +#ifdef DEBUG_BOARD_INIT + printf("Register parallel flash %d size " TARGET_FMT_lx " at " + "offset %08lx addr %08llx '%s' %x\n", + fl_idx, bios_size, bios_offset, 0x1e000000LL, + bdrv_get_device_name(dinfo->bdrv), fl_sectors); +#endif + pflash_cfi01_register(0x1e000000LL, bios_offset, + dinfo->bdrv, 65536, fl_sectors, + 4, 0x0000, 0x0000, 0x0000, 0x0000, be); + fl_idx++; + } else { + /* 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, 0x1fc00000LL, + BIOS_SIZE); + qemu_free(filename); + } else { + bios_size = -1; + } + if ((bios_size < 0 || bios_size > BIOS_SIZE) && !kernel_filename) { + fprintf(stderr, + "qemu: Could not load MIPS bios '%s', and no -kernel argument was specified\n", + 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 *addr = qemu_get_ram_ptr(bios_offset);; + uint32_t *end = addr + bios_size; + while (addr < end) { + bswap32s(addr); + } + } +#endif + } + + /* Board ID = 0x420 (Malta Board with CoreLV) + XXX: theoretically 0x1e000010 should map to flash and 0x1fc00010 should + map to the board ID. */ + stl_p(qemu_get_ram_ptr(bios_offset) + 0x10, 0x00000420); + + /* Init internal devices */ + cpu_mips_irq_init_cpu(env); + cpu_mips_clock_init(env); + + /* Interrupt controller */ + /* The 8259 is attached to the MIPS CPU INT0 pin, ie interrupt 2 */ + i8259 = i8259_init(env->irq[2]); + + /* Northbridge */ + pci_bus = gt64120_register(i8259); + + /* Southbridge */ + + if (drive_get_max_bus(IF_IDE) >= MAX_IDE_BUS) { + fprintf(stderr, "qemu: too many IDE bus\n"); + exit(1); + } + + for(i = 0; i < MAX_IDE_BUS * MAX_IDE_DEVS; i++) { + hd[i] = drive_get(IF_IDE, i / MAX_IDE_DEVS, i % MAX_IDE_DEVS); + } + + piix4_devfn = piix4_init(pci_bus, 80); + isa_bus_irqs(i8259); + pci_piix4_ide_init(pci_bus, hd, piix4_devfn + 1); + usb_uhci_piix4_init(pci_bus, piix4_devfn + 2); + smbus = piix4_pm_init(pci_bus, piix4_devfn + 3, 0x1100, isa_get_irq(9), + NULL, NULL, 0); + eeprom_buf = qemu_mallocz(8 * 256); /* XXX: make this persistent */ + for (i = 0; i < 8; i++) { + /* TODO: Populate SPD eeprom data. */ + 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); + } + pit = pit_init(0x40, isa_get_irq(0)); + cpu_exit_irq = qemu_allocate_irqs(cpu_request_exit, NULL, 1); + DMA_init(0, cpu_exit_irq); + + /* Super I/O */ + isa_create_simple("i8042"); + + rtc_init(2000, NULL); + serial_isa_init(0, serial_hds[0]); + serial_isa_init(1, serial_hds[1]); + if (parallel_hds[0]) + parallel_init(0, parallel_hds[0]); + for(i = 0; i < MAX_FD; i++) { + fd[i] = drive_get(IF_FLOPPY, 0, i); + } + fdctrl_init_isa(fd); + + /* Sound card */ + audio_init(NULL, pci_bus); + + /* Network card */ + network_init(); + + /* Optional PCI video card */ + if (cirrus_vga_enabled) { + pci_cirrus_vga_init(pci_bus); + } else if (vmsvga_enabled) { + pci_vmsvga_init(pci_bus); + } else if (std_vga_enabled) { + pci_vga_init(pci_bus); + } +} + +static QEMUMachine mips_malta_machine = { + .name = "malta", + .desc = "MIPS Malta Core LV", + .init = mips_malta_init, + .is_default = 1, +}; + +static void mips_malta_machine_init(void) +{ + qemu_register_machine(&mips_malta_machine); +} + +machine_init(mips_malta_machine_init); diff --git a/hw/mips_mipssim.c b/hw/mips_mipssim.c new file mode 100644 index 0000000..380a7eb --- /dev/null +++ b/hw/mips_mipssim.c @@ -0,0 +1,212 @@ +/* + * QEMU/mipssim emulation + * + * Emulates a very simple machine model similiar to the one use 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.h" +#include "mips.h" +#include "mips_cpudevs.h" +#include "pc.h" +#include "isa.h" +#include "net.h" +#include "sysemu.h" +#include "boards.h" +#include "mips-bios.h" +#include "loader.h" +#include "elf.h" + +static struct _loaderparams { + int ram_size; + const char *kernel_filename; + const char *kernel_cmdline; + const char *initrd_filename; +} loaderparams; + +typedef struct ResetData { + CPUState *env; + 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 + ~TARGET_PAGE_MASK) & TARGET_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; + CPUState *env = s->env; + + cpu_reset(env); + env->active_tc.PC = s->vector & ~(target_ulong)1; + if (s->vector & 1) { + env->hflags |= MIPS_HFLAG_M16; + } +} + +static void +mips_mipssim_init (ram_addr_t ram_size, + const char *boot_device, + const char *kernel_filename, const char *kernel_cmdline, + const char *initrd_filename, const char *cpu_model) +{ + char *filename; + ram_addr_t ram_offset; + ram_addr_t bios_offset; + CPUState *env; + ResetData *reset_info; + int bios_size; + + /* Init CPUs. */ + if (cpu_model == NULL) { +#ifdef TARGET_MIPS64 + cpu_model = "5Kf"; +#else + cpu_model = "24Kf"; +#endif + } + env = cpu_init(cpu_model); + if (!env) { + fprintf(stderr, "Unable to find CPU definition\n"); + exit(1); + } + reset_info = qemu_mallocz(sizeof(ResetData)); + reset_info->env = env; + reset_info->vector = env->active_tc.PC; + qemu_register_reset(main_cpu_reset, reset_info); + + /* Allocate RAM. */ + ram_offset = qemu_ram_alloc(NULL, "mips_mipssim.ram", ram_size); + bios_offset = qemu_ram_alloc(NULL, "mips_mipssim.bios", BIOS_SIZE); + + cpu_register_physical_memory(0, ram_size, ram_offset | IO_MEM_RAM); + + /* Map the BIOS / boot exception handler. */ + cpu_register_physical_memory(0x1fc00000LL, + BIOS_SIZE, bios_offset | IO_MEM_ROM); + /* 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); + qemu_free(filename); + } else { + bios_size = -1; + } + if ((bios_size < 0 || bios_size > BIOS_SIZE) && !kernel_filename) { + /* Bail out if we have neither a kernel image nor boot vector code. */ + fprintf(stderr, + "qemu: Could not load MIPS bios '%s', and no -kernel argument was specified\n", + filename); + 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. */ + isa_mmio_init(0x1fd00000, 0x00010000); + + /* 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]); + + if (nd_table[0].vlan) + /* 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_r4k.c b/hw/mips_r4k.c new file mode 100644 index 0000000..fb34dcf --- /dev/null +++ b/hw/mips_r4k.c @@ -0,0 +1,318 @@ +/* + * 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.h" +#include "mips.h" +#include "mips_cpudevs.h" +#include "pc.h" +#include "isa.h" +#include "net.h" +#include "sysemu.h" +#include "boards.h" +#include "flash.h" +#include "qemu-log.h" +#include "mips-bios.h" +#include "ide.h" +#include "loader.h" +#include "elf.h" +#include "mc146818rtc.h" +#include "blockdev.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 PITState *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_writel (void *opaque, target_phys_addr_t addr, + uint32_t val) +{ + if ((addr & 0xffff) == 0 && val == 42) + qemu_system_reset_request (); + else if ((addr & 0xffff) == 4 && val == 42) + qemu_system_shutdown_request (); +} + +static uint32_t mips_qemu_readl (void *opaque, target_phys_addr_t addr) +{ + return 0; +} + +static CPUWriteMemoryFunc * const mips_qemu_write[] = { + &mips_qemu_writel, + &mips_qemu_writel, + &mips_qemu_writel, +}; + +static CPUReadMemoryFunc * const mips_qemu_read[] = { + &mips_qemu_readl, + &mips_qemu_readl, + &mips_qemu_readl, +}; + +static int mips_qemu_iomemtype = 0; + +typedef struct ResetData { + CPUState *env; + 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 + ~TARGET_PAGE_MASK) & TARGET_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 = qemu_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); + + return entry; +} + +static void main_cpu_reset(void *opaque) +{ + ResetData *s = (ResetData *)opaque; + CPUState *env = s->env; + + cpu_reset(env); + env->active_tc.PC = s->vector; +} + +static const int sector_len = 32 * 1024; +static +void mips_r4k_init (ram_addr_t ram_size, + const char *boot_device, + const char *kernel_filename, const char *kernel_cmdline, + const char *initrd_filename, const char *cpu_model) +{ + char *filename; + ram_addr_t ram_offset; + ram_addr_t bios_offset; + int bios_size; + CPUState *env; + ResetData *reset_info; + int i; + qemu_irq *i8259; + 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 + } + env = cpu_init(cpu_model); + if (!env) { + fprintf(stderr, "Unable to find CPU definition\n"); + exit(1); + } + reset_info = qemu_mallocz(sizeof(ResetData)); + reset_info->env = env; + 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); + } + ram_offset = qemu_ram_alloc(NULL, "mips_r4k.ram", ram_size); + + cpu_register_physical_memory(0, ram_size, ram_offset | IO_MEM_RAM); + + if (!mips_qemu_iomemtype) { + mips_qemu_iomemtype = cpu_register_io_memory(mips_qemu_read, + mips_qemu_write, NULL, + DEVICE_NATIVE_ENDIAN); + } + cpu_register_physical_memory(0x1fbf0000, 0x10000, mips_qemu_iomemtype); + + /* 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_offset = qemu_ram_alloc(NULL, "mips_r4k.bios", BIOS_SIZE); + cpu_register_physical_memory(0x1fc00000, BIOS_SIZE, + bios_offset | IO_MEM_ROM); + + load_image_targphys(filename, 0x1fc00000, BIOS_SIZE); + } else if ((dinfo = drive_get(IF_PFLASH, 0, 0)) != NULL) { + uint32_t mips_rom = 0x00400000; + bios_offset = qemu_ram_alloc(NULL, "mips_r4k.bios", mips_rom); + if (!pflash_cfi01_register(0x1fc00000, bios_offset, + dinfo->bdrv, sector_len, + mips_rom / sector_len, + 4, 0, 0, 0, 0, be)) { + fprintf(stderr, "qemu: Error registering flash memory.\n"); + } + } + else { + /* not fatal */ + fprintf(stderr, "qemu: Warning, could not load MIPS bios '%s'\n", + bios_name); + } + if (filename) { + qemu_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); + + /* The PIC is attached to the MIPS CPU INT0 pin */ + i8259 = i8259_init(env->irq[2]); + isa_bus_new(NULL); + isa_bus_irqs(i8259); + + rtc_init(2000, NULL); + + /* Register 64 KB of ISA IO space at 0x14000000 */ + isa_mmio_init(0x14000000, 0x00010000); + isa_mem_base = 0x10000000; + + pit = pit_init(0x40, i8259[0]); + + for(i = 0; i < MAX_SERIAL_PORTS; i++) { + if (serial_hds[i]) { + serial_isa_init(i, serial_hds[i]); + } + } + + isa_vga_init(); + + if (nd_table[0].vlan) + isa_ne2000_init(0x300, 9, &nd_table[0]); + + if (drive_get_max_bus(IF_IDE) >= MAX_IDE_BUS) { + fprintf(stderr, "qemu: too many IDE bus\n"); + exit(1); + } + + for(i = 0; i < MAX_IDE_BUS * MAX_IDE_DEVS; i++) { + hd[i] = drive_get(IF_IDE, i / MAX_IDE_DEVS, i % MAX_IDE_DEVS); + } + + for(i = 0; i < MAX_IDE_BUS; i++) + isa_ide_init(ide_iobase[i], ide_iobase2[i], ide_irq[i], + hd[MAX_IDE_DEVS * i], + hd[MAX_IDE_DEVS * i + 1]); + + isa_create_simple("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/mips_timer.c b/hw/mips_timer.c new file mode 100644 index 0000000..9c95f28 --- /dev/null +++ b/hw/mips_timer.c @@ -0,0 +1,147 @@ +/* + * 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.h" +#include "mips_cpudevs.h" +#include "qemu-timer.h" + +#define TIMER_FREQ 100 * 1000 * 1000 + +/* XXX: do not use a global */ +uint32_t cpu_mips_get_random (CPUState *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(CPUState *env) +{ + uint64_t now, next; + uint32_t wait; + + now = qemu_get_clock(vm_clock); + 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); + qemu_mod_timer(env->timer, next); +} + +/* Expire the timer. */ +static void cpu_mips_timer_expire(CPUState *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 (CPUState *env) +{ + if (env->CP0_Cause & (1 << CP0Ca_DC)) { + return env->CP0_Count; + } else { + uint64_t now; + + now = qemu_get_clock(vm_clock); + if (qemu_timer_pending(env->timer) + && qemu_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 (CPUState *env, uint32_t count) +{ + if (env->CP0_Cause & (1 << CP0Ca_DC)) + env->CP0_Count = count; + else { + /* Store new count register */ + env->CP0_Count = + count - (uint32_t)muldiv64(qemu_get_clock(vm_clock), + TIMER_FREQ, get_ticks_per_sec()); + /* Update timer timer */ + cpu_mips_timer_update(env); + } +} + +void cpu_mips_store_compare (CPUState *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(CPUState *env) +{ + cpu_mips_store_count(env, env->CP0_Count); +} + +void cpu_mips_stop_count(CPUState *env) +{ + /* Store the current value */ + env->CP0_Count += (uint32_t)muldiv64(qemu_get_clock(vm_clock), + TIMER_FREQ, get_ticks_per_sec()); +} + +static void mips_timer_cb (void *opaque) +{ + CPUState *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 (CPUState *env) +{ + env->timer = qemu_new_timer(vm_clock, &mips_timer_cb, env); + env->CP0_Compare = 0; + cpu_mips_store_count(env, 1); +} diff --git a/hw/mipsnet.c b/hw/mipsnet.c new file mode 100644 index 0000000..c5e54ff --- /dev/null +++ b/hw/mipsnet.c @@ -0,0 +1,288 @@ +#include "hw.h" +#include "mips.h" +#include "net.h" +#include "isa.h" + +//#define DEBUG_MIPSNET_SEND +//#define DEBUG_MIPSNET_RECEIVE +//#define DEBUG_MIPSNET_DATA +//#define DEBUG_MIPSNET_IRQ + +/* 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 + +typedef struct MIPSnetState { + 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]; + int io_base; + 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; +#ifdef DEBUG_MIPSNET_IRQ + printf("mipsnet: Set IRQ to %d (%02x)\n", isr, s->intctl); +#endif + 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(VLANClientState *nc) +{ + MIPSnetState *s = DO_UPCAST(NICState, nc, nc)->opaque; + + if (s->busy) + return 0; + return !mipsnet_buffer_full(s); +} + +static ssize_t mipsnet_receive(VLANClientState *nc, const uint8_t *buf, size_t size) +{ + MIPSnetState *s = DO_UPCAST(NICState, nc, nc)->opaque; + +#ifdef DEBUG_MIPSNET_RECEIVE + printf("mipsnet: receiving len=%zu\n", size); +#endif + if (!mipsnet_can_receive(nc)) + return -1; + + 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 uint32_t mipsnet_ioport_read(void *opaque, uint32_t addr) +{ + 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++]; + } + break; + /* Reads as zero. */ + case MIPSNET_TX_DATA_BUFFER: + default: + break; + } +#ifdef DEBUG_MIPSNET_DATA + printf("mipsnet: read addr=0x%02x val=0x%02x\n", addr, ret); +#endif + return ret; +} + +static void mipsnet_ioport_write(void *opaque, uint32_t addr, uint32_t val) +{ + MIPSnetState *s = opaque; + + addr &= 0x3f; +#ifdef DEBUG_MIPSNET_DATA + printf("mipsnet: write addr=0x%02x val=0x%02x\n", addr, val); +#endif + 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); + break; + case MIPSNET_TX_DATA_BUFFER: + s->tx_buffer[s->tx_written++] = val; + if (s->tx_written == s->tx_count) { + /* Send buffer. */ +#ifdef DEBUG_MIPSNET_SEND + printf("mipsnet: sending len=%d\n", s->tx_count); +#endif + qemu_send_packet(&s->nic->nc, 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 void mipsnet_save(QEMUFile *f, void *opaque) +{ + MIPSnetState *s = opaque; + + qemu_put_be32s(f, &s->busy); + qemu_put_be32s(f, &s->rx_count); + qemu_put_be32s(f, &s->rx_read); + qemu_put_be32s(f, &s->tx_count); + qemu_put_be32s(f, &s->tx_written); + qemu_put_be32s(f, &s->intctl); + qemu_put_buffer(f, s->rx_buffer, MAX_ETH_FRAME_SIZE); + qemu_put_buffer(f, s->tx_buffer, MAX_ETH_FRAME_SIZE); +} + +static int mipsnet_load(QEMUFile *f, void *opaque, int version_id) +{ + MIPSnetState *s = opaque; + + if (version_id > 0) + return -EINVAL; + + qemu_get_be32s(f, &s->busy); + qemu_get_be32s(f, &s->rx_count); + qemu_get_be32s(f, &s->rx_read); + qemu_get_be32s(f, &s->tx_count); + qemu_get_be32s(f, &s->tx_written); + qemu_get_be32s(f, &s->intctl); + qemu_get_buffer(f, s->rx_buffer, MAX_ETH_FRAME_SIZE); + qemu_get_buffer(f, s->tx_buffer, MAX_ETH_FRAME_SIZE); + + return 0; +} + +static void mipsnet_cleanup(VLANClientState *nc) +{ + MIPSnetState *s = DO_UPCAST(NICState, nc, nc)->opaque; + + unregister_savevm(NULL, "mipsnet", s); + + isa_unassign_ioport(s->io_base, 36); + + qemu_free(s); +} + +static NetClientInfo net_mipsnet_info = { + .type = NET_CLIENT_TYPE_NIC, + .size = sizeof(NICState), + .can_receive = mipsnet_can_receive, + .receive = mipsnet_receive, + .cleanup = mipsnet_cleanup, +}; + +void mipsnet_init (int base, qemu_irq irq, NICInfo *nd) +{ + MIPSnetState *s; + + qemu_check_nic_model(nd, "mipsnet"); + + s = qemu_mallocz(sizeof(MIPSnetState)); + + register_ioport_write(base, 36, 1, mipsnet_ioport_write, s); + register_ioport_read(base, 36, 1, mipsnet_ioport_read, s); + register_ioport_write(base, 36, 2, mipsnet_ioport_write, s); + register_ioport_read(base, 36, 2, mipsnet_ioport_read, s); + register_ioport_write(base, 36, 4, mipsnet_ioport_write, s); + register_ioport_read(base, 36, 4, mipsnet_ioport_read, s); + + s->io_base = base; + s->irq = irq; + + if (nd) { + memcpy(s->conf.macaddr.a, nd->macaddr, sizeof(nd->macaddr)); + s->conf.vlan = nd->vlan; + s->conf.peer = nd->netdev; + + s->nic = qemu_new_nic(&net_mipsnet_info, &s->conf, + nd->model, nd->name, s); + + qemu_format_nic_info_str(&s->nic->nc, s->conf.macaddr.a); + } + + mipsnet_reset(s); + register_savevm(NULL, "mipsnet", 0, 0, mipsnet_save, mipsnet_load, s); +} diff --git a/hw/mpcore.c b/hw/mpcore.c new file mode 100644 index 0000000..fc05215 --- /dev/null +++ b/hw/mpcore.c @@ -0,0 +1,286 @@ +/* + * ARM MPCore internal peripheral emulation (common code). + * + * Copyright (c) 2006-2007 CodeSourcery. + * Written by Paul Brook + * + * This code is licenced under the GPL. + */ + +#include "sysbus.h" +#include "qemu-timer.h" + +#define NCPU 4 + +static inline int +gic_get_current_cpu(void) +{ + return cpu_single_env->cpu_index; +} + +#include "arm_gic.c" + +/* MPCore private memory region. */ + +typedef struct { + uint32_t count; + uint32_t load; + uint32_t control; + uint32_t status; + uint32_t old_status; + int64_t tick; + QEMUTimer *timer; + struct mpcore_priv_state *mpcore; + int id; /* Encodes both timer/watchdog and CPU. */ +} mpcore_timer_state; + +typedef struct mpcore_priv_state { + gic_state gic; + uint32_t scu_control; + int iomemtype; + mpcore_timer_state timer[8]; + uint32_t num_cpu; +} mpcore_priv_state; + +/* Per-CPU Timers. */ + +static inline void mpcore_timer_update_irq(mpcore_timer_state *s) +{ + if (s->status & ~s->old_status) { + gic_set_pending_private(&s->mpcore->gic, s->id >> 1, 29 + (s->id & 1)); + } + s->old_status = s->status; +} + +/* Return conversion factor from mpcore timer ticks to qemu timer ticks. */ +static inline uint32_t mpcore_timer_scale(mpcore_timer_state *s) +{ + return (((s->control >> 8) & 0xff) + 1) * 10; +} + +static void mpcore_timer_reload(mpcore_timer_state *s, int restart) +{ + if (s->count == 0) + return; + if (restart) + s->tick = qemu_get_clock(vm_clock); + s->tick += (int64_t)s->count * mpcore_timer_scale(s); + qemu_mod_timer(s->timer, s->tick); +} + +static void mpcore_timer_tick(void *opaque) +{ + mpcore_timer_state *s = (mpcore_timer_state *)opaque; + s->status = 1; + if (s->control & 2) { + s->count = s->load; + mpcore_timer_reload(s, 0); + } else { + s->count = 0; + } + mpcore_timer_update_irq(s); +} + +static uint32_t mpcore_timer_read(mpcore_timer_state *s, int offset) +{ + int64_t val; + switch (offset) { + case 0: /* Load */ + return s->load; + /* Fall through. */ + case 4: /* Counter. */ + if (((s->control & 1) == 0) || (s->count == 0)) + return 0; + /* Slow and ugly, but hopefully won't happen too often. */ + val = s->tick - qemu_get_clock(vm_clock); + val /= mpcore_timer_scale(s); + if (val < 0) + val = 0; + return val; + case 8: /* Control. */ + return s->control; + case 12: /* Interrupt status. */ + return s->status; + default: + return 0; + } +} + +static void mpcore_timer_write(mpcore_timer_state *s, int offset, + uint32_t value) +{ + int64_t old; + switch (offset) { + case 0: /* Load */ + s->load = value; + /* Fall through. */ + case 4: /* Counter. */ + if ((s->control & 1) && s->count) { + /* Cancel the previous timer. */ + qemu_del_timer(s->timer); + } + s->count = value; + if (s->control & 1) { + mpcore_timer_reload(s, 1); + } + break; + case 8: /* Control. */ + old = s->control; + s->control = value; + if (((old & 1) == 0) && (value & 1)) { + if (s->count == 0 && (s->control & 2)) + s->count = s->load; + mpcore_timer_reload(s, 1); + } + break; + case 12: /* Interrupt status. */ + s->status &= ~value; + mpcore_timer_update_irq(s); + break; + } +} + +static void mpcore_timer_init(mpcore_priv_state *mpcore, + mpcore_timer_state *s, int id) +{ + s->id = id; + s->mpcore = mpcore; + s->timer = qemu_new_timer(vm_clock, mpcore_timer_tick, s); +} + + +/* Per-CPU private memory mapped IO. */ + +static uint32_t mpcore_priv_read(void *opaque, target_phys_addr_t offset) +{ + mpcore_priv_state *s = (mpcore_priv_state *)opaque; + int id; + offset &= 0xfff; + if (offset < 0x100) { + /* SCU */ + switch (offset) { + case 0x00: /* Control. */ + return s->scu_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: + goto bad_reg; + } + } else if (offset < 0x600) { + /* Interrupt controller. */ + if (offset < 0x200) { + id = gic_get_current_cpu(); + } else { + id = (offset - 0x200) >> 8; + if (id >= s->num_cpu) { + return 0; + } + } + return gic_cpu_read(&s->gic, id, offset & 0xff); + } else if (offset < 0xb00) { + /* Timers. */ + if (offset < 0x700) { + id = gic_get_current_cpu(); + } else { + id = (offset - 0x700) >> 8; + if (id >= s->num_cpu) { + return 0; + } + } + id <<= 1; + if (offset & 0x20) + id++; + return mpcore_timer_read(&s->timer[id], offset & 0xf); + } +bad_reg: + hw_error("mpcore_priv_read: Bad offset %x\n", (int)offset); + return 0; +} + +static void mpcore_priv_write(void *opaque, target_phys_addr_t offset, + uint32_t value) +{ + mpcore_priv_state *s = (mpcore_priv_state *)opaque; + int id; + offset &= 0xfff; + if (offset < 0x100) { + /* SCU */ + switch (offset) { + case 0: /* Control register. */ + s->scu_control = value & 1; + break; + case 0x0c: /* Invalidate all. */ + /* This is a no-op as cache is not emulated. */ + break; + default: + goto bad_reg; + } + } else if (offset < 0x600) { + /* Interrupt controller. */ + if (offset < 0x200) { + id = gic_get_current_cpu(); + } else { + id = (offset - 0x200) >> 8; + } + if (id < s->num_cpu) { + gic_cpu_write(&s->gic, id, offset & 0xff, value); + } + } else if (offset < 0xb00) { + /* Timers. */ + if (offset < 0x700) { + id = gic_get_current_cpu(); + } else { + id = (offset - 0x700) >> 8; + } + if (id < s->num_cpu) { + id <<= 1; + if (offset & 0x20) + id++; + mpcore_timer_write(&s->timer[id], offset & 0xf, value); + } + return; + } + return; +bad_reg: + hw_error("mpcore_priv_read: Bad offset %x\n", (int)offset); +} + +static CPUReadMemoryFunc * const mpcore_priv_readfn[] = { + mpcore_priv_read, + mpcore_priv_read, + mpcore_priv_read +}; + +static CPUWriteMemoryFunc * const mpcore_priv_writefn[] = { + mpcore_priv_write, + mpcore_priv_write, + mpcore_priv_write +}; + +static void mpcore_priv_map(SysBusDevice *dev, target_phys_addr_t base) +{ + mpcore_priv_state *s = FROM_SYSBUSGIC(mpcore_priv_state, dev); + cpu_register_physical_memory(base, 0x1000, s->iomemtype); + cpu_register_physical_memory(base + 0x1000, 0x1000, s->gic.iomemtype); +} + +static int mpcore_priv_init(SysBusDevice *dev) +{ + mpcore_priv_state *s = FROM_SYSBUSGIC(mpcore_priv_state, dev); + int i; + + gic_init(&s->gic, s->num_cpu); + s->iomemtype = cpu_register_io_memory(mpcore_priv_readfn, + mpcore_priv_writefn, s, + DEVICE_NATIVE_ENDIAN); + sysbus_init_mmio_cb(dev, 0x2000, mpcore_priv_map); + for (i = 0; i < s->num_cpu * 2; i++) { + mpcore_timer_init(s, &s->timer[i], i); + } + return 0; +} diff --git a/hw/msi.c b/hw/msi.c new file mode 100644 index 0000000..3dc3a24 --- /dev/null +++ b/hw/msi.c @@ -0,0 +1,344 @@ +/* + * msi.c + * + * Copyright (c) 2010 Isaku Yamahata + * 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 . + */ + +#include "msi.h" +#include "range.h" + +/* Eventually those constants should go to Linux pci_regs.h */ +#define PCI_MSI_PENDING_32 0x10 +#define PCI_MSI_PENDING_64 0x14 + +/* 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 + +/* 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) >> (ffs(PCI_MSI_FLAGS_QSIZE) - 1)); +} + +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); +} + +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; + 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 = ffs(nr_vectors) - 1; + + flags = vectors_order << (ffs(PCI_MSI_FLAGS_QMASK) - 1); + 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 writeable. */ + 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 = pci_get_word(dev->config + msi_flags_off(dev)); + uint8_t cap_size = msi_cap_sizeof(flags); + pci_del_capability(dev, PCI_CAP_ID_MSIX, cap_size); + MSI_DEV_PRINTF(dev, "uninit\n"); +} + +void msi_reset(PCIDevice *dev) +{ + uint16_t flags; + bool msi64bit; + + 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); + uint64_t address; + uint32_t data; + + 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; + } + + if (msi64bit) { + address = pci_get_quad(dev->config + msi_address_lo_off(dev)); + } else { + address = pci_get_long(dev->config + msi_address_lo_off(dev)); + } + + /* upper bit 31:16 is zero */ + data = pci_get_word(dev->config + msi_data_off(dev, msi64bit)); + if (nr_vectors > 1) { + data &= ~(nr_vectors - 1); + data |= vector; + } + + MSI_DEV_PRINTF(dev, + "notify vector 0x%x" + " address: 0x%"PRIx64" data: 0x%"PRIx32"\n", + vector, address, data); + stl_phys(address, data); +} + +/* call this function after updating configs 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 (!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) >> (ffs(PCI_MSI_FLAGS_QSIZE) - 1); + log_max_vecs = + (flags & PCI_MSI_FLAGS_QMASK) >> (ffs(PCI_MSI_FLAGS_QMASK) - 1); + if (log_num_vecs > log_max_vecs) { + flags &= ~PCI_MSI_FLAGS_QSIZE; + flags |= log_max_vecs << (ffs(PCI_MSI_FLAGS_QSIZE) - 1); + 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/msi.h b/hw/msi.h new file mode 100644 index 0000000..5766018 --- /dev/null +++ b/hw/msi.h @@ -0,0 +1,41 @@ +/* + * msi.h + * + * Copyright (c) 2010 Isaku Yamahata + * 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 . + */ + +#ifndef QEMU_MSI_H +#define QEMU_MSI_H + +#include "qemu-common.h" +#include "pci.h" + +bool msi_enabled(const PCIDevice *dev); +int msi_init(struct PCIDevice *dev, uint8_t offset, + unsigned int nr_vectors, bool msi64bit, bool msi_per_vector_mask); +void msi_uninit(struct PCIDevice *dev); +void msi_reset(PCIDevice *dev); +void msi_notify(PCIDevice *dev, unsigned int vector); +void msi_write_config(PCIDevice *dev, uint32_t addr, uint32_t val, int len); +unsigned int msi_nr_vectors_allocated(const PCIDevice *dev); + +static inline bool msi_present(const PCIDevice *dev) +{ + return dev->cap_present & QEMU_PCI_CAP_MSI; +} + +#endif /* QEMU_MSI_H */ diff --git a/hw/msix.c b/hw/msix.c new file mode 100644 index 0000000..daaf9b7 --- /dev/null +++ b/hw/msix.c @@ -0,0 +1,420 @@ +/* + * MSI-X device support + * + * This module includes support for MSI-X in pci devices. + * + * Author: Michael S. Tsirkin + * + * 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. + */ + +#include "hw.h" +#include "msix.h" +#include "pci.h" +#include "range.h" + +/* MSI-X capability structure */ +#define MSIX_TABLE_OFFSET 4 +#define MSIX_PBA_OFFSET 8 +#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) + +/* MSI-X table format */ +#define MSIX_MSG_ADDR 0 +#define MSIX_MSG_UPPER_ADDR 4 +#define MSIX_MSG_DATA 8 +#define MSIX_VECTOR_CTRL 12 +#define MSIX_ENTRY_SIZE 16 +#define MSIX_VECTOR_MASK 0x1 + +/* How much space does an MSIX table need. */ +/* The spec requires giving the table structure + * a 4K aligned region all by itself. */ +#define MSIX_PAGE_SIZE 0x1000 +/* Reserve second half of the page for pending bits */ +#define MSIX_PAGE_PENDING (MSIX_PAGE_SIZE / 2) +#define MSIX_MAX_ENTRIES 32 + + +/* Flag for interrupt controller to declare MSI-X support */ +int msix_supported; + +/* Add MSI-X capability to the config space for the device. */ +/* Given a bar and its size, add MSI-X table on top of it + * and fill MSI-X capability in the config space. + * Original bar size must be a power of 2 or 0. + * New bar size is returned. */ +static int msix_add_config(struct PCIDevice *pdev, unsigned short nentries, + unsigned bar_nr, unsigned bar_size) +{ + int config_offset; + uint8_t *config; + uint32_t new_size; + + if (nentries < 1 || nentries > PCI_MSIX_FLAGS_QSIZE + 1) + return -EINVAL; + if (bar_size > 0x80000000) + return -ENOSPC; + + /* Add space for MSI-X structures */ + if (!bar_size) { + new_size = MSIX_PAGE_SIZE; + } else if (bar_size < MSIX_PAGE_SIZE) { + bar_size = MSIX_PAGE_SIZE; + new_size = MSIX_PAGE_SIZE * 2; + } else { + new_size = bar_size * 2; + } + + pdev->msix_bar_size = new_size; + config_offset = pci_add_capability(pdev, PCI_CAP_ID_MSIX, + 0, MSIX_CAP_LENGTH); + if (config_offset < 0) + return config_offset; + config = pdev->config + config_offset; + + pci_set_word(config + PCI_MSIX_FLAGS, nentries - 1); + /* Table on top of BAR */ + pci_set_long(config + MSIX_TABLE_OFFSET, bar_size | bar_nr); + /* Pending bits on top of that */ + pci_set_long(config + MSIX_PBA_OFFSET, (bar_size + MSIX_PAGE_PENDING) | + bar_nr); + pdev->msix_cap = config_offset; + /* Make flags bit writeable. */ + pdev->wmask[config_offset + MSIX_CONTROL_OFFSET] |= MSIX_ENABLE_MASK | + MSIX_MASKALL_MASK; + return 0; +} + +static uint32_t msix_mmio_readl(void *opaque, target_phys_addr_t addr) +{ + PCIDevice *dev = opaque; + unsigned int offset = addr & (MSIX_PAGE_SIZE - 1) & ~0x3; + void *page = dev->msix_table_page; + + return pci_get_long(page + offset); +} + +static uint32_t msix_mmio_read_unallowed(void *opaque, target_phys_addr_t addr) +{ + fprintf(stderr, "MSI-X: only dword read is allowed!\n"); + return 0; +} + +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_table_page + MSIX_PAGE_PENDING + vector / 8; +} + +static int msix_is_pending(PCIDevice *dev, int vector) +{ + return *msix_pending_byte(dev, vector) & msix_pending_mask(vector); +} + +static void msix_set_pending(PCIDevice *dev, 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 int msix_function_masked(PCIDevice *dev) +{ + return dev->config[dev->msix_cap + MSIX_CONTROL_OFFSET] & MSIX_MASKALL_MASK; +} + +static int msix_is_masked(PCIDevice *dev, int vector) +{ + unsigned offset = vector * MSIX_ENTRY_SIZE + MSIX_VECTOR_CTRL; + return msix_function_masked(dev) || + dev->msix_table_page[offset] & MSIX_VECTOR_MASK; +} + +static void msix_handle_mask_update(PCIDevice *dev, int vector) +{ + if (!msix_is_masked(dev, vector) && msix_is_pending(dev, vector)) { + msix_clr_pending(dev, vector); + msix_notify(dev, vector); + } +} + +/* 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; + + if (!range_covers_byte(addr, len, enable_pos)) { + return; + } + + if (!msix_enabled(dev)) { + return; + } + + pci_device_deassert_intx(dev); + + if (msix_function_masked(dev)) { + return; + } + + for (vector = 0; vector < dev->msix_entries_nr; ++vector) { + msix_handle_mask_update(dev, vector); + } +} + +static void msix_mmio_writel(void *opaque, target_phys_addr_t addr, + uint32_t val) +{ + PCIDevice *dev = opaque; + unsigned int offset = addr & (MSIX_PAGE_SIZE - 1) & ~0x3; + int vector = offset / MSIX_ENTRY_SIZE; + pci_set_long(dev->msix_table_page + offset, val); + msix_handle_mask_update(dev, vector); +} + +static void msix_mmio_write_unallowed(void *opaque, target_phys_addr_t addr, + uint32_t val) +{ + fprintf(stderr, "MSI-X: only dword write is allowed!\n"); +} + +static CPUWriteMemoryFunc * const msix_mmio_write[] = { + msix_mmio_write_unallowed, msix_mmio_write_unallowed, msix_mmio_writel +}; + +static CPUReadMemoryFunc * const msix_mmio_read[] = { + msix_mmio_read_unallowed, msix_mmio_read_unallowed, msix_mmio_readl +}; + +/* Should be called from device's map method. */ +void msix_mmio_map(PCIDevice *d, int region_num, + pcibus_t addr, pcibus_t size, int type) +{ + uint8_t *config = d->config + d->msix_cap; + uint32_t table = pci_get_long(config + MSIX_TABLE_OFFSET); + uint32_t offset = table & ~(MSIX_PAGE_SIZE - 1); + /* TODO: for assigned devices, we'll want to make it possible to map + * pending bits separately in case they are in a separate bar. */ + int table_bir = table & PCI_MSIX_FLAGS_BIRMASK; + + if (table_bir != region_num) + return; + if (size <= offset) + return; + cpu_register_physical_memory(addr + offset, size - offset, + d->msix_mmio_index); +} + +static void msix_mask_all(struct PCIDevice *dev, unsigned nentries) +{ + int vector; + for (vector = 0; vector < nentries; ++vector) { + unsigned offset = vector * MSIX_ENTRY_SIZE + MSIX_VECTOR_CTRL; + dev->msix_table_page[offset] |= MSIX_VECTOR_MASK; + } +} + +/* Initialize the MSI-X structures. Note: if MSI-X is supported, BAR size is + * modified, it should be retrieved with msix_bar_size. */ +int msix_init(struct PCIDevice *dev, unsigned short nentries, + unsigned bar_nr, unsigned bar_size) +{ + int ret; + /* Nothing to do if MSI is not supported by interrupt controller */ + if (!msix_supported) + return -ENOTSUP; + + if (nentries > MSIX_MAX_ENTRIES) + return -EINVAL; + + dev->msix_entry_used = qemu_mallocz(MSIX_MAX_ENTRIES * + sizeof *dev->msix_entry_used); + + dev->msix_table_page = qemu_mallocz(MSIX_PAGE_SIZE); + msix_mask_all(dev, nentries); + + dev->msix_mmio_index = cpu_register_io_memory(msix_mmio_read, + msix_mmio_write, dev, + DEVICE_NATIVE_ENDIAN); + if (dev->msix_mmio_index == -1) { + ret = -EBUSY; + goto err_index; + } + + dev->msix_entries_nr = nentries; + ret = msix_add_config(dev, nentries, bar_nr, bar_size); + if (ret) + goto err_config; + + dev->cap_present |= QEMU_PCI_CAP_MSIX; + return 0; + +err_config: + dev->msix_entries_nr = 0; + cpu_unregister_io_memory(dev->msix_mmio_index); +err_index: + qemu_free(dev->msix_table_page); + dev->msix_table_page = NULL; + qemu_free(dev->msix_entry_used); + dev->msix_entry_used = NULL; + return ret; +} + +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); + } +} + +/* Clean up resources for the device. */ +int msix_uninit(PCIDevice *dev) +{ + if (!(dev->cap_present & QEMU_PCI_CAP_MSIX)) + return 0; + 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; + cpu_unregister_io_memory(dev->msix_mmio_index); + qemu_free(dev->msix_table_page); + dev->msix_table_page = NULL; + qemu_free(dev->msix_entry_used); + dev->msix_entry_used = NULL; + dev->cap_present &= ~QEMU_PCI_CAP_MSIX; + return 0; +} + +void msix_save(PCIDevice *dev, QEMUFile *f) +{ + unsigned n = dev->msix_entries_nr; + + if (!(dev->cap_present & QEMU_PCI_CAP_MSIX)) { + return; + } + + qemu_put_buffer(f, dev->msix_table_page, n * MSIX_ENTRY_SIZE); + qemu_put_buffer(f, dev->msix_table_page + MSIX_PAGE_PENDING, (n + 7) / 8); +} + +/* Should be called after restoring the config space. */ +void msix_load(PCIDevice *dev, QEMUFile *f) +{ + unsigned n = dev->msix_entries_nr; + + if (!(dev->cap_present & QEMU_PCI_CAP_MSIX)) { + return; + } + + msix_free_irq_entries(dev); + qemu_get_buffer(f, dev->msix_table_page, n * MSIX_ENTRY_SIZE); + qemu_get_buffer(f, dev->msix_table_page + MSIX_PAGE_PENDING, (n + 7) / 8); +} + +/* 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); +} + +/* Size of bar where MSI-X table resides, or 0 if MSI-X not supported. */ +uint32_t msix_bar_size(PCIDevice *dev) +{ + return (dev->cap_present & QEMU_PCI_CAP_MSIX) ? + dev->msix_bar_size : 0; +} + +/* Send an MSI-X message */ +void msix_notify(PCIDevice *dev, unsigned vector) +{ + uint8_t *table_entry = dev->msix_table_page + vector * MSIX_ENTRY_SIZE; + uint64_t address; + uint32_t data; + + if (vector >= dev->msix_entries_nr || !dev->msix_entry_used[vector]) + return; + if (msix_is_masked(dev, vector)) { + msix_set_pending(dev, vector); + return; + } + + address = pci_get_long(table_entry + MSIX_MSG_UPPER_ADDR); + address = (address << 32) | pci_get_long(table_entry + MSIX_MSG_ADDR); + data = pci_get_long(table_entry + MSIX_MSG_DATA); + stl_phys(address, data); +} + +void msix_reset(PCIDevice *dev) +{ + if (!(dev->cap_present & QEMU_PCI_CAP_MSIX)) + return; + msix_free_irq_entries(dev); + dev->config[dev->msix_cap + MSIX_CONTROL_OFFSET] &= + ~dev->wmask[dev->msix_cap + MSIX_CONTROL_OFFSET]; + memset(dev->msix_table_page, 0, MSIX_PAGE_SIZE); + 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 (!(dev->cap_present & QEMU_PCI_CAP_MSIX)) + return; + msix_free_irq_entries(dev); +} diff --git a/hw/msix.h b/hw/msix.h new file mode 100644 index 0000000..a9f7993 --- /dev/null +++ b/hw/msix.h @@ -0,0 +1,36 @@ +#ifndef QEMU_MSIX_H +#define QEMU_MSIX_H + +#include "qemu-common.h" +#include "pci.h" + +int msix_init(PCIDevice *pdev, unsigned short nentries, + unsigned bar_nr, unsigned bar_size); + +void msix_write_config(PCIDevice *pci_dev, uint32_t address, + uint32_t val, int len); + +void msix_mmio_map(PCIDevice *pci_dev, int region_num, + pcibus_t addr, pcibus_t size, int type); + +int msix_uninit(PCIDevice *d); + +void msix_save(PCIDevice *dev, QEMUFile *f); +void msix_load(PCIDevice *dev, QEMUFile *f); + +int msix_enabled(PCIDevice *dev); +int msix_present(PCIDevice *dev); + +uint32_t msix_bar_size(PCIDevice *dev); + +int msix_vector_use(PCIDevice *dev, unsigned vector); +void msix_vector_unuse(PCIDevice *dev, unsigned vector); +void msix_unuse_all_vectors(PCIDevice *dev); + +void msix_notify(PCIDevice *dev, unsigned vector); + +void msix_reset(PCIDevice *dev); + +extern int msix_supported; + +#endif diff --git a/hw/msmouse.c b/hw/msmouse.c new file mode 100644 index 0000000..05f893c --- /dev/null +++ b/hw/msmouse.c @@ -0,0 +1,78 @@ +/* + * QEMU Microsoft serial mouse emulation + * + * Copyright (c) 2008 Lubomir Rintel + * + * 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 +#include "../qemu-common.h" +#include "../qemu-char.h" +#include "../console.h" +#include "msmouse.h" + +#define MSMOUSE_LO6(n) ((n) & 0x3f) +#define MSMOUSE_HI2(n) (((n) & 0xc0) >> 6) + +static void msmouse_event(void *opaque, + int dx, int dy, int dz, int buttons_state) +{ + CharDriverState *chr = (CharDriverState *)opaque; + + unsigned char bytes[4] = { 0x40, 0x00, 0x00, 0x00 }; + + /* Movement deltas */ + bytes[0] |= (MSMOUSE_HI2(dy) << 2) | MSMOUSE_HI2(dx); + bytes[1] |= MSMOUSE_LO6(dx); + bytes[2] |= MSMOUSE_LO6(dy); + + /* Buttons */ + bytes[0] |= (buttons_state & 0x01 ? 0x20 : 0x00); + bytes[0] |= (buttons_state & 0x02 ? 0x10 : 0x00); + bytes[3] |= (buttons_state & 0x04 ? 0x20 : 0x00); + + /* We always send the packet of, so that we do not have to keep track + of previous state of the middle button. This can potentially confuse + some very old drivers for two button mice though. */ + qemu_chr_read(chr, bytes, 4); +} + +static int msmouse_chr_write (struct CharDriverState *s, const uint8_t *buf, int len) +{ + /* Ignore writes to mouse port */ + return len; +} + +static void msmouse_chr_close (struct CharDriverState *chr) +{ + qemu_free (chr); +} + +CharDriverState *qemu_chr_open_msmouse(QemuOpts *opts) +{ + CharDriverState *chr; + + chr = qemu_mallocz(sizeof(CharDriverState)); + chr->chr_write = msmouse_chr_write; + chr->chr_close = msmouse_chr_close; + + qemu_add_mouse_event_handler(msmouse_event, chr, 0, "QEMU Microsoft Mouse"); + + return chr; +} diff --git a/hw/msmouse.h b/hw/msmouse.h new file mode 100644 index 0000000..456cb21 --- /dev/null +++ b/hw/msmouse.h @@ -0,0 +1,2 @@ +/* msmouse.c */ +CharDriverState *qemu_chr_open_msmouse(QemuOpts *opts); diff --git a/hw/mst_fpga.c b/hw/mst_fpga.c new file mode 100644 index 0000000..5252fc5 --- /dev/null +++ b/hw/mst_fpga.c @@ -0,0 +1,240 @@ +/* + * PXA270-based Intel Mainstone platforms. + * FPGA driver + * + * Copyright (c) 2007 by Armin Kuster or + * + * + * This code is licensed under the GNU GPL v2. + */ +#include "hw.h" +#include "pxa.h" +#include "mainstone.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 + +typedef struct mst_irq_state{ + qemu_irq *parent; + qemu_irq *pins; + + 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_update_gpio(mst_irq_state *s) +{ + uint32_t level, diff; + int bit; + level = s->prev_level ^ s->intsetclr; + + for (diff = s->prev_level ^ level; diff; diff ^= 1 << bit) { + bit = ffs(diff) - 1; + qemu_set_irq(s->pins[bit], (level >> bit) & 1 ); + } + s->prev_level = level; +} + +static void +mst_fpga_set_irq(void *opaque, int irq, int level) +{ + mst_irq_state *s = (mst_irq_state *)opaque; + + if (level) + s->prev_level |= 1u << irq; + else + s->prev_level &= ~(1u << irq); + + if(s->intmskena & (1u << irq)) { + s->intsetclr = 1u << irq; + qemu_set_irq(s->parent[0], level); + } +} + + +static uint32_t +mst_fpga_readb(void *opaque, target_phys_addr_t addr) +{ + 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 " + REG_FMT " \n", addr); + } + return 0; +} + +static void +mst_fpga_writeb(void *opaque, target_phys_addr_t addr, uint32_t value) +{ + 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 interupt */ + s->intmskena = (value & 0xFEEFF); + mst_fpga_update_gpio(s); + break; + case MST_INTSETCLR: /* clear or set interrupt */ + s->intsetclr = (value & 0xFEEFF); + break; + case MST_PCMCIA0: + s->pcmcia0 = value; + break; + case MST_PCMCIA1: + s->pcmcia1 = value; + break; + default: + printf("Mainstone - mst_fpga_writeb: Bad register offset " + REG_FMT " \n", addr); + } +} + +static CPUReadMemoryFunc * const mst_fpga_readfn[] = { + mst_fpga_readb, + mst_fpga_readb, + mst_fpga_readb, +}; +static CPUWriteMemoryFunc * const mst_fpga_writefn[] = { + mst_fpga_writeb, + mst_fpga_writeb, + mst_fpga_writeb, +}; + +static void +mst_fpga_save(QEMUFile *f, void *opaque) +{ + struct mst_irq_state *s = (mst_irq_state *) opaque; + + qemu_put_be32s(f, &s->prev_level); + qemu_put_be32s(f, &s->leddat1); + qemu_put_be32s(f, &s->leddat2); + qemu_put_be32s(f, &s->ledctrl); + qemu_put_be32s(f, &s->gpswr); + qemu_put_be32s(f, &s->mscwr1); + qemu_put_be32s(f, &s->mscwr2); + qemu_put_be32s(f, &s->mscwr3); + qemu_put_be32s(f, &s->mscrd); + qemu_put_be32s(f, &s->intmskena); + qemu_put_be32s(f, &s->intsetclr); + qemu_put_be32s(f, &s->pcmcia0); + qemu_put_be32s(f, &s->pcmcia1); +} + +static int +mst_fpga_load(QEMUFile *f, void *opaque, int version_id) +{ + mst_irq_state *s = (mst_irq_state *) opaque; + + qemu_get_be32s(f, &s->prev_level); + qemu_get_be32s(f, &s->leddat1); + qemu_get_be32s(f, &s->leddat2); + qemu_get_be32s(f, &s->ledctrl); + qemu_get_be32s(f, &s->gpswr); + qemu_get_be32s(f, &s->mscwr1); + qemu_get_be32s(f, &s->mscwr2); + qemu_get_be32s(f, &s->mscwr3); + qemu_get_be32s(f, &s->mscrd); + qemu_get_be32s(f, &s->intmskena); + qemu_get_be32s(f, &s->intsetclr); + qemu_get_be32s(f, &s->pcmcia0); + qemu_get_be32s(f, &s->pcmcia1); + return 0; +} + +qemu_irq *mst_irq_init(PXA2xxState *cpu, uint32_t base, int irq) +{ + mst_irq_state *s; + int iomemtype; + qemu_irq *qi; + + s = (mst_irq_state *) + qemu_mallocz(sizeof(mst_irq_state)); + + s->parent = &cpu->pic[irq]; + + /* alloc the external 16 irqs */ + qi = qemu_allocate_irqs(mst_fpga_set_irq, s, MST_NUM_IRQS); + s->pins = qi; + + iomemtype = cpu_register_io_memory(mst_fpga_readfn, + mst_fpga_writefn, s, DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(base, 0x00100000, iomemtype); + register_savevm(NULL, "mainstone_fpga", 0, 0, mst_fpga_save, + mst_fpga_load, s); + return qi; +} diff --git a/hw/multiboot.c b/hw/multiboot.c new file mode 100644 index 0000000..0d2bfb4 --- /dev/null +++ b/hw/multiboot.c @@ -0,0 +1,339 @@ +/* + * 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.h" +#include "fw_cfg.h" +#include "multiboot.h" +#include "loader.h" +#include "elf.h" +#include "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_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, +}; + +typedef struct { + /* buffer holding kernel, cmdlines and mb_infos */ + void *mb_buf; + /* address in target */ + target_phys_addr_t mb_buf_phys; + /* size of mb_buf in bytes */ + unsigned mb_buf_size; + /* offset of mb-info's in bytes */ + target_phys_addr_t offset_mbinfo; + /* offset in buffer for cmdlines in bytes */ + target_phys_addr_t offset_cmdlines; + /* offset of modules in bytes */ + target_phys_addr_t offset_mods; + /* available slots for mb modules infos */ + int mb_mods_avail; + /* currently used slots of mb modules */ + int mb_mods_count; +} MultibootState; + +static uint32_t mb_add_cmdline(MultibootState *s, const char *cmdline) +{ + int len = strlen(cmdline) + 1; + target_phys_addr_t p = s->offset_cmdlines; + + pstrcpy((char *)s->mb_buf + p, len, cmdline); + s->offset_cmdlines += len; + return s->mb_buf_phys + p; +} + +static void mb_add_mod(MultibootState *s, + target_phys_addr_t start, target_phys_addr_t end, + target_phys_addr_t 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(void *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; + + /* 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 = qemu_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); + mh_load_addr = ldl_p(header+i+16); + uint32_t mb_kernel_text_offset = i - (mh_header_addr - mh_load_addr); + + mh_entry_addr = ldl_p(header+i+28); + mb_kernel_size = kernel_file_size - mb_kernel_text_offset; + + /* 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", ldl_p(header+i+20)); + mb_debug("multiboot: mh_bss_end_addr = %#x\n", ldl_p(header+i+24)); + mb_debug("qemu: loading multiboot kernel (%#x bytes) at %#x\n", + mb_kernel_size, mh_load_addr); + + mbs.mb_buf = qemu_malloc(mb_kernel_size); + fseek(f, mb_kernel_text_offset, SEEK_SET); + if (fread(mbs.mb_buf, 1, mb_kernel_size, f) != mb_kernel_size) { + fprintf(stderr, "fread() failed\n"); + exit(1); + } + 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 and mb_mods */ + mbs.mb_buf_size += strlen(kernel_filename) + 1; + mbs.mb_buf_size += strlen(kernel_cmdline) + 1; + if (initrd_filename) { + const char *r = initrd_filename; + mbs.mb_buf_size += strlen(r) + 1; + mbs.mb_mods_avail = 1; + while ((r = strchr(r, ','))) { + mbs.mb_mods_avail++; + r++; + } + mbs.mb_buf_size += MB_MOD_SIZE * mbs.mb_mods_avail; + } + + mbs.mb_buf_size = TARGET_PAGE_ALIGN(mbs.mb_buf_size); + + /* enlarge mb_buf to hold cmdlines and mb-info structs */ + mbs.mb_buf = qemu_realloc(mbs.mb_buf, mbs.mb_buf_size); + mbs.offset_cmdlines = mbs.offset_mbinfo + mbs.mb_mods_avail * MB_MOD_SIZE; + + if (initrd_filename) { + char *next_initrd; + + mbs.offset_mods = mbs.mb_buf_size; + + do { + char *next_space; + int mb_mod_length; + uint32_t offs = mbs.mb_buf_size; + + next_initrd = strchr(initrd_filename, ','); + if (next_initrd) + *next_initrd = '\0'; + /* if a space comes after the module filename, treat everything + after that as parameters */ + target_phys_addr_t 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 get %s image size\n", initrd_filename); + exit(1); + } + + mbs.mb_buf_size = TARGET_PAGE_ALIGN(mb_mod_length + mbs.mb_buf_size); + mbs.mb_buf = qemu_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 (next_initrd); + } + + /* 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_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); + stl_p(bootinfo + MBI_MEM_LOWER, 640); + stl_p(bootinfo + MBI_MEM_UPPER, (ram_size / 1024) - 1024); + stl_p(bootinfo + MBI_BOOT_DEVICE, 0x8001ffff); /* 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 = qemu_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/multiboot.h b/hw/multiboot.h new file mode 100644 index 0000000..98fb1b7 --- /dev/null +++ b/hw/multiboot.h @@ -0,0 +1,12 @@ +#ifndef QEMU_MULTIBOOT_H +#define QEMU_MULTIBOOT_H + +int load_multiboot(void *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/musicpal.c b/hw/musicpal.c new file mode 100644 index 0000000..d98aa8d --- /dev/null +++ b/hw/musicpal.c @@ -0,0 +1,1665 @@ +/* + * Marvell MV88W8618 / Freecom MusicPal emulation. + * + * Copyright (c) 2008 Jan Kiszka + * + * This code is licenced under the GNU GPL v2. + */ + +#include "sysbus.h" +#include "arm-misc.h" +#include "devices.h" +#include "net.h" +#include "sysemu.h" +#include "boards.h" +#include "pc.h" +#include "qemu-timer.h" +#include "block.h" +#include "flash.h" +#include "console.h" +#include "i2c.h" +#include "blockdev.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 +#define MP_ETH_CTDP2 0x4E8 +#define MP_ETH_CTDP3 0x4EC + +/* 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 (1 << 31) + +/* RX descriptor status */ +#define MP_ETH_RX_OWN (1 << 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; + +typedef struct mv88w8618_eth_state { + SysBusDevice busdev; + 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, (void *)desc, sizeof(*desc)); +} + +static void eth_rx_desc_get(uint32_t addr, mv88w8618_rx_desc *desc) +{ + cpu_physical_memory_read(addr, (void *)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 int eth_can_receive(VLANClientState *nc) +{ + return 1; +} + +static ssize_t eth_receive(VLANClientState *nc, const uint8_t *buf, size_t size) +{ + mv88w8618_eth_state *s = DO_UPCAST(NICState, nc, nc)->opaque; + 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, (void *)desc, sizeof(*desc)); +} + +static void eth_tx_desc_get(uint32_t addr, mv88w8618_tx_desc *desc) +{ + cpu_physical_memory_read(addr, (void *)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(&s->nic->nc, 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 uint32_t mv88w8618_eth_read(void *opaque, target_phys_addr_t offset) +{ + 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_CTDP3: + return s->tx_queue[(offset - MP_ETH_CTDP0)/4]; + + default: + return 0; + } +} + +static void mv88w8618_eth_write(void *opaque, target_phys_addr_t offset, + uint32_t value) +{ + 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_CTDP3: + s->tx_queue[(offset - MP_ETH_CTDP0)/4] = value; + break; + } +} + +static CPUReadMemoryFunc * const mv88w8618_eth_readfn[] = { + mv88w8618_eth_read, + mv88w8618_eth_read, + mv88w8618_eth_read +}; + +static CPUWriteMemoryFunc * const mv88w8618_eth_writefn[] = { + mv88w8618_eth_write, + mv88w8618_eth_write, + mv88w8618_eth_write +}; + +static void eth_cleanup(VLANClientState *nc) +{ + mv88w8618_eth_state *s = DO_UPCAST(NICState, nc, nc)->opaque; + + s->nic = NULL; +} + +static NetClientInfo net_mv88w8618_info = { + .type = NET_CLIENT_TYPE_NIC, + .size = sizeof(NICState), + .can_receive = eth_can_receive, + .receive = eth_receive, + .cleanup = eth_cleanup, +}; + +static int mv88w8618_eth_init(SysBusDevice *dev) +{ + mv88w8618_eth_state *s = FROM_SYSBUS(mv88w8618_eth_state, dev); + + sysbus_init_irq(dev, &s->irq); + s->nic = qemu_new_nic(&net_mv88w8618_info, &s->conf, + dev->qdev.info->name, dev->qdev.id, s); + s->mmio_index = cpu_register_io_memory(mv88w8618_eth_readfn, + mv88w8618_eth_writefn, s, + DEVICE_NATIVE_ENDIAN); + sysbus_init_mmio(dev, MP_ETH_SIZE, s->mmio_index); + return 0; +} + +static const VMStateDescription mv88w8618_eth_vmsd = { + .name = "mv88w8618_eth", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 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 SysBusDeviceInfo mv88w8618_eth_info = { + .init = mv88w8618_eth_init, + .qdev.name = "mv88w8618_eth", + .qdev.size = sizeof(mv88w8618_eth_state), + .qdev.vmsd = &mv88w8618_eth_vmsd, + .qdev.props = (Property[]) { + DEFINE_NIC_PROPERTIES(mv88w8618_eth_state, conf), + DEFINE_PROP_END_OF_LIST(), + }, +}; + +/* 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 */ + +typedef struct musicpal_lcd_state { + SysBusDevice busdev; + uint32_t brightness; + uint32_t mode; + uint32_t irqctrl; + uint32_t page; + uint32_t page_off; + DisplayState *ds; + 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; \ + type *pixel = &((type *) ds_get_data(s->ds))[(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) + +#include "pixel_ops.h" + +static void lcd_refresh(void *opaque) +{ + musicpal_lcd_state *s = opaque; + int x, y, col; + + switch (ds_get_bits_per_pixel(s->ds)) { + 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(s->ds->surface) ? + rgb_to_pixel32bgr : rgb_to_pixel32)) + default: + hw_error("unsupported colour depth %i\n", + ds_get_bits_per_pixel(s->ds)); + } + + dpy_update(s->ds, 0, 0, 128*3, 64*3); +} + +static void lcd_invalidate(void *opaque) +{ +} + +static void musicpal_lcd_gpio_brigthness_in(void *opaque, int irq, int level) +{ + musicpal_lcd_state *s = opaque; + s->brightness &= ~(1 << irq); + s->brightness |= level << irq; +} + +static uint32_t musicpal_lcd_read(void *opaque, target_phys_addr_t offset) +{ + musicpal_lcd_state *s = opaque; + + switch (offset) { + case MP_LCD_IRQCTRL: + return s->irqctrl; + + default: + return 0; + } +} + +static void musicpal_lcd_write(void *opaque, target_phys_addr_t offset, + uint32_t value) +{ + 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 CPUReadMemoryFunc * const musicpal_lcd_readfn[] = { + musicpal_lcd_read, + musicpal_lcd_read, + musicpal_lcd_read +}; + +static CPUWriteMemoryFunc * const musicpal_lcd_writefn[] = { + musicpal_lcd_write, + musicpal_lcd_write, + musicpal_lcd_write +}; + +static int musicpal_lcd_init(SysBusDevice *dev) +{ + musicpal_lcd_state *s = FROM_SYSBUS(musicpal_lcd_state, dev); + int iomemtype; + + s->brightness = 7; + + iomemtype = cpu_register_io_memory(musicpal_lcd_readfn, + musicpal_lcd_writefn, s, + DEVICE_NATIVE_ENDIAN); + sysbus_init_mmio(dev, MP_LCD_SIZE, iomemtype); + + s->ds = graphic_console_init(lcd_refresh, lcd_invalidate, + NULL, NULL, s); + qemu_console_resize(s->ds, 128*3, 64*3); + + qdev_init_gpio_in(&dev->qdev, musicpal_lcd_gpio_brigthness_in, 3); + + return 0; +} + +static const VMStateDescription musicpal_lcd_vmsd = { + .name = "musicpal_lcd", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 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 SysBusDeviceInfo musicpal_lcd_info = { + .init = musicpal_lcd_init, + .qdev.name = "musicpal_lcd", + .qdev.size = sizeof(musicpal_lcd_state), + .qdev.vmsd = &musicpal_lcd_vmsd, +}; + +/* PIC register offsets */ +#define MP_PIC_STATUS 0x00 +#define MP_PIC_ENABLE_SET 0x08 +#define MP_PIC_ENABLE_CLR 0x0C + +typedef struct mv88w8618_pic_state +{ + SysBusDevice busdev; + 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 uint32_t mv88w8618_pic_read(void *opaque, target_phys_addr_t offset) +{ + 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, target_phys_addr_t offset, + uint32_t value) +{ + 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 = FROM_SYSBUS(mv88w8618_pic_state, + sysbus_from_qdev(d)); + + s->level = 0; + s->enabled = 0; +} + +static CPUReadMemoryFunc * const mv88w8618_pic_readfn[] = { + mv88w8618_pic_read, + mv88w8618_pic_read, + mv88w8618_pic_read +}; + +static CPUWriteMemoryFunc * const mv88w8618_pic_writefn[] = { + mv88w8618_pic_write, + mv88w8618_pic_write, + mv88w8618_pic_write +}; + +static int mv88w8618_pic_init(SysBusDevice *dev) +{ + mv88w8618_pic_state *s = FROM_SYSBUS(mv88w8618_pic_state, dev); + int iomemtype; + + qdev_init_gpio_in(&dev->qdev, mv88w8618_pic_set_irq, 32); + sysbus_init_irq(dev, &s->parent_irq); + iomemtype = cpu_register_io_memory(mv88w8618_pic_readfn, + mv88w8618_pic_writefn, s, + DEVICE_NATIVE_ENDIAN); + sysbus_init_mmio(dev, MP_PIC_SIZE, iomemtype); + return 0; +} + +static const VMStateDescription mv88w8618_pic_vmsd = { + .name = "mv88w8618_pic", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32(level, mv88w8618_pic_state), + VMSTATE_UINT32(enabled, mv88w8618_pic_state), + VMSTATE_END_OF_LIST() + } +}; + +static SysBusDeviceInfo mv88w8618_pic_info = { + .init = mv88w8618_pic_init, + .qdev.name = "mv88w8618_pic", + .qdev.size = sizeof(mv88w8618_pic_state), + .qdev.reset = mv88w8618_pic_reset, + .qdev.vmsd = &mv88w8618_pic_vmsd, +}; + +/* 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; + +typedef struct mv88w8618_pit_state { + SysBusDevice busdev; + 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 uint32_t mv88w8618_pit_read(void *opaque, target_phys_addr_t offset) +{ + 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, target_phys_addr_t offset, + uint32_t value) +{ + 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 = FROM_SYSBUS(mv88w8618_pit_state, + sysbus_from_qdev(d)); + int i; + + for (i = 0; i < 4; i++) { + ptimer_stop(s->timer[i].ptimer); + s->timer[i].limit = 0; + } +} + +static CPUReadMemoryFunc * const mv88w8618_pit_readfn[] = { + mv88w8618_pit_read, + mv88w8618_pit_read, + mv88w8618_pit_read +}; + +static CPUWriteMemoryFunc * const mv88w8618_pit_writefn[] = { + mv88w8618_pit_write, + mv88w8618_pit_write, + mv88w8618_pit_write +}; + +static int mv88w8618_pit_init(SysBusDevice *dev) +{ + int iomemtype; + mv88w8618_pit_state *s = FROM_SYSBUS(mv88w8618_pit_state, 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); + } + + iomemtype = cpu_register_io_memory(mv88w8618_pit_readfn, + mv88w8618_pit_writefn, s, + DEVICE_NATIVE_ENDIAN); + sysbus_init_mmio(dev, MP_PIT_SIZE, iomemtype); + return 0; +} + +static const VMStateDescription mv88w8618_timer_vmsd = { + .name = "timer", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 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, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + VMSTATE_STRUCT_ARRAY(timer, mv88w8618_pit_state, 4, 1, + mv88w8618_timer_vmsd, mv88w8618_timer_state), + VMSTATE_END_OF_LIST() + } +}; + +static SysBusDeviceInfo mv88w8618_pit_info = { + .init = mv88w8618_pit_init, + .qdev.name = "mv88w8618_pit", + .qdev.size = sizeof(mv88w8618_pit_state), + .qdev.reset = mv88w8618_pit_reset, + .qdev.vmsd = &mv88w8618_pit_vmsd, +}; + +/* Flash config register offsets */ +#define MP_FLASHCFG_CFGR0 0x04 + +typedef struct mv88w8618_flashcfg_state { + SysBusDevice busdev; + uint32_t cfgr0; +} mv88w8618_flashcfg_state; + +static uint32_t mv88w8618_flashcfg_read(void *opaque, + target_phys_addr_t offset) +{ + mv88w8618_flashcfg_state *s = opaque; + + switch (offset) { + case MP_FLASHCFG_CFGR0: + return s->cfgr0; + + default: + return 0; + } +} + +static void mv88w8618_flashcfg_write(void *opaque, target_phys_addr_t offset, + uint32_t value) +{ + mv88w8618_flashcfg_state *s = opaque; + + switch (offset) { + case MP_FLASHCFG_CFGR0: + s->cfgr0 = value; + break; + } +} + +static CPUReadMemoryFunc * const mv88w8618_flashcfg_readfn[] = { + mv88w8618_flashcfg_read, + mv88w8618_flashcfg_read, + mv88w8618_flashcfg_read +}; + +static CPUWriteMemoryFunc * const mv88w8618_flashcfg_writefn[] = { + mv88w8618_flashcfg_write, + mv88w8618_flashcfg_write, + mv88w8618_flashcfg_write +}; + +static int mv88w8618_flashcfg_init(SysBusDevice *dev) +{ + int iomemtype; + mv88w8618_flashcfg_state *s = FROM_SYSBUS(mv88w8618_flashcfg_state, dev); + + s->cfgr0 = 0xfffe4285; /* Default as set by U-Boot for 8 MB flash */ + iomemtype = cpu_register_io_memory(mv88w8618_flashcfg_readfn, + mv88w8618_flashcfg_writefn, s, + DEVICE_NATIVE_ENDIAN); + sysbus_init_mmio(dev, MP_FLASHCFG_SIZE, iomemtype); + return 0; +} + +static const VMStateDescription mv88w8618_flashcfg_vmsd = { + .name = "mv88w8618_flashcfg", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32(cfgr0, mv88w8618_flashcfg_state), + VMSTATE_END_OF_LIST() + } +}; + +static SysBusDeviceInfo mv88w8618_flashcfg_info = { + .init = mv88w8618_flashcfg_init, + .qdev.name = "mv88w8618_flashcfg", + .qdev.size = sizeof(mv88w8618_flashcfg_state), + .qdev.vmsd = &mv88w8618_flashcfg_vmsd, +}; + +/* Misc register offsets */ +#define MP_MISC_BOARD_REVISION 0x18 + +#define MP_BOARD_REVISION 0x31 + +static uint32_t musicpal_misc_read(void *opaque, target_phys_addr_t offset) +{ + switch (offset) { + case MP_MISC_BOARD_REVISION: + return MP_BOARD_REVISION; + + default: + return 0; + } +} + +static void musicpal_misc_write(void *opaque, target_phys_addr_t offset, + uint32_t value) +{ +} + +static CPUReadMemoryFunc * const musicpal_misc_readfn[] = { + musicpal_misc_read, + musicpal_misc_read, + musicpal_misc_read, +}; + +static CPUWriteMemoryFunc * const musicpal_misc_writefn[] = { + musicpal_misc_write, + musicpal_misc_write, + musicpal_misc_write, +}; + +static void musicpal_misc_init(void) +{ + int iomemtype; + + iomemtype = cpu_register_io_memory(musicpal_misc_readfn, + musicpal_misc_writefn, NULL, + DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(MP_MISC_BASE, MP_MISC_SIZE, iomemtype); +} + +/* WLAN register offsets */ +#define MP_WLAN_MAGIC1 0x11c +#define MP_WLAN_MAGIC2 0x124 + +static uint32_t mv88w8618_wlan_read(void *opaque, target_phys_addr_t offset) +{ + 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, target_phys_addr_t offset, + uint32_t value) +{ +} + +static CPUReadMemoryFunc * const mv88w8618_wlan_readfn[] = { + mv88w8618_wlan_read, + mv88w8618_wlan_read, + mv88w8618_wlan_read, +}; + +static CPUWriteMemoryFunc * const mv88w8618_wlan_writefn[] = { + mv88w8618_wlan_write, + mv88w8618_wlan_write, + mv88w8618_wlan_write, +}; + +static int mv88w8618_wlan_init(SysBusDevice *dev) +{ + int iomemtype; + + iomemtype = cpu_register_io_memory(mv88w8618_wlan_readfn, + mv88w8618_wlan_writefn, NULL, + DEVICE_NATIVE_ENDIAN); + sysbus_init_mmio(dev, MP_WLAN_SIZE, iomemtype); + 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 + +typedef struct musicpal_gpio_state { + SysBusDevice busdev; + 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 uint32_t musicpal_gpio_read(void *opaque, target_phys_addr_t offset) +{ + 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, target_phys_addr_t offset, + uint32_t value) +{ + 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 CPUReadMemoryFunc * const musicpal_gpio_readfn[] = { + musicpal_gpio_read, + musicpal_gpio_read, + musicpal_gpio_read, +}; + +static CPUWriteMemoryFunc * const musicpal_gpio_writefn[] = { + musicpal_gpio_write, + musicpal_gpio_write, + musicpal_gpio_write, +}; + +static void musicpal_gpio_reset(DeviceState *d) +{ + musicpal_gpio_state *s = FROM_SYSBUS(musicpal_gpio_state, + sysbus_from_qdev(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 *dev) +{ + musicpal_gpio_state *s = FROM_SYSBUS(musicpal_gpio_state, dev); + int iomemtype; + + sysbus_init_irq(dev, &s->irq); + + iomemtype = cpu_register_io_memory(musicpal_gpio_readfn, + musicpal_gpio_writefn, s, + DEVICE_NATIVE_ENDIAN); + sysbus_init_mmio(dev, MP_GPIO_SIZE, iomemtype); + + qdev_init_gpio_out(&dev->qdev, s->out, ARRAY_SIZE(s->out)); + + qdev_init_gpio_in(&dev->qdev, musicpal_gpio_pin_event, 32); + + return 0; +} + +static const VMStateDescription musicpal_gpio_vmsd = { + .name = "musicpal_gpio", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 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 SysBusDeviceInfo musicpal_gpio_info = { + .init = musicpal_gpio_init, + .qdev.name = "musicpal_gpio", + .qdev.size = sizeof(musicpal_gpio_state), + .qdev.reset = musicpal_gpio_reset, + .qdev.vmsd = &musicpal_gpio_vmsd, +}; + +/* 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) + +typedef struct musicpal_key_state { + SysBusDevice busdev; + 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 *dev) +{ + musicpal_key_state *s = FROM_SYSBUS(musicpal_key_state, dev); + + sysbus_init_mmio(dev, 0x0, 0); + + s->kbd_extended = 0; + s->pressed_keys = 0; + + qdev_init_gpio_out(&dev->qdev, 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, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32(kbd_extended, musicpal_key_state), + VMSTATE_UINT32(pressed_keys, musicpal_key_state), + VMSTATE_END_OF_LIST() + } +}; + +static SysBusDeviceInfo musicpal_key_info = { + .init = musicpal_key_init, + .qdev.name = "musicpal_key", + .qdev.size = sizeof(musicpal_key_state), + .qdev.vmsd = &musicpal_key_vmsd, +}; + +static struct arm_boot_info musicpal_binfo = { + .loader_start = 0x0, + .board_id = 0x20e, +}; + +static void musicpal_init(ram_addr_t ram_size, + const char *boot_device, + const char *kernel_filename, const char *kernel_cmdline, + const char *initrd_filename, const char *cpu_model) +{ + CPUState *env; + qemu_irq *cpu_pic; + qemu_irq pic[32]; + DeviceState *dev; + DeviceState *i2c_dev; + DeviceState *lcd_dev; + DeviceState *key_dev; + DeviceState *wm8750_dev; + SysBusDevice *s; + i2c_bus *i2c; + int i; + unsigned long flash_size; + DriveInfo *dinfo; + ram_addr_t sram_off; + + if (!cpu_model) { + cpu_model = "arm926"; + } + env = cpu_init(cpu_model); + if (!env) { + fprintf(stderr, "Unable to find CPU definition\n"); + exit(1); + } + cpu_pic = arm_pic_init_cpu(env); + + /* For now we use a fixed - the original - RAM size */ + cpu_register_physical_memory(0, MP_RAM_DEFAULT_SIZE, + qemu_ram_alloc(NULL, "musicpal.ram", + MP_RAM_DEFAULT_SIZE)); + + sram_off = qemu_ram_alloc(NULL, "musicpal.sram", MP_SRAM_SIZE); + cpu_register_physical_memory(MP_SRAM_BASE, MP_SRAM_SIZE, sram_off); + + dev = sysbus_create_simple("mv88w8618_pic", MP_PIC_BASE, + cpu_pic[ARM_PIC_CPU_IRQ]); + for (i = 0; i < 32; i++) { + pic[i] = qdev_get_gpio_in(dev, i); + } + sysbus_create_varargs("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]) { +#ifdef TARGET_WORDS_BIGENDIAN + serial_mm_init(MP_UART1_BASE, 2, pic[MP_UART1_IRQ], 1825000, + serial_hds[0], 1, 1); +#else + serial_mm_init(MP_UART1_BASE, 2, pic[MP_UART1_IRQ], 1825000, + serial_hds[0], 1, 0); +#endif + } + if (serial_hds[1]) { +#ifdef TARGET_WORDS_BIGENDIAN + serial_mm_init(MP_UART2_BASE, 2, pic[MP_UART2_IRQ], 1825000, + serial_hds[1], 1, 1); +#else + serial_mm_init(MP_UART2_BASE, 2, pic[MP_UART2_IRQ], 1825000, + serial_hds[1], 1, 0); +#endif + } + + /* Register flash */ + dinfo = drive_get(IF_PFLASH, 0, 0); + if (dinfo) { + flash_size = bdrv_getlength(dinfo->bdrv); + 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(0-MP_FLASH_SIZE_MAX, qemu_ram_alloc(NULL, + "musicpal.flash", flash_size), + dinfo->bdrv, 0x10000, + (flash_size + 0xffff) >> 16, + MP_FLASH_SIZE_MAX / flash_size, + 2, 0x00BF, 0x236D, 0x0000, 0x0000, + 0x5555, 0x2AAA, 1); +#else + pflash_cfi02_register(0-MP_FLASH_SIZE_MAX, qemu_ram_alloc(NULL, + "musicpal.flash", flash_size), + dinfo->bdrv, 0x10000, + (flash_size + 0xffff) >> 16, + MP_FLASH_SIZE_MAX / flash_size, + 2, 0x00BF, 0x236D, 0x0000, 0x0000, + 0x5555, 0x2AAA, 0); +#endif + + } + sysbus_create_simple("mv88w8618_flashcfg", MP_FLASHCFG_BASE, NULL); + + qemu_check_nic_model(&nd_table[0], "mv88w8618"); + dev = qdev_create(NULL, "mv88w8618_eth"); + qdev_set_nic_properties(dev, &nd_table[0]); + qdev_init_nofail(dev); + sysbus_mmio_map(sysbus_from_qdev(dev), 0, MP_ETH_BASE); + sysbus_connect_irq(sysbus_from_qdev(dev), 0, pic[MP_ETH_IRQ]); + + sysbus_create_simple("mv88w8618_wlan", MP_WLAN_BASE, NULL); + + musicpal_misc_init(); + + dev = sysbus_create_simple("musicpal_gpio", MP_GPIO_BASE, pic[MP_GPIO_IRQ]); + i2c_dev = sysbus_create_simple("gpio_i2c", 0, NULL); + i2c = (i2c_bus *)qdev_get_child_bus(i2c_dev, "i2c"); + + lcd_dev = sysbus_create_simple("musicpal_lcd", MP_LCD_BASE, NULL); + key_dev = sysbus_create_simple("musicpal_key", 0, 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 = sysbus_from_qdev(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(env, &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 musicpal_register_devices(void) +{ + sysbus_register_withprop(&mv88w8618_pic_info); + sysbus_register_withprop(&mv88w8618_pit_info); + sysbus_register_withprop(&mv88w8618_flashcfg_info); + sysbus_register_withprop(&mv88w8618_eth_info); + sysbus_register_dev("mv88w8618_wlan", sizeof(SysBusDevice), + mv88w8618_wlan_init); + sysbus_register_withprop(&musicpal_lcd_info); + sysbus_register_withprop(&musicpal_gpio_info); + sysbus_register_withprop(&musicpal_key_info); +} + +device_init(musicpal_register_devices) diff --git a/hw/nand.c b/hw/nand.c new file mode 100644 index 0000000..f414aa1 --- /dev/null +++ b/hw/nand.c @@ -0,0 +1,661 @@ +/* + * 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 + * + * This code is licensed under the GNU GPL v2. + */ + +#ifndef NAND_IO + +# include "hw.h" +# include "flash.h" +# include "blockdev.h" +/* FIXME: Pass block device as an argument. */ + +# 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_BUSY (1 << 6) +# define NAND_IOSTATUS_UNPROTCT (1 << 7) + +# define MAX_PAGE 0x800 +# define MAX_OOB 0x40 + +struct NANDFlashState { + uint8_t manf_id, chip_id; + int size, pages; + int page_shift, oob_shift, erase_shift, addr_shift; + uint8_t *storage; + BlockDriverState *bdrv; + int mem_oob; + + int cle, ale, ce, wp, gnd; + + uint8_t io[MAX_PAGE + MAX_OOB + 0x400]; + uint8_t *ioaddr; + int iolen; + + uint32_t cmd, addr; + int addrlen; + int status; + int offset; + + void (*blk_write)(NANDFlashState *s); + void (*blk_erase)(NANDFlashState *s); + void (*blk_load)(NANDFlashState *s, uint32_t addr, int offset); +}; + +# 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(NANDFlashState *s) +{ + s->cmd = NAND_CMD_READ0; + s->addr = 0; + s->addrlen = 0; + s->iolen = 0; + s->offset = 0; + s->status &= NAND_IOSTATUS_UNPROTCT; +} + +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->io[0] = s->manf_id; + s->io[1] = s->chip_id; + s->io[2] = 'Q'; /* Don't-care byte (often 0xa5) */ + if (nand_flash_ids[s->chip_id].options & NAND_SAMSUNG_LP) + s->io[3] = 0x15; /* Page Size, Block Size, Spare Size.. */ + else + s->io[3] = 0xc0; /* Multi-plane */ + s->ioaddr = s->io; + s->iolen = 4; + 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(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: + if (nand_flash_ids[s->chip_id].options & NAND_SAMSUNG_LP) + s->addr <<= 16; + else + s->addr <<= 8; + + if (s->wp) { + s->blk_erase(s); + } + break; + + case NAND_CMD_READSTATUS: + s->io[0] = s->status; + s->ioaddr = s->io; + s->iolen = 1; + break; + + default: + printf("%s: Unknown NAND command 0x%02x\n", __FUNCTION__, s->cmd); + } +} + +static void nand_save(QEMUFile *f, void *opaque) +{ + NANDFlashState *s = (NANDFlashState *) opaque; + qemu_put_byte(f, s->cle); + qemu_put_byte(f, s->ale); + qemu_put_byte(f, s->ce); + qemu_put_byte(f, s->wp); + qemu_put_byte(f, s->gnd); + qemu_put_buffer(f, s->io, sizeof(s->io)); + qemu_put_be32(f, s->ioaddr - s->io); + qemu_put_be32(f, s->iolen); + + qemu_put_be32s(f, &s->cmd); + qemu_put_be32s(f, &s->addr); + qemu_put_be32(f, s->addrlen); + qemu_put_be32(f, s->status); + qemu_put_be32(f, s->offset); + /* XXX: do we want to save s->storage too? */ +} + +static int nand_load(QEMUFile *f, void *opaque, int version_id) +{ + NANDFlashState *s = (NANDFlashState *) opaque; + s->cle = qemu_get_byte(f); + s->ale = qemu_get_byte(f); + s->ce = qemu_get_byte(f); + s->wp = qemu_get_byte(f); + s->gnd = qemu_get_byte(f); + qemu_get_buffer(f, s->io, sizeof(s->io)); + s->ioaddr = s->io + qemu_get_be32(f); + s->iolen = qemu_get_be32(f); + if (s->ioaddr >= s->io + sizeof(s->io) || s->ioaddr < s->io) + return -EINVAL; + + qemu_get_be32s(f, &s->cmd); + qemu_get_be32s(f, &s->addr); + s->addrlen = qemu_get_be32(f); + s->status = qemu_get_be32(f); + s->offset = qemu_get_be32(f); + return 0; +} + +/* + * 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(NANDFlashState *s, + int cle, int ale, int ce, int wp, int gnd) +{ + 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(NANDFlashState *s, int *rb) +{ + *rb = 1; +} + +void nand_setio(NANDFlashState *s, uint8_t value) +{ + 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 ++; + + if (s->addrlen == 1 && s->cmd == NAND_CMD_READID) + nand_command(s); + + if (!(nand_flash_ids[s->chip_id].options & NAND_SAMSUNG_LP) && + s->addrlen == 3 && ( + s->cmd == NAND_CMD_READ0 || + s->cmd == NAND_CMD_PAGEPROGRAM1)) + nand_command(s); + if ((nand_flash_ids[s->chip_id].options & NAND_SAMSUNG_LP) && + s->addrlen == 4 && ( + s->cmd == NAND_CMD_READ0 || + s->cmd == NAND_CMD_PAGEPROGRAM1)) + nand_command(s); + } + + if (!s->cle && !s->ale && s->cmd == NAND_CMD_PAGEPROGRAM1) { + if (s->iolen < (1 << s->page_shift) + (1 << s->oob_shift)) + s->io[s->iolen ++] = value; + } 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)) { + s->io[s->iolen + (s->addr & ((1 << s->addr_shift) - 1))] = value; + s->addr ++; + } + } +} + +uint8_t nand_getio(NANDFlashState *s) +{ + int offset; + + /* Allow sequential reading */ + if (!s->iolen && s->cmd == NAND_CMD_READ0) { + offset = (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; + + s->iolen --; + s->addr++; + return *(s->ioaddr ++); +} + +NANDFlashState *nand_init(int manf_id, int chip_id) +{ + int pagesize; + NANDFlashState *s; + DriveInfo *dinfo; + + if (nand_flash_ids[chip_id].size == 0) { + hw_error("%s: Unsupported NAND chip ID.\n", __FUNCTION__); + } + + s = (NANDFlashState *) qemu_mallocz(sizeof(NANDFlashState)); + dinfo = drive_get(IF_MTD, 0, 0); + if (dinfo) + s->bdrv = dinfo->bdrv; + s->manf_id = manf_id; + s->chip_id = chip_id; + 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: + hw_error("%s: Unsupported NAND block size.\n", __FUNCTION__); + } + + pagesize = 1 << s->oob_shift; + s->mem_oob = 1; + if (s->bdrv && bdrv_getlength(s->bdrv) >= + (s->pages << s->page_shift) + (s->pages << s->oob_shift)) { + pagesize = 0; + s->mem_oob = 0; + } + + if (!s->bdrv) + pagesize += 1 << s->page_shift; + if (pagesize) + s->storage = (uint8_t *) memset(qemu_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; + + register_savevm(NULL, "nand", -1, 0, nand_save, nand_load, s); + + return s; +} + +void nand_done(NANDFlashState *s) +{ + if (s->bdrv) { + bdrv_close(s->bdrv); + bdrv_delete(s->bdrv); + } + + if (!s->bdrv || s->mem_oob) + qemu_free(s->storage); + + qemu_free(s); +} + +#else + +/* Program a single page */ +static void glue(nand_blk_write_, PAGE_SIZE)(NANDFlashState *s) +{ + uint32_t off, page, sector, soff; + uint8_t iobuf[(PAGE_SECTORS + 2) * 0x200]; + if (PAGE(s->addr) >= s->pages) + return; + + if (!s->bdrv) { + memcpy(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 (bdrv_read(s->bdrv, sector, iobuf, PAGE_SECTORS) == -1) { + printf("%s: read error in sector %i\n", __FUNCTION__, sector); + return; + } + + memcpy(iobuf + (soff | off), s->io, MIN(s->iolen, PAGE_SIZE - off)); + if (off + s->iolen > PAGE_SIZE) { + page = PAGE(s->addr); + memcpy(s->storage + (page << OOB_SHIFT), s->io + PAGE_SIZE - off, + MIN(OOB_SIZE, off + s->iolen - PAGE_SIZE)); + } + + if (bdrv_write(s->bdrv, sector, iobuf, PAGE_SECTORS) == -1) + printf("%s: write error in sector %i\n", __FUNCTION__, sector); + } else { + off = PAGE_START(s->addr) + (s->addr & PAGE_MASK) + s->offset; + sector = off >> 9; + soff = off & 0x1ff; + if (bdrv_read(s->bdrv, sector, iobuf, PAGE_SECTORS + 2) == -1) { + printf("%s: read error in sector %i\n", __FUNCTION__, sector); + return; + } + + memcpy(iobuf + soff, s->io, s->iolen); + + if (bdrv_write(s->bdrv, sector, iobuf, PAGE_SECTORS + 2) == -1) + printf("%s: write error in sector %i\n", __FUNCTION__, sector); + } + s->offset = 0; +} + +/* Erase a single block */ +static void glue(nand_blk_erase_, PAGE_SIZE)(NANDFlashState *s) +{ + uint32_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->bdrv) { + 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 (bdrv_write(s->bdrv, i, iobuf, 1) == -1) + printf("%s: write error in sector %i\n", __FUNCTION__, i); + } else { + addr = PAGE_START(addr); + page = addr >> 9; + if (bdrv_read(s->bdrv, page, iobuf, 1) == -1) + printf("%s: read error in sector %i\n", __FUNCTION__, page); + memset(iobuf + (addr & 0x1ff), 0xff, (~addr & 0x1ff) + 1); + if (bdrv_write(s->bdrv, page, iobuf, 1) == -1) + printf("%s: write error in sector %i\n", __FUNCTION__, page); + + memset(iobuf, 0xff, 0x200); + i = (addr & ~0x1ff) + 0x200; + for (addr += ((PAGE_SIZE + OOB_SIZE) << s->erase_shift) - 0x200; + i < addr; i += 0x200) + if (bdrv_write(s->bdrv, i >> 9, iobuf, 1) == -1) + printf("%s: write error in sector %i\n", __FUNCTION__, i >> 9); + + page = i >> 9; + if (bdrv_read(s->bdrv, page, iobuf, 1) == -1) + printf("%s: read error in sector %i\n", __FUNCTION__, page); + memset(iobuf, 0xff, ((addr - 1) & 0x1ff) + 1); + if (bdrv_write(s->bdrv, page, iobuf, 1) == -1) + printf("%s: write error in sector %i\n", __FUNCTION__, page); + } +} + +static void glue(nand_blk_load_, PAGE_SIZE)(NANDFlashState *s, + uint32_t addr, int offset) +{ + if (PAGE(addr) >= s->pages) + return; + + if (s->bdrv) { + if (s->mem_oob) { + if (bdrv_read(s->bdrv, SECTOR(addr), s->io, PAGE_SECTORS) == -1) + printf("%s: read error in sector %i\n", + __FUNCTION__, 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 (bdrv_read(s->bdrv, PAGE_START(addr) >> 9, + s->io, (PAGE_SECTORS + 2)) == -1) + printf("%s: read error in sector %i\n", + __FUNCTION__, 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/ne2000-isa.c b/hw/ne2000-isa.c new file mode 100644 index 0000000..3ff0d89 --- /dev/null +++ b/hw/ne2000-isa.c @@ -0,0 +1,125 @@ +/* + * 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.h" +#include "pc.h" +#include "isa.h" +#include "qdev.h" +#include "net.h" +#include "ne2000.h" + +typedef struct ISANE2000State { + ISADevice dev; + uint32_t iobase; + uint32_t isairq; + NE2000State ne2000; +} ISANE2000State; + +static void isa_ne2000_cleanup(VLANClientState *nc) +{ + NE2000State *s = DO_UPCAST(NICState, nc, nc)->opaque; + + s->nic = NULL; +} + +static NetClientInfo net_ne2000_isa_info = { + .type = NET_CLIENT_TYPE_NIC, + .size = sizeof(NICState), + .can_receive = ne2000_can_receive, + .receive = ne2000_receive, + .cleanup = isa_ne2000_cleanup, +}; + +static const VMStateDescription vmstate_isa_ne2000 = { + .name = "ne2000", + .version_id = 2, + .minimum_version_id = 0, + .minimum_version_id_old = 0, + .fields = (VMStateField []) { + VMSTATE_STRUCT(ne2000, ISANE2000State, 0, vmstate_ne2000, NE2000State), + VMSTATE_END_OF_LIST() + } +}; + +static int isa_ne2000_initfn(ISADevice *dev) +{ + ISANE2000State *isa = DO_UPCAST(ISANE2000State, dev, dev); + NE2000State *s = &isa->ne2000; + + register_ioport_write(isa->iobase, 16, 1, ne2000_ioport_write, s); + register_ioport_read(isa->iobase, 16, 1, ne2000_ioport_read, s); + isa_init_ioport_range(dev, isa->iobase, 16); + + register_ioport_write(isa->iobase + 0x10, 1, 1, ne2000_asic_ioport_write, s); + register_ioport_read(isa->iobase + 0x10, 1, 1, ne2000_asic_ioport_read, s); + register_ioport_write(isa->iobase + 0x10, 2, 2, ne2000_asic_ioport_write, s); + register_ioport_read(isa->iobase + 0x10, 2, 2, ne2000_asic_ioport_read, s); + isa_init_ioport_range(dev, isa->iobase + 0x10, 2); + + register_ioport_write(isa->iobase + 0x1f, 1, 1, ne2000_reset_ioport_write, s); + register_ioport_read(isa->iobase + 0x1f, 1, 1, ne2000_reset_ioport_read, s); + isa_init_ioport(dev, isa->iobase + 0x1f); + + isa_init_irq(dev, &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, + dev->qdev.info->name, dev->qdev.id, s); + qemu_format_nic_info_str(&s->nic->nc, s->c.macaddr.a); + + return 0; +} + +void isa_ne2000_init(int base, int irq, NICInfo *nd) +{ + ISADevice *dev; + + qemu_check_nic_model(nd, "ne2k_isa"); + + dev = isa_create("ne2k_isa"); + qdev_prop_set_uint32(&dev->qdev, "iobase", base); + qdev_prop_set_uint32(&dev->qdev, "irq", irq); + qdev_set_nic_properties(&dev->qdev, nd); + qdev_init_nofail(&dev->qdev); +} + +static ISADeviceInfo ne2000_isa_info = { + .qdev.name = "ne2k_isa", + .qdev.size = sizeof(ISANE2000State), + .init = isa_ne2000_initfn, + .qdev.props = (Property[]) { + DEFINE_PROP_HEX32("iobase", ISANE2000State, iobase, 0x300), + DEFINE_PROP_UINT32("irq", ISANE2000State, isairq, 9), + DEFINE_NIC_PROPERTIES(ISANE2000State, ne2000.c), + DEFINE_PROP_END_OF_LIST(), + }, +}; + +static void ne2000_isa_register_devices(void) +{ + isa_qdev_register(&ne2000_isa_info); +} + +device_init(ne2000_isa_register_devices) diff --git a/hw/ne2000.c b/hw/ne2000.c new file mode 100644 index 0000000..5966359 --- /dev/null +++ b/hw/ne2000.c @@ -0,0 +1,781 @@ +/* + * 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.h" +#include "pci.h" +#include "net.h" +#include "ne2000.h" +#include "loader.h" +#include "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)); +} + +#define POLYNOMIAL 0x04c11db6 + +/* From FreeBSD */ +/* XXX: optimize */ +static int 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 >> 26); +} + +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(VLANClientState *nc) +{ + NE2000State *s = DO_UPCAST(NICState, nc, nc)->opaque; + + if (s->cmd & E8390_STOP) + return 1; + return !ne2000_buffer_full(s); +} + +#define MIN_BUF_SIZE 60 + +ssize_t ne2000_receive(VLANClientState *nc, const uint8_t *buf, size_t size_) +{ + NE2000State *s = DO_UPCAST(NICState, nc, nc)->opaque; + 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; + /* 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 + avail = 0; + 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_; +} + +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(&s->nic->nc, 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: + s->start = val << 8; + break; + case EN0_STOPPG: + s->stop = val << 8; + break; + case EN0_BOUNDARY: + 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: + s->curpag = val; + break; + case EN1_MULT ... EN1_MULT + 7: + s->mult[offset - EN1_MULT] = val; + break; + } + } +} + +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 < NE2000_MEM_SIZE)) { + cpu_to_le32wu((uint32_t *)(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 < NE2000_MEM_SIZE)) { + return le32_to_cpupu((uint32_t *)(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; + } +} + +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); + } +} + +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; +} + +void ne2000_reset_ioport_write(void *opaque, uint32_t addr, uint32_t val) +{ + /* nothing to do (end of reset pulse) */ +} + +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, + .minimum_version_id_old = 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, + .minimum_version_id_old = 3, + .fields = (VMStateField []) { + VMSTATE_PCI_DEVICE(dev, PCINE2000State), + VMSTATE_STRUCT(ne2000, PCINE2000State, 0, vmstate_ne2000, NE2000State), + VMSTATE_END_OF_LIST() + } +}; + +/***********************************************************/ +/* PCI NE2000 definitions */ + +static void ne2000_map(PCIDevice *pci_dev, int region_num, + pcibus_t addr, pcibus_t size, int type) +{ + PCINE2000State *d = DO_UPCAST(PCINE2000State, dev, pci_dev); + NE2000State *s = &d->ne2000; + + register_ioport_write(addr, 16, 1, ne2000_ioport_write, s); + register_ioport_read(addr, 16, 1, ne2000_ioport_read, s); + + register_ioport_write(addr + 0x10, 1, 1, ne2000_asic_ioport_write, s); + register_ioport_read(addr + 0x10, 1, 1, ne2000_asic_ioport_read, s); + register_ioport_write(addr + 0x10, 2, 2, ne2000_asic_ioport_write, s); + register_ioport_read(addr + 0x10, 2, 2, ne2000_asic_ioport_read, s); + register_ioport_write(addr + 0x10, 4, 4, ne2000_asic_ioport_writel, s); + register_ioport_read(addr + 0x10, 4, 4, ne2000_asic_ioport_readl, s); + + register_ioport_write(addr + 0x1f, 1, 1, ne2000_reset_ioport_write, s); + register_ioport_read(addr + 0x1f, 1, 1, ne2000_reset_ioport_read, s); +} + +static void ne2000_cleanup(VLANClientState *nc) +{ + NE2000State *s = DO_UPCAST(NICState, nc, nc)->opaque; + + s->nic = NULL; +} + +static NetClientInfo net_ne2000_info = { + .type = NET_CLIENT_TYPE_NIC, + .size = sizeof(NICState), + .can_receive = ne2000_can_receive, + .receive = ne2000_receive, + .cleanup = ne2000_cleanup, +}; + +static int pci_ne2000_init(PCIDevice *pci_dev) +{ + PCINE2000State *d = DO_UPCAST(PCINE2000State, dev, pci_dev); + NE2000State *s; + uint8_t *pci_conf; + + pci_conf = d->dev.config; + pci_config_set_vendor_id(pci_conf, PCI_VENDOR_ID_REALTEK); + pci_config_set_device_id(pci_conf, PCI_DEVICE_ID_REALTEK_8029); + pci_config_set_class(pci_conf, PCI_CLASS_NETWORK_ETHERNET); + /* TODO: RST# value should be 0. PCI spec 6.2.4 */ + pci_conf[PCI_INTERRUPT_PIN] = 1; // interrupt pin 0 + + pci_register_bar(&d->dev, 0, 0x100, + PCI_BASE_ADDRESS_SPACE_IO, ne2000_map); + s = &d->ne2000; + s->irq = d->dev.irq[0]; + + qemu_macaddr_default_if_unset(&s->c.macaddr); + ne2000_reset(s); + + s->nic = qemu_new_nic(&net_ne2000_info, &s->c, + pci_dev->qdev.info->name, pci_dev->qdev.id, s); + qemu_format_nic_info_str(&s->nic->nc, s->c.macaddr.a); + + if (!pci_dev->qdev.hotplugged) { + static int loaded = 0; + if (!loaded) { + rom_add_option("pxe-ne2k_pci.bin", -1); + loaded = 1; + } + } + + add_boot_device_path(s->c.bootindex, &pci_dev->qdev, "/ethernet-phy@0"); + + return 0; +} + +static int pci_ne2000_exit(PCIDevice *pci_dev) +{ + PCINE2000State *d = DO_UPCAST(PCINE2000State, dev, pci_dev); + NE2000State *s = &d->ne2000; + + qemu_del_vlan_client(&s->nic->nc); + return 0; +} + +static PCIDeviceInfo ne2000_info = { + .qdev.name = "ne2k_pci", + .qdev.size = sizeof(PCINE2000State), + .qdev.vmsd = &vmstate_pci_ne2000, + .init = pci_ne2000_init, + .exit = pci_ne2000_exit, + .qdev.props = (Property[]) { + DEFINE_NIC_PROPERTIES(PCINE2000State, ne2000.c), + DEFINE_PROP_END_OF_LIST(), + } +}; + +static void ne2000_register_devices(void) +{ + pci_qdev_register(&ne2000_info); +} + +device_init(ne2000_register_devices) diff --git a/hw/ne2000.h b/hw/ne2000.h new file mode 100644 index 0000000..54fdfca --- /dev/null +++ b/hw/ne2000.h @@ -0,0 +1,39 @@ +#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 { + 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_ioport_write(void *opaque, uint32_t addr, uint32_t val); +uint32_t ne2000_ioport_read(void *opaque, uint32_t addr); +void ne2000_asic_ioport_write(void *opaque, uint32_t addr, uint32_t val); +uint32_t ne2000_asic_ioport_read(void *opaque, uint32_t addr); +void ne2000_reset_ioport_write(void *opaque, uint32_t addr, uint32_t val); +uint32_t ne2000_reset_ioport_read(void *opaque, uint32_t addr); +extern const VMStateDescription vmstate_ne2000; +void ne2000_reset(NE2000State *s); +int ne2000_can_receive(VLANClientState *vc); +ssize_t ne2000_receive(VLANClientState *vc, const uint8_t *buf, size_t size_); diff --git a/hw/nseries.c b/hw/nseries.c new file mode 100644 index 0000000..2f6f473 --- /dev/null +++ b/hw/nseries.c @@ -0,0 +1,1417 @@ +/* + * Nokia N-series internet tablets. + * + * Copyright (C) 2007 Nokia Corporation + * Written by Andrzej Zaborowski + * + * 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 . + */ + +#include "qemu-common.h" +#include "sysemu.h" +#include "omap.h" +#include "arm-misc.h" +#include "irq.h" +#include "console.h" +#include "boards.h" +#include "i2c.h" +#include "devices.h" +#include "flash.h" +#include "hw.h" +#include "bt.h" +#include "loader.h" + +/* Nokia N8x0 support */ +struct n800_s { + struct omap_mpu_state_s *cpu; + + struct rfbi_chip_s blizzard; + struct { + void *opaque; + uint32_t (*txrx)(void *opaque, uint32_t value, int len); + uWireSlave *chip; + } ts; + i2c_bus *i2c; + + int keymap[0x80]; + i2c_slave *kbd; + + TUSBState *usb; + void *retu; + void *tahvo; + void *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); + + printf("%s: MMC slot %i active\n", __FUNCTION__, level + 1); +} + +static void n8x0_gpio_setup(struct n800_s *s) +{ + qemu_irq *mmc_cs = qemu_allocate_irqs(n800_mmc_cs_cb, s->cpu->mmc, 1); + omap2_gpio_out_set(s->cpu->gpif, N8X0_MMC_CS_GPIO, mmc_cs[0]); + + qemu_irq_lower(omap2_gpio_in_get(s->cpu->gpif, N800_BAT_COVER_GPIO)[0]); +} + +#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; + + /* Either ec40xx or ec48xx are OK for the ID */ + omap_gpmc_attach(s->cpu->gpmc, N8X0_ONENAND_CS, 0, onenand_base_update, + onenand_base_unmap, + (s->nand = onenand_init(0xec4800, 1, + omap2_gpio_in_get(s->cpu->gpif, + N8X0_ONENAND_GPIO)[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 void n8x0_i2c_setup(struct n800_s *s) +{ + DeviceState *dev; + qemu_irq tmp_irq = omap2_gpio_in_get(s->cpu->gpif, N8X0_TMP105_GPIO)[0]; + + /* Attach the CPU on one end of our I2C bus. */ + s->i2c = omap_i2c_bus(s->cpu->i2c[0]); + + /* Attach a menelaus PM chip */ + dev = i2c_create_slave(s->i2c, "twl92230", N8X0_MENELAUS_ADDR); + qdev_connect_gpio_out(dev, 3, s->cpu->irq[0][OMAP_INT_24XX_SYS_NIRQ]); + + /* Attach a TMP105 PM chip (A0 wired to ground) */ + dev = i2c_create_slave(s->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 = omap2_gpio_in_get(s->cpu->gpif, N800_TSC_KP_IRQ_GPIO)[0]; + qemu_irq dav = omap2_gpio_in_get(s->cpu->gpif, N800_TSC_TS_GPIO)[0]; + + 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 = omap2_gpio_in_get(s->cpu->gpif, N810_TSC_TS_GPIO)[0]; + + 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 = omap2_gpio_in_get(s->cpu->gpif, N810_KEYBOARD_GPIO)[0]; + DeviceState *dev; + 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. */ + dev = i2c_create_slave(s->i2c, "lm8323", N810_LM8323_ADDR); + qdev_connect_gpio_out(dev, 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) +{ + if (!s->sleep) + fprintf(stderr, "%s: Display off\n", __FUNCTION__); + + 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 = ffs(s->param[0] & 0xf) - 1; + else if (s->pm < 0) + s->pm = 1; + break; + + case 0x28: /* DISPOFF */ + s->onoff = 0; + fprintf(stderr, "%s: Display off\n", __FUNCTION__); + break; + case 0x29: /* DISPON */ + s->onoff = 1; + fprintf(stderr, "%s: Display on\n", __FUNCTION__); + 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: + fprintf(stderr, "%s: unknown command %02x\n", __FUNCTION__, s->cmd); + break; + } + + return ret; +} + +static void *mipid_init(void) +{ + struct mipid_s *s = (struct mipid_s *) qemu_mallocz(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->cpu->mcspi[0], s->ts.txrx, tsc, 0); + omap_mcspi_attach(s->cpu->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(qemu_malloc(800 * 480 * 2), 0xff, 800 * 480 * 2); + /* Display Memory Data Port */ + chip->block(chip->opaque, 1, fb_blank, 800 * 480 * 2, 800); + qemu_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->cpu->dss, 0, &s->blizzard); +} + +static void n8x0_cbus_setup(struct n800_s *s) +{ + qemu_irq dat_out = omap2_gpio_in_get(s->cpu->gpif, N8X0_CBUS_DAT_GPIO)[0]; + qemu_irq retu_irq = omap2_gpio_in_get(s->cpu->gpif, N8X0_RETU_GPIO)[0]; + qemu_irq tahvo_irq = omap2_gpio_in_get(s->cpu->gpif, N8X0_TAHVO_GPIO)[0]; + + CBus *cbus = cbus_init(dat_out); + + omap2_gpio_out_set(s->cpu->gpif, N8X0_CBUS_CLK_GPIO, cbus->clk); + omap2_gpio_out_set(s->cpu->gpif, N8X0_CBUS_DAT_GPIO, cbus->dat); + omap2_gpio_out_set(s->cpu->gpif, 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( + omap2_gpio_in_get(s->cpu->gpif, + N8X0_BT_HOST_WKUP_GPIO)[0]); + + omap2_gpio_out_set(s->cpu->gpif, N8X0_BT_RESET_GPIO, + csrhci_pins_get(radio)[csrhci_pin_reset]); + omap2_gpio_out_set(s->cpu->gpif, N8X0_BT_WKUP_GPIO, + csrhci_pins_get(radio)[csrhci_pin_wakeup]); + + omap_uart_attach(s->cpu->uart[BT_UART], radio); +} + +static void n8x0_usb_power_cb(void *opaque, int line, int level) +{ + struct n800_s *s = opaque; + + tusb6010_power(s->usb, level); +} + +static void n8x0_usb_setup(struct n800_s *s) +{ + qemu_irq tusb_irq = omap2_gpio_in_get(s->cpu->gpif, N8X0_TUSB_INT_GPIO)[0]; + qemu_irq tusb_pwr = qemu_allocate_irqs(n8x0_usb_power_cb, s, 1)[0]; + TUSBState *tusb = tusb6010_init(tusb_irq); + + /* Using the NOR interface */ + omap_gpmc_attach(s->cpu->gpmc, N8X0_USB_ASYNC_CS, + tusb6010_async_io(tusb), NULL, NULL, tusb); + omap_gpmc_attach(s->cpu->gpmc, N8X0_USB_SYNC_CS, + tusb6010_sync_io(tusb), NULL, NULL, tusb); + + s->usb = tusb; + omap2_gpio_out_set(s->cpu->gpif, N8X0_TUSB_ENABLE_GPIO, 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_raw(p + 10, 0x04f70000); + strcpy((void *) (p + 9), "RX-34"); + + /* RAM size in MB? */ + stl_raw(p + 12, 0x80); + + /* Pointer to the list of tags */ + stl_raw(p + 13, OMAP2_SRAM_BASE + 0x9000); + + /* The NOLO tags start here */ + p = sram_base + 0x9000; +#define ADD_TAG(tag, len) \ + stw_raw((uint16_t *) p + 0, tag); \ + stw_raw((uint16_t *) p + 1, len); p ++; \ + stl_raw(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_raw(v ++, n800_pinout[i]); + + /* Kernel memsize? */ + ADD_TAG(0x6e05, 1); + stl_raw(v ++, 2); + + /* NOLO serial console */ + ADD_TAG(0x6e02, 4); + stl_raw(v ++, XLDR_LL_UART); /* UART number (1 - 3) */ + +#if 0 + /* CBUS settings (Retu/AVilma) */ + ADD_TAG(0x6e03, 6); + stw_raw((uint16_t *) v + 0, 65); /* CBUS GPIO0 */ + stw_raw((uint16_t *) v + 1, 66); /* CBUS GPIO1 */ + stw_raw((uint16_t *) v + 2, 64); /* CBUS GPIO2 */ + v += 2; +#endif + + /* Nokia ASIC BB5 (Retu/Tahvo) */ + ADD_TAG(0x6e0a, 4); + stw_raw((uint16_t *) v + 0, 111); /* "Retu" interrupt GPIO */ + stw_raw((uint16_t *) v + 1, 108); /* "Tahvo" interrupt GPIO */ + v ++; + + /* LCD console? */ + ADD_TAG(0x6e04, 4); + stw_raw((uint16_t *) v + 0, 30); /* ??? */ + stw_raw((uint16_t *) v + 1, 24); /* ??? */ + v ++; + +#if 0 + /* LCD settings */ + ADD_TAG(0x6e06, 2); + stw_raw((uint16_t *) (v ++), 15); /* ??? */ +#endif + + /* I^2C (Menelaus) */ + ADD_TAG(0x6e07, 4); + stl_raw(v ++, 0x00720000); /* ??? */ + + /* Unknown */ + ADD_TAG(0x6e0b, 6); + stw_raw((uint16_t *) v + 0, 94); /* ??? */ + stw_raw((uint16_t *) v + 1, 23); /* ??? */ + stw_raw((uint16_t *) v + 2, 0); /* ??? */ + v += 2; + + /* OMAP gpio switch info */ + ADD_TAG(0x6e0c, 80); + strcpy((void *) v, "bat_cover"); v += 3; + stw_raw((uint16_t *) v + 0, 110); /* GPIO num ??? */ + stw_raw((uint16_t *) v + 1, 1); /* GPIO num ??? */ + v += 2; + strcpy((void *) v, "cam_act"); v += 3; + stw_raw((uint16_t *) v + 0, 95); /* GPIO num ??? */ + stw_raw((uint16_t *) v + 1, 32); /* GPIO num ??? */ + v += 2; + strcpy((void *) v, "cam_turn"); v += 3; + stw_raw((uint16_t *) v + 0, 12); /* GPIO num ??? */ + stw_raw((uint16_t *) v + 1, 33); /* GPIO num ??? */ + v += 2; + strcpy((void *) v, "headphone"); v += 3; + stw_raw((uint16_t *) v + 0, 107); /* GPIO num ??? */ + stw_raw((uint16_t *) v + 1, 17); /* GPIO num ??? */ + v += 2; + + /* Bluetooth */ + ADD_TAG(0x6e0e, 12); + stl_raw(v ++, 0x5c623d01); /* ??? */ + stl_raw(v ++, 0x00000201); /* ??? */ + stl_raw(v ++, 0x00000000); /* ??? */ + + /* CX3110x WLAN settings */ + ADD_TAG(0x6e0f, 8); + stl_raw(v ++, 0x00610025); /* ??? */ + stl_raw(v ++, 0xffff0057); /* ??? */ + + /* MMC host settings */ + ADD_TAG(0x6e10, 12); + stl_raw(v ++, 0xffff000f); /* ??? */ + stl_raw(v ++, 0xffffffff); /* ??? */ + stl_raw(v ++, 0x00000060); /* ??? */ + + /* OneNAND chip select */ + ADD_TAG(0x6e11, 10); + stl_raw(v ++, 0x00000401); /* ??? */ + stl_raw(v ++, 0x0002003a); /* ??? */ + stl_raw(v ++, 0x00000002); /* ??? */ + + /* TEA5761 sensor settings */ + ADD_TAG(0x6e12, 2); + stl_raw(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_raw(p ++, 0x00000000); + stl_raw(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 */ + (void *) &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, (void *) &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->cpu->env->GE = 0x5; + + /* If the machine has a slided keyboard, open it */ + if (s->kbd) + qemu_irq_raise(omap2_gpio_in_get(s->cpu->gpif, N810_SLIDE_GPIO)[0]); +} + +#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_raw(w ++, OMAP_TAG_UART); /* u16 tag */ + stw_raw(w ++, 4); /* u16 len */ + stw_raw(w ++, (1 << 2) | (1 << 1) | (1 << 0)); /* uint enabled_uarts */ + w ++; + +#if 0 + stw_raw(w ++, OMAP_TAG_SERIAL_CONSOLE); /* u16 tag */ + stw_raw(w ++, 4); /* u16 len */ + stw_raw(w ++, XLDR_LL_UART + 1); /* u8 console_uart */ + stw_raw(w ++, 115200); /* u32 console_speed */ +#endif + + stw_raw(w ++, OMAP_TAG_LCD); /* u16 tag */ + stw_raw(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_raw(w ++, N810_BLIZZARD_RESET_GPIO); /* TODO: n800 s16 nreset_gpio */ + stw_raw(w ++, 24); /* u8 data_lines */ + + stw_raw(w ++, OMAP_TAG_CBUS); /* u16 tag */ + stw_raw(w ++, 8); /* u16 len */ + stw_raw(w ++, N8X0_CBUS_CLK_GPIO); /* s16 clk_gpio */ + stw_raw(w ++, N8X0_CBUS_DAT_GPIO); /* s16 dat_gpio */ + stw_raw(w ++, N8X0_CBUS_SEL_GPIO); /* s16 sel_gpio */ + w ++; + + stw_raw(w ++, OMAP_TAG_EM_ASIC_BB5); /* u16 tag */ + stw_raw(w ++, 4); /* u16 len */ + stw_raw(w ++, N8X0_RETU_GPIO); /* s16 retu_irq_gpio */ + stw_raw(w ++, N8X0_TAHVO_GPIO); /* s16 tahvo_irq_gpio */ + + gpiosw = (model == 810) ? n810_gpiosw_info : n800_gpiosw_info; + for (; gpiosw->name; gpiosw ++) { + stw_raw(w ++, OMAP_TAG_GPIO_SWITCH); /* u16 tag */ + stw_raw(w ++, 20); /* u16 len */ + strcpy((void *) w, gpiosw->name); /* char name[12] */ + w += 6; + stw_raw(w ++, gpiosw->line); /* u16 gpio */ + stw_raw(w ++, gpiosw->type); + stw_raw(w ++, 0); + stw_raw(w ++, 0); + } + + stw_raw(w ++, OMAP_TAG_NOKIA_BT); /* u16 tag */ + stw_raw(w ++, 12); /* u16 len */ + b = (void *) w; + stb_raw(b ++, 0x01); /* u8 chip_type (CSR) */ + stb_raw(b ++, N8X0_BT_WKUP_GPIO); /* u8 bt_wakeup_gpio */ + stb_raw(b ++, N8X0_BT_HOST_WKUP_GPIO); /* u8 host_wakeup_gpio */ + stb_raw(b ++, N8X0_BT_RESET_GPIO); /* u8 reset_gpio */ + stb_raw(b ++, BT_UART + 1); /* u8 bt_uart */ + memcpy(b, &n8x0_bd_addr, 6); /* u8 bd_addr[6] */ + b += 6; + stb_raw(b ++, 0x02); /* u8 bt_sysclk (38.4) */ + w = (void *) b; + + stw_raw(w ++, OMAP_TAG_WLAN_CX3110X); /* u16 tag */ + stw_raw(w ++, 8); /* u16 len */ + stw_raw(w ++, 0x25); /* u8 chip_type */ + stw_raw(w ++, N8X0_WLAN_PWR_GPIO); /* s16 power_gpio */ + stw_raw(w ++, N8X0_WLAN_IRQ_GPIO); /* s16 irq_gpio */ + stw_raw(w ++, -1); /* s16 spi_cs_gpio */ + + stw_raw(w ++, OMAP_TAG_MMC); /* u16 tag */ + stw_raw(w ++, 16); /* u16 len */ + if (model == 810) { + stw_raw(w ++, 0x23f); /* unsigned flags */ + stw_raw(w ++, -1); /* s16 power_pin */ + stw_raw(w ++, -1); /* s16 switch_pin */ + stw_raw(w ++, -1); /* s16 wp_pin */ + stw_raw(w ++, 0x240); /* unsigned flags */ + stw_raw(w ++, 0xc000); /* s16 power_pin */ + stw_raw(w ++, 0x0248); /* s16 switch_pin */ + stw_raw(w ++, 0xc000); /* s16 wp_pin */ + } else { + stw_raw(w ++, 0xf); /* unsigned flags */ + stw_raw(w ++, -1); /* s16 power_pin */ + stw_raw(w ++, -1); /* s16 switch_pin */ + stw_raw(w ++, -1); /* s16 wp_pin */ + stw_raw(w ++, 0); /* unsigned flags */ + stw_raw(w ++, 0); /* s16 power_pin */ + stw_raw(w ++, 0); /* s16 switch_pin */ + stw_raw(w ++, 0); /* s16 wp_pin */ + } + + stw_raw(w ++, OMAP_TAG_TEA5761); /* u16 tag */ + stw_raw(w ++, 4); /* u16 len */ + stw_raw(w ++, N8X0_TEA5761_CS_GPIO); /* u16 enable_gpio */ + w ++; + + partition = (model == 810) ? n810_part_info : n800_part_info; + for (; partition->name; partition ++) { + stw_raw(w ++, OMAP_TAG_PARTITION); /* u16 tag */ + stw_raw(w ++, 28); /* u16 len */ + strcpy((void *) w, partition->name); /* char name[16] */ + l = (void *) (w + 8); + stl_raw(l ++, partition->size); /* unsigned int size */ + stl_raw(l ++, partition->offset); /* unsigned int offset */ + stl_raw(l ++, partition->mask); /* unsigned int mask_flags */ + w = (void *) l; + } + + stw_raw(w ++, OMAP_TAG_BOOT_REASON); /* u16 tag */ + stw_raw(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_raw(w ++, OMAP_TAG_VERSION_STR); /* u16 tag */ + stw_raw(w ++, 24); /* u16 len */ + strcpy((void *) w, "product"); /* char component[12] */ + w += 6; + strcpy((void *) w, tag); /* char version[12] */ + w += 6; + + stw_raw(w ++, OMAP_TAG_VERSION_STR); /* u16 tag */ + stw_raw(w ++, 24); /* u16 len */ + strcpy((void *) w, "hw-build"); /* char component[12] */ + w += 6; + strcpy((void *) w, "QEMU " QEMU_VERSION); /* char version[12] */ + w += 6; + + tag = (model == 810) ? "1.1.10-qemu" : "1.1.6-qemu"; + stw_raw(w ++, OMAP_TAG_VERSION_STR); /* u16 tag */ + stw_raw(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(struct arm_boot_info *info, void *p) +{ + return n8x0_atag_setup(p, 800); +} + +static int n810_atag_setup(struct arm_boot_info *info, void *p) +{ + return n8x0_atag_setup(p, 810); +} + +static void n8x0_init(ram_addr_t ram_size, const char *boot_device, + const char *kernel_filename, + const char *kernel_cmdline, const char *initrd_filename, + const char *cpu_model, struct arm_boot_info *binfo, int model) +{ + struct n800_s *s = (struct n800_s *) qemu_mallocz(sizeof(*s)); + int sdram_size = binfo->ram_size; + DisplayState *ds; + + s->cpu = omap2420_mpu_init(sdram_size, 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 (kernel_filename) { + /* Or at the linux loader. */ + binfo->kernel_filename = kernel_filename; + binfo->kernel_cmdline = kernel_cmdline; + binfo->initrd_filename = initrd_filename; + arm_load_kernel(s->cpu->env, binfo); + + qemu_register_reset(n8x0_boot_init, s); + } + + if (option_rom[0].name && (boot_device[0] == 'n' || !kernel_filename)) { + int rom_size; + uint8_t nolo_tags[0x10000]; + /* No, wait, better start at the ROM. */ + s->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. */ + rom_size = load_image_targphys(option_rom[0].name, + OMAP2_Q2_BASE + 0x400000, + sdram_size - 0x400000); + printf("%i bytes of image loaded\n", rom_size); + + n800_setup_nolo_tags(nolo_tags); + cpu_physical_memory_write(OMAP2_SRAM_BASE, nolo_tags, 0x10000); + } + /* FIXME: We shouldn't really be doing this here. The LCD controller + will set the size once configured, so this just sets an initial + size until the guest activates the display. */ + ds = get_displaystate(); + ds->surface = qemu_resize_displaysurface(ds, 800, 480); + dpy_resize(ds); +} + +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(ram_addr_t ram_size, + const char *boot_device, + const char *kernel_filename, const char *kernel_cmdline, + const char *initrd_filename, const char *cpu_model) +{ + return n8x0_init(ram_size, boot_device, + kernel_filename, kernel_cmdline, initrd_filename, + cpu_model, &n800_binfo, 800); +} + +static void n810_init(ram_addr_t ram_size, + const char *boot_device, + const char *kernel_filename, const char *kernel_cmdline, + const char *initrd_filename, const char *cpu_model) +{ + return n8x0_init(ram_size, boot_device, + kernel_filename, kernel_cmdline, initrd_filename, + cpu_model, &n810_binfo, 810); +} + +static QEMUMachine n800_machine = { + .name = "n800", + .desc = "Nokia N800 tablet aka. RX-34 (OMAP2420)", + .init = n800_init, +}; + +static QEMUMachine n810_machine = { + .name = "n810", + .desc = "Nokia N810 tablet aka. RX-44 (OMAP2420)", + .init = n810_init, +}; + +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/nvram.h b/hw/nvram.h new file mode 100644 index 0000000..0f55b24 --- /dev/null +++ b/hw/nvram.h @@ -0,0 +1,42 @@ +#ifndef NVRAM_H +#define NVRAM_H + +/* NVRAM helpers */ +typedef uint32_t (*nvram_read_t)(void *private, uint32_t addr); +typedef void (*nvram_write_t)(void *private, uint32_t addr, uint32_t val); +typedef struct nvram_t { + void *opaque; + nvram_read_t read_fn; + nvram_write_t write_fn; +} nvram_t; + +void NVRAM_set_byte (nvram_t *nvram, uint32_t addr, uint8_t value); +uint8_t NVRAM_get_byte (nvram_t *nvram, uint32_t addr); +void NVRAM_set_word (nvram_t *nvram, uint32_t addr, uint16_t value); +uint16_t NVRAM_get_word (nvram_t *nvram, uint32_t addr); +void NVRAM_set_lword (nvram_t *nvram, uint32_t addr, uint32_t value); +uint32_t NVRAM_get_lword (nvram_t *nvram, uint32_t addr); +void NVRAM_set_string (nvram_t *nvram, uint32_t addr, + const char *str, uint32_t max); +int NVRAM_get_string (nvram_t *nvram, uint8_t *dst, uint16_t addr, int max); +void NVRAM_set_crc (nvram_t *nvram, uint32_t addr, + uint32_t start, uint32_t count); +int PPC_NVRAM_set_params (nvram_t *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); +typedef struct M48t59State M48t59State; + +void m48t59_write (void *private, uint32_t addr, uint32_t val); +uint32_t m48t59_read (void *private, uint32_t addr); +void m48t59_toggle_lock (void *private, int lock); +M48t59State *m48t59_init_isa(uint32_t io_base, uint16_t size, int type); +M48t59State *m48t59_init(qemu_irq IRQ, target_phys_addr_t mem_base, + uint32_t io_base, uint16_t size, int type); +void m48t59_set_addr (void *opaque, uint32_t addr); + +#endif /* !NVRAM_H */ diff --git a/hw/omap.h b/hw/omap.h new file mode 100644 index 0000000..c227a82 --- /dev/null +++ b/hw/omap.h @@ -0,0 +1,1141 @@ +/* + * Texas Instruments OMAP processors. + * + * Copyright (C) 2006-2008 Andrzej Zaborowski + * + * 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 . + */ +#ifndef hw_omap_h +# define hw_omap_h "omap.h" + +# define OMAP_EMIFS_BASE 0x00000000 +# define OMAP2_Q0_BASE 0x00000000 +# define OMAP_CS0_BASE 0x00000000 +# define OMAP_CS1_BASE 0x04000000 +# define OMAP_CS2_BASE 0x08000000 +# define OMAP_CS3_BASE 0x0c000000 +# define OMAP_EMIFF_BASE 0x10000000 +# define OMAP_IMIF_BASE 0x20000000 +# define OMAP_LOCALBUS_BASE 0x30000000 +# define OMAP2_Q1_BASE 0x40000000 +# define OMAP2_L4_BASE 0x48000000 +# define OMAP2_SRAM_BASE 0x40200000 +# define OMAP2_L3_BASE 0x68000000 +# define OMAP2_Q2_BASE 0x80000000 +# define OMAP2_Q3_BASE 0xc0000000 +# define OMAP_MPUI_BASE 0xe1000000 + +# define OMAP730_SRAM_SIZE 0x00032000 +# define OMAP15XX_SRAM_SIZE 0x00030000 +# define OMAP16XX_SRAM_SIZE 0x00004000 +# define OMAP1611_SRAM_SIZE 0x0003e800 +# define OMAP242X_SRAM_SIZE 0x000a0000 +# define OMAP243X_SRAM_SIZE 0x00010000 +# define OMAP_CS0_SIZE 0x04000000 +# define OMAP_CS1_SIZE 0x04000000 +# define OMAP_CS2_SIZE 0x04000000 +# define OMAP_CS3_SIZE 0x04000000 + +/* omap_clk.c */ +struct omap_mpu_state_s; +typedef struct clk *omap_clk; +omap_clk omap_findclk(struct omap_mpu_state_s *mpu, const char *name); +void omap_clk_init(struct omap_mpu_state_s *mpu); +void omap_clk_adduser(struct clk *clk, qemu_irq user); +void omap_clk_get(omap_clk clk); +void omap_clk_put(omap_clk clk); +void omap_clk_onoff(omap_clk clk, int on); +void omap_clk_canidle(omap_clk clk, int can); +void omap_clk_setrate(omap_clk clk, int divide, int multiply); +int64_t omap_clk_getrate(omap_clk clk); +void omap_clk_reparent(omap_clk clk, omap_clk parent); + +/* OMAP2 l4 Interconnect */ +struct omap_l4_s; +struct omap_l4_region_s { + target_phys_addr_t offset; + size_t size; + int access; +}; +struct omap_l4_agent_info_s { + int ta; + int region; + int regions; + int ta_region; +}; +struct omap_target_agent_s { + struct omap_l4_s *bus; + int regions; + const struct omap_l4_region_s *start; + target_phys_addr_t base; + uint32_t component; + uint32_t control; + uint32_t status; +}; +struct omap_l4_s *omap_l4_init(target_phys_addr_t base, int ta_num); + +struct omap_target_agent_s; +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); +target_phys_addr_t omap_l4_attach(struct omap_target_agent_s *ta, int region, + int iotype); +int l4_register_io_memory(CPUReadMemoryFunc * const *mem_read, + CPUWriteMemoryFunc * const *mem_write, void *opaque); + +/* OMAP interrupt controller */ +struct omap_intr_handler_s; +struct omap_intr_handler_s *omap_inth_init(target_phys_addr_t base, + unsigned long size, unsigned char nbanks, qemu_irq **pins, + qemu_irq parent_irq, qemu_irq parent_fiq, omap_clk clk); +struct omap_intr_handler_s *omap2_inth_init(target_phys_addr_t base, + int size, int nbanks, qemu_irq **pins, + qemu_irq parent_irq, qemu_irq parent_fiq, + omap_clk fclk, omap_clk iclk); +void omap_inth_reset(struct omap_intr_handler_s *s); +qemu_irq omap_inth_get_pin(struct omap_intr_handler_s *s, int n); + +/* OMAP2 SDRAM controller */ +struct omap_sdrc_s; +struct omap_sdrc_s *omap_sdrc_init(target_phys_addr_t base); +void omap_sdrc_reset(struct omap_sdrc_s *s); + +/* OMAP2 general purpose memory controller */ +struct omap_gpmc_s; +struct omap_gpmc_s *omap_gpmc_init(target_phys_addr_t base, qemu_irq irq); +void omap_gpmc_reset(struct omap_gpmc_s *s); +void omap_gpmc_attach(struct omap_gpmc_s *s, int cs, int iomemtype, + void (*base_upd)(void *opaque, target_phys_addr_t new), + void (*unmap)(void *opaque), void *opaque); + +/* + * Common IRQ numbers for level 1 interrupt handler + * See /usr/include/asm-arm/arch-omap/irqs.h in Linux. + */ +# define OMAP_INT_CAMERA 1 +# define OMAP_INT_FIQ 3 +# define OMAP_INT_RTDX 6 +# define OMAP_INT_DSP_MMU_ABORT 7 +# define OMAP_INT_HOST 8 +# define OMAP_INT_ABORT 9 +# define OMAP_INT_BRIDGE_PRIV 13 +# define OMAP_INT_GPIO_BANK1 14 +# define OMAP_INT_UART3 15 +# define OMAP_INT_TIMER3 16 +# define OMAP_INT_DMA_CH0_6 19 +# define OMAP_INT_DMA_CH1_7 20 +# define OMAP_INT_DMA_CH2_8 21 +# define OMAP_INT_DMA_CH3 22 +# define OMAP_INT_DMA_CH4 23 +# define OMAP_INT_DMA_CH5 24 +# define OMAP_INT_DMA_LCD 25 +# define OMAP_INT_TIMER1 26 +# define OMAP_INT_WD_TIMER 27 +# define OMAP_INT_BRIDGE_PUB 28 +# define OMAP_INT_TIMER2 30 +# define OMAP_INT_LCD_CTRL 31 + +/* + * Common OMAP-15xx IRQ numbers for level 1 interrupt handler + */ +# define OMAP_INT_15XX_IH2_IRQ 0 +# define OMAP_INT_15XX_LB_MMU 17 +# define OMAP_INT_15XX_LOCAL_BUS 29 + +/* + * OMAP-1510 specific IRQ numbers for level 1 interrupt handler + */ +# define OMAP_INT_1510_SPI_TX 4 +# define OMAP_INT_1510_SPI_RX 5 +# define OMAP_INT_1510_DSP_MAILBOX1 10 +# define OMAP_INT_1510_DSP_MAILBOX2 11 + +/* + * OMAP-310 specific IRQ numbers for level 1 interrupt handler + */ +# define OMAP_INT_310_McBSP2_TX 4 +# define OMAP_INT_310_McBSP2_RX 5 +# define OMAP_INT_310_HSB_MAILBOX1 12 +# define OMAP_INT_310_HSAB_MMU 18 + +/* + * OMAP-1610 specific IRQ numbers for level 1 interrupt handler + */ +# define OMAP_INT_1610_IH2_IRQ 0 +# define OMAP_INT_1610_IH2_FIQ 2 +# define OMAP_INT_1610_McBSP2_TX 4 +# define OMAP_INT_1610_McBSP2_RX 5 +# define OMAP_INT_1610_DSP_MAILBOX1 10 +# define OMAP_INT_1610_DSP_MAILBOX2 11 +# define OMAP_INT_1610_LCD_LINE 12 +# define OMAP_INT_1610_GPTIMER1 17 +# define OMAP_INT_1610_GPTIMER2 18 +# define OMAP_INT_1610_SSR_FIFO_0 29 + +/* + * OMAP-730 specific IRQ numbers for level 1 interrupt handler + */ +# define OMAP_INT_730_IH2_FIQ 0 +# define OMAP_INT_730_IH2_IRQ 1 +# define OMAP_INT_730_USB_NON_ISO 2 +# define OMAP_INT_730_USB_ISO 3 +# define OMAP_INT_730_ICR 4 +# define OMAP_INT_730_EAC 5 +# define OMAP_INT_730_GPIO_BANK1 6 +# define OMAP_INT_730_GPIO_BANK2 7 +# define OMAP_INT_730_GPIO_BANK3 8 +# define OMAP_INT_730_McBSP2TX 10 +# define OMAP_INT_730_McBSP2RX 11 +# define OMAP_INT_730_McBSP2RX_OVF 12 +# define OMAP_INT_730_LCD_LINE 14 +# define OMAP_INT_730_GSM_PROTECT 15 +# define OMAP_INT_730_TIMER3 16 +# define OMAP_INT_730_GPIO_BANK5 17 +# define OMAP_INT_730_GPIO_BANK6 18 +# define OMAP_INT_730_SPGIO_WR 29 + +/* + * Common IRQ numbers for level 2 interrupt handler + */ +# define OMAP_INT_KEYBOARD 1 +# define OMAP_INT_uWireTX 2 +# define OMAP_INT_uWireRX 3 +# define OMAP_INT_I2C 4 +# define OMAP_INT_MPUIO 5 +# define OMAP_INT_USB_HHC_1 6 +# define OMAP_INT_McBSP3TX 10 +# define OMAP_INT_McBSP3RX 11 +# define OMAP_INT_McBSP1TX 12 +# define OMAP_INT_McBSP1RX 13 +# define OMAP_INT_UART1 14 +# define OMAP_INT_UART2 15 +# define OMAP_INT_USB_W2FC 20 +# define OMAP_INT_1WIRE 21 +# define OMAP_INT_OS_TIMER 22 +# define OMAP_INT_OQN 23 +# define OMAP_INT_GAUGE_32K 24 +# define OMAP_INT_RTC_TIMER 25 +# define OMAP_INT_RTC_ALARM 26 +# define OMAP_INT_DSP_MMU 28 + +/* + * OMAP-1510 specific IRQ numbers for level 2 interrupt handler + */ +# define OMAP_INT_1510_BT_MCSI1TX 16 +# define OMAP_INT_1510_BT_MCSI1RX 17 +# define OMAP_INT_1510_SoSSI_MATCH 19 +# define OMAP_INT_1510_MEM_STICK 27 +# define OMAP_INT_1510_COM_SPI_RO 31 + +/* + * OMAP-310 specific IRQ numbers for level 2 interrupt handler + */ +# define OMAP_INT_310_FAC 0 +# define OMAP_INT_310_USB_HHC_2 7 +# define OMAP_INT_310_MCSI1_FE 16 +# define OMAP_INT_310_MCSI2_FE 17 +# define OMAP_INT_310_USB_W2FC_ISO 29 +# define OMAP_INT_310_USB_W2FC_NON_ISO 30 +# define OMAP_INT_310_McBSP2RX_OF 31 + +/* + * OMAP-1610 specific IRQ numbers for level 2 interrupt handler + */ +# define OMAP_INT_1610_FAC 0 +# define OMAP_INT_1610_USB_HHC_2 7 +# define OMAP_INT_1610_USB_OTG 8 +# define OMAP_INT_1610_SoSSI 9 +# define OMAP_INT_1610_BT_MCSI1TX 16 +# define OMAP_INT_1610_BT_MCSI1RX 17 +# define OMAP_INT_1610_SoSSI_MATCH 19 +# define OMAP_INT_1610_MEM_STICK 27 +# define OMAP_INT_1610_McBSP2RX_OF 31 +# define OMAP_INT_1610_STI 32 +# define OMAP_INT_1610_STI_WAKEUP 33 +# define OMAP_INT_1610_GPTIMER3 34 +# define OMAP_INT_1610_GPTIMER4 35 +# define OMAP_INT_1610_GPTIMER5 36 +# define OMAP_INT_1610_GPTIMER6 37 +# define OMAP_INT_1610_GPTIMER7 38 +# define OMAP_INT_1610_GPTIMER8 39 +# define OMAP_INT_1610_GPIO_BANK2 40 +# define OMAP_INT_1610_GPIO_BANK3 41 +# define OMAP_INT_1610_MMC2 42 +# define OMAP_INT_1610_CF 43 +# define OMAP_INT_1610_WAKE_UP_REQ 46 +# define OMAP_INT_1610_GPIO_BANK4 48 +# define OMAP_INT_1610_SPI 49 +# define OMAP_INT_1610_DMA_CH6 53 +# define OMAP_INT_1610_DMA_CH7 54 +# define OMAP_INT_1610_DMA_CH8 55 +# define OMAP_INT_1610_DMA_CH9 56 +# define OMAP_INT_1610_DMA_CH10 57 +# define OMAP_INT_1610_DMA_CH11 58 +# define OMAP_INT_1610_DMA_CH12 59 +# define OMAP_INT_1610_DMA_CH13 60 +# define OMAP_INT_1610_DMA_CH14 61 +# define OMAP_INT_1610_DMA_CH15 62 +# define OMAP_INT_1610_NAND 63 + +/* + * OMAP-730 specific IRQ numbers for level 2 interrupt handler + */ +# define OMAP_INT_730_HW_ERRORS 0 +# define OMAP_INT_730_NFIQ_PWR_FAIL 1 +# define OMAP_INT_730_CFCD 2 +# define OMAP_INT_730_CFIREQ 3 +# define OMAP_INT_730_I2C 4 +# define OMAP_INT_730_PCC 5 +# define OMAP_INT_730_MPU_EXT_NIRQ 6 +# define OMAP_INT_730_SPI_100K_1 7 +# define OMAP_INT_730_SYREN_SPI 8 +# define OMAP_INT_730_VLYNQ 9 +# define OMAP_INT_730_GPIO_BANK4 10 +# define OMAP_INT_730_McBSP1TX 11 +# define OMAP_INT_730_McBSP1RX 12 +# define OMAP_INT_730_McBSP1RX_OF 13 +# define OMAP_INT_730_UART_MODEM_IRDA_2 14 +# define OMAP_INT_730_UART_MODEM_1 15 +# define OMAP_INT_730_MCSI 16 +# define OMAP_INT_730_uWireTX 17 +# define OMAP_INT_730_uWireRX 18 +# define OMAP_INT_730_SMC_CD 19 +# define OMAP_INT_730_SMC_IREQ 20 +# define OMAP_INT_730_HDQ_1WIRE 21 +# define OMAP_INT_730_TIMER32K 22 +# define OMAP_INT_730_MMC_SDIO 23 +# define OMAP_INT_730_UPLD 24 +# define OMAP_INT_730_USB_HHC_1 27 +# define OMAP_INT_730_USB_HHC_2 28 +# define OMAP_INT_730_USB_GENI 29 +# define OMAP_INT_730_USB_OTG 30 +# define OMAP_INT_730_CAMERA_IF 31 +# define OMAP_INT_730_RNG 32 +# define OMAP_INT_730_DUAL_MODE_TIMER 33 +# define OMAP_INT_730_DBB_RF_EN 34 +# define OMAP_INT_730_MPUIO_KEYPAD 35 +# define OMAP_INT_730_SHA1_MD5 36 +# define OMAP_INT_730_SPI_100K_2 37 +# define OMAP_INT_730_RNG_IDLE 38 +# define OMAP_INT_730_MPUIO 39 +# define OMAP_INT_730_LLPC_LCD_CTRL_OFF 40 +# define OMAP_INT_730_LLPC_OE_FALLING 41 +# define OMAP_INT_730_LLPC_OE_RISING 42 +# define OMAP_INT_730_LLPC_VSYNC 43 +# define OMAP_INT_730_WAKE_UP_REQ 46 +# define OMAP_INT_730_DMA_CH6 53 +# define OMAP_INT_730_DMA_CH7 54 +# define OMAP_INT_730_DMA_CH8 55 +# define OMAP_INT_730_DMA_CH9 56 +# define OMAP_INT_730_DMA_CH10 57 +# define OMAP_INT_730_DMA_CH11 58 +# define OMAP_INT_730_DMA_CH12 59 +# define OMAP_INT_730_DMA_CH13 60 +# define OMAP_INT_730_DMA_CH14 61 +# define OMAP_INT_730_DMA_CH15 62 +# define OMAP_INT_730_NAND 63 + +/* + * OMAP-24xx common IRQ numbers + */ +# define OMAP_INT_24XX_STI 4 +# define OMAP_INT_24XX_SYS_NIRQ 7 +# define OMAP_INT_24XX_L3_IRQ 10 +# define OMAP_INT_24XX_PRCM_MPU_IRQ 11 +# define OMAP_INT_24XX_SDMA_IRQ0 12 +# define OMAP_INT_24XX_SDMA_IRQ1 13 +# define OMAP_INT_24XX_SDMA_IRQ2 14 +# define OMAP_INT_24XX_SDMA_IRQ3 15 +# define OMAP_INT_243X_MCBSP2_IRQ 16 +# define OMAP_INT_243X_MCBSP3_IRQ 17 +# define OMAP_INT_243X_MCBSP4_IRQ 18 +# define OMAP_INT_243X_MCBSP5_IRQ 19 +# define OMAP_INT_24XX_GPMC_IRQ 20 +# define OMAP_INT_24XX_GUFFAW_IRQ 21 +# define OMAP_INT_24XX_IVA_IRQ 22 +# define OMAP_INT_24XX_EAC_IRQ 23 +# define OMAP_INT_24XX_CAM_IRQ 24 +# define OMAP_INT_24XX_DSS_IRQ 25 +# define OMAP_INT_24XX_MAIL_U0_MPU 26 +# define OMAP_INT_24XX_DSP_UMA 27 +# define OMAP_INT_24XX_DSP_MMU 28 +# define OMAP_INT_24XX_GPIO_BANK1 29 +# define OMAP_INT_24XX_GPIO_BANK2 30 +# define OMAP_INT_24XX_GPIO_BANK3 31 +# define OMAP_INT_24XX_GPIO_BANK4 32 +# define OMAP_INT_243X_GPIO_BANK5 33 +# define OMAP_INT_24XX_MAIL_U3_MPU 34 +# define OMAP_INT_24XX_WDT3 35 +# define OMAP_INT_24XX_WDT4 36 +# define OMAP_INT_24XX_GPTIMER1 37 +# define OMAP_INT_24XX_GPTIMER2 38 +# define OMAP_INT_24XX_GPTIMER3 39 +# define OMAP_INT_24XX_GPTIMER4 40 +# define OMAP_INT_24XX_GPTIMER5 41 +# define OMAP_INT_24XX_GPTIMER6 42 +# define OMAP_INT_24XX_GPTIMER7 43 +# define OMAP_INT_24XX_GPTIMER8 44 +# define OMAP_INT_24XX_GPTIMER9 45 +# define OMAP_INT_24XX_GPTIMER10 46 +# define OMAP_INT_24XX_GPTIMER11 47 +# define OMAP_INT_24XX_GPTIMER12 48 +# define OMAP_INT_24XX_PKA_IRQ 50 +# define OMAP_INT_24XX_SHA1MD5_IRQ 51 +# define OMAP_INT_24XX_RNG_IRQ 52 +# define OMAP_INT_24XX_MG_IRQ 53 +# define OMAP_INT_24XX_I2C1_IRQ 56 +# define OMAP_INT_24XX_I2C2_IRQ 57 +# define OMAP_INT_24XX_MCBSP1_IRQ_TX 59 +# define OMAP_INT_24XX_MCBSP1_IRQ_RX 60 +# define OMAP_INT_24XX_MCBSP2_IRQ_TX 62 +# define OMAP_INT_24XX_MCBSP2_IRQ_RX 63 +# define OMAP_INT_243X_MCBSP1_IRQ 64 +# define OMAP_INT_24XX_MCSPI1_IRQ 65 +# define OMAP_INT_24XX_MCSPI2_IRQ 66 +# define OMAP_INT_24XX_SSI1_IRQ0 67 +# define OMAP_INT_24XX_SSI1_IRQ1 68 +# define OMAP_INT_24XX_SSI2_IRQ0 69 +# define OMAP_INT_24XX_SSI2_IRQ1 70 +# define OMAP_INT_24XX_SSI_GDD_IRQ 71 +# define OMAP_INT_24XX_UART1_IRQ 72 +# define OMAP_INT_24XX_UART2_IRQ 73 +# define OMAP_INT_24XX_UART3_IRQ 74 +# define OMAP_INT_24XX_USB_IRQ_GEN 75 +# define OMAP_INT_24XX_USB_IRQ_NISO 76 +# define OMAP_INT_24XX_USB_IRQ_ISO 77 +# define OMAP_INT_24XX_USB_IRQ_HGEN 78 +# define OMAP_INT_24XX_USB_IRQ_HSOF 79 +# define OMAP_INT_24XX_USB_IRQ_OTG 80 +# define OMAP_INT_24XX_VLYNQ_IRQ 81 +# define OMAP_INT_24XX_MMC_IRQ 83 +# define OMAP_INT_24XX_MS_IRQ 84 +# define OMAP_INT_24XX_FAC_IRQ 85 +# define OMAP_INT_24XX_MCSPI3_IRQ 91 +# define OMAP_INT_243X_HS_USB_MC 92 +# define OMAP_INT_243X_HS_USB_DMA 93 +# define OMAP_INT_243X_CARKIT 94 +# define OMAP_INT_34XX_GPTIMER12 95 + +/* omap_dma.c */ +enum omap_dma_model { + omap_dma_3_0, + omap_dma_3_1, + omap_dma_3_2, + omap_dma_4, +}; + +struct soc_dma_s; +struct soc_dma_s *omap_dma_init(target_phys_addr_t base, qemu_irq *irqs, + qemu_irq lcd_irq, struct omap_mpu_state_s *mpu, omap_clk clk, + enum omap_dma_model model); +struct soc_dma_s *omap_dma4_init(target_phys_addr_t base, qemu_irq *irqs, + struct omap_mpu_state_s *mpu, int fifo, + int chans, omap_clk iclk, omap_clk fclk); +void omap_dma_reset(struct soc_dma_s *s); + +struct dma_irq_map { + int ih; + int intr; +}; + +/* Only used in OMAP DMA 3.x gigacells */ +enum omap_dma_port { + emiff = 0, + emifs, + imif, /* omap16xx: ocp_t1 */ + tipb, + local, /* omap16xx: ocp_t2 */ + tipb_mpui, + __omap_dma_port_last, +}; + +typedef enum { + constant = 0, + post_incremented, + single_index, + double_index, +} omap_dma_addressing_t; + +/* Only used in OMAP DMA 3.x gigacells */ +struct omap_dma_lcd_channel_s { + enum omap_dma_port src; + target_phys_addr_t src_f1_top; + target_phys_addr_t src_f1_bottom; + target_phys_addr_t src_f2_top; + target_phys_addr_t src_f2_bottom; + + /* Used in OMAP DMA 3.2 gigacell */ + unsigned char brust_f1; + unsigned char pack_f1; + unsigned char data_type_f1; + unsigned char brust_f2; + unsigned char pack_f2; + unsigned char data_type_f2; + unsigned char end_prog; + unsigned char repeat; + unsigned char auto_init; + unsigned char priority; + unsigned char fs; + unsigned char running; + unsigned char bs; + unsigned char omap_3_1_compatible_disable; + unsigned char dst; + unsigned char lch_type; + int16_t element_index_f1; + int16_t element_index_f2; + int32_t frame_index_f1; + int32_t frame_index_f2; + uint16_t elements_f1; + uint16_t frames_f1; + uint16_t elements_f2; + uint16_t frames_f2; + omap_dma_addressing_t mode_f1; + omap_dma_addressing_t mode_f2; + + /* Destination port is fixed. */ + int interrupts; + int condition; + int dual; + + int current_frame; + target_phys_addr_t phys_framebuffer[2]; + qemu_irq irq; + struct omap_mpu_state_s *mpu; +} *omap_dma_get_lcdch(struct soc_dma_s *s); + +/* + * DMA request numbers for OMAP1 + * See /usr/include/asm-arm/arch-omap/dma.h in Linux. + */ +# define OMAP_DMA_NO_DEVICE 0 +# define OMAP_DMA_MCSI1_TX 1 +# define OMAP_DMA_MCSI1_RX 2 +# define OMAP_DMA_I2C_RX 3 +# define OMAP_DMA_I2C_TX 4 +# define OMAP_DMA_EXT_NDMA_REQ0 5 +# define OMAP_DMA_EXT_NDMA_REQ1 6 +# define OMAP_DMA_UWIRE_TX 7 +# define OMAP_DMA_MCBSP1_TX 8 +# define OMAP_DMA_MCBSP1_RX 9 +# define OMAP_DMA_MCBSP3_TX 10 +# define OMAP_DMA_MCBSP3_RX 11 +# define OMAP_DMA_UART1_TX 12 +# define OMAP_DMA_UART1_RX 13 +# define OMAP_DMA_UART2_TX 14 +# define OMAP_DMA_UART2_RX 15 +# define OMAP_DMA_MCBSP2_TX 16 +# define OMAP_DMA_MCBSP2_RX 17 +# define OMAP_DMA_UART3_TX 18 +# define OMAP_DMA_UART3_RX 19 +# define OMAP_DMA_CAMERA_IF_RX 20 +# define OMAP_DMA_MMC_TX 21 +# define OMAP_DMA_MMC_RX 22 +# define OMAP_DMA_NAND 23 /* Not in OMAP310 */ +# define OMAP_DMA_IRQ_LCD_LINE 24 /* Not in OMAP310 */ +# define OMAP_DMA_MEMORY_STICK 25 /* Not in OMAP310 */ +# define OMAP_DMA_USB_W2FC_RX0 26 +# define OMAP_DMA_USB_W2FC_RX1 27 +# define OMAP_DMA_USB_W2FC_RX2 28 +# define OMAP_DMA_USB_W2FC_TX0 29 +# define OMAP_DMA_USB_W2FC_TX1 30 +# define OMAP_DMA_USB_W2FC_TX2 31 + +/* These are only for 1610 */ +# define OMAP_DMA_CRYPTO_DES_IN 32 +# define OMAP_DMA_SPI_TX 33 +# define OMAP_DMA_SPI_RX 34 +# define OMAP_DMA_CRYPTO_HASH 35 +# define OMAP_DMA_CCP_ATTN 36 +# define OMAP_DMA_CCP_FIFO_NOT_EMPTY 37 +# define OMAP_DMA_CMT_APE_TX_CHAN_0 38 +# define OMAP_DMA_CMT_APE_RV_CHAN_0 39 +# define OMAP_DMA_CMT_APE_TX_CHAN_1 40 +# define OMAP_DMA_CMT_APE_RV_CHAN_1 41 +# define OMAP_DMA_CMT_APE_TX_CHAN_2 42 +# define OMAP_DMA_CMT_APE_RV_CHAN_2 43 +# define OMAP_DMA_CMT_APE_TX_CHAN_3 44 +# define OMAP_DMA_CMT_APE_RV_CHAN_3 45 +# define OMAP_DMA_CMT_APE_TX_CHAN_4 46 +# define OMAP_DMA_CMT_APE_RV_CHAN_4 47 +# define OMAP_DMA_CMT_APE_TX_CHAN_5 48 +# define OMAP_DMA_CMT_APE_RV_CHAN_5 49 +# define OMAP_DMA_CMT_APE_TX_CHAN_6 50 +# define OMAP_DMA_CMT_APE_RV_CHAN_6 51 +# define OMAP_DMA_CMT_APE_TX_CHAN_7 52 +# define OMAP_DMA_CMT_APE_RV_CHAN_7 53 +# define OMAP_DMA_MMC2_TX 54 +# define OMAP_DMA_MMC2_RX 55 +# define OMAP_DMA_CRYPTO_DES_OUT 56 + +/* + * DMA request numbers for the OMAP2 + */ +# define OMAP24XX_DMA_NO_DEVICE 0 +# define OMAP24XX_DMA_XTI_DMA 1 /* Not in OMAP2420 */ +# define OMAP24XX_DMA_EXT_DMAREQ0 2 +# define OMAP24XX_DMA_EXT_DMAREQ1 3 +# define OMAP24XX_DMA_GPMC 4 +# define OMAP24XX_DMA_GFX 5 /* Not in OMAP2420 */ +# define OMAP24XX_DMA_DSS 6 +# define OMAP24XX_DMA_VLYNQ_TX 7 /* Not in OMAP2420 */ +# define OMAP24XX_DMA_CWT 8 /* Not in OMAP2420 */ +# define OMAP24XX_DMA_AES_TX 9 /* Not in OMAP2420 */ +# define OMAP24XX_DMA_AES_RX 10 /* Not in OMAP2420 */ +# define OMAP24XX_DMA_DES_TX 11 /* Not in OMAP2420 */ +# define OMAP24XX_DMA_DES_RX 12 /* Not in OMAP2420 */ +# define OMAP24XX_DMA_SHA1MD5_RX 13 /* Not in OMAP2420 */ +# define OMAP24XX_DMA_EXT_DMAREQ2 14 +# define OMAP24XX_DMA_EXT_DMAREQ3 15 +# define OMAP24XX_DMA_EXT_DMAREQ4 16 +# define OMAP24XX_DMA_EAC_AC_RD 17 +# define OMAP24XX_DMA_EAC_AC_WR 18 +# define OMAP24XX_DMA_EAC_MD_UL_RD 19 +# define OMAP24XX_DMA_EAC_MD_UL_WR 20 +# define OMAP24XX_DMA_EAC_MD_DL_RD 21 +# define OMAP24XX_DMA_EAC_MD_DL_WR 22 +# define OMAP24XX_DMA_EAC_BT_UL_RD 23 +# define OMAP24XX_DMA_EAC_BT_UL_WR 24 +# define OMAP24XX_DMA_EAC_BT_DL_RD 25 +# define OMAP24XX_DMA_EAC_BT_DL_WR 26 +# define OMAP24XX_DMA_I2C1_TX 27 +# define OMAP24XX_DMA_I2C1_RX 28 +# define OMAP24XX_DMA_I2C2_TX 29 +# define OMAP24XX_DMA_I2C2_RX 30 +# define OMAP24XX_DMA_MCBSP1_TX 31 +# define OMAP24XX_DMA_MCBSP1_RX 32 +# define OMAP24XX_DMA_MCBSP2_TX 33 +# define OMAP24XX_DMA_MCBSP2_RX 34 +# define OMAP24XX_DMA_SPI1_TX0 35 +# define OMAP24XX_DMA_SPI1_RX0 36 +# define OMAP24XX_DMA_SPI1_TX1 37 +# define OMAP24XX_DMA_SPI1_RX1 38 +# define OMAP24XX_DMA_SPI1_TX2 39 +# define OMAP24XX_DMA_SPI1_RX2 40 +# define OMAP24XX_DMA_SPI1_TX3 41 +# define OMAP24XX_DMA_SPI1_RX3 42 +# define OMAP24XX_DMA_SPI2_TX0 43 +# define OMAP24XX_DMA_SPI2_RX0 44 +# define OMAP24XX_DMA_SPI2_TX1 45 +# define OMAP24XX_DMA_SPI2_RX1 46 + +# define OMAP24XX_DMA_UART1_TX 49 +# define OMAP24XX_DMA_UART1_RX 50 +# define OMAP24XX_DMA_UART2_TX 51 +# define OMAP24XX_DMA_UART2_RX 52 +# define OMAP24XX_DMA_UART3_TX 53 +# define OMAP24XX_DMA_UART3_RX 54 +# define OMAP24XX_DMA_USB_W2FC_TX0 55 +# define OMAP24XX_DMA_USB_W2FC_RX0 56 +# define OMAP24XX_DMA_USB_W2FC_TX1 57 +# define OMAP24XX_DMA_USB_W2FC_RX1 58 +# define OMAP24XX_DMA_USB_W2FC_TX2 59 +# define OMAP24XX_DMA_USB_W2FC_RX2 60 +# define OMAP24XX_DMA_MMC1_TX 61 +# define OMAP24XX_DMA_MMC1_RX 62 +# define OMAP24XX_DMA_MS 63 /* Not in OMAP2420 */ +# define OMAP24XX_DMA_EXT_DMAREQ5 64 + +/* omap[123].c */ +/* OMAP2 gp timer */ +struct omap_gp_timer_s; +struct omap_gp_timer_s *omap_gp_timer_init(struct omap_target_agent_s *ta, + qemu_irq irq, omap_clk fclk, omap_clk iclk); +void omap_gp_timer_reset(struct omap_gp_timer_s *s); + +/* OMAP2 sysctimer */ +struct omap_synctimer_s; +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); +void omap_synctimer_reset(struct omap_synctimer_s *s); + +struct omap_uart_s; +struct omap_uart_s *omap_uart_init(target_phys_addr_t 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 *omap2_uart_init(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); +void omap_uart_reset(struct omap_uart_s *s); +void omap_uart_attach(struct omap_uart_s *s, CharDriverState *chr); + +struct omap_mpuio_s; +struct omap_mpuio_s *omap_mpuio_init(target_phys_addr_t base, + qemu_irq kbd_int, qemu_irq gpio_int, qemu_irq wakeup, + omap_clk clk); +qemu_irq *omap_mpuio_in_get(struct omap_mpuio_s *s); +void omap_mpuio_out_set(struct omap_mpuio_s *s, int line, qemu_irq handler); +void omap_mpuio_key(struct omap_mpuio_s *s, int row, int col, int down); + +/* omap1 gpio module interface */ +struct omap_gpio_s; +struct omap_gpio_s *omap_gpio_init(target_phys_addr_t base, + qemu_irq irq, omap_clk clk); +void omap_gpio_reset(struct omap_gpio_s *s); +qemu_irq *omap_gpio_in_get(struct omap_gpio_s *s); +void omap_gpio_out_set(struct omap_gpio_s *s, int line, qemu_irq handler); + +/* omap2 gpio interface */ +struct omap_gpif_s; +struct omap_gpif_s *omap2_gpio_init(struct omap_target_agent_s *ta, + qemu_irq *irq, omap_clk *fclk, omap_clk iclk, int modules); +void omap_gpif_reset(struct omap_gpif_s *s); +qemu_irq *omap2_gpio_in_get(struct omap_gpif_s *s, int start); +void omap2_gpio_out_set(struct omap_gpif_s *s, int line, qemu_irq handler); + +struct uWireSlave { + uint16_t (*receive)(void *opaque); + void (*send)(void *opaque, uint16_t data); + void *opaque; +}; +struct omap_uwire_s; +struct omap_uwire_s *omap_uwire_init(target_phys_addr_t base, + qemu_irq *irq, qemu_irq dma, omap_clk clk); +void omap_uwire_attach(struct omap_uwire_s *s, + uWireSlave *slave, int chipselect); + +/* OMAP2 spi */ +struct omap_mcspi_s; +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); +void omap_mcspi_attach(struct omap_mcspi_s *s, + uint32_t (*txrx)(void *opaque, uint32_t, int), void *opaque, + int chipselect); +void omap_mcspi_reset(struct omap_mcspi_s *s); + +struct I2SCodec { + void *opaque; + + /* The CPU can call this if it is generating the clock signal on the + * i2s port. The CODEC can ignore it if it is set up as a clock + * master and generates its own clock. */ + void (*set_rate)(void *opaque, int in, int out); + + void (*tx_swallow)(void *opaque); + qemu_irq rx_swallow; + qemu_irq tx_start; + + int tx_rate; + int cts; + int rx_rate; + int rts; + + struct i2s_fifo_s { + uint8_t *fifo; + int len; + int start; + int size; + } in, out; +}; +struct omap_mcbsp_s; +struct omap_mcbsp_s *omap_mcbsp_init(target_phys_addr_t base, + qemu_irq *irq, qemu_irq *dma, omap_clk clk); +void omap_mcbsp_i2s_attach(struct omap_mcbsp_s *s, I2SCodec *slave); + +void omap_tap_init(struct omap_target_agent_s *ta, + struct omap_mpu_state_s *mpu); + +/* omap_lcdc.c */ +struct omap_lcd_panel_s; +void omap_lcdc_reset(struct omap_lcd_panel_s *s); +struct omap_lcd_panel_s *omap_lcdc_init(target_phys_addr_t base, qemu_irq irq, + struct omap_dma_lcd_channel_s *dma, + ram_addr_t imif_base, ram_addr_t emiff_base, omap_clk clk); + +/* omap_dss.c */ +struct rfbi_chip_s { + void *opaque; + void (*write)(void *opaque, int dc, uint16_t value); + void (*block)(void *opaque, int dc, void *buf, size_t len, int pitch); + uint16_t (*read)(void *opaque, int dc); +}; +struct omap_dss_s; +void omap_dss_reset(struct omap_dss_s *s); +struct omap_dss_s *omap_dss_init(struct omap_target_agent_s *ta, + target_phys_addr_t l3_base, + qemu_irq irq, qemu_irq drq, + omap_clk fck1, omap_clk fck2, omap_clk ck54m, + omap_clk ick1, omap_clk ick2); +void omap_rfbi_attach(struct omap_dss_s *s, int cs, struct rfbi_chip_s *chip); + +/* omap_mmc.c */ +struct omap_mmc_s; +struct omap_mmc_s *omap_mmc_init(target_phys_addr_t base, + BlockDriverState *bd, + qemu_irq irq, qemu_irq dma[], omap_clk clk); +struct omap_mmc_s *omap2_mmc_init(struct omap_target_agent_s *ta, + BlockDriverState *bd, qemu_irq irq, qemu_irq dma[], + omap_clk fclk, omap_clk iclk); +void omap_mmc_reset(struct omap_mmc_s *s); +void omap_mmc_handlers(struct omap_mmc_s *s, qemu_irq ro, qemu_irq cover); +void omap_mmc_enable(struct omap_mmc_s *s, int enable); + +/* omap_i2c.c */ +struct omap_i2c_s; +struct omap_i2c_s *omap_i2c_init(target_phys_addr_t base, + qemu_irq irq, qemu_irq *dma, omap_clk clk); +struct omap_i2c_s *omap2_i2c_init(struct omap_target_agent_s *ta, + qemu_irq irq, qemu_irq *dma, omap_clk fclk, omap_clk iclk); +void omap_i2c_reset(struct omap_i2c_s *s); +i2c_bus *omap_i2c_bus(struct omap_i2c_s *s); + +# define cpu_is_omap310(cpu) (cpu->mpu_model == omap310) +# define cpu_is_omap1510(cpu) (cpu->mpu_model == omap1510) +# define cpu_is_omap1610(cpu) (cpu->mpu_model == omap1610) +# define cpu_is_omap1710(cpu) (cpu->mpu_model == omap1710) +# define cpu_is_omap2410(cpu) (cpu->mpu_model == omap2410) +# define cpu_is_omap2420(cpu) (cpu->mpu_model == omap2420) +# define cpu_is_omap2430(cpu) (cpu->mpu_model == omap2430) +# define cpu_is_omap3430(cpu) (cpu->mpu_model == omap3430) + +# define cpu_is_omap15xx(cpu) \ + (cpu_is_omap310(cpu) || cpu_is_omap1510(cpu)) +# define cpu_is_omap16xx(cpu) \ + (cpu_is_omap1610(cpu) || cpu_is_omap1710(cpu)) +# define cpu_is_omap24xx(cpu) \ + (cpu_is_omap2410(cpu) || cpu_is_omap2420(cpu) || cpu_is_omap2430(cpu)) + +# define cpu_class_omap1(cpu) \ + (cpu_is_omap15xx(cpu) || cpu_is_omap16xx(cpu)) +# define cpu_class_omap2(cpu) cpu_is_omap24xx(cpu) +# define cpu_class_omap3(cpu) cpu_is_omap3430(cpu) + +struct omap_mpu_state_s { + enum omap_mpu_model { + omap310, + omap1510, + omap1610, + omap1710, + omap2410, + omap2420, + omap2422, + omap2423, + omap2430, + omap3430, + } mpu_model; + + CPUState *env; + + qemu_irq *irq[2]; + qemu_irq *drq; + + qemu_irq wakeup; + + struct omap_dma_port_if_s { + uint32_t (*read[3])(struct omap_mpu_state_s *s, + target_phys_addr_t offset); + void (*write[3])(struct omap_mpu_state_s *s, + target_phys_addr_t offset, uint32_t value); + int (*addr_valid)(struct omap_mpu_state_s *s, + target_phys_addr_t addr); + } port[__omap_dma_port_last]; + + unsigned long sdram_size; + unsigned long sram_size; + + /* MPUI-TIPB peripherals */ + struct omap_uart_s *uart[3]; + + struct omap_gpio_s *gpio; + + struct omap_mcbsp_s *mcbsp1; + struct omap_mcbsp_s *mcbsp3; + + /* MPU public TIPB peripherals */ + struct omap_32khz_timer_s *os_timer; + + struct omap_mmc_s *mmc; + + struct omap_mpuio_s *mpuio; + + struct omap_uwire_s *microwire; + + struct { + uint8_t output; + uint8_t level; + uint8_t enable; + int clk; + } pwl; + + struct { + uint8_t frc; + uint8_t vrc; + uint8_t gcr; + omap_clk clk; + } pwt; + + struct omap_i2c_s *i2c[2]; + + struct omap_rtc_s *rtc; + + struct omap_mcbsp_s *mcbsp2; + + struct omap_lpg_s *led[2]; + + /* MPU private TIPB peripherals */ + struct omap_intr_handler_s *ih[2]; + + struct soc_dma_s *dma; + + struct omap_mpu_timer_s *timer[3]; + struct omap_watchdog_timer_s *wdt; + + struct omap_lcd_panel_s *lcd; + + uint32_t ulpd_pm_regs[21]; + int64_t ulpd_gauge_start; + + uint32_t func_mux_ctrl[14]; + uint32_t comp_mode_ctrl[1]; + uint32_t pull_dwn_ctrl[4]; + uint32_t gate_inh_ctrl[1]; + uint32_t voltage_ctrl[1]; + uint32_t test_dbg_ctrl[1]; + uint32_t mod_conf_ctrl[1]; + int compat1509; + + uint32_t mpui_ctrl; + + struct omap_tipb_bridge_s *private_tipb; + struct omap_tipb_bridge_s *public_tipb; + + uint32_t tcmi_regs[17]; + + struct dpll_ctl_s { + uint16_t mode; + omap_clk dpll; + } dpll[3]; + + omap_clk clks; + struct { + int cold_start; + int clocking_scheme; + uint16_t arm_ckctl; + uint16_t arm_idlect1; + uint16_t arm_idlect2; + uint16_t arm_ewupct; + uint16_t arm_rstct1; + uint16_t arm_rstct2; + uint16_t arm_ckout1; + int dpll1_mode; + uint16_t dsp_idlect1; + uint16_t dsp_idlect2; + uint16_t dsp_rstct2; + } clkm; + + /* OMAP2-only peripherals */ + struct omap_l4_s *l4; + + struct omap_gp_timer_s *gptimer[12]; + struct omap_synctimer_s *synctimer; + + struct omap_prcm_s *prcm; + struct omap_sdrc_s *sdrc; + struct omap_gpmc_s *gpmc; + struct omap_sysctl_s *sysc; + + struct omap_gpif_s *gpif; + + struct omap_mcspi_s *mcspi[2]; + + struct omap_dss_s *dss; + + struct omap_eac_s *eac; +}; + +/* omap1.c */ +struct omap_mpu_state_s *omap310_mpu_init(unsigned long sdram_size, + const char *core); + +/* omap2.c */ +struct omap_mpu_state_s *omap2420_mpu_init(unsigned long sdram_size, + const char *core); + +# if TARGET_PHYS_ADDR_BITS == 32 +# define OMAP_FMT_plx "%#08x" +# elif TARGET_PHYS_ADDR_BITS == 64 +# define OMAP_FMT_plx "%#08" PRIx64 +# else +# error TARGET_PHYS_ADDR_BITS undefined +# endif + +uint32_t omap_badwidth_read8(void *opaque, target_phys_addr_t addr); +void omap_badwidth_write8(void *opaque, target_phys_addr_t addr, + uint32_t value); +uint32_t omap_badwidth_read16(void *opaque, target_phys_addr_t addr); +void omap_badwidth_write16(void *opaque, target_phys_addr_t addr, + uint32_t value); +uint32_t omap_badwidth_read32(void *opaque, target_phys_addr_t addr); +void omap_badwidth_write32(void *opaque, target_phys_addr_t addr, + uint32_t value); + +void omap_mpu_wakeup(void *opaque, int irq, int req); + +# define OMAP_BAD_REG(paddr) \ + fprintf(stderr, "%s: Bad register " OMAP_FMT_plx "\n", \ + __FUNCTION__, paddr) +# define OMAP_RO_REG(paddr) \ + fprintf(stderr, "%s: Read-only register " OMAP_FMT_plx "\n", \ + __FUNCTION__, paddr) + +/* OMAP-specific Linux bootloader tags for the ATAG_BOARD area + (Board-specifc tags are not here) */ +#define OMAP_TAG_CLOCK 0x4f01 +#define OMAP_TAG_MMC 0x4f02 +#define OMAP_TAG_SERIAL_CONSOLE 0x4f03 +#define OMAP_TAG_USB 0x4f04 +#define OMAP_TAG_LCD 0x4f05 +#define OMAP_TAG_GPIO_SWITCH 0x4f06 +#define OMAP_TAG_UART 0x4f07 +#define OMAP_TAG_FBMEM 0x4f08 +#define OMAP_TAG_STI_CONSOLE 0x4f09 +#define OMAP_TAG_CAMERA_SENSOR 0x4f0a +#define OMAP_TAG_PARTITION 0x4f0b +#define OMAP_TAG_TEA5761 0x4f10 +#define OMAP_TAG_TMP105 0x4f11 +#define OMAP_TAG_BOOT_REASON 0x4f80 +#define OMAP_TAG_FLASH_PART_STR 0x4f81 +#define OMAP_TAG_VERSION_STR 0x4f82 + +enum { + OMAP_GPIOSW_TYPE_COVER = 0 << 4, + OMAP_GPIOSW_TYPE_CONNECTION = 1 << 4, + OMAP_GPIOSW_TYPE_ACTIVITY = 2 << 4, +}; + +#define OMAP_GPIOSW_INVERTED 0x0001 +#define OMAP_GPIOSW_OUTPUT 0x0002 + +# define TCMI_VERBOSE 1 +//# define MEM_VERBOSE 1 + +# ifdef TCMI_VERBOSE +# define OMAP_8B_REG(paddr) \ + fprintf(stderr, "%s: 8-bit register " OMAP_FMT_plx "\n", \ + __FUNCTION__, paddr) +# define OMAP_16B_REG(paddr) \ + fprintf(stderr, "%s: 16-bit register " OMAP_FMT_plx "\n", \ + __FUNCTION__, paddr) +# define OMAP_32B_REG(paddr) \ + fprintf(stderr, "%s: 32-bit register " OMAP_FMT_plx "\n", \ + __FUNCTION__, paddr) +# else +# define OMAP_8B_REG(paddr) +# define OMAP_16B_REG(paddr) +# define OMAP_32B_REG(paddr) +# endif + +# define OMAP_MPUI_REG_MASK 0x000007ff + +# ifdef MEM_VERBOSE +struct io_fn { + CPUReadMemoryFunc * const *mem_read; + CPUWriteMemoryFunc * const *mem_write; + void *opaque; + int in; +}; + +static uint32_t io_readb(void *opaque, target_phys_addr_t addr) +{ + struct io_fn *s = opaque; + uint32_t ret; + + s->in ++; + ret = s->mem_read[0](s->opaque, addr); + s->in --; + if (!s->in) + fprintf(stderr, "%08x ---> %02x\n", (uint32_t) addr, ret); + return ret; +} +static uint32_t io_readh(void *opaque, target_phys_addr_t addr) +{ + struct io_fn *s = opaque; + uint32_t ret; + + s->in ++; + ret = s->mem_read[1](s->opaque, addr); + s->in --; + if (!s->in) + fprintf(stderr, "%08x ---> %04x\n", (uint32_t) addr, ret); + return ret; +} +static uint32_t io_readw(void *opaque, target_phys_addr_t addr) +{ + struct io_fn *s = opaque; + uint32_t ret; + + s->in ++; + ret = s->mem_read[2](s->opaque, addr); + s->in --; + if (!s->in) + fprintf(stderr, "%08x ---> %08x\n", (uint32_t) addr, ret); + return ret; +} +static void io_writeb(void *opaque, target_phys_addr_t addr, uint32_t value) +{ + struct io_fn *s = opaque; + + if (!s->in) + fprintf(stderr, "%08x <--- %02x\n", (uint32_t) addr, value); + s->in ++; + s->mem_write[0](s->opaque, addr, value); + s->in --; +} +static void io_writeh(void *opaque, target_phys_addr_t addr, uint32_t value) +{ + struct io_fn *s = opaque; + + if (!s->in) + fprintf(stderr, "%08x <--- %04x\n", (uint32_t) addr, value); + s->in ++; + s->mem_write[1](s->opaque, addr, value); + s->in --; +} +static void io_writew(void *opaque, target_phys_addr_t addr, uint32_t value) +{ + struct io_fn *s = opaque; + + if (!s->in) + fprintf(stderr, "%08x <--- %08x\n", (uint32_t) addr, value); + s->in ++; + s->mem_write[2](s->opaque, addr, value); + s->in --; +} + +static CPUReadMemoryFunc * const io_readfn[] = { io_readb, io_readh, io_readw, }; +static CPUWriteMemoryFunc * const io_writefn[] = { io_writeb, io_writeh, io_writew, }; + +inline static int debug_register_io_memory(CPUReadMemoryFunc * const *mem_read, + CPUWriteMemoryFunc * const *mem_write, + void *opaque) +{ + struct io_fn *s = qemu_malloc(sizeof(struct io_fn)); + + s->mem_read = mem_read; + s->mem_write = mem_write; + s->opaque = opaque; + s->in = 0; + return cpu_register_io_memory(io_readfn, io_writefn, s, + DEVICE_NATIVE_ENDIAN); +} +# define cpu_register_io_memory debug_register_io_memory +# endif + +/* Define when we want to reduce the number of IO regions registered. */ +/*# define L4_MUX_HACK*/ + +#endif /* hw_omap_h */ diff --git a/hw/omap1.c b/hw/omap1.c new file mode 100644 index 0000000..d5e4dab --- /dev/null +++ b/hw/omap1.c @@ -0,0 +1,3894 @@ +/* + * TI OMAP processors emulation. + * + * Copyright (C) 2006-2008 Andrzej Zaborowski + * + * 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 . + */ +#include "hw.h" +#include "arm-misc.h" +#include "omap.h" +#include "sysemu.h" +#include "qemu-timer.h" +#include "qemu-char.h" +#include "soc_dma.h" +/* We use pc-style serial ports. */ +#include "pc.h" +#include "blockdev.h" +#include "range.h" + +/* Should signal the TCMI/GPMC */ +uint32_t omap_badwidth_read8(void *opaque, target_phys_addr_t addr) +{ + uint8_t ret; + + OMAP_8B_REG(addr); + cpu_physical_memory_read(addr, (void *) &ret, 1); + return ret; +} + +void omap_badwidth_write8(void *opaque, target_phys_addr_t addr, + uint32_t value) +{ + uint8_t val8 = value; + + OMAP_8B_REG(addr); + cpu_physical_memory_write(addr, (void *) &val8, 1); +} + +uint32_t omap_badwidth_read16(void *opaque, target_phys_addr_t addr) +{ + uint16_t ret; + + OMAP_16B_REG(addr); + cpu_physical_memory_read(addr, (void *) &ret, 2); + return ret; +} + +void omap_badwidth_write16(void *opaque, target_phys_addr_t addr, + uint32_t value) +{ + uint16_t val16 = value; + + OMAP_16B_REG(addr); + cpu_physical_memory_write(addr, (void *) &val16, 2); +} + +uint32_t omap_badwidth_read32(void *opaque, target_phys_addr_t addr) +{ + uint32_t ret; + + OMAP_32B_REG(addr); + cpu_physical_memory_read(addr, (void *) &ret, 4); + return ret; +} + +void omap_badwidth_write32(void *opaque, target_phys_addr_t addr, + uint32_t value) +{ + OMAP_32B_REG(addr); + cpu_physical_memory_write(addr, (void *) &value, 4); +} + +/* MPU OS timers */ +struct omap_mpu_timer_s { + 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_get_clock(vm_clock) - 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_get_clock(vm_clock); +} + +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) + qemu_mod_timer(timer->timer, timer->time + expires); + else + qemu_bh_schedule(timer->tick); + } else + qemu_del_timer(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_irqs(omap_timer_clk_update, timer, 1)[0]); + timer->rate = omap_clk_getrate(timer->clk); +} + +static uint32_t omap_mpu_timer_read(void *opaque, target_phys_addr_t addr) +{ + struct omap_mpu_timer_s *s = (struct omap_mpu_timer_s *) opaque; + + 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, target_phys_addr_t addr, + uint32_t value) +{ + struct omap_mpu_timer_s *s = (struct omap_mpu_timer_s *) opaque; + + 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 CPUReadMemoryFunc * const omap_mpu_timer_readfn[] = { + omap_badwidth_read32, + omap_badwidth_read32, + omap_mpu_timer_read, +}; + +static CPUWriteMemoryFunc * const omap_mpu_timer_writefn[] = { + omap_badwidth_write32, + omap_badwidth_write32, + omap_mpu_timer_write, +}; + +static void omap_mpu_timer_reset(struct omap_mpu_timer_s *s) +{ + qemu_del_timer(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(target_phys_addr_t base, + qemu_irq irq, omap_clk clk) +{ + int iomemtype; + struct omap_mpu_timer_s *s = (struct omap_mpu_timer_s *) + qemu_mallocz(sizeof(struct omap_mpu_timer_s)); + + s->irq = irq; + s->clk = clk; + s->timer = qemu_new_timer(vm_clock, omap_timer_tick, s); + s->tick = qemu_bh_new(omap_timer_fire, s); + omap_mpu_timer_reset(s); + omap_timer_clk_setup(s); + + iomemtype = cpu_register_io_memory(omap_mpu_timer_readfn, + omap_mpu_timer_writefn, s, DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(base, 0x100, iomemtype); + + return s; +} + +/* Watchdog timer */ +struct omap_watchdog_timer_s { + struct omap_mpu_timer_s timer; + uint8_t last_wr; + int mode; + int free; + int reset; +}; + +static uint32_t omap_wd_timer_read(void *opaque, target_phys_addr_t addr) +{ + struct omap_watchdog_timer_s *s = (struct omap_watchdog_timer_s *) opaque; + + 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, target_phys_addr_t addr, + uint32_t value) +{ + struct omap_watchdog_timer_s *s = (struct omap_watchdog_timer_s *) opaque; + + 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 CPUReadMemoryFunc * const omap_wd_timer_readfn[] = { + omap_badwidth_read16, + omap_wd_timer_read, + omap_badwidth_read16, +}; + +static CPUWriteMemoryFunc * const omap_wd_timer_writefn[] = { + omap_badwidth_write16, + omap_wd_timer_write, + omap_badwidth_write16, +}; + +static void omap_wd_timer_reset(struct omap_watchdog_timer_s *s) +{ + qemu_del_timer(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(target_phys_addr_t base, + qemu_irq irq, omap_clk clk) +{ + int iomemtype; + struct omap_watchdog_timer_s *s = (struct omap_watchdog_timer_s *) + qemu_mallocz(sizeof(struct omap_watchdog_timer_s)); + + s->timer.irq = irq; + s->timer.clk = clk; + s->timer.timer = qemu_new_timer(vm_clock, omap_timer_tick, &s->timer); + omap_wd_timer_reset(s); + omap_timer_clk_setup(&s->timer); + + iomemtype = cpu_register_io_memory(omap_wd_timer_readfn, + omap_wd_timer_writefn, s, DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(base, 0x100, iomemtype); + + return s; +} + +/* 32-kHz timer */ +struct omap_32khz_timer_s { + struct omap_mpu_timer_s timer; +}; + +static uint32_t omap_os_timer_read(void *opaque, target_phys_addr_t addr) +{ + struct omap_32khz_timer_s *s = (struct omap_32khz_timer_s *) opaque; + int offset = addr & OMAP_MPUI_REG_MASK; + + 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, target_phys_addr_t addr, + uint32_t value) +{ + struct omap_32khz_timer_s *s = (struct omap_32khz_timer_s *) opaque; + int offset = addr & OMAP_MPUI_REG_MASK; + + 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 CPUReadMemoryFunc * const omap_os_timer_readfn[] = { + omap_badwidth_read32, + omap_badwidth_read32, + omap_os_timer_read, +}; + +static CPUWriteMemoryFunc * const omap_os_timer_writefn[] = { + omap_badwidth_write32, + omap_badwidth_write32, + omap_os_timer_write, +}; + +static void omap_os_timer_reset(struct omap_32khz_timer_s *s) +{ + qemu_del_timer(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(target_phys_addr_t base, + qemu_irq irq, omap_clk clk) +{ + int iomemtype; + struct omap_32khz_timer_s *s = (struct omap_32khz_timer_s *) + qemu_mallocz(sizeof(struct omap_32khz_timer_s)); + + s->timer.irq = irq; + s->timer.clk = clk; + s->timer.timer = qemu_new_timer(vm_clock, omap_timer_tick, &s->timer); + omap_os_timer_reset(s); + omap_timer_clk_setup(&s->timer); + + iomemtype = cpu_register_io_memory(omap_os_timer_readfn, + omap_os_timer_writefn, s, DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(base, 0x800, iomemtype); + + return s; +} + +/* Ultra Low-Power Device Module */ +static uint32_t omap_ulpd_pm_read(void *opaque, target_phys_addr_t addr) +{ + struct omap_mpu_state_s *s = (struct omap_mpu_state_s *) opaque; + uint16_t ret; + + switch (addr) { + case 0x14: /* IT_STATUS */ + ret = s->ulpd_pm_regs[addr >> 2]; + s->ulpd_pm_regs[addr >> 2] = 0; + qemu_irq_lower(s->irq[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); + 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, target_phys_addr_t addr, + uint32_t value) +{ + 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; + + 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_get_clock(vm_clock); + + 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(s->irq[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); + 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 CPUReadMemoryFunc * const omap_ulpd_pm_readfn[] = { + omap_badwidth_read16, + omap_ulpd_pm_read, + omap_badwidth_read16, +}; + +static CPUWriteMemoryFunc * const omap_ulpd_pm_writefn[] = { + omap_badwidth_write16, + omap_ulpd_pm_write, + omap_badwidth_write16, +}; + +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(target_phys_addr_t base, + struct omap_mpu_state_s *mpu) +{ + int iomemtype = cpu_register_io_memory(omap_ulpd_pm_readfn, + omap_ulpd_pm_writefn, mpu, DEVICE_NATIVE_ENDIAN); + + cpu_register_physical_memory(base, 0x800, iomemtype); + omap_ulpd_pm_reset(mpu); +} + +/* OMAP Pin Configuration */ +static uint32_t omap_pin_cfg_read(void *opaque, target_phys_addr_t addr) +{ + struct omap_mpu_state_s *s = (struct omap_mpu_state_s *) opaque; + + 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 & (1 << 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 & (1 << 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, target_phys_addr_t addr, + uint32_t value) +{ + struct omap_mpu_state_s *s = (struct omap_mpu_state_s *) opaque; + uint32_t diff; + + 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 CPUReadMemoryFunc * const omap_pin_cfg_readfn[] = { + omap_badwidth_read32, + omap_badwidth_read32, + omap_pin_cfg_read, +}; + +static CPUWriteMemoryFunc * const omap_pin_cfg_writefn[] = { + omap_badwidth_write32, + omap_badwidth_write32, + omap_pin_cfg_write, +}; + +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(target_phys_addr_t base, + struct omap_mpu_state_s *mpu) +{ + int iomemtype = cpu_register_io_memory(omap_pin_cfg_readfn, + omap_pin_cfg_writefn, mpu, DEVICE_NATIVE_ENDIAN); + + cpu_register_physical_memory(base, 0x800, iomemtype); + omap_pin_cfg_reset(mpu); +} + +/* Device Identification, Die Identification */ +static uint32_t omap_id_read(void *opaque, target_phys_addr_t addr) +{ + struct omap_mpu_state_s *s = (struct omap_mpu_state_s *) opaque; + + 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, target_phys_addr_t addr, + uint32_t value) +{ + OMAP_BAD_REG(addr); +} + +static CPUReadMemoryFunc * const omap_id_readfn[] = { + omap_badwidth_read32, + omap_badwidth_read32, + omap_id_read, +}; + +static CPUWriteMemoryFunc * const omap_id_writefn[] = { + omap_badwidth_write32, + omap_badwidth_write32, + omap_id_write, +}; + +static void omap_id_init(struct omap_mpu_state_s *mpu) +{ + int iomemtype = cpu_register_io_memory(omap_id_readfn, + omap_id_writefn, mpu, DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory_offset(0xfffe1800, 0x800, iomemtype, 0xfffe1800); + cpu_register_physical_memory_offset(0xfffed400, 0x100, iomemtype, 0xfffed400); + if (!cpu_is_omap15xx(mpu)) + cpu_register_physical_memory_offset(0xfffe2000, 0x800, iomemtype, 0xfffe2000); +} + +/* MPUI Control (Dummy) */ +static uint32_t omap_mpui_read(void *opaque, target_phys_addr_t addr) +{ + struct omap_mpu_state_s *s = (struct omap_mpu_state_s *) opaque; + + 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, target_phys_addr_t addr, + uint32_t value) +{ + struct omap_mpu_state_s *s = (struct omap_mpu_state_s *) opaque; + + 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); + case 0x18: /* DSP_BOOT_CONFIG */ + case 0x1c: /* DSP_MPUI_CONFIG */ + break; + + default: + OMAP_BAD_REG(addr); + } +} + +static CPUReadMemoryFunc * const omap_mpui_readfn[] = { + omap_badwidth_read32, + omap_badwidth_read32, + omap_mpui_read, +}; + +static CPUWriteMemoryFunc * const omap_mpui_writefn[] = { + omap_badwidth_write32, + omap_badwidth_write32, + omap_mpui_write, +}; + +static void omap_mpui_reset(struct omap_mpu_state_s *s) +{ + s->mpui_ctrl = 0x0003ff1b; +} + +static void omap_mpui_init(target_phys_addr_t base, + struct omap_mpu_state_s *mpu) +{ + int iomemtype = cpu_register_io_memory(omap_mpui_readfn, + omap_mpui_writefn, mpu, DEVICE_NATIVE_ENDIAN); + + cpu_register_physical_memory(base, 0x100, iomemtype); + + omap_mpui_reset(mpu); +} + +/* TIPB Bridges */ +struct omap_tipb_bridge_s { + qemu_irq abort; + + int width_intr; + uint16_t control; + uint16_t alloc; + uint16_t buffer; + uint16_t enh_control; +}; + +static uint32_t omap_tipb_bridge_read(void *opaque, target_phys_addr_t addr) +{ + struct omap_tipb_bridge_s *s = (struct omap_tipb_bridge_s *) opaque; + + 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, target_phys_addr_t addr, + uint32_t value) +{ + struct omap_tipb_bridge_s *s = (struct omap_tipb_bridge_s *) opaque; + + 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 CPUReadMemoryFunc * const omap_tipb_bridge_readfn[] = { + omap_badwidth_read16, + omap_tipb_bridge_read, + omap_tipb_bridge_read, +}; + +static CPUWriteMemoryFunc * const omap_tipb_bridge_writefn[] = { + omap_badwidth_write16, + omap_tipb_bridge_write, + omap_tipb_bridge_write, +}; + +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(target_phys_addr_t base, + qemu_irq abort_irq, omap_clk clk) +{ + int iomemtype; + struct omap_tipb_bridge_s *s = (struct omap_tipb_bridge_s *) + qemu_mallocz(sizeof(struct omap_tipb_bridge_s)); + + s->abort = abort_irq; + omap_tipb_bridge_reset(s); + + iomemtype = cpu_register_io_memory(omap_tipb_bridge_readfn, + omap_tipb_bridge_writefn, s, DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(base, 0x100, iomemtype); + + return s; +} + +/* Dummy Traffic Controller's Memory Interface */ +static uint32_t omap_tcmi_read(void *opaque, target_phys_addr_t addr) +{ + struct omap_mpu_state_s *s = (struct omap_mpu_state_s *) opaque; + uint32_t ret; + + 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, target_phys_addr_t addr, + uint32_t value) +{ + struct omap_mpu_state_s *s = (struct omap_mpu_state_s *) opaque; + + 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 CPUReadMemoryFunc * const omap_tcmi_readfn[] = { + omap_badwidth_read32, + omap_badwidth_read32, + omap_tcmi_read, +}; + +static CPUWriteMemoryFunc * const omap_tcmi_writefn[] = { + omap_badwidth_write32, + omap_badwidth_write32, + omap_tcmi_write, +}; + +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(target_phys_addr_t base, + struct omap_mpu_state_s *mpu) +{ + int iomemtype = cpu_register_io_memory(omap_tcmi_readfn, + omap_tcmi_writefn, mpu, DEVICE_NATIVE_ENDIAN); + + cpu_register_physical_memory(base, 0x100, iomemtype); + omap_tcmi_reset(mpu); +} + +/* Digital phase-locked loops control */ +static uint32_t omap_dpll_read(void *opaque, target_phys_addr_t addr) +{ + struct dpll_ctl_s *s = (struct dpll_ctl_s *) opaque; + + if (addr == 0x00) /* CTL_REG */ + return s->mode; + + OMAP_BAD_REG(addr); + return 0; +} + +static void omap_dpll_write(void *opaque, target_phys_addr_t addr, + uint32_t value) +{ + 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 (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 CPUReadMemoryFunc * const omap_dpll_readfn[] = { + omap_badwidth_read16, + omap_dpll_read, + omap_badwidth_read16, +}; + +static CPUWriteMemoryFunc * const omap_dpll_writefn[] = { + omap_badwidth_write16, + omap_dpll_write, + omap_badwidth_write16, +}; + +static void omap_dpll_reset(struct dpll_ctl_s *s) +{ + s->mode = 0x2002; + omap_clk_setrate(s->dpll, 1, 1); +} + +static void omap_dpll_init(struct dpll_ctl_s *s, target_phys_addr_t base, + omap_clk clk) +{ + int iomemtype = cpu_register_io_memory(omap_dpll_readfn, + omap_dpll_writefn, s, DEVICE_NATIVE_ENDIAN); + + s->dpll = clk; + omap_dpll_reset(s); + + cpu_register_physical_memory(base, 0x100, iomemtype); +} + +/* MPU Clock/Reset/Power Mode Control */ +static uint32_t omap_clkm_read(void *opaque, target_phys_addr_t addr) +{ + struct omap_mpu_state_s *s = (struct omap_mpu_state_s *) opaque; + + 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(s->env, 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, target_phys_addr_t addr, + uint32_t value) +{ + 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", + }; + + 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 CPUReadMemoryFunc * const omap_clkm_readfn[] = { + omap_badwidth_read16, + omap_clkm_read, + omap_badwidth_read16, +}; + +static CPUWriteMemoryFunc * const omap_clkm_writefn[] = { + omap_badwidth_write16, + omap_clkm_write, + omap_badwidth_write16, +}; + +static uint32_t omap_clkdsp_read(void *opaque, target_phys_addr_t addr) +{ + struct omap_mpu_state_s *s = (struct omap_mpu_state_s *) opaque; + + 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 */ + return (s->clkm.clocking_scheme << 11) | s->clkm.cold_start | + (s->env->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, target_phys_addr_t addr, + uint32_t value) +{ + struct omap_mpu_state_s *s = (struct omap_mpu_state_s *) opaque; + uint16_t diff; + + 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 CPUReadMemoryFunc * const omap_clkdsp_readfn[] = { + omap_badwidth_read16, + omap_clkdsp_read, + omap_badwidth_read16, +}; + +static CPUWriteMemoryFunc * const omap_clkdsp_writefn[] = { + omap_badwidth_write16, + omap_clkdsp_write, + omap_badwidth_write16, +}; + +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(target_phys_addr_t mpu_base, + target_phys_addr_t dsp_base, struct omap_mpu_state_s *s) +{ + int iomemtype[2] = { + cpu_register_io_memory(omap_clkm_readfn, omap_clkm_writefn, s, + DEVICE_NATIVE_ENDIAN), + cpu_register_io_memory(omap_clkdsp_readfn, omap_clkdsp_writefn, s, + DEVICE_NATIVE_ENDIAN), + }; + + s->clkm.arm_idlect1 = 0x03ff; + s->clkm.arm_idlect2 = 0x0100; + s->clkm.dsp_idlect1 = 0x0002; + omap_clkm_reset(s); + s->clkm.cold_start = 0x3a; + + cpu_register_physical_memory(mpu_base, 0x100, iomemtype[0]); + cpu_register_physical_memory(dsp_base, 0x1000, iomemtype[1]); +} + +/* MPU I/O */ +struct omap_mpuio_s { + qemu_irq irq; + qemu_irq kbd_irq; + qemu_irq *in; + qemu_irq handler[16]; + qemu_irq wakeup; + + 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 uint32_t omap_mpuio_read(void *opaque, target_phys_addr_t addr) +{ + struct omap_mpuio_s *s = (struct omap_mpuio_s *) opaque; + int offset = addr & OMAP_MPUI_REG_MASK; + uint16_t ret; + + 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, target_phys_addr_t addr, + uint32_t value) +{ + struct omap_mpuio_s *s = (struct omap_mpuio_s *) opaque; + int offset = addr & OMAP_MPUI_REG_MASK; + uint16_t diff; + int ln; + + switch (offset) { + case 0x04: /* OUTPUT_REG */ + diff = (s->outputs ^ value) & ~s->dir; + s->outputs = value; + while ((ln = ffs(diff))) { + ln --; + 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 = ffs(diff))) { + ln --; + 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 CPUReadMemoryFunc * const omap_mpuio_readfn[] = { + omap_badwidth_read16, + omap_mpuio_read, + omap_badwidth_read16, +}; + +static CPUWriteMemoryFunc * const omap_mpuio_writefn[] = { + omap_badwidth_write16, + omap_mpuio_write, + omap_badwidth_write16, +}; + +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); +} + +struct omap_mpuio_s *omap_mpuio_init(target_phys_addr_t base, + qemu_irq kbd_int, qemu_irq gpio_int, qemu_irq wakeup, + omap_clk clk) +{ + int iomemtype; + struct omap_mpuio_s *s = (struct omap_mpuio_s *) + qemu_mallocz(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); + + iomemtype = cpu_register_io_memory(omap_mpuio_readfn, + omap_mpuio_writefn, s, DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(base, 0x800, iomemtype); + + omap_clk_adduser(clk, qemu_allocate_irqs(omap_mpuio_onoff, s, 1)[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 { + 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 uint32_t omap_uwire_read(void *opaque, target_phys_addr_t addr) +{ + struct omap_uwire_s *s = (struct omap_uwire_s *) opaque; + int offset = addr & OMAP_MPUI_REG_MASK; + + 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, target_phys_addr_t addr, + uint32_t value) +{ + struct omap_uwire_s *s = (struct omap_uwire_s *) opaque; + int offset = addr & OMAP_MPUI_REG_MASK; + + 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 CPUReadMemoryFunc * const omap_uwire_readfn[] = { + omap_badwidth_read16, + omap_uwire_read, + omap_badwidth_read16, +}; + +static CPUWriteMemoryFunc * const omap_uwire_writefn[] = { + omap_badwidth_write16, + omap_uwire_write, + omap_badwidth_write16, +}; + +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; +} + +struct omap_uwire_s *omap_uwire_init(target_phys_addr_t base, + qemu_irq *irq, qemu_irq dma, omap_clk clk) +{ + int iomemtype; + struct omap_uwire_s *s = (struct omap_uwire_s *) + qemu_mallocz(sizeof(struct omap_uwire_s)); + + s->txirq = irq[0]; + s->rxirq = irq[1]; + s->txdrq = dma; + omap_uwire_reset(s); + + iomemtype = cpu_register_io_memory(omap_uwire_readfn, + omap_uwire_writefn, s, DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(base, 0x800, iomemtype); + + 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 */ +static void omap_pwl_update(struct omap_mpu_state_s *s) +{ + int output = (s->pwl.clk && s->pwl.enable) ? s->pwl.level : 0; + + if (output != s->pwl.output) { + s->pwl.output = output; + printf("%s: Backlight now at %i/256\n", __FUNCTION__, output); + } +} + +static uint32_t omap_pwl_read(void *opaque, target_phys_addr_t addr) +{ + struct omap_mpu_state_s *s = (struct omap_mpu_state_s *) opaque; + int offset = addr & OMAP_MPUI_REG_MASK; + + switch (offset) { + case 0x00: /* PWL_LEVEL */ + return s->pwl.level; + case 0x04: /* PWL_CTRL */ + return s->pwl.enable; + } + OMAP_BAD_REG(addr); + return 0; +} + +static void omap_pwl_write(void *opaque, target_phys_addr_t addr, + uint32_t value) +{ + struct omap_mpu_state_s *s = (struct omap_mpu_state_s *) opaque; + int offset = addr & OMAP_MPUI_REG_MASK; + + switch (offset) { + case 0x00: /* PWL_LEVEL */ + s->pwl.level = value; + omap_pwl_update(s); + break; + case 0x04: /* PWL_CTRL */ + s->pwl.enable = value & 1; + omap_pwl_update(s); + break; + default: + OMAP_BAD_REG(addr); + return; + } +} + +static CPUReadMemoryFunc * const omap_pwl_readfn[] = { + omap_pwl_read, + omap_badwidth_read8, + omap_badwidth_read8, +}; + +static CPUWriteMemoryFunc * const omap_pwl_writefn[] = { + omap_pwl_write, + omap_badwidth_write8, + omap_badwidth_write8, +}; + +static void omap_pwl_reset(struct omap_mpu_state_s *s) +{ + s->pwl.output = 0; + s->pwl.level = 0; + s->pwl.enable = 0; + s->pwl.clk = 1; + omap_pwl_update(s); +} + +static void omap_pwl_clk_update(void *opaque, int line, int on) +{ + struct omap_mpu_state_s *s = (struct omap_mpu_state_s *) opaque; + + s->pwl.clk = on; + omap_pwl_update(s); +} + +static void omap_pwl_init(target_phys_addr_t base, struct omap_mpu_state_s *s, + omap_clk clk) +{ + int iomemtype; + + omap_pwl_reset(s); + + iomemtype = cpu_register_io_memory(omap_pwl_readfn, + omap_pwl_writefn, s, DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(base, 0x800, iomemtype); + + omap_clk_adduser(clk, qemu_allocate_irqs(omap_pwl_clk_update, s, 1)[0]); +} + +/* Pulse-Width Tone module */ +static uint32_t omap_pwt_read(void *opaque, target_phys_addr_t addr) +{ + struct omap_mpu_state_s *s = (struct omap_mpu_state_s *) opaque; + int offset = addr & OMAP_MPUI_REG_MASK; + + switch (offset) { + case 0x00: /* FRC */ + return s->pwt.frc; + case 0x04: /* VCR */ + return s->pwt.vrc; + case 0x08: /* GCR */ + return s->pwt.gcr; + } + OMAP_BAD_REG(addr); + return 0; +} + +static void omap_pwt_write(void *opaque, target_phys_addr_t addr, + uint32_t value) +{ + struct omap_mpu_state_s *s = (struct omap_mpu_state_s *) opaque; + int offset = addr & OMAP_MPUI_REG_MASK; + + switch (offset) { + case 0x00: /* FRC */ + s->pwt.frc = value & 0x3f; + break; + case 0x04: /* VRC */ + if ((value ^ s->pwt.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->pwt.clk) >> 3) / + /* Pre-multiplexer divider */ + ((s->pwt.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->pwt.vrc = value & 0x7f; + break; + case 0x08: /* GCR */ + s->pwt.gcr = value & 3; + break; + default: + OMAP_BAD_REG(addr); + return; + } +} + +static CPUReadMemoryFunc * const omap_pwt_readfn[] = { + omap_pwt_read, + omap_badwidth_read8, + omap_badwidth_read8, +}; + +static CPUWriteMemoryFunc * const omap_pwt_writefn[] = { + omap_pwt_write, + omap_badwidth_write8, + omap_badwidth_write8, +}; + +static void omap_pwt_reset(struct omap_mpu_state_s *s) +{ + s->pwt.frc = 0; + s->pwt.vrc = 0; + s->pwt.gcr = 0; +} + +static void omap_pwt_init(target_phys_addr_t base, struct omap_mpu_state_s *s, + omap_clk clk) +{ + int iomemtype; + + s->pwt.clk = clk; + omap_pwt_reset(s); + + iomemtype = cpu_register_io_memory(omap_pwt_readfn, + omap_pwt_writefn, s, DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(base, 0x800, iomemtype); +} + +/* Real-time Clock module */ +struct omap_rtc_s { + 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 uint32_t omap_rtc_read(void *opaque, target_phys_addr_t addr) +{ + struct omap_rtc_s *s = (struct omap_rtc_s *) opaque; + int offset = addr & OMAP_MPUI_REG_MASK; + uint8_t i; + + 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, target_phys_addr_t addr, + uint32_t value) +{ + 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]; + + 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 -= (s->current_tm.tm_year % 100) * 31536000; + s->ti += 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 CPUReadMemoryFunc * const omap_rtc_readfn[] = { + omap_rtc_read, + omap_badwidth_read8, + omap_badwidth_read8, +}; + +static CPUWriteMemoryFunc * const omap_rtc_writefn[] = { + omap_rtc_write, + omap_badwidth_write8, + omap_badwidth_write8, +}; + +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; + } + + memcpy(&s->current_tm, localtime(&s->ti), sizeof(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; + + qemu_mod_timer(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_get_clock(rt_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(target_phys_addr_t base, + qemu_irq *irq, omap_clk clk) +{ + int iomemtype; + struct omap_rtc_s *s = (struct omap_rtc_s *) + qemu_mallocz(sizeof(struct omap_rtc_s)); + + s->irq = irq[0]; + s->alarm = irq[1]; + s->clk = qemu_new_timer(rt_clock, omap_rtc_tick, s); + + omap_rtc_reset(s); + + iomemtype = cpu_register_io_memory(omap_rtc_readfn, + omap_rtc_writefn, s, DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(base, 0x800, iomemtype); + + return s; +} + +/* Multi-channel Buffered Serial Port interfaces */ +struct omap_mcbsp_s { + 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); + qemu_mod_timer(s->source_timer, qemu_get_clock(vm_clock) + + 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) +{ + qemu_del_timer(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); + qemu_mod_timer(s->sink_timer, qemu_get_clock(vm_clock) + + 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); + qemu_del_timer(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 uint32_t omap_mcbsp_read(void *opaque, target_phys_addr_t addr) +{ + struct omap_mcbsp_s *s = (struct omap_mcbsp_s *) opaque; + int offset = addr & OMAP_MPUI_REG_MASK; + uint16_t ret; + + 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, target_phys_addr_t 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, target_phys_addr_t 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 CPUReadMemoryFunc * const omap_mcbsp_readfn[] = { + omap_badwidth_read16, + omap_mcbsp_read, + omap_badwidth_read16, +}; + +static CPUWriteMemoryFunc * const omap_mcbsp_writefn[] = { + omap_badwidth_write16, + omap_mcbsp_writeh, + omap_mcbsp_writew, +}; + +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; + qemu_del_timer(s->source_timer); + qemu_del_timer(s->sink_timer); +} + +struct omap_mcbsp_s *omap_mcbsp_init(target_phys_addr_t base, + qemu_irq *irq, qemu_irq *dma, omap_clk clk) +{ + int iomemtype; + struct omap_mcbsp_s *s = (struct omap_mcbsp_s *) + qemu_mallocz(sizeof(struct omap_mcbsp_s)); + + s->txirq = irq[0]; + s->rxirq = irq[1]; + s->txdrq = dma[0]; + s->rxdrq = dma[1]; + s->sink_timer = qemu_new_timer(vm_clock, omap_mcbsp_sink_tick, s); + s->source_timer = qemu_new_timer(vm_clock, omap_mcbsp_source_tick, s); + omap_mcbsp_reset(s); + + iomemtype = cpu_register_io_memory(omap_mcbsp_readfn, + omap_mcbsp_writefn, s, DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(base, 0x800, iomemtype); + + 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_irqs(omap_mcbsp_i2s_swallow, s, 1)[0]; + slave->tx_start = qemu_allocate_irqs(omap_mcbsp_i2s_start, s, 1)[0]; +} + +/* LED Pulse Generators */ +struct omap_lpg_s { + 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) + qemu_mod_timer(s->tm, qemu_get_clock(rt_clock) + s->period - s->on); + else + qemu_mod_timer(s->tm, qemu_get_clock(rt_clock) + 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 */ + } + + qemu_del_timer(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 uint32_t omap_lpg_read(void *opaque, target_phys_addr_t addr) +{ + struct omap_lpg_s *s = (struct omap_lpg_s *) opaque; + int offset = addr & OMAP_MPUI_REG_MASK; + + 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, target_phys_addr_t addr, + uint32_t value) +{ + struct omap_lpg_s *s = (struct omap_lpg_s *) opaque; + int offset = addr & OMAP_MPUI_REG_MASK; + + 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 CPUReadMemoryFunc * const omap_lpg_readfn[] = { + omap_lpg_read, + omap_badwidth_read8, + omap_badwidth_read8, +}; + +static CPUWriteMemoryFunc * const omap_lpg_writefn[] = { + omap_lpg_write, + omap_badwidth_write8, + omap_badwidth_write8, +}; + +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(target_phys_addr_t base, omap_clk clk) +{ + int iomemtype; + struct omap_lpg_s *s = (struct omap_lpg_s *) + qemu_mallocz(sizeof(struct omap_lpg_s)); + + s->tm = qemu_new_timer(rt_clock, omap_lpg_tick, s); + + omap_lpg_reset(s); + + iomemtype = cpu_register_io_memory(omap_lpg_readfn, + omap_lpg_writefn, s, DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(base, 0x800, iomemtype); + + omap_clk_adduser(clk, qemu_allocate_irqs(omap_lpg_clk_update, s, 1)[0]); + + return s; +} + +/* MPUI Peripheral Bridge configuration */ +static uint32_t omap_mpui_io_read(void *opaque, target_phys_addr_t addr) +{ + if (addr == OMAP_MPUI_BASE) /* CMR */ + return 0xfe4d; + + OMAP_BAD_REG(addr); + return 0; +} + +static CPUReadMemoryFunc * const omap_mpui_io_readfn[] = { + omap_badwidth_read16, + omap_mpui_io_read, + omap_badwidth_read16, +}; + +static CPUWriteMemoryFunc * const omap_mpui_io_writefn[] = { + omap_badwidth_write16, + omap_badwidth_write16, + omap_badwidth_write16, +}; + +static void omap_setup_mpui_io(struct omap_mpu_state_s *mpu) +{ + int iomemtype = cpu_register_io_memory(omap_mpui_io_readfn, + omap_mpui_io_writefn, mpu, DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(OMAP_MPUI_BASE, 0x7fff, iomemtype); +} + +/* General chip reset */ +static void omap1_mpu_reset(void *opaque) +{ + struct omap_mpu_state_s *mpu = (struct omap_mpu_state_s *) opaque; + + omap_inth_reset(mpu->ih[0]); + omap_inth_reset(mpu->ih[1]); + 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_gpio_reset(mpu->gpio); + omap_uwire_reset(mpu->microwire); + omap_pwl_reset(mpu); + omap_pwt_reset(mpu); + omap_i2c_reset(mpu->i2c[0]); + 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(mpu->env); +} + +static const struct omap_map_s { + target_phys_addr_t phys_dsp; + target_phys_addr_t 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(const struct omap_map_s *map) +{ + int io; + + for (; map->phys_dsp; map ++) { + io = cpu_get_physical_page_desc(map->phys_mpu); + + cpu_register_physical_memory(map->phys_dsp, map->size, io); + } +} + +void omap_mpu_wakeup(void *opaque, int irq, int req) +{ + struct omap_mpu_state_s *mpu = (struct omap_mpu_state_s *) opaque; + + if (mpu->env->halted) + cpu_interrupt(mpu->env, 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, + target_phys_addr_t addr) +{ + return range_covers_byte(OMAP_EMIFF_BASE, s->sdram_size, addr); +} + +static int omap_validate_emifs_addr(struct omap_mpu_state_s *s, + target_phys_addr_t 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, + target_phys_addr_t addr) +{ + return range_covers_byte(OMAP_IMIF_BASE, s->sram_size, addr); +} + +static int omap_validate_tipb_addr(struct omap_mpu_state_s *s, + target_phys_addr_t addr) +{ + return range_covers_byte(0xfffb0000, 0xffff0000 - 0xfffb0000, addr); +} + +static int omap_validate_local_addr(struct omap_mpu_state_s *s, + target_phys_addr_t addr) +{ + return range_covers_byte(OMAP_LOCALBUS_BASE, 0x1000000, addr); +} + +static int omap_validate_tipb_mpui_addr(struct omap_mpu_state_s *s, + target_phys_addr_t addr) +{ + return range_covers_byte(0xe1010000, 0xe1020004 - 0xe1010000, addr); +} + +struct omap_mpu_state_s *omap310_mpu_init(unsigned long sdram_size, + const char *core) +{ + int i; + struct omap_mpu_state_s *s = (struct omap_mpu_state_s *) + qemu_mallocz(sizeof(struct omap_mpu_state_s)); + ram_addr_t imif_base, emiff_base; + qemu_irq *cpu_irq; + qemu_irq dma_irqs[6]; + DriveInfo *dinfo; + + if (!core) + core = "ti925t"; + + /* Core */ + s->mpu_model = omap310; + s->env = cpu_init(core); + if (!s->env) { + 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_irqs(omap_mpu_wakeup, s, 1)[0]; + + /* Clocks */ + omap_clk_init(s); + + /* Memory-mapped stuff */ + cpu_register_physical_memory(OMAP_EMIFF_BASE, s->sdram_size, + (emiff_base = qemu_ram_alloc(NULL, "omap1.dram", + s->sdram_size)) | IO_MEM_RAM); + cpu_register_physical_memory(OMAP_IMIF_BASE, s->sram_size, + (imif_base = qemu_ram_alloc(NULL, "omap1.sram", + s->sram_size)) | IO_MEM_RAM); + + omap_clkm_init(0xfffece00, 0xe1008000, s); + + cpu_irq = arm_pic_init_cpu(s->env); + s->ih[0] = omap_inth_init(0xfffecb00, 0x100, 1, &s->irq[0], + cpu_irq[ARM_PIC_CPU_IRQ], cpu_irq[ARM_PIC_CPU_FIQ], + omap_findclk(s, "arminth_ck")); + s->ih[1] = omap_inth_init(0xfffe0000, 0x800, 1, &s->irq[1], + omap_inth_get_pin(s->ih[0], OMAP_INT_15XX_IH2_IRQ), + NULL, omap_findclk(s, "arminth_ck")); + + for (i = 0; i < 6; i ++) + dma_irqs[i] = + s->irq[omap1_dma_irq_map[i].ih][omap1_dma_irq_map[i].intr]; + s->dma = omap_dma_init(0xfffed800, dma_irqs, s->irq[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_ram(s->dma, + emiff_base, OMAP_EMIFF_BASE, s->sdram_size); + soc_dma_port_add_mem_ram(s->dma, + imif_base, OMAP_IMIF_BASE, s->sram_size); + + s->timer[0] = omap_mpu_timer_init(0xfffec500, + s->irq[0][OMAP_INT_TIMER1], + omap_findclk(s, "mputim_ck")); + s->timer[1] = omap_mpu_timer_init(0xfffec600, + s->irq[0][OMAP_INT_TIMER2], + omap_findclk(s, "mputim_ck")); + s->timer[2] = omap_mpu_timer_init(0xfffec700, + s->irq[0][OMAP_INT_TIMER3], + omap_findclk(s, "mputim_ck")); + + s->wdt = omap_wd_timer_init(0xfffec800, + s->irq[0][OMAP_INT_WD_TIMER], + omap_findclk(s, "armwdt_ck")); + + s->os_timer = omap_os_timer_init(0xfffb9000, + s->irq[1][OMAP_INT_OS_TIMER], + omap_findclk(s, "clk32-kHz")); + + s->lcd = omap_lcdc_init(0xfffec000, s->irq[0][OMAP_INT_LCD_CTRL], + omap_dma_get_lcdch(s->dma), imif_base, emiff_base, + omap_findclk(s, "lcd_ck")); + + omap_ulpd_pm_init(0xfffe0800, s); + omap_pin_cfg_init(0xfffe1000, s); + omap_id_init(s); + + omap_mpui_init(0xfffec900, s); + + s->private_tipb = omap_tipb_bridge_init(0xfffeca00, + s->irq[0][OMAP_INT_BRIDGE_PRIV], + omap_findclk(s, "tipb_ck")); + s->public_tipb = omap_tipb_bridge_init(0xfffed300, + s->irq[0][OMAP_INT_BRIDGE_PUB], + omap_findclk(s, "tipb_ck")); + + omap_tcmi_init(0xfffecc00, s); + + s->uart[0] = omap_uart_init(0xfffb0000, s->irq[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, s->irq[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, s->irq[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); + + omap_dpll_init(&s->dpll[0], 0xfffecf00, omap_findclk(s, "dpll1")); + omap_dpll_init(&s->dpll[1], 0xfffed000, omap_findclk(s, "dpll2")); + omap_dpll_init(&s->dpll[2], 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, dinfo->bdrv, + s->irq[1][OMAP_INT_OQN], &s->drq[OMAP_DMA_MMC_TX], + omap_findclk(s, "mmc_ck")); + + s->mpuio = omap_mpuio_init(0xfffb5000, + s->irq[1][OMAP_INT_KEYBOARD], s->irq[1][OMAP_INT_MPUIO], + s->wakeup, omap_findclk(s, "clk32-kHz")); + + s->gpio = omap_gpio_init(0xfffce000, s->irq[0][OMAP_INT_GPIO_BANK1], + omap_findclk(s, "arm_gpio_ck")); + + s->microwire = omap_uwire_init(0xfffb3000, &s->irq[1][OMAP_INT_uWireTX], + s->drq[OMAP_DMA_UWIRE_TX], omap_findclk(s, "mpuper_ck")); + + omap_pwl_init(0xfffb5800, s, omap_findclk(s, "armxor_ck")); + omap_pwt_init(0xfffb6000, s, omap_findclk(s, "armxor_ck")); + + s->i2c[0] = omap_i2c_init(0xfffb3800, s->irq[1][OMAP_INT_I2C], + &s->drq[OMAP_DMA_I2C_RX], omap_findclk(s, "mpuper_ck")); + + s->rtc = omap_rtc_init(0xfffb4800, &s->irq[1][OMAP_INT_RTC_TIMER], + omap_findclk(s, "clk32-kHz")); + + s->mcbsp1 = omap_mcbsp_init(0xfffb1800, &s->irq[1][OMAP_INT_McBSP1TX], + &s->drq[OMAP_DMA_MCBSP1_TX], omap_findclk(s, "dspxor_ck")); + s->mcbsp2 = omap_mcbsp_init(0xfffb1000, &s->irq[0][OMAP_INT_310_McBSP2_TX], + &s->drq[OMAP_DMA_MCBSP2_TX], omap_findclk(s, "mpuper_ck")); + s->mcbsp3 = omap_mcbsp_init(0xfffb7000, &s->irq[1][OMAP_INT_McBSP3TX], + &s->drq[OMAP_DMA_MCBSP3_TX], omap_findclk(s, "dspxor_ck")); + + s->led[0] = omap_lpg_init(0xfffbd000, omap_findclk(s, "clk32-kHz")); + s->led[1] = omap_lpg_init(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(omap15xx_dsp_mm); + omap_setup_mpui_io(s); + + qemu_register_reset(omap1_mpu_reset, s); + + return s; +} diff --git a/hw/omap2.c b/hw/omap2.c new file mode 100644 index 0000000..0f13272 --- /dev/null +++ b/hw/omap2.c @@ -0,0 +1,2616 @@ +/* + * TI OMAP processors emulation. + * + * Copyright (C) 2007-2008 Nokia Corporation + * Written by Andrzej Zaborowski + * + * 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 . + */ + +#include "blockdev.h" +#include "hw.h" +#include "arm-misc.h" +#include "omap.h" +#include "sysemu.h" +#include "qemu-timer.h" +#include "qemu-char.h" +#include "flash.h" +#include "soc_dma.h" +#include "audio/audio.h" + +/* Enhanced Audio Controller (CODEC only) */ +struct omap_eac_s { + qemu_irq irq; + + 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 uint32_t omap_eac_read(void *opaque, target_phys_addr_t addr) +{ + struct omap_eac_s *s = (struct omap_eac_s *) opaque; + uint32_t ret; + + 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, target_phys_addr_t addr, + uint32_t value) +{ + struct omap_eac_s *s = (struct omap_eac_s *) opaque; + + 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 CPUReadMemoryFunc * const omap_eac_readfn[] = { + omap_badwidth_read16, + omap_eac_read, + omap_badwidth_read16, +}; + +static CPUWriteMemoryFunc * const omap_eac_writefn[] = { + omap_badwidth_write16, + omap_eac_write, + omap_badwidth_write16, +}; + +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) +{ + int iomemtype; + struct omap_eac_s *s = (struct omap_eac_s *) + qemu_mallocz(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); + + iomemtype = cpu_register_io_memory(omap_eac_readfn, + omap_eac_writefn, s, DEVICE_NATIVE_ENDIAN); + omap_l4_attach(ta, 0, iomemtype); + + return s; +} + +/* STI/XTI (emulation interface) console - reverse engineered only */ +struct omap_sti_s { + qemu_irq irq; + 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 uint32_t omap_sti_read(void *opaque, target_phys_addr_t addr) +{ + struct omap_sti_s *s = (struct omap_sti_s *) opaque; + + 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, target_phys_addr_t addr, + uint32_t value) +{ + struct omap_sti_s *s = (struct omap_sti_s *) opaque; + + 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 CPUReadMemoryFunc * const omap_sti_readfn[] = { + omap_badwidth_read32, + omap_badwidth_read32, + omap_sti_read, +}; + +static CPUWriteMemoryFunc * const omap_sti_writefn[] = { + omap_badwidth_write32, + omap_badwidth_write32, + omap_sti_write, +}; + +static uint32_t omap_sti_fifo_read(void *opaque, target_phys_addr_t addr) +{ + OMAP_BAD_REG(addr); + return 0; +} + +static void omap_sti_fifo_write(void *opaque, target_phys_addr_t addr, + uint32_t value) +{ + struct omap_sti_s *s = (struct omap_sti_s *) opaque; + int ch = addr >> 6; + uint8_t byte = value; + + if (ch == STI_TRACE_CONTROL_CHANNEL) { + /* Flush channel value. */ + qemu_chr_write(s->chr, (const uint8_t *) "\r", 1); + } else if (ch == STI_TRACE_CONSOLE_CHANNEL || 1) { + if (value == 0xc0 || value == 0xc3) { + /* Open channel ch. */ + } else if (value == 0x00) + qemu_chr_write(s->chr, (const uint8_t *) "\n", 1); + else + qemu_chr_write(s->chr, &byte, 1); + } +} + +static CPUReadMemoryFunc * const omap_sti_fifo_readfn[] = { + omap_sti_fifo_read, + omap_badwidth_read8, + omap_badwidth_read8, +}; + +static CPUWriteMemoryFunc * const omap_sti_fifo_writefn[] = { + omap_sti_fifo_write, + omap_badwidth_write8, + omap_badwidth_write8, +}; + +static struct omap_sti_s *omap_sti_init(struct omap_target_agent_s *ta, + target_phys_addr_t channel_base, qemu_irq irq, omap_clk clk, + CharDriverState *chr) +{ + int iomemtype; + struct omap_sti_s *s = (struct omap_sti_s *) + qemu_mallocz(sizeof(struct omap_sti_s)); + + s->irq = irq; + omap_sti_reset(s); + + s->chr = chr ?: qemu_chr_open("null", "null", NULL); + + iomemtype = l4_register_io_memory(omap_sti_readfn, + omap_sti_writefn, s); + omap_l4_attach(ta, 0, iomemtype); + + iomemtype = cpu_register_io_memory(omap_sti_fifo_readfn, + omap_sti_fifo_writefn, s, DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(channel_base, 0x10000, iomemtype); + + 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; + + 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 uint32_t omap_prcm_read(void *opaque, target_phys_addr_t addr) +{ + struct omap_prcm_s *s = (struct omap_prcm_s *) opaque; + uint32_t ret; + + 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, target_phys_addr_t addr, + uint32_t value) +{ + struct omap_prcm_s *s = (struct omap_prcm_s *) opaque; + + 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 compatiblity\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 compatiblity\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 compatiblity\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 CPUReadMemoryFunc * const omap_prcm_readfn[] = { + omap_badwidth_read32, + omap_badwidth_read32, + omap_prcm_read, +}; + +static CPUWriteMemoryFunc * const omap_prcm_writefn[] = { + omap_badwidth_write32, + omap_badwidth_write32, + omap_prcm_write, +}; + +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) +{ + int iomemtype; + struct omap_prcm_s *s = (struct omap_prcm_s *) + qemu_mallocz(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); + + iomemtype = l4_register_io_memory(omap_prcm_readfn, + omap_prcm_writefn, s); + omap_l4_attach(ta, 0, iomemtype); + omap_l4_attach(ta, 1, iomemtype); + + return s; +} + +/* System and Pinout control */ +struct omap_sysctl_s { + struct omap_mpu_state_s *mpu; + + 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, target_phys_addr_t 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, target_phys_addr_t 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, target_phys_addr_t 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, target_phys_addr_t 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 CPUReadMemoryFunc * const omap_sysctl_readfn[] = { + omap_sysctl_read8, + omap_badwidth_read32, /* TODO */ + omap_sysctl_read, +}; + +static CPUWriteMemoryFunc * const omap_sysctl_writefn[] = { + omap_sysctl_write8, + omap_badwidth_write32, /* TODO */ + omap_sysctl_write, +}; + +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) +{ + int iomemtype; + struct omap_sysctl_s *s = (struct omap_sysctl_s *) + qemu_mallocz(sizeof(struct omap_sysctl_s)); + + s->mpu = mpu; + omap_sysctl_reset(s); + + iomemtype = l4_register_io_memory(omap_sysctl_readfn, + omap_sysctl_writefn, s); + omap_l4_attach(ta, 0, iomemtype); + + 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_inth_reset(mpu->ih[0]); + 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_gpif_reset(mpu->gpif); + omap_mcspi_reset(mpu->mcspi[0]); + omap_mcspi_reset(mpu->mcspi[1]); + omap_i2c_reset(mpu->i2c[0]); + omap_i2c_reset(mpu->i2c[1]); + cpu_reset(mpu->env); +} + +static int omap2_validate_addr(struct omap_mpu_state_s *s, + target_phys_addr_t 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(unsigned long sdram_size, + const char *core) +{ + struct omap_mpu_state_s *s = (struct omap_mpu_state_s *) + qemu_mallocz(sizeof(struct omap_mpu_state_s)); + ram_addr_t sram_base, q2_base; + qemu_irq *cpu_irq; + qemu_irq dma_irqs[4]; + omap_clk gpio_clks[4]; + DriveInfo *dinfo; + int i; + + /* Core */ + s->mpu_model = omap2420; + s->env = cpu_init(core ?: "arm1136-r2"); + if (!s->env) { + 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_irqs(omap_mpu_wakeup, s, 1)[0]; + + /* Clocks */ + omap_clk_init(s); + + /* Memory-mapped stuff */ + cpu_register_physical_memory(OMAP2_Q2_BASE, s->sdram_size, + (q2_base = qemu_ram_alloc(NULL, "omap2.dram", + s->sdram_size)) | IO_MEM_RAM); + cpu_register_physical_memory(OMAP2_SRAM_BASE, s->sram_size, + (sram_base = qemu_ram_alloc(NULL, "omap2.sram", + s->sram_size)) | IO_MEM_RAM); + + s->l4 = omap_l4_init(OMAP2_L4_BASE, 54); + + /* Actually mapped at any 2K boundary in the ARM11 private-peripheral if */ + cpu_irq = arm_pic_init_cpu(s->env); + s->ih[0] = omap2_inth_init(0x480fe000, 0x1000, 3, &s->irq[0], + cpu_irq[ARM_PIC_CPU_IRQ], cpu_irq[ARM_PIC_CPU_FIQ], + omap_findclk(s, "mpu_intc_fclk"), + omap_findclk(s, "mpu_intc_iclk")); + + s->prcm = omap_prcm_init(omap_l4tao(s->l4, 3), + s->irq[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] = + s->irq[omap2_dma_irq_map[i].ih][omap2_dma_irq_map[i].intr]; + s->dma = omap_dma4_init(0x48056000, dma_irqs, 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_ram(s->dma, q2_base, OMAP2_Q2_BASE, s->sdram_size); + soc_dma_port_add_mem_ram(s->dma, sram_base, OMAP2_SRAM_BASE, s->sram_size); + + s->uart[0] = omap2_uart_init(omap_l4ta(s->l4, 19), + s->irq[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(omap_l4ta(s->l4, 20), + s->irq[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(omap_l4ta(s->l4, 21), + s->irq[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), + s->irq[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), + s->irq[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), + s->irq[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), + s->irq[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), + s->irq[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), + s->irq[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), + s->irq[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), + s->irq[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), + s->irq[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), + s->irq[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), + s->irq[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), + s->irq[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] = omap2_i2c_init(omap_l4tao(s->l4, 5), + s->irq[0][OMAP_INT_24XX_I2C1_IRQ], + &s->drq[OMAP24XX_DMA_I2C1_TX], + omap_findclk(s, "i2c1.fclk"), + omap_findclk(s, "i2c1.iclk")); + s->i2c[1] = omap2_i2c_init(omap_l4tao(s->l4, 6), + s->irq[0][OMAP_INT_24XX_I2C2_IRQ], + &s->drq[OMAP24XX_DMA_I2C2_TX], + omap_findclk(s, "i2c2.fclk"), + omap_findclk(s, "i2c2.iclk")); + + gpio_clks[0] = omap_findclk(s, "gpio1_dbclk"); + gpio_clks[1] = omap_findclk(s, "gpio2_dbclk"); + gpio_clks[2] = omap_findclk(s, "gpio3_dbclk"); + gpio_clks[3] = omap_findclk(s, "gpio4_dbclk"); + s->gpif = omap2_gpio_init(omap_l4ta(s->l4, 3), + &s->irq[0][OMAP_INT_24XX_GPIO_BANK1], + gpio_clks, omap_findclk(s, "gpio_iclk"), 4); + + s->sdrc = omap_sdrc_init(0x68009000); + s->gpmc = omap_gpmc_init(0x6800a000, s->irq[0][OMAP_INT_24XX_GPMC_IRQ]); + + 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), dinfo->bdrv, + s->irq[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, + s->irq[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, + s->irq[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), 0x68000800, + /* XXX wire M_IRQ_25, D_L2_IRQ_30 and I_IRQ_13 together */ + s->irq[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), 0x54000000, + s->irq[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), + s->irq[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/omap_clk.c b/hw/omap_clk.c new file mode 100644 index 0000000..6bcabef --- /dev/null +++ b/hw/omap_clk.c @@ -0,0 +1,1260 @@ +/* + * OMAP clocks. + * + * Copyright (C) 2006-2008 Andrzej Zaborowski + * + * 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 . + */ +#include "hw.h" +#include "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[4] = { + { + .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, + }, +}; + +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 *) qemu_mallocz(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/omap_dma.c b/hw/omap_dma.c new file mode 100644 index 0000000..8e2dcc9 --- /dev/null +++ b/hw/omap_dma.c @@ -0,0 +1,2082 @@ +/* + * TI OMAP DMA gigacell. + * + * Copyright (C) 2006-2008 Andrzej Zaborowski + * Copyright (C) 2007-2008 Lauro Ramos Venancio + * + * 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 . + */ +#include "qemu-common.h" +#include "qemu-timer.h" +#include "omap.h" +#include "irq.h" +#include "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]; + target_phys_addr_t 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 { + target_phys_addr_t 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; + + 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) +{ + return 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 & (1 << 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 & (1 << 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((target_phys_addr_t) 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((target_phys_addr_t) 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 |= 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 |= 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 |= 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 |= 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 |= 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 |= 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 uint32_t omap_dma_read(void *opaque, target_phys_addr_t addr) +{ + struct omap_dma_s *s = (struct omap_dma_s *) opaque; + int reg, ch; + uint16_t ret; + + 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, target_phys_addr_t addr, + uint32_t value) +{ + struct omap_dma_s *s = (struct omap_dma_s *) opaque; + int reg, ch; + + 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 CPUReadMemoryFunc * const omap_dma_readfn[] = { + omap_badwidth_read16, + omap_dma_read, + omap_badwidth_read16, +}; + +static CPUWriteMemoryFunc * const omap_dma_writefn[] = { + omap_badwidth_write16, + omap_dma_write, + omap_badwidth_write16, +}; + +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 & (1 << drq)) { + s->dma->drqbmp |= 1 << drq; + omap_dma_process_request(s, drq); + } + } else + s->dma->drqbmp &= ~(1 << 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(target_phys_addr_t base, qemu_irq *irqs, + qemu_irq lcd_irq, struct omap_mpu_state_s *mpu, omap_clk clk, + enum omap_dma_model model) +{ + int iomemtype, num_irqs, memsize, i; + struct omap_dma_s *s = (struct omap_dma_s *) + qemu_mallocz(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_irqs(omap_dma_clk_update, s, 1)[0]); + omap_dma_reset(s->dma); + omap_dma_clk_update(s, 0, 1); + + iomemtype = cpu_register_io_memory(omap_dma_readfn, + omap_dma_writefn, s, DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(base, memsize, iomemtype); + + 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 uint32_t omap_dma4_read(void *opaque, target_phys_addr_t addr) +{ + struct omap_dma_s *s = (struct omap_dma_s *) opaque; + int irqn = 0, chnum; + struct omap_dma_channel_s *ch; + + switch (addr) { + case 0x00: /* DMA4_REVISION */ + return 0x40; + + case 0x14: /* DMA4_IRQSTATUS_L3 */ + irqn ++; + case 0x10: /* DMA4_IRQSTATUS_L2 */ + irqn ++; + case 0x0c: /* DMA4_IRQSTATUS_L1 */ + irqn ++; + case 0x08: /* DMA4_IRQSTATUS_L0 */ + return s->irqstat[irqn]; + + case 0x24: /* DMA4_IRQENABLE_L3 */ + irqn ++; + case 0x20: /* DMA4_IRQENABLE_L2 */ + irqn ++; + case 0x1c: /* DMA4_IRQENABLE_L1 */ + irqn ++; + 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, target_phys_addr_t addr, + uint32_t value) +{ + struct omap_dma_s *s = (struct omap_dma_s *) opaque; + int chnum, irqn = 0; + struct omap_dma_channel_s *ch; + + switch (addr) { + case 0x14: /* DMA4_IRQSTATUS_L3 */ + irqn ++; + case 0x10: /* DMA4_IRQSTATUS_L2 */ + irqn ++; + case 0x0c: /* DMA4_IRQSTATUS_L1 */ + irqn ++; + 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 ++; + case 0x20: /* DMA4_IRQENABLE_L2 */ + irqn ++; + case 0x1c: /* DMA4_IRQENABLE_L1 */ + irqn ++; + 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] = (target_phys_addr_t) (uint32_t) value; + ch->set_update = 1; + break; + + case 0x20: /* DMA4_CDSA */ + ch->addr[1] = (target_phys_addr_t) (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 CPUReadMemoryFunc * const omap_dma4_readfn[] = { + omap_badwidth_read16, + omap_dma4_read, + omap_dma4_read, +}; + +static CPUWriteMemoryFunc * const omap_dma4_writefn[] = { + omap_badwidth_write16, + omap_dma4_write, + omap_dma4_write, +}; + +struct soc_dma_s *omap_dma4_init(target_phys_addr_t base, qemu_irq *irqs, + struct omap_mpu_state_s *mpu, int fifo, + int chans, omap_clk iclk, omap_clk fclk) +{ + int iomemtype, i; + struct omap_dma_s *s = (struct omap_dma_s *) + qemu_mallocz(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_irqs(omap_dma_clk_update, s, 1)[0]); + omap_dma_reset(s->dma); + omap_dma_clk_update(s, 0, !!s->dma->freq); + + iomemtype = cpu_register_io_memory(omap_dma4_readfn, + omap_dma4_writefn, s, DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(base, 0x1000, iomemtype); + + 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/omap_dss.c b/hw/omap_dss.c new file mode 100644 index 0000000..afe287a --- /dev/null +++ b/hw/omap_dss.c @@ -0,0 +1,1068 @@ +/* + * OMAP2 Display Subsystem. + * + * Copyright (C) 2008 Nokia Corporation + * Written by Andrzej Zaborowski + * + * 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 . + */ +#include "hw.h" +#include "console.h" +#include "omap.h" + +struct omap_dss_s { + qemu_irq irq; + qemu_irq drq; + DisplayState *state; + + 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; + + target_phys_addr_t 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 uint32_t omap_diss_read(void *opaque, target_phys_addr_t addr) +{ + struct omap_dss_s *s = (struct omap_dss_s *) opaque; + + 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, target_phys_addr_t addr, + uint32_t value) +{ + struct omap_dss_s *s = (struct omap_dss_s *) opaque; + + 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 CPUReadMemoryFunc * const omap_diss1_readfn[] = { + omap_badwidth_read32, + omap_badwidth_read32, + omap_diss_read, +}; + +static CPUWriteMemoryFunc * const omap_diss1_writefn[] = { + omap_badwidth_write32, + omap_badwidth_write32, + omap_diss_write, +}; + +static uint32_t omap_disc_read(void *opaque, target_phys_addr_t addr) +{ + struct omap_dss_s *s = (struct omap_dss_s *) opaque; + + 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, target_phys_addr_t addr, + uint32_t value) +{ + struct omap_dss_s *s = (struct omap_dss_s *) opaque; + + 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", __FUNCTION__); + 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] = (target_phys_addr_t) value; + s->dispc.invalidate = 1; + break; + case 0x084: /* DISPC_GFX_BA1 */ + s->dispc.l[0].addr[1] = (target_phys_addr_t) 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] = (target_phys_addr_t) 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 CPUReadMemoryFunc * const omap_disc1_readfn[] = { + omap_badwidth_read32, + omap_badwidth_read32, + omap_disc_read, +}; + +static CPUWriteMemoryFunc * const omap_disc1_writefn[] = { + omap_badwidth_write32, + omap_badwidth_write32, + omap_disc_write, +}; + +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; + target_phys_addr_t len; + target_phys_addr_t data_addr; + int pitch; + static void *bounce_buffer; + static target_phys_addr_t 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 = qemu_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 uint32_t omap_rfbi_read(void *opaque, target_phys_addr_t addr) +{ + struct omap_dss_s *s = (struct omap_dss_s *) opaque; + + 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, target_phys_addr_t addr, + uint32_t value) +{ + struct omap_dss_s *s = (struct omap_dss_s *) opaque; + + 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[0]->read(s->rfbi.chip[0]->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[0]->read(s->rfbi.chip[0]->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 CPUReadMemoryFunc * const omap_rfbi1_readfn[] = { + omap_badwidth_read32, + omap_badwidth_read32, + omap_rfbi_read, +}; + +static CPUWriteMemoryFunc * const omap_rfbi1_writefn[] = { + omap_badwidth_write32, + omap_badwidth_write32, + omap_rfbi_write, +}; + +static uint32_t omap_venc_read(void *opaque, target_phys_addr_t 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, target_phys_addr_t addr, + uint32_t value) +{ + 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 CPUReadMemoryFunc * const omap_venc1_readfn[] = { + omap_badwidth_read32, + omap_badwidth_read32, + omap_venc_read, +}; + +static CPUWriteMemoryFunc * const omap_venc1_writefn[] = { + omap_badwidth_write32, + omap_badwidth_write32, + omap_venc_write, +}; + +static uint32_t omap_im3_read(void *opaque, target_phys_addr_t 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, target_phys_addr_t addr, + uint32_t value) +{ + 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 CPUReadMemoryFunc * const omap_im3_readfn[] = { + omap_badwidth_read32, + omap_badwidth_read32, + omap_im3_read, +}; + +static CPUWriteMemoryFunc * const omap_im3_writefn[] = { + omap_badwidth_write32, + omap_badwidth_write32, + omap_im3_write, +}; + +struct omap_dss_s *omap_dss_init(struct omap_target_agent_s *ta, + target_phys_addr_t l3_base, + qemu_irq irq, qemu_irq drq, + omap_clk fck1, omap_clk fck2, omap_clk ck54m, + omap_clk ick1, omap_clk ick2) +{ + int iomemtype[5]; + struct omap_dss_s *s = (struct omap_dss_s *) + qemu_mallocz(sizeof(struct omap_dss_s)); + + s->irq = irq; + s->drq = drq; + omap_dss_reset(s); + + iomemtype[0] = l4_register_io_memory(omap_diss1_readfn, + omap_diss1_writefn, s); + iomemtype[1] = l4_register_io_memory(omap_disc1_readfn, + omap_disc1_writefn, s); + iomemtype[2] = l4_register_io_memory(omap_rfbi1_readfn, + omap_rfbi1_writefn, s); + iomemtype[3] = l4_register_io_memory(omap_venc1_readfn, + omap_venc1_writefn, s); + iomemtype[4] = cpu_register_io_memory(omap_im3_readfn, + omap_im3_writefn, s, DEVICE_NATIVE_ENDIAN); + omap_l4_attach(ta, 0, iomemtype[0]); + omap_l4_attach(ta, 1, iomemtype[1]); + omap_l4_attach(ta, 2, iomemtype[2]); + omap_l4_attach(ta, 3, iomemtype[3]); + cpu_register_physical_memory(l3_base, 0x1000, iomemtype[4]); + +#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/omap_gpio.c b/hw/omap_gpio.c new file mode 100644 index 0000000..478f7d9 --- /dev/null +++ b/hw/omap_gpio.c @@ -0,0 +1,725 @@ +/* + * TI OMAP processors GPIO emulation. + * + * Copyright (C) 2006-2008 Andrzej Zaborowski + * 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 . + */ + +#include "hw.h" +#include "omap.h" +/* General-Purpose I/O */ +struct omap_gpio_s { + qemu_irq irq; + qemu_irq *in; + 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; +}; + +static void omap_gpio_set(void *opaque, int line, int level) +{ + struct omap_gpio_s *s = (struct omap_gpio_s *) opaque; + 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 uint32_t omap_gpio_read(void *opaque, target_phys_addr_t addr) +{ + struct omap_gpio_s *s = (struct omap_gpio_s *) opaque; + int offset = addr & OMAP_MPUI_REG_MASK; + + 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, target_phys_addr_t addr, + uint32_t value) +{ + struct omap_gpio_s *s = (struct omap_gpio_s *) opaque; + int offset = addr & OMAP_MPUI_REG_MASK; + uint16_t diff; + int ln; + + 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 = ffs(diff))) { + ln --; + 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 = ffs(diff))) { + ln --; + 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 CPUReadMemoryFunc * const omap_gpio_readfn[] = { + omap_badwidth_read16, + omap_gpio_read, + omap_badwidth_read16, +}; + +static CPUWriteMemoryFunc * const omap_gpio_writefn[] = { + omap_badwidth_write16, + omap_gpio_write, + omap_badwidth_write16, +}; + +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 omap_gpio_s *omap_gpio_init(target_phys_addr_t base, + qemu_irq irq, omap_clk clk) +{ + int iomemtype; + struct omap_gpio_s *s = (struct omap_gpio_s *) + qemu_mallocz(sizeof(struct omap_gpio_s)); + + s->irq = irq; + s->in = qemu_allocate_irqs(omap_gpio_set, s, 16); + omap_gpio_reset(s); + + iomemtype = cpu_register_io_memory(omap_gpio_readfn, + omap_gpio_writefn, s, DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(base, 0x1000, iomemtype); + + return s; +} + +qemu_irq *omap_gpio_in_get(struct omap_gpio_s *s) +{ + return s->in; +} + +void omap_gpio_out_set(struct omap_gpio_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; +} + +/* General-Purpose Interface of OMAP2 */ +struct omap2_gpio_s { + qemu_irq irq[2]; + qemu_irq wkup; + qemu_irq *in; + qemu_irq handler[32]; + + 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; +}; + +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 = ffs(diff))) { + ln --; + 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_module_set(void *opaque, int line, int level) +{ + struct omap2_gpio_s *s = (struct omap2_gpio_s *) opaque; + + 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, target_phys_addr_t addr) +{ + struct omap2_gpio_s *s = (struct omap2_gpio_s *) opaque; + + switch (addr) { + case 0x00: /* GPIO_REVISION */ + return 0x18; + + 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, target_phys_addr_t 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 = ffs(diff))) { + 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, target_phys_addr_t addr) +{ + return omap2_gpio_module_readp(opaque, addr) >> ((addr & 3) << 3); +} + +static void omap2_gpio_module_writep(void *opaque, target_phys_addr_t 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 CPUReadMemoryFunc * const omap2_gpio_module_readfn[] = { + omap2_gpio_module_readp, + omap2_gpio_module_readp, + omap2_gpio_module_read, +}; + +static CPUWriteMemoryFunc * const omap2_gpio_module_writefn[] = { + omap2_gpio_module_writep, + omap2_gpio_module_writep, + omap2_gpio_module_write, +}; + +static void omap2_gpio_module_init(struct omap2_gpio_s *s, + struct omap_target_agent_s *ta, int region, + qemu_irq mpu, qemu_irq dsp, qemu_irq wkup, + omap_clk fclk, omap_clk iclk) +{ + int iomemtype; + + s->irq[0] = mpu; + s->irq[1] = dsp; + s->wkup = wkup; + s->in = qemu_allocate_irqs(omap2_gpio_module_set, s, 32); + + iomemtype = l4_register_io_memory(omap2_gpio_module_readfn, + omap2_gpio_module_writefn, s); + omap_l4_attach(ta, region, iomemtype); +} + +struct omap_gpif_s { + struct omap2_gpio_s module[5]; + int modules; + + int autoidle; + int gpo; +}; + +void omap_gpif_reset(struct omap_gpif_s *s) +{ + int i; + + for (i = 0; i < s->modules; i ++) + omap2_gpio_module_reset(s->module + i); + + s->autoidle = 0; + s->gpo = 0; +} + +static uint32_t omap_gpif_top_read(void *opaque, target_phys_addr_t addr) +{ + struct omap_gpif_s *s = (struct omap_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 omap_gpif_top_write(void *opaque, target_phys_addr_t addr, + uint32_t value) +{ + struct omap_gpif_s *s = (struct omap_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 */ + omap_gpif_reset(s); + s->autoidle = value & 1; + break; + + case 0x40: /* IPGENERICOCPSPL_GPO */ + s->gpo = value & 1; + break; + + default: + OMAP_BAD_REG(addr); + return; + } +} + +static CPUReadMemoryFunc * const omap_gpif_top_readfn[] = { + omap_gpif_top_read, + omap_gpif_top_read, + omap_gpif_top_read, +}; + +static CPUWriteMemoryFunc * const omap_gpif_top_writefn[] = { + omap_gpif_top_write, + omap_gpif_top_write, + omap_gpif_top_write, +}; + +struct omap_gpif_s *omap2_gpio_init(struct omap_target_agent_s *ta, + qemu_irq *irq, omap_clk *fclk, omap_clk iclk, int modules) +{ + int iomemtype, i; + struct omap_gpif_s *s = (struct omap_gpif_s *) + qemu_mallocz(sizeof(struct omap_gpif_s)); + int region[4] = { 0, 2, 4, 5 }; + + s->modules = modules; + for (i = 0; i < modules; i ++) + omap2_gpio_module_init(s->module + i, ta, region[i], + irq[i], NULL, NULL, fclk[i], iclk); + + omap_gpif_reset(s); + + iomemtype = l4_register_io_memory(omap_gpif_top_readfn, + omap_gpif_top_writefn, s); + omap_l4_attach(ta, 1, iomemtype); + + return s; +} + +qemu_irq *omap2_gpio_in_get(struct omap_gpif_s *s, int start) +{ + if (start >= s->modules * 32 || start < 0) + hw_error("%s: No GPIO line %i\n", __FUNCTION__, start); + return s->module[start >> 5].in + (start & 31); +} + +void omap2_gpio_out_set(struct omap_gpif_s *s, int line, qemu_irq handler) +{ + if (line >= s->modules * 32 || line < 0) + hw_error("%s: No GPIO line %i\n", __FUNCTION__, line); + s->module[line >> 5].handler[line & 31] = handler; +} diff --git a/hw/omap_gpmc.c b/hw/omap_gpmc.c new file mode 100644 index 0000000..8bf3343 --- /dev/null +++ b/hw/omap_gpmc.c @@ -0,0 +1,419 @@ +/* + * TI OMAP general purpose memory controller emulation. + * + * Copyright (C) 2007-2009 Nokia Corporation + * Original code written by Andrzej Zaborowski + * 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 . + */ +#include "hw.h" +#include "flash.h" +#include "omap.h" + +/* General-Purpose Memory Controller */ +struct omap_gpmc_s { + qemu_irq irq; + + uint8_t sysconfig; + uint16_t irqst; + uint16_t irqen; + uint16_t timeout; + uint16_t config; + uint32_t prefconfig[2]; + int prefcontrol; + int preffifo; + int prefcount; + struct omap_gpmc_cs_file_s { + uint32_t config[7]; + target_phys_addr_t base; + size_t size; + int iomemtype; + void (*base_update)(void *opaque, target_phys_addr_t new); + void (*unmap)(void *opaque); + void *opaque; + } cs_file[8]; + int ecc_cs; + int ecc_ptr; + uint32_t ecc_cfg; + ECCState ecc[9]; +}; + +static void omap_gpmc_int_update(struct omap_gpmc_s *s) +{ + qemu_set_irq(s->irq, s->irqen & s->irqst); +} + +static void omap_gpmc_cs_map(struct omap_gpmc_cs_file_s *f, int base, int mask) +{ + /* TODO: check for overlapping regions and report access errors */ + if ((mask != 0x8 && mask != 0xc && mask != 0xe && mask != 0xf) || + (base < 0 || base >= 0x40) || + (base & 0x0f & ~mask)) { + fprintf(stderr, "%s: wrong cs address mapping/decoding!\n", + __FUNCTION__); + return; + } + + if (!f->opaque) + return; + + f->base = base << 24; + f->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 size bytes + * starting from base. */ + if (f->iomemtype) + cpu_register_physical_memory(f->base, f->size, f->iomemtype); + + if (f->base_update) + f->base_update(f->opaque, f->base); +} + +static void omap_gpmc_cs_unmap(struct omap_gpmc_cs_file_s *f) +{ + if (f->size) { + if (f->unmap) + f->unmap(f->opaque); + if (f->iomemtype) + cpu_register_physical_memory(f->base, f->size, IO_MEM_UNASSIGNED); + f->base = 0; + f->size = 0; + } +} + +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); + s->timeout = 0; + s->config = 0xa00; + s->prefconfig[0] = 0x00004000; + s->prefconfig[1] = 0x00000000; + s->prefcontrol = 0; + s->preffifo = 0; + s->prefcount = 0; + for (i = 0; i < 8; i ++) { + if (s->cs_file[i].config[6] & (1 << 6)) /* CSVALID */ + omap_gpmc_cs_unmap(s->cs_file + i); + s->cs_file[i].config[0] = i ? 1 << 12 : 0; + 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 | (i ? 0 : 1 << 6); + if (s->cs_file[i].config[6] & (1 << 6)) /* CSVALID */ + omap_gpmc_cs_map(&s->cs_file[i], + s->cs_file[i].config[6] & 0x1f, /* MASKADDR */ + (s->cs_file[i].config[6] >> 8 & 0xf)); /* BASEADDR */ + } + omap_gpmc_cs_map(s->cs_file, 0, 0xf); + s->ecc_cs = 0; + s->ecc_ptr = 0; + s->ecc_cfg = 0x3fcff000; + for (i = 0; i < 9; i ++) + ecc_reset(&s->ecc[i]); +} + +static uint32_t omap_gpmc_read(void *opaque, target_phys_addr_t addr) +{ + struct omap_gpmc_s *s = (struct omap_gpmc_s *) opaque; + int cs; + struct omap_gpmc_cs_file_s *f; + + switch (addr) { + case 0x000: /* GPMC_REVISION */ + return 0x20; + + 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: /* GPMC_NAND_DATA */ + return 0; + } + break; + + case 0x1e0: /* GPMC_PREFETCH_CONFIG1 */ + return s->prefconfig[0]; + case 0x1e4: /* GPMC_PREFETCH_CONFIG2 */ + return s->prefconfig[1]; + case 0x1ec: /* GPMC_PREFETCH_CONTROL */ + return s->prefcontrol; + case 0x1f0: /* GPMC_PREFETCH_STATUS */ + return (s->preffifo << 24) | + ((s->preffifo > + ((s->prefconfig[0] >> 8) & 0x7f) ? 1 : 0) << 16) | + s->prefcount; + + 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, target_phys_addr_t addr, + uint32_t value) +{ + struct omap_gpmc_s *s = (struct omap_gpmc_s *) opaque; + int cs; + struct omap_gpmc_cs_file_s *f; + + 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 %i\n", + __FUNCTION__, value >> 3); + if (value & 2) + omap_gpmc_reset(s); + s->sysconfig = value & 0x19; + break; + + case 0x018: /* GPMC_IRQSTATUS */ + s->irqen = ~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) { + if (f->config[6] & (1 << 6)) /* CSVALID */ + omap_gpmc_cs_unmap(f); + if (value & (1 << 6)) /* CSVALID */ + omap_gpmc_cs_map(f, value & 0x1f, /* MASKADDR */ + (value >> 8 & 0xf)); /* BASEADDR */ + } + f->config[6] = value & 0x00000f7f; + break; + case 0x7c: /* GPMC_NAND_COMMAND */ + case 0x80: /* GPMC_NAND_ADDRESS */ + case 0x84: /* GPMC_NAND_DATA */ + break; + + default: + goto bad_reg; + } + break; + + case 0x1e0: /* GPMC_PREFETCH_CONFIG1 */ + s->prefconfig[0] = value & 0x7f8f7fbf; + /* TODO: update interrupts, fifos, dmas */ + break; + + case 0x1e4: /* GPMC_PREFETCH_CONFIG2 */ + s->prefconfig[1] = value & 0x3fff; + break; + + case 0x1ec: /* GPMC_PREFETCH_CONTROL */ + s->prefcontrol = value & 1; + if (s->prefcontrol) { + if (s->prefconfig[0] & 1) + s->preffifo = 0x40; + else + s->preffifo = 0x00; + } + /* TODO: start */ + 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 CPUReadMemoryFunc * const omap_gpmc_readfn[] = { + omap_badwidth_read32, /* TODO */ + omap_badwidth_read32, /* TODO */ + omap_gpmc_read, +}; + +static CPUWriteMemoryFunc * const omap_gpmc_writefn[] = { + omap_badwidth_write32, /* TODO */ + omap_badwidth_write32, /* TODO */ + omap_gpmc_write, +}; + +struct omap_gpmc_s *omap_gpmc_init(target_phys_addr_t base, qemu_irq irq) +{ + int iomemtype; + struct omap_gpmc_s *s = (struct omap_gpmc_s *) + qemu_mallocz(sizeof(struct omap_gpmc_s)); + + omap_gpmc_reset(s); + + iomemtype = cpu_register_io_memory(omap_gpmc_readfn, + omap_gpmc_writefn, s, DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(base, 0x1000, iomemtype); + + return s; +} + +void omap_gpmc_attach(struct omap_gpmc_s *s, int cs, int iomemtype, + void (*base_upd)(void *opaque, target_phys_addr_t new), + void (*unmap)(void *opaque), void *opaque) +{ + struct omap_gpmc_cs_file_s *f; + + if (cs < 0 || cs >= 8) { + fprintf(stderr, "%s: bad chip-select %i\n", __FUNCTION__, cs); + exit(-1); + } + f = &s->cs_file[cs]; + + f->iomemtype = iomemtype; + f->base_update = base_upd; + f->unmap = unmap; + f->opaque = opaque; + + if (f->config[6] & (1 << 6)) /* CSVALID */ + omap_gpmc_cs_map(f, f->config[6] & 0x1f, /* MASKADDR */ + (f->config[6] >> 8 & 0xf)); /* BASEADDR */ +} diff --git a/hw/omap_gptimer.c b/hw/omap_gptimer.c new file mode 100644 index 0000000..9c0f9f2 --- /dev/null +++ b/hw/omap_gptimer.c @@ -0,0 +1,484 @@ +/* + * TI OMAP2 general purpose timers emulation. + * + * Copyright (C) 2007-2008 Nokia Corporation + * Written by Andrzej Zaborowski + * + * 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 . + */ +#include "hw.h" +#include "qemu-timer.h" +#include "omap.h" + +/* GP timers */ +struct omap_gp_timer_s { + 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_get_clock(vm_clock) - 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_get_clock(vm_clock); + } +} + +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); + qemu_mod_timer(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); + qemu_mod_timer(timer->match, timer->time + matches); + } else + qemu_del_timer(timer->match); + } else { + qemu_del_timer(timer->timer); + qemu_del_timer(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_get_clock(vm_clock); + } + + 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_irqs(omap_gp_timer_clk_update, timer, 1)[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, target_phys_addr_t 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, target_phys_addr_t 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 CPUReadMemoryFunc * const omap_gp_timer_readfn[] = { + omap_badwidth_read32, + omap_gp_timer_readh, + omap_gp_timer_readw, +}; + +static void omap_gp_timer_write(void *opaque, target_phys_addr_t 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_get_clock(vm_clock); + s->val = value; + omap_gp_timer_update(s); + break; + + case 0x2c: /* TLDR */ + s->load_val = value; + break; + + case 0x30: /* TTGR */ + s->time = qemu_get_clock(vm_clock); + 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, target_phys_addr_t addr, + uint32_t value) +{ + struct omap_gp_timer_s *s = (struct omap_gp_timer_s *) opaque; + + if (addr & 2) + return omap_gp_timer_write(opaque, addr, (value << 16) | s->writeh); + else + s->writeh = (uint16_t) value; +} + +static CPUWriteMemoryFunc * const omap_gp_timer_writefn[] = { + omap_badwidth_write32, + omap_gp_timer_writeh, + omap_gp_timer_write, +}; + +struct omap_gp_timer_s *omap_gp_timer_init(struct omap_target_agent_s *ta, + qemu_irq irq, omap_clk fclk, omap_clk iclk) +{ + int iomemtype; + struct omap_gp_timer_s *s = (struct omap_gp_timer_s *) + qemu_mallocz(sizeof(struct omap_gp_timer_s)); + + s->ta = ta; + s->irq = irq; + s->clk = fclk; + s->timer = qemu_new_timer(vm_clock, omap_gp_timer_tick, s); + s->match = qemu_new_timer(vm_clock, omap_gp_timer_match, s); + s->in = qemu_allocate_irqs(omap_gp_timer_input, s, 1)[0]; + omap_gp_timer_reset(s); + omap_gp_timer_clk_setup(s); + + iomemtype = l4_register_io_memory(omap_gp_timer_readfn, + omap_gp_timer_writefn, s); + omap_l4_attach(ta, 0, iomemtype); + + return s; +} diff --git a/hw/omap_i2c.c b/hw/omap_i2c.c new file mode 100644 index 0000000..5cabb5a --- /dev/null +++ b/hw/omap_i2c.c @@ -0,0 +1,470 @@ +/* + * TI OMAP on-chip I2C controller. Only "new I2C" mode supported. + * + * Copyright (C) 2007 Andrzej Zaborowski + * + * 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 . + */ +#include "hw.h" +#include "i2c.h" +#include "omap.h" + +struct omap_i2c_s { + qemu_irq irq; + qemu_irq drq[2]; + i2c_bus *bus; + + uint8_t revision; + 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; +}; + +#define OMAP2_INTR_REV 0x34 +#define OMAP2_GC_REV 0x34 + +static void omap_i2c_interrupts_update(struct omap_i2c_s *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(struct omap_i2c_s *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 */ +} + +void omap_i2c_reset(struct omap_i2c_s *s) +{ + 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, target_phys_addr_t addr) +{ + struct omap_i2c_s *s = (struct omap_i2c_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 = ffs(s->stat & s->mask); + if (ret) + s->stat ^= 1 << (ret - 1); + 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, target_phys_addr_t addr, + uint32_t value) +{ + struct omap_i2c_s *s = (struct omap_i2c_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(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(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, target_phys_addr_t addr, + uint32_t value) +{ + struct omap_i2c_s *s = (struct omap_i2c_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 CPUReadMemoryFunc * const omap_i2c_readfn[] = { + omap_badwidth_read16, + omap_i2c_read, + omap_badwidth_read16, +}; + +static CPUWriteMemoryFunc * const omap_i2c_writefn[] = { + omap_i2c_writeb, /* Only the last fifo write can be 8 bit. */ + omap_i2c_write, + omap_badwidth_write16, +}; + +struct omap_i2c_s *omap_i2c_init(target_phys_addr_t base, + qemu_irq irq, qemu_irq *dma, omap_clk clk) +{ + int iomemtype; + struct omap_i2c_s *s = (struct omap_i2c_s *) + qemu_mallocz(sizeof(struct omap_i2c_s)); + + /* TODO: set a value greater or equal to real hardware */ + s->revision = 0x11; + s->irq = irq; + s->drq[0] = dma[0]; + s->drq[1] = dma[1]; + s->bus = i2c_init_bus(NULL, "i2c"); + omap_i2c_reset(s); + + iomemtype = cpu_register_io_memory(omap_i2c_readfn, + omap_i2c_writefn, s, DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(base, 0x800, iomemtype); + + return s; +} + +struct omap_i2c_s *omap2_i2c_init(struct omap_target_agent_s *ta, + qemu_irq irq, qemu_irq *dma, omap_clk fclk, omap_clk iclk) +{ + int iomemtype; + struct omap_i2c_s *s = (struct omap_i2c_s *) + qemu_mallocz(sizeof(struct omap_i2c_s)); + + s->revision = 0x34; + s->irq = irq; + s->drq[0] = dma[0]; + s->drq[1] = dma[1]; + s->bus = i2c_init_bus(NULL, "i2c"); + omap_i2c_reset(s); + + iomemtype = l4_register_io_memory(omap_i2c_readfn, + omap_i2c_writefn, s); + omap_l4_attach(ta, 0, iomemtype); + + return s; +} + +i2c_bus *omap_i2c_bus(struct omap_i2c_s *s) +{ + return s->bus; +} diff --git a/hw/omap_intc.c b/hw/omap_intc.c new file mode 100644 index 0000000..001e20b --- /dev/null +++ b/hw/omap_intc.c @@ -0,0 +1,598 @@ +/* + * TI OMAP interrupt controller emulation. + * + * Copyright (C) 2006-2008 Andrzej Zaborowski + * 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 . + */ +#include "hw.h" +#include "omap.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]; +}; + +struct omap_intr_handler_s { + qemu_irq *pins; + qemu_irq parent_intr[2]; + unsigned char nbanks; + int level_only; + + /* state */ + uint32_t new_agr[2]; + int sir_intr[2]; + int autoidle; + uint32_t mask; + struct omap_intr_handler_bank_s bank[]; +}; + +inline qemu_irq omap_inth_get_pin(struct omap_intr_handler_s *s, int n) +{ + return s->pins[n]; +} + +static void omap_inth_sir_update(struct omap_intr_handler_s *s, int is_fiq) +{ + int i, j, sir_intr, p_intr, p, f; + 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); + for (f = ffs(level), i = f - 1, level >>= f - 1; f; i += f, + level >>= f) { + p = s->bank[j].priority[i]; + if (p <= p_intr) { + p_intr = p; + sir_intr = 32 * j + i; + } + f = ffs(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 uint32_t omap_inth_read(void *opaque, target_phys_addr_t addr) +{ + 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, target_phys_addr_t addr, + uint32_t value) +{ + 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 CPUReadMemoryFunc * const omap_inth_readfn[] = { + omap_badwidth_read32, + omap_badwidth_read32, + omap_inth_read, +}; + +static CPUWriteMemoryFunc * const omap_inth_writefn[] = { + omap_inth_write, + omap_inth_write, + omap_inth_write, +}; + +void omap_inth_reset(struct omap_intr_handler_s *s) +{ + 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); +} + +struct omap_intr_handler_s *omap_inth_init(target_phys_addr_t base, + unsigned long size, unsigned char nbanks, qemu_irq **pins, + qemu_irq parent_irq, qemu_irq parent_fiq, omap_clk clk) +{ + int iomemtype; + struct omap_intr_handler_s *s = (struct omap_intr_handler_s *) + qemu_mallocz(sizeof(struct omap_intr_handler_s) + + sizeof(struct omap_intr_handler_bank_s) * nbanks); + + s->parent_intr[0] = parent_irq; + s->parent_intr[1] = parent_fiq; + s->nbanks = nbanks; + s->pins = qemu_allocate_irqs(omap_set_intr, s, nbanks * 32); + if (pins) + *pins = s->pins; + + omap_inth_reset(s); + + iomemtype = cpu_register_io_memory(omap_inth_readfn, + omap_inth_writefn, s, DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(base, size, iomemtype); + + return s; +} + +static uint32_t omap2_inth_read(void *opaque, target_phys_addr_t addr) +{ + 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]; + } + } + + switch (offset) { + case 0x00: /* INTC_REVISION */ + return 0x21; + + 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, target_phys_addr_t addr, + uint32_t value) +{ + 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]; + } + } + + switch (offset) { + case 0x10: /* INTC_SYSCONFIG */ + s->autoidle &= 4; + s->autoidle |= (value & 1) << 2; + if (value & 2) /* SOFTRESET */ + omap_inth_reset(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 CPUReadMemoryFunc * const omap2_inth_readfn[] = { + omap_badwidth_read32, + omap_badwidth_read32, + omap2_inth_read, +}; + +static CPUWriteMemoryFunc * const omap2_inth_writefn[] = { + omap2_inth_write, + omap2_inth_write, + omap2_inth_write, +}; + +struct omap_intr_handler_s *omap2_inth_init(target_phys_addr_t base, + int size, int nbanks, qemu_irq **pins, + qemu_irq parent_irq, qemu_irq parent_fiq, + omap_clk fclk, omap_clk iclk) +{ + int iomemtype; + struct omap_intr_handler_s *s = (struct omap_intr_handler_s *) + qemu_mallocz(sizeof(struct omap_intr_handler_s) + + sizeof(struct omap_intr_handler_bank_s) * nbanks); + + s->parent_intr[0] = parent_irq; + s->parent_intr[1] = parent_fiq; + s->nbanks = nbanks; + s->level_only = 1; + s->pins = qemu_allocate_irqs(omap_set_intr_noedge, s, nbanks * 32); + if (pins) + *pins = s->pins; + + omap_inth_reset(s); + + iomemtype = cpu_register_io_memory(omap2_inth_readfn, + omap2_inth_writefn, s, DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(base, size, iomemtype); + + return s; +} diff --git a/hw/omap_l4.c b/hw/omap_l4.c new file mode 100644 index 0000000..4af0ca8 --- /dev/null +++ b/hw/omap_l4.c @@ -0,0 +1,272 @@ +/* + * TI OMAP L4 interconnect emulation. + * + * Copyright (C) 2007-2009 Nokia Corporation + * Written by Andrzej Zaborowski + * + * 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 . + */ +#include "hw.h" +#include "omap.h" + +#ifdef L4_MUX_HACK +static int omap_l4_io_entries; +static int omap_cpu_io_entry; +static struct omap_l4_entry { + CPUReadMemoryFunc * const *mem_read; + CPUWriteMemoryFunc * const *mem_write; + void *opaque; +} *omap_l4_io_entry; +static CPUReadMemoryFunc * const *omap_l4_io_readb_fn; +static CPUReadMemoryFunc * const *omap_l4_io_readh_fn; +static CPUReadMemoryFunc * const *omap_l4_io_readw_fn; +static CPUWriteMemoryFunc * const *omap_l4_io_writeb_fn; +static CPUWriteMemoryFunc * const *omap_l4_io_writeh_fn; +static CPUWriteMemoryFunc * const *omap_l4_io_writew_fn; +static void **omap_l4_io_opaque; + +int l4_register_io_memory(CPUReadMemoryFunc * const *mem_read, + CPUWriteMemoryFunc * const *mem_write, void *opaque) +{ + omap_l4_io_entry[omap_l4_io_entries].mem_read = mem_read; + omap_l4_io_entry[omap_l4_io_entries].mem_write = mem_write; + omap_l4_io_entry[omap_l4_io_entries].opaque = opaque; + + return omap_l4_io_entries ++; +} + +static uint32_t omap_l4_io_readb(void *opaque, target_phys_addr_t addr) +{ + unsigned int i = (addr - OMAP2_L4_BASE) >> TARGET_PAGE_BITS; + + return omap_l4_io_readb_fn[i](omap_l4_io_opaque[i], addr); +} + +static uint32_t omap_l4_io_readh(void *opaque, target_phys_addr_t addr) +{ + unsigned int i = (addr - OMAP2_L4_BASE) >> TARGET_PAGE_BITS; + + return omap_l4_io_readh_fn[i](omap_l4_io_opaque[i], addr); +} + +static uint32_t omap_l4_io_readw(void *opaque, target_phys_addr_t addr) +{ + unsigned int i = (addr - OMAP2_L4_BASE) >> TARGET_PAGE_BITS; + + return omap_l4_io_readw_fn[i](omap_l4_io_opaque[i], addr); +} + +static void omap_l4_io_writeb(void *opaque, target_phys_addr_t addr, + uint32_t value) +{ + unsigned int i = (addr - OMAP2_L4_BASE) >> TARGET_PAGE_BITS; + + return omap_l4_io_writeb_fn[i](omap_l4_io_opaque[i], addr, value); +} + +static void omap_l4_io_writeh(void *opaque, target_phys_addr_t addr, + uint32_t value) +{ + unsigned int i = (addr - OMAP2_L4_BASE) >> TARGET_PAGE_BITS; + + return omap_l4_io_writeh_fn[i](omap_l4_io_opaque[i], addr, value); +} + +static void omap_l4_io_writew(void *opaque, target_phys_addr_t addr, + uint32_t value) +{ + unsigned int i = (addr - OMAP2_L4_BASE) >> TARGET_PAGE_BITS; + + return omap_l4_io_writew_fn[i](omap_l4_io_opaque[i], addr, value); +} + +static CPUReadMemoryFunc * const omap_l4_io_readfn[] = { + omap_l4_io_readb, + omap_l4_io_readh, + omap_l4_io_readw, +}; + +static CPUWriteMemoryFunc * const omap_l4_io_writefn[] = { + omap_l4_io_writeb, + omap_l4_io_writeh, + omap_l4_io_writew, +}; +#else +int l4_register_io_memory(CPUReadMemoryFunc * const *mem_read, + CPUWriteMemoryFunc * const *mem_write, + void *opaque) +{ + return cpu_register_io_memory(mem_read, mem_write, opaque, + DEVICE_NATIVE_ENDIAN); +} +#endif + +struct omap_l4_s { + target_phys_addr_t base; + int ta_num; + struct omap_target_agent_s ta[0]; +}; + +struct omap_l4_s *omap_l4_init(target_phys_addr_t base, int ta_num) +{ + struct omap_l4_s *bus = qemu_mallocz( + sizeof(*bus) + ta_num * sizeof(*bus->ta)); + + bus->ta_num = ta_num; + bus->base = base; + +#ifdef L4_MUX_HACK + omap_l4_io_entries = 1; + omap_l4_io_entry = qemu_mallocz(125 * sizeof(*omap_l4_io_entry)); + + omap_cpu_io_entry = + cpu_register_io_memory(omap_l4_io_readfn, + omap_l4_io_writefn, bus, DEVICE_NATIVE_ENDIAN); +# define L4_PAGES (0xb4000 / TARGET_PAGE_SIZE) + omap_l4_io_readb_fn = qemu_mallocz(sizeof(void *) * L4_PAGES); + omap_l4_io_readh_fn = qemu_mallocz(sizeof(void *) * L4_PAGES); + omap_l4_io_readw_fn = qemu_mallocz(sizeof(void *) * L4_PAGES); + omap_l4_io_writeb_fn = qemu_mallocz(sizeof(void *) * L4_PAGES); + omap_l4_io_writeh_fn = qemu_mallocz(sizeof(void *) * L4_PAGES); + omap_l4_io_writew_fn = qemu_mallocz(sizeof(void *) * L4_PAGES); + omap_l4_io_opaque = qemu_mallocz(sizeof(void *) * L4_PAGES); +#endif + + return bus; +} + +static uint32_t omap_l4ta_read(void *opaque, target_phys_addr_t addr) +{ + struct omap_target_agent_s *s = (struct omap_target_agent_s *) opaque; + + 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, target_phys_addr_t addr, + uint32_t value) +{ + struct omap_target_agent_s *s = (struct omap_target_agent_s *) opaque; + + 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 CPUReadMemoryFunc * const omap_l4ta_readfn[] = { + omap_badwidth_read16, + omap_l4ta_read, + omap_badwidth_read16, +}; + +static CPUWriteMemoryFunc * const omap_l4ta_writefn[] = { + omap_badwidth_write32, + omap_badwidth_write32, + omap_l4ta_write, +}; + +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, iomemtype; + 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 = ®ions[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 */ + + iomemtype = l4_register_io_memory(omap_l4ta_readfn, + omap_l4ta_writefn, ta); + ta->base = omap_l4_attach(ta, info->ta_region, iomemtype); + + return ta; +} + +target_phys_addr_t omap_l4_attach(struct omap_target_agent_s *ta, int region, + int iotype) +{ + target_phys_addr_t base; + ssize_t size; +#ifdef L4_MUX_HACK + int i; +#endif + + 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; + size = ta->start[region].size; + if (iotype) { +#ifndef L4_MUX_HACK + cpu_register_physical_memory(base, size, iotype); +#else + cpu_register_physical_memory(base, size, omap_cpu_io_entry); + i = (base - ta->bus->base) / TARGET_PAGE_SIZE; + for (; size > 0; size -= TARGET_PAGE_SIZE, i ++) { + omap_l4_io_readb_fn[i] = omap_l4_io_entry[iotype].mem_read[0]; + omap_l4_io_readh_fn[i] = omap_l4_io_entry[iotype].mem_read[1]; + omap_l4_io_readw_fn[i] = omap_l4_io_entry[iotype].mem_read[2]; + omap_l4_io_writeb_fn[i] = omap_l4_io_entry[iotype].mem_write[0]; + omap_l4_io_writeh_fn[i] = omap_l4_io_entry[iotype].mem_write[1]; + omap_l4_io_writew_fn[i] = omap_l4_io_entry[iotype].mem_write[2]; + omap_l4_io_opaque[i] = omap_l4_io_entry[iotype].opaque; + } +#endif + } + + return base; +} diff --git a/hw/omap_lcd_template.h b/hw/omap_lcd_template.h new file mode 100644 index 0000000..2fb96f8 --- /dev/null +++ b/hw/omap_lcd_template.h @@ -0,0 +1,175 @@ +/* + * QEMU OMAP LCD Emulator templates + * + * Copyright (c) 2006 Andrzej Zaborowski + * + * 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_raw((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_raw((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_raw((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_raw((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_raw((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/omap_lcdc.c b/hw/omap_lcdc.c new file mode 100644 index 0000000..0c2c550 --- /dev/null +++ b/hw/omap_lcdc.c @@ -0,0 +1,461 @@ +/* + * OMAP LCD controller. + * + * Copyright (C) 2006-2007 Andrzej Zaborowski + * + * 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 . + */ +#include "hw.h" +#include "console.h" +#include "omap.h" +#include "framebuffer.h" + +struct omap_lcd_panel_s { + qemu_irq irq; + DisplayState *state; + ram_addr_t imif_base; + ram_addr_t emiff_base; + + 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); +} + +#include "pixel_ops.h" + +#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; + draw_line_func draw_line; + int size, height, first, last; + int width, linesize, step, bpp, frame_offset; + target_phys_addr_t frame_base; + + if (!omap_lcd || omap_lcd->plm == 1 || + !omap_lcd->enable || !ds_get_bits_per_pixel(omap_lcd->state)) + 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[ds_get_bits_per_pixel(omap_lcd->state)]; + bpp = 2; + break; + + case 2: + draw_line = draw_line_table4[ds_get_bits_per_pixel(omap_lcd->state)]; + bpp = 4; + break; + + case 3: + draw_line = draw_line_table8[ds_get_bits_per_pixel(omap_lcd->state)]; + bpp = 8; + break; + + case 4 ... 7: + if (!omap_lcd->tft) + draw_line = draw_line_table12[ds_get_bits_per_pixel(omap_lcd->state)]; + else + draw_line = draw_line_table16[ds_get_bits_per_pixel(omap_lcd->state)]; + bpp = 16; + break; + + default: + /* Unsupported at the moment. */ + return; + } + + /* Resolution */ + width = omap_lcd->width; + if (width != ds_get_width(omap_lcd->state) || + omap_lcd->height != ds_get_height(omap_lcd->state)) { + qemu_console_resize(omap_lcd->state, + omap_lcd->width, omap_lcd->height); + 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 (!ds_get_bits_per_pixel(omap_lcd->state)) + 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 = ds_get_linesize(omap_lcd->state); + framebuffer_update_display(omap_lcd->state, + frame_base, width, height, + step, linesize, 0, + omap_lcd->invalidate, + draw_line, omap_lcd->palette, + &first, &last); + if (first >= 0) { + dpy_update(omap_lcd->state, 0, first, width, last - first + 1); + } + omap_lcd->invalidate = 0; +} + +static int ppm_save(const char *filename, uint8_t *data, + int w, int h, int linesize) +{ + FILE *f; + uint8_t *d, *d1; + unsigned int v; + int y, x, bpp; + + f = fopen(filename, "wb"); + if (!f) + return -1; + fprintf(f, "P6\n%d %d\n%d\n", w, h, 255); + d1 = data; + bpp = linesize / w; + for (y = 0; y < h; y ++) { + d = d1; + for (x = 0; x < w; x ++) { + v = *(uint32_t *) d; + switch (bpp) { + case 2: + fputc((v >> 8) & 0xf8, f); + fputc((v >> 3) & 0xfc, f); + fputc((v << 3) & 0xf8, f); + break; + case 3: + case 4: + default: + fputc((v >> 16) & 0xff, f); + fputc((v >> 8) & 0xff, f); + fputc((v) & 0xff, f); + break; + } + d += bpp; + } + d1 += linesize; + } + fclose(f); + return 0; +} + +static void omap_screen_dump(void *opaque, const char *filename) { + struct omap_lcd_panel_s *omap_lcd = opaque; + omap_update_display(opaque); + if (omap_lcd && ds_get_data(omap_lcd->state)) + ppm_save(filename, ds_get_data(omap_lcd->state), + omap_lcd->width, omap_lcd->height, + ds_get_linesize(omap_lcd->state)); +} + +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 uint32_t omap_lcdc_read(void *opaque, target_phys_addr_t addr) +{ + 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, target_phys_addr_t addr, + uint32_t value) +{ + 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 CPUReadMemoryFunc * const omap_lcdc_readfn[] = { + omap_lcdc_read, + omap_lcdc_read, + omap_lcdc_read, +}; + +static CPUWriteMemoryFunc * const omap_lcdc_writefn[] = { + omap_lcdc_write, + omap_lcdc_write, + omap_lcdc_write, +}; + +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; +} + +struct omap_lcd_panel_s *omap_lcdc_init(target_phys_addr_t base, qemu_irq irq, + struct omap_dma_lcd_channel_s *dma, + ram_addr_t imif_base, ram_addr_t emiff_base, omap_clk clk) +{ + int iomemtype; + struct omap_lcd_panel_s *s = (struct omap_lcd_panel_s *) + qemu_mallocz(sizeof(struct omap_lcd_panel_s)); + + s->irq = irq; + s->dma = dma; + s->imif_base = imif_base; + s->emiff_base = emiff_base; + omap_lcdc_reset(s); + + iomemtype = cpu_register_io_memory(omap_lcdc_readfn, + omap_lcdc_writefn, s, DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(base, 0x100, iomemtype); + + s->state = graphic_console_init(omap_update_display, + omap_invalidate_display, + omap_screen_dump, NULL, s); + + return s; +} diff --git a/hw/omap_mmc.c b/hw/omap_mmc.c new file mode 100644 index 0000000..e9ec2f3 --- /dev/null +++ b/hw/omap_mmc.c @@ -0,0 +1,641 @@ +/* + * OMAP on-chip MMC/SD host emulation. + * + * Copyright (C) 2006-2007 Andrzej Zaborowski + * + * 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 . + */ +#include "hw.h" +#include "omap.h" +#include "sd.h" + +struct omap_mmc_s { + qemu_irq irq; + qemu_irq *dma; + qemu_irq coverswitch; + 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 uint32_t omap_mmc_read(void *opaque, target_phys_addr_t offset) +{ + uint16_t i; + struct omap_mmc_s *s = (struct omap_mmc_s *) opaque; + offset &= OMAP_MPUI_REG_MASK; + + 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, target_phys_addr_t offset, + uint32_t value) +{ + int i; + struct omap_mmc_s *s = (struct omap_mmc_s *) opaque; + offset &= OMAP_MPUI_REG_MASK; + + 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 CPUReadMemoryFunc * const omap_mmc_readfn[] = { + omap_badwidth_read16, + omap_mmc_read, + omap_badwidth_read16, +}; + +static CPUWriteMemoryFunc * const omap_mmc_writefn[] = { + omap_badwidth_write16, + omap_mmc_write, + omap_badwidth_write16, +}; + +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(target_phys_addr_t base, + BlockDriverState *bd, + qemu_irq irq, qemu_irq dma[], omap_clk clk) +{ + int iomemtype; + struct omap_mmc_s *s = (struct omap_mmc_s *) + qemu_mallocz(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); + + iomemtype = cpu_register_io_memory(omap_mmc_readfn, + omap_mmc_writefn, s, DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(base, 0x800, iomemtype); + + /* Instantiate the storage */ + s->card = sd_init(bd, 0); + + return s; +} + +struct omap_mmc_s *omap2_mmc_init(struct omap_target_agent_s *ta, + BlockDriverState *bd, qemu_irq irq, qemu_irq dma[], + omap_clk fclk, omap_clk iclk) +{ + int iomemtype; + struct omap_mmc_s *s = (struct omap_mmc_s *) + qemu_mallocz(sizeof(struct omap_mmc_s)); + + s->irq = irq; + s->dma = dma; + s->clk = fclk; + s->lines = 4; + s->rev = 2; + + omap_mmc_reset(s); + + iomemtype = l4_register_io_memory(omap_mmc_readfn, + omap_mmc_writefn, s); + omap_l4_attach(ta, 0, iomemtype); + + /* Instantiate the storage */ + s->card = sd_init(bd, 0); + + s->cdet = qemu_allocate_irqs(omap_mmc_cover_cb, s, 1)[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/omap_sdrc.c b/hw/omap_sdrc.c new file mode 100644 index 0000000..e183762 --- /dev/null +++ b/hw/omap_sdrc.c @@ -0,0 +1,165 @@ +/* + * TI OMAP SDRAM controller emulation. + * + * Copyright (C) 2007-2008 Nokia Corporation + * Written by Andrzej Zaborowski + * + * 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 . + */ +#include "hw.h" +#include "omap.h" + +/* SDRAM Controller Subsystem */ +struct omap_sdrc_s { + uint8_t config; +}; + +void omap_sdrc_reset(struct omap_sdrc_s *s) +{ + s->config = 0x10; +} + +static uint32_t omap_sdrc_read(void *opaque, target_phys_addr_t addr) +{ + struct omap_sdrc_s *s = (struct omap_sdrc_s *) opaque; + + 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, target_phys_addr_t addr, + uint32_t value) +{ + struct omap_sdrc_s *s = (struct omap_sdrc_s *) opaque; + + 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__, 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 CPUReadMemoryFunc * const omap_sdrc_readfn[] = { + omap_badwidth_read32, + omap_badwidth_read32, + omap_sdrc_read, +}; + +static CPUWriteMemoryFunc * const omap_sdrc_writefn[] = { + omap_badwidth_write32, + omap_badwidth_write32, + omap_sdrc_write, +}; + +struct omap_sdrc_s *omap_sdrc_init(target_phys_addr_t base) +{ + int iomemtype; + struct omap_sdrc_s *s = (struct omap_sdrc_s *) + qemu_mallocz(sizeof(struct omap_sdrc_s)); + + omap_sdrc_reset(s); + + iomemtype = cpu_register_io_memory(omap_sdrc_readfn, + omap_sdrc_writefn, s, DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(base, 0x1000, iomemtype); + + return s; +} diff --git a/hw/omap_spi.c b/hw/omap_spi.c new file mode 100644 index 0000000..a6b0349 --- /dev/null +++ b/hw/omap_spi.c @@ -0,0 +1,346 @@ +/* + * TI OMAP processor's Multichannel SPI emulation. + * + * Copyright (C) 2007-2009 Nokia Corporation + * + * Original code for OMAP2 by Andrzej Zaborowski + * + * 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.h" +#include "omap.h" + +/* Multichannel SPI */ +struct omap_mcspi_s { + 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 uint32_t omap_mcspi_read(void *opaque, target_phys_addr_t addr) +{ + struct omap_mcspi_s *s = (struct omap_mcspi_s *) opaque; + int ch = 0; + uint32_t ret; + + 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 ++; + case 0x54: ch ++; + case 0x40: ch ++; + case 0x2c: /* MCSPI_CHCONF */ + return s->ch[ch].config; + + case 0x6c: ch ++; + case 0x58: ch ++; + case 0x44: ch ++; + case 0x30: /* MCSPI_CHSTAT */ + return s->ch[ch].status; + + case 0x70: ch ++; + case 0x5c: ch ++; + case 0x48: ch ++; + case 0x34: /* MCSPI_CHCTRL */ + return s->ch[ch].control; + + case 0x74: ch ++; + case 0x60: ch ++; + case 0x4c: ch ++; + case 0x38: /* MCSPI_TX */ + return s->ch[ch].tx; + + case 0x78: ch ++; + case 0x64: ch ++; + case 0x50: ch ++; + 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, target_phys_addr_t addr, + uint32_t value) +{ + struct omap_mcspi_s *s = (struct omap_mcspi_s *) opaque; + int ch = 0; + + 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 ++; + case 0x54: ch ++; + case 0x40: ch ++; + 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 (%i)\n", + __FUNCTION__, (value >> 7) & 0x1f); + s->ch[ch].config = value & 0x7fffff; + break; + + case 0x70: ch ++; + case 0x5c: ch ++; + case 0x48: ch ++; + 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 ++; + case 0x60: ch ++; + case 0x4c: ch ++; + 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 CPUReadMemoryFunc * const omap_mcspi_readfn[] = { + omap_badwidth_read32, + omap_badwidth_read32, + omap_mcspi_read, +}; + +static CPUWriteMemoryFunc * const omap_mcspi_writefn[] = { + omap_badwidth_write32, + omap_badwidth_write32, + omap_mcspi_write, +}; + +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) +{ + int iomemtype; + struct omap_mcspi_s *s = (struct omap_mcspi_s *) + qemu_mallocz(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); + + iomemtype = l4_register_io_memory(omap_mcspi_readfn, + omap_mcspi_writefn, s); + omap_l4_attach(ta, 0, iomemtype); + + 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/omap_sx1.c b/hw/omap_sx1.c new file mode 100644 index 0000000..06bccbd --- /dev/null +++ b/hw/omap_sx1.c @@ -0,0 +1,254 @@ +/* omap_sx1.c Support for the Siemens SX1 smartphone emulation. + * + * Copyright (C) 2008 + * Jean-Christophe PLAGNIOL-VILLARD + * Copyright (C) 2007 Vladimir Ananiev + * + * based on PalmOne's (TM) PDAs support (palm.c) + */ + +/* + * PalmOne's (TM) PDAs. + * + * Copyright (C) 2006-2007 Andrzej Zaborowski + * + * 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 . + */ +#include "hw.h" +#include "sysemu.h" +#include "console.h" +#include "omap.h" +#include "boards.h" +#include "arm-misc.h" +#include "flash.h" +#include "blockdev.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 uint32_t static_readb(void *opaque, target_phys_addr_t offset) +{ + uint32_t *val = (uint32_t *) opaque; + + return *val >> ((offset & 3) << 3); +} + +static uint32_t static_readh(void *opaque, target_phys_addr_t offset) +{ + uint32_t *val = (uint32_t *) opaque; + + return *val >> ((offset & 1) << 3); +} + +static uint32_t static_readw(void *opaque, target_phys_addr_t offset) +{ + uint32_t *val = (uint32_t *) opaque; + + return *val >> ((offset & 0) << 3); +} + +static void static_write(void *opaque, target_phys_addr_t offset, + uint32_t value) +{ +#ifdef SPY + printf("%s: value %08lx written at " PA_FMT "\n", + __FUNCTION__, value, offset); +#endif +} + +static CPUReadMemoryFunc * const static_readfn[] = { + static_readb, + static_readh, + static_readw, +}; + +static CPUWriteMemoryFunc * const static_writefn[] = { + static_write, + static_write, + static_write, +}; + +#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(ram_addr_t ram_size, + const char *boot_device, + const char *kernel_filename, const char *kernel_cmdline, + const char *initrd_filename, const char *cpu_model, + const int version) +{ + struct omap_mpu_state_s *cpu; + int io; + 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; + } + + cpu = omap310_mpu_init(sx1_binfo.ram_size, cpu_model); + + /* External Flash (EMIFS) */ + cpu_register_physical_memory(OMAP_CS0_BASE, flash_size, + qemu_ram_alloc(NULL, "omap_sx1.flash0-0", + flash_size) | IO_MEM_ROM); + + io = cpu_register_io_memory(static_readfn, static_writefn, &cs0val, + DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(OMAP_CS0_BASE + flash_size, + OMAP_CS0_SIZE - flash_size, io); + io = cpu_register_io_memory(static_readfn, static_writefn, &cs2val, + DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(OMAP_CS2_BASE, OMAP_CS2_SIZE, io); + io = cpu_register_io_memory(static_readfn, static_writefn, &cs3val, + DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(OMAP_CS3_BASE, OMAP_CS3_SIZE, io); + + 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, qemu_ram_alloc(NULL, + "omap_sx1.flash0-1", flash_size), + dinfo->bdrv, 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) { + cpu_register_physical_memory(OMAP_CS1_BASE, flash1_size, + qemu_ram_alloc(NULL, "omap_sx1.flash1-0", + flash1_size) | IO_MEM_ROM); + io = cpu_register_io_memory(static_readfn, static_writefn, &cs1val, + DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(OMAP_CS1_BASE + flash1_size, + OMAP_CS1_SIZE - flash1_size, io); + + if (!pflash_cfi01_register(OMAP_CS1_BASE, qemu_ram_alloc(NULL, + "omap_sx1.flash1-1", flash1_size), + dinfo->bdrv, 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 { + io = cpu_register_io_memory(static_readfn, static_writefn, &cs1val, + DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(OMAP_CS1_BASE, OMAP_CS1_SIZE, io); + } + + if (!kernel_filename && !fl_idx) { + fprintf(stderr, "Kernel or Flash image must be specified\n"); + exit(1); + } + + /* Load the kernel. */ + if (kernel_filename) { + sx1_binfo.kernel_filename = kernel_filename; + sx1_binfo.kernel_cmdline = kernel_cmdline; + sx1_binfo.initrd_filename = initrd_filename; + arm_load_kernel(cpu->env, &sx1_binfo); + } + + /* TODO: fix next line */ + //~ qemu_console_resize(ds, 640, 480); +} + +static void sx1_init_v1(ram_addr_t ram_size, + const char *boot_device, + const char *kernel_filename, const char *kernel_cmdline, + const char *initrd_filename, const char *cpu_model) +{ + sx1_init(ram_size, boot_device, kernel_filename, + kernel_cmdline, initrd_filename, cpu_model, 1); +} + +static void sx1_init_v2(ram_addr_t ram_size, + const char *boot_device, + const char *kernel_filename, const char *kernel_cmdline, + const char *initrd_filename, const char *cpu_model) +{ + sx1_init(ram_size, boot_device, kernel_filename, + kernel_cmdline, initrd_filename, cpu_model, 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/omap_synctimer.c b/hw/omap_synctimer.c new file mode 100644 index 0000000..118668a --- /dev/null +++ b/hw/omap_synctimer.c @@ -0,0 +1,96 @@ +/* + * TI OMAP2 32kHz sync timer emulation. + * + * Copyright (C) 2007-2008 Nokia Corporation + * Written by Andrzej Zaborowski + * + * 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 . + */ +#include "hw.h" +#include "qemu-timer.h" +#include "omap.h" +struct omap_synctimer_s { + 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_get_clock(vm_clock), 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, target_phys_addr_t 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, target_phys_addr_t 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 CPUReadMemoryFunc * const omap_synctimer_readfn[] = { + omap_badwidth_read32, + omap_synctimer_readh, + omap_synctimer_readw, +}; + +static void omap_synctimer_write(void *opaque, target_phys_addr_t addr, + uint32_t value) +{ + OMAP_BAD_REG(addr); +} + +static CPUWriteMemoryFunc * const omap_synctimer_writefn[] = { + omap_badwidth_write32, + omap_synctimer_write, + omap_synctimer_write, +}; + +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 = qemu_mallocz(sizeof(*s)); + + omap_synctimer_reset(s); + omap_l4_attach(ta, 0, l4_register_io_memory( + omap_synctimer_readfn, omap_synctimer_writefn, s)); + + return s; +} diff --git a/hw/omap_tap.c b/hw/omap_tap.c new file mode 100644 index 0000000..1f18ddd --- /dev/null +++ b/hw/omap_tap.c @@ -0,0 +1,112 @@ +/* + * TI OMAP TEST-Chip-level TAP emulation. + * + * Copyright (C) 2007-2008 Nokia Corporation + * Written by Andrzej Zaborowski + * + * 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 . + */ + +#include "hw.h" +#include "omap.h" + +/* TEST-Chip-level TAP */ +static uint32_t omap_tap_read(void *opaque, target_phys_addr_t addr) +{ + struct omap_mpu_state_s *s = (struct omap_mpu_state_s *) opaque; + + 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, target_phys_addr_t addr, + uint32_t value) +{ + OMAP_BAD_REG(addr); +} + +static CPUReadMemoryFunc * const omap_tap_readfn[] = { + omap_badwidth_read32, + omap_badwidth_read32, + omap_tap_read, +}; + +static CPUWriteMemoryFunc * const omap_tap_writefn[] = { + omap_badwidth_write32, + omap_badwidth_write32, + omap_tap_write, +}; + +void omap_tap_init(struct omap_target_agent_s *ta, + struct omap_mpu_state_s *mpu) +{ + omap_l4_attach(ta, 0, l4_register_io_memory( + omap_tap_readfn, omap_tap_writefn, mpu)); +} diff --git a/hw/omap_uart.c b/hw/omap_uart.c new file mode 100644 index 0000000..9cee81d --- /dev/null +++ b/hw/omap_uart.c @@ -0,0 +1,196 @@ +/* + * TI OMAP processors UART emulation. + * + * Copyright (C) 2006-2008 Andrzej Zaborowski + * 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 . + */ +#include "qemu-char.h" +#include "hw.h" +#include "omap.h" +/* We use pc-style serial ports. */ +#include "pc.h" + +/* UARTs */ +struct omap_uart_s { + target_phys_addr_t 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(target_phys_addr_t 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 *) + qemu_mallocz(sizeof(struct omap_uart_s)); + + s->base = base; + s->fclk = fclk; + s->irq = irq; +#ifdef TARGET_WORDS_BIGENDIAN + s->serial = serial_mm_init(base, 2, irq, omap_clk_getrate(fclk)/16, + chr ?: qemu_chr_open(label, "null", NULL), 1, + 1); +#else + s->serial = serial_mm_init(base, 2, irq, omap_clk_getrate(fclk)/16, + chr ?: qemu_chr_open(label, "null", NULL), 1, + 0); +#endif + return s; +} + +static uint32_t omap_uart_read(void *opaque, target_phys_addr_t addr) +{ + struct omap_uart_s *s = (struct omap_uart_s *) opaque; + + addr &= 0xff; + 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, target_phys_addr_t addr, + uint32_t value) +{ + struct omap_uart_s *s = (struct omap_uart_s *) opaque; + + addr &= 0xff; + 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 CPUReadMemoryFunc * const omap_uart_readfn[] = { + omap_uart_read, + omap_uart_read, + omap_badwidth_read8, +}; + +static CPUWriteMemoryFunc * const omap_uart_writefn[] = { + omap_uart_write, + omap_uart_write, + omap_badwidth_write8, +}; + +struct omap_uart_s *omap2_uart_init(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) +{ + target_phys_addr_t base = omap_l4_attach(ta, 0, 0); + struct omap_uart_s *s = omap_uart_init(base, irq, + fclk, iclk, txdma, rxdma, label, chr); + int iomemtype = cpu_register_io_memory(omap_uart_readfn, + omap_uart_writefn, s, DEVICE_NATIVE_ENDIAN); + + s->ta = ta; + + cpu_register_physical_memory(base + 0x20, 0x100, iomemtype); + + return s; +} + +void omap_uart_attach(struct omap_uart_s *s, CharDriverState *chr) +{ + /* TODO: Should reuse or destroy current s->serial */ +#ifdef TARGET_WORDS_BIGENDIAN + s->serial = serial_mm_init(s->base, 2, s->irq, + omap_clk_getrate(s->fclk) / 16, + chr ?: qemu_chr_open("null", "null", NULL), 1, + 1); +#else + s->serial = serial_mm_init(s->base, 2, s->irq, + omap_clk_getrate(s->fclk) / 16, + chr ?: qemu_chr_open("null", "null", NULL), 1, + 0); +#endif +} diff --git a/hw/onenand.c b/hw/onenand.c new file mode 100644 index 0000000..71c1ab4 --- /dev/null +++ b/hw/onenand.c @@ -0,0 +1,661 @@ +/* + * OneNAND flash memories emulation. + * + * Copyright (C) 2008 Nokia Corporation + * Written by Andrzej Zaborowski + * + * 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 . + */ + +#include "qemu-common.h" +#include "hw.h" +#include "flash.h" +#include "irq.h" +#include "blockdev.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) + +typedef struct { + uint32_t id; + int shift; + target_phys_addr_t base; + qemu_irq intr; + qemu_irq rdy; + BlockDriverState *bdrv; + BlockDriverState *bdrv_cur; + uint8_t *image; + uint8_t *otp; + uint8_t *current; + ram_addr_t ram; + uint8_t *boot[2]; + uint8_t *data[2][2]; + int iomemtype; + 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, +}; + +void onenand_base_update(void *opaque, target_phys_addr_t new) +{ + OneNANDState *s = (OneNANDState *) opaque; + + s->base = new; + + /* 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. */ + cpu_register_physical_memory(s->base + (0x0000 << s->shift), + 0x0200 << s->shift, s->iomemtype); + cpu_register_physical_memory(s->base + (0x0200 << s->shift), + 0xbe00 << s->shift, + (s->ram +(0x0200 << s->shift)) | IO_MEM_RAM); + if (s->iomemtype) + cpu_register_physical_memory_offset(s->base + (0xc000 << s->shift), + 0x4000 << s->shift, s->iomemtype, (0xc000 << s->shift)); +} + +void onenand_base_unmap(void *opaque) +{ + OneNANDState *s = (OneNANDState *) opaque; + + cpu_register_physical_memory(s->base, + 0x10000 << s->shift, IO_MEM_UNASSIGNED); +} + +static void onenand_intr_update(OneNANDState *s) +{ + qemu_set_irq(s->intr, ((s->intstatus >> 15) ^ (~s->config[0] >> 6)) & 1); +} + +/* 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->bdrv_cur = s->bdrv; + 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->bdrv && bdrv_read(s->bdrv, 0, s->boot[0], 8) < 0) + hw_error("%s: Loading the BootRAM failed.\n", __FUNCTION__); + } +} + +static inline int onenand_load_main(OneNANDState *s, int sec, int secn, + void *dest) +{ + if (s->bdrv_cur) + return bdrv_read(s->bdrv_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) +{ + if (s->bdrv_cur) + return bdrv_write(s->bdrv_cur, sec, src, secn) < 0; + else if (sec + secn > s->secs_cur) + return 1; + + memcpy(s->current + (sec << 9), src, secn << 9); + + return 0; +} + +static inline int onenand_load_spare(OneNANDState *s, int sec, int secn, + void *dest) +{ + uint8_t buf[512]; + + if (s->bdrv_cur) { + if (bdrv_read(s->bdrv_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) +{ + uint8_t buf[512]; + + if (s->bdrv_cur) { + if (bdrv_read(s->bdrv_cur, s->secs_cur + (sec >> 5), buf, 1) < 0) + return 1; + memcpy(buf + ((sec & 31) << 4), src, secn << 4); + return bdrv_write(s->bdrv_cur, s->secs_cur + (sec >> 5), buf, 1) < 0; + } else if (sec + secn > s->secs_cur) + return 1; + + memcpy(s->current + (s->secs_cur << 9) + (sec << 4), src, secn << 4); + + return 0; +} + +static inline int onenand_erase(OneNANDState *s, int sec, int num) +{ + /* TODO: optimise */ + uint8_t buf[512]; + + memset(buf, 0xff, sizeof(buf)); + for (; num > 0; num --, sec ++) { + if (onenand_prog_main(s, sec, 1, buf)) + return 1; + if (onenand_prog_spare(s, sec, 1, buf)) + return 1; + } + + return 0; +} + +static void onenand_command(OneNANDState *s, int cmd) +{ + 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 (cmd) { + 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->bdrv_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", + __FUNCTION__, cmd); + } + + onenand_intr_update(s); +} + +static uint32_t onenand_read(void *opaque, target_phys_addr_t addr) +{ + 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 >> 16) & 0xff; + case 0xf001: /* Device ID */ + return (s->id >> 8) & 0xff; + /* TODO: get the following values from a real chip! */ + case 0xf002: /* Version ID */ + return (s->id >> 0) & 0xff; + 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, target_phys_addr_t addr, + uint32_t value) +{ + 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 >> 16) & 0xff; + s->boot[0][1 << s->shift] = (s->id >> 8) & 0xff; + s->boot[0][2 << s->shift] = s->wpstatus & 0xff; + break; + + default: + fprintf(stderr, "%s: unknown OneNAND boot command %x\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, s->command); + 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 CPUReadMemoryFunc * const onenand_readfn[] = { + onenand_read, /* TODO */ + onenand_read, + onenand_read, +}; + +static CPUWriteMemoryFunc * const onenand_writefn[] = { + onenand_write, /* TODO */ + onenand_write, + onenand_write, +}; + +void *onenand_init(uint32_t id, int regshift, qemu_irq irq) +{ + OneNANDState *s = (OneNANDState *) qemu_mallocz(sizeof(*s)); + DriveInfo *dinfo = drive_get(IF_MTD, 0, 0); + uint32_t size = 1 << (24 + ((id >> 12) & 7)); + void *ram; + + s->shift = regshift; + s->intr = irq; + s->rdy = NULL; + s->id = id; + s->blocks = size >> BLOCK_SHIFT; + s->secs = size >> 9; + s->blockwp = qemu_malloc(s->blocks); + s->density_mask = (id & (1 << 11)) ? (1 << (6 + ((id >> 12) & 7))) : 0; + s->iomemtype = cpu_register_io_memory(onenand_readfn, + onenand_writefn, s, DEVICE_NATIVE_ENDIAN); + if (!dinfo) + s->image = memset(qemu_malloc(size + (size >> 5)), + 0xff, size + (size >> 5)); + else + s->bdrv = dinfo->bdrv; + s->otp = memset(qemu_malloc((64 + 2) << PAGE_SHIFT), + 0xff, (64 + 2) << PAGE_SHIFT); + s->ram = qemu_ram_alloc(NULL, "onenand.ram", 0xc000 << s->shift); + ram = qemu_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_reset(s, 1); + + return s; +} + +void *onenand_raw_otp(void *opaque) +{ + OneNANDState *s = (OneNANDState *) opaque; + + return s->otp; +} diff --git a/hw/openpic.c b/hw/openpic.c new file mode 100644 index 0000000..6d2cf99 --- /dev/null +++ b/hw/openpic.c @@ -0,0 +1,1686 @@ +/* + * OpenPIC emulation + * + * Copyright (c) 2004 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. + */ +/* + * + * 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.h" +#include "ppc_mac.h" +#include "pci.h" +#include "openpic.h" + +//#define DEBUG_OPENPIC + +#ifdef DEBUG_OPENPIC +#define DPRINTF(fmt, ...) do { printf(fmt , ## __VA_ARGS__); } while (0) +#else +#define DPRINTF(fmt, ...) do { } while (0) +#endif + +#define USE_MPCxxx /* Intel model is broken, for now */ + +#if defined (USE_INTEL_GW80314) +/* Intel GW80314 I/O Companion chip */ + +#define MAX_CPU 4 +#define MAX_IRQ 32 +#define MAX_DBL 4 +#define MAX_MBX 4 +#define MAX_TMR 4 +#define VECTOR_BITS 8 +#define MAX_IPI 0 + +#define VID (0x00000000) + +#elif defined(USE_MPCxxx) + +#define MAX_CPU 2 +#define MAX_IRQ 128 +#define MAX_DBL 0 +#define MAX_MBX 0 +#define MAX_TMR 4 +#define VECTOR_BITS 8 +#define MAX_IPI 4 +#define VID 0x03 /* MPIC version ID */ +#define VENI 0x00000000 /* Vendor ID */ + +enum { + IRQ_IPVP = 0, + IRQ_IDE, +}; + +/* OpenPIC */ +#define OPENPIC_MAX_CPU 2 +#define OPENPIC_MAX_IRQ 64 +#define OPENPIC_EXT_IRQ 48 +#define OPENPIC_MAX_TMR MAX_TMR +#define OPENPIC_MAX_IPI MAX_IPI + +/* Interrupt definitions */ +#define OPENPIC_IRQ_FE (OPENPIC_EXT_IRQ) /* Internal functional IRQ */ +#define OPENPIC_IRQ_ERR (OPENPIC_EXT_IRQ + 1) /* Error IRQ */ +#define OPENPIC_IRQ_TIM0 (OPENPIC_EXT_IRQ + 2) /* First timer IRQ */ +#if OPENPIC_MAX_IPI > 0 +#define OPENPIC_IRQ_IPI0 (OPENPIC_IRQ_TIM0 + OPENPIC_MAX_TMR) /* First IPI IRQ */ +#define OPENPIC_IRQ_DBL0 (OPENPIC_IRQ_IPI0 + (OPENPIC_MAX_CPU * OPENPIC_MAX_IPI)) /* First doorbell IRQ */ +#else +#define OPENPIC_IRQ_DBL0 (OPENPIC_IRQ_TIM0 + OPENPIC_MAX_TMR) /* First doorbell IRQ */ +#define OPENPIC_IRQ_MBX0 (OPENPIC_IRQ_DBL0 + OPENPIC_MAX_DBL) /* First mailbox IRQ */ +#endif + +/* MPIC */ +#define MPIC_MAX_CPU 1 +#define MPIC_MAX_EXT 12 +#define MPIC_MAX_INT 64 +#define MPIC_MAX_MSG 4 +#define MPIC_MAX_MSI 8 +#define MPIC_MAX_TMR MAX_TMR +#define MPIC_MAX_IPI MAX_IPI +#define MPIC_MAX_IRQ (MPIC_MAX_EXT + MPIC_MAX_INT + MPIC_MAX_TMR + MPIC_MAX_MSG + MPIC_MAX_MSI + (MPIC_MAX_IPI * MPIC_MAX_CPU)) + +/* Interrupt definitions */ +#define MPIC_EXT_IRQ 0 +#define MPIC_INT_IRQ (MPIC_EXT_IRQ + MPIC_MAX_EXT) +#define MPIC_TMR_IRQ (MPIC_INT_IRQ + MPIC_MAX_INT) +#define MPIC_MSG_IRQ (MPIC_TMR_IRQ + MPIC_MAX_TMR) +#define MPIC_MSI_IRQ (MPIC_MSG_IRQ + MPIC_MAX_MSG) +#define MPIC_IPI_IRQ (MPIC_MSI_IRQ + MPIC_MAX_MSI) + +#define MPIC_GLB_REG_START 0x0 +#define MPIC_GLB_REG_SIZE 0x10F0 +#define MPIC_TMR_REG_START 0x10F0 +#define MPIC_TMR_REG_SIZE 0x220 +#define MPIC_EXT_REG_START 0x10000 +#define MPIC_EXT_REG_SIZE 0x180 +#define MPIC_INT_REG_START 0x10200 +#define MPIC_INT_REG_SIZE 0x800 +#define MPIC_MSG_REG_START 0x11600 +#define MPIC_MSG_REG_SIZE 0x100 +#define MPIC_MSI_REG_START 0x11C00 +#define MPIC_MSI_REG_SIZE 0x100 +#define MPIC_CPU_REG_START 0x20000 +#define MPIC_CPU_REG_SIZE 0x100 + +enum mpic_ide_bits { + IDR_EP = 0, + IDR_CI0 = 1, + IDR_CI1 = 2, + IDR_P1 = 30, + IDR_P0 = 31, +}; + +#else +#error "Please select which OpenPic implementation is to be emulated" +#endif + +#define OPENPIC_PAGE_SIZE 4096 + +#define BF_WIDTH(_bits_) \ +(((_bits_) + (sizeof(uint32_t) * 8) - 1) / (sizeof(uint32_t) * 8)) + +static inline void set_bit (uint32_t *field, int bit) +{ + field[bit >> 5] |= 1 << (bit & 0x1F); +} + +static inline void reset_bit (uint32_t *field, int bit) +{ + field[bit >> 5] &= ~(1 << (bit & 0x1F)); +} + +static inline int test_bit (uint32_t *field, int bit) +{ + return (field[bit >> 5] & 1 << (bit & 0x1F)) != 0; +} + +enum { + IRQ_EXTERNAL = 0x01, + IRQ_INTERNAL = 0x02, + IRQ_TIMER = 0x04, + IRQ_SPECIAL = 0x08, +}; + +typedef struct IRQ_queue_t { + uint32_t queue[BF_WIDTH(MAX_IRQ)]; + int next; + int priority; +} IRQ_queue_t; + +typedef struct IRQ_src_t { + uint32_t ipvp; /* IRQ vector/priority register */ + uint32_t ide; /* IRQ destination register */ + int type; + int last_cpu; + int pending; /* TRUE if IRQ is pending */ +} IRQ_src_t; + +enum IPVP_bits { + IPVP_MASK = 31, + IPVP_ACTIVITY = 30, + IPVP_MODE = 29, + IPVP_POLARITY = 23, + IPVP_SENSE = 22, +}; +#define IPVP_PRIORITY_MASK (0x1F << 16) +#define IPVP_PRIORITY(_ipvpr_) ((int)(((_ipvpr_) & IPVP_PRIORITY_MASK) >> 16)) +#define IPVP_VECTOR_MASK ((1 << VECTOR_BITS) - 1) +#define IPVP_VECTOR(_ipvpr_) ((_ipvpr_) & IPVP_VECTOR_MASK) + +typedef struct IRQ_dst_t { + uint32_t tfrr; + uint32_t pctp; /* CPU current task priority */ + uint32_t pcsr; /* CPU sensitivity register */ + IRQ_queue_t raised; + IRQ_queue_t servicing; + qemu_irq *irqs; +} IRQ_dst_t; + +typedef struct openpic_t { + PCIDevice pci_dev; + int mem_index; + /* Global registers */ + uint32_t frep; /* Feature reporting register */ + uint32_t glbc; /* Global configuration register */ + uint32_t micr; /* MPIC interrupt configuration register */ + uint32_t veni; /* Vendor identification register */ + uint32_t pint; /* Processor initialization register */ + uint32_t spve; /* Spurious vector register */ + uint32_t tifr; /* Timer frequency reporting register */ + /* Source registers */ + IRQ_src_t src[MAX_IRQ]; + /* Local registers per output pin */ + IRQ_dst_t dst[MAX_CPU]; + int nb_cpus; + /* Timer registers */ + struct { + uint32_t ticc; /* Global timer current count register */ + uint32_t tibc; /* Global timer base count register */ + } timers[MAX_TMR]; +#if MAX_DBL > 0 + /* Doorbell registers */ + uint32_t dar; /* Doorbell activate register */ + struct { + uint32_t dmr; /* Doorbell messaging register */ + } doorbells[MAX_DBL]; +#endif +#if MAX_MBX > 0 + /* Mailbox registers */ + struct { + uint32_t mbr; /* Mailbox register */ + } mailboxes[MAX_MAILBOXES]; +#endif + /* IRQ out is used when in bypass mode (not implemented) */ + qemu_irq irq_out; + int max_irq; + int irq_ipi0; + int irq_tim0; + void (*reset) (void *); + void (*irq_raise) (struct openpic_t *, int, IRQ_src_t *); +} openpic_t; + +static inline void IRQ_setbit (IRQ_queue_t *q, int n_IRQ) +{ + set_bit(q->queue, n_IRQ); +} + +static inline void IRQ_resetbit (IRQ_queue_t *q, int n_IRQ) +{ + reset_bit(q->queue, n_IRQ); +} + +static inline int IRQ_testbit (IRQ_queue_t *q, int n_IRQ) +{ + return test_bit(q->queue, n_IRQ); +} + +static void IRQ_check (openpic_t *opp, IRQ_queue_t *q) +{ + int next, i; + int priority; + + next = -1; + priority = -1; + for (i = 0; i < opp->max_irq; i++) { + if (IRQ_testbit(q, i)) { + DPRINTF("IRQ_check: irq %d set ipvp_pr=%d pr=%d\n", + i, IPVP_PRIORITY(opp->src[i].ipvp), priority); + if (IPVP_PRIORITY(opp->src[i].ipvp) > priority) { + next = i; + priority = IPVP_PRIORITY(opp->src[i].ipvp); + } + } + } + q->next = next; + q->priority = priority; +} + +static int IRQ_get_next (openpic_t *opp, IRQ_queue_t *q) +{ + if (q->next == -1) { + /* XXX: optimize */ + IRQ_check(opp, q); + } + + return q->next; +} + +static void IRQ_local_pipe (openpic_t *opp, int n_CPU, int n_IRQ) +{ + IRQ_dst_t *dst; + IRQ_src_t *src; + int priority; + + dst = &opp->dst[n_CPU]; + src = &opp->src[n_IRQ]; + priority = IPVP_PRIORITY(src->ipvp); + if (priority <= dst->pctp) { + /* Too low priority */ + DPRINTF("%s: IRQ %d has too low priority on CPU %d\n", + __func__, n_IRQ, n_CPU); + return; + } + if (IRQ_testbit(&dst->raised, n_IRQ)) { + /* Interrupt miss */ + DPRINTF("%s: IRQ %d was missed on CPU %d\n", + __func__, n_IRQ, n_CPU); + return; + } + set_bit(&src->ipvp, IPVP_ACTIVITY); + IRQ_setbit(&dst->raised, n_IRQ); + if (priority < dst->raised.priority) { + /* An higher priority IRQ is already raised */ + DPRINTF("%s: IRQ %d is hidden by raised IRQ %d on CPU %d\n", + __func__, n_IRQ, dst->raised.next, n_CPU); + return; + } + IRQ_get_next(opp, &dst->raised); + if (IRQ_get_next(opp, &dst->servicing) != -1 && + 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); + /* Already servicing a higher priority IRQ */ + return; + } + DPRINTF("Raise OpenPIC INT output cpu %d irq %d\n", n_CPU, n_IRQ); + opp->irq_raise(opp, n_CPU, src); +} + +/* update pic state because registers for n_IRQ have changed value */ +static void openpic_update_irq(openpic_t *opp, int n_IRQ) +{ + IRQ_src_t *src; + int i; + + src = &opp->src[n_IRQ]; + + if (!src->pending) { + /* no irq pending */ + DPRINTF("%s: IRQ %d is not pending\n", __func__, n_IRQ); + return; + } + if (test_bit(&src->ipvp, IPVP_MASK)) { + /* Interrupt source is disabled */ + DPRINTF("%s: IRQ %d is disabled\n", __func__, n_IRQ); + return; + } + if (IPVP_PRIORITY(src->ipvp) == 0) { + /* Priority set to zero */ + DPRINTF("%s: IRQ %d has 0 priority\n", __func__, n_IRQ); + return; + } + if (test_bit(&src->ipvp, IPVP_ACTIVITY)) { + /* IRQ already active */ + DPRINTF("%s: IRQ %d is already active\n", __func__, n_IRQ); + return; + } + if (src->ide == 0x00000000) { + /* No target */ + DPRINTF("%s: IRQ %d has no target\n", __func__, n_IRQ); + return; + } + + if (src->ide == (1 << src->last_cpu)) { + /* Only one CPU is allowed to receive this IRQ */ + IRQ_local_pipe(opp, src->last_cpu, n_IRQ); + } else if (!test_bit(&src->ipvp, IPVP_MODE)) { + /* Directed delivery mode */ + for (i = 0; i < opp->nb_cpus; i++) { + if (test_bit(&src->ide, i)) + IRQ_local_pipe(opp, i, n_IRQ); + } + } else { + /* Distributed delivery mode */ + for (i = src->last_cpu + 1; i != src->last_cpu; i++) { + if (i == opp->nb_cpus) + i = 0; + if (test_bit(&src->ide, i)) { + IRQ_local_pipe(opp, i, n_IRQ); + src->last_cpu = i; + break; + } + } + } +} + +static void openpic_set_irq(void *opaque, int n_IRQ, int level) +{ + openpic_t *opp = opaque; + IRQ_src_t *src; + + src = &opp->src[n_IRQ]; + DPRINTF("openpic: set irq %d = %d ipvp=%08x\n", + n_IRQ, level, src->ipvp); + if (test_bit(&src->ipvp, IPVP_SENSE)) { + /* level-sensitive irq */ + src->pending = level; + if (!level) + reset_bit(&src->ipvp, IPVP_ACTIVITY); + } else { + /* edge-sensitive irq */ + if (level) + src->pending = 1; + } + openpic_update_irq(opp, n_IRQ); +} + +static void openpic_reset (void *opaque) +{ + openpic_t *opp = (openpic_t *)opaque; + int i; + + opp->glbc = 0x80000000; + /* Initialise controller registers */ + opp->frep = ((OPENPIC_EXT_IRQ - 1) << 16) | ((MAX_CPU - 1) << 8) | VID; + opp->veni = VENI; + opp->pint = 0x00000000; + opp->spve = 0x000000FF; + opp->tifr = 0x003F7A00; + /* ? */ + opp->micr = 0x00000000; + /* Initialise IRQ sources */ + for (i = 0; i < opp->max_irq; i++) { + opp->src[i].ipvp = 0xA0000000; + opp->src[i].ide = 0x00000000; + } + /* Initialise IRQ destinations */ + for (i = 0; i < MAX_CPU; i++) { + opp->dst[i].pctp = 0x0000000F; + opp->dst[i].pcsr = 0x00000000; + memset(&opp->dst[i].raised, 0, sizeof(IRQ_queue_t)); + opp->dst[i].raised.next = -1; + memset(&opp->dst[i].servicing, 0, sizeof(IRQ_queue_t)); + opp->dst[i].servicing.next = -1; + } + /* Initialise timers */ + for (i = 0; i < MAX_TMR; i++) { + opp->timers[i].ticc = 0x00000000; + opp->timers[i].tibc = 0x80000000; + } + /* Initialise doorbells */ +#if MAX_DBL > 0 + opp->dar = 0x00000000; + for (i = 0; i < MAX_DBL; i++) { + opp->doorbells[i].dmr = 0x00000000; + } +#endif + /* Initialise mailboxes */ +#if MAX_MBX > 0 + for (i = 0; i < MAX_MBX; i++) { /* ? */ + opp->mailboxes[i].mbr = 0x00000000; + } +#endif + /* Go out of RESET state */ + opp->glbc = 0x00000000; +} + +static inline uint32_t read_IRQreg (openpic_t *opp, int n_IRQ, uint32_t reg) +{ + uint32_t retval; + + switch (reg) { + case IRQ_IPVP: + retval = opp->src[n_IRQ].ipvp; + break; + case IRQ_IDE: + retval = opp->src[n_IRQ].ide; + break; + } + + return retval; +} + +static inline void write_IRQreg (openpic_t *opp, int n_IRQ, + uint32_t reg, uint32_t val) +{ + uint32_t tmp; + + switch (reg) { + case IRQ_IPVP: + /* NOTE: not fully accurate for special IRQs, but simple and + sufficient */ + /* ACTIVITY bit is read-only */ + opp->src[n_IRQ].ipvp = + (opp->src[n_IRQ].ipvp & 0x40000000) | + (val & 0x800F00FF); + openpic_update_irq(opp, n_IRQ); + DPRINTF("Set IPVP %d to 0x%08x -> 0x%08x\n", + n_IRQ, val, opp->src[n_IRQ].ipvp); + break; + case IRQ_IDE: + tmp = val & 0xC0000000; + tmp |= val & ((1 << MAX_CPU) - 1); + opp->src[n_IRQ].ide = tmp; + DPRINTF("Set IDE %d to 0x%08x\n", n_IRQ, opp->src[n_IRQ].ide); + break; + } +} + +#if 0 // Code provision for Intel model +#if MAX_DBL > 0 +static uint32_t read_doorbell_register (openpic_t *opp, + int n_dbl, uint32_t offset) +{ + uint32_t retval; + + switch (offset) { + case DBL_IPVP_OFFSET: + retval = read_IRQreg(opp, IRQ_DBL0 + n_dbl, IRQ_IPVP); + break; + case DBL_IDE_OFFSET: + retval = read_IRQreg(opp, IRQ_DBL0 + n_dbl, IRQ_IDE); + break; + case DBL_DMR_OFFSET: + retval = opp->doorbells[n_dbl].dmr; + break; + } + + return retval; +} + +static void write_doorbell_register (penpic_t *opp, int n_dbl, + uint32_t offset, uint32_t value) +{ + switch (offset) { + case DBL_IVPR_OFFSET: + write_IRQreg(opp, IRQ_DBL0 + n_dbl, IRQ_IPVP, value); + break; + case DBL_IDE_OFFSET: + write_IRQreg(opp, IRQ_DBL0 + n_dbl, IRQ_IDE, value); + break; + case DBL_DMR_OFFSET: + opp->doorbells[n_dbl].dmr = value; + break; + } +} +#endif + +#if MAX_MBX > 0 +static uint32_t read_mailbox_register (openpic_t *opp, + int n_mbx, uint32_t offset) +{ + uint32_t retval; + + switch (offset) { + case MBX_MBR_OFFSET: + retval = opp->mailboxes[n_mbx].mbr; + break; + case MBX_IVPR_OFFSET: + retval = read_IRQreg(opp, IRQ_MBX0 + n_mbx, IRQ_IPVP); + break; + case MBX_DMR_OFFSET: + retval = read_IRQreg(opp, IRQ_MBX0 + n_mbx, IRQ_IDE); + break; + } + + return retval; +} + +static void write_mailbox_register (openpic_t *opp, int n_mbx, + uint32_t address, uint32_t value) +{ + switch (offset) { + case MBX_MBR_OFFSET: + opp->mailboxes[n_mbx].mbr = value; + break; + case MBX_IVPR_OFFSET: + write_IRQreg(opp, IRQ_MBX0 + n_mbx, IRQ_IPVP, value); + break; + case MBX_DMR_OFFSET: + write_IRQreg(opp, IRQ_MBX0 + n_mbx, IRQ_IDE, value); + break; + } +} +#endif +#endif /* 0 : Code provision for Intel model */ + +static void openpic_gbl_write (void *opaque, target_phys_addr_t addr, uint32_t val) +{ + openpic_t *opp = opaque; + IRQ_dst_t *dst; + int idx; + + DPRINTF("%s: addr " TARGET_FMT_plx " <= %08x\n", __func__, addr, val); + if (addr & 0xF) + return; + addr &= 0xFF; + switch (addr) { + case 0x00: /* FREP */ + break; + case 0x20: /* GLBC */ + if (val & 0x80000000 && opp->reset) + opp->reset(opp); + opp->glbc = val & ~0x80000000; + break; + case 0x80: /* VENI */ + break; + case 0x90: /* PINT */ + for (idx = 0; idx < opp->nb_cpus; idx++) { + if ((val & (1 << idx)) && !(opp->pint & (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->pint & (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->pint = val; + break; +#if MAX_IPI > 0 + case 0xA0: /* IPI_IPVP */ + case 0xB0: + case 0xC0: + case 0xD0: + { + int idx; + idx = (addr - 0xA0) >> 4; + write_IRQreg(opp, opp->irq_ipi0 + idx, IRQ_IPVP, val); + } + break; +#endif + case 0xE0: /* SPVE */ + opp->spve = val & 0x000000FF; + break; + case 0xF0: /* TIFR */ + opp->tifr = val; + break; + default: + break; + } +} + +static uint32_t openpic_gbl_read (void *opaque, target_phys_addr_t addr) +{ + openpic_t *opp = opaque; + uint32_t retval; + + DPRINTF("%s: addr " TARGET_FMT_plx "\n", __func__, addr); + retval = 0xFFFFFFFF; + if (addr & 0xF) + return retval; + addr &= 0xFF; + switch (addr) { + case 0x00: /* FREP */ + retval = opp->frep; + break; + case 0x20: /* GLBC */ + retval = opp->glbc; + break; + case 0x80: /* VENI */ + retval = opp->veni; + break; + case 0x90: /* PINT */ + retval = 0x00000000; + break; +#if MAX_IPI > 0 + case 0xA0: /* IPI_IPVP */ + case 0xB0: + case 0xC0: + case 0xD0: + { + int idx; + idx = (addr - 0xA0) >> 4; + retval = read_IRQreg(opp, opp->irq_ipi0 + idx, IRQ_IPVP); + } + break; +#endif + case 0xE0: /* SPVE */ + retval = opp->spve; + break; + case 0xF0: /* TIFR */ + retval = opp->tifr; + break; + default: + break; + } + DPRINTF("%s: => %08x\n", __func__, retval); + + return retval; +} + +static void openpic_timer_write (void *opaque, uint32_t addr, uint32_t val) +{ + openpic_t *opp = opaque; + int idx; + + DPRINTF("%s: addr %08x <= %08x\n", __func__, addr, val); + if (addr & 0xF) + return; + addr -= 0x1100; + addr &= 0xFFFF; + idx = (addr & 0xFFF0) >> 6; + addr = addr & 0x30; + switch (addr) { + case 0x00: /* TICC */ + break; + case 0x10: /* TIBC */ + if ((opp->timers[idx].ticc & 0x80000000) != 0 && + (val & 0x80000000) == 0 && + (opp->timers[idx].tibc & 0x80000000) != 0) + opp->timers[idx].ticc &= ~0x80000000; + opp->timers[idx].tibc = val; + break; + case 0x20: /* TIVP */ + write_IRQreg(opp, opp->irq_tim0 + idx, IRQ_IPVP, val); + break; + case 0x30: /* TIDE */ + write_IRQreg(opp, opp->irq_tim0 + idx, IRQ_IDE, val); + break; + } +} + +static uint32_t openpic_timer_read (void *opaque, uint32_t addr) +{ + openpic_t *opp = opaque; + uint32_t retval; + int idx; + + DPRINTF("%s: addr %08x\n", __func__, addr); + retval = 0xFFFFFFFF; + if (addr & 0xF) + return retval; + addr -= 0x1100; + addr &= 0xFFFF; + idx = (addr & 0xFFF0) >> 6; + addr = addr & 0x30; + switch (addr) { + case 0x00: /* TICC */ + retval = opp->timers[idx].ticc; + break; + case 0x10: /* TIBC */ + retval = opp->timers[idx].tibc; + break; + case 0x20: /* TIPV */ + retval = read_IRQreg(opp, opp->irq_tim0 + idx, IRQ_IPVP); + break; + case 0x30: /* TIDE */ + retval = read_IRQreg(opp, opp->irq_tim0 + idx, IRQ_IDE); + break; + } + DPRINTF("%s: => %08x\n", __func__, retval); + + return retval; +} + +static void openpic_src_write (void *opaque, uint32_t addr, uint32_t val) +{ + openpic_t *opp = opaque; + int idx; + + DPRINTF("%s: addr %08x <= %08x\n", __func__, addr, val); + if (addr & 0xF) + return; + addr = addr & 0xFFF0; + idx = addr >> 5; + if (addr & 0x10) { + /* EXDE / IFEDE / IEEDE */ + write_IRQreg(opp, idx, IRQ_IDE, val); + } else { + /* EXVP / IFEVP / IEEVP */ + write_IRQreg(opp, idx, IRQ_IPVP, val); + } +} + +static uint32_t openpic_src_read (void *opaque, uint32_t addr) +{ + openpic_t *opp = opaque; + uint32_t retval; + int idx; + + DPRINTF("%s: addr %08x\n", __func__, addr); + retval = 0xFFFFFFFF; + if (addr & 0xF) + return retval; + addr = addr & 0xFFF0; + idx = addr >> 5; + if (addr & 0x10) { + /* EXDE / IFEDE / IEEDE */ + retval = read_IRQreg(opp, idx, IRQ_IDE); + } else { + /* EXVP / IFEVP / IEEVP */ + retval = read_IRQreg(opp, idx, IRQ_IPVP); + } + DPRINTF("%s: => %08x\n", __func__, retval); + + return retval; +} + +static void openpic_cpu_write (void *opaque, target_phys_addr_t addr, uint32_t val) +{ + openpic_t *opp = opaque; + IRQ_src_t *src; + IRQ_dst_t *dst; + int idx, s_IRQ, n_IRQ; + + DPRINTF("%s: addr " TARGET_FMT_plx " <= %08x\n", __func__, addr, val); + if (addr & 0xF) + return; + addr &= 0x1FFF0; + idx = addr / 0x1000; + dst = &opp->dst[idx]; + addr &= 0xFF0; + switch (addr) { +#if MAX_IPI > 0 + case 0x40: /* PIPD */ + case 0x50: + case 0x60: + case 0x70: + idx = (addr - 0x40) >> 4; + write_IRQreg(opp, opp->irq_ipi0 + idx, IRQ_IDE, val); + openpic_set_irq(opp, opp->irq_ipi0 + idx, 1); + openpic_set_irq(opp, opp->irq_ipi0 + idx, 0); + break; +#endif + case 0x80: /* PCTP */ + dst->pctp = val & 0x0000000F; + break; + case 0x90: /* WHOAMI */ + /* Read-only register */ + break; + case 0xA0: /* PIAC */ + /* Read-only register */ + break; + case 0xB0: /* PEOI */ + DPRINTF("PEOI\n"); + s_IRQ = IRQ_get_next(opp, &dst->servicing); + IRQ_resetbit(&dst->servicing, s_IRQ); + dst->servicing.next = -1; + /* 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 || + IPVP_PRIORITY(src->ipvp) > dst->servicing.priority)) { + DPRINTF("Raise OpenPIC INT output cpu %d irq %d\n", + idx, n_IRQ); + opp->irq_raise(opp, idx, src); + } + break; + default: + break; + } +} + +static uint32_t openpic_cpu_read (void *opaque, target_phys_addr_t addr) +{ + openpic_t *opp = opaque; + IRQ_src_t *src; + IRQ_dst_t *dst; + uint32_t retval; + int idx, n_IRQ; + + DPRINTF("%s: addr " TARGET_FMT_plx "\n", __func__, addr); + retval = 0xFFFFFFFF; + if (addr & 0xF) + return retval; + addr &= 0x1FFF0; + idx = addr / 0x1000; + dst = &opp->dst[idx]; + addr &= 0xFF0; + switch (addr) { + case 0x80: /* PCTP */ + retval = dst->pctp; + break; + case 0x90: /* WHOAMI */ + retval = idx; + break; + case 0xA0: /* PIAC */ + DPRINTF("Lower OpenPIC INT output\n"); + qemu_irq_lower(dst->irqs[OPENPIC_OUTPUT_INT]); + n_IRQ = IRQ_get_next(opp, &dst->raised); + DPRINTF("PIAC: irq=%d\n", n_IRQ); + if (n_IRQ == -1) { + /* No more interrupt pending */ + retval = IPVP_VECTOR(opp->spve); + } else { + src = &opp->src[n_IRQ]; + if (!test_bit(&src->ipvp, IPVP_ACTIVITY) || + !(IPVP_PRIORITY(src->ipvp) > dst->pctp)) { + /* - Spurious level-sensitive IRQ + * - Priorities has been changed + * and the pending IRQ isn't allowed anymore + */ + reset_bit(&src->ipvp, IPVP_ACTIVITY); + retval = IPVP_VECTOR(opp->spve); + } else { + /* IRQ enter servicing state */ + IRQ_setbit(&dst->servicing, n_IRQ); + retval = IPVP_VECTOR(src->ipvp); + } + IRQ_resetbit(&dst->raised, n_IRQ); + dst->raised.next = -1; + if (!test_bit(&src->ipvp, IPVP_SENSE)) { + /* edge-sensitive IRQ */ + reset_bit(&src->ipvp, IPVP_ACTIVITY); + src->pending = 0; + } + } + break; + case 0xB0: /* PEOI */ + retval = 0; + break; +#if MAX_IPI > 0 + case 0x40: /* IDE */ + case 0x50: + idx = (addr - 0x40) >> 4; + retval = read_IRQreg(opp, opp->irq_ipi0 + idx, IRQ_IDE); + break; +#endif + default: + break; + } + DPRINTF("%s: => %08x\n", __func__, retval); + + return retval; +} + +static void openpic_buggy_write (void *opaque, + target_phys_addr_t addr, uint32_t val) +{ + printf("Invalid OPENPIC write access !\n"); +} + +static uint32_t openpic_buggy_read (void *opaque, target_phys_addr_t addr) +{ + printf("Invalid OPENPIC read access !\n"); + + return -1; +} + +static void openpic_writel (void *opaque, + target_phys_addr_t addr, uint32_t val) +{ + openpic_t *opp = opaque; + + addr &= 0x3FFFF; + DPRINTF("%s: offset %08x val: %08x\n", __func__, (int)addr, val); + if (addr < 0x1100) { + /* Global registers */ + openpic_gbl_write(opp, addr, val); + } else if (addr < 0x10000) { + /* Timers registers */ + openpic_timer_write(opp, addr, val); + } else if (addr < 0x20000) { + /* Source registers */ + openpic_src_write(opp, addr, val); + } else { + /* CPU registers */ + openpic_cpu_write(opp, addr, val); + } +} + +static uint32_t openpic_readl (void *opaque,target_phys_addr_t addr) +{ + openpic_t *opp = opaque; + uint32_t retval; + + addr &= 0x3FFFF; + DPRINTF("%s: offset %08x\n", __func__, (int)addr); + if (addr < 0x1100) { + /* Global registers */ + retval = openpic_gbl_read(opp, addr); + } else if (addr < 0x10000) { + /* Timers registers */ + retval = openpic_timer_read(opp, addr); + } else if (addr < 0x20000) { + /* Source registers */ + retval = openpic_src_read(opp, addr); + } else { + /* CPU registers */ + retval = openpic_cpu_read(opp, addr); + } + + return retval; +} + +static CPUWriteMemoryFunc * const openpic_write[] = { + &openpic_buggy_write, + &openpic_buggy_write, + &openpic_writel, +}; + +static CPUReadMemoryFunc * const openpic_read[] = { + &openpic_buggy_read, + &openpic_buggy_read, + &openpic_readl, +}; + +static void openpic_map(PCIDevice *pci_dev, int region_num, + pcibus_t addr, pcibus_t size, int type) +{ + openpic_t *opp; + + DPRINTF("Map OpenPIC\n"); + opp = (openpic_t *)pci_dev; + /* Global registers */ + DPRINTF("Register OPENPIC gbl %08x => %08x\n", + addr + 0x1000, addr + 0x1000 + 0x100); + /* Timer registers */ + DPRINTF("Register OPENPIC timer %08x => %08x\n", + addr + 0x1100, addr + 0x1100 + 0x40 * MAX_TMR); + /* Interrupt source registers */ + DPRINTF("Register OPENPIC src %08x => %08x\n", + addr + 0x10000, addr + 0x10000 + 0x20 * (OPENPIC_EXT_IRQ + 2)); + /* Per CPU registers */ + DPRINTF("Register OPENPIC dst %08x => %08x\n", + addr + 0x20000, addr + 0x20000 + 0x1000 * MAX_CPU); + cpu_register_physical_memory(addr, 0x40000, opp->mem_index); +#if 0 // Don't implement ISU for now + opp_io_memory = cpu_register_io_memory(openpic_src_read, + openpic_src_write, NULL + DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(isu_base, 0x20 * (EXT_IRQ + 2), + opp_io_memory); +#endif +} + +static void openpic_save_IRQ_queue(QEMUFile* f, IRQ_queue_t *q) +{ + unsigned int i; + + for (i = 0; i < BF_WIDTH(MAX_IRQ); i++) + qemu_put_be32s(f, &q->queue[i]); + + qemu_put_sbe32s(f, &q->next); + qemu_put_sbe32s(f, &q->priority); +} + +static void openpic_save(QEMUFile* f, void *opaque) +{ + openpic_t *opp = (openpic_t *)opaque; + unsigned int i; + + qemu_put_be32s(f, &opp->frep); + qemu_put_be32s(f, &opp->glbc); + qemu_put_be32s(f, &opp->micr); + qemu_put_be32s(f, &opp->veni); + qemu_put_be32s(f, &opp->pint); + qemu_put_be32s(f, &opp->spve); + qemu_put_be32s(f, &opp->tifr); + + for (i = 0; i < opp->max_irq; i++) { + qemu_put_be32s(f, &opp->src[i].ipvp); + qemu_put_be32s(f, &opp->src[i].ide); + qemu_put_sbe32s(f, &opp->src[i].type); + qemu_put_sbe32s(f, &opp->src[i].last_cpu); + qemu_put_sbe32s(f, &opp->src[i].pending); + } + + qemu_put_sbe32s(f, &opp->nb_cpus); + + for (i = 0; i < opp->nb_cpus; i++) { + qemu_put_be32s(f, &opp->dst[i].tfrr); + qemu_put_be32s(f, &opp->dst[i].pctp); + qemu_put_be32s(f, &opp->dst[i].pcsr); + openpic_save_IRQ_queue(f, &opp->dst[i].raised); + openpic_save_IRQ_queue(f, &opp->dst[i].servicing); + } + + for (i = 0; i < MAX_TMR; i++) { + qemu_put_be32s(f, &opp->timers[i].ticc); + qemu_put_be32s(f, &opp->timers[i].tibc); + } + +#if MAX_DBL > 0 + qemu_put_be32s(f, &opp->dar); + + for (i = 0; i < MAX_DBL; i++) { + qemu_put_be32s(f, &opp->doorbells[i].dmr); + } +#endif + +#if MAX_MBX > 0 + for (i = 0; i < MAX_MAILBOXES; i++) { + qemu_put_be32s(f, &opp->mailboxes[i].mbr); + } +#endif + + pci_device_save(&opp->pci_dev, f); +} + +static void openpic_load_IRQ_queue(QEMUFile* f, IRQ_queue_t *q) +{ + unsigned int i; + + for (i = 0; i < BF_WIDTH(MAX_IRQ); i++) + qemu_get_be32s(f, &q->queue[i]); + + qemu_get_sbe32s(f, &q->next); + qemu_get_sbe32s(f, &q->priority); +} + +static int openpic_load(QEMUFile* f, void *opaque, int version_id) +{ + openpic_t *opp = (openpic_t *)opaque; + unsigned int i; + + if (version_id != 1) + return -EINVAL; + + qemu_get_be32s(f, &opp->frep); + qemu_get_be32s(f, &opp->glbc); + qemu_get_be32s(f, &opp->micr); + qemu_get_be32s(f, &opp->veni); + qemu_get_be32s(f, &opp->pint); + qemu_get_be32s(f, &opp->spve); + qemu_get_be32s(f, &opp->tifr); + + for (i = 0; i < opp->max_irq; i++) { + qemu_get_be32s(f, &opp->src[i].ipvp); + qemu_get_be32s(f, &opp->src[i].ide); + qemu_get_sbe32s(f, &opp->src[i].type); + qemu_get_sbe32s(f, &opp->src[i].last_cpu); + qemu_get_sbe32s(f, &opp->src[i].pending); + } + + qemu_get_sbe32s(f, &opp->nb_cpus); + + for (i = 0; i < opp->nb_cpus; i++) { + qemu_get_be32s(f, &opp->dst[i].tfrr); + qemu_get_be32s(f, &opp->dst[i].pctp); + qemu_get_be32s(f, &opp->dst[i].pcsr); + openpic_load_IRQ_queue(f, &opp->dst[i].raised); + openpic_load_IRQ_queue(f, &opp->dst[i].servicing); + } + + for (i = 0; i < MAX_TMR; i++) { + qemu_get_be32s(f, &opp->timers[i].ticc); + qemu_get_be32s(f, &opp->timers[i].tibc); + } + +#if MAX_DBL > 0 + qemu_get_be32s(f, &opp->dar); + + for (i = 0; i < MAX_DBL; i++) { + qemu_get_be32s(f, &opp->doorbells[i].dmr); + } +#endif + +#if MAX_MBX > 0 + for (i = 0; i < MAX_MAILBOXES; i++) { + qemu_get_be32s(f, &opp->mailboxes[i].mbr); + } +#endif + + return pci_device_load(&opp->pci_dev, f); +} + +static void openpic_irq_raise(openpic_t *opp, int n_CPU, IRQ_src_t *src) +{ + qemu_irq_raise(opp->dst[n_CPU].irqs[OPENPIC_OUTPUT_INT]); +} + +qemu_irq *openpic_init (PCIBus *bus, int *pmem_index, int nb_cpus, + qemu_irq **irqs, qemu_irq irq_out) +{ + openpic_t *opp; + uint8_t *pci_conf; + int i, m; + + /* XXX: for now, only one CPU is supported */ + if (nb_cpus != 1) + return NULL; + if (bus) { + opp = (openpic_t *)pci_register_device(bus, "OpenPIC", sizeof(openpic_t), + -1, NULL, NULL); + pci_conf = opp->pci_dev.config; + pci_config_set_vendor_id(pci_conf, PCI_VENDOR_ID_IBM); + pci_config_set_device_id(pci_conf, PCI_DEVICE_ID_IBM_OPENPIC2); + pci_config_set_class(pci_conf, PCI_CLASS_SYSTEM_OTHER); // FIXME? + pci_conf[0x3d] = 0x00; // no interrupt pin + + /* Register I/O spaces */ + pci_register_bar(&opp->pci_dev, 0, 0x40000, + PCI_BASE_ADDRESS_SPACE_MEMORY, &openpic_map); + } else { + opp = qemu_mallocz(sizeof(openpic_t)); + } + opp->mem_index = cpu_register_io_memory(openpic_read, openpic_write, opp, + DEVICE_LITTLE_ENDIAN); + + // isu_base &= 0xFFFC0000; + opp->nb_cpus = nb_cpus; + opp->max_irq = OPENPIC_MAX_IRQ; + opp->irq_ipi0 = OPENPIC_IRQ_IPI0; + opp->irq_tim0 = OPENPIC_IRQ_TIM0; + /* Set IRQ types */ + for (i = 0; i < OPENPIC_EXT_IRQ; i++) { + opp->src[i].type = IRQ_EXTERNAL; + } + for (; i < OPENPIC_IRQ_TIM0; i++) { + opp->src[i].type = IRQ_SPECIAL; + } +#if MAX_IPI > 0 + m = OPENPIC_IRQ_IPI0; +#else + m = OPENPIC_IRQ_DBL0; +#endif + for (; i < m; i++) { + opp->src[i].type = IRQ_TIMER; + } + for (; i < OPENPIC_MAX_IRQ; i++) { + opp->src[i].type = IRQ_INTERNAL; + } + for (i = 0; i < nb_cpus; i++) + opp->dst[i].irqs = irqs[i]; + opp->irq_out = irq_out; + + register_savevm(&opp->pci_dev.qdev, "openpic", 0, 2, + openpic_save, openpic_load, opp); + qemu_register_reset(openpic_reset, opp); + + opp->irq_raise = openpic_irq_raise; + opp->reset = openpic_reset; + + if (pmem_index) + *pmem_index = opp->mem_index; + + return qemu_allocate_irqs(openpic_set_irq, opp, opp->max_irq); +} + +static void mpic_irq_raise(openpic_t *mpp, int n_CPU, IRQ_src_t *src) +{ + int n_ci = IDR_CI0 - n_CPU; + + if(test_bit(&src->ide, n_ci)) { + qemu_irq_raise(mpp->dst[n_CPU].irqs[OPENPIC_OUTPUT_CINT]); + } + else { + qemu_irq_raise(mpp->dst[n_CPU].irqs[OPENPIC_OUTPUT_INT]); + } +} + +static void mpic_reset (void *opaque) +{ + openpic_t *mpp = (openpic_t *)opaque; + int i; + + mpp->glbc = 0x80000000; + /* Initialise controller registers */ + mpp->frep = 0x004f0002; + mpp->veni = VENI; + mpp->pint = 0x00000000; + mpp->spve = 0x0000FFFF; + /* Initialise IRQ sources */ + for (i = 0; i < mpp->max_irq; i++) { + mpp->src[i].ipvp = 0x80800000; + mpp->src[i].ide = 0x00000001; + } + /* Initialise IRQ destinations */ + for (i = 0; i < MAX_CPU; i++) { + mpp->dst[i].pctp = 0x0000000F; + mpp->dst[i].tfrr = 0x00000000; + memset(&mpp->dst[i].raised, 0, sizeof(IRQ_queue_t)); + mpp->dst[i].raised.next = -1; + memset(&mpp->dst[i].servicing, 0, sizeof(IRQ_queue_t)); + mpp->dst[i].servicing.next = -1; + } + /* Initialise timers */ + for (i = 0; i < MAX_TMR; i++) { + mpp->timers[i].ticc = 0x00000000; + mpp->timers[i].tibc = 0x80000000; + } + /* Go out of RESET state */ + mpp->glbc = 0x00000000; +} + +static void mpic_timer_write (void *opaque, target_phys_addr_t addr, uint32_t val) +{ + openpic_t *mpp = opaque; + int idx, cpu; + + DPRINTF("%s: addr " TARGET_FMT_plx " <= %08x\n", __func__, addr, val); + if (addr & 0xF) + return; + addr &= 0xFFFF; + cpu = addr >> 12; + idx = (addr >> 6) & 0x3; + switch (addr & 0x30) { + case 0x00: /* gtccr */ + break; + case 0x10: /* gtbcr */ + if ((mpp->timers[idx].ticc & 0x80000000) != 0 && + (val & 0x80000000) == 0 && + (mpp->timers[idx].tibc & 0x80000000) != 0) + mpp->timers[idx].ticc &= ~0x80000000; + mpp->timers[idx].tibc = val; + break; + case 0x20: /* GTIVPR */ + write_IRQreg(mpp, MPIC_TMR_IRQ + idx, IRQ_IPVP, val); + break; + case 0x30: /* GTIDR & TFRR */ + if ((addr & 0xF0) == 0xF0) + mpp->dst[cpu].tfrr = val; + else + write_IRQreg(mpp, MPIC_TMR_IRQ + idx, IRQ_IDE, val); + break; + } +} + +static uint32_t mpic_timer_read (void *opaque, target_phys_addr_t addr) +{ + openpic_t *mpp = opaque; + uint32_t retval; + int idx, cpu; + + DPRINTF("%s: addr " TARGET_FMT_plx "\n", __func__, addr); + retval = 0xFFFFFFFF; + if (addr & 0xF) + return retval; + addr &= 0xFFFF; + cpu = addr >> 12; + idx = (addr >> 6) & 0x3; + switch (addr & 0x30) { + case 0x00: /* gtccr */ + retval = mpp->timers[idx].ticc; + break; + case 0x10: /* gtbcr */ + retval = mpp->timers[idx].tibc; + break; + case 0x20: /* TIPV */ + retval = read_IRQreg(mpp, MPIC_TMR_IRQ + idx, IRQ_IPVP); + break; + case 0x30: /* TIDR */ + if ((addr &0xF0) == 0XF0) + retval = mpp->dst[cpu].tfrr; + else + retval = read_IRQreg(mpp, MPIC_TMR_IRQ + idx, IRQ_IDE); + break; + } + DPRINTF("%s: => %08x\n", __func__, retval); + + return retval; +} + +static void mpic_src_ext_write (void *opaque, target_phys_addr_t addr, + uint32_t val) +{ + openpic_t *mpp = opaque; + int idx = MPIC_EXT_IRQ; + + DPRINTF("%s: addr " TARGET_FMT_plx " <= %08x\n", __func__, addr, val); + if (addr & 0xF) + return; + + addr -= MPIC_EXT_REG_START & (OPENPIC_PAGE_SIZE - 1); + if (addr < MPIC_EXT_REG_SIZE) { + idx += (addr & 0xFFF0) >> 5; + if (addr & 0x10) { + /* EXDE / IFEDE / IEEDE */ + write_IRQreg(mpp, idx, IRQ_IDE, val); + } else { + /* EXVP / IFEVP / IEEVP */ + write_IRQreg(mpp, idx, IRQ_IPVP, val); + } + } +} + +static uint32_t mpic_src_ext_read (void *opaque, target_phys_addr_t addr) +{ + openpic_t *mpp = opaque; + uint32_t retval; + int idx = MPIC_EXT_IRQ; + + DPRINTF("%s: addr " TARGET_FMT_plx "\n", __func__, addr); + retval = 0xFFFFFFFF; + if (addr & 0xF) + return retval; + + addr -= MPIC_EXT_REG_START & (OPENPIC_PAGE_SIZE - 1); + if (addr < MPIC_EXT_REG_SIZE) { + idx += (addr & 0xFFF0) >> 5; + if (addr & 0x10) { + /* EXDE / IFEDE / IEEDE */ + retval = read_IRQreg(mpp, idx, IRQ_IDE); + } else { + /* EXVP / IFEVP / IEEVP */ + retval = read_IRQreg(mpp, idx, IRQ_IPVP); + } + DPRINTF("%s: => %08x\n", __func__, retval); + } + + return retval; +} + +static void mpic_src_int_write (void *opaque, target_phys_addr_t addr, + uint32_t val) +{ + openpic_t *mpp = opaque; + int idx = MPIC_INT_IRQ; + + DPRINTF("%s: addr " TARGET_FMT_plx " <= %08x\n", __func__, addr, val); + if (addr & 0xF) + return; + + addr -= MPIC_INT_REG_START & (OPENPIC_PAGE_SIZE - 1); + if (addr < MPIC_INT_REG_SIZE) { + idx += (addr & 0xFFF0) >> 5; + if (addr & 0x10) { + /* EXDE / IFEDE / IEEDE */ + write_IRQreg(mpp, idx, IRQ_IDE, val); + } else { + /* EXVP / IFEVP / IEEVP */ + write_IRQreg(mpp, idx, IRQ_IPVP, val); + } + } +} + +static uint32_t mpic_src_int_read (void *opaque, target_phys_addr_t addr) +{ + openpic_t *mpp = opaque; + uint32_t retval; + int idx = MPIC_INT_IRQ; + + DPRINTF("%s: addr " TARGET_FMT_plx "\n", __func__, addr); + retval = 0xFFFFFFFF; + if (addr & 0xF) + return retval; + + addr -= MPIC_INT_REG_START & (OPENPIC_PAGE_SIZE - 1); + if (addr < MPIC_INT_REG_SIZE) { + idx += (addr & 0xFFF0) >> 5; + if (addr & 0x10) { + /* EXDE / IFEDE / IEEDE */ + retval = read_IRQreg(mpp, idx, IRQ_IDE); + } else { + /* EXVP / IFEVP / IEEVP */ + retval = read_IRQreg(mpp, idx, IRQ_IPVP); + } + DPRINTF("%s: => %08x\n", __func__, retval); + } + + return retval; +} + +static void mpic_src_msg_write (void *opaque, target_phys_addr_t addr, + uint32_t val) +{ + openpic_t *mpp = opaque; + int idx = MPIC_MSG_IRQ; + + DPRINTF("%s: addr " TARGET_FMT_plx " <= %08x\n", __func__, addr, val); + if (addr & 0xF) + return; + + addr -= MPIC_MSG_REG_START & (OPENPIC_PAGE_SIZE - 1); + if (addr < MPIC_MSG_REG_SIZE) { + idx += (addr & 0xFFF0) >> 5; + if (addr & 0x10) { + /* EXDE / IFEDE / IEEDE */ + write_IRQreg(mpp, idx, IRQ_IDE, val); + } else { + /* EXVP / IFEVP / IEEVP */ + write_IRQreg(mpp, idx, IRQ_IPVP, val); + } + } +} + +static uint32_t mpic_src_msg_read (void *opaque, target_phys_addr_t addr) +{ + openpic_t *mpp = opaque; + uint32_t retval; + int idx = MPIC_MSG_IRQ; + + DPRINTF("%s: addr " TARGET_FMT_plx "\n", __func__, addr); + retval = 0xFFFFFFFF; + if (addr & 0xF) + return retval; + + addr -= MPIC_MSG_REG_START & (OPENPIC_PAGE_SIZE - 1); + if (addr < MPIC_MSG_REG_SIZE) { + idx += (addr & 0xFFF0) >> 5; + if (addr & 0x10) { + /* EXDE / IFEDE / IEEDE */ + retval = read_IRQreg(mpp, idx, IRQ_IDE); + } else { + /* EXVP / IFEVP / IEEVP */ + retval = read_IRQreg(mpp, idx, IRQ_IPVP); + } + DPRINTF("%s: => %08x\n", __func__, retval); + } + + return retval; +} + +static void mpic_src_msi_write (void *opaque, target_phys_addr_t addr, + uint32_t val) +{ + openpic_t *mpp = opaque; + int idx = MPIC_MSI_IRQ; + + DPRINTF("%s: addr " TARGET_FMT_plx " <= %08x\n", __func__, addr, val); + if (addr & 0xF) + return; + + addr -= MPIC_MSI_REG_START & (OPENPIC_PAGE_SIZE - 1); + if (addr < MPIC_MSI_REG_SIZE) { + idx += (addr & 0xFFF0) >> 5; + if (addr & 0x10) { + /* EXDE / IFEDE / IEEDE */ + write_IRQreg(mpp, idx, IRQ_IDE, val); + } else { + /* EXVP / IFEVP / IEEVP */ + write_IRQreg(mpp, idx, IRQ_IPVP, val); + } + } +} +static uint32_t mpic_src_msi_read (void *opaque, target_phys_addr_t addr) +{ + openpic_t *mpp = opaque; + uint32_t retval; + int idx = MPIC_MSI_IRQ; + + DPRINTF("%s: addr " TARGET_FMT_plx "\n", __func__, addr); + retval = 0xFFFFFFFF; + if (addr & 0xF) + return retval; + + addr -= MPIC_MSI_REG_START & (OPENPIC_PAGE_SIZE - 1); + if (addr < MPIC_MSI_REG_SIZE) { + idx += (addr & 0xFFF0) >> 5; + if (addr & 0x10) { + /* EXDE / IFEDE / IEEDE */ + retval = read_IRQreg(mpp, idx, IRQ_IDE); + } else { + /* EXVP / IFEVP / IEEVP */ + retval = read_IRQreg(mpp, idx, IRQ_IPVP); + } + DPRINTF("%s: => %08x\n", __func__, retval); + } + + return retval; +} + +static CPUWriteMemoryFunc * const mpic_glb_write[] = { + &openpic_buggy_write, + &openpic_buggy_write, + &openpic_gbl_write, +}; + +static CPUReadMemoryFunc * const mpic_glb_read[] = { + &openpic_buggy_read, + &openpic_buggy_read, + &openpic_gbl_read, +}; + +static CPUWriteMemoryFunc * const mpic_tmr_write[] = { + &openpic_buggy_write, + &openpic_buggy_write, + &mpic_timer_write, +}; + +static CPUReadMemoryFunc * const mpic_tmr_read[] = { + &openpic_buggy_read, + &openpic_buggy_read, + &mpic_timer_read, +}; + +static CPUWriteMemoryFunc * const mpic_cpu_write[] = { + &openpic_buggy_write, + &openpic_buggy_write, + &openpic_cpu_write, +}; + +static CPUReadMemoryFunc * const mpic_cpu_read[] = { + &openpic_buggy_read, + &openpic_buggy_read, + &openpic_cpu_read, +}; + +static CPUWriteMemoryFunc * const mpic_ext_write[] = { + &openpic_buggy_write, + &openpic_buggy_write, + &mpic_src_ext_write, +}; + +static CPUReadMemoryFunc * const mpic_ext_read[] = { + &openpic_buggy_read, + &openpic_buggy_read, + &mpic_src_ext_read, +}; + +static CPUWriteMemoryFunc * const mpic_int_write[] = { + &openpic_buggy_write, + &openpic_buggy_write, + &mpic_src_int_write, +}; + +static CPUReadMemoryFunc * const mpic_int_read[] = { + &openpic_buggy_read, + &openpic_buggy_read, + &mpic_src_int_read, +}; + +static CPUWriteMemoryFunc * const mpic_msg_write[] = { + &openpic_buggy_write, + &openpic_buggy_write, + &mpic_src_msg_write, +}; + +static CPUReadMemoryFunc * const mpic_msg_read[] = { + &openpic_buggy_read, + &openpic_buggy_read, + &mpic_src_msg_read, +}; +static CPUWriteMemoryFunc * const mpic_msi_write[] = { + &openpic_buggy_write, + &openpic_buggy_write, + &mpic_src_msi_write, +}; + +static CPUReadMemoryFunc * const mpic_msi_read[] = { + &openpic_buggy_read, + &openpic_buggy_read, + &mpic_src_msi_read, +}; + +qemu_irq *mpic_init (target_phys_addr_t base, int nb_cpus, + qemu_irq **irqs, qemu_irq irq_out) +{ + openpic_t *mpp; + int i; + struct { + CPUReadMemoryFunc * const *read; + CPUWriteMemoryFunc * const *write; + target_phys_addr_t start_addr; + ram_addr_t size; + } const list[] = { + {mpic_glb_read, mpic_glb_write, MPIC_GLB_REG_START, MPIC_GLB_REG_SIZE}, + {mpic_tmr_read, mpic_tmr_write, MPIC_TMR_REG_START, MPIC_TMR_REG_SIZE}, + {mpic_ext_read, mpic_ext_write, MPIC_EXT_REG_START, MPIC_EXT_REG_SIZE}, + {mpic_int_read, mpic_int_write, MPIC_INT_REG_START, MPIC_INT_REG_SIZE}, + {mpic_msg_read, mpic_msg_write, MPIC_MSG_REG_START, MPIC_MSG_REG_SIZE}, + {mpic_msi_read, mpic_msi_write, MPIC_MSI_REG_START, MPIC_MSI_REG_SIZE}, + {mpic_cpu_read, mpic_cpu_write, MPIC_CPU_REG_START, MPIC_CPU_REG_SIZE}, + }; + + /* XXX: for now, only one CPU is supported */ + if (nb_cpus != 1) + return NULL; + + mpp = qemu_mallocz(sizeof(openpic_t)); + + for (i = 0; i < sizeof(list)/sizeof(list[0]); i++) { + int mem_index; + + mem_index = cpu_register_io_memory(list[i].read, list[i].write, mpp, + DEVICE_BIG_ENDIAN); + if (mem_index < 0) { + goto free; + } + cpu_register_physical_memory(base + list[i].start_addr, + list[i].size, mem_index); + } + + mpp->nb_cpus = nb_cpus; + mpp->max_irq = MPIC_MAX_IRQ; + mpp->irq_ipi0 = MPIC_IPI_IRQ; + mpp->irq_tim0 = MPIC_TMR_IRQ; + + for (i = 0; i < nb_cpus; i++) + mpp->dst[i].irqs = irqs[i]; + mpp->irq_out = irq_out; + + mpp->irq_raise = mpic_irq_raise; + mpp->reset = mpic_reset; + + register_savevm(NULL, "mpic", 0, 2, openpic_save, openpic_load, mpp); + qemu_register_reset(mpic_reset, mpp); + + return qemu_allocate_irqs(openpic_set_irq, mpp, mpp->max_irq); + +free: + qemu_free(mpp); + return NULL; +} diff --git a/hw/openpic.h b/hw/openpic.h new file mode 100644 index 0000000..0957c1f --- /dev/null +++ b/hw/openpic.h @@ -0,0 +1,18 @@ +#if !defined(__OPENPIC_H__) +#define __OPENPIC_H__ + +/* OpenPIC have 5 outputs per CPU connected and one IRQ out single output */ +enum { + OPENPIC_OUTPUT_INT = 0, /* IRQ */ + OPENPIC_OUTPUT_CINT, /* critical IRQ */ + OPENPIC_OUTPUT_MCK, /* Machine check event */ + OPENPIC_OUTPUT_DEBUG, /* Inconditional debug event */ + OPENPIC_OUTPUT_RESET, /* Core reset event */ + OPENPIC_OUTPUT_NB, +}; + +qemu_irq *openpic_init (PCIBus *bus, int *pmem_index, int nb_cpus, + qemu_irq **irqs, qemu_irq irq_out); +qemu_irq *mpic_init (target_phys_addr_t base, int nb_cpus, + qemu_irq **irqs, qemu_irq irq_out); +#endif /* __OPENPIC_H__ */ diff --git a/hw/palm.c b/hw/palm.c new file mode 100644 index 0000000..f22d777 --- /dev/null +++ b/hw/palm.c @@ -0,0 +1,289 @@ +/* + * PalmOne's (TM) PDAs. + * + * Copyright (C) 2006-2007 Andrzej Zaborowski + * + * 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 . + */ +#include "hw.h" +#include "audio/audio.h" +#include "sysemu.h" +#include "console.h" +#include "omap.h" +#include "boards.h" +#include "arm-misc.h" +#include "devices.h" +#include "loader.h" + +static uint32_t static_readb(void *opaque, target_phys_addr_t offset) +{ + uint32_t *val = (uint32_t *) opaque; + return *val >> ((offset & 3) << 3); +} + +static uint32_t static_readh(void *opaque, target_phys_addr_t offset) +{ + uint32_t *val = (uint32_t *) opaque; + return *val >> ((offset & 1) << 3); +} + +static uint32_t static_readw(void *opaque, target_phys_addr_t offset) +{ + uint32_t *val = (uint32_t *) opaque; + return *val >> ((offset & 0) << 3); +} + +static void static_write(void *opaque, target_phys_addr_t offset, + uint32_t value) +{ +#ifdef SPY + printf("%s: value %08lx written at " PA_FMT "\n", + __FUNCTION__, value, offset); +#endif +} + +static CPUReadMemoryFunc * const static_readfn[] = { + static_readb, + static_readh, + static_readw, +}; + +static CPUWriteMemoryFunc * const static_writefn[] = { + static_write, + static_write, + static_write, +}; + +/* 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(omap_gpio_in_get(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, + omap_gpio_in_get(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); + omap_gpio_out_set(cpu->gpio, PALMTE_MMC_POWER_GPIO, misc_gpio[0]); + omap_gpio_out_set(cpu->gpio, PALMTE_SPEAKER_GPIO, misc_gpio[1]); + omap_gpio_out_set(cpu->gpio, 11, misc_gpio[2]); + omap_gpio_out_set(cpu->gpio, 12, misc_gpio[3]); + omap_gpio_out_set(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(omap_gpio_in_get(cpu->gpio)[PALMTE_USBDETECT_GPIO]); + qemu_irq_lower(omap_gpio_in_get(cpu->gpio)[PALMTE_USB_OR_DC_GPIO]); + qemu_irq_lower(omap_gpio_in_get(cpu->gpio)[4]); + qemu_irq_lower(omap_gpio_in_get(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(ram_addr_t ram_size, + const char *boot_device, + const char *kernel_filename, const char *kernel_cmdline, + const char *initrd_filename, const char *cpu_model) +{ + struct omap_mpu_state_s *cpu; + int flash_size = 0x00800000; + int sdram_size = palmte_binfo.ram_size; + int io; + 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; + DisplayState *ds = get_displaystate(); + + cpu = omap310_mpu_init(sdram_size, cpu_model); + + /* External Flash (EMIFS) */ + cpu_register_physical_memory(OMAP_CS0_BASE, flash_size, + qemu_ram_alloc(NULL, "palmte.flash", + flash_size) | IO_MEM_ROM); + + io = cpu_register_io_memory(static_readfn, static_writefn, &cs0val, + DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(OMAP_CS0_BASE + flash_size, + OMAP_CS0_SIZE - flash_size, io); + io = cpu_register_io_memory(static_readfn, static_writefn, &cs1val, + DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(OMAP_CS1_BASE, OMAP_CS1_SIZE, io); + io = cpu_register_io_memory(static_readfn, static_writefn, &cs2val, + DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(OMAP_CS2_BASE, OMAP_CS2_SIZE, io); + io = cpu_register_io_memory(static_readfn, static_writefn, &cs3val, + DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(OMAP_CS3_BASE, OMAP_CS3_SIZE, io); + + palmte_microwire_setup(cpu); + + qemu_add_kbd_event_handler(palmte_button_event, cpu); + + palmte_gpio_setup(cpu); + + /* 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) { + fprintf(stderr, "Kernel or ROM image must be specified\n"); + exit(1); + } + + /* Load the kernel. */ + if (kernel_filename) { + palmte_binfo.kernel_filename = kernel_filename; + palmte_binfo.kernel_cmdline = kernel_cmdline; + palmte_binfo.initrd_filename = initrd_filename; + arm_load_kernel(cpu->env, &palmte_binfo); + } + + /* FIXME: We shouldn't really be doing this here. The LCD controller + will set the size once configured, so this just sets an initial + size until the guest activates the display. */ + ds->surface = qemu_resize_displaysurface(ds, 320, 320); + dpy_resize(ds); +} + +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/parallel.c b/hw/parallel.c new file mode 100644 index 0000000..ce311aa --- /dev/null +++ b/hw/parallel.c @@ -0,0 +1,609 @@ +/* + * 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.h" +#include "qemu-char.h" +#include "isa.h" +#include "pc.h" +#include "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) + +struct ParallelState { + 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; +}; + +typedef struct ISAParallelState { + ISADevice dev; + 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_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_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_ioctl(s->chr, CHR_IOCTL_PP_DATA_DIR, &dir); + parm &= ~PARA_CTR_DIR; + } + + qemu_chr_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_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_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_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_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_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_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_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_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_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_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_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 int parallel_isa_initfn(ISADevice *dev) +{ + static int index; + ISAParallelState *isa = DO_UPCAST(ISAParallelState, dev, dev); + ParallelState *s = &isa->state; + int base; + uint8_t dummy; + + if (!s->chr) { + fprintf(stderr, "Can't create parallel device, empty char device\n"); + exit(1); + } + + if (isa->index == -1) + isa->index = index; + if (isa->index >= MAX_PARALLEL_PORTS) + return -1; + if (isa->iobase == -1) + isa->iobase = isa_parallel_io[isa->index]; + index++; + + base = isa->iobase; + isa_init_irq(dev, &s->irq, isa->isairq); + qemu_register_reset(parallel_reset, s); + + if (qemu_chr_ioctl(s->chr, CHR_IOCTL_PP_READ_STATUS, &dummy) == 0) { + s->hw_driver = 1; + s->status = dummy; + } + + if (s->hw_driver) { + register_ioport_write(base, 8, 1, parallel_ioport_write_hw, s); + register_ioport_read(base, 8, 1, parallel_ioport_read_hw, s); + isa_init_ioport_range(dev, base, 8); + + register_ioport_write(base+4, 1, 2, parallel_ioport_eppdata_write_hw2, s); + register_ioport_read(base+4, 1, 2, parallel_ioport_eppdata_read_hw2, s); + register_ioport_write(base+4, 1, 4, parallel_ioport_eppdata_write_hw4, s); + register_ioport_read(base+4, 1, 4, parallel_ioport_eppdata_read_hw4, s); + isa_init_ioport(dev, base+4); + register_ioport_write(base+0x400, 8, 1, parallel_ioport_ecp_write, s); + register_ioport_read(base+0x400, 8, 1, parallel_ioport_ecp_read, s); + isa_init_ioport_range(dev, base+0x400, 8); + } + else { + register_ioport_write(base, 8, 1, parallel_ioport_write_sw, s); + register_ioport_read(base, 8, 1, parallel_ioport_read_sw, s); + isa_init_ioport_range(dev, base, 8); + } + return 0; +} + +ParallelState *parallel_init(int index, CharDriverState *chr) +{ + ISADevice *dev; + + dev = isa_create("isa-parallel"); + qdev_prop_set_uint32(&dev->qdev, "index", index); + qdev_prop_set_chr(&dev->qdev, "chardev", chr); + if (qdev_init(&dev->qdev) < 0) + return NULL; + return &DO_UPCAST(ISAParallelState, dev, dev)->state; +} + +/* Memory mapped interface */ +static uint32_t parallel_mm_readb (void *opaque, target_phys_addr_t addr) +{ + ParallelState *s = opaque; + + return parallel_ioport_read_sw(s, addr >> s->it_shift) & 0xFF; +} + +static void parallel_mm_writeb (void *opaque, + target_phys_addr_t 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, target_phys_addr_t addr) +{ + ParallelState *s = opaque; + + return parallel_ioport_read_sw(s, addr >> s->it_shift) & 0xFFFF; +} + +static void parallel_mm_writew (void *opaque, + target_phys_addr_t 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, target_phys_addr_t addr) +{ + ParallelState *s = opaque; + + return parallel_ioport_read_sw(s, addr >> s->it_shift); +} + +static void parallel_mm_writel (void *opaque, + target_phys_addr_t addr, uint32_t value) +{ + ParallelState *s = opaque; + + parallel_ioport_write_sw(s, addr >> s->it_shift, value); +} + +static CPUReadMemoryFunc * const parallel_mm_read_sw[] = { + ¶llel_mm_readb, + ¶llel_mm_readw, + ¶llel_mm_readl, +}; + +static CPUWriteMemoryFunc * const parallel_mm_write_sw[] = { + ¶llel_mm_writeb, + ¶llel_mm_writew, + ¶llel_mm_writel, +}; + +/* If fd is zero, it means that the parallel device uses the console */ +ParallelState *parallel_mm_init(target_phys_addr_t base, int it_shift, qemu_irq irq, CharDriverState *chr) +{ + ParallelState *s; + int io_sw; + + s = qemu_mallocz(sizeof(ParallelState)); + s->irq = irq; + s->chr = chr; + s->it_shift = it_shift; + qemu_register_reset(parallel_reset, s); + + io_sw = cpu_register_io_memory(parallel_mm_read_sw, parallel_mm_write_sw, + s, DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(base, 8 << it_shift, io_sw); + return s; +} + +static ISADeviceInfo parallel_isa_info = { + .qdev.name = "isa-parallel", + .qdev.size = sizeof(ISAParallelState), + .init = parallel_isa_initfn, + .qdev.props = (Property[]) { + DEFINE_PROP_UINT32("index", ISAParallelState, index, -1), + DEFINE_PROP_HEX32("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_register_devices(void) +{ + isa_qdev_register(¶llel_isa_info); +} + +device_init(parallel_register_devices) diff --git a/hw/pc.c b/hw/pc.c new file mode 100644 index 0000000..9f1b776 --- /dev/null +++ b/hw/pc.c @@ -0,0 +1,1153 @@ +/* + * 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.h" +#include "pc.h" +#include "apic.h" +#include "fdc.h" +#include "ide.h" +#include "pci.h" +#include "vmware_vga.h" +#include "monitor.h" +#include "fw_cfg.h" +#include "hpet_emul.h" +#include "smbios.h" +#include "loader.h" +#include "elf.h" +#include "multiboot.h" +#include "mc146818rtc.h" +#include "msix.h" +#include "sysbus.h" +#include "sysemu.h" +#include "blockdev.h" +#include "ui/qemu-spice.h" + +/* output Bochs bios info messages */ +//#define DEBUG_BIOS + +/* 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 + +#define BIOS_FILENAME "bios.bin" + +#define PC_MAX_BIOS_SIZE (4 * 1024 * 1024) + +/* Leave a chunk of memory at the top of RAM for the BIOS ACPI tables. */ +#define 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 MSI_ADDR_BASE 0xfee00000 + +#define E820_NR_ENTRIES 16 + +struct e820_entry { + uint64_t address; + uint64_t length; + uint32_t type; +} __attribute((__packed__, __aligned__(4))); + +struct e820_table { + uint32_t count; + struct e820_entry entry[E820_NR_ENTRIES]; +} __attribute((__packed__, __aligned__(4))); + +static struct e820_table e820_table; + +void isa_irq_handler(void *opaque, int n, int level) +{ + IsaIrqState *isa = (IsaIrqState *)opaque; + + DPRINTF("isa_irqs: %s irq %d\n", level? "raise" : "lower", n); + if (n < 16) { + qemu_set_irq(isa->i8259[n], level); + } + if (isa->ioapic) + qemu_set_irq(isa->ioapic[n], level); +}; + +static void ioport80_write(void *opaque, uint32_t addr, uint32_t data) +{ +} + +/* 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, uint32_t addr, uint32_t data) +{ + qemu_irq_lower(ferr_irq); +} + +/* TSC handling */ +uint64_t cpu_get_tsc(CPUX86State *env) +{ + return cpu_get_ticks(); +} + +/* SMM support */ + +static cpu_set_smm_t smm_set; +static void *smm_arg; + +void cpu_smm_register(cpu_set_smm_t callback, void *arg) +{ + assert(smm_set == NULL); + assert(smm_arg == NULL); + smm_set = callback; + smm_arg = arg; +} + +void cpu_smm_update(CPUState *env) +{ + if (smm_set && smm_arg && env == first_cpu) + smm_set(!!(env->hflags & HF_SMM_MASK), smm_arg); +} + + +/* IRQ handling */ +int cpu_get_pic_interrupt(CPUState *env) +{ + int intno; + + intno = apic_get_interrupt(env->apic_state); + if (intno >= 0) { + /* set irq request if a PIC irq is still pending */ + /* XXX: improve that */ + pic_update_irq(isa_pic); + return intno; + } + /* read the irq from the PIC */ + if (!apic_accept_pic_intr(env->apic_state)) { + return -1; + } + + intno = pic_read_irq(isa_pic); + return intno; +} + +static void pic_irq_request(void *opaque, int irq, int level) +{ + CPUState *env = first_cpu; + + DPRINTF("pic_irqs: %s irq %d\n", level? "raise" : "lower", irq); + if (env->apic_state) { + while (env) { + if (apic_accept_pic_intr(env->apic_state)) { + apic_deliver_pic_intr(env->apic_state, level); + } + env = env->next_cpu; + } + } else { + if (level) + cpu_interrupt(env, CPU_INTERRUPT_HARD); + else + cpu_reset_interrupt(env, CPU_INTERRUPT_HARD); + } +} + +/* PC cmos mappings */ + +#define REG_EQUIPMENT_BYTE 0x14 + +static int cmos_get_fd_drive_type(int fd0) +{ + int val; + + switch (fd0) { + case 0: + /* 1.44 Mb 3"5 drive */ + val = 4; + break; + case 1: + /* 2.88 Mb 3"5 drive */ + val = 5; + break; + case 2: + /* 1.2 Mb 5"5 drive */ + val = 2; + break; + default: + val = 0; + break; + } + return val; +} + +static void cmos_init_hd(int type_ofs, int info_ofs, BlockDriverState *hd, + ISADevice *s) +{ + int cylinders, heads, sectors; + bdrv_get_geometry_hint(hd, &cylinders, &heads, §ors); + 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 int set_boot_dev(ISADevice *s, const char *boot_device, int fd_bootchk) +{ +#define PC_MAX_BOOT_DEVICES 3 + int nbds, bds[3] = { 0, }; + int i; + + nbds = strlen(boot_device); + if (nbds > PC_MAX_BOOT_DEVICES) { + error_report("Too many boot devices for PC"); + return(1); + } + for (i = 0; i < nbds; i++) { + bds[i] = boot_device2nibble(boot_device[i]); + if (bds[i] == 0) { + error_report("Invalid boot device for PC: '%c'", + boot_device[i]); + return(1); + } + } + rtc_set_memory(s, 0x3d, (bds[1] << 4) | bds[0]); + rtc_set_memory(s, 0x38, (bds[2] << 4) | (fd_bootchk ? 0x0 : 0x1)); + return(0); +} + +static int pc_boot_set(void *opaque, const char *boot_device) +{ + return set_boot_dev(opaque, boot_device, 0); +} + +typedef struct pc_cmos_init_late_arg { + ISADevice *rtc_state; + BusState *idebus0, *idebus1; +} pc_cmos_init_late_arg; + +static void pc_cmos_init_late(void *opaque) +{ + pc_cmos_init_late_arg *arg = opaque; + ISADevice *s = arg->rtc_state; + int val; + BlockDriverState *hd_table[4]; + int i; + + ide_get_bs(hd_table, arg->idebus0); + ide_get_bs(hd_table + 2, arg->idebus1); + + rtc_set_memory(s, 0x12, (hd_table[0] ? 0xf0 : 0) | (hd_table[1] ? 0x0f : 0)); + if (hd_table[0]) + cmos_init_hd(0x19, 0x1b, hd_table[0], s); + if (hd_table[1]) + cmos_init_hd(0x1a, 0x24, hd_table[1], s); + + val = 0; + for (i = 0; i < 4; i++) { + if (hd_table[i]) { + int cylinders, heads, sectors, translation; + /* NOTE: bdrv_get_geometry_hint() 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. */ + translation = bdrv_get_translation_hint(hd_table[i]); + if (translation == BIOS_ATA_TRANSLATION_AUTO) { + bdrv_get_geometry_hint(hd_table[i], &cylinders, &heads, §ors); + if (cylinders <= 1024 && heads <= 16 && sectors <= 63) { + /* No translation. */ + translation = 0; + } else { + /* LBA translation. */ + translation = 1; + } + } else { + translation--; + } + val |= translation << (i * 2); + } + } + rtc_set_memory(s, 0x39, val); + + 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, + BusState *idebus0, BusState *idebus1, + FDCtrl *floppy_controller, ISADevice *s) +{ + int val; + int fd0, fd1, nb; + static pc_cmos_init_late_arg arg; + + /* various important CMOS locations needed by PC/Bochs bios */ + + /* memory size */ + val = 640; /* base memory in K */ + rtc_set_memory(s, 0x15, val); + rtc_set_memory(s, 0x16, val >> 8); + + val = (ram_size / 1024) - 1024; + 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); + + if (above_4g_mem_size) { + rtc_set_memory(s, 0x5b, (unsigned int)above_4g_mem_size >> 16); + rtc_set_memory(s, 0x5c, (unsigned int)above_4g_mem_size >> 24); + rtc_set_memory(s, 0x5d, (uint64_t)above_4g_mem_size >> 32); + } + + if (ram_size > (16 * 1024 * 1024)) + val = (ram_size / 65536) - ((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); + + /* set the number of CPU */ + rtc_set_memory(s, 0x5f, smp_cpus - 1); + + /* set boot devices, and disable floppy signature check if requested */ + if (set_boot_dev(s, boot_device, fd_bootchk)) { + exit(1); + } + + /* floppy type */ + + fd0 = fdctrl_get_drive_type(floppy_controller, 0); + fd1 = fdctrl_get_drive_type(floppy_controller, 1); + + val = (cmos_get_fd_drive_type(fd0) << 4) | cmos_get_fd_drive_type(fd1); + rtc_set_memory(s, 0x10, val); + + val = 0; + nb = 0; + if (fd0 < 3) + nb++; + if (fd1 < 3) + 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; + } + val |= 0x02; /* FPU is there */ + val |= 0x04; /* PS/2 mouse installed */ + rtc_set_memory(s, REG_EQUIPMENT_BYTE, val); + + /* hard drives */ + arg.rtc_state = s; + arg.idebus0 = idebus0; + arg.idebus1 = idebus1; + qemu_register_reset(pc_cmos_init_late, &arg); +} + +/* port 92 stuff: could be split off */ +typedef struct Port92State { + ISADevice dev; + uint8_t outport; + qemu_irq *a20_out; +} Port92State; + +static void port92_write(void *opaque, uint32_t addr, uint32_t val) +{ + Port92State *s = opaque; + + DPRINTF("port92: write 0x%02x\n", val); + s->outport = val; + qemu_set_irq(*s->a20_out, (val >> 1) & 1); + if (val & 1) { + qemu_system_reset_request(); + } +} + +static uint32_t port92_read(void *opaque, uint32_t addr) +{ + 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 = DO_UPCAST(Port92State, dev, dev); + + s->a20_out = a20_out; +} + +static const VMStateDescription vmstate_port92_isa = { + .name = "port92", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField []) { + VMSTATE_UINT8(outport, Port92State), + VMSTATE_END_OF_LIST() + } +}; + +static void port92_reset(DeviceState *d) +{ + Port92State *s = container_of(d, Port92State, dev.qdev); + + s->outport &= ~1; +} + +static int port92_initfn(ISADevice *dev) +{ + Port92State *s = DO_UPCAST(Port92State, dev, dev); + + register_ioport_read(0x92, 1, 1, port92_read, s); + register_ioport_write(0x92, 1, 1, port92_write, s); + isa_init_ioport(dev, 0x92); + s->outport = 0; + return 0; +} + +static ISADeviceInfo port92_info = { + .qdev.name = "port92", + .qdev.size = sizeof(Port92State), + .qdev.vmsd = &vmstate_port92_isa, + .qdev.no_user = 1, + .qdev.reset = port92_reset, + .init = port92_initfn, +}; + +static void port92_register(void) +{ + isa_qdev_register(&port92_info); +} +device_init(port92_register) + +static void handle_a20_line_change(void *opaque, int irq, int level) +{ + CPUState *cpu = opaque; + + /* XXX: send to all CPUs ? */ + /* XXX: add logic to handle multiple A20 line sources */ + cpu_x86_set_a20(cpu, level); +} + +/***********************************************************/ +/* Bochs BIOS debug ports */ + +static void bochs_bios_write(void *opaque, uint32_t addr, uint32_t val) +{ + static const char shutdown_str[8] = "Shutdown"; + static int shutdown_index = 0; + + switch(addr) { + /* Bochs BIOS messages */ + case 0x400: + case 0x401: + /* used to be panic, now unused */ + break; + case 0x402: + case 0x403: +#ifdef DEBUG_BIOS + fprintf(stderr, "%c", val); +#endif + break; + case 0x8900: + /* same as Bochs power off */ + if (val == shutdown_str[shutdown_index]) { + shutdown_index++; + if (shutdown_index == 8) { + shutdown_index = 0; + qemu_system_shutdown_request(); + } + } else { + shutdown_index = 0; + } + break; + + /* LGPL'ed VGA BIOS messages */ + case 0x501: + case 0x502: + fprintf(stderr, "VGA BIOS panic, line %d\n", val); + exit(1); + case 0x500: + case 0x503: +#ifdef DEBUG_BIOS + fprintf(stderr, "%c", val); +#endif + break; + } +} + +int e820_add_entry(uint64_t address, uint64_t length, uint32_t type) +{ + int index = le32_to_cpu(e820_table.count); + struct e820_entry *entry; + + if (index >= E820_NR_ENTRIES) + return -EBUSY; + entry = &e820_table.entry[index++]; + + entry->address = cpu_to_le64(address); + entry->length = cpu_to_le64(length); + entry->type = cpu_to_le32(type); + + e820_table.count = cpu_to_le32(index); + return index; +} + +static void *bochs_bios_init(void) +{ + void *fw_cfg; + uint8_t *smbios_table; + size_t smbios_len; + uint64_t *numa_fw_cfg; + int i, j; + + register_ioport_write(0x400, 1, 2, bochs_bios_write, NULL); + register_ioport_write(0x401, 1, 2, bochs_bios_write, NULL); + register_ioport_write(0x402, 1, 1, bochs_bios_write, NULL); + register_ioport_write(0x403, 1, 1, bochs_bios_write, NULL); + register_ioport_write(0x8900, 1, 1, bochs_bios_write, NULL); + + register_ioport_write(0x501, 1, 2, bochs_bios_write, NULL); + register_ioport_write(0x502, 1, 2, bochs_bios_write, NULL); + register_ioport_write(0x500, 1, 1, bochs_bios_write, NULL); + register_ioport_write(0x503, 1, 1, bochs_bios_write, NULL); + + fw_cfg = fw_cfg_init(BIOS_CFG_IOPORT, BIOS_CFG_IOPORT + 1, 0, 0); + + fw_cfg_add_i32(fw_cfg, FW_CFG_ID, 1); + fw_cfg_add_i64(fw_cfg, FW_CFG_RAM_SIZE, (uint64_t)ram_size); + fw_cfg_add_bytes(fw_cfg, FW_CFG_ACPI_TABLES, (uint8_t *)acpi_tables, + acpi_tables_len); + fw_cfg_add_bytes(fw_cfg, FW_CFG_IRQ0_OVERRIDE, &irq0override, 1); + + smbios_table = smbios_get_table(&smbios_len); + if (smbios_table) + fw_cfg_add_bytes(fw_cfg, FW_CFG_SMBIOS_ENTRIES, + smbios_table, smbios_len); + fw_cfg_add_bytes(fw_cfg, FW_CFG_E820_TABLE, (uint8_t *)&e820_table, + sizeof(struct e820_table)); + + fw_cfg_add_bytes(fw_cfg, FW_CFG_HPET, (uint8_t *)&hpet_cfg, + sizeof(struct hpet_fw_config)); + /* 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 = qemu_mallocz((1 + smp_cpus + nb_numa_nodes) * 8); + numa_fw_cfg[0] = cpu_to_le64(nb_numa_nodes); + for (i = 0; i < smp_cpus; i++) { + for (j = 0; j < nb_numa_nodes; j++) { + if (node_cpumask[j] & (1 << i)) { + numa_fw_cfg[i + 1] = cpu_to_le64(j); + break; + } + } + } + for (i = 0; i < nb_numa_nodes; i++) { + numa_fw_cfg[smp_cpus + 1 + i] = cpu_to_le64(node_mem[i]); + } + fw_cfg_add_bytes(fw_cfg, FW_CFG_NUMA, (uint8_t *)numa_fw_cfg, + (1 + smp_cpus + nb_numa_nodes) * 8); + + 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(void *fw_cfg, + const char *kernel_filename, + const char *initrd_filename, + const char *kernel_cmdline, + target_phys_addr_t 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; + target_phys_addr_t 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_bytes(fw_cfg, FW_CFG_CMDLINE_DATA, + (uint8_t*)strdup(kernel_cmdline), + strlen(kernel_cmdline)+1); + + 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\n", + initrd_filename); + exit(1); + } + + initrd_addr = (initrd_max-initrd_size) & ~4095; + + initrd_data = qemu_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 = qemu_malloc(setup_size); + kernel = qemu_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 }; + +static const int parallel_io[MAX_PARALLEL_PORTS] = { 0x378, 0x278, 0x3bc }; +static const int parallel_irq[MAX_PARALLEL_PORTS] = { 7, 7, 7 }; + +void pc_init_ne2k_isa(NICInfo *nd) +{ + static int nb_ne2k = 0; + + if (nb_ne2k == NE2000_NB_MAX) + return; + isa_ne2000_init(ne2000_io[nb_ne2k], + ne2000_irq[nb_ne2k], nd); + nb_ne2k++; +} + +int cpu_is_bsp(CPUState *env) +{ + /* We hard-wire the BSP to the first CPU. */ + return env->cpu_index == 0; +} + +DeviceState *cpu_get_current_apic(void) +{ + if (cpu_single_env) { + return cpu_single_env->apic_state; + } else { + return NULL; + } +} + +static DeviceState *apic_init(void *env, uint8_t apic_id) +{ + DeviceState *dev; + SysBusDevice *d; + static int apic_mapped; + + dev = qdev_create(NULL, "apic"); + qdev_prop_set_uint8(dev, "id", apic_id); + qdev_prop_set_ptr(dev, "cpu_env", env); + qdev_init_nofail(dev); + d = sysbus_from_qdev(dev); + + /* XXX: mapping more APICs at the same memory location */ + if (apic_mapped == 0) { + /* NOTE: the APIC is directly connected to the CPU - it is not + on the global memory bus. */ + /* XXX: what if the base changes? */ + sysbus_mmio_map(d, 0, MSI_ADDR_BASE); + apic_mapped = 1; + } + + msix_supported = 1; + + return dev; +} + +/* set CMOS shutdown status register (index 0xF) as S3_resume(0xFE) + BIOS will read it and start S3 resume at POST Entry */ +void pc_cmos_set_s3_resume(void *opaque, int irq, int level) +{ + ISADevice *s = opaque; + + if (level) { + rtc_set_memory(s, 0xF, 0xFE); + } +} + +void pc_acpi_smi_interrupt(void *opaque, int irq, int level) +{ + CPUState *s = opaque; + + if (level) { + cpu_interrupt(s, CPU_INTERRUPT_SMI); + } +} + +static void pc_cpu_reset(void *opaque) +{ + CPUState *env = opaque; + + cpu_reset(env); + env->halted = !cpu_is_bsp(env); +} + +static CPUState *pc_new_cpu(const char *cpu_model) +{ + CPUState *env; + + env = cpu_init(cpu_model); + if (!env) { + fprintf(stderr, "Unable to find x86 CPU definition\n"); + exit(1); + } + if ((env->cpuid_features & CPUID_APIC) || smp_cpus > 1) { + env->cpuid_apic_id = env->cpu_index; + env->apic_state = apic_init(env, env->cpuid_apic_id); + } + qemu_register_reset(pc_cpu_reset, env); + pc_cpu_reset(env); + return env; +} + +void pc_cpus_init(const char *cpu_model) +{ + int i; + + /* init CPUs */ + if (cpu_model == NULL) { +#ifdef TARGET_X86_64 + cpu_model = "qemu64"; +#else + cpu_model = "qemu32"; +#endif + } + + for(i = 0; i < smp_cpus; i++) { + pc_new_cpu(cpu_model); + } +} + +void pc_memory_init(ram_addr_t ram_size, + const char *kernel_filename, + const char *kernel_cmdline, + const char *initrd_filename, + ram_addr_t *below_4g_mem_size_p, + ram_addr_t *above_4g_mem_size_p) +{ + char *filename; + int ret, linux_boot, i; + ram_addr_t ram_addr, bios_offset, option_rom_offset; + ram_addr_t below_4g_mem_size, above_4g_mem_size = 0; + int bios_size, isa_bios_size; + void *fw_cfg; + + if (ram_size >= 0xe0000000 ) { + above_4g_mem_size = ram_size - 0xe0000000; + below_4g_mem_size = 0xe0000000; + } else { + below_4g_mem_size = ram_size; + } + *above_4g_mem_size_p = above_4g_mem_size; + *below_4g_mem_size_p = below_4g_mem_size; + +#if TARGET_PHYS_ADDR_BITS == 32 + if (above_4g_mem_size > 0) { + hw_error("To much RAM for 32-bit physical address"); + } +#endif + linux_boot = (kernel_filename != NULL); + + /* allocate RAM */ + ram_addr = qemu_ram_alloc(NULL, "pc.ram", + below_4g_mem_size + above_4g_mem_size); + cpu_register_physical_memory(0, 0xa0000, ram_addr); + cpu_register_physical_memory(0x100000, + below_4g_mem_size - 0x100000, + ram_addr + 0x100000); +#if TARGET_PHYS_ADDR_BITS > 32 + if (above_4g_mem_size > 0) { + cpu_register_physical_memory(0x100000000ULL, above_4g_mem_size, + ram_addr + below_4g_mem_size); + } +#endif + + /* 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_offset = qemu_ram_alloc(NULL, "pc.bios", bios_size); + 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); + } + if (filename) { + qemu_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; + cpu_register_physical_memory(0x100000 - isa_bios_size, + isa_bios_size, + (bios_offset + bios_size - isa_bios_size) | IO_MEM_ROM); + + option_rom_offset = qemu_ram_alloc(NULL, "pc.rom", PC_ROM_SIZE); + cpu_register_physical_memory(PC_ROM_MIN_VGA, PC_ROM_SIZE, option_rom_offset); + + /* map all the bios at the top of memory */ + cpu_register_physical_memory((uint32_t)(-bios_size), + bios_size, bios_offset | IO_MEM_ROM); + + fw_cfg = bochs_bios_init(); + rom_set_fw(fw_cfg); + + if (linux_boot) { + load_linux(fw_cfg, kernel_filename, initrd_filename, 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); + } +} + +qemu_irq *pc_allocate_cpu_irq(void) +{ + return qemu_allocate_irqs(pic_irq_request, NULL, 1); +} + +void pc_vga_init(PCIBus *pci_bus) +{ + if (cirrus_vga_enabled) { + if (pci_bus) { + pci_cirrus_vga_init(pci_bus); + } else { + isa_cirrus_vga_init(); + } + } else if (vmsvga_enabled) { + if (pci_bus) + pci_vmsvga_init(pci_bus); + else + fprintf(stderr, "%s: vmware_vga: no PCI bus\n", __FUNCTION__); +#ifdef CONFIG_SPICE + } else if (qxl_enabled) { + if (pci_bus) + pci_create_simple(pci_bus, -1, "qxl-vga"); + else + fprintf(stderr, "%s: qxl: no PCI bus\n", __FUNCTION__); +#endif + } else if (std_vga_enabled) { + if (pci_bus) { + pci_vga_init(pci_bus); + } else { + isa_vga_init(); + } + } +} + +static void cpu_request_exit(void *opaque, int irq, int level) +{ + CPUState *env = cpu_single_env; + + if (env && level) { + cpu_exit(env); + } +} + +void pc_basic_device_init(qemu_irq *isa_irq, + FDCtrl **floppy_controller, + ISADevice **rtc_state) +{ + int i; + DriveInfo *fd[MAX_FD]; + PITState *pit; + qemu_irq rtc_irq = NULL; + qemu_irq *a20_line; + ISADevice *i8042, *port92; + qemu_irq *cpu_exit_irq; + + register_ioport_write(0x80, 1, 1, ioport80_write, NULL); + + register_ioport_write(0xf0, 1, 1, ioportF0_write, NULL); + + if (!no_hpet) { + DeviceState *hpet = sysbus_create_simple("hpet", HPET_BASE, NULL); + + for (i = 0; i < 24; i++) { + sysbus_connect_irq(sysbus_from_qdev(hpet), i, isa_irq[i]); + } + rtc_irq = qdev_get_gpio_in(hpet, 0); + } + *rtc_state = rtc_init(2000, rtc_irq); + + qemu_register_boot_set(pc_boot_set, *rtc_state); + + pit = pit_init(0x40, isa_get_irq(0)); + pcspk_init(pit); + + for(i = 0; i < MAX_SERIAL_PORTS; i++) { + if (serial_hds[i]) { + serial_isa_init(i, serial_hds[i]); + } + } + + for(i = 0; i < MAX_PARALLEL_PORTS; i++) { + if (parallel_hds[i]) { + parallel_init(i, parallel_hds[i]); + } + } + + a20_line = qemu_allocate_irqs(handle_a20_line_change, first_cpu, 2); + i8042 = isa_create_simple("i8042"); + i8042_setup_a20_line(i8042, &a20_line[0]); + vmmouse_init(i8042); + port92 = isa_create_simple("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); + } + *floppy_controller = fdctrl_init_isa(fd); +} + +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"); + } +} diff --git a/hw/pc.h b/hw/pc.h new file mode 100644 index 0000000..a048768 --- /dev/null +++ b/hw/pc.h @@ -0,0 +1,175 @@ +#ifndef HW_PC_H +#define HW_PC_H + +#include "qemu-common.h" +#include "ioport.h" +#include "isa.h" +#include "fdc.h" + +/* PC-style peripherals (also used by other machines). */ + +/* serial.c */ + +SerialState *serial_init(int base, qemu_irq irq, int baudbase, + CharDriverState *chr); +SerialState *serial_mm_init (target_phys_addr_t base, int it_shift, + qemu_irq irq, int baudbase, + CharDriverState *chr, int ioregister, + int be); +SerialState *serial_isa_init(int index, CharDriverState *chr); +void serial_set_frequency(SerialState *s, uint32_t frequency); + +/* parallel.c */ + +typedef struct ParallelState ParallelState; +ParallelState *parallel_init(int index, CharDriverState *chr); +ParallelState *parallel_mm_init(target_phys_addr_t base, int it_shift, qemu_irq irq, CharDriverState *chr); + +/* i8259.c */ + +typedef struct PicState2 PicState2; +extern PicState2 *isa_pic; +void pic_set_irq(int irq, int level); +void pic_set_irq_new(void *opaque, int irq, int level); +qemu_irq *i8259_init(qemu_irq parent_irq); +int pic_read_irq(PicState2 *s); +void pic_update_irq(PicState2 *s); +uint32_t pic_intack_read(PicState2 *s); +void pic_info(Monitor *mon); +void irq_info(Monitor *mon); + +/* ISA */ +#define IOAPIC_NUM_PINS 0x18 + +typedef struct isa_irq_state { + qemu_irq *i8259; + qemu_irq ioapic[IOAPIC_NUM_PINS]; +} IsaIrqState; + +void isa_irq_handler(void *opaque, int n, int level); + +/* i8254.c */ + +#define PIT_FREQ 1193182 + +typedef struct PITState PITState; + +PITState *pit_init(int base, qemu_irq irq); +void pit_set_gate(PITState *pit, int channel, int val); +int pit_get_gate(PITState *pit, int channel); +int pit_get_initial_count(PITState *pit, int channel); +int pit_get_mode(PITState *pit, int channel); +int pit_get_out(PITState *pit, int channel, int64_t current_time); + +void hpet_pit_disable(void); +void hpet_pit_enable(void); + +/* vmport.c */ +void vmport_init(void); +void vmport_register(unsigned char command, IOPortReadFunc *func, void *opaque); + +/* vmmouse.c */ +void *vmmouse_init(void *m); + +/* pckbd.c */ + +void i8042_init(qemu_irq kbd_irq, qemu_irq mouse_irq, uint32_t io_base); +void i8042_mm_init(qemu_irq kbd_irq, qemu_irq mouse_irq, + target_phys_addr_t base, ram_addr_t size, + target_phys_addr_t mask); +void i8042_isa_mouse_fake_event(void *opaque); +void i8042_setup_a20_line(ISADevice *dev, qemu_irq *a20_out); + +/* pc.c */ +extern int fd_bootchk; + +void pc_register_ferr_irq(qemu_irq irq); +void pc_cmos_set_s3_resume(void *opaque, int irq, int level); +void pc_acpi_smi_interrupt(void *opaque, int irq, int level); + +void pc_cpus_init(const char *cpu_model); +void pc_memory_init(ram_addr_t ram_size, + const char *kernel_filename, + const char *kernel_cmdline, + const char *initrd_filename, + ram_addr_t *below_4g_mem_size_p, + ram_addr_t *above_4g_mem_size_p); +qemu_irq *pc_allocate_cpu_irq(void); +void pc_vga_init(PCIBus *pci_bus); +void pc_basic_device_init(qemu_irq *isa_irq, + FDCtrl **floppy_controller, + ISADevice **rtc_state); +void pc_init_ne2k_isa(NICInfo *nd); +void pc_cmos_init(ram_addr_t ram_size, ram_addr_t above_4g_mem_size, + const char *boot_device, + BusState *ide0, BusState *ide1, + FDCtrl *floppy_controller, ISADevice *s); +void pc_pci_device_init(PCIBus *pci_bus); + +typedef void (*cpu_set_smm_t)(int smm, void *arg); +void cpu_smm_register(cpu_set_smm_t callback, void *arg); + +/* acpi.c */ +extern int acpi_enabled; +extern char *acpi_tables; +extern size_t acpi_tables_len; + +void acpi_bios_init(void); +int acpi_table_add(const char *table_desc); + +/* acpi_piix.c */ + +i2c_bus *piix4_pm_init(PCIBus *bus, int devfn, uint32_t smb_io_base, + qemu_irq sci_irq, qemu_irq cmos_s3, qemu_irq smi_irq, + int kvm_enabled); +void piix4_smbus_register_device(SMBusDevice *dev, uint8_t addr); + +/* hpet.c */ +extern int no_hpet; + +/* pcspk.c */ +void pcspk_init(PITState *); +int pcspk_audio_init(qemu_irq *pic); + +/* piix_pci.c */ +struct PCII440FXState; +typedef struct PCII440FXState PCII440FXState; + +PCIBus *i440fx_init(PCII440FXState **pi440fx_state, int *piix_devfn, qemu_irq *pic, ram_addr_t ram_size); +void i440fx_init_memory_mappings(PCII440FXState *d); + +/* piix4.c */ +extern PCIDevice *piix4_dev; +int piix4_init(PCIBus *bus, int devfn); + +/* vga.c */ +enum vga_retrace_method { + VGA_RETRACE_DUMB, + VGA_RETRACE_PRECISE +}; + +extern enum vga_retrace_method vga_retrace_method; + +int isa_vga_init(void); +int pci_vga_init(PCIBus *bus); +int isa_vga_mm_init(target_phys_addr_t vram_base, + target_phys_addr_t ctrl_base, int it_shift); + +/* cirrus_vga.c */ +void pci_cirrus_vga_init(PCIBus *bus); +void isa_cirrus_vga_init(void); + +/* ne2000.c */ + +void isa_ne2000_init(int base, int irq, NICInfo *nd); + +/* e820 types */ +#define E820_RAM 1 +#define E820_RESERVED 2 +#define E820_ACPI 3 +#define E820_NVS 4 +#define E820_UNUSABLE 5 + +int e820_add_entry(uint64_t, uint64_t, uint32_t); + +#endif diff --git a/hw/pc_piix.c b/hw/pc_piix.c new file mode 100644 index 0000000..3fa1e9d --- /dev/null +++ b/hw/pc_piix.c @@ -0,0 +1,389 @@ +/* + * 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.h" +#include "pc.h" +#include "apic.h" +#include "pci.h" +#include "usb-uhci.h" +#include "usb-ohci.h" +#include "net.h" +#include "boards.h" +#include "ide.h" +#include "kvm.h" +#include "sysemu.h" +#include "sysbus.h" +#include "arch_init.h" +#include "blockdev.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 void ioapic_init(IsaIrqState *isa_irq_state) +{ + DeviceState *dev; + SysBusDevice *d; + unsigned int i; + + dev = qdev_create(NULL, "ioapic"); + qdev_init_nofail(dev); + d = sysbus_from_qdev(dev); + sysbus_mmio_map(d, 0, 0xfec00000); + + for (i = 0; i < IOAPIC_NUM_PINS; i++) { + isa_irq_state->ioapic[i] = qdev_get_gpio_in(dev, i); + } +} + +/* PC hardware initialisation */ +static void pc_init1(ram_addr_t ram_size, + const char *boot_device, + const char *kernel_filename, + const char *kernel_cmdline, + const char *initrd_filename, + const char *cpu_model, + int pci_enabled) +{ + int i; + ram_addr_t below_4g_mem_size, above_4g_mem_size; + PCIBus *pci_bus; + PCII440FXState *i440fx_state; + int piix3_devfn = -1; + qemu_irq *cpu_irq; + qemu_irq *isa_irq; + qemu_irq *i8259; + qemu_irq *cmos_s3; + qemu_irq *smi_irq; + IsaIrqState *isa_irq_state; + DriveInfo *hd[MAX_IDE_BUS * MAX_IDE_DEVS]; + FDCtrl *floppy_controller; + BusState *idebus[MAX_IDE_BUS]; + ISADevice *rtc_state; + + pc_cpus_init(cpu_model); + + vmport_init(); + + /* allocate ram and load rom/bios */ + pc_memory_init(ram_size, kernel_filename, kernel_cmdline, initrd_filename, + &below_4g_mem_size, &above_4g_mem_size); + + cpu_irq = pc_allocate_cpu_irq(); + i8259 = i8259_init(cpu_irq[0]); + isa_irq_state = qemu_mallocz(sizeof(*isa_irq_state)); + isa_irq_state->i8259 = i8259; + if (pci_enabled) { + ioapic_init(isa_irq_state); + } + isa_irq = qemu_allocate_irqs(isa_irq_handler, isa_irq_state, 24); + + if (pci_enabled) { + pci_bus = i440fx_init(&i440fx_state, &piix3_devfn, isa_irq, ram_size); + } else { + pci_bus = NULL; + i440fx_state = NULL; + isa_bus_new(NULL); + } + isa_bus_irqs(isa_irq); + + pc_register_ferr_irq(isa_get_irq(13)); + + pc_vga_init(pci_enabled? pci_bus: NULL); + + /* init basic PC hardware */ + pc_basic_device_init(isa_irq, &floppy_controller, &rtc_state); + + for(i = 0; i < nb_nics; i++) { + NICInfo *nd = &nd_table[i]; + + if (!pci_enabled || (nd->model && strcmp(nd->model, "ne2k_isa") == 0)) + pc_init_ne2k_isa(nd); + else + pci_nic_init_nofail(nd, "e1000", NULL); + } + + if (drive_get_max_bus(IF_IDE) >= MAX_IDE_BUS) { + fprintf(stderr, "qemu: too many IDE bus\n"); + exit(1); + } + + for(i = 0; i < MAX_IDE_BUS * MAX_IDE_DEVS; i++) { + hd[i] = drive_get(IF_IDE, i / MAX_IDE_DEVS, i % MAX_IDE_DEVS); + } + + if (pci_enabled) { + PCIDevice *dev; + 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; + dev = isa_ide_init(ide_iobase[i], ide_iobase2[i], ide_irq[i], + hd[MAX_IDE_DEVS * i], hd[MAX_IDE_DEVS * i + 1]); + idebus[i] = qdev_get_child_bus(&dev->qdev, "ide.0"); + } + } + + audio_init(isa_irq, pci_enabled ? pci_bus : NULL); + + pc_cmos_init(below_4g_mem_size, above_4g_mem_size, boot_device, + idebus[0], idebus[1], floppy_controller, rtc_state); + + if (pci_enabled && usb_enabled) { + usb_uhci_piix3_init(pci_bus, piix3_devfn + 2); + } + + if (pci_enabled && acpi_enabled) { + uint8_t *eeprom_buf = qemu_mallocz(8 * 256); /* XXX: make this persistent */ + i2c_bus *smbus; + + cmos_s3 = qemu_allocate_irqs(pc_cmos_set_s3_resume, rtc_state, 1); + smi_irq = qemu_allocate_irqs(pc_acpi_smi_interrupt, first_cpu, 1); + /* TODO: Populate SPD eeprom data. */ + smbus = piix4_pm_init(pci_bus, piix3_devfn + 3, 0xb100, + isa_get_irq(9), *cmos_s3, *smi_irq, + kvm_enabled()); + for (i = 0; i < 8; 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); + } + } + + if (i440fx_state) { + i440fx_init_memory_mappings(i440fx_state); + } + + if (pci_enabled) { + pc_pci_device_init(pci_bus); + } +} + +static void pc_init_pci(ram_addr_t ram_size, + const char *boot_device, + const char *kernel_filename, + const char *kernel_cmdline, + const char *initrd_filename, + const char *cpu_model) +{ + pc_init1(ram_size, boot_device, + kernel_filename, kernel_cmdline, + initrd_filename, cpu_model, 1); +} + +static void pc_init_isa(ram_addr_t ram_size, + const char *boot_device, + const char *kernel_filename, + const char *kernel_cmdline, + const char *initrd_filename, + const char *cpu_model) +{ + if (cpu_model == NULL) + cpu_model = "486"; + pc_init1(ram_size, boot_device, + kernel_filename, kernel_cmdline, + initrd_filename, cpu_model, 0); +} + +static QEMUMachine pc_machine = { + .name = "pc-0.14", + .alias = "pc", + .desc = "Standard PC", + .init = pc_init_pci, + .max_cpus = 255, + .is_default = 1, +}; + +static QEMUMachine pc_machine_v0_13 = { + .name = "pc-0.13", + .desc = "Standard PC", + .init = pc_init_pci, + .max_cpus = 255, + .compat_props = (GlobalProperty[]) { + { + .driver = "virtio-9p-pci", + .property = "vectors", + .value = stringify(0), + },{ + .driver = "VGA", + .property = "rombar", + .value = stringify(0), + },{ + .driver = "vmware-svga", + .property = "rombar", + .value = stringify(0), + },{ + .driver = "PCI", + .property = "command_serr_enable", + .value = "off", + }, + { /* end of list */ } + }, +}; + +static QEMUMachine pc_machine_v0_12 = { + .name = "pc-0.12", + .desc = "Standard PC", + .init = pc_init_pci, + .max_cpus = 255, + .compat_props = (GlobalProperty[]) { + { + .driver = "virtio-serial-pci", + .property = "max_ports", + .value = stringify(1), + },{ + .driver = "virtio-serial-pci", + .property = "vectors", + .value = stringify(0), + },{ + .driver = "VGA", + .property = "rombar", + .value = stringify(0), + },{ + .driver = "vmware-svga", + .property = "rombar", + .value = stringify(0), + },{ + .driver = "PCI", + .property = "command_serr_enable", + .value = "off", + }, + { /* end of list */ } + } +}; + +static QEMUMachine pc_machine_v0_11 = { + .name = "pc-0.11", + .desc = "Standard PC, qemu 0.11", + .init = pc_init_pci, + .max_cpus = 255, + .compat_props = (GlobalProperty[]) { + { + .driver = "virtio-blk-pci", + .property = "vectors", + .value = stringify(0), + },{ + .driver = "virtio-serial-pci", + .property = "max_ports", + .value = stringify(1), + },{ + .driver = "virtio-serial-pci", + .property = "vectors", + .value = stringify(0), + },{ + .driver = "ide-drive", + .property = "ver", + .value = "0.11", + },{ + .driver = "scsi-disk", + .property = "ver", + .value = "0.11", + },{ + .driver = "PCI", + .property = "rombar", + .value = stringify(0), + },{ + .driver = "PCI", + .property = "command_serr_enable", + .value = "off", + }, + { /* end of list */ } + } +}; + +static QEMUMachine pc_machine_v0_10 = { + .name = "pc-0.10", + .desc = "Standard PC, qemu 0.10", + .init = pc_init_pci, + .max_cpus = 255, + .compat_props = (GlobalProperty[]) { + { + .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-serial-pci", + .property = "max_ports", + .value = stringify(1), + },{ + .driver = "virtio-serial-pci", + .property = "vectors", + .value = stringify(0), + },{ + .driver = "virtio-net-pci", + .property = "vectors", + .value = stringify(0), + },{ + .driver = "virtio-blk-pci", + .property = "vectors", + .value = stringify(0), + },{ + .driver = "ide-drive", + .property = "ver", + .value = "0.10", + },{ + .driver = "scsi-disk", + .property = "ver", + .value = "0.10", + },{ + .driver = "PCI", + .property = "rombar", + .value = stringify(0), + },{ + .driver = "PCI", + .property = "command_serr_enable", + .value = "off", + }, + { /* end of list */ } + }, +}; + +static QEMUMachine isapc_machine = { + .name = "isapc", + .desc = "ISA-only PC", + .init = pc_init_isa, + .max_cpus = 1, +}; + +static void pc_machine_init(void) +{ + qemu_register_machine(&pc_machine); + qemu_register_machine(&pc_machine_v0_13); + qemu_register_machine(&pc_machine_v0_12); + qemu_register_machine(&pc_machine_v0_11); + qemu_register_machine(&pc_machine_v0_10); + qemu_register_machine(&isapc_machine); +} + +machine_init(pc_machine_init); diff --git a/hw/pci-hotplug.c b/hw/pci-hotplug.c new file mode 100644 index 0000000..478fe9b --- /dev/null +++ b/hw/pci-hotplug.c @@ -0,0 +1,291 @@ +/* + * QEMU PCI hotplug 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.h" +#include "boards.h" +#include "pci.h" +#include "net.h" +#include "pc.h" +#include "monitor.h" +#include "scsi.h" +#include "virtio-blk.h" +#include "qemu-config.h" +#include "blockdev.h" + +#if defined(TARGET_I386) +static PCIDevice *qemu_pci_hot_add_nic(Monitor *mon, + const char *devaddr, + const char *opts_str) +{ + QemuOpts *opts; + PCIBus *bus; + int ret, devfn; + + bus = pci_get_bus_devfn(&devfn, devaddr); + if (!bus) { + monitor_printf(mon, "Invalid PCI device address %s\n", devaddr); + return NULL; + } + if (!((BusState*)bus)->allow_hotplug) { + monitor_printf(mon, "PCI bus doesn't support hotplug\n"); + return NULL; + } + + opts = qemu_opts_parse(qemu_find_opts("net"), opts_str ? opts_str : "", 0); + if (!opts) { + return NULL; + } + + qemu_opt_set(opts, "type", "nic"); + + ret = net_client_init(mon, opts, 0); + if (ret < 0) + return NULL; + if (nd_table[ret].devaddr) { + monitor_printf(mon, "Parameter addr not supported\n"); + return NULL; + } + return pci_nic_init(&nd_table[ret], "rtl8139", devaddr); +} + +static int scsi_hot_add(Monitor *mon, DeviceState *adapter, + DriveInfo *dinfo, int printinfo) +{ + SCSIBus *scsibus; + SCSIDevice *scsidev; + + scsibus = DO_UPCAST(SCSIBus, qbus, QLIST_FIRST(&adapter->child_bus)); + if (!scsibus || strcmp(scsibus->qbus.info->name, "SCSI") != 0) { + error_report("Device is not a SCSI adapter"); + return -1; + } + + /* + * drive_init() tries to find a default for dinfo->unit. Doesn't + * work at all for hotplug though as we assign the device to a + * specific bus instead of the first bus with spare scsi ids. + * + * Ditch the calculated value and reload from option string (if + * specified). + */ + dinfo->unit = qemu_opt_get_number(dinfo->opts, "unit", -1); + dinfo->bus = scsibus->busnr; + scsidev = scsi_bus_legacy_add_drive(scsibus, dinfo->bdrv, dinfo->unit, false); + if (!scsidev) { + return -1; + } + dinfo->unit = scsidev->id; + + if (printinfo) + monitor_printf(mon, "OK bus %d, unit %d\n", + scsibus->busnr, scsidev->id); + return 0; +} + +void drive_hot_add(Monitor *mon, const QDict *qdict) +{ + int dom, pci_bus; + unsigned slot; + int type; + PCIDevice *dev; + DriveInfo *dinfo = NULL; + const char *pci_addr = qdict_get_str(qdict, "pci_addr"); + const char *opts = qdict_get_str(qdict, "opts"); + + dinfo = add_init_drive(opts); + if (!dinfo) + goto err; + if (dinfo->devaddr) { + monitor_printf(mon, "Parameter addr not supported\n"); + goto err; + } + type = dinfo->type; + + switch (type) { + case IF_SCSI: + if (pci_read_devaddr(mon, pci_addr, &dom, &pci_bus, &slot)) { + goto err; + } + dev = pci_find_device(pci_find_root_bus(dom), pci_bus, slot, 0); + if (!dev) { + monitor_printf(mon, "no pci device with address %s\n", pci_addr); + goto err; + } + if (scsi_hot_add(mon, &dev->qdev, dinfo, 1) != 0) { + goto err; + } + break; + case IF_NONE: + monitor_printf(mon, "OK\n"); + break; + default: + monitor_printf(mon, "Can't hot-add drive to type %d\n", type); + goto err; + } + return; + +err: + if (dinfo) + drive_put_ref(dinfo); + return; +} + +static PCIDevice *qemu_pci_hot_add_storage(Monitor *mon, + const char *devaddr, + const char *opts) +{ + PCIDevice *dev; + DriveInfo *dinfo = NULL; + int type = -1; + char buf[128]; + PCIBus *bus; + int devfn; + + if (get_param_value(buf, sizeof(buf), "if", opts)) { + if (!strcmp(buf, "scsi")) + type = IF_SCSI; + else if (!strcmp(buf, "virtio")) { + type = IF_VIRTIO; + } else { + monitor_printf(mon, "type %s not a hotpluggable PCI device.\n", buf); + return NULL; + } + } else { + monitor_printf(mon, "no if= specified\n"); + return NULL; + } + + if (get_param_value(buf, sizeof(buf), "file", opts)) { + dinfo = add_init_drive(opts); + if (!dinfo) + return NULL; + if (dinfo->devaddr) { + monitor_printf(mon, "Parameter addr not supported\n"); + return NULL; + } + } else { + dinfo = NULL; + } + + bus = pci_get_bus_devfn(&devfn, devaddr); + if (!bus) { + monitor_printf(mon, "Invalid PCI device address %s\n", devaddr); + return NULL; + } + if (!((BusState*)bus)->allow_hotplug) { + monitor_printf(mon, "PCI bus doesn't support hotplug\n"); + return NULL; + } + + switch (type) { + case IF_SCSI: + dev = pci_create(bus, devfn, "lsi53c895a"); + if (qdev_init(&dev->qdev) < 0) + dev = NULL; + if (dev && dinfo) { + if (scsi_hot_add(mon, &dev->qdev, dinfo, 0) != 0) { + qdev_unplug(&dev->qdev); + dev = NULL; + } + } + break; + case IF_VIRTIO: + if (!dinfo) { + monitor_printf(mon, "virtio requires a backing file/device.\n"); + return NULL; + } + dev = pci_create(bus, devfn, "virtio-blk-pci"); + if (qdev_prop_set_drive(&dev->qdev, "drive", dinfo->bdrv) < 0) { + qdev_free(&dev->qdev); + dev = NULL; + break; + } + if (qdev_init(&dev->qdev) < 0) + dev = NULL; + break; + default: + dev = NULL; + } + return dev; +} + +void pci_device_hot_add(Monitor *mon, const QDict *qdict) +{ + PCIDevice *dev = NULL; + const char *pci_addr = qdict_get_str(qdict, "pci_addr"); + const char *type = qdict_get_str(qdict, "type"); + const char *opts = qdict_get_try_str(qdict, "opts"); + + /* strip legacy tag */ + if (!strncmp(pci_addr, "pci_addr=", 9)) { + pci_addr += 9; + } + + if (!opts) { + opts = ""; + } + + if (!strcmp(pci_addr, "auto")) + pci_addr = NULL; + + if (strcmp(type, "nic") == 0) { + dev = qemu_pci_hot_add_nic(mon, pci_addr, opts); + } else if (strcmp(type, "storage") == 0) { + dev = qemu_pci_hot_add_storage(mon, pci_addr, opts); + } else { + monitor_printf(mon, "invalid type: %s\n", type); + } + + if (dev) { + monitor_printf(mon, "OK domain %d, bus %d, slot %d, function %d\n", + pci_find_domain(dev->bus), + pci_bus_num(dev->bus), PCI_SLOT(dev->devfn), + PCI_FUNC(dev->devfn)); + } else + monitor_printf(mon, "failed to add %s\n", opts); +} +#endif + +static int pci_device_hot_remove(Monitor *mon, const char *pci_addr) +{ + PCIDevice *d; + int dom, bus; + unsigned slot; + + if (pci_read_devaddr(mon, pci_addr, &dom, &bus, &slot)) { + return -1; + } + + d = pci_find_device(pci_find_root_bus(dom), bus, slot, 0); + if (!d) { + monitor_printf(mon, "slot %d empty\n", slot); + return -1; + } + return qdev_unplug(&d->qdev); +} + +void do_pci_device_hot_remove(Monitor *mon, const QDict *qdict) +{ + pci_device_hot_remove(mon, qdict_get_str(qdict, "pci_addr")); +} diff --git a/hw/pci-stub.c b/hw/pci-stub.c new file mode 100644 index 0000000..c5a0aa8 --- /dev/null +++ b/hw/pci-stub.c @@ -0,0 +1,50 @@ +/* + * PCI stubs for plathome that doesn't support pci bus. + * + * Copyright (c) 2010 Isaku Yamahata + * 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 . + */ + +#include "sysemu.h" +#include "monitor.h" +#include "pci.h" + +static void pci_error_message(Monitor *mon) +{ + monitor_printf(mon, "PCI devices not supported\n"); +} + +void do_pci_info(Monitor *mon, QObject **ret_data) +{ + pci_error_message(mon); +} + +void do_pci_info_print(Monitor *mon, const QObject *data) +{ + pci_error_message(mon); +} + +int do_pcie_aer_inejct_error(Monitor *mon, + const QDict *qdict, QObject **ret_data) +{ + pci_error_message(mon); + return -ENOSYS; +} + +void pcie_aer_inject_error_print(Monitor *mon, const QObject *data) +{ + pci_error_message(mon); +} diff --git a/hw/pci.c b/hw/pci.c new file mode 100644 index 0000000..d5bbba9 --- /dev/null +++ b/hw/pci.c @@ -0,0 +1,2117 @@ +/* + * 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.h" +#include "pci.h" +#include "pci_bridge.h" +#include "pci_internals.h" +#include "monitor.h" +#include "net.h" +#include "sysemu.h" +#include "loader.h" +#include "qemu-objects.h" +#include "range.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 int pcibus_reset(BusState *qbus); + +struct BusInfo pci_bus_info = { + .name = "PCI", + .size = sizeof(PCIBus), + .print_dev = pcibus_dev_print, + .get_dev_path = pcibus_get_dev_path, + .get_fw_dev_path = pcibus_get_fw_dev_path, + .reset = pcibus_reset, + .props = (Property[]) { + 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 void pci_update_mappings(PCIDevice *d); +static void pci_set_irq(void *opaque, int irq_num, int level); +static int pci_add_option_rom(PCIDevice *pdev, bool is_default_rom); +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; + +struct PCIHostBus { + int domain; + struct PCIBus *bus; + QLIST_ENTRY(PCIHostBus) next; +}; +static QLIST_HEAD(, PCIHostBus) host_buses; + +static const VMStateDescription vmstate_pcibus = { + .name = "PCIBUS", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 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 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); +} + +/* 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) { + qemu_set_irq(dev->irq[i], 0); + } +} + +/* + * This function is called on #RST and FLR. + * FLR if PCI_EXP_DEVCTL_BCR_FLR is set + */ +void pci_device_reset(PCIDevice *dev) +{ + int r; + /* TODO: call the below unconditionally once all pci devices + * are qdevified */ + if (dev->qdev.info) { + qdev_reset_all(&dev->qdev); + } + + dev->irq_state = 0; + pci_update_irq_status(dev); + pci_device_deassert_intx(dev); + /* Clear all writeable 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); +} + +/* + * Trigger pci bus reset under a given bus. + * To be called on RST# assert. + */ +void pci_bus_reset(PCIBus *bus) +{ + int i; + + for (i = 0; i < bus->nirq; i++) { + bus->irq_count[i] = 0; + } + for (i = 0; i < ARRAY_SIZE(bus->devices); ++i) { + if (bus->devices[i]) { + pci_device_reset(bus->devices[i]); + } + } +} + +static int pcibus_reset(BusState *qbus) +{ + pci_bus_reset(DO_UPCAST(PCIBus, qbus, qbus)); + + /* topology traverse is done by pci_bus_reset(). + Tell qbus/qdev walker not to traverse the tree */ + return 1; +} + +static void pci_host_bus_register(int domain, PCIBus *bus) +{ + struct PCIHostBus *host; + host = qemu_mallocz(sizeof(*host)); + host->domain = domain; + host->bus = bus; + QLIST_INSERT_HEAD(&host_buses, host, next); +} + +PCIBus *pci_find_root_bus(int domain) +{ + struct PCIHostBus *host; + + QLIST_FOREACH(host, &host_buses, next) { + if (host->domain == domain) { + return host->bus; + } + } + + return NULL; +} + +int pci_find_domain(const PCIBus *bus) +{ + PCIDevice *d; + struct PCIHostBus *host; + + /* obtain root bus */ + while ((d = bus->parent_dev) != NULL) { + bus = d->bus; + } + + QLIST_FOREACH(host, &host_buses, next) { + if (host->bus == bus) { + return host->domain; + } + } + + abort(); /* should not be reached */ + return -1; +} + +void pci_bus_new_inplace(PCIBus *bus, DeviceState *parent, + const char *name, int devfn_min) +{ + qbus_create_inplace(&bus->qbus, &pci_bus_info, parent, name); + assert(PCI_FUNC(devfn_min) == 0); + bus->devfn_min = devfn_min; + + /* host bridge */ + QLIST_INIT(&bus->child); + pci_host_bus_register(0, bus); /* for now only pci domain 0 is supported */ + + vmstate_register(NULL, -1, &vmstate_pcibus, bus); +} + +PCIBus *pci_bus_new(DeviceState *parent, const char *name, int devfn_min) +{ + PCIBus *bus; + + bus = qemu_mallocz(sizeof(*bus)); + bus->qbus.qdev_allocated = 1; + pci_bus_new_inplace(bus, parent, name, 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 = qemu_mallocz(nirq * sizeof(bus->irq_count[0])); +} + +void pci_bus_hotplug(PCIBus *bus, pci_hotplug_fn hotplug, DeviceState *qdev) +{ + bus->qbus.allow_hotplug = 1; + bus->hotplug = hotplug; + bus->hotplug_qdev = qdev; +} + +void pci_bus_set_mem_base(PCIBus *bus, target_phys_addr_t base) +{ + bus->mem_base = base; +} + +PCIBus *pci_register_bus(DeviceState *parent, const char *name, + pci_set_irq_fn set_irq, pci_map_irq_fn map_irq, + void *irq_opaque, int devfn_min, int nirq) +{ + PCIBus *bus; + + bus = pci_bus_new(parent, name, devfn_min); + pci_bus_irqs(bus, set_irq, map_irq, irq_opaque, nirq); + return bus; +} + +int pci_bus_num(PCIBus *s) +{ + if (!s->parent_dev) + return 0; /* pci host bridge */ + return s->parent_dev->config[PCI_SECONDARY_BUS]; +} + +static int get_pci_config_device(QEMUFile *f, void *pv, size_t size) +{ + PCIDevice *s = container_of(pv, PCIDevice, config); + uint8_t *config; + int i; + + assert(size == pci_config_size(s)); + config = qemu_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]) { + qemu_free(config); + return -EINVAL; + } + } + memcpy(s->config, config, size); + + pci_update_mappings(s); + + qemu_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, + .minimum_version_id_old = 1, + .fields = (VMStateField []) { + VMSTATE_INT32_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 = "PCIDevice", + .version_id = 2, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField []) { + VMSTATE_INT32_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); + /* 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 [[:]:], return -1 on error if funcp == NULL + * [[:]:]., return -1 on error + */ +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; + + /* Note: QEMU doesn't implement domains other than 0 */ + if (!pci_find_bus(pci_find_root_bus(dom), bus)) + return -1; + + *domp = dom; + *busp = bus; + *slotp = slot; + if (funcp != NULL) + *funcp = func; + return 0; +} + +int pci_read_devaddr(Monitor *mon, const char *addr, int *domp, int *busp, + unsigned *slotp) +{ + /* strip legacy tag */ + if (!strncmp(addr, "pci_addr=", 9)) { + addr += 9; + } + if (pci_parse_devaddr(addr, domp, busp, slotp, NULL)) { + monitor_printf(mon, "Invalid pci address\n"); + return -1; + } + return 0; +} + +PCIBus *pci_get_bus_devfn(int *devfnp, const char *devaddr) +{ + int dom, bus; + unsigned slot; + + if (!devaddr) { + *devfnp = -1; + return pci_find_bus(pci_find_root_bus(0), 0); + } + + if (pci_parse_devaddr(devaddr, &dom, &bus, &slot, NULL) < 0) { + return NULL; + } + + *devfnp = slot << 3; + return pci_find_bus(pci_find_root_bus(dom), 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_wmask_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); + +/* TODO: add this define to pci_regs.h in linux and then in qemu. */ +#define PCI_BRIDGE_CTL_VGA_16BIT 0x10 /* VGA 16-bit decode */ +#define PCI_BRIDGE_CTL_DISCARD 0x100 /* Primary discard timer */ +#define PCI_BRIDGE_CTL_SEC_DISCARD 0x200 /* Secondary discard timer */ +#define PCI_BRIDGE_CTL_DISCARD_STATUS 0x400 /* Discard timer status */ +#define PCI_BRIDGE_CTL_DISCARD_SERR 0x800 /* Discard timer SERR# enable */ + 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); +} + +static int pci_init_multifunction(PCIBus *bus, PCIDevice *dev) +{ + 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_report("PCI: single function device can't be populated " + "in function %x.%x", slot, PCI_FUNC(dev->devfn)); + return -1; + } + return 0; + } + + if (dev->cap_present & QEMU_PCI_CAP_MULTIFUNCTION) { + return 0; + } + /* 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_report("PCI: %x.0 indicates single function, " + "but %x.%x is already populated.", + slot, slot, func); + return -1; + } + } + return 0; +} + +static void pci_config_alloc(PCIDevice *pci_dev) +{ + int config_size = pci_config_size(pci_dev); + + pci_dev->config = qemu_mallocz(config_size); + pci_dev->cmask = qemu_mallocz(config_size); + pci_dev->wmask = qemu_mallocz(config_size); + pci_dev->w1cmask = qemu_mallocz(config_size); + pci_dev->used = qemu_mallocz(config_size); +} + +static void pci_config_free(PCIDevice *pci_dev) +{ + qemu_free(pci_dev->config); + qemu_free(pci_dev->cmask); + qemu_free(pci_dev->wmask); + qemu_free(pci_dev->w1cmask); + qemu_free(pci_dev->used); +} + +/* -1 for devfn means auto assign */ +static PCIDevice *do_pci_register_device(PCIDevice *pci_dev, PCIBus *bus, + const char *name, int devfn, + PCIConfigReadFunc *config_read, + PCIConfigWriteFunc *config_write, + bool is_bridge) +{ + if (devfn < 0) { + for(devfn = bus->devfn_min ; devfn < ARRAY_SIZE(bus->devices); + devfn += PCI_FUNC_MAX) { + if (!bus->devices[devfn]) + goto found; + } + error_report("PCI: no slot/function available for %s, all in use", name); + return NULL; + found: ; + } else if (bus->devices[devfn]) { + error_report("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; + pstrcpy(pci_dev->name, sizeof(pci_dev->name), name); + pci_dev->irq_state = 0; + pci_config_alloc(pci_dev); + + if (!is_bridge) { + pci_set_default_subsystem_id(pci_dev); + } + pci_init_cmask(pci_dev); + pci_init_wmask(pci_dev); + pci_init_w1cmask(pci_dev); + if (is_bridge) { + pci_init_wmask_bridge(pci_dev); + } + if (pci_init_multifunction(bus, pci_dev)) { + pci_config_free(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->irq = qemu_allocate_irqs(pci_set_irq, pci_dev, PCI_NUM_PINS); + pci_dev->version_id = 2; /* Current pci device vmstate version */ + return pci_dev; +} + +static void do_pci_unregister_device(PCIDevice *pci_dev) +{ + qemu_free_irqs(pci_dev->irq); + pci_dev->bus->devices[pci_dev->devfn] = NULL; + pci_config_free(pci_dev); +} + +PCIDevice *pci_register_device(PCIBus *bus, const char *name, + int instance_size, int devfn, + PCIConfigReadFunc *config_read, + PCIConfigWriteFunc *config_write) +{ + PCIDevice *pci_dev; + + pci_dev = qemu_mallocz(instance_size); + pci_dev = do_pci_register_device(pci_dev, bus, name, devfn, + config_read, config_write, + PCI_HEADER_TYPE_NORMAL); + if (pci_dev == NULL) { + hw_error("PCI: can't register device\n"); + } + return pci_dev; +} + +static target_phys_addr_t pci_to_cpu_addr(PCIBus *bus, + target_phys_addr_t addr) +{ + return addr + bus->mem_base; +} + +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; + if (r->type == PCI_BASE_ADDRESS_SPACE_IO) { + isa_unassign_ioport(r->addr, r->filtered_size); + } else { + cpu_register_physical_memory(pci_to_cpu_addr(pci_dev->bus, + r->addr), + r->filtered_size, + IO_MEM_UNASSIGNED); + } + } +} + +static int pci_unregister_device(DeviceState *dev) +{ + PCIDevice *pci_dev = DO_UPCAST(PCIDevice, qdev, dev); + PCIDeviceInfo *info = DO_UPCAST(PCIDeviceInfo, qdev, dev->info); + int ret = 0; + + if (info->exit) + ret = info->exit(pci_dev); + if (ret) + return ret; + + pci_unregister_io_regions(pci_dev); + pci_del_option_rom(pci_dev); + qemu_free(pci_dev->romfile); + do_pci_unregister_device(pci_dev); + return 0; +} + +void pci_register_bar(PCIDevice *pci_dev, int region_num, + pcibus_t size, uint8_t type, + PCIMapIORegionFunc *map_func) +{ + PCIIORegion *r; + uint32_t addr; + uint64_t wmask; + + 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->filtered_size = size; + r->type = type; + r->map_func = map_func; + + wmask = ~(size - 1); + addr = pci_bar(pci_dev, region_num); + if (region_num == PCI_ROM_SLOT) { + /* ROM enable bit is writeable */ + 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); + } +} + +static void pci_bridge_filter(PCIDevice *d, pcibus_t *addr, pcibus_t *size, + uint8_t type) +{ + pcibus_t base = *addr; + pcibus_t limit = *addr + *size - 1; + PCIDevice *br; + + for (br = d->bus->parent_dev; br; br = br->bus->parent_dev) { + uint16_t cmd = pci_get_word(d->config + PCI_COMMAND); + + if (type & PCI_BASE_ADDRESS_SPACE_IO) { + if (!(cmd & PCI_COMMAND_IO)) { + goto no_map; + } + } else { + if (!(cmd & PCI_COMMAND_MEMORY)) { + goto no_map; + } + } + + base = MAX(base, pci_bridge_get_base(br, type)); + limit = MIN(limit, pci_bridge_get_limit(br, type)); + } + + if (base > limit) { + goto no_map; + } + *addr = base; + *size = limit - base + 1; + return; +no_map: + *addr = PCI_BAR_UNMAPPED; + *size = 0; +} + +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; + /* NOTE: we have only 64K ioports on PC */ + if (last_addr <= new_addr || new_addr == 0 || last_addr > UINT16_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 >= TARGET_PHYS_ADDR_MAX) { + return PCI_BAR_UNMAPPED; + } + + return new_addr; +} + +static void pci_update_mappings(PCIDevice *d) +{ + PCIIORegion *r; + int i; + pcibus_t new_addr, filtered_size; + + 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); + + /* bridge filtering */ + filtered_size = r->size; + if (new_addr != PCI_BAR_UNMAPPED) { + pci_bridge_filter(d, &new_addr, &filtered_size, r->type); + } + + /* This bar isn't changed */ + if (new_addr == r->addr && filtered_size == r->filtered_size) + continue; + + /* now do the real mapping */ + if (r->addr != PCI_BAR_UNMAPPED) { + if (r->type & PCI_BASE_ADDRESS_SPACE_IO) { + int class; + /* NOTE: specific hack for IDE in PC case: + only one byte must be mapped. */ + class = pci_get_word(d->config + PCI_CLASS_DEVICE); + if (class == 0x0101 && r->size == 4) { + isa_unassign_ioport(r->addr + 2, 1); + } else { + isa_unassign_ioport(r->addr, r->filtered_size); + } + } else { + cpu_register_physical_memory(pci_to_cpu_addr(d->bus, r->addr), + r->filtered_size, + IO_MEM_UNASSIGNED); + qemu_unregister_coalesced_mmio(r->addr, r->filtered_size); + } + } + r->addr = new_addr; + r->filtered_size = filtered_size; + if (r->addr != PCI_BAR_UNMAPPED) { + /* + * TODO: currently almost all the map funcions assumes + * filtered_size == size and addr & ~(size - 1) == addr. + * However with bridge filtering, they aren't always true. + * Teach them such cases, such that filtered_size < size and + * addr & (size - 1) != 0. + */ + if (r->type & PCI_BASE_ADDRESS_SPACE_IO) { + r->map_func(d, i, r->addr, r->filtered_size, r->type); + } else { + r->map_func(d, i, pci_to_cpu_addr(d->bus, r->addr), + r->filtered_size, r->type); + } + } + } +} + +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; + assert(len == 1 || len == 2 || len == 4); + len = MIN(len, pci_config_size(d) - address); + memcpy(&val, d->config + address, len); + return le32_to_cpu(val); +} + +void pci_default_write_config(PCIDevice *d, uint32_t addr, uint32_t val, int l) +{ + int i, was_irq_disabled = pci_irq_disabled(d); + uint32_t config_size = pci_config_size(d); + + for (i = 0; i < l && addr + i < config_size; 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); +} + +/***********************************************************/ +/* generic PCI irq support */ + +/* 0 <= irq_num <= 3. level must be 0 or 1 */ +static void pci_set_irq(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); +} + +/***********************************************************/ +/* 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"}, + { 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"}, + { 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"}, + { 0, NULL} +}; + +static void pci_for_each_device_under_bus(PCIBus *bus, + void (*fn)(PCIBus *b, PCIDevice *d)) +{ + PCIDevice *d; + int devfn; + + for(devfn = 0; devfn < ARRAY_SIZE(bus->devices); devfn++) { + d = bus->devices[devfn]; + if (d) { + fn(bus, d); + } + } +} + +void pci_for_each_device(PCIBus *bus, int bus_num, + void (*fn)(PCIBus *b, PCIDevice *d)) +{ + bus = pci_find_bus(bus, bus_num); + + if (bus) { + pci_for_each_device_under_bus(bus, fn); + } +} + +static void pci_device_print(Monitor *mon, QDict *device) +{ + QDict *qdict; + QListEntry *entry; + uint64_t addr, size; + + monitor_printf(mon, " Bus %2" PRId64 ", ", qdict_get_int(device, "bus")); + monitor_printf(mon, "device %3" PRId64 ", function %" PRId64 ":\n", + qdict_get_int(device, "slot"), + qdict_get_int(device, "function")); + monitor_printf(mon, " "); + + qdict = qdict_get_qdict(device, "class_info"); + if (qdict_haskey(qdict, "desc")) { + monitor_printf(mon, "%s", qdict_get_str(qdict, "desc")); + } else { + monitor_printf(mon, "Class %04" PRId64, qdict_get_int(qdict, "class")); + } + + qdict = qdict_get_qdict(device, "id"); + monitor_printf(mon, ": PCI device %04" PRIx64 ":%04" PRIx64 "\n", + qdict_get_int(qdict, "device"), + qdict_get_int(qdict, "vendor")); + + if (qdict_haskey(device, "irq")) { + monitor_printf(mon, " IRQ %" PRId64 ".\n", + qdict_get_int(device, "irq")); + } + + if (qdict_haskey(device, "pci_bridge")) { + QDict *info; + + qdict = qdict_get_qdict(device, "pci_bridge"); + + info = qdict_get_qdict(qdict, "bus"); + monitor_printf(mon, " BUS %" PRId64 ".\n", + qdict_get_int(info, "number")); + monitor_printf(mon, " secondary bus %" PRId64 ".\n", + qdict_get_int(info, "secondary")); + monitor_printf(mon, " subordinate bus %" PRId64 ".\n", + qdict_get_int(info, "subordinate")); + + info = qdict_get_qdict(qdict, "io_range"); + monitor_printf(mon, " IO range [0x%04"PRIx64", 0x%04"PRIx64"]\n", + qdict_get_int(info, "base"), + qdict_get_int(info, "limit")); + + info = qdict_get_qdict(qdict, "memory_range"); + monitor_printf(mon, + " memory range [0x%08"PRIx64", 0x%08"PRIx64"]\n", + qdict_get_int(info, "base"), + qdict_get_int(info, "limit")); + + info = qdict_get_qdict(qdict, "prefetchable_range"); + monitor_printf(mon, " prefetchable memory range " + "[0x%08"PRIx64", 0x%08"PRIx64"]\n", + qdict_get_int(info, "base"), + qdict_get_int(info, "limit")); + } + + QLIST_FOREACH_ENTRY(qdict_get_qlist(device, "regions"), entry) { + qdict = qobject_to_qdict(qlist_entry_obj(entry)); + monitor_printf(mon, " BAR%d: ", (int) qdict_get_int(qdict, "bar")); + + addr = qdict_get_int(qdict, "address"); + size = qdict_get_int(qdict, "size"); + + if (!strcmp(qdict_get_str(qdict, "type"), "io")) { + monitor_printf(mon, "I/O at 0x%04"FMT_PCIBUS + " [0x%04"FMT_PCIBUS"].\n", + addr, addr + size - 1); + } else { + monitor_printf(mon, "%d bit%s memory at 0x%08"FMT_PCIBUS + " [0x%08"FMT_PCIBUS"].\n", + qdict_get_bool(qdict, "mem_type_64") ? 64 : 32, + qdict_get_bool(qdict, "prefetch") ? + " prefetchable" : "", addr, addr + size - 1); + } + } + + monitor_printf(mon, " id \"%s\"\n", qdict_get_str(device, "qdev_id")); + + if (qdict_haskey(device, "pci_bridge")) { + qdict = qdict_get_qdict(device, "pci_bridge"); + if (qdict_haskey(qdict, "devices")) { + QListEntry *dev; + QLIST_FOREACH_ENTRY(qdict_get_qlist(qdict, "devices"), dev) { + pci_device_print(mon, qobject_to_qdict(qlist_entry_obj(dev))); + } + } + } +} + +void do_pci_info_print(Monitor *mon, const QObject *data) +{ + QListEntry *bus, *dev; + + QLIST_FOREACH_ENTRY(qobject_to_qlist(data), bus) { + QDict *qdict = qobject_to_qdict(qlist_entry_obj(bus)); + QLIST_FOREACH_ENTRY(qdict_get_qlist(qdict, "devices"), dev) { + pci_device_print(mon, qobject_to_qdict(qlist_entry_obj(dev))); + } + } +} + +static QObject *pci_get_dev_class(const PCIDevice *dev) +{ + int class; + const pci_class_desc *desc; + + class = pci_get_word(dev->config + PCI_CLASS_DEVICE); + desc = pci_class_descriptions; + while (desc->desc && class != desc->class) + desc++; + + if (desc->desc) { + return qobject_from_jsonf("{ 'desc': %s, 'class': %d }", + desc->desc, class); + } else { + return qobject_from_jsonf("{ 'class': %d }", class); + } +} + +static QObject *pci_get_dev_id(const PCIDevice *dev) +{ + return qobject_from_jsonf("{ 'device': %d, 'vendor': %d }", + pci_get_word(dev->config + PCI_VENDOR_ID), + pci_get_word(dev->config + PCI_DEVICE_ID)); +} + +static QObject *pci_get_regions_list(const PCIDevice *dev) +{ + int i; + QList *regions_list; + + regions_list = qlist_new(); + + for (i = 0; i < PCI_NUM_REGIONS; i++) { + QObject *obj; + const PCIIORegion *r = &dev->io_regions[i]; + + if (!r->size) { + continue; + } + + if (r->type & PCI_BASE_ADDRESS_SPACE_IO) { + obj = qobject_from_jsonf("{ 'bar': %d, 'type': 'io', " + "'address': %" PRId64 ", " + "'size': %" PRId64 " }", + i, r->addr, r->size); + } else { + int mem_type_64 = r->type & PCI_BASE_ADDRESS_MEM_TYPE_64; + + obj = qobject_from_jsonf("{ 'bar': %d, 'type': 'memory', " + "'mem_type_64': %i, 'prefetch': %i, " + "'address': %" PRId64 ", " + "'size': %" PRId64 " }", + i, mem_type_64, + r->type & PCI_BASE_ADDRESS_MEM_PREFETCH, + r->addr, r->size); + } + + qlist_append_obj(regions_list, obj); + } + + return QOBJECT(regions_list); +} + +static QObject *pci_get_devices_list(PCIBus *bus, int bus_num); + +static QObject *pci_get_dev_dict(PCIDevice *dev, PCIBus *bus, int bus_num) +{ + uint8_t type; + QObject *obj; + + obj = qobject_from_jsonf("{ 'bus': %d, 'slot': %d, 'function': %d," "'class_info': %p, 'id': %p, 'regions': %p," + " 'qdev_id': %s }", + bus_num, + PCI_SLOT(dev->devfn), PCI_FUNC(dev->devfn), + pci_get_dev_class(dev), pci_get_dev_id(dev), + pci_get_regions_list(dev), + dev->qdev.id ? dev->qdev.id : ""); + + if (dev->config[PCI_INTERRUPT_PIN] != 0) { + QDict *qdict = qobject_to_qdict(obj); + qdict_put(qdict, "irq", qint_from_int(dev->config[PCI_INTERRUPT_LINE])); + } + + type = dev->config[PCI_HEADER_TYPE] & ~PCI_HEADER_TYPE_MULTI_FUNCTION; + if (type == PCI_HEADER_TYPE_BRIDGE) { + QDict *qdict; + QObject *pci_bridge; + + pci_bridge = qobject_from_jsonf("{ 'bus': " + "{ 'number': %d, 'secondary': %d, 'subordinate': %d }, " + "'io_range': { 'base': %" PRId64 ", 'limit': %" PRId64 "}, " + "'memory_range': { 'base': %" PRId64 ", 'limit': %" PRId64 "}, " + "'prefetchable_range': { 'base': %" PRId64 ", 'limit': %" PRId64 "} }", + dev->config[PCI_PRIMARY_BUS], dev->config[PCI_SECONDARY_BUS], + dev->config[PCI_SUBORDINATE_BUS], + pci_bridge_get_base(dev, PCI_BASE_ADDRESS_SPACE_IO), + pci_bridge_get_limit(dev, PCI_BASE_ADDRESS_SPACE_IO), + pci_bridge_get_base(dev, PCI_BASE_ADDRESS_SPACE_MEMORY), + pci_bridge_get_limit(dev, PCI_BASE_ADDRESS_SPACE_MEMORY), + pci_bridge_get_base(dev, PCI_BASE_ADDRESS_SPACE_MEMORY | + PCI_BASE_ADDRESS_MEM_PREFETCH), + pci_bridge_get_limit(dev, PCI_BASE_ADDRESS_SPACE_MEMORY | + PCI_BASE_ADDRESS_MEM_PREFETCH)); + + if (dev->config[PCI_SECONDARY_BUS] != 0) { + PCIBus *child_bus = pci_find_bus(bus, dev->config[PCI_SECONDARY_BUS]); + + if (child_bus) { + qdict = qobject_to_qdict(pci_bridge); + qdict_put_obj(qdict, "devices", + pci_get_devices_list(child_bus, + dev->config[PCI_SECONDARY_BUS])); + } + } + qdict = qobject_to_qdict(obj); + qdict_put_obj(qdict, "pci_bridge", pci_bridge); + } + + return obj; +} + +static QObject *pci_get_devices_list(PCIBus *bus, int bus_num) +{ + int devfn; + PCIDevice *dev; + QList *dev_list; + + dev_list = qlist_new(); + + for (devfn = 0; devfn < ARRAY_SIZE(bus->devices); devfn++) { + dev = bus->devices[devfn]; + if (dev) { + qlist_append_obj(dev_list, pci_get_dev_dict(dev, bus, bus_num)); + } + } + + return QOBJECT(dev_list); +} + +static QObject *pci_get_bus_dict(PCIBus *bus, int bus_num) +{ + bus = pci_find_bus(bus, bus_num); + if (bus) { + return qobject_from_jsonf("{ 'bus': %d, 'devices': %p }", + bus_num, pci_get_devices_list(bus, bus_num)); + } + + return NULL; +} + +void do_pci_info(Monitor *mon, QObject **ret_data) +{ + QList *bus_list; + struct PCIHostBus *host; + + bus_list = qlist_new(); + + QLIST_FOREACH(host, &host_buses, next) { + QObject *obj = pci_get_bus_dict(host->bus, 0); + if (obj) { + qlist_append_obj(bus_list, obj); + } + } + + *ret_data = QOBJECT(bus_list); +} + +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. */ +/* FIXME callers should check for failure, but don't */ +PCIDevice *pci_nic_init(NICInfo *nd, const char *default_model, + const char *default_devaddr) +{ + const char *devaddr = nd->devaddr ? nd->devaddr : default_devaddr; + PCIBus *bus; + int devfn; + PCIDevice *pci_dev; + DeviceState *dev; + int i; + + i = qemu_find_nic_model(nd, pci_nic_models, default_model); + if (i < 0) + return NULL; + + bus = pci_get_bus_devfn(&devfn, devaddr); + if (!bus) { + error_report("Invalid PCI device address %s for device %s", + devaddr, pci_nic_names[i]); + return NULL; + } + + pci_dev = pci_create(bus, devfn, pci_nic_names[i]); + dev = &pci_dev->qdev; + qdev_set_nic_properties(dev, nd); + if (qdev_init(dev) < 0) + return NULL; + return pci_dev; +} + +PCIDevice *pci_nic_init_nofail(NICInfo *nd, const char *default_model, + const char *default_devaddr) +{ + PCIDevice *res; + + if (qemu_show_nic_models(nd->model, pci_nic_models)) + exit(0); + + res = pci_nic_init(nd, default_model, default_devaddr); + if (!res) + exit(1); + return res; +} + +static void pci_bridge_update_mappings_fn(PCIBus *b, PCIDevice *d) +{ + pci_update_mappings(d); +} + +void pci_bridge_update_mappings(PCIBus *b) +{ + PCIBus *child; + + pci_for_each_device_under_bus(b, pci_bridge_update_mappings_fn); + + QLIST_FOREACH(child, &b->child, sibling) { + pci_bridge_update_mappings(child); + } +} + +/* 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]; +} + +PCIBus *pci_find_bus(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 (bus->parent_dev && + !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) { + assert(sec->parent_dev); + if (sec->parent_dev->config[PCI_SECONDARY_BUS] == bus_num) { + return sec; + } + if (pci_secondary_bus_in_range(sec->parent_dev, bus_num)) { + break; + } + } + } + + return NULL; +} + +PCIDevice *pci_find_device(PCIBus *bus, int bus_num, int slot, int function) +{ + bus = pci_find_bus(bus, bus_num); + + if (!bus) + return NULL; + + return bus->devices[PCI_DEVFN(slot, function)]; +} + +static int pci_qdev_init(DeviceState *qdev, DeviceInfo *base) +{ + PCIDevice *pci_dev = (PCIDevice *)qdev; + PCIDeviceInfo *info = container_of(base, PCIDeviceInfo, qdev); + PCIBus *bus; + int devfn, rc; + bool is_default_rom; + + /* initialize cap_present for pci_is_express() and pci_config_size() */ + if (info->is_express) { + pci_dev->cap_present |= QEMU_PCI_CAP_EXPRESS; + } + + bus = FROM_QBUS(PCIBus, qdev_get_parent_bus(qdev)); + devfn = pci_dev->devfn; + pci_dev = do_pci_register_device(pci_dev, bus, base->name, devfn, + info->config_read, info->config_write, + info->is_bridge); + if (pci_dev == NULL) + return -1; + if (qdev->hotplugged && info->no_hotplug) { + qerror_report(QERR_DEVICE_NO_HOTPLUG, info->qdev.name); + do_pci_unregister_device(pci_dev); + return -1; + } + rc = info->init(pci_dev); + if (rc != 0) { + do_pci_unregister_device(pci_dev); + return rc; + } + + /* rom loading */ + is_default_rom = false; + if (pci_dev->romfile == NULL && info->romfile != NULL) { + pci_dev->romfile = qemu_strdup(info->romfile); + is_default_rom = true; + } + pci_add_option_rom(pci_dev, is_default_rom); + + if (bus->hotplug) { + /* Let buses differentiate between hotplug and when device is + * enabled during qemu machine creation. */ + rc = bus->hotplug(bus->hotplug_qdev, pci_dev, + qdev->hotplugged ? PCI_HOTPLUG_ENABLED: + PCI_COLDPLUG_ENABLED); + if (rc != 0) { + int r = pci_unregister_device(&pci_dev->qdev); + assert(!r); + return rc; + } + } + return 0; +} + +static int pci_unplug_device(DeviceState *qdev) +{ + PCIDevice *dev = DO_UPCAST(PCIDevice, qdev, qdev); + PCIDeviceInfo *info = container_of(qdev->info, PCIDeviceInfo, qdev); + + if (info->no_hotplug) { + qerror_report(QERR_DEVICE_NO_HOTPLUG, info->qdev.name); + return -1; + } + return dev->bus->hotplug(dev->bus->hotplug_qdev, dev, + PCI_HOTPLUG_DISABLED); +} + +void pci_qdev_register(PCIDeviceInfo *info) +{ + info->qdev.init = pci_qdev_init; + info->qdev.unplug = pci_unplug_device; + info->qdev.exit = pci_unregister_device; + info->qdev.bus_info = &pci_bus_info; + qdev_register(&info->qdev); +} + +void pci_qdev_register_many(PCIDeviceInfo *info) +{ + while (info->qdev.name) { + pci_qdev_register(info); + info++; + } +} + +PCIDevice *pci_create_multifunction(PCIBus *bus, int devfn, bool multifunction, + const char *name) +{ + DeviceState *dev; + + dev = qdev_create(&bus->qbus, name); + qdev_prop_set_uint32(dev, "addr", devfn); + qdev_prop_set_bit(dev, "multifunction", multifunction); + return DO_UPCAST(PCIDevice, qdev, 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 int pci_find_space(PCIDevice *pdev, uint8_t size) +{ + int config_size = pci_config_size(pdev); + int offset = PCI_CONFIG_HEADER_SIZE; + int i; + for (i = PCI_CONFIG_HEADER_SIZE; i < config_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 void pci_map_option_rom(PCIDevice *pdev, int region_num, pcibus_t addr, pcibus_t size, int type) +{ + cpu_register_physical_memory(addr, size, pdev->rom_offset); +} + +/* 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 int pci_add_option_rom(PCIDevice *pdev, bool is_default_rom) +{ + int size; + char *path; + void *ptr; + char name[32]; + + if (!pdev->romfile) + return 0; + if (strlen(pdev->romfile) == 0) + return 0; + + 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); + if (class == 0x0300) { + rom_add_vga(pdev->romfile); + } else { + rom_add_option(pdev->romfile, -1); + } + return 0; + } + + path = qemu_find_file(QEMU_FILE_TYPE_BIOS, pdev->romfile); + if (path == NULL) { + path = qemu_strdup(pdev->romfile); + } + + size = get_image_size(path); + if (size < 0) { + error_report("%s: failed to find romfile \"%s\"", + __FUNCTION__, pdev->romfile); + return -1; + } + if (size & (size - 1)) { + size = 1 << qemu_fls(size); + } + + if (pdev->qdev.info->vmsd) + snprintf(name, sizeof(name), "%s.rom", pdev->qdev.info->vmsd->name); + else + snprintf(name, sizeof(name), "%s.rom", pdev->qdev.info->name); + pdev->rom_offset = qemu_ram_alloc(&pdev->qdev, name, size); + + ptr = qemu_get_ram_ptr(pdev->rom_offset); + load_image(path, ptr); + qemu_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, size, + 0, pci_map_option_rom); + + return 0; +} + +static void pci_del_option_rom(PCIDevice *pdev) +{ + if (!pdev->rom_offset) + return; + + qemu_ram_free(pdev->rom_offset); + pdev->rom_offset = 0; +} + +/* + * if !offset + * Reserve space and add capability to the linked list in pci config space + * + * 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) +{ + uint8_t *config; + if (!offset) { + offset = pci_find_space(pdev, size); + if (!offset) { + return -ENOSPC; + } + } + + 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, size); + /* 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 writeable 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, size); + + if (!pdev->config[PCI_CAPABILITY_LIST]) + pdev->config[PCI_STATUS] &= ~PCI_STATUS_CAP_LIST; +} + +/* Reserve space for capability at a known offset (to call after load). */ +void pci_reserve_capability(PCIDevice *pdev, uint8_t offset, uint8_t size) +{ + memset(pdev->used + offset, 0xff, size); +} + +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 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. */ + char domain[] = "DDDD:00"; + char slot[] = ":SS.F"; + int domain_len = sizeof domain - 1 /* For '\0' */; + int slot_len = sizeof slot - 1 /* For '\0' */; + int path_len; + char *path, *p; + int s; + + /* 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 = domain_len + slot_len * slot_depth; + + /* Allocate memory, fill in the terminating null byte. */ + path = qemu_malloc(path_len + 1 /* For '\0' */); + path[path_len] = '\0'; + + /* First field is the domain. */ + s = snprintf(domain, sizeof domain, "%04x:00", pci_find_domain(d->bus)); + assert(s == domain_len); + memcpy(path, domain, domain_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 (qdev->info->init == &pci_qdev_init && + qdev->parent_bus->info == &pci_bus_info) { + *pdev = DO_UPCAST(PCIDevice, qdev, qdev); + return 0; + } + return -EINVAL; +} + +int pci_qdev_find_device(const char *id, PCIDevice **pdev) +{ + struct PCIHostBus *host; + int rc = -ENODEV; + + QLIST_FOREACH(host, &host_buses, next) { + int tmp = pci_qdev_find_recursive(host->bus, id, pdev); + if (!tmp) { + rc = 0; + break; + } + if (tmp != -ENODEV) { + rc = tmp; + } + } + + return rc; +} diff --git a/hw/pci.h b/hw/pci.h new file mode 100644 index 0000000..0d2753f --- /dev/null +++ b/hw/pci.h @@ -0,0 +1,469 @@ +#ifndef QEMU_PCI_H +#define QEMU_PCI_H + +#include "qemu-common.h" +#include "qobject.h" + +#include "qdev.h" + +/* PCI includes legacy ISA access. */ +#include "isa.h" + +#include "pcie.h" + +/* PCI bus */ + +#define PCI_DEVFN(slot, func) ((((slot) & 0x1f) << 3) | ((func) & 0x07)) +#define PCI_SLOT(devfn) (((devfn) >> 3) & 0x1f) +#define PCI_FUNC(devfn) ((devfn) & 0x07) +#define PCI_FUNC_MAX 8 + +/* Class, Vendor and Device IDs from Linux's pci_ids.h */ +#include "pci_ids.h" + +/* QEMU-specific Vendor and Device ID definitions */ + +/* IBM (0x1014) */ +#define PCI_DEVICE_ID_IBM_440GX 0x027f +#define PCI_DEVICE_ID_IBM_OPENPIC2 0xffff + +/* Hitachi (0x1054) */ +#define PCI_VENDOR_ID_HITACHI 0x1054 +#define PCI_DEVICE_ID_HITACHI_SH7751R 0x350e + +/* Apple (0x106b) */ +#define PCI_DEVICE_ID_APPLE_343S1201 0x0010 +#define PCI_DEVICE_ID_APPLE_UNI_N_I_PCI 0x001e +#define PCI_DEVICE_ID_APPLE_UNI_N_PCI 0x001f +#define PCI_DEVICE_ID_APPLE_UNI_N_KEYL 0x0022 +#define PCI_DEVICE_ID_APPLE_IPID_USB 0x003f + +/* Realtek (0x10ec) */ +#define PCI_DEVICE_ID_REALTEK_8029 0x8029 + +/* Xilinx (0x10ee) */ +#define PCI_DEVICE_ID_XILINX_XC2VP30 0x0300 + +/* Marvell (0x11ab) */ +#define PCI_DEVICE_ID_MARVELL_GT6412X 0x4620 + +/* QEMU/Bochs VGA (0x1234) */ +#define PCI_VENDOR_ID_QEMU 0x1234 +#define PCI_DEVICE_ID_QEMU_VGA 0x1111 + +/* VMWare (0x15ad) */ +#define PCI_VENDOR_ID_VMWARE 0x15ad +#define PCI_DEVICE_ID_VMWARE_SVGA2 0x0405 +#define PCI_DEVICE_ID_VMWARE_SVGA 0x0710 +#define PCI_DEVICE_ID_VMWARE_NET 0x0720 +#define PCI_DEVICE_ID_VMWARE_SCSI 0x0730 +#define PCI_DEVICE_ID_VMWARE_IDE 0x1729 + +/* Intel (0x8086) */ +#define PCI_DEVICE_ID_INTEL_82551IT 0x1209 +#define PCI_DEVICE_ID_INTEL_82557 0x1229 +#define PCI_DEVICE_ID_INTEL_82801IR 0x2922 + +/* Red Hat / Qumranet (for QEMU) -- see pci-ids.txt */ +#define PCI_VENDOR_ID_REDHAT_QUMRANET 0x1af4 +#define PCI_SUBVENDOR_ID_REDHAT_QUMRANET 0x1af4 +#define PCI_SUBDEVICE_ID_QEMU 0x1100 + +#define PCI_DEVICE_ID_VIRTIO_NET 0x1000 +#define PCI_DEVICE_ID_VIRTIO_BLOCK 0x1001 +#define PCI_DEVICE_ID_VIRTIO_BALLOON 0x1002 +#define PCI_DEVICE_ID_VIRTIO_CONSOLE 0x1003 + +#define FMT_PCIBUS PRIx64 + +typedef void PCIConfigWriteFunc(PCIDevice *pci_dev, + uint32_t address, uint32_t data, int len); +typedef uint32_t PCIConfigReadFunc(PCIDevice *pci_dev, + uint32_t address, int len); +typedef void PCIMapIORegionFunc(PCIDevice *pci_dev, int region_num, + pcibus_t addr, pcibus_t size, int type); +typedef int PCIUnregisterFunc(PCIDevice *pci_dev); + +typedef struct PCIIORegion { + pcibus_t addr; /* current PCI mapping address. -1 means not mapped */ +#define PCI_BAR_UNMAPPED (~(pcibus_t)0) + pcibus_t size; + pcibus_t filtered_size; + uint8_t type; + PCIMapIORegionFunc *map_func; +} PCIIORegion; + +#define PCI_ROM_SLOT 6 +#define PCI_NUM_REGIONS 7 + +#include "pci_regs.h" + +/* PCI HEADER_TYPE */ +#define PCI_HEADER_TYPE_MULTI_FUNCTION 0x80 + +/* Size of the standard PCI config header */ +#define PCI_CONFIG_HEADER_SIZE 0x40 +/* Size of the standard PCI config space */ +#define PCI_CONFIG_SPACE_SIZE 0x100 +/* Size of the standart PCIe config space: 4KB */ +#define PCIE_CONFIG_SPACE_SIZE 0x1000 + +#define PCI_NUM_PINS 4 /* A-D */ + +/* Bits in cap_present field. */ +enum { + QEMU_PCI_CAP_MSI = 0x1, + QEMU_PCI_CAP_MSIX = 0x2, + QEMU_PCI_CAP_EXPRESS = 0x4, + + /* multifunction capable device */ +#define QEMU_PCI_CAP_MULTIFUNCTION_BITNR 3 + QEMU_PCI_CAP_MULTIFUNCTION = (1 << QEMU_PCI_CAP_MULTIFUNCTION_BITNR), + + /* command register SERR bit enabled */ +#define QEMU_PCI_CAP_SERR_BITNR 4 + QEMU_PCI_CAP_SERR = (1 << QEMU_PCI_CAP_SERR_BITNR), +}; + +struct PCIDevice { + DeviceState qdev; + /* PCI config space */ + uint8_t *config; + + /* Used to enable config checks on load. Note that writeable bits are + * never checked even if set in cmask. */ + uint8_t *cmask; + + /* Used to implement R/W bytes */ + uint8_t *wmask; + + /* Used to implement RW1C(Write 1 to Clear) bytes */ + uint8_t *w1cmask; + + /* Used to allocate config space for capabilities. */ + uint8_t *used; + + /* the following fields are read only */ + PCIBus *bus; + uint32_t devfn; + char name[64]; + PCIIORegion io_regions[PCI_NUM_REGIONS]; + + /* do not access the following fields */ + PCIConfigReadFunc *config_read; + PCIConfigWriteFunc *config_write; + + /* IRQ objects for the INTA-INTD pins. */ + qemu_irq *irq; + + /* Current IRQ levels. Used internally by the generic PCI code. */ + uint8_t irq_state; + + /* Capability bits */ + uint32_t cap_present; + + /* Offset of MSI-X capability in config space */ + uint8_t msix_cap; + + /* MSI-X entries */ + int msix_entries_nr; + + /* Space to store MSIX table */ + uint8_t *msix_table_page; + /* MMIO index used to map MSIX table and pending bit entries. */ + int msix_mmio_index; + /* Reference-count for entries actually in use by driver. */ + unsigned *msix_entry_used; + /* Region including the MSI-X table */ + uint32_t msix_bar_size; + /* Version id needed for VMState */ + int32_t version_id; + + /* Offset of MSI capability in config space */ + uint8_t msi_cap; + + /* PCI Express */ + PCIExpressDevice exp; + + /* Location of option rom */ + char *romfile; + ram_addr_t rom_offset; + uint32_t rom_bar; +}; + +PCIDevice *pci_register_device(PCIBus *bus, const char *name, + int instance_size, int devfn, + PCIConfigReadFunc *config_read, + PCIConfigWriteFunc *config_write); + +void pci_register_bar(PCIDevice *pci_dev, int region_num, + pcibus_t size, uint8_t type, + PCIMapIORegionFunc *map_func); + +int pci_add_capability(PCIDevice *pdev, uint8_t cap_id, + uint8_t offset, uint8_t size); + +void pci_del_capability(PCIDevice *pci_dev, uint8_t cap_id, uint8_t cap_size); + +void pci_reserve_capability(PCIDevice *pci_dev, uint8_t offset, uint8_t size); + +uint8_t pci_find_capability(PCIDevice *pci_dev, uint8_t cap_id); + + +uint32_t pci_default_read_config(PCIDevice *d, + uint32_t address, int len); +void pci_default_write_config(PCIDevice *d, + uint32_t address, uint32_t val, int len); +void pci_device_save(PCIDevice *s, QEMUFile *f); +int pci_device_load(PCIDevice *s, QEMUFile *f); + +typedef void (*pci_set_irq_fn)(void *opaque, int irq_num, int level); +typedef int (*pci_map_irq_fn)(PCIDevice *pci_dev, int irq_num); + +typedef enum { + PCI_HOTPLUG_DISABLED, + PCI_HOTPLUG_ENABLED, + PCI_COLDPLUG_ENABLED, +} PCIHotplugState; + +typedef int (*pci_hotplug_fn)(DeviceState *qdev, PCIDevice *pci_dev, + PCIHotplugState state); +void pci_bus_new_inplace(PCIBus *bus, DeviceState *parent, + const char *name, int devfn_min); +PCIBus *pci_bus_new(DeviceState *parent, const char *name, int devfn_min); +void pci_bus_irqs(PCIBus *bus, pci_set_irq_fn set_irq, pci_map_irq_fn map_irq, + void *irq_opaque, int nirq); +void pci_bus_hotplug(PCIBus *bus, pci_hotplug_fn hotplug, DeviceState *dev); +PCIBus *pci_register_bus(DeviceState *parent, const char *name, + pci_set_irq_fn set_irq, pci_map_irq_fn map_irq, + void *irq_opaque, int devfn_min, int nirq); +void pci_device_reset(PCIDevice *dev); +void pci_bus_reset(PCIBus *bus); + +void pci_bus_set_mem_base(PCIBus *bus, target_phys_addr_t base); + +PCIDevice *pci_nic_init(NICInfo *nd, const char *default_model, + const char *default_devaddr); +PCIDevice *pci_nic_init_nofail(NICInfo *nd, const char *default_model, + const char *default_devaddr); +int pci_bus_num(PCIBus *s); +void pci_for_each_device(PCIBus *bus, int bus_num, void (*fn)(PCIBus *bus, PCIDevice *d)); +PCIBus *pci_find_root_bus(int domain); +int pci_find_domain(const PCIBus *bus); +PCIBus *pci_find_bus(PCIBus *bus, int bus_num); +PCIDevice *pci_find_device(PCIBus *bus, int bus_num, int slot, int function); +int pci_qdev_find_device(const char *id, PCIDevice **pdev); +PCIBus *pci_get_bus_devfn(int *devfnp, const char *devaddr); + +int pci_parse_devaddr(const char *addr, int *domp, int *busp, + unsigned int *slotp, unsigned int *funcp); +int pci_read_devaddr(Monitor *mon, const char *addr, int *domp, int *busp, + unsigned *slotp); + +void do_pci_info_print(Monitor *mon, const QObject *data); +void do_pci_info(Monitor *mon, QObject **ret_data); +void pci_bridge_update_mappings(PCIBus *b); + +void pci_device_deassert_intx(PCIDevice *dev); + +static inline void +pci_set_byte(uint8_t *config, uint8_t val) +{ + *config = val; +} + +static inline uint8_t +pci_get_byte(const uint8_t *config) +{ + return *config; +} + +static inline void +pci_set_word(uint8_t *config, uint16_t val) +{ + cpu_to_le16wu((uint16_t *)config, val); +} + +static inline uint16_t +pci_get_word(const uint8_t *config) +{ + return le16_to_cpupu((const uint16_t *)config); +} + +static inline void +pci_set_long(uint8_t *config, uint32_t val) +{ + cpu_to_le32wu((uint32_t *)config, val); +} + +static inline uint32_t +pci_get_long(const uint8_t *config) +{ + return le32_to_cpupu((const uint32_t *)config); +} + +static inline void +pci_set_quad(uint8_t *config, uint64_t val) +{ + cpu_to_le64w((uint64_t *)config, val); +} + +static inline uint64_t +pci_get_quad(const uint8_t *config) +{ + return le64_to_cpup((const uint64_t *)config); +} + +static inline void +pci_config_set_vendor_id(uint8_t *pci_config, uint16_t val) +{ + pci_set_word(&pci_config[PCI_VENDOR_ID], val); +} + +static inline void +pci_config_set_device_id(uint8_t *pci_config, uint16_t val) +{ + pci_set_word(&pci_config[PCI_DEVICE_ID], val); +} + +static inline void +pci_config_set_revision(uint8_t *pci_config, uint8_t val) +{ + pci_set_byte(&pci_config[PCI_REVISION_ID], val); +} + +static inline void +pci_config_set_class(uint8_t *pci_config, uint16_t val) +{ + pci_set_word(&pci_config[PCI_CLASS_DEVICE], val); +} + +static inline void +pci_config_set_prog_interface(uint8_t *pci_config, uint8_t val) +{ + pci_set_byte(&pci_config[PCI_CLASS_PROG], val); +} + +static inline void +pci_config_set_interrupt_pin(uint8_t *pci_config, uint8_t val) +{ + pci_set_byte(&pci_config[PCI_INTERRUPT_PIN], val); +} + +/* + * helper functions to do bit mask operation on configuration space. + * Just to set bit, use test-and-set and discard returned value. + * Just to clear bit, use test-and-clear and discard returned value. + * NOTE: They aren't atomic. + */ +static inline uint8_t +pci_byte_test_and_clear_mask(uint8_t *config, uint8_t mask) +{ + uint8_t val = pci_get_byte(config); + pci_set_byte(config, val & ~mask); + return val & mask; +} + +static inline uint8_t +pci_byte_test_and_set_mask(uint8_t *config, uint8_t mask) +{ + uint8_t val = pci_get_byte(config); + pci_set_byte(config, val | mask); + return val & mask; +} + +static inline uint16_t +pci_word_test_and_clear_mask(uint8_t *config, uint16_t mask) +{ + uint16_t val = pci_get_word(config); + pci_set_word(config, val & ~mask); + return val & mask; +} + +static inline uint16_t +pci_word_test_and_set_mask(uint8_t *config, uint16_t mask) +{ + uint16_t val = pci_get_word(config); + pci_set_word(config, val | mask); + return val & mask; +} + +static inline uint32_t +pci_long_test_and_clear_mask(uint8_t *config, uint32_t mask) +{ + uint32_t val = pci_get_long(config); + pci_set_long(config, val & ~mask); + return val & mask; +} + +static inline uint32_t +pci_long_test_and_set_mask(uint8_t *config, uint32_t mask) +{ + uint32_t val = pci_get_long(config); + pci_set_long(config, val | mask); + return val & mask; +} + +static inline uint64_t +pci_quad_test_and_clear_mask(uint8_t *config, uint64_t mask) +{ + uint64_t val = pci_get_quad(config); + pci_set_quad(config, val & ~mask); + return val & mask; +} + +static inline uint64_t +pci_quad_test_and_set_mask(uint8_t *config, uint64_t mask) +{ + uint64_t val = pci_get_quad(config); + pci_set_quad(config, val | mask); + return val & mask; +} + +typedef int (*pci_qdev_initfn)(PCIDevice *dev); +typedef struct { + DeviceInfo qdev; + pci_qdev_initfn init; + PCIUnregisterFunc *exit; + PCIConfigReadFunc *config_read; + PCIConfigWriteFunc *config_write; + + /* + * pci-to-pci bridge or normal device. + * This doesn't mean pci host switch. + * When card bus bridge is supported, this would be enhanced. + */ + int is_bridge; + + /* pcie stuff */ + int is_express; /* is this device pci express? */ + + /* device isn't hot-pluggable */ + int no_hotplug; + + /* rom bar */ + const char *romfile; +} PCIDeviceInfo; + +void pci_qdev_register(PCIDeviceInfo *info); +void pci_qdev_register_many(PCIDeviceInfo *info); + +PCIDevice *pci_create_multifunction(PCIBus *bus, int devfn, bool multifunction, + const char *name); +PCIDevice *pci_create_simple_multifunction(PCIBus *bus, int devfn, + bool multifunction, + const char *name); +PCIDevice *pci_create(PCIBus *bus, int devfn, const char *name); +PCIDevice *pci_create_simple(PCIBus *bus, int devfn, const char *name); + +static inline int pci_is_express(const PCIDevice *d) +{ + return d->cap_present & QEMU_PCI_CAP_EXPRESS; +} + +static inline uint32_t pci_config_size(const PCIDevice *d) +{ + return pci_is_express(d) ? PCIE_CONFIG_SPACE_SIZE : PCI_CONFIG_SPACE_SIZE; +} + +#endif diff --git a/hw/pci_bridge.c b/hw/pci_bridge.c new file mode 100644 index 0000000..464d897 --- /dev/null +++ b/hw/pci_bridge.c @@ -0,0 +1,275 @@ +/* + * 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 + * VA Linux Systems Japan K.K. + */ + +#include "pci_bridge.h" +#include "pci_internals.h" +#include "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; +} + +/* 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 = container_of(d, PCIBridge, dev); + uint16_t oldctl = pci_get_word(d->config + PCI_BRIDGE_CONTROL); + uint16_t newctl; + + pci_default_write_config(d, address, val, len); + + if (/* 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)) { + pci_bridge_update_mappings(&s->sec_bus); + } + + newctl = pci_get_word(d->config + PCI_BRIDGE_CONTROL); + if (~oldctl & newctl & PCI_BRIDGE_CTL_BUS_RESET) { + /* Trigger hot reset on 0->1 transition. */ + pci_bus_reset(&s->sec_bus); + } +} + +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_word(conf + PCI_PREF_BASE_UPPER32, 0); + pci_set_word(conf + PCI_PREF_LIMIT_UPPER32, 0); +} + +/* reset bridge specific configuration registers */ +void pci_bridge_reset_reg(PCIDevice *dev) +{ + 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_word(conf + PCI_PREF_BASE_UPPER32, 0); + pci_set_word(conf + PCI_PREF_LIMIT_UPPER32, 0); + + pci_set_word(conf + PCI_BRIDGE_CONTROL, 0); +} + +/* default reset function for PCI-to-PCI bridge */ +void pci_bridge_reset(DeviceState *qdev) +{ + PCIDevice *dev = DO_UPCAST(PCIDevice, qdev, qdev); + pci_bridge_reset_reg(dev); +} + +/* default qdev initialization function for PCI-to-PCI bridge */ +int pci_bridge_initfn(PCIDevice *dev) +{ + PCIBus *parent = dev->bus; + PCIBridge *br = DO_UPCAST(PCIBridge, dev, dev); + PCIBus *sec_bus = &br->sec_bus; + + pci_set_word(dev->config + PCI_STATUS, + PCI_STATUS_66MHZ | PCI_STATUS_FAST_BACK); + 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); + + qbus_create_inplace(&sec_bus->qbus, &pci_bus_info, &dev->qdev, + br->bus_name); + sec_bus->parent_dev = dev; + sec_bus->map_irq = br->map_irq; + + 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 */ +int pci_bridge_exitfn(PCIDevice *pci_dev) +{ + PCIBridge *s = DO_UPCAST(PCIBridge, dev, pci_dev); + assert(QLIST_EMPTY(&s->sec_bus.child)); + QLIST_REMOVE(&s->sec_bus, sibling); + /* qbus_free() is called automatically by qdev_free() */ + return 0; +} + +/* + * 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; +} diff --git a/hw/pci_bridge.h b/hw/pci_bridge.h new file mode 100644 index 0000000..84411a6 --- /dev/null +++ b/hw/pci_bridge.h @@ -0,0 +1,66 @@ +/* + * QEMU PCI bridge + * + * Copyright (c) 2004 Fabrice Bellard + * + * 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, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * split out pci bus specific stuff from pci.[hc] to pci_bridge.[hc] + * Copyright (c) 2009 Isaku Yamahata + * VA Linux Systems Japan K.K. + * + */ + +#ifndef QEMU_PCI_BRIDGE_H +#define QEMU_PCI_BRIDGE_H + +#include "pci.h" + +int pci_bridge_ssvid_init(PCIDevice *dev, uint8_t offset, + uint16_t svid, uint16_t ssid); + +PCIDevice *pci_bridge_get_device(PCIBus *bus); +PCIBus *pci_bridge_get_sec_bus(PCIBridge *br); + +pcibus_t pci_bridge_get_base(const PCIDevice *bridge, uint8_t type); +pcibus_t pci_bridge_get_limit(const PCIDevice *bridge, uint8_t type); + +void pci_bridge_write_config(PCIDevice *d, + uint32_t address, uint32_t val, int len); +void pci_bridge_disable_base_limit(PCIDevice *dev); +void pci_bridge_reset_reg(PCIDevice *dev); +void pci_bridge_reset(DeviceState *qdev); + +int pci_bridge_initfn(PCIDevice *pci_dev); +int pci_bridge_exitfn(PCIDevice *pci_dev); + + +/* + * 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); + +#endif /* QEMU_PCI_BRIDGE_H */ +/* + * Local variables: + * c-indent-level: 4 + * c-basic-offset: 4 + * tab-width: 8 + * indent-tab-mode: nil + * End: + */ diff --git a/hw/pci_host.c b/hw/pci_host.c new file mode 100644 index 0000000..7c40155 --- /dev/null +++ b/hw/pci_host.c @@ -0,0 +1,159 @@ +/* + * pci_host.c + * + * Copyright (c) 2009 Isaku Yamahata + * 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 . + */ + +#include "pci.h" +#include "pci_host.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 functio to get a PCIDeice* 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, PCI_SLOT(devfn), PCI_FUNC(devfn)); +} + +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_dev->config_write(pci_dev, config_addr, 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; + + assert(len == 1 || len == 2 || len == 4); + if (!pci_dev) { + return ~0x0; + } + + val = pci_dev->config_read(pci_dev, config_addr, 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(ReadWriteHandler *handler, + pcibus_t addr, uint32_t val, int len) +{ + PCIHostState *s = container_of(handler, PCIHostState, conf_handler); + + PCI_DPRINTF("%s addr %" FMT_PCIBUS " %d val %"PRIx32"\n", + __func__, addr, len, val); + s->config_reg = val; +} + +static uint32_t pci_host_config_read(ReadWriteHandler *handler, + pcibus_t addr, int len) +{ + PCIHostState *s = container_of(handler, PCIHostState, conf_handler); + uint32_t val = s->config_reg; + + PCI_DPRINTF("%s addr %" FMT_PCIBUS " len %d val %"PRIx32"\n", + __func__, addr, len, val); + return val; +} + +static void pci_host_data_write(ReadWriteHandler *handler, + pcibus_t addr, uint32_t val, int len) +{ + PCIHostState *s = container_of(handler, PCIHostState, data_handler); + PCI_DPRINTF("write addr %" FMT_PCIBUS " len %d val %x\n", + addr, len, val); + if (s->config_reg & (1u << 31)) + pci_data_write(s->bus, s->config_reg | (addr & 3), val, len); +} + +static uint32_t pci_host_data_read(ReadWriteHandler *handler, + pcibus_t addr, int len) +{ + PCIHostState *s = container_of(handler, PCIHostState, data_handler); + uint32_t val; + if (!(s->config_reg & (1 << 31))) + return 0xffffffff; + val = pci_data_read(s->bus, s->config_reg | (addr & 3), len); + PCI_DPRINTF("read addr %" FMT_PCIBUS " len %d val %x\n", + addr, len, val); + return val; +} + +static void pci_host_init(PCIHostState *s) +{ + s->conf_handler.write = pci_host_config_write; + s->conf_handler.read = pci_host_config_read; + s->data_handler.write = pci_host_data_write; + s->data_handler.read = pci_host_data_read; +} + +int pci_host_conf_register_mmio(PCIHostState *s, int endian) +{ + pci_host_init(s); + return cpu_register_io_memory_simple(&s->conf_handler, endian); +} + +void pci_host_conf_register_ioport(pio_addr_t ioport, PCIHostState *s) +{ + pci_host_init(s); + register_ioport_simple(&s->conf_handler, ioport, 4, 4); + sysbus_init_ioports(&s->busdev, ioport, 4); +} + +int pci_host_data_register_mmio(PCIHostState *s, int endian) +{ + pci_host_init(s); + return cpu_register_io_memory_simple(&s->data_handler, endian); +} + +void pci_host_data_register_ioport(pio_addr_t ioport, PCIHostState *s) +{ + pci_host_init(s); + register_ioport_simple(&s->data_handler, ioport, 4, 1); + register_ioport_simple(&s->data_handler, ioport, 4, 2); + register_ioport_simple(&s->data_handler, ioport, 4, 4); + sysbus_init_ioports(&s->busdev, ioport, 4); +} diff --git a/hw/pci_host.h b/hw/pci_host.h new file mode 100644 index 0000000..0a58595 --- /dev/null +++ b/hw/pci_host.h @@ -0,0 +1,53 @@ +/* + * QEMU Common PCI Host bridge configuration data space access routines. + * + * 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. + */ + +/* Worker routines for a PCI host controller that uses an {address,data} + register pair to access PCI configuration space. */ + +#ifndef PCI_HOST_H +#define PCI_HOST_H + +#include "sysbus.h" +#include "rwhandler.h" + +struct PCIHostState { + SysBusDevice busdev; + ReadWriteHandler conf_handler; + ReadWriteHandler data_handler; + uint32_t config_reg; + PCIBus *bus; +}; + +void pci_data_write(PCIBus *s, uint32_t addr, uint32_t val, int len); +uint32_t pci_data_read(PCIBus *s, uint32_t addr, int len); + +/* for mmio */ +int pci_host_conf_register_mmio(PCIHostState *s, int endian); +int pci_host_data_register_mmio(PCIHostState *s, int endian); + +/* for ioio */ +void pci_host_conf_register_ioport(pio_addr_t ioport, PCIHostState *s); +void pci_host_data_register_ioport(pio_addr_t ioport, PCIHostState *s); + +#endif /* PCI_HOST_H */ diff --git a/hw/pci_ids.h b/hw/pci_ids.h new file mode 100644 index 0000000..ea3418c --- /dev/null +++ b/hw/pci_ids.h @@ -0,0 +1,110 @@ +/* + * PCI Class, Vendor and Device IDs + * + * Please keep sorted. + * + * Abbreviated version of linux/pci_ids.h + * + * QEMU-specific definitions belong in pci.h + */ + +/* Device classes and subclasses */ + +#define PCI_BASE_CLASS_STORAGE 0x01 +#define PCI_BASE_CLASS_NETWORK 0x02 + +#define PCI_CLASS_STORAGE_SCSI 0x0100 +#define PCI_CLASS_STORAGE_IDE 0x0101 +#define PCI_CLASS_STORAGE_SATA 0x0106 +#define PCI_CLASS_STORAGE_OTHER 0x0180 + +#define PCI_CLASS_NETWORK_ETHERNET 0x0200 + +#define PCI_CLASS_DISPLAY_VGA 0x0300 +#define PCI_CLASS_DISPLAY_OTHER 0x0380 + +#define PCI_CLASS_MULTIMEDIA_AUDIO 0x0401 + +#define PCI_CLASS_MEMORY_RAM 0x0500 + +#define PCI_CLASS_SYSTEM_OTHER 0x0880 + +#define PCI_CLASS_SERIAL_USB 0x0c03 + +#define PCI_CLASS_BRIDGE_HOST 0x0600 +#define PCI_CLASS_BRIDGE_ISA 0x0601 +#define PCI_CLASS_BRIDGE_PCI 0x0604 +#define PCI_CLASS_BRIDGE_OTHER 0x0680 + +#define PCI_CLASS_COMMUNICATION_OTHER 0x0780 + +#define PCI_CLASS_PROCESSOR_CO 0x0b40 +#define PCI_CLASS_PROCESSOR_POWERPC 0x0b20 + +#define PCI_CLASS_OTHERS 0xff + +/* Vendors and devices. Sort key: vendor first, device next. */ + +#define PCI_VENDOR_ID_LSI_LOGIC 0x1000 +#define PCI_DEVICE_ID_LSI_53C895A 0x0012 + +#define PCI_VENDOR_ID_DEC 0x1011 +#define PCI_DEVICE_ID_DEC_21154 0x0026 + +#define PCI_VENDOR_ID_CIRRUS 0x1013 + +#define PCI_VENDOR_ID_IBM 0x1014 + +#define PCI_VENDOR_ID_AMD 0x1022 +#define PCI_DEVICE_ID_AMD_LANCE 0x2000 + +#define PCI_VENDOR_ID_TI 0x104c + +#define PCI_VENDOR_ID_MOTOROLA 0x1057 +#define PCI_DEVICE_ID_MOTOROLA_MPC106 0x0002 +#define PCI_DEVICE_ID_MOTOROLA_RAVEN 0x4801 + +#define PCI_VENDOR_ID_APPLE 0x106b +#define PCI_DEVICE_ID_APPLE_UNI_N_AGP 0x0020 +#define PCI_DEVICE_ID_APPLE_U3_AGP 0x004b + +#define PCI_VENDOR_ID_SUN 0x108e +#define PCI_DEVICE_ID_SUN_EBUS 0x1000 +#define PCI_DEVICE_ID_SUN_SIMBA 0x5000 +#define PCI_DEVICE_ID_SUN_SABRE 0xa000 + +#define PCI_VENDOR_ID_CMD 0x1095 +#define PCI_DEVICE_ID_CMD_646 0x0646 + +#define PCI_VENDOR_ID_REALTEK 0x10ec +#define PCI_DEVICE_ID_REALTEK_8139 0x8139 + +#define PCI_VENDOR_ID_XILINX 0x10ee + +#define PCI_VENDOR_ID_VIA 0x1106 +#define PCI_DEVICE_ID_VIA_ISA_BRIDGE 0x0686 +#define PCI_DEVICE_ID_VIA_IDE 0x0571 +#define PCI_DEVICE_ID_VIA_UHCI 0x3038 +#define PCI_DEVICE_ID_VIA_ACPI 0x3057 +#define PCI_DEVICE_ID_VIA_AC97 0x3058 +#define PCI_DEVICE_ID_VIA_MC97 0x3068 + +#define PCI_VENDOR_ID_MARVELL 0x11ab + +#define PCI_VENDOR_ID_ENSONIQ 0x1274 +#define PCI_DEVICE_ID_ENSONIQ_ES1370 0x5000 + +#define PCI_VENDOR_ID_FREESCALE 0x1957 +#define PCI_DEVICE_ID_MPC8533E 0x0030 + +#define PCI_VENDOR_ID_INTEL 0x8086 +#define PCI_DEVICE_ID_INTEL_82441 0x1237 +#define PCI_DEVICE_ID_INTEL_82801AA_5 0x2415 +#define PCI_DEVICE_ID_INTEL_ESB_9 0x25ab +#define PCI_DEVICE_ID_INTEL_82371SB_0 0x7000 +#define PCI_DEVICE_ID_INTEL_82371SB_1 0x7010 +#define PCI_DEVICE_ID_INTEL_82371SB_2 0x7020 +#define PCI_DEVICE_ID_INTEL_82371AB_0 0x7110 +#define PCI_DEVICE_ID_INTEL_82371AB 0x7111 +#define PCI_DEVICE_ID_INTEL_82371AB_2 0x7112 +#define PCI_DEVICE_ID_INTEL_82371AB_3 0x7113 diff --git a/hw/pci_internals.h b/hw/pci_internals.h new file mode 100644 index 0000000..e3c93a3 --- /dev/null +++ b/hw/pci_internals.h @@ -0,0 +1,47 @@ +#ifndef QEMU_PCI_INTERNALS_H +#define QEMU_PCI_INTERNALS_H + +/* + * This header files is private to pci.c and pci_bridge.c + * So following structures are opaque to others and shouldn't be + * accessed. + * + * For pci-to-pci bridge needs to include this header file to embed + * PCIBridge in its structure or to get sizeof(PCIBridge), + * However, they shouldn't access those following members directly. + * Use accessor function in pci.h, pci_bridge.h + */ + +extern struct BusInfo pci_bus_info; + +struct PCIBus { + BusState qbus; + int devfn_min; + pci_set_irq_fn set_irq; + pci_map_irq_fn map_irq; + pci_hotplug_fn hotplug; + DeviceState *hotplug_qdev; + void *irq_opaque; + PCIDevice *devices[256]; + PCIDevice *parent_dev; + target_phys_addr_t mem_base; + + QLIST_HEAD(, PCIBus) child; /* this will be replaced by qdev later */ + QLIST_ENTRY(PCIBus) sibling;/* this will be replaced by qdev later */ + + /* The bus IRQ state is the logical OR of the connected devices. + Keep a count of the number of devices with raised IRQs. */ + int nirq; + int *irq_count; +}; + +struct PCIBridge { + PCIDevice dev; + + /* private member */ + PCIBus sec_bus; + pci_map_irq_fn map_irq; + const char *bus_name; +}; + +#endif /* QEMU_PCI_INTERNALS_H */ diff --git a/hw/pci_regs.h b/hw/pci_regs.h new file mode 100644 index 0000000..dd0bed4 --- /dev/null +++ b/hw/pci_regs.h @@ -0,0 +1,665 @@ +/* + * pci_regs.h + * + * PCI standard defines + * Copyright 1994, Drew Eckhardt + * Copyright 1997--1999 Martin Mares + * + * For more information, please consult the following manuals (look at + * http://www.pcisig.com/ for how to get them): + * + * PCI BIOS Specification + * PCI Local Bus Specification + * PCI to PCI Bridge Specification + * PCI System Design Guide + * + * For hypertransport information, please consult the following manuals + * from http://www.hypertransport.org + * + * The Hypertransport I/O Link Specification + */ + +#ifndef LINUX_PCI_REGS_H +#define LINUX_PCI_REGS_H + +/* + * Under PCI, each device has 256 bytes of configuration address space, + * of which the first 64 bytes are standardized as follows: + */ +#define PCI_VENDOR_ID 0x00 /* 16 bits */ +#define PCI_DEVICE_ID 0x02 /* 16 bits */ +#define PCI_COMMAND 0x04 /* 16 bits */ +#define PCI_COMMAND_IO 0x1 /* Enable response in I/O space */ +#define PCI_COMMAND_MEMORY 0x2 /* Enable response in Memory space */ +#define PCI_COMMAND_MASTER 0x4 /* Enable bus mastering */ +#define PCI_COMMAND_SPECIAL 0x8 /* Enable response to special cycles */ +#define PCI_COMMAND_INVALIDATE 0x10 /* Use memory write and invalidate */ +#define PCI_COMMAND_VGA_PALETTE 0x20 /* Enable palette snooping */ +#define PCI_COMMAND_PARITY 0x40 /* Enable parity checking */ +#define PCI_COMMAND_WAIT 0x80 /* Enable address/data stepping */ +#define PCI_COMMAND_SERR 0x100 /* Enable SERR */ +#define PCI_COMMAND_FAST_BACK 0x200 /* Enable back-to-back writes */ +#define PCI_COMMAND_INTX_DISABLE 0x400 /* INTx Emulation Disable */ + +#define PCI_STATUS 0x06 /* 16 bits */ +#define PCI_STATUS_INTERRUPT 0x08 /* Interrupt status */ +#define PCI_STATUS_CAP_LIST 0x10 /* Support Capability List */ +#define PCI_STATUS_66MHZ 0x20 /* Support 66 Mhz PCI 2.1 bus */ +#define PCI_STATUS_UDF 0x40 /* Support User Definable Features [obsolete] */ +#define PCI_STATUS_FAST_BACK 0x80 /* Accept fast-back to back */ +#define PCI_STATUS_PARITY 0x100 /* Detected parity error */ +#define PCI_STATUS_DEVSEL_MASK 0x600 /* DEVSEL timing */ +#define PCI_STATUS_DEVSEL_FAST 0x000 +#define PCI_STATUS_DEVSEL_MEDIUM 0x200 +#define PCI_STATUS_DEVSEL_SLOW 0x400 +#define PCI_STATUS_SIG_TARGET_ABORT 0x800 /* Set on target abort */ +#define PCI_STATUS_REC_TARGET_ABORT 0x1000 /* Master ack of " */ +#define PCI_STATUS_REC_MASTER_ABORT 0x2000 /* Set on master abort */ +#define PCI_STATUS_SIG_SYSTEM_ERROR 0x4000 /* Set when we drive SERR */ +#define PCI_STATUS_DETECTED_PARITY 0x8000 /* Set on parity error */ + +#define PCI_CLASS_REVISION 0x08 /* High 24 bits are class, low 8 revision */ +#define PCI_REVISION_ID 0x08 /* Revision ID */ +#define PCI_CLASS_PROG 0x09 /* Reg. Level Programming Interface */ +#define PCI_CLASS_DEVICE 0x0a /* Device class */ + +#define PCI_CACHE_LINE_SIZE 0x0c /* 8 bits */ +#define PCI_LATENCY_TIMER 0x0d /* 8 bits */ +#define PCI_HEADER_TYPE 0x0e /* 8 bits */ +#define PCI_HEADER_TYPE_NORMAL 0 +#define PCI_HEADER_TYPE_BRIDGE 1 +#define PCI_HEADER_TYPE_CARDBUS 2 + +#define PCI_BIST 0x0f /* 8 bits */ +#define PCI_BIST_CODE_MASK 0x0f /* Return result */ +#define PCI_BIST_START 0x40 /* 1 to start BIST, 2 secs or less */ +#define PCI_BIST_CAPABLE 0x80 /* 1 if BIST capable */ + +/* + * Base addresses specify locations in memory or I/O space. + * Decoded size can be determined by writing a value of + * 0xffffffff to the register, and reading it back. Only + * 1 bits are decoded. + */ +#define PCI_BASE_ADDRESS_0 0x10 /* 32 bits */ +#define PCI_BASE_ADDRESS_1 0x14 /* 32 bits [htype 0,1 only] */ +#define PCI_BASE_ADDRESS_2 0x18 /* 32 bits [htype 0 only] */ +#define PCI_BASE_ADDRESS_3 0x1c /* 32 bits */ +#define PCI_BASE_ADDRESS_4 0x20 /* 32 bits */ +#define PCI_BASE_ADDRESS_5 0x24 /* 32 bits */ +#define PCI_BASE_ADDRESS_SPACE 0x01 /* 0 = memory, 1 = I/O */ +#define PCI_BASE_ADDRESS_SPACE_IO 0x01 +#define PCI_BASE_ADDRESS_SPACE_MEMORY 0x00 +#define PCI_BASE_ADDRESS_MEM_TYPE_MASK 0x06 +#define PCI_BASE_ADDRESS_MEM_TYPE_32 0x00 /* 32 bit address */ +#define PCI_BASE_ADDRESS_MEM_TYPE_1M 0x02 /* Below 1M [obsolete] */ +#define PCI_BASE_ADDRESS_MEM_TYPE_64 0x04 /* 64 bit address */ +#define PCI_BASE_ADDRESS_MEM_PREFETCH 0x08 /* prefetchable? */ +#define PCI_BASE_ADDRESS_MEM_MASK (~0x0fUL) +#define PCI_BASE_ADDRESS_IO_MASK (~0x03UL) +/* bit 1 is reserved if address_space = 1 */ + +/* Header type 0 (normal devices) */ +#define PCI_CARDBUS_CIS 0x28 +#define PCI_SUBSYSTEM_VENDOR_ID 0x2c +#define PCI_SUBSYSTEM_ID 0x2e +#define PCI_ROM_ADDRESS 0x30 /* Bits 31..11 are address, 10..1 reserved */ +#define PCI_ROM_ADDRESS_ENABLE 0x01 +#define PCI_ROM_ADDRESS_MASK (~0x7ffUL) + +#define PCI_CAPABILITY_LIST 0x34 /* Offset of first capability list entry */ + +/* 0x35-0x3b are reserved */ +#define PCI_INTERRUPT_LINE 0x3c /* 8 bits */ +#define PCI_INTERRUPT_PIN 0x3d /* 8 bits */ +#define PCI_MIN_GNT 0x3e /* 8 bits */ +#define PCI_MAX_LAT 0x3f /* 8 bits */ + +/* Header type 1 (PCI-to-PCI bridges) */ +#define PCI_PRIMARY_BUS 0x18 /* Primary bus number */ +#define PCI_SECONDARY_BUS 0x19 /* Secondary bus number */ +#define PCI_SUBORDINATE_BUS 0x1a /* Highest bus number behind the bridge */ +#define PCI_SEC_LATENCY_TIMER 0x1b /* Latency timer for secondary interface */ +#define PCI_IO_BASE 0x1c /* I/O range behind the bridge */ +#define PCI_IO_LIMIT 0x1d +#define PCI_IO_RANGE_TYPE_MASK 0x0fUL /* I/O bridging type */ +#define PCI_IO_RANGE_TYPE_16 0x00 +#define PCI_IO_RANGE_TYPE_32 0x01 +#define PCI_IO_RANGE_MASK (~0x0fUL) +#define PCI_SEC_STATUS 0x1e /* Secondary status register, only bit 14 used */ +#define PCI_MEMORY_BASE 0x20 /* Memory range behind */ +#define PCI_MEMORY_LIMIT 0x22 +#define PCI_MEMORY_RANGE_TYPE_MASK 0x0fUL +#define PCI_MEMORY_RANGE_MASK (~0x0fUL) +#define PCI_PREF_MEMORY_BASE 0x24 /* Prefetchable memory range behind */ +#define PCI_PREF_MEMORY_LIMIT 0x26 +#define PCI_PREF_RANGE_TYPE_MASK 0x0fUL +#define PCI_PREF_RANGE_TYPE_32 0x00 +#define PCI_PREF_RANGE_TYPE_64 0x01 +#define PCI_PREF_RANGE_MASK (~0x0fUL) +#define PCI_PREF_BASE_UPPER32 0x28 /* Upper half of prefetchable memory range */ +#define PCI_PREF_LIMIT_UPPER32 0x2c +#define PCI_IO_BASE_UPPER16 0x30 /* Upper half of I/O addresses */ +#define PCI_IO_LIMIT_UPPER16 0x32 +/* 0x34 same as for htype 0 */ +/* 0x35-0x3b is reserved */ +#define PCI_ROM_ADDRESS1 0x38 /* Same as PCI_ROM_ADDRESS, but for htype 1 */ +/* 0x3c-0x3d are same as for htype 0 */ +#define PCI_BRIDGE_CONTROL 0x3e +#define PCI_BRIDGE_CTL_PARITY 0x01 /* Enable parity detection on secondary interface */ +#define PCI_BRIDGE_CTL_SERR 0x02 /* The same for SERR forwarding */ +#define PCI_BRIDGE_CTL_ISA 0x04 /* Enable ISA mode */ +#define PCI_BRIDGE_CTL_VGA 0x08 /* Forward VGA addresses */ +#define PCI_BRIDGE_CTL_MASTER_ABORT 0x20 /* Report master aborts */ +#define PCI_BRIDGE_CTL_BUS_RESET 0x40 /* Secondary bus reset */ +#define PCI_BRIDGE_CTL_FAST_BACK 0x80 /* Fast Back2Back enabled on secondary interface */ + +/* Header type 2 (CardBus bridges) */ +#define PCI_CB_CAPABILITY_LIST 0x14 +/* 0x15 reserved */ +#define PCI_CB_SEC_STATUS 0x16 /* Secondary status */ +#define PCI_CB_PRIMARY_BUS 0x18 /* PCI bus number */ +#define PCI_CB_CARD_BUS 0x19 /* CardBus bus number */ +#define PCI_CB_SUBORDINATE_BUS 0x1a /* Subordinate bus number */ +#define PCI_CB_LATENCY_TIMER 0x1b /* CardBus latency timer */ +#define PCI_CB_MEMORY_BASE_0 0x1c +#define PCI_CB_MEMORY_LIMIT_0 0x20 +#define PCI_CB_MEMORY_BASE_1 0x24 +#define PCI_CB_MEMORY_LIMIT_1 0x28 +#define PCI_CB_IO_BASE_0 0x2c +#define PCI_CB_IO_BASE_0_HI 0x2e +#define PCI_CB_IO_LIMIT_0 0x30 +#define PCI_CB_IO_LIMIT_0_HI 0x32 +#define PCI_CB_IO_BASE_1 0x34 +#define PCI_CB_IO_BASE_1_HI 0x36 +#define PCI_CB_IO_LIMIT_1 0x38 +#define PCI_CB_IO_LIMIT_1_HI 0x3a +#define PCI_CB_IO_RANGE_MASK (~0x03UL) +/* 0x3c-0x3d are same as for htype 0 */ +#define PCI_CB_BRIDGE_CONTROL 0x3e +#define PCI_CB_BRIDGE_CTL_PARITY 0x01 /* Similar to standard bridge control register */ +#define PCI_CB_BRIDGE_CTL_SERR 0x02 +#define PCI_CB_BRIDGE_CTL_ISA 0x04 +#define PCI_CB_BRIDGE_CTL_VGA 0x08 +#define PCI_CB_BRIDGE_CTL_MASTER_ABORT 0x20 +#define PCI_CB_BRIDGE_CTL_CB_RESET 0x40 /* CardBus reset */ +#define PCI_CB_BRIDGE_CTL_16BIT_INT 0x80 /* Enable interrupt for 16-bit cards */ +#define PCI_CB_BRIDGE_CTL_PREFETCH_MEM0 0x100 /* Prefetch enable for both memory regions */ +#define PCI_CB_BRIDGE_CTL_PREFETCH_MEM1 0x200 +#define PCI_CB_BRIDGE_CTL_POST_WRITES 0x400 +#define PCI_CB_SUBSYSTEM_VENDOR_ID 0x40 +#define PCI_CB_SUBSYSTEM_ID 0x42 +#define PCI_CB_LEGACY_MODE_BASE 0x44 /* 16-bit PC Card legacy mode base address (ExCa) */ +/* 0x48-0x7f reserved */ + +/* Capability lists */ + +#define PCI_CAP_LIST_ID 0 /* Capability ID */ +#define PCI_CAP_ID_PM 0x01 /* Power Management */ +#define PCI_CAP_ID_AGP 0x02 /* Accelerated Graphics Port */ +#define PCI_CAP_ID_VPD 0x03 /* Vital Product Data */ +#define PCI_CAP_ID_SLOTID 0x04 /* Slot Identification */ +#define PCI_CAP_ID_MSI 0x05 /* Message Signalled Interrupts */ +#define PCI_CAP_ID_CHSWP 0x06 /* CompactPCI HotSwap */ +#define PCI_CAP_ID_PCIX 0x07 /* PCI-X */ +#define PCI_CAP_ID_HT 0x08 /* HyperTransport */ +#define PCI_CAP_ID_VNDR 0x09 /* Vendor specific */ +#define PCI_CAP_ID_DBG 0x0A /* Debug port */ +#define PCI_CAP_ID_CCRC 0x0B /* CompactPCI Central Resource Control */ +#define PCI_CAP_ID_SHPC 0x0C /* PCI Standard Hot-Plug Controller */ +#define PCI_CAP_ID_SSVID 0x0D /* Bridge subsystem vendor/device ID */ +#define PCI_CAP_ID_AGP3 0x0E /* AGP Target PCI-PCI bridge */ +#define PCI_CAP_ID_EXP 0x10 /* PCI Express */ +#define PCI_CAP_ID_MSIX 0x11 /* MSI-X */ +#define PCI_CAP_ID_AF 0x13 /* PCI Advanced Features */ +#define PCI_CAP_LIST_NEXT 1 /* Next capability in the list */ +#define PCI_CAP_FLAGS 2 /* Capability defined flags (16 bits) */ +#define PCI_CAP_SIZEOF 4 + +/* Power Management Registers */ + +#define PCI_PM_PMC 2 /* PM Capabilities Register */ +#define PCI_PM_CAP_VER_MASK 0x0007 /* Version */ +#define PCI_PM_CAP_PME_CLOCK 0x0008 /* PME clock required */ +#define PCI_PM_CAP_RESERVED 0x0010 /* Reserved field */ +#define PCI_PM_CAP_DSI 0x0020 /* Device specific initialization */ +#define PCI_PM_CAP_AUX_POWER 0x01C0 /* Auxilliary power support mask */ +#define PCI_PM_CAP_D1 0x0200 /* D1 power state support */ +#define PCI_PM_CAP_D2 0x0400 /* D2 power state support */ +#define PCI_PM_CAP_PME 0x0800 /* PME pin supported */ +#define PCI_PM_CAP_PME_MASK 0xF800 /* PME Mask of all supported states */ +#define PCI_PM_CAP_PME_D0 0x0800 /* PME# from D0 */ +#define PCI_PM_CAP_PME_D1 0x1000 /* PME# from D1 */ +#define PCI_PM_CAP_PME_D2 0x2000 /* PME# from D2 */ +#define PCI_PM_CAP_PME_D3 0x4000 /* PME# from D3 (hot) */ +#define PCI_PM_CAP_PME_D3cold 0x8000 /* PME# from D3 (cold) */ +#define PCI_PM_CAP_PME_SHIFT 11 /* Start of the PME Mask in PMC */ +#define PCI_PM_CTRL 4 /* PM control and status register */ +#define PCI_PM_CTRL_STATE_MASK 0x0003 /* Current power state (D0 to D3) */ +#define PCI_PM_CTRL_NO_SOFT_RESET 0x0008 /* No reset for D3hot->D0 */ +#define PCI_PM_CTRL_PME_ENABLE 0x0100 /* PME pin enable */ +#define PCI_PM_CTRL_DATA_SEL_MASK 0x1e00 /* Data select (??) */ +#define PCI_PM_CTRL_DATA_SCALE_MASK 0x6000 /* Data scale (??) */ +#define PCI_PM_CTRL_PME_STATUS 0x8000 /* PME pin status */ +#define PCI_PM_PPB_EXTENSIONS 6 /* PPB support extensions (??) */ +#define PCI_PM_PPB_B2_B3 0x40 /* Stop clock when in D3hot (??) */ +#define PCI_PM_BPCC_ENABLE 0x80 /* Bus power/clock control enable (??) */ +#define PCI_PM_DATA_REGISTER 7 /* (??) */ +#define PCI_PM_SIZEOF 8 + +/* AGP registers */ + +#define PCI_AGP_VERSION 2 /* BCD version number */ +#define PCI_AGP_RFU 3 /* Rest of capability flags */ +#define PCI_AGP_STATUS 4 /* Status register */ +#define PCI_AGP_STATUS_RQ_MASK 0xff000000 /* Maximum number of requests - 1 */ +#define PCI_AGP_STATUS_SBA 0x0200 /* Sideband addressing supported */ +#define PCI_AGP_STATUS_64BIT 0x0020 /* 64-bit addressing supported */ +#define PCI_AGP_STATUS_FW 0x0010 /* FW transfers supported */ +#define PCI_AGP_STATUS_RATE4 0x0004 /* 4x transfer rate supported */ +#define PCI_AGP_STATUS_RATE2 0x0002 /* 2x transfer rate supported */ +#define PCI_AGP_STATUS_RATE1 0x0001 /* 1x transfer rate supported */ +#define PCI_AGP_COMMAND 8 /* Control register */ +#define PCI_AGP_COMMAND_RQ_MASK 0xff000000 /* Master: Maximum number of requests */ +#define PCI_AGP_COMMAND_SBA 0x0200 /* Sideband addressing enabled */ +#define PCI_AGP_COMMAND_AGP 0x0100 /* Allow processing of AGP transactions */ +#define PCI_AGP_COMMAND_64BIT 0x0020 /* Allow processing of 64-bit addresses */ +#define PCI_AGP_COMMAND_FW 0x0010 /* Force FW transfers */ +#define PCI_AGP_COMMAND_RATE4 0x0004 /* Use 4x rate */ +#define PCI_AGP_COMMAND_RATE2 0x0002 /* Use 2x rate */ +#define PCI_AGP_COMMAND_RATE1 0x0001 /* Use 1x rate */ +#define PCI_AGP_SIZEOF 12 + +/* Vital Product Data */ + +#define PCI_VPD_ADDR 2 /* Address to access (15 bits!) */ +#define PCI_VPD_ADDR_MASK 0x7fff /* Address mask */ +#define PCI_VPD_ADDR_F 0x8000 /* Write 0, 1 indicates completion */ +#define PCI_VPD_DATA 4 /* 32-bits of data returned here */ + +/* Slot Identification */ + +#define PCI_SID_ESR 2 /* Expansion Slot Register */ +#define PCI_SID_ESR_NSLOTS 0x1f /* Number of expansion slots available */ +#define PCI_SID_ESR_FIC 0x20 /* First In Chassis Flag */ +#define PCI_SID_CHASSIS_NR 3 /* Chassis Number */ + +/* Message Signalled Interrupts registers */ + +#define PCI_MSI_FLAGS 2 /* Various flags */ +#define PCI_MSI_FLAGS_64BIT 0x80 /* 64-bit addresses allowed */ +#define PCI_MSI_FLAGS_QSIZE 0x70 /* Message queue size configured */ +#define PCI_MSI_FLAGS_QMASK 0x0e /* Maximum queue size available */ +#define PCI_MSI_FLAGS_ENABLE 0x01 /* MSI feature enabled */ +#define PCI_MSI_FLAGS_MASKBIT 0x100 /* 64-bit mask bits allowed */ +#define PCI_MSI_RFU 3 /* Rest of capability flags */ +#define PCI_MSI_ADDRESS_LO 4 /* Lower 32 bits */ +#define PCI_MSI_ADDRESS_HI 8 /* Upper 32 bits (if PCI_MSI_FLAGS_64BIT set) */ +#define PCI_MSI_DATA_32 8 /* 16 bits of data for 32-bit devices */ +#define PCI_MSI_MASK_32 12 /* Mask bits register for 32-bit devices */ +#define PCI_MSI_DATA_64 12 /* 16 bits of data for 64-bit devices */ +#define PCI_MSI_MASK_64 16 /* Mask bits register for 64-bit devices */ + +/* MSI-X registers (these are at offset PCI_MSIX_FLAGS) */ +#define PCI_MSIX_FLAGS 2 +#define PCI_MSIX_FLAGS_QSIZE 0x7FF +#define PCI_MSIX_FLAGS_ENABLE (1 << 15) +#define PCI_MSIX_FLAGS_MASKALL (1 << 14) +#define PCI_MSIX_FLAGS_BIRMASK (7 << 0) + +/* CompactPCI Hotswap Register */ + +#define PCI_CHSWP_CSR 2 /* Control and Status Register */ +#define PCI_CHSWP_DHA 0x01 /* Device Hiding Arm */ +#define PCI_CHSWP_EIM 0x02 /* ENUM# Signal Mask */ +#define PCI_CHSWP_PIE 0x04 /* Pending Insert or Extract */ +#define PCI_CHSWP_LOO 0x08 /* LED On / Off */ +#define PCI_CHSWP_PI 0x30 /* Programming Interface */ +#define PCI_CHSWP_EXT 0x40 /* ENUM# status - extraction */ +#define PCI_CHSWP_INS 0x80 /* ENUM# status - insertion */ + +/* PCI Advanced Feature registers */ + +#define PCI_AF_LENGTH 2 +#define PCI_AF_CAP 3 +#define PCI_AF_CAP_TP 0x01 +#define PCI_AF_CAP_FLR 0x02 +#define PCI_AF_CTRL 4 +#define PCI_AF_CTRL_FLR 0x01 +#define PCI_AF_STATUS 5 +#define PCI_AF_STATUS_TP 0x01 + +/* PCI-X registers */ + +#define PCI_X_CMD 2 /* Modes & Features */ +#define PCI_X_CMD_DPERR_E 0x0001 /* Data Parity Error Recovery Enable */ +#define PCI_X_CMD_ERO 0x0002 /* Enable Relaxed Ordering */ +#define PCI_X_CMD_READ_512 0x0000 /* 512 byte maximum read byte count */ +#define PCI_X_CMD_READ_1K 0x0004 /* 1Kbyte maximum read byte count */ +#define PCI_X_CMD_READ_2K 0x0008 /* 2Kbyte maximum read byte count */ +#define PCI_X_CMD_READ_4K 0x000c /* 4Kbyte maximum read byte count */ +#define PCI_X_CMD_MAX_READ 0x000c /* Max Memory Read Byte Count */ + /* Max # of outstanding split transactions */ +#define PCI_X_CMD_SPLIT_1 0x0000 /* Max 1 */ +#define PCI_X_CMD_SPLIT_2 0x0010 /* Max 2 */ +#define PCI_X_CMD_SPLIT_3 0x0020 /* Max 3 */ +#define PCI_X_CMD_SPLIT_4 0x0030 /* Max 4 */ +#define PCI_X_CMD_SPLIT_8 0x0040 /* Max 8 */ +#define PCI_X_CMD_SPLIT_12 0x0050 /* Max 12 */ +#define PCI_X_CMD_SPLIT_16 0x0060 /* Max 16 */ +#define PCI_X_CMD_SPLIT_32 0x0070 /* Max 32 */ +#define PCI_X_CMD_MAX_SPLIT 0x0070 /* Max Outstanding Split Transactions */ +#define PCI_X_CMD_VERSION(x) (((x) >> 12) & 3) /* Version */ +#define PCI_X_STATUS 4 /* PCI-X capabilities */ +#define PCI_X_STATUS_DEVFN 0x000000ff /* A copy of devfn */ +#define PCI_X_STATUS_BUS 0x0000ff00 /* A copy of bus nr */ +#define PCI_X_STATUS_64BIT 0x00010000 /* 64-bit device */ +#define PCI_X_STATUS_133MHZ 0x00020000 /* 133 MHz capable */ +#define PCI_X_STATUS_SPL_DISC 0x00040000 /* Split Completion Discarded */ +#define PCI_X_STATUS_UNX_SPL 0x00080000 /* Unexpected Split Completion */ +#define PCI_X_STATUS_COMPLEX 0x00100000 /* Device Complexity */ +#define PCI_X_STATUS_MAX_READ 0x00600000 /* Designed Max Memory Read Count */ +#define PCI_X_STATUS_MAX_SPLIT 0x03800000 /* Designed Max Outstanding Split Transactions */ +#define PCI_X_STATUS_MAX_CUM 0x1c000000 /* Designed Max Cumulative Read Size */ +#define PCI_X_STATUS_SPL_ERR 0x20000000 /* Rcvd Split Completion Error Msg */ +#define PCI_X_STATUS_266MHZ 0x40000000 /* 266 MHz capable */ +#define PCI_X_STATUS_533MHZ 0x80000000 /* 533 MHz capable */ + +/* PCI Express capability registers */ + +#define PCI_EXP_FLAGS 2 /* Capabilities register */ +#define PCI_EXP_FLAGS_VERS 0x000f /* Capability version */ +#define PCI_EXP_FLAGS_TYPE 0x00f0 /* Device/Port type */ +#define PCI_EXP_TYPE_ENDPOINT 0x0 /* Express Endpoint */ +#define PCI_EXP_TYPE_LEG_END 0x1 /* Legacy Endpoint */ +#define PCI_EXP_TYPE_ROOT_PORT 0x4 /* Root Port */ +#define PCI_EXP_TYPE_UPSTREAM 0x5 /* Upstream Port */ +#define PCI_EXP_TYPE_DOWNSTREAM 0x6 /* Downstream Port */ +#define PCI_EXP_TYPE_PCI_BRIDGE 0x7 /* PCI/PCI-X Bridge */ +#define PCI_EXP_TYPE_RC_END 0x9 /* Root Complex Integrated Endpoint */ +#define PCI_EXP_TYPE_RC_EC 0x10 /* Root Complex Event Collector */ +#define PCI_EXP_FLAGS_SLOT 0x0100 /* Slot implemented */ +#define PCI_EXP_FLAGS_IRQ 0x3e00 /* Interrupt message number */ +#define PCI_EXP_DEVCAP 4 /* Device capabilities */ +#define PCI_EXP_DEVCAP_PAYLOAD 0x07 /* Max_Payload_Size */ +#define PCI_EXP_DEVCAP_PHANTOM 0x18 /* Phantom functions */ +#define PCI_EXP_DEVCAP_EXT_TAG 0x20 /* Extended tags */ +#define PCI_EXP_DEVCAP_L0S 0x1c0 /* L0s Acceptable Latency */ +#define PCI_EXP_DEVCAP_L1 0xe00 /* L1 Acceptable Latency */ +#define PCI_EXP_DEVCAP_ATN_BUT 0x1000 /* Attention Button Present */ +#define PCI_EXP_DEVCAP_ATN_IND 0x2000 /* Attention Indicator Present */ +#define PCI_EXP_DEVCAP_PWR_IND 0x4000 /* Power Indicator Present */ +#define PCI_EXP_DEVCAP_RBER 0x8000 /* Role-Based Error Reporting */ +#define PCI_EXP_DEVCAP_PWR_VAL 0x3fc0000 /* Slot Power Limit Value */ +#define PCI_EXP_DEVCAP_PWR_SCL 0xc000000 /* Slot Power Limit Scale */ +#define PCI_EXP_DEVCAP_FLR 0x10000000 /* Function Level Reset */ +#define PCI_EXP_DEVCTL 8 /* Device Control */ +#define PCI_EXP_DEVCTL_CERE 0x0001 /* Correctable Error Reporting En. */ +#define PCI_EXP_DEVCTL_NFERE 0x0002 /* Non-Fatal Error Reporting Enable */ +#define PCI_EXP_DEVCTL_FERE 0x0004 /* Fatal Error Reporting Enable */ +#define PCI_EXP_DEVCTL_URRE 0x0008 /* Unsupported Request Reporting En. */ +#define PCI_EXP_DEVCTL_RELAX_EN 0x0010 /* Enable relaxed ordering */ +#define PCI_EXP_DEVCTL_PAYLOAD 0x00e0 /* Max_Payload_Size */ +#define PCI_EXP_DEVCTL_EXT_TAG 0x0100 /* Extended Tag Field Enable */ +#define PCI_EXP_DEVCTL_PHANTOM 0x0200 /* Phantom Functions Enable */ +#define PCI_EXP_DEVCTL_AUX_PME 0x0400 /* Auxiliary Power PM Enable */ +#define PCI_EXP_DEVCTL_NOSNOOP_EN 0x0800 /* Enable No Snoop */ +#define PCI_EXP_DEVCTL_READRQ 0x7000 /* Max_Read_Request_Size */ +#define PCI_EXP_DEVCTL_BCR_FLR 0x8000 /* Bridge Configuration Retry / FLR */ +#define PCI_EXP_DEVSTA 10 /* Device Status */ +#define PCI_EXP_DEVSTA_CED 0x01 /* Correctable Error Detected */ +#define PCI_EXP_DEVSTA_NFED 0x02 /* Non-Fatal Error Detected */ +#define PCI_EXP_DEVSTA_FED 0x04 /* Fatal Error Detected */ +#define PCI_EXP_DEVSTA_URD 0x08 /* Unsupported Request Detected */ +#define PCI_EXP_DEVSTA_AUXPD 0x10 /* AUX Power Detected */ +#define PCI_EXP_DEVSTA_TRPND 0x20 /* Transactions Pending */ +#define PCI_EXP_LNKCAP 12 /* Link Capabilities */ +#define PCI_EXP_LNKCAP_SLS 0x0000000f /* Supported Link Speeds */ +#define PCI_EXP_LNKCAP_MLW 0x000003f0 /* Maximum Link Width */ +#define PCI_EXP_LNKCAP_ASPMS 0x00000c00 /* ASPM Support */ +#define PCI_EXP_LNKCAP_L0SEL 0x00007000 /* L0s Exit Latency */ +#define PCI_EXP_LNKCAP_L1EL 0x00038000 /* L1 Exit Latency */ +#define PCI_EXP_LNKCAP_CLKPM 0x00040000 /* L1 Clock Power Management */ +#define PCI_EXP_LNKCAP_SDERC 0x00080000 /* Suprise Down Error Reporting Capable */ +#define PCI_EXP_LNKCAP_DLLLARC 0x00100000 /* Data Link Layer Link Active Reporting Capable */ +#define PCI_EXP_LNKCAP_LBNC 0x00200000 /* Link Bandwidth Notification Capability */ +#define PCI_EXP_LNKCAP_PN 0xff000000 /* Port Number */ +#define PCI_EXP_LNKCTL 16 /* Link Control */ +#define PCI_EXP_LNKCTL_ASPMC 0x0003 /* ASPM Control */ +#define PCI_EXP_LNKCTL_RCB 0x0008 /* Read Completion Boundary */ +#define PCI_EXP_LNKCTL_LD 0x0010 /* Link Disable */ +#define PCI_EXP_LNKCTL_RL 0x0020 /* Retrain Link */ +#define PCI_EXP_LNKCTL_CCC 0x0040 /* Common Clock Configuration */ +#define PCI_EXP_LNKCTL_ES 0x0080 /* Extended Synch */ +#define PCI_EXP_LNKCTL_CLKREQ_EN 0x100 /* Enable clkreq */ +#define PCI_EXP_LNKCTL_HAWD 0x0200 /* Hardware Autonomous Width Disable */ +#define PCI_EXP_LNKCTL_LBMIE 0x0400 /* Link Bandwidth Management Interrupt Enable */ +#define PCI_EXP_LNKCTL_LABIE 0x0800 /* Lnk Autonomous Bandwidth Interrupt Enable */ +#define PCI_EXP_LNKSTA 18 /* Link Status */ +#define PCI_EXP_LNKSTA_CLS 0x000f /* Current Link Speed */ +#define PCI_EXP_LNKSTA_NLW 0x03f0 /* Nogotiated Link Width */ +#define PCI_EXP_LNKSTA_LT 0x0800 /* Link Training */ +#define PCI_EXP_LNKSTA_SLC 0x1000 /* Slot Clock Configuration */ +#define PCI_EXP_LNKSTA_DLLLA 0x2000 /* Data Link Layer Link Active */ +#define PCI_EXP_LNKSTA_LBMS 0x4000 /* Link Bandwidth Management Status */ +#define PCI_EXP_LNKSTA_LABS 0x8000 /* Link Autonomous Bandwidth Status */ +#define PCI_EXP_SLTCAP 20 /* Slot Capabilities */ +#define PCI_EXP_SLTCAP_ABP 0x00000001 /* Attention Button Present */ +#define PCI_EXP_SLTCAP_PCP 0x00000002 /* Power Controller Present */ +#define PCI_EXP_SLTCAP_MRLSP 0x00000004 /* MRL Sensor Present */ +#define PCI_EXP_SLTCAP_AIP 0x00000008 /* Attention Indicator Present */ +#define PCI_EXP_SLTCAP_PIP 0x00000010 /* Power Indicator Present */ +#define PCI_EXP_SLTCAP_HPS 0x00000020 /* Hot-Plug Surprise */ +#define PCI_EXP_SLTCAP_HPC 0x00000040 /* Hot-Plug Capable */ +#define PCI_EXP_SLTCAP_SPLV 0x00007f80 /* Slot Power Limit Value */ +#define PCI_EXP_SLTCAP_SPLS 0x00018000 /* Slot Power Limit Scale */ +#define PCI_EXP_SLTCAP_EIP 0x00020000 /* Electromechanical Interlock Present */ +#define PCI_EXP_SLTCAP_NCCS 0x00040000 /* No Command Completed Support */ +#define PCI_EXP_SLTCAP_PSN 0xfff80000 /* Physical Slot Number */ +#define PCI_EXP_SLTCTL 24 /* Slot Control */ +#define PCI_EXP_SLTCTL_ABPE 0x0001 /* Attention Button Pressed Enable */ +#define PCI_EXP_SLTCTL_PFDE 0x0002 /* Power Fault Detected Enable */ +#define PCI_EXP_SLTCTL_MRLSCE 0x0004 /* MRL Sensor Changed Enable */ +#define PCI_EXP_SLTCTL_PDCE 0x0008 /* Presence Detect Changed Enable */ +#define PCI_EXP_SLTCTL_CCIE 0x0010 /* Command Completed Interrupt Enable */ +#define PCI_EXP_SLTCTL_HPIE 0x0020 /* Hot-Plug Interrupt Enable */ +#define PCI_EXP_SLTCTL_AIC 0x00c0 /* Attention Indicator Control */ +#define PCI_EXP_SLTCTL_PIC 0x0300 /* Power Indicator Control */ +#define PCI_EXP_SLTCTL_PCC 0x0400 /* Power Controller Control */ +#define PCI_EXP_SLTCTL_EIC 0x0800 /* Electromechanical Interlock Control */ +#define PCI_EXP_SLTCTL_DLLSCE 0x1000 /* Data Link Layer State Changed Enable */ +#define PCI_EXP_SLTSTA 26 /* Slot Status */ +#define PCI_EXP_SLTSTA_ABP 0x0001 /* Attention Button Pressed */ +#define PCI_EXP_SLTSTA_PFD 0x0002 /* Power Fault Detected */ +#define PCI_EXP_SLTSTA_MRLSC 0x0004 /* MRL Sensor Changed */ +#define PCI_EXP_SLTSTA_PDC 0x0008 /* Presence Detect Changed */ +#define PCI_EXP_SLTSTA_CC 0x0010 /* Command Completed */ +#define PCI_EXP_SLTSTA_MRLSS 0x0020 /* MRL Sensor State */ +#define PCI_EXP_SLTSTA_PDS 0x0040 /* Presence Detect State */ +#define PCI_EXP_SLTSTA_EIS 0x0080 /* Electromechanical Interlock Status */ +#define PCI_EXP_SLTSTA_DLLSC 0x0100 /* Data Link Layer State Changed */ +#define PCI_EXP_RTCTL 28 /* Root Control */ +#define PCI_EXP_RTCTL_SECEE 0x01 /* System Error on Correctable Error */ +#define PCI_EXP_RTCTL_SENFEE 0x02 /* System Error on Non-Fatal Error */ +#define PCI_EXP_RTCTL_SEFEE 0x04 /* System Error on Fatal Error */ +#define PCI_EXP_RTCTL_PMEIE 0x08 /* PME Interrupt Enable */ +#define PCI_EXP_RTCTL_CRSSVE 0x10 /* CRS Software Visibility Enable */ +#define PCI_EXP_RTCAP 30 /* Root Capabilities */ +#define PCI_EXP_RTSTA 32 /* Root Status */ +#define PCI_EXP_DEVCAP2 36 /* Device Capabilities 2 */ +#define PCI_EXP_DEVCAP2_ARI 0x20 /* Alternative Routing-ID */ +#define PCI_EXP_DEVCTL2 40 /* Device Control 2 */ +#define PCI_EXP_DEVCTL2_ARI 0x20 /* Alternative Routing-ID */ +#define PCI_EXP_LNKCTL2 48 /* Link Control 2 */ +#define PCI_EXP_SLTCTL2 56 /* Slot Control 2 */ + +/* Extended Capabilities (PCI-X 2.0 and Express) */ +#define PCI_EXT_CAP_ID(header) (header & 0x0000ffff) +#define PCI_EXT_CAP_VER(header) ((header >> 16) & 0xf) +#define PCI_EXT_CAP_NEXT(header) ((header >> 20) & 0xffc) + +#define PCI_EXT_CAP_ID_ERR 1 +#define PCI_EXT_CAP_ID_VC 2 +#define PCI_EXT_CAP_ID_DSN 3 +#define PCI_EXT_CAP_ID_PWR 4 +#define PCI_EXT_CAP_ID_ARI 14 +#define PCI_EXT_CAP_ID_ATS 15 +#define PCI_EXT_CAP_ID_SRIOV 16 + +/* Advanced Error Reporting */ +#define PCI_ERR_UNCOR_STATUS 4 /* Uncorrectable Error Status */ +#define PCI_ERR_UNC_TRAIN 0x00000001 /* Training */ +#define PCI_ERR_UNC_DLP 0x00000010 /* Data Link Protocol */ +#define PCI_ERR_UNC_POISON_TLP 0x00001000 /* Poisoned TLP */ +#define PCI_ERR_UNC_FCP 0x00002000 /* Flow Control Protocol */ +#define PCI_ERR_UNC_COMP_TIME 0x00004000 /* Completion Timeout */ +#define PCI_ERR_UNC_COMP_ABORT 0x00008000 /* Completer Abort */ +#define PCI_ERR_UNC_UNX_COMP 0x00010000 /* Unexpected Completion */ +#define PCI_ERR_UNC_RX_OVER 0x00020000 /* Receiver Overflow */ +#define PCI_ERR_UNC_MALF_TLP 0x00040000 /* Malformed TLP */ +#define PCI_ERR_UNC_ECRC 0x00080000 /* ECRC Error Status */ +#define PCI_ERR_UNC_UNSUP 0x00100000 /* Unsupported Request */ +#define PCI_ERR_UNCOR_MASK 8 /* Uncorrectable Error Mask */ + /* Same bits as above */ +#define PCI_ERR_UNCOR_SEVER 12 /* Uncorrectable Error Severity */ + /* Same bits as above */ +#define PCI_ERR_COR_STATUS 16 /* Correctable Error Status */ +#define PCI_ERR_COR_RCVR 0x00000001 /* Receiver Error Status */ +#define PCI_ERR_COR_BAD_TLP 0x00000040 /* Bad TLP Status */ +#define PCI_ERR_COR_BAD_DLLP 0x00000080 /* Bad DLLP Status */ +#define PCI_ERR_COR_REP_ROLL 0x00000100 /* REPLAY_NUM Rollover */ +#define PCI_ERR_COR_REP_TIMER 0x00001000 /* Replay Timer Timeout */ +#define PCI_ERR_COR_MASK 20 /* Correctable Error Mask */ + /* Same bits as above */ +#define PCI_ERR_CAP 24 /* Advanced Error Capabilities */ +#define PCI_ERR_CAP_FEP(x) ((x) & 31) /* First Error Pointer */ +#define PCI_ERR_CAP_ECRC_GENC 0x00000020 /* ECRC Generation Capable */ +#define PCI_ERR_CAP_ECRC_GENE 0x00000040 /* ECRC Generation Enable */ +#define PCI_ERR_CAP_ECRC_CHKC 0x00000080 /* ECRC Check Capable */ +#define PCI_ERR_CAP_ECRC_CHKE 0x00000100 /* ECRC Check Enable */ +#define PCI_ERR_HEADER_LOG 28 /* Header Log Register (16 bytes) */ +#define PCI_ERR_ROOT_COMMAND 44 /* Root Error Command */ +/* Correctable Err Reporting Enable */ +#define PCI_ERR_ROOT_CMD_COR_EN 0x00000001 +/* Non-fatal Err Reporting Enable */ +#define PCI_ERR_ROOT_CMD_NONFATAL_EN 0x00000002 +/* Fatal Err Reporting Enable */ +#define PCI_ERR_ROOT_CMD_FATAL_EN 0x00000004 +#define PCI_ERR_ROOT_STATUS 48 +#define PCI_ERR_ROOT_COR_RCV 0x00000001 /* ERR_COR Received */ +/* Multi ERR_COR Received */ +#define PCI_ERR_ROOT_MULTI_COR_RCV 0x00000002 +/* ERR_FATAL/NONFATAL Recevied */ +#define PCI_ERR_ROOT_UNCOR_RCV 0x00000004 +/* Multi ERR_FATAL/NONFATAL Recevied */ +#define PCI_ERR_ROOT_MULTI_UNCOR_RCV 0x00000008 +#define PCI_ERR_ROOT_FIRST_FATAL 0x00000010 /* First Fatal */ +#define PCI_ERR_ROOT_NONFATAL_RCV 0x00000020 /* Non-Fatal Received */ +#define PCI_ERR_ROOT_FATAL_RCV 0x00000040 /* Fatal Received */ +#define PCI_ERR_ROOT_COR_SRC 52 +#define PCI_ERR_ROOT_SRC 54 + +/* Virtual Channel */ +#define PCI_VC_PORT_REG1 4 +#define PCI_VC_PORT_REG2 8 +#define PCI_VC_PORT_CTRL 12 +#define PCI_VC_PORT_STATUS 14 +#define PCI_VC_RES_CAP 16 +#define PCI_VC_RES_CTRL 20 +#define PCI_VC_RES_STATUS 26 + +/* Power Budgeting */ +#define PCI_PWR_DSR 4 /* Data Select Register */ +#define PCI_PWR_DATA 8 /* Data Register */ +#define PCI_PWR_DATA_BASE(x) ((x) & 0xff) /* Base Power */ +#define PCI_PWR_DATA_SCALE(x) (((x) >> 8) & 3) /* Data Scale */ +#define PCI_PWR_DATA_PM_SUB(x) (((x) >> 10) & 7) /* PM Sub State */ +#define PCI_PWR_DATA_PM_STATE(x) (((x) >> 13) & 3) /* PM State */ +#define PCI_PWR_DATA_TYPE(x) (((x) >> 15) & 7) /* Type */ +#define PCI_PWR_DATA_RAIL(x) (((x) >> 18) & 7) /* Power Rail */ +#define PCI_PWR_CAP 12 /* Capability */ +#define PCI_PWR_CAP_BUDGET(x) ((x) & 1) /* Included in system budget */ + +/* + * Hypertransport sub capability types + * + * Unfortunately there are both 3 bit and 5 bit capability types defined + * in the HT spec, catering for that is a little messy. You probably don't + * want to use these directly, just use pci_find_ht_capability() and it + * will do the right thing for you. + */ +#define HT_3BIT_CAP_MASK 0xE0 +#define HT_CAPTYPE_SLAVE 0x00 /* Slave/Primary link configuration */ +#define HT_CAPTYPE_HOST 0x20 /* Host/Secondary link configuration */ + +#define HT_5BIT_CAP_MASK 0xF8 +#define HT_CAPTYPE_IRQ 0x80 /* IRQ Configuration */ +#define HT_CAPTYPE_REMAPPING_40 0xA0 /* 40 bit address remapping */ +#define HT_CAPTYPE_REMAPPING_64 0xA2 /* 64 bit address remapping */ +#define HT_CAPTYPE_UNITID_CLUMP 0x90 /* Unit ID clumping */ +#define HT_CAPTYPE_EXTCONF 0x98 /* Extended Configuration Space Access */ +#define HT_CAPTYPE_MSI_MAPPING 0xA8 /* MSI Mapping Capability */ +#define HT_MSI_FLAGS 0x02 /* Offset to flags */ +#define HT_MSI_FLAGS_ENABLE 0x1 /* Mapping enable */ +#define HT_MSI_FLAGS_FIXED 0x2 /* Fixed mapping only */ +#define HT_MSI_FIXED_ADDR 0x00000000FEE00000ULL /* Fixed addr */ +#define HT_MSI_ADDR_LO 0x04 /* Offset to low addr bits */ +#define HT_MSI_ADDR_LO_MASK 0xFFF00000 /* Low address bit mask */ +#define HT_MSI_ADDR_HI 0x08 /* Offset to high addr bits */ +#define HT_CAPTYPE_DIRECT_ROUTE 0xB0 /* Direct routing configuration */ +#define HT_CAPTYPE_VCSET 0xB8 /* Virtual Channel configuration */ +#define HT_CAPTYPE_ERROR_RETRY 0xC0 /* Retry on error configuration */ +#define HT_CAPTYPE_GEN3 0xD0 /* Generation 3 hypertransport configuration */ +#define HT_CAPTYPE_PM 0xE0 /* Hypertransport powermanagement configuration */ + +/* Alternative Routing-ID Interpretation */ +#define PCI_ARI_CAP 0x04 /* ARI Capability Register */ +#define PCI_ARI_CAP_MFVC 0x0001 /* MFVC Function Groups Capability */ +#define PCI_ARI_CAP_ACS 0x0002 /* ACS Function Groups Capability */ +#define PCI_ARI_CAP_NFN(x) (((x) >> 8) & 0xff) /* Next Function Number */ +#define PCI_ARI_CTRL 0x06 /* ARI Control Register */ +#define PCI_ARI_CTRL_MFVC 0x0001 /* MFVC Function Groups Enable */ +#define PCI_ARI_CTRL_ACS 0x0002 /* ACS Function Groups Enable */ +#define PCI_ARI_CTRL_FG(x) (((x) >> 4) & 7) /* Function Group */ + +/* Address Translation Service */ +#define PCI_ATS_CAP 0x04 /* ATS Capability Register */ +#define PCI_ATS_CAP_QDEP(x) ((x) & 0x1f) /* Invalidate Queue Depth */ +#define PCI_ATS_MAX_QDEP 32 /* Max Invalidate Queue Depth */ +#define PCI_ATS_CTRL 0x06 /* ATS Control Register */ +#define PCI_ATS_CTRL_ENABLE 0x8000 /* ATS Enable */ +#define PCI_ATS_CTRL_STU(x) ((x) & 0x1f) /* Smallest Translation Unit */ +#define PCI_ATS_MIN_STU 12 /* shift of minimum STU block */ + +/* Single Root I/O Virtualization */ +#define PCI_SRIOV_CAP 0x04 /* SR-IOV Capabilities */ +#define PCI_SRIOV_CAP_VFM 0x01 /* VF Migration Capable */ +#define PCI_SRIOV_CAP_INTR(x) ((x) >> 21) /* Interrupt Message Number */ +#define PCI_SRIOV_CTRL 0x08 /* SR-IOV Control */ +#define PCI_SRIOV_CTRL_VFE 0x01 /* VF Enable */ +#define PCI_SRIOV_CTRL_VFM 0x02 /* VF Migration Enable */ +#define PCI_SRIOV_CTRL_INTR 0x04 /* VF Migration Interrupt Enable */ +#define PCI_SRIOV_CTRL_MSE 0x08 /* VF Memory Space Enable */ +#define PCI_SRIOV_CTRL_ARI 0x10 /* ARI Capable Hierarchy */ +#define PCI_SRIOV_STATUS 0x0a /* SR-IOV Status */ +#define PCI_SRIOV_STATUS_VFM 0x01 /* VF Migration Status */ +#define PCI_SRIOV_INITIAL_VF 0x0c /* Initial VFs */ +#define PCI_SRIOV_TOTAL_VF 0x0e /* Total VFs */ +#define PCI_SRIOV_NUM_VF 0x10 /* Number of VFs */ +#define PCI_SRIOV_FUNC_LINK 0x12 /* Function Dependency Link */ +#define PCI_SRIOV_VF_OFFSET 0x14 /* First VF Offset */ +#define PCI_SRIOV_VF_STRIDE 0x16 /* Following VF Stride */ +#define PCI_SRIOV_VF_DID 0x1a /* VF Device ID */ +#define PCI_SRIOV_SUP_PGSIZE 0x1c /* Supported Page Sizes */ +#define PCI_SRIOV_SYS_PGSIZE 0x20 /* System Page Size */ +#define PCI_SRIOV_BAR 0x24 /* VF BAR0 */ +#define PCI_SRIOV_NUM_BARS 6 /* Number of VF BARs */ +#define PCI_SRIOV_VFM 0x3c /* VF Migration State Array Offset*/ +#define PCI_SRIOV_VFM_BIR(x) ((x) & 7) /* State BIR */ +#define PCI_SRIOV_VFM_OFFSET(x) ((x) & ~7) /* State Offset */ +#define PCI_SRIOV_VFM_UA 0x0 /* Inactive.Unavailable */ +#define PCI_SRIOV_VFM_MI 0x1 /* Dormant.MigrateIn */ +#define PCI_SRIOV_VFM_MO 0x2 /* Active.MigrateOut */ +#define PCI_SRIOV_VFM_AV 0x3 /* Active.Available */ + +#endif /* LINUX_PCI_REGS_H */ diff --git a/hw/pcie.c b/hw/pcie.c new file mode 100644 index 0000000..6a113a9 --- /dev/null +++ b/hw/pcie.c @@ -0,0 +1,544 @@ +/* + * pcie.c + * + * Copyright (c) 2010 Isaku Yamahata + * 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 . + */ + +#include "sysemu.h" +#include "range.h" +#include "pci_bridge.h" +#include "pcie.h" +#include "msix.h" +#include "msi.h" +#include "pci_internals.h" +#include "pcie_regs.h" +#include "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_set_long(exp_cap + PCI_EXP_DEVCAP2, + PCI_EXP_DEVCAP2_EFF | PCI_EXP_DEVCAP2_EETLPP); + + pci_set_word(dev->wmask + pos, PCI_EXP_DEVCTL2_EETLPPB); + return pos; +} + +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_URD | 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 { + qemu_set_irq(dev->irq[dev->exp.hpev_intx], dev->exp.hpev_notified); + } +} + +/* + * A PCI Express Hot-Plug Event has occured, 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 int pcie_cap_slot_hotplug(DeviceState *qdev, + PCIDevice *pci_dev, PCIHotplugState state) +{ + PCIDevice *d = DO_UPCAST(PCIDevice, qdev, qdev); + uint8_t *exp_cap = d->config + d->exp.exp_cap; + uint16_t sltsta = pci_get_word(exp_cap + PCI_EXP_SLTSTA); + + /* 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 (state == PCI_COLDPLUG_ENABLED) { + pci_word_test_and_set_mask(exp_cap + PCI_EXP_SLTSTA, + PCI_EXP_SLTSTA_PDS); + return 0; + } + + PCIE_DEV_PRINTF(pci_dev, "hotplug state: %d\n", state); + if (sltsta & PCI_EXP_SLTSTA_EIS) { + /* the slot is electromechanically locked. + * This error is propagated up to qdev and then to HMP/QMP. + */ + return -EBUSY; + } + + /* 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); + + if (state == PCI_HOTPLUG_ENABLED) { + pci_word_test_and_set_mask(exp_cap + PCI_EXP_SLTSTA, + PCI_EXP_SLTSTA_PDS); + pcie_cap_slot_event(d, PCI_EXP_HP_EV_PDC); + } else { + qdev_free(&pci_dev->qdev); + pci_word_test_and_clear_mask(exp_cap + PCI_EXP_SLTSTA, + PCI_EXP_SLTSTA_PDS); + pcie_cap_slot_event(d, PCI_EXP_HP_EV_PDC); + } + return 0; +} + +/* 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); + + 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; + + pci_bus_hotplug(pci_bridge_get_sec_bus(DO_UPCAST(PCIBridge, dev, dev)), + pcie_cap_slot_hotplug, &dev->qdev); +} + +void pcie_cap_slot_reset(PCIDevice *dev) +{ + uint8_t *exp_cap = dev->config + dev->exp.exp_cap; + + 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_PIC_OFF | + PCI_EXP_SLTCTL_AIC_OFF); + + 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); +} + +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_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); + } + + 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) */ +/* ari forwarding support for down stream port */ +void pcie_cap_ari_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_ari_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_ari_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 allocation 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) +{ + uint16_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, PCI_ARI_CAP_NFN(nextfn)); +} diff --git a/hw/pcie.h b/hw/pcie.h new file mode 100644 index 0000000..bc909e2 --- /dev/null +++ b/hw/pcie.h @@ -0,0 +1,132 @@ +/* + * pcie.h + * + * Copyright (c) 2010 Isaku Yamahata + * 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 . + */ + +#ifndef QEMU_PCIE_H +#define QEMU_PCIE_H + +#include "hw.h" +#include "pci_regs.h" +#include "pcie_regs.h" +#include "pcie_aer.h" + +typedef enum { + /* for attention and power indicator */ + PCI_EXP_HP_IND_RESERVED = PCI_EXP_SLTCTL_IND_RESERVED, + PCI_EXP_HP_IND_ON = PCI_EXP_SLTCTL_IND_ON, + PCI_EXP_HP_IND_BLINK = PCI_EXP_SLTCTL_IND_BLINK, + PCI_EXP_HP_IND_OFF = PCI_EXP_SLTCTL_IND_OFF, +} PCIExpressIndicator; + +typedef enum { + /* these bits must match the bits in Slot Control/Status registers. + * PCI_EXP_HP_EV_xxx = PCI_EXP_SLTCTL_xxxE = PCI_EXP_SLTSTA_xxx + * + * Not all the bits of slot control register match with the ones of + * slot status. Not some bits of slot status register is used to + * show status, not to report event occurence. + * So such bits must be masked out when checking the software + * notification condition. + */ + PCI_EXP_HP_EV_ABP = PCI_EXP_SLTCTL_ABPE, + /* attention button pressed */ + PCI_EXP_HP_EV_PDC = PCI_EXP_SLTCTL_PDCE, + /* presence detect changed */ + PCI_EXP_HP_EV_CCI = PCI_EXP_SLTCTL_CCIE, + /* command completed */ + + PCI_EXP_HP_EV_SUPPORTED = PCI_EXP_HP_EV_ABP | + PCI_EXP_HP_EV_PDC | + PCI_EXP_HP_EV_CCI, + /* supported event mask */ + + /* events not listed aren't supported */ +} PCIExpressHotPlugEvent; + +struct PCIExpressDevice { + /* Offset of express capability in config space */ + uint8_t exp_cap; + + /* SLOT */ + unsigned int hpev_intx; /* INTx for hot plug event (0-3:INT[A-D]#) + * default is 0 = INTA# + * If the chip wants to use other interrupt + * line, initialize this member with the + * desired number. + * If the chip dynamically changes this member, + * also initialize it when loaded as + * appropreately. + */ + bool hpev_notified; /* Logical AND of conditions for hot plug event. + Following 6.7.3.4: + Software Notification of Hot-Plug Events, an interrupt + is sent whenever the logical and of these conditions + transitions from false to true. */ + + /* AER */ + uint16_t aer_cap; + PCIEAERLog aer_log; + unsigned int aer_intx; /* INTx for error reporting + * default is 0 = INTA# + * If the chip wants to use other interrupt + * line, initialize this member with the + * desired number. + * If the chip dynamically changes this member, + * also initialize it when loaded as + * appropreately. + */ +}; + +/* PCI express capability helper functions */ +int pcie_cap_init(PCIDevice *dev, uint8_t offset, uint8_t type, uint8_t port); +void pcie_cap_exit(PCIDevice *dev); +uint8_t pcie_cap_get_type(const PCIDevice *dev); +void pcie_cap_flags_set_vector(PCIDevice *dev, uint8_t vector); +uint8_t pcie_cap_flags_get_vector(PCIDevice *dev); + +void pcie_cap_deverr_init(PCIDevice *dev); +void pcie_cap_deverr_reset(PCIDevice *dev); + +void pcie_cap_slot_init(PCIDevice *dev, uint16_t slot); +void pcie_cap_slot_reset(PCIDevice *dev); +void pcie_cap_slot_write_config(PCIDevice *dev, + uint32_t addr, uint32_t val, int len); +int pcie_cap_slot_post_load(void *opaque, int version_id); +void pcie_cap_slot_push_attention_button(PCIDevice *dev); + +void pcie_cap_root_init(PCIDevice *dev); +void pcie_cap_root_reset(PCIDevice *dev); + +void pcie_cap_flr_init(PCIDevice *dev); +void pcie_cap_flr_write_config(PCIDevice *dev, + uint32_t addr, uint32_t val, int len); + +void pcie_cap_ari_init(PCIDevice *dev); +void pcie_cap_ari_reset(PCIDevice *dev); +bool pcie_cap_is_ari_enabled(const PCIDevice *dev); + +/* PCI express extended capability helper functions */ +uint16_t pcie_find_capability(PCIDevice *dev, uint16_t cap_id); +void pcie_add_capability(PCIDevice *dev, + uint16_t cap_id, uint8_t cap_ver, + uint16_t offset, uint16_t size); + +void pcie_ari_init(PCIDevice *dev, uint16_t offset, uint16_t nextfn); + +#endif /* QEMU_PCIE_H */ diff --git a/hw/pcie_aer.c b/hw/pcie_aer.c new file mode 100644 index 0000000..6e653dd --- /dev/null +++ b/hw/pcie_aer.c @@ -0,0 +1,1031 @@ +/* + * pcie_aer.c + * + * Copyright (c) 2010 Isaku Yamahata + * 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 . + */ + +#include "sysemu.h" +#include "qemu-objects.h" +#include "monitor.h" +#include "pci_bridge.h" +#include "pcie.h" +#include "msix.h" +#include "msi.h" +#include "pci_internals.h" +#include "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__) + +/* 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 = qemu_mallocz(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_STATUS); + + 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) +{ + qemu_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 { + qemu_set_irq(dev->irq[dev->exp.aer_intx], 1); + } +} + +/* + * 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_COR_SRC, 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_SRC, 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 = ffs(err->status) - 1; + 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]; + cpu_to_be32wu((uint32_t*)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_DEVCTL2) & + 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]; + cpu_to_be32wu((uint32_t*)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 responsiblity to determine which function should + * report the rerror. + * + * 6.2.4 Error Logging + * 6.2.5 Sqeunce of Device Error Signaling and Logging Operations + * table 6-2: Flowchard 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); +} + +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)) { + qemu_set_irq(dev->irq[dev->exp.aer_intx], !!(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, + .minimum_version_id_old = 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() + } +}; + +#define VMSTATE_PCIE_AER_ERRS(_field, _state, _field_num, _vmsd, _type) { \ + .name = (stringify(_field)), \ + .version_id = 0, \ + .num_offset = vmstate_offset_value(_state, _field_num, uint16_t), \ + .size = sizeof(_type), \ + .vmsd = &(_vmsd), \ + .flags = VMS_POINTER | VMS_VARRAY_UINT16 | VMS_STRUCT, \ + .offset = vmstate_offset_pointer(_state, _field, _type), \ +} + +const VMStateDescription vmstate_pcie_aer_log = { + .name = "PCIE_AER_ERROR_LOG", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT16(log_num, PCIEAERLog), + VMSTATE_UINT16(log_max, PCIEAERLog), + VMSTATE_PCIE_AER_ERRS(log, PCIEAERLog, log_num, + vmstate_pcie_aer_err, PCIEAERErr), + VMSTATE_END_OF_LIST() + } +}; + +void pcie_aer_inject_error_print(Monitor *mon, const QObject *data) +{ + QDict *qdict; + int devfn; + assert(qobject_type(data) == QTYPE_QDICT); + qdict = qobject_to_qdict(data); + + devfn = (int)qdict_get_int(qdict, "devfn"); + monitor_printf(mon, "OK id: %s domain: %x, bus: %x devfn: %x.%x\n", + qdict_get_str(qdict, "id"), + (int) qdict_get_int(qdict, "domain"), + (int) qdict_get_int(qdict, "bus"), + PCI_SLOT(devfn), PCI_FUNC(devfn)); +} + +typedef struct PCIEAERErrorName { + const char *name; + uint32_t val; + bool correctable; +} PCIEAERErrorName; + +/* + * AER error name -> value convertion 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; +} + +int do_pcie_aer_inejct_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_int(qdict, "correctable"); + if (!e || *e != '\0') { + monitor_printf(mon, "invalid error status value. \"%s\"", + error_name); + return -EINVAL; + } + } + 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_int(qdict, "advisory_non_fatal")) { + 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, " + "'domain': %d, 'bus': %d, 'devfn': %d, " + "'ret': %d}", + id, + pci_find_domain(dev->bus), + pci_bus_num(dev->bus), dev->devfn, + ret); + assert(*ret_data); + + return 0; +} diff --git a/hw/pcie_aer.h b/hw/pcie_aer.h new file mode 100644 index 0000000..7539500 --- /dev/null +++ b/hw/pcie_aer.h @@ -0,0 +1,106 @@ +/* + * pcie_aer.h + * + * Copyright (c) 2010 Isaku Yamahata + * 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 . + */ + +#ifndef QEMU_PCIE_AER_H +#define QEMU_PCIE_AER_H + +#include "hw.h" + +/* definitions which PCIExpressDevice uses */ + +/* AER log */ +struct PCIEAERLog { + /* This structure is saved/loaded. + So explicitly size them instead of unsigned int */ + + /* the number of currently recorded log in log member */ + uint16_t log_num; + + /* + * The maximum number of the log. Errors can be logged up to this. + * + * This is configurable property. + * The specified value will be clipped down to PCIE_AER_LOG_MAX_LIMIT + * to avoid unreasonable memory usage. + * I bet that 128 log size would be big enough, otherwise too many errors + * for system to function normaly. But could consecutive errors occur? + */ +#define PCIE_AER_LOG_MAX_DEFAULT 8 +#define PCIE_AER_LOG_MAX_LIMIT 128 +#define PCIE_AER_LOG_MAX_UNSET 0xffff + uint16_t log_max; + + /* Error log. log_max-sized array */ + PCIEAERErr *log; +}; + +/* aer error message: error signaling message has only error sevirity and + source id. See 2.2.8.3 error signaling messages */ +struct PCIEAERMsg { + /* + * PCI_ERR_ROOT_CMD_{COR, NONFATAL, FATAL}_EN + * = PCI_EXP_DEVCTL_{CERE, NFERE, FERE} + */ + uint32_t severity; + + uint16_t source_id; /* bdf */ +}; + +static inline bool +pcie_aer_msg_is_uncor(const PCIEAERMsg *msg) +{ + return msg->severity == PCI_ERR_ROOT_CMD_NONFATAL_EN || + msg->severity == PCI_ERR_ROOT_CMD_FATAL_EN; +} + +/* error */ +struct PCIEAERErr { + uint32_t status; /* error status bits */ + uint16_t source_id; /* bdf */ + +#define PCIE_AER_ERR_IS_CORRECTABLE 0x1 /* correctable/uncorrectable */ +#define PCIE_AER_ERR_MAYBE_ADVISORY 0x2 /* maybe advisory non-fatal */ +#define PCIE_AER_ERR_HEADER_VALID 0x4 /* TLP header is logged */ +#define PCIE_AER_ERR_TLP_PREFIX_PRESENT 0x8 /* TLP Prefix is logged */ + uint16_t flags; + + uint32_t header[4]; /* TLP header */ + uint32_t prefix[4]; /* TLP header prefix */ +}; + +extern const VMStateDescription vmstate_pcie_aer_log; + +int pcie_aer_init(PCIDevice *dev, uint16_t offset); +void pcie_aer_exit(PCIDevice *dev); +void pcie_aer_write_config(PCIDevice *dev, + uint32_t addr, uint32_t val, int len); + +/* aer root port */ +void pcie_aer_root_set_vector(PCIDevice *dev, unsigned int vector); +void pcie_aer_root_init(PCIDevice *dev); +void pcie_aer_root_reset(PCIDevice *dev); +void pcie_aer_root_write_config(PCIDevice *dev, + uint32_t addr, uint32_t val, int len, + uint32_t root_cmd_prev); + +/* error injection */ +int pcie_aer_inject_error(PCIDevice *dev, const PCIEAERErr *err); + +#endif /* QEMU_PCIE_AER_H */ diff --git a/hw/pcie_host.c b/hw/pcie_host.c new file mode 100644 index 0000000..21069ee --- /dev/null +++ b/hw/pcie_host.c @@ -0,0 +1,177 @@ +/* + * pcie_host.c + * utility functions for pci express host bridge. + * + * Copyright (c) 2009 Isaku Yamahata + * 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 . + */ + +#include "hw.h" +#include "pci.h" +#include "pcie_host.h" + +/* + * PCI express mmcfig address + * bit 20 - 28: bus number + * bit 15 - 19: device number + * bit 12 - 14: function number + * bit 0 - 11: offset in configuration space of a given device + */ +#define PCIE_MMCFG_SIZE_MAX (1ULL << 28) +#define PCIE_MMCFG_SIZE_MIN (1ULL << 20) +#define PCIE_MMCFG_BUS_BIT 20 +#define PCIE_MMCFG_BUS_MASK 0x1ff +#define PCIE_MMCFG_DEVFN_BIT 12 +#define PCIE_MMCFG_DEVFN_MASK 0xff +#define PCIE_MMCFG_CONFOFFSET_MASK 0xfff +#define PCIE_MMCFG_BUS(addr) (((addr) >> PCIE_MMCFG_BUS_BIT) & \ + PCIE_MMCFG_BUS_MASK) +#define PCIE_MMCFG_DEVFN(addr) (((addr) >> PCIE_MMCFG_DEVFN_BIT) & \ + PCIE_MMCFG_DEVFN_MASK) +#define PCIE_MMCFG_CONFOFFSET(addr) ((addr) & PCIE_MMCFG_CONFOFFSET_MASK) + + +/* 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), + PCI_SLOT(PCIE_MMCFG_DEVFN(mmcfg_addr)), + PCI_FUNC(PCIE_MMCFG_DEVFN(mmcfg_addr))); +} + +static void pcie_mmcfg_data_write(PCIBus *s, + uint32_t mmcfg_addr, uint32_t val, int len) +{ + PCIDevice *pci_dev = pcie_dev_find_by_mmcfg_addr(s, mmcfg_addr); + + if (!pci_dev) + return; + + pci_dev->config_write(pci_dev, + PCIE_MMCFG_CONFOFFSET(mmcfg_addr), val, len); +} + +static uint32_t pcie_mmcfg_data_read(PCIBus *s, uint32_t addr, int len) +{ + PCIDevice *pci_dev = pcie_dev_find_by_mmcfg_addr(s, addr); + + assert(len == 1 || len == 2 || len == 4); + if (!pci_dev) { + return ~0x0; + } + return pci_dev->config_read(pci_dev, PCIE_MMCFG_CONFOFFSET(addr), len); +} + +static void pcie_mmcfg_data_writeb(void *opaque, + target_phys_addr_t addr, uint32_t value) +{ + PCIExpressHost *e = opaque; + pcie_mmcfg_data_write(e->pci.bus, addr - e->base_addr, value, 1); +} + +static void pcie_mmcfg_data_writew(void *opaque, + target_phys_addr_t addr, uint32_t value) +{ + PCIExpressHost *e = opaque; + pcie_mmcfg_data_write(e->pci.bus, addr - e->base_addr, value, 2); +} + +static void pcie_mmcfg_data_writel(void *opaque, + target_phys_addr_t addr, uint32_t value) +{ + PCIExpressHost *e = opaque; + pcie_mmcfg_data_write(e->pci.bus, addr - e->base_addr, value, 4); +} + +static uint32_t pcie_mmcfg_data_readb(void *opaque, target_phys_addr_t addr) +{ + PCIExpressHost *e = opaque; + return pcie_mmcfg_data_read(e->pci.bus, addr - e->base_addr, 1); +} + +static uint32_t pcie_mmcfg_data_readw(void *opaque, target_phys_addr_t addr) +{ + PCIExpressHost *e = opaque; + return pcie_mmcfg_data_read(e->pci.bus, addr - e->base_addr, 2); +} + +static uint32_t pcie_mmcfg_data_readl(void *opaque, target_phys_addr_t addr) +{ + PCIExpressHost *e = opaque; + return pcie_mmcfg_data_read(e->pci.bus, addr - e->base_addr, 4); +} + + +static CPUWriteMemoryFunc * const pcie_mmcfg_write[] = +{ + pcie_mmcfg_data_writeb, + pcie_mmcfg_data_writew, + pcie_mmcfg_data_writel, +}; + +static CPUReadMemoryFunc * const pcie_mmcfg_read[] = +{ + pcie_mmcfg_data_readb, + pcie_mmcfg_data_readw, + pcie_mmcfg_data_readl, +}; + +/* pcie_host::base_addr == PCIE_BASE_ADDR_UNMAPPED when it isn't mapped. */ +#define PCIE_BASE_ADDR_UNMAPPED ((target_phys_addr_t)-1ULL) + +int pcie_host_init(PCIExpressHost *e) +{ + e->base_addr = PCIE_BASE_ADDR_UNMAPPED; + e->mmio_index = + cpu_register_io_memory(pcie_mmcfg_read, pcie_mmcfg_write, e, + DEVICE_NATIVE_ENDIAN); + if (e->mmio_index < 0) { + return -1; + } + + return 0; +} + +void pcie_host_mmcfg_unmap(PCIExpressHost *e) +{ + if (e->base_addr != PCIE_BASE_ADDR_UNMAPPED) { + cpu_register_physical_memory(e->base_addr, e->size, IO_MEM_UNASSIGNED); + e->base_addr = PCIE_BASE_ADDR_UNMAPPED; + } +} + +void pcie_host_mmcfg_map(PCIExpressHost *e, + target_phys_addr_t addr, uint32_t size) +{ + assert(!(size & (size - 1))); /* power of 2 */ + assert(size >= PCIE_MMCFG_SIZE_MIN); + assert(size <= PCIE_MMCFG_SIZE_MAX); + + e->base_addr = addr; + e->size = size; + cpu_register_physical_memory(e->base_addr, e->size, e->mmio_index); +} + +void pcie_host_mmcfg_update(PCIExpressHost *e, + int enable, + target_phys_addr_t addr, uint32_t size) +{ + pcie_host_mmcfg_unmap(e); + if (enable) { + pcie_host_mmcfg_map(e, addr, size); + } +} diff --git a/hw/pcie_host.h b/hw/pcie_host.h new file mode 100644 index 0000000..a202661 --- /dev/null +++ b/hw/pcie_host.h @@ -0,0 +1,49 @@ +/* + * pcie_host.h + * + * Copyright (c) 2009 Isaku Yamahata + * 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 . + */ + +#ifndef PCIE_HOST_H +#define PCIE_HOST_H + +#include "pci_host.h" + +struct PCIExpressHost { + PCIHostState pci; + + /* express part */ + + /* base address where MMCONFIG area is mapped. */ + target_phys_addr_t base_addr; + + /* the size of MMCONFIG area. It's host bridge dependent */ + target_phys_addr_t size; + + /* result of cpu_register_io_memory() to map MMCONFIG area */ + int mmio_index; +}; + +int pcie_host_init(PCIExpressHost *e); +void pcie_host_mmcfg_unmap(PCIExpressHost *e); +void pcie_host_mmcfg_map(PCIExpressHost *e, + target_phys_addr_t addr, uint32_t size); +void pcie_host_mmcfg_update(PCIExpressHost *e, + int enable, + target_phys_addr_t addr, uint32_t size); + +#endif /* PCIE_HOST_H */ diff --git a/hw/pcie_port.c b/hw/pcie_port.c new file mode 100644 index 0000000..340dcdb --- /dev/null +++ b/hw/pcie_port.c @@ -0,0 +1,124 @@ +/* + * pcie_port.c + * + * Copyright (c) 2010 Isaku Yamahata + * 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 . + */ + +#include "pcie_port.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, some bits are hardwared to 0. */ + pci_set_word(d->wmask + PCI_BRIDGE_CONTROL, + PCI_BRIDGE_CTL_PARITY | + PCI_BRIDGE_CTL_ISA | + PCI_BRIDGE_CTL_VGA | + PCI_BRIDGE_CTL_SERR | + PCI_BRIDGE_CTL_BUS_RESET); + + /* 7.5.3.5 Prefetchable Memory Base Limit + * The Prefetchable Memory Base and Prefetchable Memory Limit registers + * must indicate that 64-bit addresses are supported, as defined in + * PCI-to-PCI Bridge Architecture Specification, Revision 1.2. + */ + 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); +} + +/************************************************************************** + * (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 = qemu_mallocz(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); +} diff --git a/hw/pcie_port.h b/hw/pcie_port.h new file mode 100644 index 0000000..3709583 --- /dev/null +++ b/hw/pcie_port.h @@ -0,0 +1,51 @@ +/* + * pcie_port.h + * + * Copyright (c) 2010 Isaku Yamahata + * 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 . + */ + +#ifndef QEMU_PCIE_PORT_H +#define QEMU_PCIE_PORT_H + +#include "pci_bridge.h" +#include "pci_internals.h" + +struct PCIEPort { + PCIBridge br; + + /* pci express switch port */ + uint8_t port; +}; + +void pcie_port_init_reg(PCIDevice *d); + +struct PCIESlot { + PCIEPort port; + + /* pci express switch port with slot */ + uint8_t chassis; + uint16_t slot; + QLIST_ENTRY(PCIESlot) next; +}; + +void pcie_chassis_create(uint8_t chassis_number); +void pcie_main_chassis_create(void); +PCIESlot *pcie_chassis_find_slot(uint8_t chassis, uint16_t slot); +int pcie_chassis_add_slot(struct PCIESlot *slot); +void pcie_chassis_del_slot(PCIESlot *s); + +#endif /* QEMU_PCIE_PORT_H */ diff --git a/hw/pcie_regs.h b/hw/pcie_regs.h new file mode 100644 index 0000000..4d123d9 --- /dev/null +++ b/hw/pcie_regs.h @@ -0,0 +1,156 @@ +/* + * constants for pcie configurations space from pci express spec. + * + * TODO: + * Those constants and macros should go to Linux pci_regs.h + * Once they're merged, they will go away. + */ +#ifndef QEMU_PCIE_REGS_H +#define QEMU_PCIE_REGS_H + + +/* express capability */ + +#define PCI_EXP_VER2_SIZEOF 0x3c /* express capability of ver. 2 */ +#define PCI_EXT_CAP_VER_SHIFT 16 +#define PCI_EXT_CAP_NEXT_SHIFT 20 +#define PCI_EXT_CAP_NEXT_MASK (0xffc << PCI_EXT_CAP_NEXT_SHIFT) + +#define PCI_EXT_CAP(id, ver, next) \ + ((id) | \ + ((ver) << PCI_EXT_CAP_VER_SHIFT) | \ + ((next) << PCI_EXT_CAP_NEXT_SHIFT)) + +#define PCI_EXT_CAP_ALIGN 4 +#define PCI_EXT_CAP_ALIGNUP(x) \ + (((x) + PCI_EXT_CAP_ALIGN - 1) & ~(PCI_EXT_CAP_ALIGN - 1)) + +/* PCI_EXP_FLAGS */ +#define PCI_EXP_FLAGS_VER2 2 /* for now, supports only ver. 2 */ +#define PCI_EXP_FLAGS_IRQ_SHIFT (ffs(PCI_EXP_FLAGS_IRQ) - 1) +#define PCI_EXP_FLAGS_TYPE_SHIFT (ffs(PCI_EXP_FLAGS_TYPE) - 1) + + +/* PCI_EXP_LINK{CAP, STA} */ +/* link speed */ +#define PCI_EXP_LNK_LS_25 1 + +#define PCI_EXP_LNK_MLW_SHIFT (ffs(PCI_EXP_LNKCAP_MLW) - 1) +#define PCI_EXP_LNK_MLW_1 (1 << PCI_EXP_LNK_MLW_SHIFT) + +/* PCI_EXP_LINKCAP */ +#define PCI_EXP_LNKCAP_ASPMS_SHIFT (ffs(PCI_EXP_LNKCAP_ASPMS) - 1) +#define PCI_EXP_LNKCAP_ASPMS_0S (1 << PCI_EXP_LNKCAP_ASPMS_SHIFT) + +#define PCI_EXP_LNKCAP_PN_SHIFT (ffs(PCI_EXP_LNKCAP_PN) - 1) + +#define PCI_EXP_SLTCAP_PSN_SHIFT (ffs(PCI_EXP_SLTCAP_PSN) - 1) + +#define PCI_EXP_SLTCTL_IND_RESERVED 0x0 +#define PCI_EXP_SLTCTL_IND_ON 0x1 +#define PCI_EXP_SLTCTL_IND_BLINK 0x2 +#define PCI_EXP_SLTCTL_IND_OFF 0x3 +#define PCI_EXP_SLTCTL_AIC_SHIFT (ffs(PCI_EXP_SLTCTL_AIC) - 1) +#define PCI_EXP_SLTCTL_AIC_OFF \ + (PCI_EXP_SLTCTL_IND_OFF << PCI_EXP_SLTCTL_AIC_SHIFT) + +#define PCI_EXP_SLTCTL_PIC_SHIFT (ffs(PCI_EXP_SLTCTL_PIC) - 1) +#define PCI_EXP_SLTCTL_PIC_OFF \ + (PCI_EXP_SLTCTL_IND_OFF << PCI_EXP_SLTCTL_PIC_SHIFT) + +#define PCI_EXP_SLTCTL_SUPPORTED \ + (PCI_EXP_SLTCTL_ABPE | \ + PCI_EXP_SLTCTL_PDCE | \ + PCI_EXP_SLTCTL_CCIE | \ + PCI_EXP_SLTCTL_HPIE | \ + PCI_EXP_SLTCTL_AIC | \ + PCI_EXP_SLTCTL_PCC | \ + PCI_EXP_SLTCTL_EIC) + +#define PCI_EXP_DEVCAP2_EFF 0x100000 +#define PCI_EXP_DEVCAP2_EETLPP 0x200000 + +#define PCI_EXP_DEVCTL2_EETLPPB 0x80 + +/* ARI */ +#define PCI_ARI_VER 1 +#define PCI_ARI_SIZEOF 8 + +/* AER */ +#define PCI_ERR_VER 2 +#define PCI_ERR_SIZEOF 0x48 + +#define PCI_ERR_UNC_SDN 0x00000020 /* surprise down */ +#define PCI_ERR_UNC_ACSV 0x00200000 /* ACS Violation */ +#define PCI_ERR_UNC_INTN 0x00400000 /* Internal Error */ +#define PCI_ERR_UNC_MCBTLP 0x00800000 /* MC Blcoked TLP */ +#define PCI_ERR_UNC_ATOP_EBLOCKED 0x01000000 /* atomic op egress blocked */ +#define PCI_ERR_UNC_TLP_PRF_BLOCKED 0x02000000 /* TLP Prefix Blocked */ +#define PCI_ERR_COR_ADV_NONFATAL 0x00002000 /* Advisory Non-Fatal */ +#define PCI_ERR_COR_INTERNAL 0x00004000 /* Corrected Internal */ +#define PCI_ERR_COR_HL_OVERFLOW 0x00008000 /* Header Long Overflow */ +#define PCI_ERR_CAP_FEP_MASK 0x0000001f +#define PCI_ERR_CAP_MHRC 0x00000200 +#define PCI_ERR_CAP_MHRE 0x00000400 +#define PCI_ERR_CAP_TLP 0x00000800 + +#define PCI_ERR_HEADER_LOG_SIZE 16 +#define PCI_ERR_TLP_PREFIX_LOG 0x38 +#define PCI_ERR_TLP_PREFIX_LOG_SIZE 16 + +#define PCI_SEC_STATUS_RCV_SYSTEM_ERROR 0x4000 + +/* aer root error command/status */ +#define PCI_ERR_ROOT_CMD_EN_MASK (PCI_ERR_ROOT_CMD_COR_EN | \ + PCI_ERR_ROOT_CMD_NONFATAL_EN | \ + PCI_ERR_ROOT_CMD_FATAL_EN) + +#define PCI_ERR_ROOT_IRQ_MAX 32 +#define PCI_ERR_ROOT_IRQ 0xf8000000 +#define PCI_ERR_ROOT_IRQ_SHIFT (ffs(PCI_ERR_ROOT_IRQ) - 1) +#define PCI_ERR_ROOT_STATUS_REPORT_MASK (PCI_ERR_ROOT_COR_RCV | \ + PCI_ERR_ROOT_MULTI_COR_RCV | \ + PCI_ERR_ROOT_UNCOR_RCV | \ + PCI_ERR_ROOT_MULTI_UNCOR_RCV | \ + PCI_ERR_ROOT_FIRST_FATAL | \ + PCI_ERR_ROOT_NONFATAL_RCV | \ + PCI_ERR_ROOT_FATAL_RCV) + +#define PCI_ERR_UNC_SUPPORTED (PCI_ERR_UNC_DLP | \ + PCI_ERR_UNC_SDN | \ + PCI_ERR_UNC_POISON_TLP | \ + PCI_ERR_UNC_FCP | \ + PCI_ERR_UNC_COMP_TIME | \ + PCI_ERR_UNC_COMP_ABORT | \ + PCI_ERR_UNC_UNX_COMP | \ + PCI_ERR_UNC_RX_OVER | \ + PCI_ERR_UNC_MALF_TLP | \ + PCI_ERR_UNC_ECRC | \ + PCI_ERR_UNC_UNSUP | \ + PCI_ERR_UNC_ACSV | \ + PCI_ERR_UNC_INTN | \ + PCI_ERR_UNC_MCBTLP | \ + PCI_ERR_UNC_ATOP_EBLOCKED | \ + PCI_ERR_UNC_TLP_PRF_BLOCKED) + +#define PCI_ERR_UNC_SEVERITY_DEFAULT (PCI_ERR_UNC_DLP | \ + PCI_ERR_UNC_SDN | \ + PCI_ERR_UNC_FCP | \ + PCI_ERR_UNC_RX_OVER | \ + PCI_ERR_UNC_MALF_TLP | \ + PCI_ERR_UNC_INTN) + +#define PCI_ERR_COR_SUPPORTED (PCI_ERR_COR_RCVR | \ + PCI_ERR_COR_BAD_TLP | \ + PCI_ERR_COR_BAD_DLLP | \ + PCI_ERR_COR_REP_ROLL | \ + PCI_ERR_COR_REP_TIMER | \ + PCI_ERR_COR_ADV_NONFATAL | \ + PCI_ERR_COR_INTERNAL | \ + PCI_ERR_COR_HL_OVERFLOW) + +#define PCI_ERR_COR_MASK_DEFAULT (PCI_ERR_COR_ADV_NONFATAL | \ + PCI_ERR_COR_INTERNAL | \ + PCI_ERR_COR_HL_OVERFLOW) + +#endif /* QEMU_PCIE_REGS_H */ diff --git a/hw/pckbd.c b/hw/pckbd.c new file mode 100644 index 0000000..ae65c04 --- /dev/null +++ b/hw/pckbd.c @@ -0,0 +1,499 @@ +/* + * 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.h" +#include "isa.h" +#include "pc.h" +#include "ps2.h" +#include "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 */ + +/* 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; + /* 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; + target_phys_addr_t 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 uint32_t kbd_read_status(void *opaque, uint32_t addr) +{ + 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, uint32_t addr, uint32_t val) +{ + KBDState *s = opaque; + + DPRINTF("kbd: write cmd=0x%02x\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, 0x00, 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", val); + break; + } +} + +static uint32_t kbd_read_data(void *opaque, uint32_t addr) +{ + 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, uint32_t addr, uint32_t val) +{ + KBDState *s = opaque; + + DPRINTF("kbd: write data=0x%02x\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; +} + +static const VMStateDescription vmstate_kbd = { + .name = "pckbd", + .version_id = 3, + .minimum_version_id = 3, + .minimum_version_id_old = 3, + .fields = (VMStateField []) { + VMSTATE_UINT8(write_cmd, KBDState), + VMSTATE_UINT8(status, KBDState), + VMSTATE_UINT8(mode, KBDState), + VMSTATE_UINT8(pending, KBDState), + VMSTATE_END_OF_LIST() + } +}; + +/* Memory mapped interface */ +static uint32_t kbd_mm_readb (void *opaque, target_phys_addr_t addr) +{ + KBDState *s = opaque; + + if (addr & s->mask) + return kbd_read_status(s, 0) & 0xff; + else + return kbd_read_data(s, 0) & 0xff; +} + +static void kbd_mm_writeb (void *opaque, target_phys_addr_t addr, uint32_t value) +{ + KBDState *s = opaque; + + if (addr & s->mask) + kbd_write_command(s, 0, value & 0xff); + else + kbd_write_data(s, 0, value & 0xff); +} + +static CPUReadMemoryFunc * const kbd_mm_read[] = { + &kbd_mm_readb, + &kbd_mm_readb, + &kbd_mm_readb, +}; + +static CPUWriteMemoryFunc * const kbd_mm_write[] = { + &kbd_mm_writeb, + &kbd_mm_writeb, + &kbd_mm_writeb, +}; + +void i8042_mm_init(qemu_irq kbd_irq, qemu_irq mouse_irq, + target_phys_addr_t base, ram_addr_t size, + target_phys_addr_t mask) +{ + KBDState *s = qemu_mallocz(sizeof(KBDState)); + int s_io_memory; + + s->irq_kbd = kbd_irq; + s->irq_mouse = mouse_irq; + s->mask = mask; + + vmstate_register(NULL, 0, &vmstate_kbd, s); + s_io_memory = cpu_register_io_memory(kbd_mm_read, kbd_mm_write, s, + DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(base, size, s_io_memory); + + 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); +} + +typedef struct ISAKBDState { + ISADevice dev; + KBDState kbd; +} ISAKBDState; + +void i8042_isa_mouse_fake_event(void *opaque) +{ + ISADevice *dev = opaque; + KBDState *s = &(DO_UPCAST(ISAKBDState, dev, dev)->kbd); + + ps2_mouse_fake_event(s->mouse); +} + +void i8042_setup_a20_line(ISADevice *dev, qemu_irq *a20_out) +{ + KBDState *s = &(DO_UPCAST(ISAKBDState, dev, dev)->kbd); + + s->a20_out = a20_out; +} + +static const VMStateDescription vmstate_kbd_isa = { + .name = "pckbd", + .version_id = 3, + .minimum_version_id = 3, + .minimum_version_id_old = 3, + .fields = (VMStateField []) { + VMSTATE_STRUCT(kbd, ISAKBDState, 0, vmstate_kbd, KBDState), + VMSTATE_END_OF_LIST() + } +}; + +static int i8042_initfn(ISADevice *dev) +{ + KBDState *s = &(DO_UPCAST(ISAKBDState, dev, dev)->kbd); + + isa_init_irq(dev, &s->irq_kbd, 1); + isa_init_irq(dev, &s->irq_mouse, 12); + + register_ioport_read(0x60, 1, 1, kbd_read_data, s); + register_ioport_write(0x60, 1, 1, kbd_write_data, s); + isa_init_ioport(dev, 0x60); + register_ioport_read(0x64, 1, 1, kbd_read_status, s); + register_ioport_write(0x64, 1, 1, kbd_write_command, s); + isa_init_ioport(dev, 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); + return 0; +} + +static ISADeviceInfo i8042_info = { + .qdev.name = "i8042", + .qdev.size = sizeof(ISAKBDState), + .qdev.vmsd = &vmstate_kbd_isa, + .qdev.no_user = 1, + .init = i8042_initfn, +}; + +static void i8042_register(void) +{ + isa_qdev_register(&i8042_info); +} +device_init(i8042_register) diff --git a/hw/pcmcia.h b/hw/pcmcia.h new file mode 100644 index 0000000..50648c9 --- /dev/null +++ b/hw/pcmcia.h @@ -0,0 +1,51 @@ +/* PCMCIA/Cardbus */ + +#include "qemu-common.h" + +typedef struct { + qemu_irq irq; + int attached; + const char *slot_string; + const char *card_string; +} PCMCIASocket; + +void pcmcia_socket_register(PCMCIASocket *socket); +void pcmcia_socket_unregister(PCMCIASocket *socket); +void pcmcia_info(Monitor *mon); + +struct PCMCIACardState { + void *state; + PCMCIASocket *slot; + int (*attach)(void *state); + int (*detach)(void *state); + const uint8_t *cis; + int cis_len; + + /* Only valid if attached */ + uint8_t (*attr_read)(void *state, uint32_t address); + void (*attr_write)(void *state, uint32_t address, uint8_t value); + uint16_t (*common_read)(void *state, uint32_t address); + void (*common_write)(void *state, uint32_t address, uint16_t value); + uint16_t (*io_read)(void *state, uint32_t address); + void (*io_write)(void *state, uint32_t address, uint16_t value); +}; + +#define CISTPL_DEVICE 0x01 /* 5V Device Information Tuple */ +#define CISTPL_NO_LINK 0x14 /* No Link Tuple */ +#define CISTPL_VERS_1 0x15 /* Level 1 Version Tuple */ +#define CISTPL_JEDEC_C 0x18 /* JEDEC ID Tuple */ +#define CISTPL_JEDEC_A 0x19 /* JEDEC ID Tuple */ +#define CISTPL_CONFIG 0x1a /* Configuration Tuple */ +#define CISTPL_CFTABLE_ENTRY 0x1b /* 16-bit PCCard Configuration */ +#define CISTPL_DEVICE_OC 0x1c /* Additional Device Information */ +#define CISTPL_DEVICE_OA 0x1d /* Additional Device Information */ +#define CISTPL_DEVICE_GEO 0x1e /* Additional Device Information */ +#define CISTPL_DEVICE_GEO_A 0x1f /* Additional Device Information */ +#define CISTPL_MANFID 0x20 /* Manufacture ID Tuple */ +#define CISTPL_FUNCID 0x21 /* Function ID Tuple */ +#define CISTPL_FUNCE 0x22 /* Function Extension Tuple */ +#define CISTPL_END 0xff /* Tuple End */ +#define CISTPL_ENDMARK 0xff + +/* dscm1xxxx.c */ +PCMCIACardState *dscm1xxxx_init(DriveInfo *bdrv); diff --git a/hw/pcnet-pci.c b/hw/pcnet-pci.c new file mode 100644 index 0000000..339a401 --- /dev/null +++ b/hw/pcnet-pci.c @@ -0,0 +1,346 @@ +/* + * 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 "pci.h" +#include "net.h" +#include "loader.h" +#include "qemu-timer.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 + + +typedef struct { + PCIDevice pci_dev; + PCNetState state; +} PCIPCNetState; + +static void pcnet_aprom_writeb(void *opaque, uint32_t addr, uint32_t val) +{ + PCNetState *s = opaque; +#ifdef PCNET_DEBUG + printf("pcnet_aprom_writeb addr=0x%08x val=0x%02x\n", addr, val); +#endif + /* Check APROMWE bit to enable write access */ + if (pcnet_bcr_readw(s,2) & 0x100) + 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]; +#ifdef PCNET_DEBUG + printf("pcnet_aprom_readb addr=0x%08x val=0x%02x\n", addr, val); +#endif + return val; +} + +static void pcnet_ioport_map(PCIDevice *pci_dev, int region_num, + pcibus_t addr, pcibus_t size, int type) +{ + PCNetState *d = &DO_UPCAST(PCIPCNetState, pci_dev, pci_dev)->state; + +#ifdef PCNET_DEBUG_IO + printf("pcnet_ioport_map addr=0x%04"FMT_PCIBUS" size=0x%04"FMT_PCIBUS"\n", + addr, size); +#endif + + register_ioport_write(addr, 16, 1, pcnet_aprom_writeb, d); + register_ioport_read(addr, 16, 1, pcnet_aprom_readb, d); + + register_ioport_write(addr + 0x10, 0x10, 2, pcnet_ioport_writew, d); + register_ioport_read(addr + 0x10, 0x10, 2, pcnet_ioport_readw, d); + register_ioport_write(addr + 0x10, 0x10, 4, pcnet_ioport_writel, d); + register_ioport_read(addr + 0x10, 0x10, 4, pcnet_ioport_readl, d); +} + +static void pcnet_mmio_writeb(void *opaque, target_phys_addr_t addr, uint32_t val) +{ + PCNetState *d = opaque; +#ifdef PCNET_DEBUG_IO + printf("pcnet_mmio_writeb addr=0x" TARGET_FMT_plx" val=0x%02x\n", addr, + val); +#endif + if (!(addr & 0x10)) + pcnet_aprom_writeb(d, addr & 0x0f, val); +} + +static uint32_t pcnet_mmio_readb(void *opaque, target_phys_addr_t addr) +{ + PCNetState *d = opaque; + uint32_t val = -1; + if (!(addr & 0x10)) + val = pcnet_aprom_readb(d, addr & 0x0f); +#ifdef PCNET_DEBUG_IO + printf("pcnet_mmio_readb addr=0x" TARGET_FMT_plx " val=0x%02x\n", addr, + val & 0xff); +#endif + return val; +} + +static void pcnet_mmio_writew(void *opaque, target_phys_addr_t addr, uint32_t val) +{ + PCNetState *d = opaque; +#ifdef PCNET_DEBUG_IO + printf("pcnet_mmio_writew addr=0x" TARGET_FMT_plx " val=0x%04x\n", addr, + val); +#endif + 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, target_phys_addr_t 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); + } +#ifdef PCNET_DEBUG_IO + printf("pcnet_mmio_readw addr=0x" TARGET_FMT_plx" val = 0x%04x\n", addr, + val & 0xffff); +#endif + return val; +} + +static void pcnet_mmio_writel(void *opaque, target_phys_addr_t addr, uint32_t val) +{ + PCNetState *d = opaque; +#ifdef PCNET_DEBUG_IO + printf("pcnet_mmio_writel addr=0x" TARGET_FMT_plx" val=0x%08x\n", addr, + val); +#endif + 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, target_phys_addr_t 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); + } +#ifdef PCNET_DEBUG_IO + printf("pcnet_mmio_readl addr=0x" TARGET_FMT_plx " val=0x%08x\n", addr, + val); +#endif + return val; +} + +static const VMStateDescription vmstate_pci_pcnet = { + .name = "pcnet", + .version_id = 3, + .minimum_version_id = 2, + .minimum_version_id_old = 2, + .fields = (VMStateField []) { + VMSTATE_PCI_DEVICE(pci_dev, PCIPCNetState), + VMSTATE_STRUCT(state, PCIPCNetState, 0, vmstate_pcnet, PCNetState), + VMSTATE_END_OF_LIST() + } +}; + +/* PCI interface */ + +static CPUWriteMemoryFunc * const pcnet_mmio_write[] = { + &pcnet_mmio_writeb, + &pcnet_mmio_writew, + &pcnet_mmio_writel +}; + +static CPUReadMemoryFunc * const pcnet_mmio_read[] = { + &pcnet_mmio_readb, + &pcnet_mmio_readw, + &pcnet_mmio_readl +}; + +static void pcnet_mmio_map(PCIDevice *pci_dev, int region_num, + pcibus_t addr, pcibus_t size, int type) +{ + PCIPCNetState *d = DO_UPCAST(PCIPCNetState, pci_dev, pci_dev); + +#ifdef PCNET_DEBUG_IO + printf("pcnet_mmio_map addr=0x%08"FMT_PCIBUS" 0x%08"FMT_PCIBUS"\n", + addr, size); +#endif + + cpu_register_physical_memory(addr, PCNET_PNPMMIO_SIZE, d->state.mmio_index); +} + +static void pci_physical_memory_write(void *dma_opaque, target_phys_addr_t addr, + uint8_t *buf, int len, int do_bswap) +{ + cpu_physical_memory_write(addr, buf, len); +} + +static void pci_physical_memory_read(void *dma_opaque, target_phys_addr_t addr, + uint8_t *buf, int len, int do_bswap) +{ + cpu_physical_memory_read(addr, buf, len); +} + +static void pci_pcnet_cleanup(VLANClientState *nc) +{ + PCNetState *d = DO_UPCAST(NICState, nc, nc)->opaque; + + pcnet_common_cleanup(d); +} + +static int pci_pcnet_uninit(PCIDevice *dev) +{ + PCIPCNetState *d = DO_UPCAST(PCIPCNetState, pci_dev, dev); + + cpu_unregister_io_memory(d->state.mmio_index); + qemu_del_timer(d->state.poll_timer); + qemu_free_timer(d->state.poll_timer); + qemu_del_vlan_client(&d->state.nic->nc); + return 0; +} + +static NetClientInfo net_pci_pcnet_info = { + .type = NET_CLIENT_TYPE_NIC, + .size = sizeof(NICState), + .can_receive = pcnet_can_receive, + .receive = pcnet_receive, + .cleanup = pci_pcnet_cleanup, +}; + +static int pci_pcnet_init(PCIDevice *pci_dev) +{ + PCIPCNetState *d = DO_UPCAST(PCIPCNetState, pci_dev, 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_config_set_vendor_id(pci_conf, PCI_VENDOR_ID_AMD); + pci_config_set_device_id(pci_conf, PCI_DEVICE_ID_AMD_LANCE); + pci_set_word(pci_conf + PCI_STATUS, + PCI_STATUS_FAST_BACK | PCI_STATUS_DEVSEL_MEDIUM); + pci_conf[PCI_REVISION_ID] = 0x10; + pci_config_set_class(pci_conf, PCI_CLASS_NETWORK_ETHERNET); + + 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 0 + pci_conf[PCI_MIN_GNT] = 0x06; + pci_conf[PCI_MAX_LAT] = 0xff; + + /* Handler for memory-mapped I/O */ + s->mmio_index = + cpu_register_io_memory(pcnet_mmio_read, pcnet_mmio_write, &d->state, + DEVICE_NATIVE_ENDIAN); + + pci_register_bar(pci_dev, 0, PCNET_IOPORT_SIZE, + PCI_BASE_ADDRESS_SPACE_IO, pcnet_ioport_map); + + pci_register_bar(pci_dev, 1, PCNET_PNPMMIO_SIZE, + PCI_BASE_ADDRESS_SPACE_MEMORY, pcnet_mmio_map); + + s->irq = pci_dev->irq[0]; + s->phys_mem_read = pci_physical_memory_read; + s->phys_mem_write = pci_physical_memory_write; + + if (!pci_dev->qdev.hotplugged) { + static int loaded = 0; + if (!loaded) { + rom_add_option("pxe-pcnet.bin", -1); + loaded = 1; + } + } + + return pcnet_common_init(&pci_dev->qdev, s, &net_pci_pcnet_info); +} + +static void pci_reset(DeviceState *dev) +{ + PCIPCNetState *d = DO_UPCAST(PCIPCNetState, pci_dev.qdev, dev); + + pcnet_h_reset(&d->state); +} + +static PCIDeviceInfo pcnet_info = { + .qdev.name = "pcnet", + .qdev.size = sizeof(PCIPCNetState), + .qdev.reset = pci_reset, + .qdev.vmsd = &vmstate_pci_pcnet, + .init = pci_pcnet_init, + .exit = pci_pcnet_uninit, + .qdev.props = (Property[]) { + DEFINE_NIC_PROPERTIES(PCIPCNetState, state.conf), + DEFINE_PROP_END_OF_LIST(), + } +}; + +static void pci_pcnet_register_devices(void) +{ + pci_qdev_register(&pcnet_info); +} + +device_init(pci_pcnet_register_devices) diff --git a/hw/pcnet.c b/hw/pcnet.c new file mode 100644 index 0000000..db52dc5 --- /dev/null +++ b/hw/pcnet.c @@ -0,0 +1,1748 @@ +/* + * 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 "qdev.h" +#include "net.h" +#include "qemu-timer.h" +#include "qemu_socket.h" +#include "sysemu.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; +}; + +/* 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_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) + +#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_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] | ((S)->csr[ 2] << 16)) +#define CSR_CRBA(S) ((S)->csr[18] | ((S)->csr[19] << 16)) +#define CSR_CXBA(S) ((S)->csr[20] | ((S)->csr[21] << 16)) +#define CSR_NRBA(S) ((S)->csr[22] | ((S)->csr[23] << 16)) +#define CSR_BADR(S) ((S)->csr[24] | ((S)->csr[25] << 16)) +#define CSR_NRDA(S) ((S)->csr[26] | ((S)->csr[27] << 16)) +#define CSR_CRDA(S) ((S)->csr[28] | ((S)->csr[29] << 16)) +#define CSR_BADX(S) ((S)->csr[30] | ((S)->csr[31] << 16)) +#define CSR_NXDA(S) ((S)->csr[32] | ((S)->csr[33] << 16)) +#define CSR_CXDA(S) ((S)->csr[34] | ((S)->csr[35] << 16)) +#define CSR_NNRD(S) ((S)->csr[36] | ((S)->csr[37] << 16)) +#define CSR_NNXD(S) ((S)->csr[38] | ((S)->csr[39] << 16)) +#define CSR_PXDA(S) ((S)->csr[60] | ((S)->csr[61] << 16)) +#define CSR_NXBA(S) ((S)->csr[64] | ((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, + target_phys_addr_t 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, + target_phys_addr_t 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, + target_phys_addr_t 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, + target_phys_addr_t 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 target_phys_addr_t 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) +{ +#ifdef PCNET_DEBUG + printf("pcnet_s_reset\n"); +#endif + + s->lnkst = 0x40; + 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[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; +#ifdef PCNET_DEBUG + printf("pcnet user int\n"); +#endif + } + +#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) { +#ifdef PCNET_DEBUG + printf("pcnet: INTA=%d\n", isr); +#endif + } + 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; + +#ifdef PCNET_DEBUG + printf("pcnet_init init_addr=0x%08x\n", PHYSADDR(s,CSR_IADR(s))); +#endif + + 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; + } + +#if defined(PCNET_DEBUG) + printf("rlen=%d tlen=%d\n", rlen, tlen); +#endif + + 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); + +#ifdef PCNET_DEBUG + printf("pcnet ss32=%d rdra=0x%08x[%d] tdra=0x%08x[%d]\n", + BCR_SSIZE32(s), + s->rdra, CSR_RCVRL(s), s->tdra, CSR_XMTRL(s)); +#endif + + s->csr[0] |= 0x0101; + s->csr[0] &= ~0x0004; /* clear STOP bit */ +} + +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); +} + +static void pcnet_stop(PCNetState *s) +{ +#ifdef PCNET_DEBUG + printf("pcnet_stop\n"); +#endif + s->csr[0] &= ~0x7feb; + 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 + target_phys_addr_t crda = pcnet_rdra_addr(s, CSR_RCVRC(s)); + target_phys_addr_t nrda = pcnet_rdra_addr(s, -1 + CSR_RCVRC(s)); + target_phys_addr_t nnrd = pcnet_rdra_addr(s, -2 + CSR_RCVRC(s)); +#else + target_phys_addr_t 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; + target_phys_addr_t nrda = s->rdra + + (CSR_RCVRL(s) - nrdc) * + (BCR_SWSTYLE(s) ? 16 : 8 ); + int nnrc = nrdc<=1 ? CSR_RCVRL(s) : nrdc-1; + target_phys_addr_t 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) { + target_phys_addr_t 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); +} + +int pcnet_can_receive(VLANClientState *nc) +{ + PCNetState *s = DO_UPCAST(NICState, nc, nc)->opaque; + if (CSR_STOP(s) || CSR_SPND(s)) + return 0; + + return sizeof(s->buffer)-16; +} + +#define MIN_BUF_SIZE 60 + +ssize_t pcnet_receive(VLANClientState *nc, const uint8_t *buf, size_t size_) +{ + PCNetState *s = DO_UPCAST(NICState, nc, nc)->opaque; + 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; + target_phys_addr_t 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; + target_phys_addr_t crda = CSR_CRDA(s); + struct pcnet_RMD rmd; + int pktcount = 0; + + if (!s->looptest) { + 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-4]) + 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); \ + target_phys_addr_t 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)) { + target_phys_addr_t 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_; +} + +static void pcnet_transmit(PCNetState *s) +{ + target_phys_addr_t xmit_cxda = 0; + int count = CSR_XMTRL(s)-1; + int add_crc = 0; + + 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 (!GET_FIELD(tmd.status, TMDS, ENP)) { + int bcnt = 4096 - GET_FIELD(tmd.length, TMDL, BCNT); + s->phys_mem_read(s->dma_opaque, PHYSADDR(s, tmd.tbadr), + s->buffer + s->xmit_pos, bcnt, CSR_BSWP(s)); + s->xmit_pos += bcnt; + } else if (s->xmit_pos >= 0) { + int bcnt = 4096 - GET_FIELD(tmd.length, TMDL, BCNT); + s->phys_mem_read(s->dma_opaque, PHYSADDR(s, tmd.tbadr), + s->buffer + s->xmit_pos, bcnt, CSR_BSWP(s)); + s->xmit_pos += bcnt; +#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(&s->nic->nc, s->buffer, s->xmit_pos); + s->looptest = 0; + } else + if (s->nic) + qemu_send_packet(&s->nic->nc, s->buffer, s->xmit_pos); + + s->csr[0] &= ~0x0008; /* clear TDMD */ + s->csr[4] |= 0x0004; /* set TXSTRT */ + s->xmit_pos = -1; + } + + 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; + + qemu_del_timer(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_get_clock(vm_clock) * 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; + } + qemu_mod_timer(s->poll_timer, + pcnet_get_next_poll_time(s,qemu_get_clock(vm_clock))); + } +} + + +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 + 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; + int i; + uint16_t checksum; + + /* Initialize the PROM */ + + memcpy(s->prom, s->conf.macaddr.a, 6); + s->prom[12] = s->prom[13] = 0x00; + 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->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, + .minimum_version_id_old = 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(poll_timer, PCNetState), + VMSTATE_END_OF_LIST() + } +}; + +void pcnet_common_cleanup(PCNetState *d) +{ + d->nic = NULL; +} + +int pcnet_common_init(DeviceState *dev, PCNetState *s, NetClientInfo *info) +{ + s->poll_timer = qemu_new_timer(vm_clock, pcnet_poll_timer, s); + + qemu_macaddr_default_if_unset(&s->conf.macaddr); + s->nic = qemu_new_nic(info, &s->conf, dev->info->name, dev->id, s); + qemu_format_nic_info_str(&s->nic->nc, s->conf.macaddr.a); + + add_boot_device_path(s->conf.bootindex, dev, "/ethernet-phy@0"); + + return 0; +} diff --git a/hw/pcnet.h b/hw/pcnet.h new file mode 100644 index 0000000..534bdf9 --- /dev/null +++ b/hw/pcnet.h @@ -0,0 +1,42 @@ +#define PCNET_IOPORT_SIZE 0x20 +#define PCNET_PNPMMIO_SIZE 0x20 + +#define PCNET_LOOPTEST_CRC 1 +#define PCNET_LOOPTEST_NOCRC 2 + + +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]; + uint64_t timer; + int mmio_index, xmit_pos; + uint8_t buffer[4096]; + int tx_busy; + qemu_irq irq; + void (*phys_mem_read)(void *dma_opaque, target_phys_addr_t addr, + uint8_t *buf, int len, int do_bswap); + void (*phys_mem_write)(void *dma_opaque, target_phys_addr_t addr, + uint8_t *buf, int len, int do_bswap); + void *dma_opaque; + 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); +int pcnet_can_receive(VLANClientState *nc); +ssize_t pcnet_receive(VLANClientState *nc, const uint8_t *buf, size_t size_); +void pcnet_common_cleanup(PCNetState *d); +int pcnet_common_init(DeviceState *dev, PCNetState *s, NetClientInfo *info); +extern const VMStateDescription vmstate_pcnet; diff --git a/hw/pcspk.c b/hw/pcspk.c new file mode 100644 index 0000000..26a0ecb --- /dev/null +++ b/hw/pcspk.c @@ -0,0 +1,147 @@ +/* + * 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.h" +#include "pc.h" +#include "isa.h" +#include "audio/audio.h" +#include "qemu-timer.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) + +typedef struct { + uint8_t sample_buf[PCSPK_BUF_LEN]; + QEMUSoundCard card; + SWVoiceOut *voice; + PITState *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; + unsigned int n; + + if (pit_get_mode(s->pit, 2) != 3) + return; + + n = pit_get_initial_count(s->pit, 2); + /* 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; + } +} + +int pcspk_audio_init(qemu_irq *pic) +{ + 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 uint32_t pcspk_ioport_read(void *opaque, uint32_t addr) +{ + PCSpkState *s = opaque; + int out; + + s->dummy_refresh_clock ^= (1 << 4); + out = pit_get_out(s->pit, 2, qemu_get_clock(vm_clock)) << 5; + + return pit_get_gate(s->pit, 2) | (s->data_on << 1) | s->dummy_refresh_clock | out; +} + +static void pcspk_ioport_write(void *opaque, uint32_t addr, uint32_t val) +{ + 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); + } +} + +void pcspk_init(PITState *pit) +{ + PCSpkState *s = &pcspk_state; + + s->pit = pit; + register_ioport_read(0x61, 1, 1, pcspk_ioport_read, s); + register_ioport_write(0x61, 1, 1, pcspk_ioport_write, s); +} diff --git a/hw/petalogix_s3adsp1800_mmu.c b/hw/petalogix_s3adsp1800_mmu.c new file mode 100644 index 0000000..42de459 --- /dev/null +++ b/hw/petalogix_s3adsp1800_mmu.c @@ -0,0 +1,225 @@ +/* + * 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 "sysbus.h" +#include "hw.h" +#include "net.h" +#include "flash.h" +#include "sysemu.h" +#include "devices.h" +#include "boards.h" +#include "device_tree.h" +#include "xilinx.h" +#include "loader.h" +#include "elf.h" +#include "blockdev.h" + +#define LMB_BRAM_SIZE (128 * 1024) +#define FLASH_SIZE (16 * 1024 * 1024) + +static struct +{ + uint32_t bootstrap_pc; + uint32_t cmdline; + uint32_t fdt; +} boot_info; + +static void main_cpu_reset(void *opaque) +{ + CPUState *env = opaque; + + cpu_reset(env); + env->regs[5] = boot_info.cmdline; + env->regs[7] = boot_info.fdt; + env->sregs[SR_PC] = boot_info.bootstrap_pc; +} + +#define BINARY_DEVICE_TREE_FILE "petalogix-s3adsp1800.dtb" +static int petalogix_load_device_tree(target_phys_addr_t addr, + uint32_t ramsize, + target_phys_addr_t initrd_base, + target_phys_addr_t initrd_size, + const char *kernel_cmdline) +{ + char *path; + int fdt_size; +#ifdef CONFIG_FDT + void *fdt; + int r; + + /* Try the local "mb.dtb" override. */ + fdt = load_device_tree("mb.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); + qemu_free(path); + } + if (!fdt) + return 0; + } + + r = qemu_devtree_setprop_string(fdt, "/chosen", "bootargs", kernel_cmdline); + if (r < 0) + fprintf(stderr, "couldn't set /chosen/bootargs\n"); + cpu_physical_memory_write (addr, (void *)fdt, fdt_size); +#else + /* We lack libfdt so we cannot manipulate the fdt. Just pass on the blob + to the kernel. */ + fdt_size = load_image_targphys("mb.dtb", addr, 0x10000); + if (fdt_size < 0) { + path = qemu_find_file(QEMU_FILE_TYPE_BIOS, BINARY_DEVICE_TREE_FILE); + if (path) { + fdt_size = load_image_targphys(path, addr, 0x10000); + qemu_free(path); + } + } + + if (kernel_cmdline) { + fprintf(stderr, + "Warning: missing libfdt, cannot pass cmdline to kernel!\n"); + } +#endif + return fdt_size; +} + +static uint64_t translate_kernel_address(void *opaque, uint64_t addr) +{ + return addr - 0x30000000LL; +} + +static void +petalogix_s3adsp1800_init(ram_addr_t ram_size, + const char *boot_device, + const char *kernel_filename, + const char *kernel_cmdline, + const char *initrd_filename, const char *cpu_model) +{ + DeviceState *dev; + CPUState *env; + int kernel_size; + DriveInfo *dinfo; + int i; + target_phys_addr_t ddr_base = 0x90000000; + ram_addr_t phys_lmb_bram; + ram_addr_t phys_ram; + ram_addr_t phys_flash; + qemu_irq irq[32], *cpu_irq; + + /* init CPUs */ + if (cpu_model == NULL) { + cpu_model = "microblaze"; + } + env = cpu_init(cpu_model); + + env->pvr.regs[10] = 0x0c000000; /* spartan 3a dsp family. */ + qemu_register_reset(main_cpu_reset, env); + + /* Attach emulated BRAM through the LMB. */ + phys_lmb_bram = qemu_ram_alloc(NULL, "petalogix_s3adsp1800.lmb_bram", + LMB_BRAM_SIZE); + cpu_register_physical_memory(0x00000000, LMB_BRAM_SIZE, + phys_lmb_bram | IO_MEM_RAM); + + phys_ram = qemu_ram_alloc(NULL, "petalogix_s3adsp1800.ram", ram_size); + cpu_register_physical_memory(ddr_base, ram_size, phys_ram | IO_MEM_RAM); + + phys_flash = qemu_ram_alloc(NULL, "petalogix_s3adsp1800.flash", FLASH_SIZE); + dinfo = drive_get(IF_PFLASH, 0, 0); + pflash_cfi01_register(0xa0000000, phys_flash, + dinfo ? dinfo->bdrv : NULL, (64 * 1024), + FLASH_SIZE >> 16, + 1, 0x89, 0x18, 0x0000, 0x0, 1); + + cpu_irq = microblaze_pic_init_cpu(env); + dev = xilinx_intc_create(0x81800000, cpu_irq[0], 2); + for (i = 0; i < 32; i++) { + irq[i] = qdev_get_gpio_in(dev, i); + } + + sysbus_create_simple("xilinx,uartlite", 0x84000000, irq[3]); + /* 2 timers at irq 2 @ 62 Mhz. */ + xilinx_timer_create(0x83c00000, irq[0], 2, 62 * 1000000); + xilinx_ethlite_create(&nd_table[0], 0x81000000, irq[1], 0, 0); + + if (kernel_filename) { + uint64_t entry, low, high; + uint32_t base32; + + /* Boots a kernel elf binary. */ + kernel_size = load_elf(kernel_filename, NULL, NULL, + &entry, &low, &high, + 1, ELF_MACHINE, 0); + base32 = entry; + if (base32 == 0xc0000000) { + kernel_size = load_elf(kernel_filename, translate_kernel_address, + NULL, &entry, NULL, NULL, + 1, ELF_MACHINE, 0); + } + /* Always boot into physical ram. */ + boot_info.bootstrap_pc = ddr_base + (entry & 0x0fffffff); + + /* If it wasn't an ELF image, try an u-boot image. */ + if (kernel_size < 0) { + target_phys_addr_t uentry, loadaddr; + + kernel_size = load_uimage(kernel_filename, &uentry, &loadaddr, 0); + 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; + } + + 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; + petalogix_load_device_tree(boot_info.fdt, ram_size, + 0, 0, + kernel_cmdline); + } +} + +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/pflash_cfi01.c b/hw/pflash_cfi01.c new file mode 100644 index 0000000..fb20dfb --- /dev/null +++ b/hw/pflash_cfi01.c @@ -0,0 +1,726 @@ +/* + * 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 . + */ + +/* + * 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.h" +#include "flash.h" +#include "block.h" +#include "qemu-timer.h" + +#define PFLASH_BUG(fmt, ...) \ +do { \ + printf("PFLASH: Possible BUG - " fmt, ## __VA_ARGS__); \ + exit(1); \ +} while(0) + +/* #define PFLASH_DEBUG */ +#ifdef PFLASH_DEBUG +#define DPRINTF(fmt, ...) \ +do { \ + printf("PFLASH: " fmt , ## __VA_ARGS__); \ +} while (0) +#else +#define DPRINTF(fmt, ...) do { } while (0) +#endif + +struct pflash_t { + BlockDriverState *bs; + target_phys_addr_t base; + target_phys_addr_t sector_len; + target_phys_addr_t total_len; + int width; + int wcycle; /* if 0, the flash is read normally */ + int bypass; + int ro; + uint8_t cmd; + uint8_t status; + uint16_t ident[4]; + uint8_t cfi_len; + uint8_t cfi_table[0x52]; + target_phys_addr_t counter; + unsigned int writeblock_size; + QEMUTimer *timer; + ram_addr_t off; + int fl_mem; + void *storage; +}; + +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 { + cpu_register_physical_memory(pfl->base, pfl->total_len, + pfl->off | IO_MEM_ROMD | pfl->fl_mem); + pfl->wcycle = 0; + } + pfl->cmd = 0; +} + +static uint32_t pflash_read (pflash_t *pfl, target_phys_addr_t offset, + int width, int be) +{ + target_phys_addr_t boff; + uint32_t ret; + uint8_t *p; + + ret = -1; + boff = offset & 0xFF; /* why this here ?? */ + + if (pfl->width == 2) + boff = boff >> 1; + else if (pfl->width == 4) + boff = boff >> 2; + +#if 0 + DPRINTF("%s: reading offset " TARGET_FMT_plx " under cmd %02x width %d\n", + __func__, offset, pfl->cmd, width); +#endif + switch (pfl->cmd) { + case 0x00: + /* Flash area read */ + 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 + 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__); + } + + break; + case 0x20: /* Block erase */ + case 0x50: /* Clear status register */ + case 0x60: /* Block /un)lock */ + case 0x70: /* Status Register */ + case 0xe8: /* Write block */ + /* Status register read */ + ret = pfl->status; + DPRINTF("%s: status %x\n", __func__, ret); + break; + case 0x90: + switch (boff) { + case 0: + ret = pfl->ident[0] << 8 | pfl->ident[1]; + DPRINTF("%s: Manufacturer Code %04x\n", __func__, ret); + break; + case 1: + ret = pfl->ident[2] << 8 | pfl->ident[3]; + DPRINTF("%s: Device ID Code %04x\n", __func__, ret); + break; + default: + DPRINTF("%s: Read Device Information boff=%x\n", __func__, boff); + ret = 0; + break; + } + break; + case 0x98: /* Query mode */ + if (boff > pfl->cfi_len) + ret = 0; + else + ret = pfl->cfi_table[boff]; + break; + 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; + } + return ret; +} + +/* update flash content on disk */ +static void pflash_update(pflash_t *pfl, int offset, + int size) +{ + int offset_end; + if (pfl->bs) { + offset_end = offset + size; + /* round to sectors */ + offset = offset >> 9; + offset_end = (offset_end + 511) >> 9; + bdrv_write(pfl->bs, offset, pfl->storage + (offset << 9), + offset_end - offset); + } +} + +static inline void pflash_data_write(pflash_t *pfl, target_phys_addr_t offset, + uint32_t value, int width, int be) +{ + uint8_t *p = pfl->storage; + + DPRINTF("%s: block write offset " TARGET_FMT_plx + " value %x counter " TARGET_FMT_plx "\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, target_phys_addr_t 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 */ + cpu_register_physical_memory(pfl->base, pfl->total_len, pfl->fl_mem); + } + + 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 " + TARGET_FMT_plx "\n", + __func__, offset, pfl->sector_len); + + memset(p + offset, 0xff, pfl->sector_len); + pflash_update(pfl, offset, pfl->sector_len); + 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 0xff: /* Read array mode */ + DPRINTF("%s: Read array mode\n", __func__); + goto reset_flash; + default: + goto error_flash; + } + pfl->wcycle++; + pfl->cmd = cmd; + return; + case 1: + switch (pfl->cmd) { + case 0x10: /* Single Byte Program */ + case 0x40: /* Single Byte Program */ + DPRINTF("%s: Single Byte Program\n", __func__); + pflash_data_write(pfl, offset, value, width, be); + pflash_update(pfl, offset, width); + 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: + 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; + } + return; + case 2: + switch (pfl->cmd) { + case 0xe8: /* Block write */ + pflash_data_write(pfl, offset, value, width, be); + + pfl->status |= 0x80; + + if (!pfl->counter) { + target_phys_addr_t mask = pfl->writeblock_size - 1; + mask = ~mask; + + DPRINTF("%s: block write finished\n", __func__); + pfl->wcycle++; + /* Flush the entire write buffer onto backing storage. */ + pflash_update(pfl, offset & mask, pfl->writeblock_size); + } + + pfl->counter--; + break; + default: + goto error_flash; + } + return; + 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; + } + return; + default: + /* Should never happen */ + DPRINTF("%s: invalid write state\n", __func__); + goto reset_flash; + } + return; + + error_flash: + printf("%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: + cpu_register_physical_memory(pfl->base, pfl->total_len, + pfl->off | IO_MEM_ROMD | pfl->fl_mem); + + pfl->bypass = 0; + pfl->wcycle = 0; + pfl->cmd = 0; + return; +} + + +static uint32_t pflash_readb_be(void *opaque, target_phys_addr_t addr) +{ + return pflash_read(opaque, addr, 1, 1); +} + +static uint32_t pflash_readb_le(void *opaque, target_phys_addr_t addr) +{ + return pflash_read(opaque, addr, 1, 0); +} + +static uint32_t pflash_readw_be(void *opaque, target_phys_addr_t addr) +{ + pflash_t *pfl = opaque; + + return pflash_read(pfl, addr, 2, 1); +} + +static uint32_t pflash_readw_le(void *opaque, target_phys_addr_t addr) +{ + pflash_t *pfl = opaque; + + return pflash_read(pfl, addr, 2, 0); +} + +static uint32_t pflash_readl_be(void *opaque, target_phys_addr_t addr) +{ + pflash_t *pfl = opaque; + + return pflash_read(pfl, addr, 4, 1); +} + +static uint32_t pflash_readl_le(void *opaque, target_phys_addr_t addr) +{ + pflash_t *pfl = opaque; + + return pflash_read(pfl, addr, 4, 0); +} + +static void pflash_writeb_be(void *opaque, target_phys_addr_t addr, + uint32_t value) +{ + pflash_write(opaque, addr, value, 1, 1); +} + +static void pflash_writeb_le(void *opaque, target_phys_addr_t addr, + uint32_t value) +{ + pflash_write(opaque, addr, value, 1, 0); +} + +static void pflash_writew_be(void *opaque, target_phys_addr_t addr, + uint32_t value) +{ + pflash_t *pfl = opaque; + + pflash_write(pfl, addr, value, 2, 1); +} + +static void pflash_writew_le(void *opaque, target_phys_addr_t addr, + uint32_t value) +{ + pflash_t *pfl = opaque; + + pflash_write(pfl, addr, value, 2, 0); +} + +static void pflash_writel_be(void *opaque, target_phys_addr_t addr, + uint32_t value) +{ + pflash_t *pfl = opaque; + + pflash_write(pfl, addr, value, 4, 1); +} + +static void pflash_writel_le(void *opaque, target_phys_addr_t addr, + uint32_t value) +{ + pflash_t *pfl = opaque; + + pflash_write(pfl, addr, value, 4, 0); +} + +static CPUWriteMemoryFunc * const pflash_write_ops_be[] = { + &pflash_writeb_be, + &pflash_writew_be, + &pflash_writel_be, +}; + +static CPUReadMemoryFunc * const pflash_read_ops_be[] = { + &pflash_readb_be, + &pflash_readw_be, + &pflash_readl_be, +}; + +static CPUWriteMemoryFunc * const pflash_write_ops_le[] = { + &pflash_writeb_le, + &pflash_writew_le, + &pflash_writel_le, +}; + +static CPUReadMemoryFunc * const pflash_read_ops_le[] = { + &pflash_readb_le, + &pflash_readw_le, + &pflash_readl_le, +}; + +/* Count trailing zeroes of a 32 bits quantity */ +static int ctz32 (uint32_t n) +{ + int ret; + + ret = 0; + if (!(n & 0xFFFF)) { + ret += 16; + n = n >> 16; + } + if (!(n & 0xFF)) { + ret += 8; + n = n >> 8; + } + if (!(n & 0xF)) { + ret += 4; + n = n >> 4; + } + if (!(n & 0x3)) { + ret += 2; + n = n >> 2; + } + if (!(n & 0x1)) { + ret++; +#if 0 /* This is not necessary as n is never 0 */ + n = n >> 1; +#endif + } +#if 0 /* This is not necessary as n is never 0 */ + if (!n) + ret++; +#endif + + return ret; +} + +pflash_t *pflash_cfi01_register(target_phys_addr_t base, ram_addr_t off, + BlockDriverState *bs, uint32_t sector_len, + int nb_blocs, int width, + uint16_t id0, uint16_t id1, + uint16_t id2, uint16_t id3, + int be) +{ + pflash_t *pfl; + target_phys_addr_t total_len; + int ret; + + total_len = sector_len * 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 + + pfl = qemu_mallocz(sizeof(pflash_t)); + + /* FIXME: Allocate ram ourselves. */ + pfl->storage = qemu_get_ram_ptr(off); + if (be) { + pfl->fl_mem = cpu_register_io_memory(pflash_read_ops_be, + pflash_write_ops_be, pfl, + DEVICE_NATIVE_ENDIAN); + } else { + pfl->fl_mem = cpu_register_io_memory(pflash_read_ops_le, + pflash_write_ops_le, pfl, + DEVICE_NATIVE_ENDIAN); + } + pfl->off = off; + cpu_register_physical_memory(base, total_len, + off | pfl->fl_mem | IO_MEM_ROMD); + + pfl->bs = bs; + if (pfl->bs) { + /* read the initial flash content */ + ret = bdrv_read(pfl->bs, 0, pfl->storage, total_len >> 9); + if (ret < 0) { + cpu_unregister_io_memory(pfl->fl_mem); + qemu_free(pfl); + return NULL; + } + } +#if 0 /* XXX: there should be a bit to set up read-only, + * the same way the hardware does (with WP pin). + */ + pfl->ro = 1; +#else + pfl->ro = 0; +#endif + pfl->timer = qemu_new_timer(vm_clock, pflash_timer, pfl); + pfl->base = base; + pfl->sector_len = sector_len; + pfl->total_len = total_len; + pfl->width = width; + pfl->wcycle = 0; + pfl->cmd = 0; + pfl->status = 0; + pfl->ident[0] = id0; + pfl->ident[1] = id1; + pfl->ident[2] = id2; + pfl->ident[3] = id3; + /* 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(total_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 (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] = nb_blocs - 1; + pfl->cfi_table[0x2E] = (nb_blocs - 1) >> 8; + pfl->cfi_table[0x2F] = sector_len >> 8; + pfl->cfi_table[0x30] = 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] = '1'; + + 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; + + return pfl; +} diff --git a/hw/pflash_cfi02.c b/hw/pflash_cfi02.c new file mode 100644 index 0000000..3594a36 --- /dev/null +++ b/hw/pflash_cfi02.c @@ -0,0 +1,741 @@ +/* + * 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 . + */ + +/* + * 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.h" +#include "flash.h" +#include "qemu-timer.h" +#include "block.h" + +//#define PFLASH_DEBUG +#ifdef PFLASH_DEBUG +#define DPRINTF(fmt, ...) \ +do { \ + printf("PFLASH: " fmt , ## __VA_ARGS__); \ +} while (0) +#else +#define DPRINTF(fmt, ...) do { } while (0) +#endif + +struct pflash_t { + BlockDriverState *bs; + target_phys_addr_t base; + uint32_t sector_len; + uint32_t chip_len; + int mappings; + int width; + int wcycle; /* if 0, the flash is read normally */ + int bypass; + int ro; + uint8_t cmd; + uint8_t status; + uint16_t ident[4]; + uint16_t unlock_addr[2]; + uint8_t cfi_len; + uint8_t cfi_table[0x52]; + QEMUTimer *timer; + ram_addr_t off; + int fl_mem; + int rom_mode; + void *storage; +}; + +static void pflash_register_memory(pflash_t *pfl, int rom_mode) +{ + unsigned long phys_offset = pfl->fl_mem; + int i; + + if (rom_mode) + phys_offset |= pfl->off | IO_MEM_ROMD; + pfl->rom_mode = rom_mode; + + for (i = 0; i < pfl->mappings; i++) + cpu_register_physical_memory(pfl->base + i * pfl->chip_len, + pfl->chip_len, phys_offset); +} + +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, target_phys_addr_t offset, + int width, int be) +{ + target_phys_addr_t boff; + uint32_t ret; + uint8_t *p; + + DPRINTF("%s: offset " TARGET_FMT_plx "\n", __func__, offset); + ret = -1; + if (pfl->rom_mode) { + /* Lazy reset of to ROMD mode */ + if (pfl->wcycle == 0) + 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; + 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 = pfl->ident[boff & 0x01]; + break; + case 0x02: + ret = 0x00; /* Pretend all sectors are unprotected */ + break; + case 0x0E: + case 0x0F: + if (pfl->ident[2 + (boff & 0x01)] == (uint8_t)-1) + goto flash_read; + ret = pfl->ident[2 + (boff & 0x01)]; + break; + default: + goto flash_read; + } + DPRINTF("%s: ID " TARGET_FMT_pld " %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->bs) { + offset_end = offset + size; + /* round to sectors */ + offset = offset >> 9; + offset_end = (offset_end + 511) >> 9; + bdrv_write(pfl->bs, offset, pfl->storage + (offset << 9), + offset_end - offset); + } +} + +static void pflash_write (pflash_t *pfl, target_phys_addr_t offset, + uint32_t value, int width, int be) +{ + target_phys_addr_t 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); + /* 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_addr[0] || cmd != 0xAA) { + DPRINTF("%s: unlock0 failed " TARGET_FMT_plx " %02x %04x\n", + __func__, boff, cmd, pfl->unlock_addr[0]); + goto reset_flash; + } + DPRINTF("%s: unlock sequence started\n", __func__); + break; + case 1: + /* We started an unlock sequence */ + check_unlock1: + if (boff != pfl->unlock_addr[1] || 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_addr[0]) { + 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; + 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 occuring */ + /* 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_addr[0]) { + DPRINTF("%s: chip erase: invalid address " TARGET_FMT_plx "\n", + __func__, offset); + goto reset_flash; + } + /* Chip erase */ + DPRINTF("%s: start chip erase\n", __func__); + memset(pfl->storage, 0xFF, pfl->chip_len); + pfl->status = 0x00; + pflash_update(pfl, 0, pfl->chip_len); + /* Let's wait 5 seconds before chip erase is done */ + qemu_mod_timer(pfl->timer, + qemu_get_clock(vm_clock) + (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); + 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 */ + qemu_mod_timer(pfl->timer, + qemu_get_clock(vm_clock) + (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; + return; +} + + +static uint32_t pflash_readb_be(void *opaque, target_phys_addr_t addr) +{ + return pflash_read(opaque, addr, 1, 1); +} + +static uint32_t pflash_readb_le(void *opaque, target_phys_addr_t addr) +{ + return pflash_read(opaque, addr, 1, 0); +} + +static uint32_t pflash_readw_be(void *opaque, target_phys_addr_t addr) +{ + pflash_t *pfl = opaque; + + return pflash_read(pfl, addr, 2, 1); +} + +static uint32_t pflash_readw_le(void *opaque, target_phys_addr_t addr) +{ + pflash_t *pfl = opaque; + + return pflash_read(pfl, addr, 2, 0); +} + +static uint32_t pflash_readl_be(void *opaque, target_phys_addr_t addr) +{ + pflash_t *pfl = opaque; + + return pflash_read(pfl, addr, 4, 1); +} + +static uint32_t pflash_readl_le(void *opaque, target_phys_addr_t addr) +{ + pflash_t *pfl = opaque; + + return pflash_read(pfl, addr, 4, 0); +} + +static void pflash_writeb_be(void *opaque, target_phys_addr_t addr, + uint32_t value) +{ + pflash_write(opaque, addr, value, 1, 1); +} + +static void pflash_writeb_le(void *opaque, target_phys_addr_t addr, + uint32_t value) +{ + pflash_write(opaque, addr, value, 1, 0); +} + +static void pflash_writew_be(void *opaque, target_phys_addr_t addr, + uint32_t value) +{ + pflash_t *pfl = opaque; + + pflash_write(pfl, addr, value, 2, 1); +} + +static void pflash_writew_le(void *opaque, target_phys_addr_t addr, + uint32_t value) +{ + pflash_t *pfl = opaque; + + pflash_write(pfl, addr, value, 2, 0); +} + +static void pflash_writel_be(void *opaque, target_phys_addr_t addr, + uint32_t value) +{ + pflash_t *pfl = opaque; + + pflash_write(pfl, addr, value, 4, 1); +} + +static void pflash_writel_le(void *opaque, target_phys_addr_t addr, + uint32_t value) +{ + pflash_t *pfl = opaque; + + pflash_write(pfl, addr, value, 4, 0); +} + +static CPUWriteMemoryFunc * const pflash_write_ops_be[] = { + &pflash_writeb_be, + &pflash_writew_be, + &pflash_writel_be, +}; + +static CPUReadMemoryFunc * const pflash_read_ops_be[] = { + &pflash_readb_be, + &pflash_readw_be, + &pflash_readl_be, +}; + +static CPUWriteMemoryFunc * const pflash_write_ops_le[] = { + &pflash_writeb_le, + &pflash_writew_le, + &pflash_writel_le, +}; + +static CPUReadMemoryFunc * const pflash_read_ops_le[] = { + &pflash_readb_le, + &pflash_readw_le, + &pflash_readl_le, +}; + +/* Count trailing zeroes of a 32 bits quantity */ +static int ctz32 (uint32_t n) +{ + int ret; + + ret = 0; + if (!(n & 0xFFFF)) { + ret += 16; + n = n >> 16; + } + if (!(n & 0xFF)) { + ret += 8; + n = n >> 8; + } + if (!(n & 0xF)) { + ret += 4; + n = n >> 4; + } + if (!(n & 0x3)) { + ret += 2; + n = n >> 2; + } + if (!(n & 0x1)) { + ret++; +#if 0 /* This is not necessary as n is never 0 */ + n = n >> 1; +#endif + } +#if 0 /* This is not necessary as n is never 0 */ + if (!n) + ret++; +#endif + + return ret; +} + +pflash_t *pflash_cfi02_register(target_phys_addr_t base, ram_addr_t off, + BlockDriverState *bs, 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) +{ + pflash_t *pfl; + int32_t chip_len; + int ret; + + chip_len = sector_len * 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 + pfl = qemu_mallocz(sizeof(pflash_t)); + /* FIXME: Allocate ram ourselves. */ + pfl->storage = qemu_get_ram_ptr(off); + if (be) { + pfl->fl_mem = cpu_register_io_memory(pflash_read_ops_be, + pflash_write_ops_be, + pfl, DEVICE_NATIVE_ENDIAN); + } else { + pfl->fl_mem = cpu_register_io_memory(pflash_read_ops_le, + pflash_write_ops_le, + pfl, DEVICE_NATIVE_ENDIAN); + } + pfl->off = off; + pfl->base = base; + pfl->chip_len = chip_len; + pfl->mappings = nb_mappings; + pflash_register_memory(pfl, 1); + pfl->bs = bs; + if (pfl->bs) { + /* read the initial flash content */ + ret = bdrv_read(pfl->bs, 0, pfl->storage, chip_len >> 9); + if (ret < 0) { + cpu_unregister_io_memory(pfl->fl_mem); + qemu_free(pfl); + return NULL; + } + } +#if 0 /* XXX: there should be a bit to set up read-only, + * the same way the hardware does (with WP pin). + */ + pfl->ro = 1; +#else + pfl->ro = 0; +#endif + pfl->timer = qemu_new_timer(vm_clock, pflash_timer, pfl); + pfl->sector_len = sector_len; + pfl->width = width; + pfl->wcycle = 0; + pfl->cmd = 0; + pfl->status = 0; + pfl->ident[0] = id0; + pfl->ident[1] = id1; + pfl->ident[2] = id2; + pfl->ident[3] = id3; + pfl->unlock_addr[0] = unlock_addr0; + pfl->unlock_addr[1] = unlock_addr1; + /* 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] = nb_blocs - 1; + pfl->cfi_table[0x2E] = (nb_blocs - 1) >> 8; + pfl->cfi_table[0x2F] = sector_len >> 8; + pfl->cfi_table[0x30] = 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; + + return pfl; +} diff --git a/hw/piix4.c b/hw/piix4.c new file mode 100644 index 0000000..72073cd --- /dev/null +++ b/hw/piix4.c @@ -0,0 +1,127 @@ +/* + * 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.h" +#include "pc.h" +#include "pci.h" +#include "isa.h" +#include "sysbus.h" + +PCIDevice *piix4_dev; + +static void piix4_reset(void *opaque) +{ + PCIDevice *d = opaque; + uint8_t *pci_conf = d->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 void piix_save(QEMUFile* f, void *opaque) +{ + PCIDevice *d = opaque; + pci_device_save(d, f); +} + +static int piix_load(QEMUFile* f, void *opaque, int version_id) +{ + PCIDevice *d = opaque; + if (version_id != 2) + return -EINVAL; + return pci_device_load(d, f); +} + +static int piix4_initfn(PCIDevice *d) +{ + uint8_t *pci_conf; + + isa_bus_new(&d->qdev); + register_savevm(&d->qdev, "PIIX4", 0, 2, piix_save, piix_load, d); + + pci_conf = d->config; + pci_config_set_vendor_id(pci_conf, PCI_VENDOR_ID_INTEL); + pci_config_set_device_id(pci_conf, PCI_DEVICE_ID_INTEL_82371AB_0); // 82371AB/EB/MB PIIX4 PCI-to-ISA bridge + pci_config_set_class(pci_conf, PCI_CLASS_BRIDGE_ISA); + + piix4_dev = d; + qemu_register_reset(piix4_reset, d); + return 0; +} + +int piix4_init(PCIBus *bus, int devfn) +{ + PCIDevice *d; + + d = pci_create_simple_multifunction(bus, devfn, true, "PIIX4"); + return d->devfn; +} + +static PCIDeviceInfo piix4_info[] = { + { + .qdev.name = "PIIX4", + .qdev.desc = "ISA bridge", + .qdev.size = sizeof(PCIDevice), + .qdev.no_user = 1, + .no_hotplug = 1, + .init = piix4_initfn, + },{ + /* end of list */ + } +}; + +static void piix4_register(void) +{ + pci_qdev_register_many(piix4_info); +} +device_init(piix4_register); diff --git a/hw/piix_pci.c b/hw/piix_pci.c new file mode 100644 index 0000000..358da58 --- /dev/null +++ b/hw/piix_pci.c @@ -0,0 +1,380 @@ +/* + * 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.h" +#include "pc.h" +#include "pci.h" +#include "pci_host.h" +#include "isa.h" +#include "sysbus.h" +#include "range.h" + +/* + * I440FX chipset data sheet. + * http://download.intel.com/design/chipsets/datashts/29054901.pdf + */ + +typedef PCIHostState I440FXState; + +typedef struct PIIX3State { + PCIDevice dev; + int pci_irq_levels[4]; + qemu_irq *pic; +} PIIX3State; + +struct PCII440FXState { + PCIDevice dev; + target_phys_addr_t isa_page_descs[384 / 4]; + uint8_t smm_enabled; + PIIX3State *piix3; +}; + + +#define I440FX_PAM 0x59 +#define I440FX_PAM_SIZE 7 +#define I440FX_SMRAM 0x72 + +static void piix3_set_irq(void *opaque, int irq_num, int level); + +/* 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 irq_num) +{ + int slot_addend; + slot_addend = (pci_dev->devfn >> 3) - 1; + return (irq_num + slot_addend) & 3; +} + +static void update_pam(PCII440FXState *d, uint32_t start, uint32_t end, int r) +{ + uint32_t addr; + + // printf("ISA mapping %08x-0x%08x: %d\n", start, end, r); + switch(r) { + case 3: + /* RAM */ + cpu_register_physical_memory(start, end - start, + start); + break; + case 1: + /* ROM (XXX: not quite correct) */ + cpu_register_physical_memory(start, end - start, + start | IO_MEM_ROM); + break; + case 2: + case 0: + /* XXX: should distinguish read/write cases */ + for(addr = start; addr < end; addr += 4096) { + cpu_register_physical_memory(addr, 4096, + d->isa_page_descs[(addr - 0xa0000) >> 12]); + } + break; + } +} + +static void i440fx_update_memory_mappings(PCII440FXState *d) +{ + int i, r; + uint32_t smram, addr; + + update_pam(d, 0xf0000, 0x100000, (d->dev.config[I440FX_PAM] >> 4) & 3); + for(i = 0; i < 12; i++) { + r = (d->dev.config[(i >> 1) + (I440FX_PAM + 1)] >> ((i & 1) * 4)) & 3; + update_pam(d, 0xc0000 + 0x4000 * i, 0xc0000 + 0x4000 * (i + 1), r); + } + smram = d->dev.config[I440FX_SMRAM]; + if ((d->smm_enabled && (smram & 0x08)) || (smram & 0x40)) { + cpu_register_physical_memory(0xa0000, 0x20000, 0xa0000); + } else { + for(addr = 0xa0000; addr < 0xc0000; addr += 4096) { + cpu_register_physical_memory(addr, 4096, + d->isa_page_descs[(addr - 0xa0000) >> 12]); + } + } +} + +static void i440fx_set_smm(int val, void *arg) +{ + PCII440FXState *d = arg; + + val = (val != 0); + if (d->smm_enabled != val) { + d->smm_enabled = val; + i440fx_update_memory_mappings(d); + } +} + + +/* XXX: suppress when better memory API. We make the assumption that + no device (in particular the VGA) changes the memory mappings in + the 0xa0000-0x100000 range */ +void i440fx_init_memory_mappings(PCII440FXState *d) +{ + int i; + for(i = 0; i < 96; i++) { + d->isa_page_descs[i] = cpu_get_physical_page_desc(0xa0000 + i * 0x1000); + } +} + +static void i440fx_write_config(PCIDevice *dev, + uint32_t address, uint32_t val, int len) +{ + PCII440FXState *d = DO_UPCAST(PCII440FXState, dev, 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; + int ret, i; + + ret = pci_device_load(&d->dev, f); + if (ret < 0) + return ret; + i440fx_update_memory_mappings(d); + qemu_get_8s(f, &d->smm_enabled); + + if (version_id == 2) + for (i = 0; i < 4; i++) + d->piix3->pci_irq_levels[i] = qemu_get_be32(f); + + 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(dev, PCII440FXState), + VMSTATE_UINT8(smm_enabled, PCII440FXState), + VMSTATE_END_OF_LIST() + } +}; + +static int i440fx_pcihost_initfn(SysBusDevice *dev) +{ + I440FXState *s = FROM_SYSBUS(I440FXState, dev); + + pci_host_conf_register_ioport(0xcf8, s); + + pci_host_data_register_ioport(0xcfc, s); + return 0; +} + +static int i440fx_initfn(PCIDevice *dev) +{ + PCII440FXState *d = DO_UPCAST(PCII440FXState, dev, dev); + + pci_config_set_vendor_id(d->dev.config, PCI_VENDOR_ID_INTEL); + pci_config_set_device_id(d->dev.config, PCI_DEVICE_ID_INTEL_82441); + d->dev.config[0x08] = 0x02; // revision + pci_config_set_class(d->dev.config, PCI_CLASS_BRIDGE_HOST); + + d->dev.config[I440FX_SMRAM] = 0x02; + + cpu_smm_register(&i440fx_set_smm, d); + return 0; +} + +PCIBus *i440fx_init(PCII440FXState **pi440fx_state, int *piix3_devfn, qemu_irq *pic, ram_addr_t ram_size) +{ + DeviceState *dev; + PCIBus *b; + PCIDevice *d; + I440FXState *s; + PIIX3State *piix3; + + dev = qdev_create(NULL, "i440FX-pcihost"); + s = FROM_SYSBUS(I440FXState, sysbus_from_qdev(dev)); + b = pci_bus_new(&s->busdev.qdev, NULL, 0); + s->bus = b; + qdev_init_nofail(dev); + + d = pci_create_simple(b, 0, "i440FX"); + *pi440fx_state = DO_UPCAST(PCII440FXState, dev, d); + + piix3 = DO_UPCAST(PIIX3State, dev, + pci_create_simple_multifunction(b, -1, true, "PIIX3")); + piix3->pic = pic; + pci_bus_irqs(b, piix3_set_irq, pci_slot_get_pirq, piix3, 4); + (*pi440fx_state)->piix3 = piix3; + + *piix3_devfn = piix3->dev.devfn; + + ram_size = ram_size / 8 / 1024 / 1024; + if (ram_size > 255) + ram_size = 255; + (*pi440fx_state)->dev.config[0x57]=ram_size; + + return b; +} + +/* PIIX3 PCI to ISA bridge */ + +static void piix3_set_irq(void *opaque, int irq_num, int level) +{ + int i, pic_irq, pic_level; + PIIX3State *piix3 = opaque; + + piix3->pci_irq_levels[irq_num] = level; + + /* now we change the pic irq level according to the piix irq mappings */ + /* XXX: optimize */ + pic_irq = piix3->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 == piix3->dev.config[0x60 + i]) + pic_level |= piix3->pci_irq_levels[i]; + } + qemu_set_irq(piix3->pic[pic_irq], pic_level); + } +} + +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; + + memset(d->pci_irq_levels, 0, sizeof(d->pci_irq_levels)); +} + +static const VMStateDescription vmstate_piix3 = { + .name = "PIIX3", + .version_id = 3, + .minimum_version_id = 2, + .minimum_version_id_old = 2, + .fields = (VMStateField []) { + VMSTATE_PCI_DEVICE(dev, PIIX3State), + VMSTATE_INT32_ARRAY_V(pci_irq_levels, PIIX3State, 4, 3), + VMSTATE_END_OF_LIST() + } +}; + +static int piix3_initfn(PCIDevice *dev) +{ + PIIX3State *d = DO_UPCAST(PIIX3State, dev, dev); + uint8_t *pci_conf; + + isa_bus_new(&d->dev.qdev); + + pci_conf = d->dev.config; + pci_config_set_vendor_id(pci_conf, PCI_VENDOR_ID_INTEL); + pci_config_set_device_id(pci_conf, PCI_DEVICE_ID_INTEL_82371SB_0); // 82371SB PIIX3 PCI-to-ISA bridge (Step A1) + pci_config_set_class(pci_conf, PCI_CLASS_BRIDGE_ISA); + + qemu_register_reset(piix3_reset, d); + return 0; +} + +static PCIDeviceInfo i440fx_info[] = { + { + .qdev.name = "i440FX", + .qdev.desc = "Host bridge", + .qdev.size = sizeof(PCII440FXState), + .qdev.vmsd = &vmstate_i440fx, + .qdev.no_user = 1, + .no_hotplug = 1, + .init = i440fx_initfn, + .config_write = i440fx_write_config, + },{ + .qdev.name = "PIIX3", + .qdev.desc = "ISA bridge", + .qdev.size = sizeof(PIIX3State), + .qdev.vmsd = &vmstate_piix3, + .qdev.no_user = 1, + .no_hotplug = 1, + .init = piix3_initfn, + },{ + /* end of list */ + } +}; + +static SysBusDeviceInfo i440fx_pcihost_info = { + .init = i440fx_pcihost_initfn, + .qdev.name = "i440FX-pcihost", + .qdev.fw_name = "pci", + .qdev.size = sizeof(I440FXState), + .qdev.no_user = 1, +}; + +static void i440fx_register(void) +{ + sysbus_register_withprop(&i440fx_pcihost_info); + pci_qdev_register_many(i440fx_info); +} +device_init(i440fx_register); diff --git a/hw/pixel_ops.h b/hw/pixel_ops.h new file mode 100644 index 0000000..d390adf --- /dev/null +++ b/hw/pixel_ops.h @@ -0,0 +1,53 @@ +static inline unsigned int rgb_to_pixel8(unsigned int r, unsigned int g, + unsigned int b) +{ + return ((r >> 5) << 5) | ((g >> 5) << 2) | (b >> 6); +} + +static inline unsigned int rgb_to_pixel15(unsigned int r, unsigned int g, + unsigned int b) +{ + return ((r >> 3) << 10) | ((g >> 3) << 5) | (b >> 3); +} + +static inline unsigned int rgb_to_pixel15bgr(unsigned int r, unsigned int g, + unsigned int b) +{ + return ((b >> 3) << 10) | ((g >> 3) << 5) | (r >> 3); +} + +static inline unsigned int rgb_to_pixel16(unsigned int r, unsigned int g, + unsigned int b) +{ + return ((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3); +} + +static inline unsigned int rgb_to_pixel16bgr(unsigned int r, unsigned int g, + unsigned int b) +{ + return ((b >> 3) << 11) | ((g >> 2) << 5) | (r >> 3); +} + +static inline unsigned int rgb_to_pixel24(unsigned int r, unsigned int g, + unsigned int b) +{ + return (r << 16) | (g << 8) | b; +} + +static inline unsigned int rgb_to_pixel24bgr(unsigned int r, unsigned int g, + unsigned int b) +{ + return (b << 16) | (g << 8) | r; +} + +static inline unsigned int rgb_to_pixel32(unsigned int r, unsigned int g, + unsigned int b) +{ + return (r << 16) | (g << 8) | b; +} + +static inline unsigned int rgb_to_pixel32bgr(unsigned int r, unsigned int g, + unsigned int b) +{ + return (b << 16) | (g << 8) | r; +} diff --git a/hw/pl011.c b/hw/pl011.c new file mode 100644 index 0000000..77f0dbf --- /dev/null +++ b/hw/pl011.c @@ -0,0 +1,332 @@ +/* + * Arm PrimeCell PL011 UART + * + * Copyright (c) 2006 CodeSourcery. + * Written by Paul Brook + * + * This code is licenced under the GPL. + */ + +#include "sysbus.h" +#include "qemu-char.h" + +typedef struct { + SysBusDevice busdev; + uint32_t readbuff; + uint32_t flags; + uint32_t lcr; + 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; +} pl011_state; + +#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(pl011_state *s) +{ + uint32_t flags; + + flags = s->int_level & s->int_enabled; + qemu_set_irq(s->irq, flags != 0); +} + +static uint32_t pl011_read(void *opaque, target_phys_addr_t offset) +{ + pl011_state *s = (pl011_state *)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; + pl011_update(s); + qemu_chr_accept_input(s->chr); + return c; + case 1: /* UARTCR */ + return 0; + 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: + hw_error("pl011_read: Bad offset %x\n", (int)offset); + return 0; + } +} + +static void pl011_set_read_trigger(pl011_state *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, target_phys_addr_t offset, + uint32_t value) +{ + pl011_state *s = (pl011_state *)opaque; + unsigned char ch; + + switch (offset >> 2) { + case 0: /* UARTDR */ + /* ??? Check if transmitter is enabled. */ + ch = value; + if (s->chr) + qemu_chr_write(s->chr, &ch, 1); + s->int_level |= PL011_INT_TX; + pl011_update(s); + break; + case 1: /* UARTCR */ + s->cr = value; + 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 */ + 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) + hw_error("PL011: DMA not implemented\n"); + break; + default: + hw_error("pl011_write: Bad offset %x\n", (int)offset); + } +} + +static int pl011_can_receive(void *opaque) +{ + pl011_state *s = (pl011_state *)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) +{ + pl011_state *s = (pl011_state *)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->cr & 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 CPUReadMemoryFunc * const pl011_readfn[] = { + pl011_read, + pl011_read, + pl011_read +}; + +static CPUWriteMemoryFunc * const pl011_writefn[] = { + pl011_write, + pl011_write, + pl011_write +}; + +static void pl011_save(QEMUFile *f, void *opaque) +{ + pl011_state *s = (pl011_state *)opaque; + int i; + + qemu_put_be32(f, s->readbuff); + qemu_put_be32(f, s->flags); + qemu_put_be32(f, s->lcr); + qemu_put_be32(f, s->cr); + qemu_put_be32(f, s->dmacr); + qemu_put_be32(f, s->int_enabled); + qemu_put_be32(f, s->int_level); + for (i = 0; i < 16; i++) + qemu_put_be32(f, s->read_fifo[i]); + qemu_put_be32(f, s->ilpr); + qemu_put_be32(f, s->ibrd); + qemu_put_be32(f, s->fbrd); + qemu_put_be32(f, s->ifl); + qemu_put_be32(f, s->read_pos); + qemu_put_be32(f, s->read_count); + qemu_put_be32(f, s->read_trigger); +} + +static int pl011_load(QEMUFile *f, void *opaque, int version_id) +{ + pl011_state *s = (pl011_state *)opaque; + int i; + + if (version_id != 1) + return -EINVAL; + + s->readbuff = qemu_get_be32(f); + s->flags = qemu_get_be32(f); + s->lcr = qemu_get_be32(f); + s->cr = qemu_get_be32(f); + s->dmacr = qemu_get_be32(f); + s->int_enabled = qemu_get_be32(f); + s->int_level = qemu_get_be32(f); + for (i = 0; i < 16; i++) + s->read_fifo[i] = qemu_get_be32(f); + s->ilpr = qemu_get_be32(f); + s->ibrd = qemu_get_be32(f); + s->fbrd = qemu_get_be32(f); + s->ifl = qemu_get_be32(f); + s->read_pos = qemu_get_be32(f); + s->read_count = qemu_get_be32(f); + s->read_trigger = qemu_get_be32(f); + + return 0; +} + +static int pl011_init(SysBusDevice *dev, const unsigned char *id) +{ + int iomemtype; + pl011_state *s = FROM_SYSBUS(pl011_state, dev); + + iomemtype = cpu_register_io_memory(pl011_readfn, + pl011_writefn, s, + DEVICE_NATIVE_ENDIAN); + sysbus_init_mmio(dev, 0x1000,iomemtype); + sysbus_init_irq(dev, &s->irq); + s->id = id; + s->chr = qdev_init_chardev(&dev->qdev); + + s->read_trigger = 1; + s->ifl = 0x12; + s->cr = 0x300; + s->flags = 0x90; + if (s->chr) { + qemu_chr_add_handlers(s->chr, pl011_can_receive, pl011_receive, + pl011_event, s); + } + register_savevm(&dev->qdev, "pl011_uart", -1, 1, pl011_save, pl011_load, s); + return 0; +} + +static int pl011_init_arm(SysBusDevice *dev) +{ + return pl011_init(dev, pl011_id_arm); +} + +static int pl011_init_luminary(SysBusDevice *dev) +{ + return pl011_init(dev, pl011_id_luminary); +} + +static void pl011_register_devices(void) +{ + sysbus_register_dev("pl011", sizeof(pl011_state), + pl011_init_arm); + sysbus_register_dev("pl011_luminary", sizeof(pl011_state), + pl011_init_luminary); +} + +device_init(pl011_register_devices) diff --git a/hw/pl022.c b/hw/pl022.c new file mode 100644 index 0000000..ffe05ab --- /dev/null +++ b/hw/pl022.c @@ -0,0 +1,312 @@ +/* + * Arm PrimeCell PL022 Synchronous Serial Port + * + * Copyright (c) 2007 CodeSourcery. + * Written by Paul Brook + * + * This code is licenced under the GPL. + */ + +#include "sysbus.h" +#include "ssi.h" +#include "primecell.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 + +typedef struct { + SysBusDevice busdev; + 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; +} pl022_state; + +static const unsigned char pl022_id[8] = + { 0x22, 0x10, 0x04, 0x00, 0x0d, 0xf0, 0x05, 0xb1 }; + +static void pl022_update(pl022_state *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(pl022_state *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 uint32_t pl022_read(void *opaque, target_phys_addr_t offset) +{ + pl022_state *s = (pl022_state *)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: + hw_error("pl022_read: Bad offset %x\n", (int)offset); + return 0; + } +} + +static void pl022_write(void *opaque, target_phys_addr_t offset, + uint32_t value) +{ + pl022_state *s = (pl022_state *)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", 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) { + hw_error("pl022: DMA not implemented\n"); + } + break; + default: + hw_error("pl022_write: Bad offset %x\n", (int)offset); + } +} + +static void pl022_reset(pl022_state *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 CPUReadMemoryFunc * const pl022_readfn[] = { + pl022_read, + pl022_read, + pl022_read +}; + +static CPUWriteMemoryFunc * const pl022_writefn[] = { + pl022_write, + pl022_write, + pl022_write +}; + +static void pl022_save(QEMUFile *f, void *opaque) +{ + pl022_state *s = (pl022_state *)opaque; + int i; + + qemu_put_be32(f, s->cr0); + qemu_put_be32(f, s->cr1); + qemu_put_be32(f, s->bitmask); + qemu_put_be32(f, s->sr); + qemu_put_be32(f, s->cpsr); + qemu_put_be32(f, s->is); + qemu_put_be32(f, s->im); + qemu_put_be32(f, s->tx_fifo_head); + qemu_put_be32(f, s->rx_fifo_head); + qemu_put_be32(f, s->tx_fifo_len); + qemu_put_be32(f, s->rx_fifo_len); + for (i = 0; i < 8; i++) { + qemu_put_be16(f, s->tx_fifo[i]); + qemu_put_be16(f, s->rx_fifo[i]); + } +} + +static int pl022_load(QEMUFile *f, void *opaque, int version_id) +{ + pl022_state *s = (pl022_state *)opaque; + int i; + + if (version_id != 1) + return -EINVAL; + + s->cr0 = qemu_get_be32(f); + s->cr1 = qemu_get_be32(f); + s->bitmask = qemu_get_be32(f); + s->sr = qemu_get_be32(f); + s->cpsr = qemu_get_be32(f); + s->is = qemu_get_be32(f); + s->im = qemu_get_be32(f); + s->tx_fifo_head = qemu_get_be32(f); + s->rx_fifo_head = qemu_get_be32(f); + s->tx_fifo_len = qemu_get_be32(f); + s->rx_fifo_len = qemu_get_be32(f); + for (i = 0; i < 8; i++) { + s->tx_fifo[i] = qemu_get_be16(f); + s->rx_fifo[i] = qemu_get_be16(f); + } + + return 0; +} + +static int pl022_init(SysBusDevice *dev) +{ + pl022_state *s = FROM_SYSBUS(pl022_state, dev); + int iomemtype; + + iomemtype = cpu_register_io_memory(pl022_readfn, + pl022_writefn, s, + DEVICE_NATIVE_ENDIAN); + sysbus_init_mmio(dev, 0x1000, iomemtype); + sysbus_init_irq(dev, &s->irq); + s->ssi = ssi_create_bus(&dev->qdev, "ssi"); + pl022_reset(s); + register_savevm(&dev->qdev, "pl022_ssp", -1, 1, pl022_save, pl022_load, s); + return 0; +} + +static void pl022_register_devices(void) +{ + sysbus_register_dev("pl022", sizeof(pl022_state), pl022_init); +} + +device_init(pl022_register_devices) diff --git a/hw/pl031.c b/hw/pl031.c new file mode 100644 index 0000000..c488f69 --- /dev/null +++ b/hw/pl031.c @@ -0,0 +1,237 @@ +/* + * 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. + * + */ + +#include "sysbus.h" +#include "qemu-timer.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 */ + +typedef struct { + SysBusDevice busdev; + QEMUTimer *timer; + qemu_irq irq; + + uint32_t tick_offset; + + uint32_t mr; + uint32_t lr; + uint32_t cr; + uint32_t im; + uint32_t is; +} pl031_state; + +static const VMStateDescription vmstate_pl031 = { + .name = "pl031", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32(tick_offset, pl031_state), + VMSTATE_UINT32(mr, pl031_state), + VMSTATE_UINT32(lr, pl031_state), + VMSTATE_UINT32(cr, pl031_state), + VMSTATE_UINT32(im, pl031_state), + VMSTATE_UINT32(is, pl031_state), + VMSTATE_END_OF_LIST() + } +}; + +static const unsigned char pl031_id[] = { + 0x31, 0x10, 0x14, 0x00, /* Device ID */ + 0x0d, 0xf0, 0x05, 0xb1 /* Cell ID */ +}; + +static void pl031_update(pl031_state *s) +{ + qemu_set_irq(s->irq, s->is & s->im); +} + +static void pl031_interrupt(void * opaque) +{ + pl031_state *s = (pl031_state *)opaque; + + s->im = 1; + DPRINTF("Alarm raised\n"); + pl031_update(s); +} + +static uint32_t pl031_get_count(pl031_state *s) +{ + /* This assumes qemu_get_clock returns the time since the machine was + created. */ + return s->tick_offset + qemu_get_clock(vm_clock) / get_ticks_per_sec(); +} + +static void pl031_set_alarm(pl031_state *s) +{ + int64_t now; + uint32_t ticks; + + now = qemu_get_clock(vm_clock); + ticks = s->tick_offset + now / get_ticks_per_sec(); + + /* The timer wraps around. This subtraction also wraps in the same way, + and gives correct results when alarm < now_ticks. */ + ticks = s->mr - ticks; + DPRINTF("Alarm set in %ud ticks\n", ticks); + if (ticks == 0) { + qemu_del_timer(s->timer); + pl031_interrupt(s); + } else { + qemu_mod_timer(s->timer, now + (int64_t)ticks * get_ticks_per_sec()); + } +} + +static uint32_t pl031_read(void *opaque, target_phys_addr_t offset) +{ + pl031_state *s = (pl031_state *)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: + fprintf(stderr, "qemu: pl031_read: Unexpected offset 0x%x\n", + (int)offset); + break; + default: + hw_error("pl031_read: Bad offset 0x%x\n", (int)offset); + break; + } + + return 0; +} + +static void pl031_write(void * opaque, target_phys_addr_t offset, + uint32_t value) +{ + pl031_state *s = (pl031_state *)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 interupt 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: + fprintf(stderr, "qemu: pl031_write: Unexpected offset 0x%x\n", + (int)offset); + break; + + default: + hw_error("pl031_write: Bad offset 0x%x\n", (int)offset); + break; + } +} + +static CPUWriteMemoryFunc * const pl031_writefn[] = { + pl031_write, + pl031_write, + pl031_write +}; + +static CPUReadMemoryFunc * const pl031_readfn[] = { + pl031_read, + pl031_read, + pl031_read +}; + +static int pl031_init(SysBusDevice *dev) +{ + int iomemtype; + pl031_state *s = FROM_SYSBUS(pl031_state, dev); + struct tm tm; + + iomemtype = cpu_register_io_memory(pl031_readfn, pl031_writefn, s, + DEVICE_NATIVE_ENDIAN); + if (iomemtype == -1) { + hw_error("pl031_init: Can't register I/O memory\n"); + } + + sysbus_init_mmio(dev, 0x1000, iomemtype); + + sysbus_init_irq(dev, &s->irq); + /* ??? We assume vm_clock is zero at this point. */ + qemu_get_timedate(&tm, 0); + s->tick_offset = mktimegm(&tm); + + s->timer = qemu_new_timer(vm_clock, pl031_interrupt, s); + return 0; +} + +static SysBusDeviceInfo pl031_info = { + .init = pl031_init, + .qdev.name = "pl031", + .qdev.size = sizeof(pl031_state), + .qdev.vmsd = &vmstate_pl031, + .qdev.no_user = 1, +}; + +static void pl031_register_devices(void) +{ + sysbus_register_withprop(&pl031_info); +} + +device_init(pl031_register_devices) diff --git a/hw/pl050.c b/hw/pl050.c new file mode 100644 index 0000000..b155cc0 --- /dev/null +++ b/hw/pl050.c @@ -0,0 +1,187 @@ +/* + * Arm PrimeCell PL050 Keyboard / Mouse Interface + * + * Copyright (c) 2006-2007 CodeSourcery. + * Written by Paul Brook + * + * This code is licenced under the GPL. + */ + +#include "sysbus.h" +#include "ps2.h" + +typedef struct { + SysBusDevice busdev; + void *dev; + uint32_t cr; + uint32_t clk; + uint32_t last; + int pending; + qemu_irq irq; + int is_mouse; +} pl050_state; + +static const VMStateDescription vmstate_pl050 = { + .name = "pl050", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32(cr, pl050_state), + VMSTATE_UINT32(clk, pl050_state), + VMSTATE_UINT32(last, pl050_state), + VMSTATE_INT32(pending, pl050_state), + VMSTATE_INT32(is_mouse, pl050_state), + 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) +{ + pl050_state *s = (pl050_state *)opaque; + int raise; + + s->pending = level; + raise = (s->pending && (s->cr & 0x10) != 0) + || (s->cr & 0x08) != 0; + qemu_set_irq(s->irq, raise); +} + +static uint32_t pl050_read(void *opaque, target_phys_addr_t offset) +{ + pl050_state *s = (pl050_state *)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: + hw_error("pl050_read: Bad offset %x\n", (int)offset); + return 0; + } +} + +static void pl050_write(void *opaque, target_phys_addr_t offset, + uint32_t value) +{ + pl050_state *s = (pl050_state *)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: + hw_error("pl050_write: Bad offset %x\n", (int)offset); + } +} +static CPUReadMemoryFunc * const pl050_readfn[] = { + pl050_read, + pl050_read, + pl050_read +}; + +static CPUWriteMemoryFunc * const pl050_writefn[] = { + pl050_write, + pl050_write, + pl050_write +}; + +static int pl050_init(SysBusDevice *dev, int is_mouse) +{ + pl050_state *s = FROM_SYSBUS(pl050_state, dev); + int iomemtype; + + iomemtype = cpu_register_io_memory(pl050_readfn, + pl050_writefn, s, + DEVICE_NATIVE_ENDIAN); + sysbus_init_mmio(dev, 0x1000, iomemtype); + sysbus_init_irq(dev, &s->irq); + s->is_mouse = is_mouse; + if (s->is_mouse) + s->dev = ps2_mouse_init(pl050_update, s); + else + s->dev = ps2_kbd_init(pl050_update, s); + return 0; +} + +static int pl050_init_keyboard(SysBusDevice *dev) +{ + return pl050_init(dev, 0); +} + +static int pl050_init_mouse(SysBusDevice *dev) +{ + return pl050_init(dev, 1); +} + +static SysBusDeviceInfo pl050_kbd_info = { + .init = pl050_init_keyboard, + .qdev.name = "pl050_keyboard", + .qdev.size = sizeof(pl050_state), + .qdev.vmsd = &vmstate_pl050, +}; + +static SysBusDeviceInfo pl050_mouse_info = { + .init = pl050_init_mouse, + .qdev.name = "pl050_mouse", + .qdev.size = sizeof(pl050_state), + .qdev.vmsd = &vmstate_pl050, +}; + +static void pl050_register_devices(void) +{ + sysbus_register_withprop(&pl050_kbd_info); + sysbus_register_withprop(&pl050_mouse_info); +} + +device_init(pl050_register_devices) diff --git a/hw/pl061.c b/hw/pl061.c new file mode 100644 index 0000000..1997b7c --- /dev/null +++ b/hw/pl061.c @@ -0,0 +1,317 @@ +/* + * Arm PrimeCell PL061 General Purpose IO with additional + * Luminary Micro Stellaris bits. + * + * Copyright (c) 2007 CodeSourcery. + * Written by Paul Brook + * + * This code is licenced under the GPL. + */ + +#include "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, 0x00, 0x18, 0x01, 0x0d, 0xf0, 0x05, 0xb1 }; + +typedef struct { + SysBusDevice busdev; + int locked; + uint8_t data; + uint8_t old_data; + uint8_t dir; + uint8_t isense; + uint8_t ibe; + uint8_t iev; + uint8_t im; + uint8_t istate; + uint8_t afsel; + uint8_t dr2r; + uint8_t dr4r; + uint8_t dr8r; + uint8_t odr; + uint8_t pur; + uint8_t pdr; + uint8_t slr; + uint8_t den; + uint8_t cr; + uint8_t float_high; + qemu_irq irq; + qemu_irq out[8]; +} pl061_state; + +static void pl061_update(pl061_state *s) +{ + uint8_t changed; + uint8_t mask; + uint8_t out; + int i; + + /* Outputs float high. */ + /* FIXME: This is board dependent. */ + out = (s->data & s->dir) | ~s->dir; + changed = s->old_data ^ out; + if (!changed) + return; + + s->old_data = out; + for (i = 0; i < 8; i++) { + mask = 1 << i; + if ((changed & mask) && s->out) { + DPRINTF("Set output %d = %d\n", i, (out & mask) != 0); + qemu_set_irq(s->out[i], (out & mask) != 0); + } + } + + /* FIXME: Implement input interrupts. */ +} + +static uint32_t pl061_read(void *opaque, target_phys_addr_t offset) +{ + pl061_state *s = (pl061_state *)opaque; + + if (offset >= 0xfd0 && offset < 0x1000) { + return pl061_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: /* Interupt 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; + default: + hw_error("pl061_read: Bad offset %x\n", (int)offset); + return 0; + } +} + +static void pl061_write(void *opaque, target_phys_addr_t offset, + uint32_t value) +{ + pl061_state *s = (pl061_state *)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; + break; + case 0x404: /* Interrupt sense */ + s->isense = value; + break; + case 0x408: /* Interrupt both edges */ + s->ibe = value; + break; + case 0x40c: /* Interupt event */ + s->iev = value; + break; + case 0x410: /* Interrupt mask */ + s->im = value; + 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; + break; + case 0x504: /* 4mA drive */ + s->dr4r = value; + break; + case 0x508: /* 8mA drive */ + s->dr8r = value; + break; + case 0x50c: /* Open drain */ + s->odr = value; + break; + case 0x510: /* Pull-up */ + s->pur = value; + break; + case 0x514: /* Pull-down */ + s->pdr = value; + break; + case 0x518: /* Slew rate control */ + s->slr = value; + break; + case 0x51c: /* Digital enable */ + s->den = value; + break; + case 0x520: /* Lock */ + s->locked = (value != 0xacce551); + break; + case 0x524: /* Commit */ + if (!s->locked) + s->cr = value; + break; + default: + hw_error("pl061_write: Bad offset %x\n", (int)offset); + } + pl061_update(s); +} + +static void pl061_reset(pl061_state *s) +{ + s->locked = 1; + s->cr = 0xff; +} + +static void pl061_set_irq(void * opaque, int irq, int level) +{ + pl061_state *s = (pl061_state *)opaque; + uint8_t mask; + + mask = 1 << irq; + if ((s->dir & mask) == 0) { + s->data &= ~mask; + if (level) + s->data |= mask; + pl061_update(s); + } +} + +static CPUReadMemoryFunc * const pl061_readfn[] = { + pl061_read, + pl061_read, + pl061_read +}; + +static CPUWriteMemoryFunc * const pl061_writefn[] = { + pl061_write, + pl061_write, + pl061_write +}; + +static void pl061_save(QEMUFile *f, void *opaque) +{ + pl061_state *s = (pl061_state *)opaque; + + qemu_put_be32(f, s->locked); + qemu_put_be32(f, s->data); + qemu_put_be32(f, s->old_data); + qemu_put_be32(f, s->dir); + qemu_put_be32(f, s->isense); + qemu_put_be32(f, s->ibe); + qemu_put_be32(f, s->iev); + qemu_put_be32(f, s->im); + qemu_put_be32(f, s->istate); + qemu_put_be32(f, s->afsel); + qemu_put_be32(f, s->dr2r); + qemu_put_be32(f, s->dr4r); + qemu_put_be32(f, s->dr8r); + qemu_put_be32(f, s->odr); + qemu_put_be32(f, s->pur); + qemu_put_be32(f, s->pdr); + qemu_put_be32(f, s->slr); + qemu_put_be32(f, s->den); + qemu_put_be32(f, s->cr); + qemu_put_be32(f, s->float_high); +} + +static int pl061_load(QEMUFile *f, void *opaque, int version_id) +{ + pl061_state *s = (pl061_state *)opaque; + if (version_id != 1) + return -EINVAL; + + s->locked = qemu_get_be32(f); + s->data = qemu_get_be32(f); + s->old_data = qemu_get_be32(f); + s->dir = qemu_get_be32(f); + s->isense = qemu_get_be32(f); + s->ibe = qemu_get_be32(f); + s->iev = qemu_get_be32(f); + s->im = qemu_get_be32(f); + s->istate = qemu_get_be32(f); + s->afsel = qemu_get_be32(f); + s->dr2r = qemu_get_be32(f); + s->dr4r = qemu_get_be32(f); + s->dr8r = qemu_get_be32(f); + s->odr = qemu_get_be32(f); + s->pur = qemu_get_be32(f); + s->pdr = qemu_get_be32(f); + s->slr = qemu_get_be32(f); + s->den = qemu_get_be32(f); + s->cr = qemu_get_be32(f); + s->float_high = qemu_get_be32(f); + + return 0; +} + +static int pl061_init(SysBusDevice *dev) +{ + int iomemtype; + pl061_state *s = FROM_SYSBUS(pl061_state, dev); + + iomemtype = cpu_register_io_memory(pl061_readfn, + pl061_writefn, s, + DEVICE_NATIVE_ENDIAN); + sysbus_init_mmio(dev, 0x1000, iomemtype); + sysbus_init_irq(dev, &s->irq); + qdev_init_gpio_in(&dev->qdev, pl061_set_irq, 8); + qdev_init_gpio_out(&dev->qdev, s->out, 8); + pl061_reset(s); + register_savevm(&dev->qdev, "pl061_gpio", -1, 1, pl061_save, pl061_load, s); + return 0; +} + +static void pl061_register_devices(void) +{ + sysbus_register_dev("pl061", sizeof(pl061_state), + pl061_init); +} + +device_init(pl061_register_devices) diff --git a/hw/pl080.c b/hw/pl080.c new file mode 100644 index 0000000..901f04a --- /dev/null +++ b/hw/pl080.c @@ -0,0 +1,407 @@ +/* + * Arm PrimeCell PL080/PL081 DMA controller + * + * Copyright (c) 2006 CodeSourcery. + * Written by Paul Brook + * + * This code is licenced under the GPL. + */ + +#include "sysbus.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; + +typedef struct { + SysBusDevice busdev; + 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; +} pl080_state; + +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, pl080_state), + VMSTATE_UINT8(tc_mask, pl080_state), + VMSTATE_UINT8(err_int, pl080_state), + VMSTATE_UINT8(err_mask, pl080_state), + VMSTATE_UINT32(conf, pl080_state), + VMSTATE_UINT32(sync, pl080_state), + VMSTATE_UINT32(req_single, pl080_state), + VMSTATE_UINT32(req_burst, pl080_state), + VMSTATE_UINT8(tc_int, pl080_state), + VMSTATE_UINT8(tc_int, pl080_state), + VMSTATE_UINT8(tc_int, pl080_state), + VMSTATE_STRUCT_ARRAY(chan, pl080_state, PL080_MAX_CHANNELS, + 1, vmstate_pl080_channel, pl080_channel), + VMSTATE_INT32(running, pl080_state), + 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(pl080_state *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(pl080_state *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 = ldl_phys(ch->lli); + ch->dest = ldl_phys(ch->lli + 4); + ch->ctrl = ldl_phys(ch->lli + 12); + ch->lli = ldl_phys(ch->lli + 8); + } 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 uint32_t pl080_read(void *opaque, target_phys_addr_t offset) +{ + pl080_state *s = (pl080_state *)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: + hw_error("pl080_read: Bad offset %x\n", (int)offset); + return 0; + } +} + +static void pl080_write(void *opaque, target_phys_addr_t offset, + uint32_t value) +{ + pl080_state *s = (pl080_state *)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. */ + hw_error("pl080_write: Soft DMA not implemented\n"); + break; + case 12: /* Configuration */ + s->conf = value; + if (s->conf & (PL080_CONF_M1 | PL080_CONF_M1)) { + hw_error("pl080_write: Big-endian DMA not implemented\n"); + } + pl080_run(s); + break; + case 13: /* Sync */ + s->sync = value; + break; + default: + bad_offset: + hw_error("pl080_write: Bad offset %x\n", (int)offset); + } + pl080_update(s); +} + +static CPUReadMemoryFunc * const pl080_readfn[] = { + pl080_read, + pl080_read, + pl080_read +}; + +static CPUWriteMemoryFunc * const pl080_writefn[] = { + pl080_write, + pl080_write, + pl080_write +}; + +static int pl08x_init(SysBusDevice *dev, int nchannels) +{ + int iomemtype; + pl080_state *s = FROM_SYSBUS(pl080_state, dev); + + iomemtype = cpu_register_io_memory(pl080_readfn, + pl080_writefn, s, + DEVICE_NATIVE_ENDIAN); + sysbus_init_mmio(dev, 0x1000, iomemtype); + sysbus_init_irq(dev, &s->irq); + s->nchannels = nchannels; + return 0; +} + +static int pl080_init(SysBusDevice *dev) +{ + return pl08x_init(dev, 8); +} + +static int pl081_init(SysBusDevice *dev) +{ + return pl08x_init(dev, 2); +} + +static SysBusDeviceInfo pl080_info = { + .init = pl080_init, + .qdev.name = "pl080", + .qdev.size = sizeof(pl080_state), + .qdev.vmsd = &vmstate_pl080, + .qdev.no_user = 1, +}; + +static SysBusDeviceInfo pl081_info = { + .init = pl081_init, + .qdev.name = "pl081", + .qdev.size = sizeof(pl080_state), + .qdev.vmsd = &vmstate_pl080, + .qdev.no_user = 1, +}; + +/* The PL080 and PL081 are the same except for the number of channels + they implement (8 and 2 respectively). */ +static void pl080_register_devices(void) +{ + sysbus_register_withprop(&pl080_info); + sysbus_register_withprop(&pl081_info); +} + +device_init(pl080_register_devices) diff --git a/hw/pl110.c b/hw/pl110.c new file mode 100644 index 0000000..06d2dfa --- /dev/null +++ b/hw/pl110.c @@ -0,0 +1,422 @@ +/* + * Arm PrimeCell PL110 Color LCD Controller + * + * Copyright (c) 2005-2009 CodeSourcery. + * Written by Paul Brook + * + * This code is licenced under the GNU LGPL + */ + +#include "sysbus.h" +#include "console.h" +#include "framebuffer.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 +}; + +typedef struct { + SysBusDevice busdev; + DisplayState *ds; + + /* The Versatile/PB uses a slightly modified PL110 controller. */ + int versatile; + 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 pallette[256]; + uint32_t raw_pallette[128]; + qemu_irq irq; +} pl110_state; + +static const VMStateDescription vmstate_pl110 = { + .name = "pl110", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_INT32(versatile, pl110_state), + VMSTATE_UINT32_ARRAY(timing, pl110_state, 4), + VMSTATE_UINT32(cr, pl110_state), + VMSTATE_UINT32(upbase, pl110_state), + VMSTATE_UINT32(lpbase, pl110_state), + VMSTATE_UINT32(int_status, pl110_state), + VMSTATE_UINT32(int_mask, pl110_state), + VMSTATE_INT32(cols, pl110_state), + VMSTATE_INT32(rows, pl110_state), + VMSTATE_UINT32(bpp, pl110_state), + VMSTATE_INT32(invalidate, pl110_state), + VMSTATE_UINT32_ARRAY(pallette, pl110_state, 256), + VMSTATE_UINT32_ARRAY(raw_pallette, pl110_state, 128), + VMSTATE_END_OF_LIST() + } +}; + +static const unsigned char pl110_id[] = +{ 0x10, 0x11, 0x04, 0x00, 0x0d, 0xf0, 0x05, 0xb1 }; + +/* The Arm documentation (DDI0224C) says the CLDC on the Versatile board + has a different ID. However Linux only looks for the normal ID. */ +#if 0 +static const unsigned char pl110_versatile_id[] = +{ 0x93, 0x10, 0x04, 0x00, 0x0d, 0xf0, 0x05, 0xb1 }; +#else +#define pl110_versatile_id pl110_id +#endif + +#include "pixel_ops.h" + +#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(pl110_state *s) +{ + return (s->cr & PL110_CR_EN) && (s->cr & PL110_CR_PWR); +} + +static void pl110_update_display(void *opaque) +{ + pl110_state *s = (pl110_state *)opaque; + drawfn* fntable; + drawfn fn; + int dest_width; + int src_width; + int bpp_offset; + int first; + int last; + + if (!pl110_enabled(s)) + return; + + switch (ds_get_bits_per_pixel(s->ds)) { + 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 = 18; + + if (s->cr & PL110_CR_BEBO) + fn = fntable[s->bpp + 6 + bpp_offset]; + else if (s->cr & PL110_CR_BEPO) + fn = fntable[s->bpp + 12 + 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: + src_width <<= 1; + break; + case BPP_32: + src_width <<= 2; + break; + } + dest_width *= s->cols; + first = 0; + framebuffer_update_display(s->ds, + s->upbase, s->cols, s->rows, + src_width, dest_width, 0, + s->invalidate, + fn, s->pallette, + &first, &last); + if (first >= 0) { + dpy_update(s->ds, 0, first, s->cols, last - first + 1); + } + s->invalidate = 0; +} + +static void pl110_invalidate_display(void * opaque) +{ + pl110_state *s = (pl110_state *)opaque; + s->invalidate = 1; + if (pl110_enabled(s)) { + qemu_console_resize(s->ds, s->cols, s->rows); + } +} + +static void pl110_update_pallette(pl110_state *s, int n) +{ + int i; + uint32_t raw; + unsigned int r, g, b; + + raw = s->raw_pallette[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 (ds_get_bits_per_pixel(s->ds)) { + case 8: + s->pallette[n] = rgb_to_pixel8(r, g, b); + break; + case 15: + s->pallette[n] = rgb_to_pixel15(r, g, b); + break; + case 16: + s->pallette[n] = rgb_to_pixel16(r, g, b); + break; + case 24: + case 32: + s->pallette[n] = rgb_to_pixel32(r, g, b); + break; + } + n++; + } +} + +static void pl110_resize(pl110_state *s, int width, int height) +{ + if (width != s->cols || height != s->rows) { + if (pl110_enabled(s)) { + qemu_console_resize(s->ds, width, height); + } + } + s->cols = width; + s->rows = height; +} + +/* Update interrupts. */ +static void pl110_update(pl110_state *s) +{ + /* TODO: Implement interrupts. */ +} + +static uint32_t pl110_read(void *opaque, target_phys_addr_t offset) +{ + pl110_state *s = (pl110_state *)opaque; + + if (offset >= 0xfe0 && offset < 0x1000) { + if (s->versatile) + return pl110_versatile_id[(offset - 0xfe0) >> 2]; + else + return pl110_id[(offset - 0xfe0) >> 2]; + } + if (offset >= 0x200 && offset < 0x400) { + return s->raw_pallette[(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->versatile) + return s->cr; + return s->int_mask; + case 7: /* LCDControl */ + if (s->versatile) + 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: + hw_error("pl110_read: Bad offset %x\n", (int)offset); + return 0; + } +} + +static void pl110_write(void *opaque, target_phys_addr_t offset, + uint32_t val) +{ + pl110_state *s = (pl110_state *)opaque; + int n; + + /* For simplicity invalidate the display whenever a control register + is writen to. */ + s->invalidate = 1; + if (offset >= 0x200 && offset < 0x400) { + /* Pallette. */ + n = (offset - 0x200) >> 2; + s->raw_pallette[(offset - 0x200) >> 2] = val; + pl110_update_pallette(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->versatile) + goto control; + imsc: + s->int_mask = val; + pl110_update(s); + break; + case 7: /* LCDControl */ + if (s->versatile) + goto imsc; + control: + s->cr = val; + s->bpp = (val >> 1) & 7; + if (pl110_enabled(s)) { + qemu_console_resize(s->ds, s->cols, s->rows); + } + break; + case 10: /* LCDICR */ + s->int_status &= ~val; + pl110_update(s); + break; + default: + hw_error("pl110_write: Bad offset %x\n", (int)offset); + } +} + +static CPUReadMemoryFunc * const pl110_readfn[] = { + pl110_read, + pl110_read, + pl110_read +}; + +static CPUWriteMemoryFunc * const pl110_writefn[] = { + pl110_write, + pl110_write, + pl110_write +}; + +static int pl110_init(SysBusDevice *dev) +{ + pl110_state *s = FROM_SYSBUS(pl110_state, dev); + int iomemtype; + + iomemtype = cpu_register_io_memory(pl110_readfn, + pl110_writefn, s, + DEVICE_NATIVE_ENDIAN); + sysbus_init_mmio(dev, 0x1000, iomemtype); + sysbus_init_irq(dev, &s->irq); + s->ds = graphic_console_init(pl110_update_display, + pl110_invalidate_display, + NULL, NULL, s); + return 0; +} + +static int pl110_versatile_init(SysBusDevice *dev) +{ + pl110_state *s = FROM_SYSBUS(pl110_state, dev); + s->versatile = 1; + return pl110_init(dev); +} + +static SysBusDeviceInfo pl110_info = { + .init = pl110_init, + .qdev.name = "pl110", + .qdev.size = sizeof(pl110_state), + .qdev.vmsd = &vmstate_pl110, + .qdev.no_user = 1, +}; + +static SysBusDeviceInfo pl110_versatile_info = { + .init = pl110_versatile_init, + .qdev.name = "pl110_versatile", + .qdev.size = sizeof(pl110_state), + .qdev.vmsd = &vmstate_pl110, + .qdev.no_user = 1, +}; + +static void pl110_register_devices(void) +{ + sysbus_register_withprop(&pl110_info); + sysbus_register_withprop(&pl110_versatile_info); +} + +device_init(pl110_register_devices) diff --git a/hw/pl110_template.h b/hw/pl110_template.h new file mode 100644 index 0000000..b3c9077 --- /dev/null +++ b/hw/pl110_template.h @@ -0,0 +1,307 @@ +/* + * Arm PrimeCell PL110 Color LCD Controller + * + * Copyright (c) 2005 CodeSourcery, LLC. + * Written by Paul Brook + * + * This code is licenced 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) *(uint16_t *)to = from; to += 2; +#elif BITS == 24 +#define COPY_PIXEL(to, from) \ + *(to++) = from; *(to++) = (from) >> 8; *(to++) = (from) >> 16 +#elif BITS == 32 +#define COPY_PIXEL(to, from) *(uint32_t *)to = from; to += 4; +#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)[36] = +{ + 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_lblp_bgr,BITS), + glue(pl110_draw_line32_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_bbbp_bgr,BITS), + glue(pl110_draw_line32_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_lbbp_bgr,BITS), + glue(pl110_draw_line32_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_lblp_rgb,BITS), + glue(pl110_draw_line32_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_bbbp_rgb,BITS), + glue(pl110_draw_line32_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_lbbp_rgb,BITS), + glue(pl110_draw_line32_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 *pallette = opaque; + uint32_t data; + while (width > 0) { + data = *(uint32_t *)src; +#ifdef SWAP_PIXELS +#define FN(x, y) COPY_PIXEL(d, pallette[(data >> (y + 7 - (x))) & 1]); +#else +#define FN(x, y) COPY_PIXEL(d, pallette[(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 *pallette = opaque; + uint32_t data; + while (width > 0) { + data = *(uint32_t *)src; +#ifdef SWAP_PIXELS +#define FN(x, y) COPY_PIXEL(d, pallette[(data >> (y + 6 - (x)*2)) & 3]); +#else +#define FN(x, y) COPY_PIXEL(d, pallette[(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 *pallette = opaque; + uint32_t data; + while (width > 0) { + data = *(uint32_t *)src; +#ifdef SWAP_PIXELS +#define FN(x, y) COPY_PIXEL(d, pallette[(data >> (y + 4 - (x)*4)) & 0xf]); +#else +#define FN(x, y) COPY_PIXEL(d, pallette[(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 *pallette = opaque; + uint32_t data; + while (width > 0) { + data = *(uint32_t *)src; +#define FN(x) COPY_PIXEL(d, pallette[(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; + } +} + +#undef SWAP_PIXELS +#undef NAME +#undef SWAP_WORDS +#undef ORDER + +#endif diff --git a/hw/pl181.c b/hw/pl181.c new file mode 100644 index 0000000..36d9d02 --- /dev/null +++ b/hw/pl181.c @@ -0,0 +1,473 @@ +/* + * Arm PrimeCell PL181 MultiMedia Card Interface + * + * Copyright (c) 2007 CodeSourcery. + * Written by Paul Brook + * + * This code is licenced under the GPL. + */ + +#include "blockdev.h" +#include "sysbus.h" +#include "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 + +typedef struct { + SysBusDevice busdev; + 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]; + int fifo_pos; + int 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 + */ + int linux_hack; + uint32_t fifo[PL181_FIFO_LEN]; + qemu_irq irq[2]; +} pl181_state; + +#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(pl181_state *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(pl181_state *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(pl181_state *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(pl181_state *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) ((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(pl181_state *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 uint32_t pl181_read(void *opaque, target_phys_addr_t offset) +{ + pl181_state *s = (pl181_state *)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 transfered between the FIFO and the serial + data engine. DataCnt is decremented after each byte is + transfered 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) { + fprintf(stderr, "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: + hw_error("pl181_read: Bad offset %x\n", (int)offset); + return 0; + } +} + +static void pl181_write(void *opaque, target_phys_addr_t offset, + uint32_t value) +{ + pl181_state *s = (pl181_state *)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) { + fprintf(stderr, "pl181: Interrupt mode not implemented\n"); + abort(); + } if (s->cmd & PL181_CMD_PENDING) { + fprintf(stderr, "pl181: Pending commands not implemented\n"); + abort(); + } 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) { + fprintf(stderr, "pl181: Unexpected FIFO write\n"); + } else { + pl181_fifo_push(s, value); + pl181_fifo_run(s); + } + break; + default: + hw_error("pl181_write: Bad offset %x\n", (int)offset); + } + pl181_update(s); +} + +static CPUReadMemoryFunc * const pl181_readfn[] = { + pl181_read, + pl181_read, + pl181_read +}; + +static CPUWriteMemoryFunc * const pl181_writefn[] = { + pl181_write, + pl181_write, + pl181_write +}; + +static void pl181_reset(void *opaque) +{ + pl181_state *s = (pl181_state *)opaque; + + 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; +} + +static int pl181_init(SysBusDevice *dev) +{ + int iomemtype; + pl181_state *s = FROM_SYSBUS(pl181_state, dev); + DriveInfo *dinfo; + + iomemtype = cpu_register_io_memory(pl181_readfn, pl181_writefn, s, + DEVICE_NATIVE_ENDIAN); + sysbus_init_mmio(dev, 0x1000, iomemtype); + sysbus_init_irq(dev, &s->irq[0]); + sysbus_init_irq(dev, &s->irq[1]); + dinfo = drive_get_next(IF_SD); + s->card = sd_init(dinfo ? dinfo->bdrv : NULL, 0); + qemu_register_reset(pl181_reset, s); + pl181_reset(s); + /* ??? Save/restore. */ + return 0; +} + +static void pl181_register_devices(void) +{ + sysbus_register_dev("pl181", sizeof(pl181_state), pl181_init); +} + +device_init(pl181_register_devices) diff --git a/hw/pl190.c b/hw/pl190.c new file mode 100644 index 0000000..75f2ba1 --- /dev/null +++ b/hw/pl190.c @@ -0,0 +1,278 @@ +/* + * Arm PrimeCell PL190 Vector Interrupt Controller + * + * Copyright (c) 2006 CodeSourcery. + * Written by Paul Brook + * + * This code is licenced under the GPL. + */ + +#include "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 + +typedef struct { + SysBusDevice busdev; + 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; +} pl190_state; + +static const unsigned char pl190_id[] = +{ 0x90, 0x11, 0x04, 0x00, 0x0D, 0xf0, 0x05, 0xb1 }; + +static inline uint32_t pl190_irq_level(pl190_state *s) +{ + return (s->level | s->soft_level) & s->irq_enable & ~s->fiq_select; +} + +/* Update interrupts. */ +static void pl190_update(pl190_state *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) +{ + pl190_state *s = (pl190_state *)opaque; + + if (level) + s->level |= 1u << irq; + else + s->level &= ~(1u << irq); + pl190_update(s); +} + +static void pl190_update_vectors(pl190_state *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 uint32_t pl190_read(void *opaque, target_phys_addr_t offset) +{ + pl190_state *s = (pl190_state *)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. */ + for (i = 0; i < s->priority; i++) + { + if ((s->level | s->soft_level) & s->prio_mask[i]) + 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: + hw_error("pl190_read: Bad offset %x\n", (int)offset); + return 0; + } +} + +static void pl190_write(void *opaque, target_phys_addr_t offset, uint32_t val) +{ + pl190_state *s = (pl190_state *)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) { + hw_error("pl190: Test mode not implemented\n"); + } + break; + default: + hw_error("pl190_write: Bad offset %x\n", (int)offset); + return; + } + pl190_update(s); +} + +static CPUReadMemoryFunc * const pl190_readfn[] = { + pl190_read, + pl190_read, + pl190_read +}; + +static CPUWriteMemoryFunc * const pl190_writefn[] = { + pl190_write, + pl190_write, + pl190_write +}; + +static void pl190_reset(DeviceState *d) +{ + pl190_state *s = DO_UPCAST(pl190_state, busdev.qdev, 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 *dev) +{ + pl190_state *s = FROM_SYSBUS(pl190_state, dev); + int iomemtype; + + iomemtype = cpu_register_io_memory(pl190_readfn, + pl190_writefn, s, + DEVICE_NATIVE_ENDIAN); + sysbus_init_mmio(dev, 0x1000, iomemtype); + qdev_init_gpio_in(&dev->qdev, pl190_set_irq, 32); + sysbus_init_irq(dev, &s->irq); + sysbus_init_irq(dev, &s->fiq); + return 0; +} + +static const VMStateDescription vmstate_pl190 = { + .name = "pl190", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32(level, pl190_state), + VMSTATE_UINT32(soft_level, pl190_state), + VMSTATE_UINT32(irq_enable, pl190_state), + VMSTATE_UINT32(fiq_select, pl190_state), + VMSTATE_UINT8_ARRAY(vect_control, pl190_state, 16), + VMSTATE_UINT32_ARRAY(vect_addr, pl190_state, PL190_NUM_PRIO), + VMSTATE_UINT32_ARRAY(prio_mask, pl190_state, PL190_NUM_PRIO+1), + VMSTATE_INT32(protected, pl190_state), + VMSTATE_INT32(priority, pl190_state), + VMSTATE_INT32_ARRAY(prev_prio, pl190_state, PL190_NUM_PRIO), + VMSTATE_END_OF_LIST() + } +}; + +static SysBusDeviceInfo pl190_info = { + .init = pl190_init, + .qdev.name = "pl190", + .qdev.size = sizeof(pl190_state), + .qdev.vmsd = &vmstate_pl190, + .qdev.reset = pl190_reset, + .qdev.no_user = 1, +}; + +static void pl190_register_devices(void) +{ + sysbus_register_withprop(&pl190_info); +} + +device_init(pl190_register_devices) diff --git a/hw/pm_smbus.c b/hw/pm_smbus.c new file mode 100644 index 0000000..5d6046d --- /dev/null +++ b/hw/pm_smbus.c @@ -0,0 +1,176 @@ +/* + * 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 + * . + */ +#include "hw.h" +#include "pc.h" +#include "pm_smbus.h" +#include "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 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; + i2c_bus *bus = s->smbus; + + SMBUS_DPRINTF("SMBus trans addr=0x%02x prot=0x%02x\n", addr, prot); + switch(prot) { + case 0x0: + smbus_quick_command(bus, addr, read); + break; + case 0x1: + if (read) { + s->smb_data0 = smbus_receive_byte(bus, addr); + } else { + smbus_send_byte(bus, addr, cmd); + } + break; + case 0x2: + if (read) { + s->smb_data0 = smbus_read_byte(bus, addr, cmd); + } else { + smbus_write_byte(bus, addr, cmd, s->smb_data0); + } + break; + case 0x3: + if (read) { + uint16_t val; + val = smbus_read_word(bus, addr, cmd); + s->smb_data0 = val; + s->smb_data1 = val >> 8; + } else { + smbus_write_word(bus, addr, cmd, (s->smb_data1 << 8) | s->smb_data0); + } + break; + case 0x5: + if (read) { + s->smb_data0 = smbus_read_block(bus, addr, cmd, s->smb_data); + } else { + smbus_write_block(bus, addr, cmd, s->smb_data, s->smb_data0); + } + break; + default: + goto error; + } + return; + + error: + s->smb_stat |= 0x04; +} + +void smb_ioport_writeb(void *opaque, uint32_t addr, uint32_t val) +{ + PMSMBus *s = opaque; + addr &= 0x3f; + SMBUS_DPRINTF("SMB writeb port=0x%04x val=0x%02x\n", addr, val); + switch(addr) { + case SMBHSTSTS: + s->smb_stat = 0; + 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; + } +} + +uint32_t smb_ioport_readb(void *opaque, uint32_t addr) +{ + PMSMBus *s = opaque; + uint32_t val; + + addr &= 0x3f; + 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%04x val=0x%02x\n", addr, val); + return val; +} + +void pm_smbus_init(DeviceState *parent, PMSMBus *smb) +{ + smb->smbus = i2c_init_bus(parent, "i2c"); +} diff --git a/hw/pm_smbus.h b/hw/pm_smbus.h new file mode 100644 index 0000000..4750a40 --- /dev/null +++ b/hw/pm_smbus.h @@ -0,0 +1,21 @@ +#ifndef PM_SMBUS_H +#define PM_SMBUS_H + +typedef struct PMSMBus { + i2c_bus *smbus; + + uint8_t smb_stat; + uint8_t smb_ctl; + uint8_t smb_cmd; + uint8_t smb_addr; + uint8_t smb_data0; + uint8_t smb_data1; + uint8_t smb_data[32]; + uint8_t smb_index; +} PMSMBus; + +void pm_smbus_init(DeviceState *parent, PMSMBus *smb); +void smb_ioport_writeb(void *opaque, uint32_t addr, uint32_t val); +uint32_t smb_ioport_readb(void *opaque, uint32_t addr); + +#endif /* !PM_SMBUS_H */ diff --git a/hw/ppc.c b/hw/ppc.c new file mode 100644 index 0000000..968aec1 --- /dev/null +++ b/hw/ppc.c @@ -0,0 +1,1288 @@ +/* + * 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.h" +#include "ppc.h" +#include "qemu-timer.h" +#include "sysemu.h" +#include "nvram.h" +#include "qemu-log.h" +#include "loader.h" +#include "kvm.h" +#include "kvm_ppc.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 (CPUState *env); +static void cpu_ppc_tb_start (CPUState *env); + +static void ppc_set_irq (CPUState *env, int n_IRQ, int level) +{ + unsigned int old_pending = env->pending_interrupts; + + if (level) { + env->pending_interrupts |= 1 << n_IRQ; + cpu_interrupt(env, CPU_INTERRUPT_HARD); + } else { + env->pending_interrupts &= ~(1 << n_IRQ); + if (env->pending_interrupts == 0) + cpu_reset_interrupt(env, CPU_INTERRUPT_HARD); + } + + if (old_pending != env->pending_interrupts) { +#ifdef CONFIG_KVM + kvmppc_set_interrupt(env, 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, env->interrupt_request); +} + +/* PowerPC 6xx / 7xx internal IRQ controller */ +static void ppc6xx_set_irq (void *opaque, int pin, int level) +{ + CPUState *env = opaque; + 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 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(env, 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(env, 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(env, 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__); + env->halted = 1; + } + break; + case PPC6xx_INPUT_HRESET: + /* Level sensitive - active low */ + if (level) { + LOG_IRQ("%s: reset the CPU\n", __func__); + env->interrupt_request |= CPU_INTERRUPT_EXITTB; + /* XXX: TOFIX */ +#if 0 + cpu_reset(env); +#else + qemu_system_reset_request(); +#endif + } + break; + case PPC6xx_INPUT_SRESET: + LOG_IRQ("%s: set the RESET IRQ state to %d\n", + __func__, level); + ppc_set_irq(env, 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 (CPUState *env) +{ + env->irq_inputs = (void **)qemu_allocate_irqs(&ppc6xx_set_irq, env, + PPC6xx_INPUT_NB); +} + +#if defined(TARGET_PPC64) +/* PowerPC 970 internal IRQ controller */ +static void ppc970_set_irq (void *opaque, int pin, int level) +{ + CPUState *env = opaque; + 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 PPC970_INPUT_INT: + /* Level sensitive - active high */ + LOG_IRQ("%s: set the external IRQ state to %d\n", + __func__, level); + ppc_set_irq(env, 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(env, 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(env, 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__); + env->halted = 1; + } else { + LOG_IRQ("%s: restart the CPU\n", __func__); + env->halted = 0; + } + break; + case PPC970_INPUT_HRESET: + /* Level sensitive - active low */ + if (level) { +#if 0 // XXX: TOFIX + LOG_IRQ("%s: reset the CPU\n", __func__); + cpu_reset(env); +#endif + } + break; + case PPC970_INPUT_SRESET: + LOG_IRQ("%s: set the RESET IRQ state to %d\n", + __func__, level); + ppc_set_irq(env, 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 (CPUState *env) +{ + env->irq_inputs = (void **)qemu_allocate_irqs(&ppc970_set_irq, env, + PPC970_INPUT_NB); +} +#endif /* defined(TARGET_PPC64) */ + +/* PowerPC 40x internal IRQ controller */ +static void ppc40x_set_irq (void *opaque, int pin, int level) +{ + CPUState *env = opaque; + 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 PPC40x_INPUT_RESET_SYS: + if (level) { + LOG_IRQ("%s: reset the PowerPC system\n", + __func__); + ppc40x_system_reset(env); + } + break; + case PPC40x_INPUT_RESET_CHIP: + if (level) { + LOG_IRQ("%s: reset the PowerPC chip\n", __func__); + ppc40x_chip_reset(env); + } + 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(env); + } + 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(env, 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(env, PPC_INTERRUPT_EXT, level); + break; + case PPC40x_INPUT_HALT: + /* Level sensitive - active low */ + if (level) { + LOG_IRQ("%s: stop the CPU\n", __func__); + env->halted = 1; + } else { + LOG_IRQ("%s: restart the CPU\n", __func__); + env->halted = 0; + } + 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(env, 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 (CPUState *env) +{ + env->irq_inputs = (void **)qemu_allocate_irqs(&ppc40x_set_irq, + env, PPC40x_INPUT_NB); +} + +/* PowerPC E500 internal IRQ controller */ +static void ppce500_set_irq (void *opaque, int pin, int level) +{ + CPUState *env = opaque; + 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(env, 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(env, 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(env, 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(env, 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 (CPUState *env) +{ + env->irq_inputs = (void **)qemu_allocate_irqs(&ppce500_set_irq, + env, PPCE500_INPUT_NB); +} +/*****************************************************************************/ +/* PowerPC time base and decrementer emulation */ +struct ppc_tb_t { + /* Time base management */ + int64_t tb_offset; /* Compensation */ + int64_t atb_offset; /* Compensation */ + uint32_t tb_freq; /* TB frequency */ + /* Decrementer management */ + uint64_t decr_next; /* Tick for next decr interrupt */ + uint32_t decr_freq; /* decrementer frequency */ + struct QEMUTimer *decr_timer; + /* Hypervisor decrementer management */ + uint64_t hdecr_next; /* Tick for next hdecr interrupt */ + struct QEMUTimer *hdecr_timer; + uint64_t purr_load; + uint64_t purr_start; + void *opaque; +}; + +static inline 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 (CPUState *env) +{ + ppc_tb_t *tb_env = env->tb_env; + uint64_t tb; + + tb = cpu_ppc_get_tb(tb_env, qemu_get_clock(vm_clock), tb_env->tb_offset); + LOG_TB("%s: tb %016" PRIx64 "\n", __func__, tb); + + return tb; +} + +static inline uint32_t _cpu_ppc_load_tbu(CPUState *env) +{ + ppc_tb_t *tb_env = env->tb_env; + uint64_t tb; + + tb = cpu_ppc_get_tb(tb_env, qemu_get_clock(vm_clock), tb_env->tb_offset); + LOG_TB("%s: tb %016" PRIx64 "\n", __func__, tb); + + return tb >> 32; +} + +uint32_t cpu_ppc_load_tbu (CPUState *env) +{ + 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 (CPUState *env, uint32_t value) +{ + ppc_tb_t *tb_env = env->tb_env; + uint64_t tb; + + tb = cpu_ppc_get_tb(tb_env, qemu_get_clock(vm_clock), tb_env->tb_offset); + tb &= 0xFFFFFFFF00000000ULL; + cpu_ppc_store_tb(tb_env, qemu_get_clock(vm_clock), + &tb_env->tb_offset, tb | (uint64_t)value); +} + +static inline void _cpu_ppc_store_tbu(CPUState *env, uint32_t value) +{ + ppc_tb_t *tb_env = env->tb_env; + uint64_t tb; + + tb = cpu_ppc_get_tb(tb_env, qemu_get_clock(vm_clock), tb_env->tb_offset); + tb &= 0x00000000FFFFFFFFULL; + cpu_ppc_store_tb(tb_env, qemu_get_clock(vm_clock), + &tb_env->tb_offset, ((uint64_t)value << 32) | tb); +} + +void cpu_ppc_store_tbu (CPUState *env, uint32_t value) +{ + _cpu_ppc_store_tbu(env, value); +} + +uint64_t cpu_ppc_load_atbl (CPUState *env) +{ + ppc_tb_t *tb_env = env->tb_env; + uint64_t tb; + + tb = cpu_ppc_get_tb(tb_env, qemu_get_clock(vm_clock), tb_env->atb_offset); + LOG_TB("%s: tb %016" PRIx64 "\n", __func__, tb); + + return tb; +} + +uint32_t cpu_ppc_load_atbu (CPUState *env) +{ + ppc_tb_t *tb_env = env->tb_env; + uint64_t tb; + + tb = cpu_ppc_get_tb(tb_env, qemu_get_clock(vm_clock), tb_env->atb_offset); + LOG_TB("%s: tb %016" PRIx64 "\n", __func__, tb); + + return tb >> 32; +} + +void cpu_ppc_store_atbl (CPUState *env, uint32_t value) +{ + ppc_tb_t *tb_env = env->tb_env; + uint64_t tb; + + tb = cpu_ppc_get_tb(tb_env, qemu_get_clock(vm_clock), tb_env->atb_offset); + tb &= 0xFFFFFFFF00000000ULL; + cpu_ppc_store_tb(tb_env, qemu_get_clock(vm_clock), + &tb_env->atb_offset, tb | (uint64_t)value); +} + +void cpu_ppc_store_atbu (CPUState *env, uint32_t value) +{ + ppc_tb_t *tb_env = env->tb_env; + uint64_t tb; + + tb = cpu_ppc_get_tb(tb_env, qemu_get_clock(vm_clock), tb_env->atb_offset); + tb &= 0x00000000FFFFFFFFULL; + cpu_ppc_store_tb(tb_env, qemu_get_clock(vm_clock), + &tb_env->atb_offset, ((uint64_t)value << 32) | tb); +} + +static void cpu_ppc_tb_stop (CPUState *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_get_clock(vm_clock); + /* 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 (CPUState *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_get_clock(vm_clock); + /* 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); + } +} + +static inline uint32_t _cpu_ppc_load_decr(CPUState *env, uint64_t next) +{ + ppc_tb_t *tb_env = env->tb_env; + uint32_t decr; + int64_t diff; + + diff = next - qemu_get_clock(vm_clock); + if (diff >= 0) + decr = muldiv64(diff, tb_env->decr_freq, get_ticks_per_sec()); + 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 (CPUState *env) +{ + ppc_tb_t *tb_env = env->tb_env; + + return _cpu_ppc_load_decr(env, tb_env->decr_next); +} + +uint32_t cpu_ppc_load_hdecr (CPUState *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 (CPUState *env) +{ + ppc_tb_t *tb_env = env->tb_env; + uint64_t diff; + + diff = qemu_get_clock(vm_clock) - 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(CPUState *env) +{ + /* Raise it */ + LOG_TB("raise decrementer exception\n"); + ppc_set_irq(env, PPC_INTERRUPT_DECR, 1); +} + +static inline void cpu_ppc_hdecr_excp(CPUState *env) +{ + /* Raise it */ + LOG_TB("raise decrementer exception\n"); + ppc_set_irq(env, PPC_INTERRUPT_HDECR, 1); +} + +static void __cpu_ppc_store_decr (CPUState *env, uint64_t *nextp, + struct QEMUTimer *timer, + void (*raise_excp)(CPUState *), + uint32_t decr, uint32_t value, + int is_excp) +{ + ppc_tb_t *tb_env = env->tb_env; + uint64_t now, next; + + LOG_TB("%s: %08" PRIx32 " => %08" PRIx32 "\n", __func__, + decr, value); + now = qemu_get_clock(vm_clock); + next = now + muldiv64(value, get_ticks_per_sec(), tb_env->decr_freq); + if (is_excp) + next += *nextp - now; + if (next == now) + next++; + *nextp = next; + /* Adjust timer */ + qemu_mod_timer(timer, next); + /* If we set a negative value and the decrementer was positive, + * raise an exception. + */ + if ((value & 0x80000000) && !(decr & 0x80000000)) + (*raise_excp)(env); +} + +static inline void _cpu_ppc_store_decr(CPUState *env, uint32_t decr, + uint32_t value, int is_excp) +{ + ppc_tb_t *tb_env = env->tb_env; + + __cpu_ppc_store_decr(env, &tb_env->decr_next, tb_env->decr_timer, + &cpu_ppc_decr_excp, decr, value, is_excp); +} + +void cpu_ppc_store_decr (CPUState *env, uint32_t value) +{ + _cpu_ppc_store_decr(env, cpu_ppc_load_decr(env), value, 0); +} + +static void cpu_ppc_decr_cb (void *opaque) +{ + _cpu_ppc_store_decr(opaque, 0x00000000, 0xFFFFFFFF, 1); +} + +static inline void _cpu_ppc_store_hdecr(CPUState *env, uint32_t hdecr, + uint32_t value, int is_excp) +{ + ppc_tb_t *tb_env = env->tb_env; + + if (tb_env->hdecr_timer != NULL) { + __cpu_ppc_store_decr(env, &tb_env->hdecr_next, tb_env->hdecr_timer, + &cpu_ppc_hdecr_excp, hdecr, value, is_excp); + } +} + +void cpu_ppc_store_hdecr (CPUState *env, uint32_t value) +{ + _cpu_ppc_store_hdecr(env, cpu_ppc_load_hdecr(env), value, 0); +} + +static void cpu_ppc_hdecr_cb (void *opaque) +{ + _cpu_ppc_store_hdecr(opaque, 0x00000000, 0xFFFFFFFF, 1); +} + +void cpu_ppc_store_purr (CPUState *env, uint64_t value) +{ + ppc_tb_t *tb_env = env->tb_env; + + tb_env->purr_load = value; + tb_env->purr_start = qemu_get_clock(vm_clock); +} + +static void cpu_ppc_set_tb_clk (void *opaque, uint32_t freq) +{ + CPUState *env = opaque; + 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(env, 0xFFFFFFFF, 0xFFFFFFFF, 0); + _cpu_ppc_store_hdecr(env, 0xFFFFFFFF, 0xFFFFFFFF, 0); + cpu_ppc_store_purr(env, 0x0000000000000000ULL); +} + +/* Set up (once) timebase frequency (in Hz) */ +clk_setup_cb cpu_ppc_tb_init (CPUState *env, uint32_t freq) +{ + ppc_tb_t *tb_env; + + tb_env = qemu_mallocz(sizeof(ppc_tb_t)); + env->tb_env = tb_env; + /* Create new timer */ + tb_env->decr_timer = qemu_new_timer(vm_clock, &cpu_ppc_decr_cb, env); + if (0) { + /* XXX: find a suitable condition to enable the hypervisor decrementer + */ + tb_env->hdecr_timer = qemu_new_timer(vm_clock, &cpu_ppc_hdecr_cb, env); + } 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 (CPUState *env) +{ + return cpu_ppc_tb_init(env, 7812500); +} +#endif + +void cpu_ppc601_store_rtcu (CPUState *env, uint32_t value) +{ + _cpu_ppc_store_tbu(env, value); +} + +uint32_t cpu_ppc601_load_rtcu (CPUState *env) +{ + return _cpu_ppc_load_tbu(env); +} + +void cpu_ppc601_store_rtcl (CPUState *env, uint32_t value) +{ + cpu_ppc_store_tbl(env, value & 0x3FFFFF80); +} + +uint32_t cpu_ppc601_load_rtcl (CPUState *env) +{ + return cpu_ppc_load_tbl(env) & 0x3FFFFF80; +} + +/*****************************************************************************/ +/* Embedded PowerPC timers */ + +/* PIT, FIT & WDT */ +typedef struct ppcemb_timer_t ppcemb_timer_t; +struct ppcemb_timer_t { + uint64_t pit_reload; /* PIT auto-reload value */ + uint64_t fit_next; /* Tick for next FIT interrupt */ + struct QEMUTimer *fit_timer; + uint64_t wdt_next; /* Tick for next WDT interrupt */ + struct 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) +{ + CPUState *env; + ppc_tb_t *tb_env; + ppcemb_timer_t *ppcemb_timer; + uint64_t now, next; + + env = opaque; + tb_env = env->tb_env; + ppcemb_timer = tb_env->opaque; + now = qemu_get_clock(vm_clock); + 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++; + qemu_mod_timer(ppcemb_timer->fit_timer, next); + env->spr[SPR_40x_TSR] |= 1 << 26; + if ((env->spr[SPR_40x_TCR] >> 23) & 0x1) + ppc_set_irq(env, 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 (CPUState *env, ppc_tb_t *tb_env, int is_excp) +{ + ppcemb_timer_t *ppcemb_timer; + uint64_t now, next; + + ppcemb_timer = tb_env->opaque; + if (ppcemb_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__); + qemu_del_timer(tb_env->decr_timer); + } else { + LOG_TB("%s: start PIT %016" PRIx64 "\n", + __func__, ppcemb_timer->pit_reload); + now = qemu_get_clock(vm_clock); + next = now + muldiv64(ppcemb_timer->pit_reload, + get_ticks_per_sec(), tb_env->decr_freq); + if (is_excp) + next += tb_env->decr_next - now; + if (next == now) + next++; + qemu_mod_timer(tb_env->decr_timer, next); + tb_env->decr_next = next; + } +} + +static void cpu_4xx_pit_cb (void *opaque) +{ + CPUState *env; + ppc_tb_t *tb_env; + ppcemb_timer_t *ppcemb_timer; + + env = opaque; + tb_env = env->tb_env; + ppcemb_timer = tb_env->opaque; + env->spr[SPR_40x_TSR] |= 1 << 27; + if ((env->spr[SPR_40x_TCR] >> 26) & 0x1) + ppc_set_irq(env, ppcemb_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], + ppcemb_timer->pit_reload); +} + +/* Watchdog timer */ +static void cpu_4xx_wdt_cb (void *opaque) +{ + CPUState *env; + ppc_tb_t *tb_env; + ppcemb_timer_t *ppcemb_timer; + uint64_t now, next; + + env = opaque; + tb_env = env->tb_env; + ppcemb_timer = tb_env->opaque; + now = qemu_get_clock(vm_clock); + 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: + qemu_mod_timer(ppcemb_timer->wdt_timer, next); + ppcemb_timer->wdt_next = next; + env->spr[SPR_40x_TSR] |= 1 << 31; + break; + case 0x2: + qemu_mod_timer(ppcemb_timer->wdt_timer, next); + ppcemb_timer->wdt_next = next; + env->spr[SPR_40x_TSR] |= 1 << 30; + if ((env->spr[SPR_40x_TCR] >> 27) & 0x1) + ppc_set_irq(env, 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(env); + break; + case 0x2: /* Chip reset */ + ppc40x_chip_reset(env); + break; + case 0x3: /* System reset */ + ppc40x_system_reset(env); + break; + } + } +} + +void store_40x_pit (CPUState *env, target_ulong val) +{ + ppc_tb_t *tb_env; + ppcemb_timer_t *ppcemb_timer; + + tb_env = env->tb_env; + ppcemb_timer = tb_env->opaque; + LOG_TB("%s val" TARGET_FMT_lx "\n", __func__, val); + ppcemb_timer->pit_reload = val; + start_stop_pit(env, tb_env, 0); +} + +target_ulong load_40x_pit (CPUState *env) +{ + return cpu_ppc_load_decr(env); +} + +void store_booke_tsr (CPUState *env, target_ulong val) +{ + ppc_tb_t *tb_env = env->tb_env; + ppcemb_timer_t *ppcemb_timer; + + ppcemb_timer = tb_env->opaque; + + LOG_TB("%s: val " TARGET_FMT_lx "\n", __func__, val); + env->spr[SPR_40x_TSR] &= ~(val & 0xFC000000); + if (val & 0x80000000) + ppc_set_irq(env, ppcemb_timer->decr_excp, 0); +} + +void store_booke_tcr (CPUState *env, target_ulong val) +{ + ppc_tb_t *tb_env; + + tb_env = env->tb_env; + LOG_TB("%s: val " TARGET_FMT_lx "\n", __func__, val); + env->spr[SPR_40x_TCR] = val & 0xFFC00000; + start_stop_pit(env, tb_env, 1); + cpu_4xx_wdt_cb(env); +} + +static void ppc_emb_set_tb_clk (void *opaque, uint32_t freq) +{ + CPUState *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_emb_timers_init (CPUState *env, uint32_t freq, + unsigned int decr_excp) +{ + ppc_tb_t *tb_env; + ppcemb_timer_t *ppcemb_timer; + + tb_env = qemu_mallocz(sizeof(ppc_tb_t)); + env->tb_env = tb_env; + ppcemb_timer = qemu_mallocz(sizeof(ppcemb_timer_t)); + tb_env->tb_freq = freq; + tb_env->decr_freq = freq; + tb_env->opaque = ppcemb_timer; + LOG_TB("%s freq %" PRIu32 "\n", __func__, freq); + if (ppcemb_timer != NULL) { + /* We use decr timer for PIT */ + tb_env->decr_timer = qemu_new_timer(vm_clock, &cpu_4xx_pit_cb, env); + ppcemb_timer->fit_timer = + qemu_new_timer(vm_clock, &cpu_4xx_fit_cb, env); + ppcemb_timer->wdt_timer = + qemu_new_timer(vm_clock, &cpu_4xx_wdt_cb, env); + ppcemb_timer->decr_excp = decr_excp; + } + + return &ppc_emb_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 (CPUState *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 (CPUState *env, int (*read_error)(int dcrn), + int (*write_error)(int dcrn)) +{ + ppc_dcr_t *dcr_env; + + dcr_env = qemu_mallocz(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); + cpu_set_log(val | 0x100); + break; + } +} + +/*****************************************************************************/ +/* NVRAM helpers */ +static inline uint32_t nvram_read (nvram_t *nvram, uint32_t addr) +{ + return (*nvram->read_fn)(nvram->opaque, addr);; +} + +static inline void nvram_write (nvram_t *nvram, uint32_t addr, uint32_t val) +{ + (*nvram->write_fn)(nvram->opaque, addr, val); +} + +void NVRAM_set_byte (nvram_t *nvram, uint32_t addr, uint8_t value) +{ + nvram_write(nvram, addr, value); +} + +uint8_t NVRAM_get_byte (nvram_t *nvram, uint32_t addr) +{ + return nvram_read(nvram, addr); +} + +void NVRAM_set_word (nvram_t *nvram, uint32_t addr, uint16_t value) +{ + nvram_write(nvram, addr, value >> 8); + nvram_write(nvram, addr + 1, value & 0xFF); +} + +uint16_t NVRAM_get_word (nvram_t *nvram, uint32_t addr) +{ + uint16_t tmp; + + tmp = nvram_read(nvram, addr) << 8; + tmp |= nvram_read(nvram, addr + 1); + + return tmp; +} + +void NVRAM_set_lword (nvram_t *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); +} + +uint32_t NVRAM_get_lword (nvram_t *nvram, uint32_t addr) +{ + uint32_t tmp; + + tmp = nvram_read(nvram, addr) << 24; + tmp |= nvram_read(nvram, addr + 1) << 16; + tmp |= nvram_read(nvram, addr + 2) << 8; + tmp |= nvram_read(nvram, addr + 3); + + return tmp; +} + +void NVRAM_set_string (nvram_t *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'); +} + +int NVRAM_get_string (nvram_t *nvram, uint8_t *dst, uint16_t addr, int max) +{ + int i; + + memset(dst, 0, max); + for (i = 0; i < max; i++) { + dst[i] = NVRAM_get_byte(nvram, addr + i); + if (dst[i] == '\0') + break; + } + + return i; +} + +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_t *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 + +int PPC_NVRAM_set_params (nvram_t *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; +} diff --git a/hw/ppc.h b/hw/ppc.h new file mode 100644 index 0000000..34f54cf --- /dev/null +++ b/hw/ppc.h @@ -0,0 +1,56 @@ +/* PowerPC hardware exceptions management helpers */ +typedef void (*clk_setup_cb)(void *opaque, uint32_t freq); +typedef struct clk_setup_t clk_setup_t; +struct clk_setup_t { + clk_setup_cb cb; + void *opaque; +}; +static inline void clk_setup (clk_setup_t *clk, uint32_t freq) +{ + if (clk->cb != NULL) + (*clk->cb)(clk->opaque, freq); +} + +clk_setup_cb cpu_ppc_tb_init (CPUState *env, uint32_t freq); +/* Embedded PowerPC DCR management */ +typedef uint32_t (*dcr_read_cb)(void *opaque, int dcrn); +typedef void (*dcr_write_cb)(void *opaque, int dcrn, uint32_t val); +int ppc_dcr_init (CPUState *env, int (*dcr_read_error)(int dcrn), + int (*dcr_write_error)(int dcrn)); +int ppc_dcr_register (CPUState *env, int dcrn, void *opaque, + dcr_read_cb drc_read, dcr_write_cb dcr_write); +clk_setup_cb ppc_emb_timers_init (CPUState *env, uint32_t freq, + unsigned int decr_excp); + +/* Embedded PowerPC reset */ +void ppc40x_core_reset (CPUState *env); +void ppc40x_chip_reset (CPUState *env); +void ppc40x_system_reset (CPUState *env); +void PREP_debug_write (void *opaque, uint32_t addr, uint32_t val); + +extern CPUWriteMemoryFunc * const PPC_io_write[]; +extern CPUReadMemoryFunc * const PPC_io_read[]; +void PPC_debug_write (void *opaque, uint32_t addr, uint32_t val); + +void ppc40x_irq_init (CPUState *env); +void ppce500_irq_init (CPUState *env); +void ppc6xx_irq_init (CPUState *env); +void ppc970_irq_init (CPUState *env); + +/* PPC machines for OpenBIOS */ +enum { + ARCH_PREP = 0, + ARCH_MAC99, + ARCH_HEATHROW, + ARCH_MAC99_U3, +}; + +#define FW_CFG_PPC_WIDTH (FW_CFG_ARCH_LOCAL + 0x00) +#define FW_CFG_PPC_HEIGHT (FW_CFG_ARCH_LOCAL + 0x01) +#define FW_CFG_PPC_DEPTH (FW_CFG_ARCH_LOCAL + 0x02) +#define FW_CFG_PPC_TBFREQ (FW_CFG_ARCH_LOCAL + 0x03) +#define FW_CFG_PPC_IS_KVM (FW_CFG_ARCH_LOCAL + 0x05) +#define FW_CFG_PPC_KVM_HC (FW_CFG_ARCH_LOCAL + 0x06) +#define FW_CFG_PPC_KVM_PID (FW_CFG_ARCH_LOCAL + 0x07) + +#define PPC_SERIAL_MM_BAUDBASE 399193 diff --git a/hw/ppc405.h b/hw/ppc405.h new file mode 100644 index 0000000..e042a05 --- /dev/null +++ b/hw/ppc405.h @@ -0,0 +1,76 @@ +/* + * 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 "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 (CPUState *env, ppc4xx_bd_info_t *bd, + uint32_t flags); + +CPUState *ppc405cr_init (target_phys_addr_t ram_bases[4], + target_phys_addr_t ram_sizes[4], + uint32_t sysclk, qemu_irq **picp, + int do_init); +CPUState *ppc405ep_init (target_phys_addr_t ram_bases[2], + target_phys_addr_t ram_sizes[2], + uint32_t sysclk, qemu_irq **picp, + int do_init); +/* IBM STBxxx microcontrollers */ +CPUState *ppc_stb025_init (target_phys_addr_t ram_bases[2], + target_phys_addr_t ram_sizes[2], + uint32_t sysclk, qemu_irq **picp, + ram_addr_t *offsetp); + +#endif /* !defined(PPC_405_H) */ diff --git a/hw/ppc405_boards.c b/hw/ppc405_boards.c new file mode 100644 index 0000000..9abede7 --- /dev/null +++ b/hw/ppc405_boards.c @@ -0,0 +1,653 @@ +/* + * 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.h" +#include "ppc.h" +#include "ppc405.h" +#include "nvram.h" +#include "flash.h" +#include "sysemu.h" +#include "block.h" +#include "boards.h" +#include "qemu-log.h" +#include "loader.h" +#include "blockdev.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, target_phys_addr_t 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, + target_phys_addr_t 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, target_phys_addr_t 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, + target_phys_addr_t 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, target_phys_addr_t 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, + target_phys_addr_t 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 CPUReadMemoryFunc * const ref405ep_fpga_read[] = { + &ref405ep_fpga_readb, + &ref405ep_fpga_readw, + &ref405ep_fpga_readl, +}; + +static CPUWriteMemoryFunc * const ref405ep_fpga_write[] = { + &ref405ep_fpga_writeb, + &ref405ep_fpga_writew, + &ref405ep_fpga_writel, +}; + +static void ref405ep_fpga_reset (void *opaque) +{ + ref405ep_fpga_t *fpga; + + fpga = opaque; + fpga->reg0 = 0x00; + fpga->reg1 = 0x0F; +} + +static void ref405ep_fpga_init (uint32_t base) +{ + ref405ep_fpga_t *fpga; + int fpga_memory; + + fpga = qemu_mallocz(sizeof(ref405ep_fpga_t)); + fpga_memory = cpu_register_io_memory(ref405ep_fpga_read, + ref405ep_fpga_write, fpga, + DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(base, 0x00000100, fpga_memory); + qemu_register_reset(&ref405ep_fpga_reset, fpga); +} + +static void ref405ep_init (ram_addr_t ram_size, + const char *boot_device, + const char *kernel_filename, + const char *kernel_cmdline, + const char *initrd_filename, + const char *cpu_model) +{ + char *filename; + ppc4xx_bd_info_t bd; + CPUPPCState *env; + qemu_irq *pic; + ram_addr_t sram_offset, bios_offset, bdloc; + target_phys_addr_t 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; + + /* XXX: fix this */ + ram_bases[0] = qemu_ram_alloc(NULL, "ef405ep.ram", 0x08000000); + ram_sizes[0] = 0x08000000; + 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(ram_bases, ram_sizes, 33333333, &pic, + kernel_filename == NULL ? 0 : 1); + /* allocate SRAM */ + sram_size = 512 * 1024; + sram_offset = qemu_ram_alloc(NULL, "ef405ep.sram", sram_size); +#ifdef DEBUG_BOARD_INIT + printf("%s: register SRAM at offset %08lx\n", __func__, sram_offset); +#endif + cpu_register_physical_memory(0xFFF00000, sram_size, + sram_offset | IO_MEM_RAM); + /* 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) { + bios_size = bdrv_getlength(dinfo->bdrv); + bios_offset = qemu_ram_alloc(NULL, "ef405ep.bios", bios_size); + fl_sectors = (bios_size + 65535) >> 16; +#ifdef DEBUG_BOARD_INIT + printf("Register parallel flash %d size %lx" + " at offset %08lx addr %lx '%s' %d\n", + fl_idx, bios_size, bios_offset, -bios_size, + bdrv_get_device_name(dinfo->bdrv), fl_sectors); +#endif + pflash_cfi02_register((uint32_t)(-bios_size), bios_offset, + dinfo->bdrv, 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_offset = qemu_ram_alloc(NULL, "ef405ep.bios", BIOS_SIZE); + 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, qemu_get_ram_ptr(bios_offset)); + qemu_free(filename); + } else { + bios_size = -1; + } + if (bios_size < 0 || bios_size > BIOS_SIZE) { + fprintf(stderr, "qemu: could not load PowerPC bios '%s'\n", + bios_name); + exit(1); + } + bios_size = (bios_size + 0xfff) & ~0xfff; + cpu_register_physical_memory((uint32_t)(-bios_size), + bios_size, bios_offset | IO_MEM_ROM); + } + /* Register FPGA */ +#ifdef DEBUG_BOARD_INIT + printf("%s: register FPGA\n", __func__); +#endif + ref405ep_fpga_init(0xF0300000); + /* Register NVRAM */ +#ifdef DEBUG_BOARD_INIT + printf("%s: register NVRAM\n", __func__); +#endif + m48t59_init(NULL, 0xF0000000, 0, 8192, 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, (void *)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("%s: Done\n", __func__); +#endif + printf("bdloc %016lx\n", (unsigned long)bdloc); +} + +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, target_phys_addr_t 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, + target_phys_addr_t 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, target_phys_addr_t 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, + target_phys_addr_t 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, target_phys_addr_t 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, + target_phys_addr_t 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 CPUReadMemoryFunc * const taihu_cpld_read[] = { + &taihu_cpld_readb, + &taihu_cpld_readw, + &taihu_cpld_readl, +}; + +static CPUWriteMemoryFunc * const taihu_cpld_write[] = { + &taihu_cpld_writeb, + &taihu_cpld_writew, + &taihu_cpld_writel, +}; + +static void taihu_cpld_reset (void *opaque) +{ + taihu_cpld_t *cpld; + + cpld = opaque; + cpld->reg0 = 0x01; + cpld->reg1 = 0x80; +} + +static void taihu_cpld_init (uint32_t base) +{ + taihu_cpld_t *cpld; + int cpld_memory; + + cpld = qemu_mallocz(sizeof(taihu_cpld_t)); + cpld_memory = cpu_register_io_memory(taihu_cpld_read, + taihu_cpld_write, cpld, + DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(base, 0x00000100, cpld_memory); + qemu_register_reset(&taihu_cpld_reset, cpld); +} + +static void taihu_405ep_init(ram_addr_t ram_size, + const char *boot_device, + const char *kernel_filename, + const char *kernel_cmdline, + const char *initrd_filename, + const char *cpu_model) +{ + char *filename; + qemu_irq *pic; + ram_addr_t bios_offset; + target_phys_addr_t 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_bases[0] = qemu_ram_alloc(NULL, "taihu_405ep.ram-0", 0x04000000); + ram_sizes[0] = 0x04000000; + ram_bases[1] = qemu_ram_alloc(NULL, "taihu_405ep.ram-1", 0x04000000); + ram_sizes[1] = 0x04000000; + ram_size = 0x08000000; +#ifdef DEBUG_BOARD_INIT + printf("%s: register cpu\n", __func__); +#endif + ppc405ep_init(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) { + bios_size = bdrv_getlength(dinfo->bdrv); + /* XXX: should check that size is 2MB */ + // bios_size = 2 * 1024 * 1024; + fl_sectors = (bios_size + 65535) >> 16; + bios_offset = qemu_ram_alloc(NULL, "taihu_405ep.bios", bios_size); +#ifdef DEBUG_BOARD_INIT + printf("Register parallel flash %d size %lx" + " at offset %08lx addr %lx '%s' %d\n", + fl_idx, bios_size, bios_offset, -bios_size, + bdrv_get_device_name(dinfo->bdrv), fl_sectors); +#endif + pflash_cfi02_register((uint32_t)(-bios_size), bios_offset, + dinfo->bdrv, 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_offset = qemu_ram_alloc(NULL, "taihu_405ep.bios", BIOS_SIZE); + filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, bios_name); + if (filename) { + bios_size = load_image(filename, qemu_get_ram_ptr(bios_offset)); + } else { + bios_size = -1; + } + if (bios_size < 0 || bios_size > BIOS_SIZE) { + fprintf(stderr, "qemu: could not load PowerPC bios '%s'\n", + bios_name); + exit(1); + } + bios_size = (bios_size + 0xfff) & ~0xfff; + cpu_register_physical_memory((uint32_t)(-bios_size), + bios_size, bios_offset | IO_MEM_ROM); + } + /* Register Linux flash */ + dinfo = drive_get(IF_PFLASH, 0, fl_idx); + if (dinfo) { + bios_size = bdrv_getlength(dinfo->bdrv); + /* 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 offset %08lx addr " TARGET_FMT_lx " '%s'\n", + fl_idx, bios_size, bios_offset, (target_ulong)0xfc000000, + bdrv_get_device_name(dinfo->bdrv)); +#endif + bios_offset = qemu_ram_alloc(NULL, "taihu_405ep.flash", bios_size); + pflash_cfi02_register(0xfc000000, bios_offset, + dinfo->bdrv, 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(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/ppc405_uc.c b/hw/ppc405_uc.c new file mode 100644 index 0000000..334187e --- /dev/null +++ b/hw/ppc405_uc.c @@ -0,0 +1,2544 @@ +/* + * 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.h" +#include "ppc.h" +#include "ppc405.h" +#include "pc.h" +#include "qemu-timer.h" +#include "sysemu.h" +#include "qemu-log.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 (CPUState *env, ppc4xx_bd_info_t *bd, + uint32_t flags) +{ + 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_phys(bdloc + 0x00, bd->bi_memstart); + stl_phys(bdloc + 0x04, bd->bi_memsize); + stl_phys(bdloc + 0x08, bd->bi_flashstart); + stl_phys(bdloc + 0x0C, bd->bi_flashsize); + stl_phys(bdloc + 0x10, bd->bi_flashoffset); + stl_phys(bdloc + 0x14, bd->bi_sramstart); + stl_phys(bdloc + 0x18, bd->bi_sramsize); + stl_phys(bdloc + 0x1C, bd->bi_bootflags); + stl_phys(bdloc + 0x20, bd->bi_ipaddr); + for (i = 0; i < 6; i++) + stb_phys(bdloc + 0x24 + i, bd->bi_enetaddr[i]); + stw_phys(bdloc + 0x2A, bd->bi_ethspeed); + stl_phys(bdloc + 0x2C, bd->bi_intfreq); + stl_phys(bdloc + 0x30, bd->bi_busfreq); + stl_phys(bdloc + 0x34, bd->bi_baudrate); + for (i = 0; i < 4; i++) + stb_phys(bdloc + 0x38 + i, bd->bi_s_version[i]); + for (i = 0; i < 32; i++) { + stb_phys(bdloc + 0x3C + i, bd->bi_r_version[i]); + } + stl_phys(bdloc + 0x5C, bd->bi_plb_busfreq); + stl_phys(bdloc + 0x60, bd->bi_pci_busfreq); + for (i = 0; i < 6; i++) + stb_phys(bdloc + 0x64 + i, bd->bi_pci_enetaddr[i]); + n = 0x6A; + if (flags & 0x00000001) { + for (i = 0; i < 6; i++) + stb_phys(bdloc + n++, bd->bi_pci_enetaddr2[i]); + } + stl_phys(bdloc + n, bd->bi_opbfreq); + n += 4; + for (i = 0; i < 2; i++) { + stl_phys(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(CPUState *env) +{ + ppc4xx_plb_t *plb; + + plb = qemu_mallocz(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 besr[2]; +}; + +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: + case POB0_BESR1: + ret = pob->besr[dcrn - POB0_BESR0]; + 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: + case POB0_BESR1: + /* Write-clear */ + pob->besr[dcrn - POB0_BESR0] &= ~val; + break; + } +} + +static void ppc4xx_pob_reset (void *opaque) +{ + ppc4xx_pob_t *pob; + + pob = opaque; + /* No error */ + pob->bear = 0x00000000; + pob->besr[0] = 0x0000000; + pob->besr[1] = 0x0000000; +} + +static void ppc4xx_pob_init(CPUState *env) +{ + ppc4xx_pob_t *pob; + + pob = qemu_mallocz(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 { + uint8_t cr; + uint8_t pr; +}; + +static uint32_t opba_readb (void *opaque, target_phys_addr_t 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, + target_phys_addr_t 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, target_phys_addr_t 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, + target_phys_addr_t 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, target_phys_addr_t 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, + target_phys_addr_t 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 CPUReadMemoryFunc * const opba_read[] = { + &opba_readb, + &opba_readw, + &opba_readl, +}; + +static CPUWriteMemoryFunc * const opba_write[] = { + &opba_writeb, + &opba_writew, + &opba_writel, +}; + +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(target_phys_addr_t base) +{ + ppc4xx_opba_t *opba; + int io; + + opba = qemu_mallocz(sizeof(ppc4xx_opba_t)); +#ifdef DEBUG_OPBA + printf("%s: offset " TARGET_FMT_plx "\n", __func__, base); +#endif + io = cpu_register_io_memory(opba_read, opba_write, opba, + DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(base, 0x002, 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(CPUState *env) +{ + ppc4xx_ebc_t *ebc; + + ebc = qemu_mallocz(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(CPUState *env, qemu_irq irqs[4]) +{ + ppc405_dma_t *dma; + + dma = qemu_mallocz(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 { + 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, target_phys_addr_t addr) +{ +#ifdef DEBUG_GPIO + printf("%s: addr " TARGET_FMT_plx "\n", __func__, addr); +#endif + + return 0; +} + +static void ppc405_gpio_writeb (void *opaque, + target_phys_addr_t 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, target_phys_addr_t addr) +{ +#ifdef DEBUG_GPIO + printf("%s: addr " TARGET_FMT_plx "\n", __func__, addr); +#endif + + return 0; +} + +static void ppc405_gpio_writew (void *opaque, + target_phys_addr_t 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, target_phys_addr_t addr) +{ +#ifdef DEBUG_GPIO + printf("%s: addr " TARGET_FMT_plx "\n", __func__, addr); +#endif + + return 0; +} + +static void ppc405_gpio_writel (void *opaque, + target_phys_addr_t addr, uint32_t value) +{ +#ifdef DEBUG_GPIO + printf("%s: addr " TARGET_FMT_plx " val %08" PRIx32 "\n", __func__, addr, + value); +#endif +} + +static CPUReadMemoryFunc * const ppc405_gpio_read[] = { + &ppc405_gpio_readb, + &ppc405_gpio_readw, + &ppc405_gpio_readl, +}; + +static CPUWriteMemoryFunc * const ppc405_gpio_write[] = { + &ppc405_gpio_writeb, + &ppc405_gpio_writew, + &ppc405_gpio_writel, +}; + +static void ppc405_gpio_reset (void *opaque) +{ +} + +static void ppc405_gpio_init(target_phys_addr_t base) +{ + ppc405_gpio_t *gpio; + int io; + + gpio = qemu_mallocz(sizeof(ppc405_gpio_t)); +#ifdef DEBUG_GPIO + printf("%s: offset " TARGET_FMT_plx "\n", __func__, base); +#endif + io = cpu_register_io_memory(ppc405_gpio_read, ppc405_gpio_write, gpio, + DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(base, 0x038, 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 { + target_ulong offset; + 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); + cpu_register_physical_memory(ocm->isarc, 0x04000000, + IO_MEM_UNASSIGNED); + } + if (isacntl & 0x80000000) { + /* Map new instruction memory region */ +#ifdef DEBUG_OCM + printf("OCM map ISA %08" PRIx32 "\n", isarc); +#endif + cpu_register_physical_memory(isarc, 0x04000000, + ocm->offset | IO_MEM_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 + cpu_register_physical_memory(ocm->dsarc, 0x04000000, + IO_MEM_UNASSIGNED); + } + } + 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 + cpu_register_physical_memory(dsarc, 0x04000000, + ocm->offset | IO_MEM_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(CPUState *env) +{ + ppc405_ocm_t *ocm; + + ocm = qemu_mallocz(sizeof(ppc405_ocm_t)); + ocm->offset = qemu_ram_alloc(NULL, "ppc405.ocm", 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; + 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, target_phys_addr_t 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, + target_phys_addr_t 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, target_phys_addr_t 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, + target_phys_addr_t 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, target_phys_addr_t 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, + target_phys_addr_t 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 CPUReadMemoryFunc * const i2c_read[] = { + &ppc4xx_i2c_readb, + &ppc4xx_i2c_readw, + &ppc4xx_i2c_readl, +}; + +static CPUWriteMemoryFunc * const i2c_write[] = { + &ppc4xx_i2c_writeb, + &ppc4xx_i2c_writew, + &ppc4xx_i2c_writel, +}; + +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(target_phys_addr_t base, qemu_irq irq) +{ + ppc4xx_i2c_t *i2c; + int io; + + i2c = qemu_mallocz(sizeof(ppc4xx_i2c_t)); + i2c->irq = irq; +#ifdef DEBUG_I2C + printf("%s: offset " TARGET_FMT_plx "\n", __func__, base); +#endif + io = cpu_register_io_memory(i2c_read, i2c_write, i2c, + DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(base, 0x011, io); + qemu_register_reset(ppc4xx_i2c_reset, i2c); +} + +/*****************************************************************************/ +/* General purpose timers */ +typedef struct ppc4xx_gpt_t ppc4xx_gpt_t; +struct ppc4xx_gpt_t { + int64_t tb_offset; + uint32_t tb_freq; + struct 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, target_phys_addr_t 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, + target_phys_addr_t 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, target_phys_addr_t 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, + target_phys_addr_t 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, target_phys_addr_t 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_get_clock(vm_clock) + 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, + target_phys_addr_t 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_get_clock(vm_clock); + 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 CPUReadMemoryFunc * const gpt_read[] = { + &ppc4xx_gpt_readb, + &ppc4xx_gpt_readw, + &ppc4xx_gpt_readl, +}; + +static CPUWriteMemoryFunc * const gpt_write[] = { + &ppc4xx_gpt_writeb, + &ppc4xx_gpt_writew, + &ppc4xx_gpt_writel, +}; + +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; + qemu_del_timer(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(target_phys_addr_t base, qemu_irq irqs[5]) +{ + ppc4xx_gpt_t *gpt; + int i; + int io; + + gpt = qemu_mallocz(sizeof(ppc4xx_gpt_t)); + for (i = 0; i < 5; i++) { + gpt->irqs[i] = irqs[i]; + } + gpt->timer = qemu_new_timer(vm_clock, &ppc4xx_gpt_cb, gpt); +#ifdef DEBUG_GPT + printf("%s: offset " TARGET_FMT_plx "\n", __func__, base); +#endif + io = cpu_register_io_memory(gpt_read, gpt_write, gpt, DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(base, 0x0d4, io); + 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(CPUState *env, qemu_irq irqs[4]) +{ + ppc40x_mal_t *mal; + int i; + + mal = qemu_mallocz(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 (CPUState *env) +{ + target_ulong dbsr; + + printf("Reset PowerPC core\n"); + env->interrupt_request |= CPU_INTERRUPT_EXITTB; + /* XXX: TOFIX */ +#if 0 + cpu_reset(env); +#else + qemu_system_reset_request(); +#endif + dbsr = env->spr[SPR_40x_DBSR]; + dbsr &= ~0x00000300; + dbsr |= 0x00000100; + env->spr[SPR_40x_DBSR] = dbsr; +} + +void ppc40x_chip_reset (CPUState *env) +{ + target_ulong dbsr; + + printf("Reset PowerPC chip\n"); + env->interrupt_request |= CPU_INTERRUPT_EXITTB; + /* XXX: TOFIX */ +#if 0 + cpu_reset(env); +#else + qemu_system_reset_request(); +#endif + /* 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 (CPUState *env) +{ + printf("Reset PowerPC system\n"); + qemu_system_reset_request(); +} + +void store_40x_dbcr0 (CPUState *env, uint32_t val) +{ + switch ((val >> 28) & 0x3) { + case 0x0: + /* No action */ + break; + case 0x1: + /* Core reset */ + ppc40x_core_reset(env); + break; + case 0x2: + /* Chip reset */ + ppc40x_chip_reset(env); + break; + case 0x3: + /* System reset */ + ppc40x_system_reset(env); + 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 (CPUState *env, clk_setup_t clk_setup[7], + uint32_t sysclk) +{ + ppc405cr_cpc_t *cpc; + + cpc = qemu_mallocz(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); +} + +CPUState *ppc405cr_init (target_phys_addr_t ram_bases[4], + target_phys_addr_t 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]; + CPUState *env; + qemu_irq *pic, *irqs; + + memset(clk_setup, 0, sizeof(clk_setup)); + env = ppc4xx_init("405cr", &clk_setup[PPC405CR_CPU_CLK], + &clk_setup[PPC405CR_TMR_CLK], sysclk); + /* 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 = qemu_mallocz(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_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(0xef600300, 0, pic[0], PPC_SERIAL_MM_BAUDBASE, + serial_hds[0], 1, 1); + } + if (serial_hds[1] != NULL) { + serial_mm_init(0xef600400, 0, pic[1], PPC_SERIAL_MM_BAUDBASE, + serial_hds[1], 1, 1); + } + /* 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 (CPUState *env, clk_setup_t clk_setup[8], + uint32_t sysclk) +{ + ppc405ep_cpc_t *cpc; + + cpc = qemu_mallocz(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 +} + +CPUState *ppc405ep_init (target_phys_addr_t ram_bases[2], + target_phys_addr_t 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]; + CPUState *env; + qemu_irq *pic, *irqs; + + memset(clk_setup, 0, sizeof(clk_setup)); + /* init CPUs */ + env = ppc4xx_init("405ep", &clk_setup[PPC405EP_CPU_CLK], + &tlb_clk_setup, sysclk); + 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); + /* Universal interrupt controller */ + irqs = qemu_mallocz(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_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(0xef600300, 0, pic[0], PPC_SERIAL_MM_BAUDBASE, + serial_hds[0], 1, 1); + } + if (serial_hds[1] != NULL) { + serial_mm_init(0xef600400, 0, pic[1], PPC_SERIAL_MM_BAUDBASE, + serial_hds[1], 1, 1); + } + /* 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/ppc440.c b/hw/ppc440.c new file mode 100644 index 0000000..1ed001a --- /dev/null +++ b/hw/ppc440.c @@ -0,0 +1,100 @@ +/* + * Qemu PowerPC 440 chip emulation + * + * Copyright 2007 IBM Corporation. + * Authors: + * Jerone Young + * Christian Ehrhardt + * Hollis Blanchard + * + * This work is licensed under the GNU GPL license version 2 or later. + * + */ + +#include "hw.h" +#include "pc.h" +#include "isa.h" +#include "ppc.h" +#include "ppc4xx.h" +#include "ppc440.h" +#include "ppc405.h" +#include "sysemu.h" +#include "kvm.h" + +#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 +}; + +CPUState *ppc440ep_init(ram_addr_t *ram_size, PCIBus **pcip, + const unsigned int pci_irq_nrs[4], int do_init, + const char *cpu_model) +{ + target_phys_addr_t ram_bases[PPC440EP_SDRAM_NR_BANKS]; + target_phys_addr_t ram_sizes[PPC440EP_SDRAM_NR_BANKS]; + CPUState *env; + qemu_irq *pic; + qemu_irq *irqs; + qemu_irq *pci_irqs; + + if (cpu_model == NULL) + cpu_model = "405"; // XXX: should be 440EP + env = cpu_init(cpu_model); + if (!env) { + fprintf(stderr, "Unable to initialize CPU!\n"); + exit(1); + } + + ppc_dcr_init(env, NULL, NULL); + + /* interrupt controller */ + irqs = qemu_mallocz(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_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_bases, + ram_sizes, do_init); + + /* PCI */ + pci_irqs = qemu_malloc(sizeof(qemu_irq) * 4); + pci_irqs[0] = pic[pci_irq_nrs[0]]; + pci_irqs[1] = pic[pci_irq_nrs[1]]; + pci_irqs[2] = pic[pci_irq_nrs[2]]; + pci_irqs[3] = pic[pci_irq_nrs[3]]; + *pcip = ppc4xx_pci_init(env, pci_irqs, + PPC440EP_PCI_CONFIG, + PPC440EP_PCI_INTACK, + PPC440EP_PCI_SPECIAL, + PPC440EP_PCI_REGS); + if (!*pcip) + printf("couldn't create PCI controller!\n"); + + isa_mmio_init(PPC440EP_PCI_IO, PPC440EP_PCI_IOLEN); + + if (serial_hds[0] != NULL) { + serial_mm_init(0xef600300, 0, pic[0], PPC_SERIAL_MM_BAUDBASE, + serial_hds[0], 1, 1); + } + if (serial_hds[1] != NULL) { + serial_mm_init(0xef600400, 0, pic[1], PPC_SERIAL_MM_BAUDBASE, + serial_hds[1], 1, 1); + } + + return env; +} diff --git a/hw/ppc440.h b/hw/ppc440.h new file mode 100644 index 0000000..a40f917 --- /dev/null +++ b/hw/ppc440.h @@ -0,0 +1,21 @@ +/* + * Qemu PowerPC 440 board emualtion + * + * Copyright 2007 IBM Corporation. + * Authors: Jerone Young + * Christian Ehrhardt + * + * This work is licensed under the GNU GPL licence version 2 or later + * + */ + +#ifndef QEMU_PPC440_H +#define QEMU_PPC440_H + +#include "hw.h" + +CPUState *ppc440ep_init(ram_addr_t *ram_size, PCIBus **pcip, + const unsigned int pci_irq_nrs[4], int do_init, + const char *cpu_model); + +#endif diff --git a/hw/ppc440_bamboo.c b/hw/ppc440_bamboo.c new file mode 100644 index 0000000..34ddf45 --- /dev/null +++ b/hw/ppc440_bamboo.c @@ -0,0 +1,203 @@ +/* + * Qemu PowerPC 440 Bamboo board emulation + * + * Copyright 2007 IBM Corporation. + * Authors: + * Jerone Young + * Christian Ehrhardt + * Hollis Blanchard + * + * This work is licensed under the GNU GPL license version 2 or later. + * + */ + +#include "config.h" +#include "qemu-common.h" +#include "net.h" +#include "hw.h" +#include "pci.h" +#include "boards.h" +#include "sysemu.h" +#include "ppc440.h" +#include "kvm.h" +#include "kvm_ppc.h" +#include "device_tree.h" +#include "loader.h" +#include "elf.h" + +#define BINARY_DEVICE_TREE_FILE "bamboo.dtb" + +/* from u-boot */ +#define KERNEL_ADDR 0x1000000 +#define FDT_ADDR 0x1800000 +#define RAMDISK_ADDR 0x1900000 + +static int bamboo_load_device_tree(target_phys_addr_t addr, + uint32_t ramsize, + target_phys_addr_t initrd_base, + target_phys_addr_t initrd_size, + const char *kernel_cmdline) +{ + int ret = -1; +#ifdef CONFIG_FDT + uint32_t mem_reg_property[] = { 0, 0, ramsize }; + char *filename; + int fdt_size; + void *fdt; + + filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, BINARY_DEVICE_TREE_FILE); + if (!filename) { + goto out; + } + fdt = load_device_tree(filename, &fdt_size); + qemu_free(filename); + if (fdt == NULL) { + goto out; + } + + /* Manipulate device tree in memory. */ + + ret = qemu_devtree_setprop(fdt, "/memory", "reg", mem_reg_property, + sizeof(mem_reg_property)); + if (ret < 0) + fprintf(stderr, "couldn't set /memory/reg\n"); + + ret = qemu_devtree_setprop_cell(fdt, "/chosen", "linux,initrd-start", + initrd_base); + if (ret < 0) + fprintf(stderr, "couldn't set /chosen/linux,initrd-start\n"); + + ret = qemu_devtree_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_devtree_setprop_string(fdt, "/chosen", "bootargs", + kernel_cmdline); + if (ret < 0) + fprintf(stderr, "couldn't set /chosen/bootargs\n"); + + if (kvm_enabled()) + kvmppc_fdt_update(fdt); + + ret = rom_add_blob_fixed(BINARY_DEVICE_TREE_FILE, fdt, fdt_size, addr); + qemu_free(fdt); + +out: +#endif + + return ret; +} + +static void bamboo_init(ram_addr_t ram_size, + const char *boot_device, + const char *kernel_filename, + const char *kernel_cmdline, + const char *initrd_filename, + const char *cpu_model) +{ + unsigned int pci_irq_nrs[4] = { 28, 27, 26, 25 }; + PCIBus *pcibus; + CPUState *env; + uint64_t elf_entry; + uint64_t elf_lowaddr; + target_phys_addr_t entry = 0; + target_phys_addr_t loadaddr = 0; + target_long initrd_size = 0; + int success; + int i; + + /* Setup CPU. */ + env = ppc440ep_init(&ram_size, &pcibus, pci_irq_nrs, 1, cpu_model); + + 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], "e1000", NULL); + } + } + + /* Load kernel. */ + if (kernel_filename) { + success = load_uimage(kernel_filename, &entry, &loadaddr, 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); + } + + cpu_synchronize_state(env); + + /* Set initial guest state. */ + env->gpr[1] = (16<<20) - 8; + env->gpr[3] = FDT_ADDR; + env->nip = entry; + /* XXX we currently depend on KVM to create some initial TLB entries. */ + } + + if (kvm_enabled()) + kvmppc_init(); +} + +static QEMUMachine bamboo_machine = { + .name = "bamboo-0.13", + .alias = "bamboo", + .desc = "bamboo", + .init = bamboo_init, +}; + +static QEMUMachine bamboo_machine_v0_12 = { + .name = "bamboo-0.12", + .desc = "bamboo", + .init = bamboo_init, + .compat_props = (GlobalProperty[]) { + { + .driver = "virtio-serial-pci", + .property = "max_ports", + .value = stringify(1), + },{ + .driver = "virtio-serial-pci", + .property = "vectors", + .value = stringify(0), + }, + { /* end of list */ } + }, +}; + +static void bamboo_machine_init(void) +{ + qemu_register_machine(&bamboo_machine); + qemu_register_machine(&bamboo_machine_v0_12); +} + +machine_init(bamboo_machine_init); diff --git a/hw/ppc4xx.h b/hw/ppc4xx.h new file mode 100644 index 0000000..bc4ee01 --- /dev/null +++ b/hw/ppc4xx.h @@ -0,0 +1,60 @@ +/* + * QEMU PowerPC 4xx emulation 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_4XX_H) +#define PPC_4XX_H + +#include "pci.h" + +/* PowerPC 4xx core initialization */ +CPUState *ppc4xx_init (const char *cpu_model, + clk_setup_t *cpu_clk, clk_setup_t *tb_clk, + uint32_t sysclk); + +/* PowerPC 4xx universal interrupt controller */ +enum { + PPCUIC_OUTPUT_INT = 0, + PPCUIC_OUTPUT_CINT = 1, + PPCUIC_OUTPUT_NB, +}; +qemu_irq *ppcuic_init (CPUState *env, qemu_irq *irqs, + uint32_t dcr_base, int has_ssr, int has_vr); + +ram_addr_t ppc4xx_sdram_adjust(ram_addr_t ram_size, int nr_banks, + target_phys_addr_t ram_bases[], + target_phys_addr_t ram_sizes[], + const unsigned int sdram_bank_sizes[]); + +void ppc4xx_sdram_init (CPUState *env, qemu_irq irq, int nbanks, + target_phys_addr_t *ram_bases, + target_phys_addr_t *ram_sizes, + int do_init); + +PCIBus *ppc4xx_pci_init(CPUState *env, qemu_irq pci_irqs[4], + target_phys_addr_t config_space, + target_phys_addr_t int_ack, + target_phys_addr_t special_cycle, + target_phys_addr_t registers); + +#endif /* !defined(PPC_4XX_H) */ diff --git a/hw/ppc4xx_devs.c b/hw/ppc4xx_devs.c new file mode 100644 index 0000000..5f581fe --- /dev/null +++ b/hw/ppc4xx_devs.c @@ -0,0 +1,691 @@ +/* + * 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.h" +#include "ppc.h" +#include "ppc4xx.h" +#include "sysemu.h" +#include "qemu-log.h" + +//#define DEBUG_MMIO +//#define DEBUG_UNASSIGNED +#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 + +/*****************************************************************************/ +/* Generic PowerPC 4xx processor instanciation */ +CPUState *ppc4xx_init (const char *cpu_model, + clk_setup_t *cpu_clk, clk_setup_t *tb_clk, + uint32_t sysclk) +{ + CPUState *env; + + /* init CPUs */ + env = cpu_init(cpu_model); + if (!env) { + fprintf(stderr, "Unable to find PowerPC %s CPU definition\n", + cpu_model); + exit(1); + } + 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_emb_timers_init(env, sysclk, PPC_INTERRUPT_PIT); + tb_clk->opaque = env; + ppc_dcr_init(env, NULL, NULL); + /* Register qemu callbacks */ + qemu_register_reset((QEMUResetHandler*)&cpu_reset, env); + + return env; +} + +/*****************************************************************************/ +/* "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 = 1 << (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 (CPUState *env, qemu_irq *irqs, + uint32_t dcr_base, int has_ssr, int has_vr) +{ + ppcuic_t *uic; + int i; + + uic = qemu_mallocz(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; + target_phys_addr_t ram_bases[4]; + target_phys_addr_t 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 target_phys_addr_t, target_ulong + * and uint32_t + */ +static uint32_t sdram_bcr (target_phys_addr_t ram_base, + target_phys_addr_t 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 target_phys_addr_t 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 (uint32_t *bcrp, uint32_t bcr, int enabled) +{ + 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 + cpu_register_physical_memory(sdram_base(*bcrp), sdram_size(*bcrp), + IO_MEM_UNASSIGNED); + } + *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 + cpu_register_physical_memory(sdram_base(bcr), sdram_size(bcr), + sdram_base(bcr) | IO_MEM_RAM); + } +} + +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->bcr[i], + sdram_bcr(sdram->ram_bases[i], sdram->ram_sizes[i]), + 1); + } else { + sdram_set_bcr(&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 + cpu_register_physical_memory(sdram_base(sdram->bcr[i]), + sdram_size(sdram->bcr[i]), + IO_MEM_UNASSIGNED); + } +} + +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->bcr[0], val, sdram->cfg & 0x80000000); + break; + case 0x44: /* SDRAM_B1CR */ + sdram_set_bcr(&sdram->bcr[1], val, sdram->cfg & 0x80000000); + break; + case 0x48: /* SDRAM_B2CR */ + sdram_set_bcr(&sdram->bcr[2], val, sdram->cfg & 0x80000000); + break; + case 0x4C: /* SDRAM_B3CR */ + sdram_set_bcr(&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 (CPUState *env, qemu_irq irq, int nbanks, + target_phys_addr_t *ram_bases, + target_phys_addr_t *ram_sizes, + int do_init) +{ + ppc4xx_sdram_t *sdram; + + sdram = qemu_mallocz(sizeof(ppc4xx_sdram_t)); + sdram->irq = irq; + sdram->nbanks = nbanks; + memset(sdram->ram_bases, 0, 4 * sizeof(target_phys_addr_t)); + memcpy(sdram->ram_bases, ram_bases, + nbanks * sizeof(target_phys_addr_t)); + memset(sdram->ram_sizes, 0, 4 * sizeof(target_phys_addr_t)); + memcpy(sdram->ram_sizes, ram_sizes, + nbanks * sizeof(target_phys_addr_t)); + 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, + target_phys_addr_t ram_bases[], + target_phys_addr_t ram_sizes[], + const unsigned int sdram_bank_sizes[]) +{ + ram_addr_t size_left = ram_size; + int i; + int j; + + for (i = 0; i < nr_banks; i++) { + for (j = 0; sdram_bank_sizes[j] != 0; j++) { + unsigned int bank_size = sdram_bank_sizes[j]; + + if (bank_size <= size_left) { + char name[32]; + snprintf(name, sizeof(name), "ppc4xx.sdram%d", i); + ram_bases[i] = qemu_ram_alloc(NULL, name, bank_size); + ram_sizes[i] = bank_size; + size_left -= bank_size; + break; + } + } + + 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)); + + return ram_size; +} diff --git a/hw/ppc4xx_pci.c b/hw/ppc4xx_pci.c new file mode 100644 index 0000000..f62f1f9 --- /dev/null +++ b/hw/ppc4xx_pci.c @@ -0,0 +1,393 @@ +/* + * 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 . + * + * Copyright IBM Corp. 2008 + * + * Authors: Hollis Blanchard + */ + +/* This file implements emulation of the 32-bit PCI controller found in some + * 4xx SoCs, such as the 440EP. */ + +#include "hw.h" +#include "ppc.h" +#include "ppc4xx.h" +#include "pci.h" +#include "pci_host.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_NR_PMMS 3 +#define PPC4xx_PCI_NR_PTMS 2 + +struct PPC4xxPCIState { + struct PCIMasterMap pmm[PPC4xx_PCI_NR_PMMS]; + struct PCITargetMap ptm[PPC4xx_PCI_NR_PTMS]; + + PCIHostState pci_state; + PCIDevice *pci_dev; +}; +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_SIZE 0x40 + + +static uint32_t pci4xx_cfgaddr_readl(void *opaque, target_phys_addr_t addr) +{ + PPC4xxPCIState *ppc4xx_pci = opaque; + + return ppc4xx_pci->pci_state.config_reg; +} + +static CPUReadMemoryFunc * const pci4xx_cfgaddr_read[] = { + &pci4xx_cfgaddr_readl, + &pci4xx_cfgaddr_readl, + &pci4xx_cfgaddr_readl, +}; + +static void pci4xx_cfgaddr_writel(void *opaque, target_phys_addr_t addr, + uint32_t value) +{ + PPC4xxPCIState *ppc4xx_pci = opaque; + + ppc4xx_pci->pci_state.config_reg = value & ~0x3; +} + +static CPUWriteMemoryFunc * const pci4xx_cfgaddr_write[] = { + &pci4xx_cfgaddr_writel, + &pci4xx_cfgaddr_writel, + &pci4xx_cfgaddr_writel, +}; + +static void ppc4xx_pci_reg_write4(void *opaque, target_phys_addr_t offset, + uint32_t value) +{ + 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 uint32_t ppc4xx_pci_reg_read4(void *opaque, target_phys_addr_t offset) +{ + 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 CPUReadMemoryFunc * const pci_reg_read[] = { + &ppc4xx_pci_reg_read4, + &ppc4xx_pci_reg_read4, + &ppc4xx_pci_reg_read4, +}; + +static CPUWriteMemoryFunc * const pci_reg_write[] = { + &ppc4xx_pci_reg_write4, + &ppc4xx_pci_reg_write4, + &ppc4xx_pci_reg_write4, +}; + +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); + qemu_set_irq(pci_irqs[irq_num], level); +} + +static void ppc4xx_pci_save(QEMUFile *f, void *opaque) +{ + PPC4xxPCIState *controller = opaque; + int i; + + pci_device_save(controller->pci_dev, f); + + for (i = 0; i < PPC4xx_PCI_NR_PMMS; i++) { + qemu_put_be32s(f, &controller->pmm[i].la); + qemu_put_be32s(f, &controller->pmm[i].ma); + qemu_put_be32s(f, &controller->pmm[i].pcila); + qemu_put_be32s(f, &controller->pmm[i].pciha); + } + + for (i = 0; i < PPC4xx_PCI_NR_PTMS; i++) { + qemu_put_be32s(f, &controller->ptm[i].ms); + qemu_put_be32s(f, &controller->ptm[i].la); + } +} + +static int ppc4xx_pci_load(QEMUFile *f, void *opaque, int version_id) +{ + PPC4xxPCIState *controller = opaque; + int i; + + if (version_id != 1) + return -EINVAL; + + pci_device_load(controller->pci_dev, f); + + for (i = 0; i < PPC4xx_PCI_NR_PMMS; i++) { + qemu_get_be32s(f, &controller->pmm[i].la); + qemu_get_be32s(f, &controller->pmm[i].ma); + qemu_get_be32s(f, &controller->pmm[i].pcila); + qemu_get_be32s(f, &controller->pmm[i].pciha); + } + + for (i = 0; i < PPC4xx_PCI_NR_PTMS; i++) { + qemu_get_be32s(f, &controller->ptm[i].ms); + qemu_get_be32s(f, &controller->ptm[i].la); + } + + return 0; +} + +/* XXX Interrupt acknowledge cycles not supported. */ +PCIBus *ppc4xx_pci_init(CPUState *env, qemu_irq pci_irqs[4], + target_phys_addr_t config_space, + target_phys_addr_t int_ack, + target_phys_addr_t special_cycle, + target_phys_addr_t registers) +{ + PPC4xxPCIState *controller; + int index; + static int ppc4xx_pci_id; + uint8_t *pci_conf; + + controller = qemu_mallocz(sizeof(PPC4xxPCIState)); + + controller->pci_state.bus = pci_register_bus(NULL, "pci", + ppc4xx_pci_set_irq, + ppc4xx_pci_map_irq, + pci_irqs, 0, 4); + + controller->pci_dev = pci_register_device(controller->pci_state.bus, + "host bridge", sizeof(PCIDevice), + 0, NULL, NULL); + pci_conf = controller->pci_dev->config; + pci_config_set_vendor_id(pci_conf, PCI_VENDOR_ID_IBM); + pci_config_set_device_id(pci_conf, PCI_DEVICE_ID_IBM_440GX); + pci_config_set_class(pci_conf, PCI_CLASS_BRIDGE_OTHER); + + /* CFGADDR */ + index = cpu_register_io_memory(pci4xx_cfgaddr_read, + pci4xx_cfgaddr_write, controller, + DEVICE_LITTLE_ENDIAN); + if (index < 0) + goto free; + cpu_register_physical_memory(config_space + PCIC0_CFGADDR, 4, index); + + /* CFGDATA */ + index = pci_host_data_register_mmio(&controller->pci_state, 1); + if (index < 0) + goto free; + cpu_register_physical_memory(config_space + PCIC0_CFGDATA, 4, index); + + /* Internal registers */ + index = cpu_register_io_memory(pci_reg_read, pci_reg_write, controller, + DEVICE_LITTLE_ENDIAN); + if (index < 0) + goto free; + cpu_register_physical_memory(registers, PCI_REG_SIZE, index); + + qemu_register_reset(ppc4xx_pci_reset, controller); + + /* XXX load/save code not tested. */ + register_savevm(&controller->pci_dev->qdev, "ppc4xx_pci", ppc4xx_pci_id++, + 1, ppc4xx_pci_save, ppc4xx_pci_load, controller); + + return controller->pci_state.bus; + +free: + printf("%s error\n", __func__); + qemu_free(controller); + return NULL; +} diff --git a/hw/ppc_mac.h b/hw/ppc_mac.h new file mode 100644 index 0000000..ea87593 --- /dev/null +++ b/hw/ppc_mac.h @@ -0,0 +1,113 @@ +/* + * 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__ + +/* SMP is not enabled, for now */ +#define MAX_CPUS 1 + +#define BIOS_SIZE (1024 * 1024) +#define BIOS_FILENAME "ppc_rom.bin" +#define NVRAM_SIZE 0x2000 +#define PROM_FILENAME "openbios-ppc" +#define PROM_ADDR 0xfff00000 + +#define KERNEL_LOAD_ADDR 0x01000000 +#define CMDLINE_ADDR 0x027ff000 +#define INITRD_LOAD_ADDR 0x02800000 + +#define ESCC_CLOCK 3686400 + +/* Cuda */ +void cuda_init (int *cuda_mem_index, qemu_irq irq); + +/* MacIO */ +void macio_init (PCIBus *bus, int device_id, int is_oldworld, int pic_mem_index, + int dbdma_mem_index, int cuda_mem_index, void *nvram, + int nb_ide, int *ide_mem_index, int escc_mem_index); + +/* Heathrow PIC */ +qemu_irq *heathrow_pic_init(int *pmem_index, + int nb_cpus, qemu_irq **irqs); + +/* Grackle PCI */ +PCIBus *pci_grackle_init(uint32_t base, qemu_irq *pic); + +/* UniNorth PCI */ +PCIBus *pci_pmac_init(qemu_irq *pic); +PCIBus *pci_pmac_u3_init(qemu_irq *pic); + +/* Mac NVRAM */ +typedef struct MacIONVRAMState MacIONVRAMState; + +MacIONVRAMState *macio_nvram_init (int *mem_index, target_phys_addr_t size, + unsigned int it_shift); +void macio_nvram_map (void *opaque, target_phys_addr_t mem_base); +void pmac_format_nvram_partition (MacIONVRAMState *nvr, int len); +uint32_t macio_nvram_read (void *opaque, uint32_t addr); +void macio_nvram_write (void *opaque, uint32_t addr, uint32_t val); + +/* adb.c */ + +#define MAX_ADB_DEVICES 16 + +#define ADB_MAX_OUT_LEN 16 + +typedef struct ADBDevice ADBDevice; + +/* buf = NULL means polling */ +typedef int ADBDeviceRequest(ADBDevice *d, uint8_t *buf_out, + const uint8_t *buf, int len); +typedef int ADBDeviceReset(ADBDevice *d); + +struct ADBDevice { + struct ADBBusState *bus; + int devaddr; + int handler; + ADBDeviceRequest *devreq; + ADBDeviceReset *devreset; + void *opaque; +}; + +typedef struct ADBBusState { + ADBDevice devices[MAX_ADB_DEVICES]; + int nb_devices; + int poll_index; +} ADBBusState; + +int adb_request(ADBBusState *s, uint8_t *buf_out, + const uint8_t *buf, int len); +int adb_poll(ADBBusState *s, uint8_t *buf_out); + +ADBDevice *adb_register_device(ADBBusState *s, int devaddr, + ADBDeviceRequest *devreq, + ADBDeviceReset *devreset, + void *opaque); +void adb_kbd_init(ADBBusState *bus); +void adb_mouse_init(ADBBusState *bus); + +extern ADBBusState adb_bus; + +#endif /* !defined(__PPC_MAC_H__) */ diff --git a/hw/ppc_newworld.c b/hw/ppc_newworld.c new file mode 100644 index 0000000..b9245f0 --- /dev/null +++ b/hw/ppc_newworld.c @@ -0,0 +1,429 @@ +/* + * 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.h" +#include "ppc.h" +#include "ppc_mac.h" +#include "mac_dbdma.h" +#include "nvram.h" +#include "pc.h" +#include "pci.h" +#include "usb-ohci.h" +#include "net.h" +#include "sysemu.h" +#include "boards.h" +#include "fw_cfg.h" +#include "escc.h" +#include "openpic.h" +#include "ide.h" +#include "loader.h" +#include "elf.h" +#include "kvm.h" +#include "kvm_ppc.h" +#include "hw/usb.h" +#include "blockdev.h" + +#define MAX_IDE_BUS 2 +#define CFG_ADDR 0xf0000510 + +/* 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_writel (void *opaque, target_phys_addr_t addr, uint32_t value) +{ + UNIN_DPRINTF("writel addr " TARGET_FMT_plx " val %x\n", addr, value); +} + +static uint32_t unin_readl (void *opaque, target_phys_addr_t addr) +{ + uint32_t value; + + value = 0; + UNIN_DPRINTF("readl addr " TARGET_FMT_plx " val %x\n", addr, value); + + return value; +} + +static CPUWriteMemoryFunc * const unin_write[] = { + &unin_writel, + &unin_writel, + &unin_writel, +}; + +static CPUReadMemoryFunc * const unin_read[] = { + &unin_readl, + &unin_readl, + &unin_readl, +}; + +static int fw_cfg_boot_set(void *opaque, const char *boot_device) +{ + fw_cfg_add_i16(opaque, FW_CFG_BOOT_DEVICE, boot_device[0]); + return 0; +} + +static uint64_t translate_kernel_address(void *opaque, uint64_t addr) +{ + return (addr & 0x0fffffff) + KERNEL_LOAD_ADDR; +} + +/* PowerPC Mac99 hardware initialisation */ +static void ppc_core99_init (ram_addr_t ram_size, + const char *boot_device, + const char *kernel_filename, + const char *kernel_cmdline, + const char *initrd_filename, + const char *cpu_model) +{ + CPUState *env = NULL; + char *filename; + qemu_irq *pic, **openpic_irqs; + int unin_memory; + int linux_boot, i; + ram_addr_t ram_offset, bios_offset; + uint32_t kernel_base, initrd_base; + long kernel_size, initrd_size; + PCIBus *pci_bus; + MacIONVRAMState *nvr; + int nvram_mem_index; + int bios_size; + int pic_mem_index, dbdma_mem_index, cuda_mem_index, escc_mem_index; + int ide_mem_index[3]; + int ppc_boot_device; + DriveInfo *hd[MAX_IDE_BUS * MAX_IDE_DEVS]; + void *fw_cfg; + void *dbdma; + int machine_arch; + + linux_boot = (kernel_filename != NULL); + + /* init CPUs */ + if (cpu_model == NULL) +#ifdef TARGET_PPC64 + cpu_model = "970fx"; +#else + cpu_model = "G4"; +#endif + for (i = 0; i < smp_cpus; i++) { + env = cpu_init(cpu_model); + if (!env) { + fprintf(stderr, "Unable to find PowerPC CPU definition\n"); + exit(1); + } + /* Set time-base frequency to 100 Mhz */ + cpu_ppc_tb_init(env, 100UL * 1000UL * 1000UL); + qemu_register_reset((QEMUResetHandler*)&cpu_reset, env); + } + + /* allocate RAM */ + ram_offset = qemu_ram_alloc(NULL, "ppc_core99.ram", ram_size); + cpu_register_physical_memory(0, ram_size, ram_offset); + + /* allocate and load BIOS */ + bios_offset = qemu_ram_alloc(NULL, "ppc_core99.bios", BIOS_SIZE); + if (bios_name == NULL) + bios_name = PROM_FILENAME; + filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, bios_name); + cpu_register_physical_memory(PROM_ADDR, BIOS_SIZE, bios_offset | IO_MEM_ROM); + + /* Load OpenBIOS (ELF) */ + if (filename) { + bios_size = load_elf(filename, NULL, NULL, NULL, + NULL, NULL, 1, ELF_MACHINE, 0); + + qemu_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 = 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); + exit(1); + } + } 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'; + /* 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); + } + } + + isa_mem_base = 0x80000000; + + /* Register 8 MB of ISA IO space */ + isa_mmio_init(0xf2000000, 0x00800000); + + /* UniN init */ + unin_memory = cpu_register_io_memory(unin_read, unin_write, NULL, + DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(0xf8000000, 0x00001000, unin_memory); + + openpic_irqs = qemu_mallocz(smp_cpus * sizeof(qemu_irq *)); + openpic_irqs[0] = + qemu_mallocz(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 = openpic_init(NULL, &pic_mem_index, smp_cpus, openpic_irqs, NULL); + if (PPC_INPUT(env) == PPC_FLAGS_INPUT_970) { + /* 970 gets a U3 bus */ + pci_bus = pci_pmac_u3_init(pic); + machine_arch = ARCH_MAC99_U3; + } else { + pci_bus = pci_pmac_init(pic); + machine_arch = ARCH_MAC99; + } + /* init basic PC hardware */ + pci_vga_init(pci_bus); + + escc_mem_index = escc_init(0x80013000, pic[0x25], pic[0x24], + serial_hds[0], serial_hds[1], ESCC_CLOCK, 4); + + for(i = 0; i < nb_nics; i++) + pci_nic_init_nofail(&nd_table[i], "ne2k_pci", NULL); + + if (drive_get_max_bus(IF_IDE) >= MAX_IDE_BUS) { + fprintf(stderr, "qemu: too many IDE bus\n"); + exit(1); + } + dbdma = DBDMA_init(&dbdma_mem_index); + + /* We only emulate 2 out of 3 IDE controllers for now */ + ide_mem_index[0] = -1; + hd[0] = drive_get(IF_IDE, 0, 0); + hd[1] = drive_get(IF_IDE, 0, 1); + ide_mem_index[1] = pmac_ide_init(hd, pic[0x0d], dbdma, 0x16, pic[0x02]); + hd[0] = drive_get(IF_IDE, 1, 0); + hd[1] = drive_get(IF_IDE, 1, 1); + ide_mem_index[2] = pmac_ide_init(hd, pic[0x0e], dbdma, 0x1a, pic[0x02]); + + /* cuda also initialize ADB */ + if (machine_arch == ARCH_MAC99_U3) { + usb_enabled = 1; + } + cuda_init(&cuda_mem_index, pic[0x19]); + + adb_kbd_init(&adb_bus); + adb_mouse_init(&adb_bus); + + macio_init(pci_bus, PCI_DEVICE_ID_APPLE_UNI_N_KEYL, 0, pic_mem_index, + dbdma_mem_index, cuda_mem_index, NULL, 3, ide_mem_index, + escc_mem_index); + + if (usb_enabled) { + usb_ohci_init_pci(pci_bus, -1); + } + + /* U3 needs to use USB for input because Linux doesn't support via-cuda + on PPC64 */ + if (machine_arch == ARCH_MAC99_U3) { + usbdevice_create("keyboard"); + usbdevice_create("mouse"); + } + + if (graphic_depth != 15 && graphic_depth != 32 && graphic_depth != 8) + graphic_depth = 15; + + /* The NewWorld NVRAM is not located in the MacIO device */ + nvr = macio_nvram_init(&nvram_mem_index, 0x2000, 1); + pmac_format_nvram_partition(nvr, 0x2000); + macio_nvram_map(nvr, 0xFFF04000); + /* No PCI init: the BIOS will do it */ + + fw_cfg = fw_cfg_init(0, 0, CFG_ADDR, CFG_ADDR + 2); + fw_cfg_add_i32(fw_cfg, FW_CFG_ID, 1); + 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_ADDR); + pstrcpy_targphys("cmdline", CMDLINE_ADDR, 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; + + fw_cfg_add_i32(fw_cfg, FW_CFG_PPC_TBFREQ, kvmppc_get_tbfreq()); + hypercall = qemu_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 + } else { + fw_cfg_add_i32(fw_cfg, FW_CFG_PPC_TBFREQ, get_ticks_per_sec()); + } + + qemu_register_boot_set(fw_cfg_boot_set, fw_cfg); +} + +static QEMUMachine core99_machine = { + .name = "mac99", + .desc = "Mac99 based PowerMAC", + .init = ppc_core99_init, + .max_cpus = MAX_CPUS, +#ifdef TARGET_PPC64 + .is_default = 1, +#endif +}; + +static void core99_machine_init(void) +{ + qemu_register_machine(&core99_machine); +} + +machine_init(core99_machine_init); diff --git a/hw/ppc_oldworld.c b/hw/ppc_oldworld.c new file mode 100644 index 0000000..8a4e088 --- /dev/null +++ b/hw/ppc_oldworld.c @@ -0,0 +1,332 @@ + +/* + * 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.h" +#include "ppc.h" +#include "ppc_mac.h" +#include "mac_dbdma.h" +#include "nvram.h" +#include "pc.h" +#include "sysemu.h" +#include "net.h" +#include "isa.h" +#include "pci.h" +#include "usb-ohci.h" +#include "boards.h" +#include "fw_cfg.h" +#include "escc.h" +#include "ide.h" +#include "loader.h" +#include "elf.h" +#include "kvm.h" +#include "kvm_ppc.h" +#include "blockdev.h" + +#define MAX_IDE_BUS 2 +#define CFG_ADDR 0xf0000510 + +static int fw_cfg_boot_set(void *opaque, const char *boot_device) +{ + fw_cfg_add_i16(opaque, FW_CFG_BOOT_DEVICE, boot_device[0]); + return 0; +} + + +static uint64_t translate_kernel_address(void *opaque, uint64_t addr) +{ + return (addr & 0x0fffffff) + KERNEL_LOAD_ADDR; +} + +static void ppc_heathrow_init (ram_addr_t ram_size, + const char *boot_device, + const char *kernel_filename, + const char *kernel_cmdline, + const char *initrd_filename, + const char *cpu_model) +{ + CPUState *env = NULL; + char *filename; + qemu_irq *pic, **heathrow_irqs; + int linux_boot, i; + ram_addr_t ram_offset, bios_offset; + uint32_t kernel_base, initrd_base; + int32_t kernel_size, initrd_size; + PCIBus *pci_bus; + MacIONVRAMState *nvr; + int bios_size; + int pic_mem_index, nvram_mem_index, dbdma_mem_index, cuda_mem_index; + int escc_mem_index, ide_mem_index[2]; + uint16_t ppc_boot_device; + DriveInfo *hd[MAX_IDE_BUS * MAX_IDE_DEVS]; + void *fw_cfg; + void *dbdma; + + linux_boot = (kernel_filename != NULL); + + /* init CPUs */ + if (cpu_model == NULL) + cpu_model = "G3"; + for (i = 0; i < smp_cpus; i++) { + env = cpu_init(cpu_model); + if (!env) { + fprintf(stderr, "Unable to find PowerPC CPU definition\n"); + exit(1); + } + /* Set time-base frequency to 16.6 Mhz */ + cpu_ppc_tb_init(env, 16600000UL); + qemu_register_reset((QEMUResetHandler*)&cpu_reset, env); + } + + /* 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); + } + + ram_offset = qemu_ram_alloc(NULL, "ppc_heathrow.ram", ram_size); + cpu_register_physical_memory(0, ram_size, ram_offset); + + /* allocate and load BIOS */ + bios_offset = qemu_ram_alloc(NULL, "ppc_heathrow.bios", BIOS_SIZE); + if (bios_name == NULL) + bios_name = PROM_FILENAME; + filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, bios_name); + cpu_register_physical_memory(PROM_ADDR, BIOS_SIZE, bios_offset | IO_MEM_ROM); + + /* Load OpenBIOS (ELF) */ + if (filename) { + bios_size = load_elf(filename, 0, NULL, NULL, NULL, NULL, + 1, ELF_MACHINE, 0); + qemu_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 = 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); + exit(1); + } + } 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 (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); + } + } + + isa_mem_base = 0x80000000; + + /* Register 2 MB of ISA IO space */ + isa_mmio_init(0xfe000000, 0x00200000); + + /* XXX: we register only 1 output pin for heathrow PIC */ + heathrow_irqs = qemu_mallocz(smp_cpus * sizeof(qemu_irq *)); + heathrow_irqs[0] = + qemu_mallocz(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"); + } + } + + /* 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_index, 1, heathrow_irqs); + pci_bus = pci_grackle_init(0xfec00000, pic); + pci_vga_init(pci_bus); + + escc_mem_index = escc_init(0x80013000, pic[0x0f], pic[0x10], serial_hds[0], + serial_hds[1], ESCC_CLOCK, 4); + + for(i = 0; i < nb_nics; i++) + pci_nic_init_nofail(&nd_table[i], "ne2k_pci", NULL); + + + if (drive_get_max_bus(IF_IDE) >= MAX_IDE_BUS) { + fprintf(stderr, "qemu: too many IDE bus\n"); + exit(1); + } + + /* First IDE channel is a MAC IDE on the MacIO bus */ + hd[0] = drive_get(IF_IDE, 0, 0); + hd[1] = drive_get(IF_IDE, 0, 1); + dbdma = DBDMA_init(&dbdma_mem_index); + ide_mem_index[0] = -1; + ide_mem_index[1] = pmac_ide_init(hd, pic[0x0D], dbdma, 0x16, pic[0x02]); + + /* Second IDE channel is a CMD646 on the PCI bus */ + hd[0] = drive_get(IF_IDE, 1, 0); + hd[1] = drive_get(IF_IDE, 1, 1); + hd[3] = hd[2] = NULL; + pci_cmd646_ide_init(pci_bus, hd, 0); + + /* cuda also initialize ADB */ + cuda_init(&cuda_mem_index, pic[0x12]); + + adb_kbd_init(&adb_bus); + adb_mouse_init(&adb_bus); + + nvr = macio_nvram_init(&nvram_mem_index, 0x2000, 4); + pmac_format_nvram_partition(nvr, 0x2000); + + macio_init(pci_bus, PCI_DEVICE_ID_APPLE_343S1201, 1, pic_mem_index, + dbdma_mem_index, cuda_mem_index, nvr, 2, ide_mem_index, + escc_mem_index); + + if (usb_enabled) { + usb_ohci_init_pci(pci_bus, -1); + } + + 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(0, 0, CFG_ADDR, CFG_ADDR + 2); + fw_cfg_add_i32(fw_cfg, FW_CFG_ID, 1); + 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_ADDR); + pstrcpy_targphys("cmdline", CMDLINE_ADDR, 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; + + fw_cfg_add_i32(fw_cfg, FW_CFG_PPC_TBFREQ, kvmppc_get_tbfreq()); + hypercall = qemu_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 + } else { + fw_cfg_add_i32(fw_cfg, FW_CFG_PPC_TBFREQ, get_ticks_per_sec()); + } + + qemu_register_boot_set(fw_cfg_boot_set, fw_cfg); +} + +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 +}; + +static void heathrow_machine_init(void) +{ + qemu_register_machine(&heathrow_machine); +} + +machine_init(heathrow_machine_init); diff --git a/hw/ppc_prep.c b/hw/ppc_prep.c new file mode 100644 index 0000000..6b22122 --- /dev/null +++ b/hw/ppc_prep.c @@ -0,0 +1,771 @@ +/* + * 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.h" +#include "nvram.h" +#include "pc.h" +#include "fdc.h" +#include "net.h" +#include "sysemu.h" +#include "isa.h" +#include "pci.h" +#include "prep_pci.h" +#include "usb-ohci.h" +#include "ppc.h" +#include "boards.h" +#include "qemu-log.h" +#include "ide.h" +#include "loader.h" +#include "mc146818rtc.h" +#include "blockdev.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 }; + +//static PITState *pit; + +/* ISA IO ports bridge */ +#define PPC_IO_BASE 0x80000000 + +#if 0 +/* Speaker port 0x61 */ +static int speaker_data_on; +static int dummy_refresh_clock; +#endif + +static void speaker_ioport_write (void *opaque, uint32_t addr, uint32_t val) +{ +#if 0 + speaker_data_on = (val >> 1) & 1; + pit_set_gate(pit, 2, val & 1); +#endif +} + +static uint32_t speaker_ioport_read (void *opaque, uint32_t addr) +{ +#if 0 + int out; + out = pit_get_out(pit, 2, qemu_get_clock(vm_clock)); + dummy_refresh_clock ^= 1; + return (speaker_data_on << 1) | pit_get_gate(pit, 2) | (out << 5) | + (dummy_refresh_clock << 4); +#endif + return 0; +} + +/* PCI intack register */ +/* Read-only register (?) */ +static void _PPC_intack_write (void *opaque, + target_phys_addr_t addr, uint32_t value) +{ +#if 0 + printf("%s: 0x" TARGET_FMT_plx " => 0x%08" PRIx32 "\n", __func__, addr, + value); +#endif +} + +static inline uint32_t _PPC_intack_read(target_phys_addr_t addr) +{ + uint32_t retval = 0; + + if ((addr & 0xf) == 0) + retval = pic_intack_read(isa_pic); +#if 0 + printf("%s: 0x" TARGET_FMT_plx " <= %08" PRIx32 "\n", __func__, addr, + retval); +#endif + + return retval; +} + +static uint32_t PPC_intack_readb (void *opaque, target_phys_addr_t addr) +{ + return _PPC_intack_read(addr); +} + +static uint32_t PPC_intack_readw (void *opaque, target_phys_addr_t addr) +{ + return _PPC_intack_read(addr); +} + +static uint32_t PPC_intack_readl (void *opaque, target_phys_addr_t addr) +{ + return _PPC_intack_read(addr); +} + +static CPUWriteMemoryFunc * const PPC_intack_write[] = { + &_PPC_intack_write, + &_PPC_intack_write, + &_PPC_intack_write, +}; + +static CPUReadMemoryFunc * const PPC_intack_read[] = { + &PPC_intack_readb, + &PPC_intack_readw, + &PPC_intack_readl, +}; + +/* 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, + target_phys_addr_t addr, uint32_t value) +{ + printf("%s: 0x" TARGET_FMT_plx " => 0x%08" PRIx32 "\n", __func__, addr, + value); +} + +static void PPC_XCSR_writew (void *opaque, + target_phys_addr_t addr, uint32_t value) +{ + printf("%s: 0x" TARGET_FMT_plx " => 0x%08" PRIx32 "\n", __func__, addr, + value); +} + +static void PPC_XCSR_writel (void *opaque, + target_phys_addr_t 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, target_phys_addr_t 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, target_phys_addr_t 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, target_phys_addr_t addr) +{ + uint32_t retval = 0; + + printf("%s: 0x" TARGET_FMT_plx " <= %08" PRIx32 "\n", __func__, addr, + retval); + + return retval; +} + +static CPUWriteMemoryFunc * const PPC_XCSR_write[] = { + &PPC_XCSR_writeb, + &PPC_XCSR_writew, + &PPC_XCSR_writel, +}; + +static CPUReadMemoryFunc * const PPC_XCSR_read[] = { + &PPC_XCSR_readb, + &PPC_XCSR_readw, + &PPC_XCSR_readl, +}; +#endif + +/* Fake super-io ports for PREP platform (Intel 82378ZB) */ +typedef struct sysctrl_t { + qemu_irq reset_irq; + M48t59State *nvram; + uint8_t state; + uint8_t syscontrol; + uint8_t fake_io[2]; + int contiguous_map; + int endian; +} sysctrl_t; + +enum { + STATE_HARDFILE = 0x01, +}; + +static sysctrl_t *sysctrl; + +static void PREP_io_write (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); + sysctrl->fake_io[addr - 0x0398] = val; +} + +static uint32_t PREP_io_read (void *opaque, uint32_t addr) +{ + sysctrl_t *sysctrl = opaque; + + PPC_IO_DPRINTF("0x%08" PRIx32 " <= 0x%02" PRIx32 "\n", addr - PPC_IO_BASE, + sysctrl->fake_io[addr - 0x0398]); + return sysctrl->fake_io[addr - 0x0398]; +} + +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) + m48t59_toggle_lock(sysctrl->nvram, 1); + break; + case 0x0812: + /* Password protect 2 register */ + if (sysctrl->nvram != NULL) + m48t59_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; + 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 = 0x00; + 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; +} + +static inline target_phys_addr_t prep_IO_address(sysctrl_t *sysctrl, + target_phys_addr_t addr) +{ + if (sysctrl->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); + } + + return addr; +} + +static void PPC_prep_io_writeb (void *opaque, target_phys_addr_t addr, + uint32_t value) +{ + sysctrl_t *sysctrl = opaque; + + addr = prep_IO_address(sysctrl, addr); + cpu_outb(addr, value); +} + +static uint32_t PPC_prep_io_readb (void *opaque, target_phys_addr_t addr) +{ + sysctrl_t *sysctrl = opaque; + uint32_t ret; + + addr = prep_IO_address(sysctrl, addr); + ret = cpu_inb(addr); + + return ret; +} + +static void PPC_prep_io_writew (void *opaque, target_phys_addr_t addr, + uint32_t value) +{ + sysctrl_t *sysctrl = opaque; + + addr = prep_IO_address(sysctrl, addr); + PPC_IO_DPRINTF("0x" TARGET_FMT_plx " => 0x%08" PRIx32 "\n", addr, value); + cpu_outw(addr, value); +} + +static uint32_t PPC_prep_io_readw (void *opaque, target_phys_addr_t addr) +{ + sysctrl_t *sysctrl = opaque; + uint32_t ret; + + addr = prep_IO_address(sysctrl, addr); + ret = cpu_inw(addr); + PPC_IO_DPRINTF("0x" TARGET_FMT_plx " <= 0x%08" PRIx32 "\n", addr, ret); + + return ret; +} + +static void PPC_prep_io_writel (void *opaque, target_phys_addr_t addr, + uint32_t value) +{ + sysctrl_t *sysctrl = opaque; + + addr = prep_IO_address(sysctrl, addr); + PPC_IO_DPRINTF("0x" TARGET_FMT_plx " => 0x%08" PRIx32 "\n", addr, value); + cpu_outl(addr, value); +} + +static uint32_t PPC_prep_io_readl (void *opaque, target_phys_addr_t addr) +{ + sysctrl_t *sysctrl = opaque; + uint32_t ret; + + addr = prep_IO_address(sysctrl, addr); + ret = cpu_inl(addr); + PPC_IO_DPRINTF("0x" TARGET_FMT_plx " <= 0x%08" PRIx32 "\n", addr, ret); + + return ret; +} + +static CPUWriteMemoryFunc * const PPC_prep_io_write[] = { + &PPC_prep_io_writeb, + &PPC_prep_io_writew, + &PPC_prep_io_writel, +}; + +static CPUReadMemoryFunc * const PPC_prep_io_read[] = { + &PPC_prep_io_readb, + &PPC_prep_io_readw, + &PPC_prep_io_readl, +}; + +#define NVRAM_SIZE 0x2000 + +static void cpu_request_exit(void *opaque, int irq, int level) +{ + CPUState *env = cpu_single_env; + + if (env && level) { + cpu_exit(env); + } +} + +/* PowerPC PREP hardware initialisation */ +static void ppc_prep_init (ram_addr_t ram_size, + const char *boot_device, + const char *kernel_filename, + const char *kernel_cmdline, + const char *initrd_filename, + const char *cpu_model) +{ + CPUState *env = NULL; + char *filename; + nvram_t nvram; + M48t59State *m48t59; + int PPC_io_memory; + int linux_boot, i, nb_nics1, bios_size; + ram_addr_t ram_offset, bios_offset; + uint32_t kernel_base, initrd_base; + long kernel_size, initrd_size; + PCIBus *pci_bus; + qemu_irq *i8259; + qemu_irq *cpu_exit_irq; + int ppc_boot_device; + DriveInfo *hd[MAX_IDE_BUS * MAX_IDE_DEVS]; + DriveInfo *fd[MAX_FD]; + + sysctrl = qemu_mallocz(sizeof(sysctrl_t)); + + linux_boot = (kernel_filename != NULL); + + /* init CPUs */ + if (cpu_model == NULL) + cpu_model = "602"; + for (i = 0; i < smp_cpus; i++) { + env = cpu_init(cpu_model); + if (!env) { + fprintf(stderr, "Unable to find PowerPC CPU definition\n"); + exit(1); + } + 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((QEMUResetHandler*)&cpu_reset, env); + } + + /* allocate RAM */ + ram_offset = qemu_ram_alloc(NULL, "ppc_prep.ram", ram_size); + cpu_register_physical_memory(0, ram_size, ram_offset); + + /* allocate and load BIOS */ + bios_offset = qemu_ram_alloc(NULL, "ppc_prep.bios", BIOS_SIZE); + 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 <= BIOS_SIZE) { + target_phys_addr_t bios_addr; + bios_size = (bios_size + 0xfff) & ~0xfff; + bios_addr = (uint32_t)(-bios_size); + cpu_register_physical_memory(bios_addr, bios_size, + bios_offset | IO_MEM_ROM); + bios_size = load_image_targphys(filename, bios_addr, bios_size); + } + if (bios_size < 0 || bios_size > BIOS_SIZE) { + hw_error("qemu: could not load PPC PREP bios '%s'\n", bios_name); + } + if (filename) { + qemu_free(filename); + } + + 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); + } + } + + isa_mem_base = 0xc0000000; + if (PPC_INPUT(env) != PPC_FLAGS_INPUT_6xx) { + hw_error("Only 6xx bus is supported on PREP machine\n"); + } + i8259 = i8259_init(first_cpu->irq_inputs[PPC6xx_INPUT_INT]); + pci_bus = pci_prep_init(i8259); + /* Hmm, prep has no pci-isa bridge ??? */ + isa_bus_new(NULL); + isa_bus_irqs(i8259); + // pci_bus = i440fx_init(); + /* Register 8 MB of ISA IO space (needed for non-contiguous map) */ + PPC_io_memory = cpu_register_io_memory(PPC_prep_io_read, + PPC_prep_io_write, sysctrl, + DEVICE_LITTLE_ENDIAN); + cpu_register_physical_memory(0x80000000, 0x00800000, PPC_io_memory); + + /* init basic PC hardware */ + pci_vga_init(pci_bus); + // openpic = openpic_init(0x00000000, 0xF0000000, 1); + // pit = pit_init(0x40, i8259[0]); + rtc_init(2000, NULL); + + if (serial_hds[0]) + serial_isa_init(0, serial_hds[0]); + 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 = qemu_strdup("ne2k_isa"); + } + if (strcmp(nd_table[i].model, "ne2k_isa") == 0) { + isa_ne2000_init(ne2000_io[i], ne2000_irq[i], &nd_table[i]); + } else { + pci_nic_init_nofail(&nd_table[i], "ne2k_pci", NULL); + } + } + + if (drive_get_max_bus(IF_IDE) >= MAX_IDE_BUS) { + fprintf(stderr, "qemu: too many IDE bus\n"); + exit(1); + } + + for(i = 0; i < MAX_IDE_BUS * MAX_IDE_DEVS; i++) { + hd[i] = drive_get(IF_IDE, i / MAX_IDE_DEVS, i % MAX_IDE_DEVS); + } + + for(i = 0; i < MAX_IDE_BUS; i++) { + isa_ide_init(ide_iobase[i], ide_iobase2[i], ide_irq[i], + hd[2 * i], + hd[2 * i + 1]); + } + isa_create_simple("i8042"); + + cpu_exit_irq = qemu_allocate_irqs(cpu_request_exit, NULL, 1); + DMA_init(1, cpu_exit_irq); + + // SB16_init(); + + for(i = 0; i < MAX_FD; i++) { + fd[i] = drive_get(IF_FLOPPY, 0, i); + } + fdctrl_init_isa(fd); + + /* Register speaker port */ + register_ioport_read(0x61, 1, 1, speaker_ioport_read, NULL); + register_ioport_write(0x61, 1, 1, speaker_ioport_write, NULL); + /* Register fake IO ports for PREP */ + sysctrl->reset_irq = first_cpu->irq_inputs[PPC6xx_INPUT_HRESET]; + register_ioport_read(0x398, 2, 1, &PREP_io_read, sysctrl); + register_ioport_write(0x398, 2, 1, &PREP_io_write, sysctrl); + /* System control ports */ + register_ioport_read(0x0092, 0x01, 1, &PREP_io_800_readb, sysctrl); + register_ioport_write(0x0092, 0x01, 1, &PREP_io_800_writeb, sysctrl); + register_ioport_read(0x0800, 0x52, 1, &PREP_io_800_readb, sysctrl); + register_ioport_write(0x0800, 0x52, 1, &PREP_io_800_writeb, sysctrl); + /* PCI intack location */ + PPC_io_memory = cpu_register_io_memory(PPC_intack_read, + PPC_intack_write, NULL, + DEVICE_LITTLE_ENDIAN); + cpu_register_physical_memory(0xBFFFFFF0, 0x4, PPC_io_memory); + /* PowerPC control and status register group */ +#if 0 + PPC_io_memory = cpu_register_io_memory(PPC_XCSR_read, PPC_XCSR_write, + NULL, DEVICE_LITTLE_ENDIAN); + cpu_register_physical_memory(0xFEFF0000, 0x1000, PPC_io_memory); +#endif + + if (usb_enabled) { + usb_ohci_init_pci(pci_bus, -1); + } + + m48t59 = m48t59_init(i8259[8], 0, 0x0074, NVRAM_SIZE, 59); + if (m48t59 == NULL) + return; + sysctrl->nvram = m48t59; + + /* Initialise NVRAM */ + nvram.opaque = m48t59; + nvram.read_fn = &m48t59_read; + nvram.write_fn = &m48t59_write; + PPC_NVRAM_set_params(&nvram, 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); + + /* Special port to get debug messages from Open-Firmware */ + register_ioport_write(0x0F00, 4, 1, &PPC_debug_write, NULL); +} + +static QEMUMachine prep_machine = { + .name = "prep", + .desc = "PowerPC PREP platform", + .init = ppc_prep_init, + .max_cpus = MAX_CPUS, +}; + +static void prep_machine_init(void) +{ + qemu_register_machine(&prep_machine); +} + +machine_init(prep_machine_init); diff --git a/hw/ppce500.h b/hw/ppce500.h new file mode 100644 index 0000000..24d49bb --- /dev/null +++ b/hw/ppce500.h @@ -0,0 +1,22 @@ +/* + * QEMU PowerPC E500 emulation shared definitions + * + * Copyright (C) 2009 Freescale Semiconductor, Inc. All rights reserved. + * + * Author: Yu Liu, + * + * This file is derived from hw/ppc440.h + * 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. + */ + +#if !defined(PPC_E500_H) +#define PPC_E500_H + +PCIBus *ppce500_pci_init(qemu_irq *pic, target_phys_addr_t registers); + +#endif /* !defined(PPC_E500_H) */ diff --git a/hw/ppce500_mpc8544ds.c b/hw/ppce500_mpc8544ds.c new file mode 100644 index 0000000..b7670ae --- /dev/null +++ b/hw/ppce500_mpc8544ds.c @@ -0,0 +1,297 @@ +/* + * Qemu PowerPC MPC8544DS board emualtion + * + * Copyright (C) 2009 Freescale Semiconductor, Inc. All rights reserved. + * + * Author: Yu Liu, + * + * 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 + +#include "config.h" +#include "qemu-common.h" +#include "net.h" +#include "hw.h" +#include "pc.h" +#include "pci.h" +#include "boards.h" +#include "sysemu.h" +#include "kvm.h" +#include "kvm_ppc.h" +#include "device_tree.h" +#include "openpic.h" +#include "ppce500.h" +#include "loader.h" +#include "elf.h" + +#define BINARY_DEVICE_TREE_FILE "mpc8544ds.dtb" +#define UIMAGE_LOAD_BASE 0 +#define DTC_LOAD_PAD 0x500000 +#define DTC_PAD_MASK 0xFFFFF +#define INITRD_LOAD_PAD 0x2000000 +#define INITRD_PAD_MASK 0xFFFFFF + +#define RAM_SIZES_ALIGN (64UL << 20) + +#define MPC8544_CCSRBAR_BASE 0xE0000000 +#define MPC8544_MPIC_REGS_BASE (MPC8544_CCSRBAR_BASE + 0x40000) +#define MPC8544_SERIAL0_REGS_BASE (MPC8544_CCSRBAR_BASE + 0x4500) +#define MPC8544_SERIAL1_REGS_BASE (MPC8544_CCSRBAR_BASE + 0x4600) +#define MPC8544_PCI_REGS_BASE (MPC8544_CCSRBAR_BASE + 0x8000) +#define MPC8544_PCI_REGS_SIZE 0x1000 +#define MPC8544_PCI_IO 0xE1000000 +#define MPC8544_PCI_IOLEN 0x10000 + +#ifdef CONFIG_FDT +static int mpc8544_copy_soc_cell(void *fdt, const char *node, const char *prop) +{ + uint32_t cell; + int ret; + + ret = kvmppc_read_host_property(node, prop, &cell, sizeof(cell)); + if (ret < 0) { + fprintf(stderr, "couldn't read host %s/%s\n", node, prop); + goto out; + } + + ret = qemu_devtree_setprop_cell(fdt, "/cpus/PowerPC,8544@0", + prop, cell); + if (ret < 0) { + fprintf(stderr, "couldn't set guest /cpus/PowerPC,8544@0/%s\n", prop); + goto out; + } + +out: + return ret; +} +#endif + +static int mpc8544_load_device_tree(target_phys_addr_t addr, + uint32_t ramsize, + target_phys_addr_t initrd_base, + target_phys_addr_t initrd_size, + const char *kernel_cmdline) +{ + int ret = -1; +#ifdef CONFIG_FDT + uint32_t mem_reg_property[] = {0, ramsize}; + char *filename; + int fdt_size; + void *fdt; + + filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, BINARY_DEVICE_TREE_FILE); + if (!filename) { + goto out; + } + fdt = load_device_tree(filename, &fdt_size); + qemu_free(filename); + if (fdt == NULL) { + goto out; + } + + /* Manipulate device tree in memory. */ + ret = qemu_devtree_setprop(fdt, "/memory", "reg", mem_reg_property, + sizeof(mem_reg_property)); + if (ret < 0) + fprintf(stderr, "couldn't set /memory/reg\n"); + + ret = qemu_devtree_setprop_cell(fdt, "/chosen", "linux,initrd-start", + initrd_base); + if (ret < 0) + fprintf(stderr, "couldn't set /chosen/linux,initrd-start\n"); + + ret = qemu_devtree_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_devtree_setprop_string(fdt, "/chosen", "bootargs", + kernel_cmdline); + if (ret < 0) + fprintf(stderr, "couldn't set /chosen/bootargs\n"); + + if (kvm_enabled()) { + struct dirent *dirp; + DIR *dp; + char buf[128]; + + if ((dp = opendir("/proc/device-tree/cpus/")) == NULL) { + printf("Can't open directory /proc/device-tree/cpus/\n"); + ret = -1; + goto out; + } + + buf[0] = '\0'; + while ((dirp = readdir(dp)) != NULL) { + if (strncmp(dirp->d_name, "PowerPC", 7) == 0) { + snprintf(buf, 128, "/cpus/%s", dirp->d_name); + break; + } + } + closedir(dp); + if (buf[0] == '\0') { + printf("Unknow host!\n"); + ret = -1; + goto out; + } + + mpc8544_copy_soc_cell(fdt, buf, "clock-frequency"); + mpc8544_copy_soc_cell(fdt, buf, "timebase-frequency"); + } + + ret = rom_add_blob_fixed(BINARY_DEVICE_TREE_FILE, fdt, fdt_size, addr); + qemu_free(fdt); + +out: +#endif + + return ret; +} + +static void mpc8544ds_init(ram_addr_t ram_size, + const char *boot_device, + const char *kernel_filename, + const char *kernel_cmdline, + const char *initrd_filename, + const char *cpu_model) +{ + PCIBus *pci_bus; + CPUState *env; + uint64_t elf_entry; + uint64_t elf_lowaddr; + target_phys_addr_t entry=0; + target_phys_addr_t loadaddr=UIMAGE_LOAD_BASE; + target_long kernel_size=0; + target_ulong dt_base = 0; + target_ulong initrd_base = 0; + target_long initrd_size=0; + int i=0; + unsigned int pci_irq_nrs[4] = {1, 2, 3, 4}; + qemu_irq *irqs, *mpic, *pci_irqs; + + /* Setup CPU */ + env = cpu_ppc_init("e500v2_v30"); + if (!env) { + fprintf(stderr, "Unable to initialize CPU!\n"); + exit(1); + } + + /* Fixup Memory size on a alignment boundary */ + ram_size &= ~(RAM_SIZES_ALIGN - 1); + + /* Register Memory */ + cpu_register_physical_memory(0, ram_size, qemu_ram_alloc(NULL, + "mpc8544ds.ram", ram_size)); + + /* MPIC */ + irqs = qemu_mallocz(sizeof(qemu_irq) * OPENPIC_OUTPUT_NB); + irqs[OPENPIC_OUTPUT_INT] = ((qemu_irq *)env->irq_inputs)[PPCE500_INPUT_INT]; + irqs[OPENPIC_OUTPUT_CINT] = ((qemu_irq *)env->irq_inputs)[PPCE500_INPUT_CINT]; + mpic = mpic_init(MPC8544_MPIC_REGS_BASE, 1, &irqs, NULL); + + /* Serial */ + if (serial_hds[0]) { + serial_mm_init(MPC8544_SERIAL0_REGS_BASE, + 0, mpic[12+26], 399193, + serial_hds[0], 1, 1); + } + + if (serial_hds[1]) { + serial_mm_init(MPC8544_SERIAL1_REGS_BASE, + 0, mpic[12+26], 399193, + serial_hds[0], 1, 1); + } + + /* PCI */ + pci_irqs = qemu_malloc(sizeof(qemu_irq) * 4); + pci_irqs[0] = mpic[pci_irq_nrs[0]]; + pci_irqs[1] = mpic[pci_irq_nrs[1]]; + pci_irqs[2] = mpic[pci_irq_nrs[2]]; + pci_irqs[3] = mpic[pci_irq_nrs[3]]; + pci_bus = ppce500_pci_init(pci_irqs, MPC8544_PCI_REGS_BASE); + if (!pci_bus) + printf("couldn't create PCI controller!\n"); + + isa_mmio_init(MPC8544_PCI_IO, MPC8544_PCI_IOLEN); + + if (pci_bus) { + /* Register network interfaces. */ + for (i = 0; i < nb_nics; i++) { + pci_nic_init_nofail(&nd_table[i], "virtio", NULL); + } + } + + /* Load kernel. */ + if (kernel_filename) { + kernel_size = load_uimage(kernel_filename, &entry, &loadaddr, NULL); + if (kernel_size < 0) { + kernel_size = 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 (kernel_size < 0) { + fprintf(stderr, "qemu: could not load kernel '%s'\n", + kernel_filename); + exit(1); + } + } + + /* Load initrd. */ + if (initrd_filename) { + initrd_base = (kernel_size + INITRD_LOAD_PAD) & ~INITRD_PAD_MASK; + 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); + } + } + + /* If we're loading a kernel directly, we must load the device tree too. */ + if (kernel_filename) { + dt_base = (kernel_size + DTC_LOAD_PAD) & ~DTC_PAD_MASK; + if (mpc8544_load_device_tree(dt_base, ram_size, + initrd_base, initrd_size, kernel_cmdline) < 0) { + fprintf(stderr, "couldn't load device tree\n"); + exit(1); + } + + cpu_synchronize_state(env); + + /* Set initial guest state. */ + env->gpr[1] = (16<<20) - 8; + env->gpr[3] = dt_base; + env->nip = entry; + /* XXX we currently depend on KVM to create some initial TLB entries. */ + } + + if (kvm_enabled()) + kvmppc_init(); + + return; +} + +static QEMUMachine mpc8544ds_machine = { + .name = "mpc8544ds", + .desc = "mpc8544ds", + .init = mpc8544ds_init, +}; + +static void mpc8544ds_machine_init(void) +{ + qemu_register_machine(&mpc8544ds_machine); +} + +machine_init(mpc8544ds_machine_init); diff --git a/hw/ppce500_pci.c b/hw/ppce500_pci.c new file mode 100644 index 0000000..11edd03 --- /dev/null +++ b/hw/ppce500_pci.c @@ -0,0 +1,326 @@ +/* + * QEMU PowerPC E500 embedded processors pci controller emulation + * + * Copyright (C) 2009 Freescale Semiconductor, Inc. All rights reserved. + * + * Author: Yu Liu, + * + * 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.h" +#include "ppce500.h" +#include "pci.h" +#include "pci_host.h" +#include "bswap.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_REG_SIZE (0x1000 - PCIE500_REG_BASE) + +#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 + +struct pci_outbound { + uint32_t potar; + uint32_t potear; + uint32_t powbar; + uint32_t powar; +}; + +struct pci_inbound { + uint32_t pitar; + uint32_t piwbar; + uint32_t piwbear; + uint32_t piwar; +}; + +struct PPCE500PCIState { + struct pci_outbound pob[PPCE500_PCI_NR_POBS]; + struct pci_inbound pib[PPCE500_PCI_NR_PIBS]; + uint32_t gasket_time; + PCIHostState pci_state; + PCIDevice *pci_dev; +}; + +typedef struct PPCE500PCIState PPCE500PCIState; + +static uint32_t pci_reg_read4(void *opaque, target_phys_addr_t addr) +{ + PPCE500PCIState *pci = opaque; + unsigned long win; + uint32_t value = 0; + + win = addr & 0xfe0; + + switch (win) { + case PPCE500_PCI_OW1: + case PPCE500_PCI_OW2: + case PPCE500_PCI_OW3: + case PPCE500_PCI_OW4: + switch (addr & 0xC) { + case PCI_POTAR: value = pci->pob[(addr >> 5) & 0x7].potar; break; + case PCI_POTEAR: value = pci->pob[(addr >> 5) & 0x7].potear; break; + case PCI_POWBAR: value = pci->pob[(addr >> 5) & 0x7].powbar; break; + case PCI_POWAR: value = pci->pob[(addr >> 5) & 0x7].powar; break; + default: break; + } + break; + + case PPCE500_PCI_IW3: + case PPCE500_PCI_IW2: + case PPCE500_PCI_IW1: + switch (addr & 0xC) { + case PCI_PITAR: value = pci->pib[(addr >> 5) & 0x3].pitar; break; + case PCI_PIWBAR: value = pci->pib[(addr >> 5) & 0x3].piwbar; break; + case PCI_PIWBEAR: value = pci->pib[(addr >> 5) & 0x3].piwbear; break; + case PCI_PIWAR: value = pci->pib[(addr >> 5) & 0x3].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; +} + +static CPUReadMemoryFunc * const e500_pci_reg_read[] = { + &pci_reg_read4, + &pci_reg_read4, + &pci_reg_read4, +}; + +static void pci_reg_write4(void *opaque, target_phys_addr_t addr, + uint32_t value) +{ + PPCE500PCIState *pci = opaque; + unsigned long win; + + win = addr & 0xfe0; + + pci_debug("%s: value:%x -> win:%lx(addr:" TARGET_FMT_plx ")\n", + __func__, value, win, addr); + + switch (win) { + case PPCE500_PCI_OW1: + case PPCE500_PCI_OW2: + case PPCE500_PCI_OW3: + case PPCE500_PCI_OW4: + switch (addr & 0xC) { + case PCI_POTAR: pci->pob[(addr >> 5) & 0x7].potar = value; break; + case PCI_POTEAR: pci->pob[(addr >> 5) & 0x7].potear = value; break; + case PCI_POWBAR: pci->pob[(addr >> 5) & 0x7].powbar = value; break; + case PCI_POWAR: pci->pob[(addr >> 5) & 0x7].powar = value; break; + default: break; + }; + break; + + case PPCE500_PCI_IW3: + case PPCE500_PCI_IW2: + case PPCE500_PCI_IW1: + switch (addr & 0xC) { + case PCI_PITAR: pci->pib[(addr >> 5) & 0x3].pitar = value; break; + case PCI_PIWBAR: pci->pib[(addr >> 5) & 0x3].piwbar = value; break; + case PCI_PIWBEAR: pci->pib[(addr >> 5) & 0x3].piwbear = value; break; + case PCI_PIWAR: pci->pib[(addr >> 5) & 0x3].piwar = value; break; + default: break; + }; + break; + + case PPCE500_PCI_GASKET_TIMR: + pci->gasket_time = value; + break; + + default: + break; + }; +} + +static CPUWriteMemoryFunc * const e500_pci_reg_write[] = { + &pci_reg_write4, + &pci_reg_write4, + &pci_reg_write4, +}; + +static int mpc85xx_pci_map_irq(PCIDevice *pci_dev, int irq_num) +{ + int devno = pci_dev->devfn >> 3, ret = 0; + + switch (devno) { + /* Two PCI slot */ + case 0x11: + case 0x12: + ret = (irq_num + devno - 0x10) % 4; + break; + default: + printf("Error:%s:unknow dev number\n", __func__); + } + + pci_debug("%s: devfn %x irq %d -> %d devno:%x\n", __func__, + pci_dev->devfn, irq_num, ret, devno); + + return ret; +} + +static void mpc85xx_pci_set_irq(void *opaque, int irq_num, int level) +{ + qemu_irq *pic = opaque; + + pci_debug("%s: PCI irq %d, level:%d\n", __func__, irq_num, level); + + qemu_set_irq(pic[irq_num], level); +} + +static void ppce500_pci_save(QEMUFile *f, void *opaque) +{ + PPCE500PCIState *controller = opaque; + int i; + + pci_device_save(controller->pci_dev, f); + + for (i = 0; i < PPCE500_PCI_NR_POBS; i++) { + qemu_put_be32s(f, &controller->pob[i].potar); + qemu_put_be32s(f, &controller->pob[i].potear); + qemu_put_be32s(f, &controller->pob[i].powbar); + qemu_put_be32s(f, &controller->pob[i].powar); + } + + for (i = 0; i < PPCE500_PCI_NR_PIBS; i++) { + qemu_put_be32s(f, &controller->pib[i].pitar); + qemu_put_be32s(f, &controller->pib[i].piwbar); + qemu_put_be32s(f, &controller->pib[i].piwbear); + qemu_put_be32s(f, &controller->pib[i].piwar); + } + qemu_put_be32s(f, &controller->gasket_time); +} + +static int ppce500_pci_load(QEMUFile *f, void *opaque, int version_id) +{ + PPCE500PCIState *controller = opaque; + int i; + + if (version_id != 1) + return -EINVAL; + + pci_device_load(controller->pci_dev, f); + + for (i = 0; i < PPCE500_PCI_NR_POBS; i++) { + qemu_get_be32s(f, &controller->pob[i].potar); + qemu_get_be32s(f, &controller->pob[i].potear); + qemu_get_be32s(f, &controller->pob[i].powbar); + qemu_get_be32s(f, &controller->pob[i].powar); + } + + for (i = 0; i < PPCE500_PCI_NR_PIBS; i++) { + qemu_get_be32s(f, &controller->pib[i].pitar); + qemu_get_be32s(f, &controller->pib[i].piwbar); + qemu_get_be32s(f, &controller->pib[i].piwbear); + qemu_get_be32s(f, &controller->pib[i].piwar); + } + qemu_get_be32s(f, &controller->gasket_time); + + return 0; +} + +PCIBus *ppce500_pci_init(qemu_irq pci_irqs[4], target_phys_addr_t registers) +{ + PPCE500PCIState *controller; + PCIDevice *d; + int index; + static int ppce500_pci_id; + + controller = qemu_mallocz(sizeof(PPCE500PCIState)); + + controller->pci_state.bus = pci_register_bus(NULL, "pci", + mpc85xx_pci_set_irq, + mpc85xx_pci_map_irq, + pci_irqs, PCI_DEVFN(0x11, 0), + 4); + d = pci_register_device(controller->pci_state.bus, + "host bridge", sizeof(PCIDevice), + 0, NULL, NULL); + + pci_config_set_vendor_id(d->config, PCI_VENDOR_ID_FREESCALE); + pci_config_set_device_id(d->config, PCI_DEVICE_ID_MPC8533E); + pci_config_set_class(d->config, PCI_CLASS_PROCESSOR_POWERPC); + + controller->pci_dev = d; + + /* CFGADDR */ + index = pci_host_conf_register_mmio(&controller->pci_state, + DEVICE_BIG_ENDIAN); + if (index < 0) + goto free; + cpu_register_physical_memory(registers + PCIE500_CFGADDR, 4, index); + + /* CFGDATA */ + index = pci_host_data_register_mmio(&controller->pci_state, + DEVICE_BIG_ENDIAN); + if (index < 0) + goto free; + cpu_register_physical_memory(registers + PCIE500_CFGDATA, 4, index); + + index = cpu_register_io_memory(e500_pci_reg_read, + e500_pci_reg_write, controller, + DEVICE_NATIVE_ENDIAN); + if (index < 0) + goto free; + cpu_register_physical_memory(registers + PCIE500_REG_BASE, + PCIE500_REG_SIZE, index); + + /* XXX load/save code not tested. */ + register_savevm(&d->qdev, "ppce500_pci", ppce500_pci_id++, + 1, ppce500_pci_save, ppce500_pci_load, controller); + + return controller->pci_state.bus; + +free: + printf("%s error\n", __func__); + qemu_free(controller); + return NULL; +} diff --git a/hw/prep_pci.c b/hw/prep_pci.c new file mode 100644 index 0000000..f88b825 --- /dev/null +++ b/hw/prep_pci.c @@ -0,0 +1,144 @@ +/* + * QEMU PREP PCI host + * + * 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.h" +#include "pci.h" +#include "pci_host.h" +#include "prep_pci.h" + +typedef PCIHostState PREPPCIState; + +static inline uint32_t PPC_PCIIO_config(target_phys_addr_t addr) +{ + int i; + + for(i = 0; i < 11; i++) { + if ((addr & (1 << (11 + i))) != 0) + break; + } + return (addr & 0x7ff) | (i << 11); +} + +static void PPC_PCIIO_writeb (void *opaque, target_phys_addr_t addr, uint32_t val) +{ + PREPPCIState *s = opaque; + pci_data_write(s->bus, PPC_PCIIO_config(addr), val, 1); +} + +static void PPC_PCIIO_writew (void *opaque, target_phys_addr_t addr, uint32_t val) +{ + PREPPCIState *s = opaque; + val = bswap16(val); + pci_data_write(s->bus, PPC_PCIIO_config(addr), val, 2); +} + +static void PPC_PCIIO_writel (void *opaque, target_phys_addr_t addr, uint32_t val) +{ + PREPPCIState *s = opaque; + val = bswap32(val); + pci_data_write(s->bus, PPC_PCIIO_config(addr), val, 4); +} + +static uint32_t PPC_PCIIO_readb (void *opaque, target_phys_addr_t addr) +{ + PREPPCIState *s = opaque; + uint32_t val; + val = pci_data_read(s->bus, PPC_PCIIO_config(addr), 1); + return val; +} + +static uint32_t PPC_PCIIO_readw (void *opaque, target_phys_addr_t addr) +{ + PREPPCIState *s = opaque; + uint32_t val; + val = pci_data_read(s->bus, PPC_PCIIO_config(addr), 2); + val = bswap16(val); + return val; +} + +static uint32_t PPC_PCIIO_readl (void *opaque, target_phys_addr_t addr) +{ + PREPPCIState *s = opaque; + uint32_t val; + val = pci_data_read(s->bus, PPC_PCIIO_config(addr), 4); + val = bswap32(val); + return val; +} + +static CPUWriteMemoryFunc * const PPC_PCIIO_write[] = { + &PPC_PCIIO_writeb, + &PPC_PCIIO_writew, + &PPC_PCIIO_writel, +}; + +static CPUReadMemoryFunc * const PPC_PCIIO_read[] = { + &PPC_PCIIO_readb, + &PPC_PCIIO_readw, + &PPC_PCIIO_readl, +}; + +static int prep_map_irq(PCIDevice *pci_dev, int irq_num) +{ + return (irq_num + (pci_dev->devfn >> 3)) & 1; +} + +static void prep_set_irq(void *opaque, int irq_num, int level) +{ + qemu_irq *pic = opaque; + + qemu_set_irq(pic[(irq_num & 1) ? 11 : 9] , level); +} + +PCIBus *pci_prep_init(qemu_irq *pic) +{ + PREPPCIState *s; + PCIDevice *d; + int PPC_io_memory; + + s = qemu_mallocz(sizeof(PREPPCIState)); + s->bus = pci_register_bus(NULL, "pci", + prep_set_irq, prep_map_irq, pic, 0, 4); + + pci_host_conf_register_ioport(0xcf8, s); + + pci_host_data_register_ioport(0xcfc, s); + + PPC_io_memory = cpu_register_io_memory(PPC_PCIIO_read, + PPC_PCIIO_write, s, + DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(0x80800000, 0x00400000, PPC_io_memory); + + /* PCI host bridge */ + d = pci_register_device(s->bus, "PREP Host Bridge - Motorola Raven", + sizeof(PCIDevice), 0, NULL, NULL); + pci_config_set_vendor_id(d->config, PCI_VENDOR_ID_MOTOROLA); + pci_config_set_device_id(d->config, PCI_DEVICE_ID_MOTOROLA_RAVEN); + d->config[0x08] = 0x00; // revision + pci_config_set_class(d->config, PCI_CLASS_BRIDGE_HOST); + d->config[0x0C] = 0x08; // cache_line_size + d->config[0x0D] = 0x10; // latency_timer + d->config[0x34] = 0x00; // capabilities_pointer + + return s->bus; +} diff --git a/hw/prep_pci.h b/hw/prep_pci.h new file mode 100644 index 0000000..cd68512 --- /dev/null +++ b/hw/prep_pci.h @@ -0,0 +1,8 @@ +#ifndef QEMU_PREP_PCI_H +#define QEMU_PREP_PCI_H + +#include "qemu-common.h" + +PCIBus *pci_prep_init(qemu_irq *pic); + +#endif diff --git a/hw/primecell.h b/hw/primecell.h new file mode 100644 index 0000000..fb456ad --- /dev/null +++ b/hw/primecell.h @@ -0,0 +1,14 @@ +#ifndef PRIMECELL_H +#define PRIMECELL_H + +/* Declarations for ARM PrimeCell based periperals. */ +/* Also includes some devices that are currently only used by the + ARM boards. */ + +/* pl080.c */ +void *pl080_init(uint32_t base, qemu_irq irq, int nchannels); + +/* arm_sysctl.c */ +void arm_sysctl_init(uint32_t base, uint32_t sys_id, uint32_t proc_id); + +#endif diff --git a/hw/ps2.c b/hw/ps2.c new file mode 100644 index 0000000..762bb00 --- /dev/null +++ b/hw/ps2.c @@ -0,0 +1,614 @@ +/* + * 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.h" +#include "ps2.h" +#include "console.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 256 + +typedef struct { + uint8_t data[PS2_QUEUE_SIZE]; + 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 */ +} 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 +}; + +void ps2_queue(void *opaque, int b) +{ + PS2State *s = (PS2State *)opaque; + PS2Queue *q = &s->queue; + + if (q->count >= PS2_QUEUE_SIZE) + 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; + + /* XXX: add support for scancode sets 1 and 3 */ + if (!s->translate && keycode < 0xe0 && s->scancode_set == 2) + { + if (keycode & 0x80) + ps2_queue(&s->common, 0xf0); + keycode = ps2_raw_keycode[keycode & 0x7f]; + } + ps2_queue(&s->common, keycode); +} + +uint32_t ps2_read_data(void *opaque) +{ + PS2State *s = (PS2State *)opaque; + PS2Queue *q; + int val, index; + + 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_reset_keyboard(PS2KbdState *s) +{ + s->scan_enabled = 1; + s->scancode_set = 2; + kbd_put_ledstate(0); +} + +void ps2_write_keyboard(void *opaque, int val) +{ + PS2KbdState *s = (PS2KbdState *)opaque; + + 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: + kbd_put_ledstate(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; + 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; + } + + /* update deltas */ + s->mouse_dx -= dx1; + s->mouse_dy -= dy1; + s->mouse_dz -= dz1; +} + +static void ps2_mouse_event(void *opaque, + int dx, int dy, int dz, int buttons_state) +{ + PS2MouseState *s = opaque; + + /* check if deltas are recorded when disabled */ + if (!(s->mouse_status & MOUSE_STATUS_ENABLED)) + return; + + s->mouse_dx += dx; + s->mouse_dy -= dy; + s->mouse_dz += dz; + /* XXX: SDL sometimes generates nul events: we delete them */ + if (s->mouse_dx == 0 && s->mouse_dy == 0 && s->mouse_dz == 0 && + s->mouse_buttons == buttons_state) + return; + s->mouse_buttons = buttons_state; + + if (!(s->mouse_status & MOUSE_STATUS_REMOTE) && + (s->common.queue.count < (PS2_QUEUE_SIZE - 16))) { + for(;;) { + /* 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) +{ + ps2_mouse_event(opaque, 1, 0, 0, 0); +} + +void ps2_write_mouse(void *opaque, int val) +{ + PS2MouseState *s = (PS2MouseState *)opaque; +#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_kbd_reset(void *opaque) +{ + PS2KbdState *s = (PS2KbdState *) 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; + + 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, + .minimum_version_id_old = 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 int ps2_kbd_post_load(void* opaque, int version_id) +{ + PS2KbdState *s = (PS2KbdState*)opaque; + + if (version_id == 2) + s->scancode_set=2; + return 0; +} + +static const VMStateDescription vmstate_ps2_keyboard = { + .name = "ps2kbd", + .version_id = 3, + .minimum_version_id = 2, + .minimum_version_id_old = 2, + .post_load = ps2_kbd_post_load, + .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() + } +}; + +static const VMStateDescription vmstate_ps2_mouse = { + .name = "ps2mouse", + .version_id = 2, + .minimum_version_id = 2, + .minimum_version_id_old = 2, + .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() + } +}; + +void *ps2_kbd_init(void (*update_irq)(void *, int), void *update_arg) +{ + PS2KbdState *s = (PS2KbdState *)qemu_mallocz(sizeof(PS2KbdState)); + + 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_add_kbd_event_handler(ps2_put_keycode, s); + qemu_register_reset(ps2_kbd_reset, s); + return s; +} + +void *ps2_mouse_init(void (*update_irq)(void *, int), void *update_arg) +{ + PS2MouseState *s = (PS2MouseState *)qemu_mallocz(sizeof(PS2MouseState)); + + s->common.update_irq = update_irq; + s->common.update_arg = update_arg; + vmstate_register(NULL, 0, &vmstate_ps2_mouse, s); + qemu_add_mouse_event_handler(ps2_mouse_event, s, 0, "QEMU PS/2 Mouse"); + qemu_register_reset(ps2_mouse_reset, s); + return s; +} diff --git a/hw/ps2.h b/hw/ps2.h new file mode 100644 index 0000000..32a4231 --- /dev/null +++ b/hw/ps2.h @@ -0,0 +1,9 @@ +/* ps2.c */ +void *ps2_kbd_init(void (*update_irq)(void *, int), void *update_arg); +void *ps2_mouse_init(void (*update_irq)(void *, int), void *update_arg); +void ps2_write_mouse(void *, int val); +void ps2_write_keyboard(void *, int val); +uint32_t ps2_read_data(void *); +void ps2_queue(void *, int b); +void ps2_keyboard_set_translation(void *opaque, int mode); +void ps2_mouse_fake_event(void *opaque); diff --git a/hw/ptimer.c b/hw/ptimer.c new file mode 100644 index 0000000..4ddbc59 --- /dev/null +++ b/hw/ptimer.c @@ -0,0 +1,244 @@ +/* + * General purpose implementation of a simple periodic countdown timer. + * + * Copyright (c) 2007 CodeSourcery. + * + * This code is licenced under the GNU LGPL. + */ +#include "hw.h" +#include "qemu-timer.h" +#include "host-utils.h" + +struct ptimer_state +{ + int 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; + } + qemu_mod_timer(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_get_clock(vm_clock); + /* 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_get_clock(vm_clock); + 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_get_clock(vm_clock); + 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); + qemu_del_timer(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_get_clock(vm_clock); + 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_get_clock(vm_clock); + 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) +{ + s->limit = limit; + if (reload) + s->delta = limit; + if (s->enabled && reload) { + s->next_event = qemu_get_clock(vm_clock); + ptimer_reload(s); + } +} + +void qemu_put_ptimer(QEMUFile *f, ptimer_state *s) +{ + qemu_put_byte(f, s->enabled); + qemu_put_be64s(f, &s->limit); + qemu_put_be64s(f, &s->delta); + qemu_put_be32s(f, &s->period_frac); + qemu_put_sbe64s(f, &s->period); + qemu_put_sbe64s(f, &s->last_event); + qemu_put_sbe64s(f, &s->next_event); + qemu_put_timer(f, s->timer); +} + +void qemu_get_ptimer(QEMUFile *f, ptimer_state *s) +{ + s->enabled = qemu_get_byte(f); + qemu_get_be64s(f, &s->limit); + qemu_get_be64s(f, &s->delta); + qemu_get_be32s(f, &s->period_frac); + qemu_get_sbe64s(f, &s->period); + qemu_get_sbe64s(f, &s->last_event); + qemu_get_sbe64s(f, &s->next_event); + qemu_get_timer(f, s->timer); +} + +static int get_ptimer(QEMUFile *f, void *pv, size_t size) +{ + ptimer_state *v = pv; + + qemu_get_ptimer(f, v); + return 0; +} + +static void put_ptimer(QEMUFile *f, void *pv, size_t size) +{ + ptimer_state *v = pv; + + qemu_put_ptimer(f, v); +} + +const VMStateInfo vmstate_info_ptimer = { + .name = "ptimer", + .get = get_ptimer, + .put = put_ptimer, +}; + +ptimer_state *ptimer_init(QEMUBH *bh) +{ + ptimer_state *s; + + s = (ptimer_state *)qemu_mallocz(sizeof(ptimer_state)); + s->bh = bh; + s->timer = qemu_new_timer(vm_clock, ptimer_tick, s); + return s; +} diff --git a/hw/pxa.h b/hw/pxa.h new file mode 100644 index 0000000..f73d33b --- /dev/null +++ b/hw/pxa.h @@ -0,0 +1,212 @@ +/* + * Intel XScale PXA255/270 processor support. + * + * Copyright (c) 2006 Openedhand Ltd. + * Written by Andrzej Zaborowski + * + * This code is licenced under the GNU GPL v2. + */ +#ifndef PXA_H +# define PXA_H "pxa.h" + +/* Interrupt numbers */ +# define PXA2XX_PIC_SSP3 0 +# define PXA2XX_PIC_USBH2 2 +# define PXA2XX_PIC_USBH1 3 +# define PXA2XX_PIC_KEYPAD 4 +# define PXA2XX_PIC_PWRI2C 6 +# define PXA25X_PIC_HWUART 7 +# define PXA27X_PIC_OST_4_11 7 +# define PXA2XX_PIC_GPIO_0 8 +# define PXA2XX_PIC_GPIO_1 9 +# define PXA2XX_PIC_GPIO_X 10 +# define PXA2XX_PIC_I2S 13 +# define PXA26X_PIC_ASSP 15 +# define PXA25X_PIC_NSSP 16 +# define PXA27X_PIC_SSP2 16 +# define PXA2XX_PIC_LCD 17 +# define PXA2XX_PIC_I2C 18 +# define PXA2XX_PIC_ICP 19 +# define PXA2XX_PIC_STUART 20 +# define PXA2XX_PIC_BTUART 21 +# define PXA2XX_PIC_FFUART 22 +# define PXA2XX_PIC_MMC 23 +# define PXA2XX_PIC_SSP 24 +# define PXA2XX_PIC_DMA 25 +# define PXA2XX_PIC_OST_0 26 +# define PXA2XX_PIC_RTC1HZ 30 +# define PXA2XX_PIC_RTCALARM 31 + +/* DMA requests */ +# define PXA2XX_RX_RQ_I2S 2 +# define PXA2XX_TX_RQ_I2S 3 +# define PXA2XX_RX_RQ_BTUART 4 +# define PXA2XX_TX_RQ_BTUART 5 +# define PXA2XX_RX_RQ_FFUART 6 +# define PXA2XX_TX_RQ_FFUART 7 +# define PXA2XX_RX_RQ_SSP1 13 +# define PXA2XX_TX_RQ_SSP1 14 +# define PXA2XX_RX_RQ_SSP2 15 +# define PXA2XX_TX_RQ_SSP2 16 +# define PXA2XX_RX_RQ_ICP 17 +# define PXA2XX_TX_RQ_ICP 18 +# define PXA2XX_RX_RQ_STUART 19 +# define PXA2XX_TX_RQ_STUART 20 +# define PXA2XX_RX_RQ_MMCI 21 +# define PXA2XX_TX_RQ_MMCI 22 +# define PXA2XX_USB_RQ(x) ((x) + 24) +# define PXA2XX_RX_RQ_SSP3 66 +# define PXA2XX_TX_RQ_SSP3 67 + +# define PXA2XX_SDRAM_BASE 0xa0000000 +# define PXA2XX_INTERNAL_BASE 0x5c000000 +# define PXA2XX_INTERNAL_SIZE 0x40000 + +/* pxa2xx_pic.c */ +qemu_irq *pxa2xx_pic_init(target_phys_addr_t base, CPUState *env); + +/* pxa2xx_timer.c */ +void pxa25x_timer_init(target_phys_addr_t base, qemu_irq *irqs); +void pxa27x_timer_init(target_phys_addr_t base, qemu_irq *irqs, qemu_irq irq4); + +/* pxa2xx_gpio.c */ +DeviceState *pxa2xx_gpio_init(target_phys_addr_t base, + CPUState *env, qemu_irq *pic, int lines); +void pxa2xx_gpio_read_notifier(DeviceState *dev, qemu_irq handler); + +/* pxa2xx_dma.c */ +typedef struct PXA2xxDMAState PXA2xxDMAState; +PXA2xxDMAState *pxa255_dma_init(target_phys_addr_t base, + qemu_irq irq); +PXA2xxDMAState *pxa27x_dma_init(target_phys_addr_t base, + qemu_irq irq); +void pxa2xx_dma_request(PXA2xxDMAState *s, int req_num, int on); + +/* pxa2xx_lcd.c */ +typedef struct PXA2xxLCDState PXA2xxLCDState; +PXA2xxLCDState *pxa2xx_lcdc_init(target_phys_addr_t base, + qemu_irq irq); +void pxa2xx_lcd_vsync_notifier(PXA2xxLCDState *s, qemu_irq handler); +void pxa2xx_lcdc_oritentation(void *opaque, int angle); + +/* pxa2xx_mmci.c */ +typedef struct PXA2xxMMCIState PXA2xxMMCIState; +PXA2xxMMCIState *pxa2xx_mmci_init(target_phys_addr_t base, + BlockDriverState *bd, qemu_irq irq, void *dma); +void pxa2xx_mmci_handlers(PXA2xxMMCIState *s, qemu_irq readonly, + qemu_irq coverswitch); + +/* pxa2xx_pcmcia.c */ +typedef struct PXA2xxPCMCIAState PXA2xxPCMCIAState; +PXA2xxPCMCIAState *pxa2xx_pcmcia_init(target_phys_addr_t base); +int pxa2xx_pcmcia_attach(void *opaque, PCMCIACardState *card); +int pxa2xx_pcmcia_dettach(void *opaque); +void pxa2xx_pcmcia_set_irq_cb(void *opaque, qemu_irq irq, qemu_irq cd_irq); + +/* pxa2xx_keypad.c */ +struct keymap { + int column; + int row; +}; +typedef struct PXA2xxKeyPadState PXA2xxKeyPadState; +PXA2xxKeyPadState *pxa27x_keypad_init(target_phys_addr_t base, + qemu_irq irq); +void pxa27x_register_keypad(PXA2xxKeyPadState *kp, struct keymap *map, + int size); + +/* pxa2xx.c */ +typedef struct PXA2xxI2CState PXA2xxI2CState; +PXA2xxI2CState *pxa2xx_i2c_init(target_phys_addr_t base, + qemu_irq irq, uint32_t page_size); +i2c_bus *pxa2xx_i2c_bus(PXA2xxI2CState *s); + +typedef struct PXA2xxI2SState PXA2xxI2SState; +typedef struct PXA2xxFIrState PXA2xxFIrState; + +typedef struct { + CPUState *env; + qemu_irq *pic; + qemu_irq reset; + PXA2xxDMAState *dma; + DeviceState *gpio; + PXA2xxLCDState *lcd; + SSIBus **ssp; + PXA2xxI2CState *i2c[2]; + PXA2xxMMCIState *mmc; + PXA2xxPCMCIAState *pcmcia[2]; + PXA2xxI2SState *i2s; + PXA2xxFIrState *fir; + PXA2xxKeyPadState *kp; + + /* Power management */ + target_phys_addr_t pm_base; + uint32_t pm_regs[0x40]; + + /* Clock management */ + target_phys_addr_t cm_base; + uint32_t cm_regs[4]; + uint32_t clkcfg; + + /* Memory management */ + target_phys_addr_t mm_base; + uint32_t mm_regs[0x1a]; + + /* Performance monitoring */ + uint32_t pmnc; + + /* Real-Time clock */ + target_phys_addr_t rtc_base; + 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; +} PXA2xxState; + +struct PXA2xxI2SState { + qemu_irq irq; + PXA2xxDMAState *dma; + void (*data_req)(void *, int, int); + + uint32_t control[2]; + uint32_t status; + uint32_t mask; + uint32_t clk; + + int enable; + int rx_len; + int tx_len; + void (*codec_out)(void *, uint32_t); + uint32_t (*codec_in)(void *); + void *opaque; + + int fifo_len; + uint32_t fifo[16]; +}; + +# define PA_FMT "0x%08lx" +# define REG_FMT "0x" TARGET_FMT_plx + +PXA2xxState *pxa270_init(unsigned int sdram_size, const char *revision); +PXA2xxState *pxa255_init(unsigned int sdram_size); + +#endif /* PXA_H */ diff --git a/hw/pxa2xx.c b/hw/pxa2xx.c new file mode 100644 index 0000000..d966846 --- /dev/null +++ b/hw/pxa2xx.c @@ -0,0 +1,2292 @@ +/* + * Intel XScale PXA255/270 processor support. + * + * Copyright (c) 2006 Openedhand Ltd. + * Written by Andrzej Zaborowski + * + * This code is licenced under the GPL. + */ + +#include "sysbus.h" +#include "pxa.h" +#include "sysemu.h" +#include "pc.h" +#include "i2c.h" +#include "ssi.h" +#include "qemu-timer.h" +#include "qemu-char.h" +#include "blockdev.h" + +static struct { + target_phys_addr_t 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 { + target_phys_addr_t 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 uint32_t pxa2xx_pm_read(void *opaque, target_phys_addr_t addr) +{ + 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, target_phys_addr_t addr, + uint32_t value) +{ + PXA2xxState *s = (PXA2xxState *) opaque; + + switch (addr) { + case PMCR: + s->pm_regs[addr >> 2] &= 0x15 & ~(value & 0x2a); + 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 CPUReadMemoryFunc * const pxa2xx_pm_readfn[] = { + pxa2xx_pm_read, + pxa2xx_pm_read, + pxa2xx_pm_read, +}; + +static CPUWriteMemoryFunc * const pxa2xx_pm_writefn[] = { + pxa2xx_pm_write, + pxa2xx_pm_write, + pxa2xx_pm_write, +}; + +static void pxa2xx_pm_save(QEMUFile *f, void *opaque) +{ + PXA2xxState *s = (PXA2xxState *) opaque; + int i; + + for (i = 0; i < 0x40; i ++) + qemu_put_be32s(f, &s->pm_regs[i]); +} + +static int pxa2xx_pm_load(QEMUFile *f, void *opaque, int version_id) +{ + PXA2xxState *s = (PXA2xxState *) opaque; + int i; + + for (i = 0; i < 0x40; i ++) + qemu_get_be32s(f, &s->pm_regs[i]); + + return 0; +} + +#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 uint32_t pxa2xx_cm_read(void *opaque, target_phys_addr_t addr) +{ + 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, target_phys_addr_t addr, + uint32_t value) +{ + 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 CPUReadMemoryFunc * const pxa2xx_cm_readfn[] = { + pxa2xx_cm_read, + pxa2xx_cm_read, + pxa2xx_cm_read, +}; + +static CPUWriteMemoryFunc * const pxa2xx_cm_writefn[] = { + pxa2xx_cm_write, + pxa2xx_cm_write, + pxa2xx_cm_write, +}; + +static void pxa2xx_cm_save(QEMUFile *f, void *opaque) +{ + PXA2xxState *s = (PXA2xxState *) opaque; + int i; + + for (i = 0; i < 4; i ++) + qemu_put_be32s(f, &s->cm_regs[i]); + qemu_put_be32s(f, &s->clkcfg); + qemu_put_be32s(f, &s->pmnc); +} + +static int pxa2xx_cm_load(QEMUFile *f, void *opaque, int version_id) +{ + PXA2xxState *s = (PXA2xxState *) opaque; + int i; + + for (i = 0; i < 4; i ++) + qemu_get_be32s(f, &s->cm_regs[i]); + qemu_get_be32s(f, &s->clkcfg); + qemu_get_be32s(f, &s->pmnc); + + return 0; +} + +static uint32_t pxa2xx_clkpwr_read(void *opaque, int op2, int reg, int crm) +{ + PXA2xxState *s = (PXA2xxState *) opaque; + + switch (reg) { + case 6: /* Clock Configuration register */ + return s->clkcfg; + + case 7: /* Power Mode register */ + return 0; + + default: + printf("%s: Bad register 0x%x\n", __FUNCTION__, reg); + break; + } + return 0; +} + +static void pxa2xx_clkpwr_write(void *opaque, int op2, int reg, int crm, + uint32_t value) +{ + PXA2xxState *s = (PXA2xxState *) opaque; + static const char *pwrmode[8] = { + "Normal", "Idle", "Deep-idle", "Standby", + "Sleep", "reserved (!)", "reserved (!)", "Deep-sleep", + }; + + switch (reg) { + case 6: /* Clock Configuration register */ + s->clkcfg = value & 0xf; + if (value & 2) + printf("%s: CPU frequency change attempt\n", __FUNCTION__); + break; + + case 7: /* Power Mode register */ + if (value & 8) + printf("%s: CPU voltage change attempt\n", __FUNCTION__); + switch (value & 7) { + case 0: + /* Do nothing */ + break; + + case 1: + /* Idle */ + if (!(s->cm_regs[CCCR >> 2] & (1 << 31))) { /* CPDIS */ + cpu_interrupt(s->env, CPU_INTERRUPT_HALT); + break; + } + /* Fall through. */ + + case 2: + /* Deep-Idle */ + cpu_interrupt(s->env, CPU_INTERRUPT_HALT); + s->pm_regs[RCSR >> 2] |= 0x8; /* Set GPR */ + goto message; + + case 3: + s->env->uncached_cpsr = + ARM_CPU_MODE_SVC | CPSR_A | CPSR_F | CPSR_I; + s->env->cp15.c1_sys = 0; + s->env->cp15.c1_coproc = 0; + s->env->cp15.c2_base0 = 0; + s->env->cp15.c3 = 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->env->regs, 0, 4 * 15); + s->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(cpu_single_env, CPU_INTERRUPT_HALT); + + goto message; + + default: + message: + printf("%s: machine entered %s mode\n", __FUNCTION__, + pwrmode[value & 7]); + } + break; + + default: + printf("%s: Bad register 0x%x\n", __FUNCTION__, reg); + break; + } +} + +/* Performace Monitoring Registers */ +#define CPPMNC 0 /* Performance Monitor Control register */ +#define CPCCNT 1 /* Clock Counter register */ +#define CPINTEN 4 /* Interrupt Enable register */ +#define CPFLAG 5 /* Overflow Flag register */ +#define CPEVTSEL 8 /* Event Selection register */ + +#define CPPMN0 0 /* Performance Count register 0 */ +#define CPPMN1 1 /* Performance Count register 1 */ +#define CPPMN2 2 /* Performance Count register 2 */ +#define CPPMN3 3 /* Performance Count register 3 */ + +static uint32_t pxa2xx_perf_read(void *opaque, int op2, int reg, int crm) +{ + PXA2xxState *s = (PXA2xxState *) opaque; + + switch (reg) { + case CPPMNC: + return s->pmnc; + case CPCCNT: + if (s->pmnc & 1) + return qemu_get_clock(vm_clock); + else + return 0; + case CPINTEN: + case CPFLAG: + case CPEVTSEL: + return 0; + + default: + printf("%s: Bad register 0x%x\n", __FUNCTION__, reg); + break; + } + return 0; +} + +static void pxa2xx_perf_write(void *opaque, int op2, int reg, int crm, + uint32_t value) +{ + PXA2xxState *s = (PXA2xxState *) opaque; + + switch (reg) { + case CPPMNC: + s->pmnc = value; + break; + + case CPCCNT: + case CPINTEN: + case CPFLAG: + case CPEVTSEL: + break; + + default: + printf("%s: Bad register 0x%x\n", __FUNCTION__, reg); + break; + } +} + +static uint32_t pxa2xx_cp14_read(void *opaque, int op2, int reg, int crm) +{ + switch (crm) { + case 0: + return pxa2xx_clkpwr_read(opaque, op2, reg, crm); + case 1: + return pxa2xx_perf_read(opaque, op2, reg, crm); + case 2: + switch (reg) { + case CPPMN0: + case CPPMN1: + case CPPMN2: + case CPPMN3: + return 0; + } + /* Fall through */ + default: + printf("%s: Bad register 0x%x\n", __FUNCTION__, reg); + break; + } + return 0; +} + +static void pxa2xx_cp14_write(void *opaque, int op2, int reg, int crm, + uint32_t value) +{ + switch (crm) { + case 0: + pxa2xx_clkpwr_write(opaque, op2, reg, crm, value); + break; + case 1: + pxa2xx_perf_write(opaque, op2, reg, crm, value); + break; + case 2: + switch (reg) { + case CPPMN0: + case CPPMN1: + case CPPMN2: + case CPPMN3: + return; + } + /* Fall through */ + default: + printf("%s: Bad register 0x%x\n", __FUNCTION__, reg); + break; + } +} + +#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 uint32_t pxa2xx_mm_read(void *opaque, target_phys_addr_t addr) +{ + 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, target_phys_addr_t addr, + uint32_t value) +{ + 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 CPUReadMemoryFunc * const pxa2xx_mm_readfn[] = { + pxa2xx_mm_read, + pxa2xx_mm_read, + pxa2xx_mm_read, +}; + +static CPUWriteMemoryFunc * const pxa2xx_mm_writefn[] = { + pxa2xx_mm_write, + pxa2xx_mm_write, + pxa2xx_mm_write, +}; + +static void pxa2xx_mm_save(QEMUFile *f, void *opaque) +{ + PXA2xxState *s = (PXA2xxState *) opaque; + int i; + + for (i = 0; i < 0x1a; i ++) + qemu_put_be32s(f, &s->mm_regs[i]); +} + +static int pxa2xx_mm_load(QEMUFile *f, void *opaque, int version_id) +{ + PXA2xxState *s = (PXA2xxState *) opaque; + int i; + + for (i = 0; i < 0x1a; i ++) + qemu_get_be32s(f, &s->mm_regs[i]); + + return 0; +} + +/* Synchronous Serial Ports */ +typedef struct { + SysBusDevice busdev; + qemu_irq irq; + int 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]; + int rx_level; + int rx_start; +} PXA2xxSSPState; + +#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 (1 << 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 uint32_t pxa2xx_ssp_read(void *opaque, target_phys_addr_t addr) +{ + 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, target_phys_addr_t addr, + uint32_t value) +{ + PXA2xxSSPState *s = (PXA2xxSSPState *) opaque; + + 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 CPUReadMemoryFunc * const pxa2xx_ssp_readfn[] = { + pxa2xx_ssp_read, + pxa2xx_ssp_read, + pxa2xx_ssp_read, +}; + +static CPUWriteMemoryFunc * const pxa2xx_ssp_writefn[] = { + pxa2xx_ssp_write, + pxa2xx_ssp_write, + pxa2xx_ssp_write, +}; + +static void pxa2xx_ssp_save(QEMUFile *f, void *opaque) +{ + PXA2xxSSPState *s = (PXA2xxSSPState *) opaque; + int i; + + qemu_put_be32(f, s->enable); + + qemu_put_be32s(f, &s->sscr[0]); + qemu_put_be32s(f, &s->sscr[1]); + qemu_put_be32s(f, &s->sspsp); + qemu_put_be32s(f, &s->ssto); + qemu_put_be32s(f, &s->ssitr); + qemu_put_be32s(f, &s->sssr); + qemu_put_8s(f, &s->sstsa); + qemu_put_8s(f, &s->ssrsa); + qemu_put_8s(f, &s->ssacd); + + qemu_put_byte(f, s->rx_level); + for (i = 0; i < s->rx_level; i ++) + qemu_put_byte(f, s->rx_fifo[(s->rx_start + i) & 0xf]); +} + +static int pxa2xx_ssp_load(QEMUFile *f, void *opaque, int version_id) +{ + PXA2xxSSPState *s = (PXA2xxSSPState *) opaque; + int i; + + s->enable = qemu_get_be32(f); + + qemu_get_be32s(f, &s->sscr[0]); + qemu_get_be32s(f, &s->sscr[1]); + qemu_get_be32s(f, &s->sspsp); + qemu_get_be32s(f, &s->ssto); + qemu_get_be32s(f, &s->ssitr); + qemu_get_be32s(f, &s->sssr); + qemu_get_8s(f, &s->sstsa); + qemu_get_8s(f, &s->ssrsa); + qemu_get_8s(f, &s->ssacd); + + s->rx_level = qemu_get_byte(f); + s->rx_start = 0; + for (i = 0; i < s->rx_level; i ++) + s->rx_fifo[i] = qemu_get_byte(f); + + return 0; +} + +static int pxa2xx_ssp_init(SysBusDevice *dev) +{ + int iomemtype; + PXA2xxSSPState *s = FROM_SYSBUS(PXA2xxSSPState, dev); + + sysbus_init_irq(dev, &s->irq); + + iomemtype = cpu_register_io_memory(pxa2xx_ssp_readfn, + pxa2xx_ssp_writefn, s, + DEVICE_NATIVE_ENDIAN); + sysbus_init_mmio(dev, 0x1000, iomemtype); + register_savevm(&dev->qdev, "pxa2xx_ssp", -1, 0, + pxa2xx_ssp_save, pxa2xx_ssp_load, s); + + s->bus = ssi_create_bus(&dev->qdev, "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 */ + +static inline void pxa2xx_rtc_int_update(PXA2xxState *s) +{ + qemu_set_irq(s->pic[PXA2XX_PIC_RTCALARM], !!(s->rtsr & 0x2553)); +} + +static void pxa2xx_rtc_hzupdate(PXA2xxState *s) +{ + int64_t rt = qemu_get_clock(rt_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(PXA2xxState *s) +{ + int64_t rt = qemu_get_clock(rt_clock); + if (s->rtsr & (1 << 12)) + s->last_swcr += (rt - s->last_sw) / 10; + s->last_sw = rt; +} + +static void pxa2xx_rtc_piupdate(PXA2xxState *s) +{ + int64_t rt = qemu_get_clock(rt_clock); + if (s->rtsr & (1 << 15)) + s->last_swcr += rt - s->last_pi; + s->last_pi = rt; +} + +static inline void pxa2xx_rtc_alarm_update(PXA2xxState *s, + uint32_t rtsr) +{ + if ((rtsr & (1 << 2)) && !(rtsr & (1 << 0))) + qemu_mod_timer(s->rtc_hz, s->last_hz + + (((s->rtar - s->last_rcnr) * 1000 * + ((s->rttr & 0xffff) + 1)) >> 15)); + else + qemu_del_timer(s->rtc_hz); + + if ((rtsr & (1 << 5)) && !(rtsr & (1 << 4))) + qemu_mod_timer(s->rtc_rdal1, s->last_hz + + (((s->rdar1 - s->last_rdcr) * 1000 * + ((s->rttr & 0xffff) + 1)) >> 15)); /* TODO: fixup */ + else + qemu_del_timer(s->rtc_rdal1); + + if ((rtsr & (1 << 7)) && !(rtsr & (1 << 6))) + qemu_mod_timer(s->rtc_rdal2, s->last_hz + + (((s->rdar2 - s->last_rdcr) * 1000 * + ((s->rttr & 0xffff) + 1)) >> 15)); /* TODO: fixup */ + else + qemu_del_timer(s->rtc_rdal2); + + if ((rtsr & 0x1200) == 0x1200 && !(rtsr & (1 << 8))) + qemu_mod_timer(s->rtc_swal1, s->last_sw + + (s->swar1 - s->last_swcr) * 10); /* TODO: fixup */ + else + qemu_del_timer(s->rtc_swal1); + + if ((rtsr & 0x1800) == 0x1800 && !(rtsr & (1 << 10))) + qemu_mod_timer(s->rtc_swal2, s->last_sw + + (s->swar2 - s->last_swcr) * 10); /* TODO: fixup */ + else + qemu_del_timer(s->rtc_swal2); + + if ((rtsr & 0xc000) == 0xc000 && !(rtsr & (1 << 13))) + qemu_mod_timer(s->rtc_pi, s->last_pi + + (s->piar & 0xffff) - s->last_rtcpicr); + else + qemu_del_timer(s->rtc_pi); +} + +static inline void pxa2xx_rtc_hz_tick(void *opaque) +{ + PXA2xxState *s = (PXA2xxState *) 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) +{ + PXA2xxState *s = (PXA2xxState *) 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) +{ + PXA2xxState *s = (PXA2xxState *) 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) +{ + PXA2xxState *s = (PXA2xxState *) 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) +{ + PXA2xxState *s = (PXA2xxState *) 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) +{ + PXA2xxState *s = (PXA2xxState *) 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 uint32_t pxa2xx_rtc_read(void *opaque, target_phys_addr_t addr) +{ + PXA2xxState *s = (PXA2xxState *) 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_get_clock(rt_clock) - s->last_hz) << 15) / + (1000 * ((s->rttr & 0xffff) + 1)); + case RDCR: + return s->last_rdcr + ((qemu_get_clock(rt_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_get_clock(rt_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, target_phys_addr_t addr, + uint32_t value) +{ + PXA2xxState *s = (PXA2xxState *) opaque; + + switch (addr) { + case RTTR: + if (!(s->rttr & (1 << 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 CPUReadMemoryFunc * const pxa2xx_rtc_readfn[] = { + pxa2xx_rtc_read, + pxa2xx_rtc_read, + pxa2xx_rtc_read, +}; + +static CPUWriteMemoryFunc * const pxa2xx_rtc_writefn[] = { + pxa2xx_rtc_write, + pxa2xx_rtc_write, + pxa2xx_rtc_write, +}; + +static void pxa2xx_rtc_init(PXA2xxState *s) +{ + 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_get_clock(rt_clock); + + s->rtc_hz = qemu_new_timer(rt_clock, pxa2xx_rtc_hz_tick, s); + s->rtc_rdal1 = qemu_new_timer(rt_clock, pxa2xx_rtc_rdal1_tick, s); + s->rtc_rdal2 = qemu_new_timer(rt_clock, pxa2xx_rtc_rdal2_tick, s); + s->rtc_swal1 = qemu_new_timer(rt_clock, pxa2xx_rtc_swal1_tick, s); + s->rtc_swal2 = qemu_new_timer(rt_clock, pxa2xx_rtc_swal2_tick, s); + s->rtc_pi = qemu_new_timer(rt_clock, pxa2xx_rtc_pi_tick, s); +} + +static void pxa2xx_rtc_save(QEMUFile *f, void *opaque) +{ + PXA2xxState *s = (PXA2xxState *) opaque; + + pxa2xx_rtc_hzupdate(s); + pxa2xx_rtc_piupdate(s); + pxa2xx_rtc_swupdate(s); + + qemu_put_be32s(f, &s->rttr); + qemu_put_be32s(f, &s->rtsr); + qemu_put_be32s(f, &s->rtar); + qemu_put_be32s(f, &s->rdar1); + qemu_put_be32s(f, &s->rdar2); + qemu_put_be32s(f, &s->ryar1); + qemu_put_be32s(f, &s->ryar2); + qemu_put_be32s(f, &s->swar1); + qemu_put_be32s(f, &s->swar2); + qemu_put_be32s(f, &s->piar); + qemu_put_be32s(f, &s->last_rcnr); + qemu_put_be32s(f, &s->last_rdcr); + qemu_put_be32s(f, &s->last_rycr); + qemu_put_be32s(f, &s->last_swcr); + qemu_put_be32s(f, &s->last_rtcpicr); + qemu_put_sbe64s(f, &s->last_hz); + qemu_put_sbe64s(f, &s->last_sw); + qemu_put_sbe64s(f, &s->last_pi); +} + +static int pxa2xx_rtc_load(QEMUFile *f, void *opaque, int version_id) +{ + PXA2xxState *s = (PXA2xxState *) opaque; + + qemu_get_be32s(f, &s->rttr); + qemu_get_be32s(f, &s->rtsr); + qemu_get_be32s(f, &s->rtar); + qemu_get_be32s(f, &s->rdar1); + qemu_get_be32s(f, &s->rdar2); + qemu_get_be32s(f, &s->ryar1); + qemu_get_be32s(f, &s->ryar2); + qemu_get_be32s(f, &s->swar1); + qemu_get_be32s(f, &s->swar2); + qemu_get_be32s(f, &s->piar); + qemu_get_be32s(f, &s->last_rcnr); + qemu_get_be32s(f, &s->last_rdcr); + qemu_get_be32s(f, &s->last_rycr); + qemu_get_be32s(f, &s->last_swcr); + qemu_get_be32s(f, &s->last_rtcpicr); + qemu_get_sbe64s(f, &s->last_hz); + qemu_get_sbe64s(f, &s->last_sw); + qemu_get_sbe64s(f, &s->last_pi); + + pxa2xx_rtc_alarm_update(s, s->rtsr); + + return 0; +} + +/* I2C Interface */ +typedef struct { + i2c_slave i2c; + PXA2xxI2CState *host; +} PXA2xxI2CSlaveState; + +struct PXA2xxI2CState { + PXA2xxI2CSlaveState *slave; + i2c_bus *bus; + qemu_irq irq; + target_phys_addr_t offset; + + 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(i2c_slave *i2c, enum i2c_event event) +{ + PXA2xxI2CSlaveState *slave = FROM_I2C_SLAVE(PXA2xxI2CSlaveState, 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(i2c_slave *i2c) +{ + PXA2xxI2CSlaveState *slave = FROM_I2C_SLAVE(PXA2xxI2CSlaveState, 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(i2c_slave *i2c, uint8_t data) +{ + PXA2xxI2CSlaveState *slave = FROM_I2C_SLAVE(PXA2xxI2CSlaveState, 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 uint32_t pxa2xx_i2c_read(void *opaque, target_phys_addr_t addr) +{ + PXA2xxI2CState *s = (PXA2xxI2CState *) opaque; + + addr -= s->offset; + switch (addr) { + case ICR: + return s->control; + case ISR: + return s->status | (i2c_bus_busy(s->bus) << 2); + case ISAR: + return s->slave->i2c.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, target_phys_addr_t addr, + uint32_t value) +{ + PXA2xxI2CState *s = (PXA2xxI2CState *) opaque; + 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(&s->slave->i2c, value & 0x7f); + break; + + case IDBR: + s->data = value & 0xff; + break; + + default: + printf("%s: Bad register " REG_FMT "\n", __FUNCTION__, addr); + } +} + +static CPUReadMemoryFunc * const pxa2xx_i2c_readfn[] = { + pxa2xx_i2c_read, + pxa2xx_i2c_read, + pxa2xx_i2c_read, +}; + +static CPUWriteMemoryFunc * const pxa2xx_i2c_writefn[] = { + pxa2xx_i2c_write, + pxa2xx_i2c_write, + pxa2xx_i2c_write, +}; + +static const VMStateDescription vmstate_pxa2xx_i2c_slave = { + .name = "pxa2xx_i2c_slave", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField []) { + VMSTATE_I2C_SLAVE(i2c, PXA2xxI2CSlaveState), + VMSTATE_END_OF_LIST() + } +}; + +static const VMStateDescription vmstate_pxa2xx_i2c = { + .name = "pxa2xx_i2c", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 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(i2c_slave *i2c) +{ + /* Nothing to do. */ + return 0; +} + +static I2CSlaveInfo pxa2xx_i2c_slave_info = { + .qdev.name = "pxa2xx-i2c-slave", + .qdev.size = sizeof(PXA2xxI2CSlaveState), + .init = pxa2xx_i2c_slave_init, + .event = pxa2xx_i2c_event, + .recv = pxa2xx_i2c_rx, + .send = pxa2xx_i2c_tx +}; + +PXA2xxI2CState *pxa2xx_i2c_init(target_phys_addr_t base, + qemu_irq irq, uint32_t region_size) +{ + int iomemtype; + DeviceState *dev; + PXA2xxI2CState *s = qemu_mallocz(sizeof(PXA2xxI2CState)); + + /* FIXME: Should the slave device really be on a separate bus? */ + dev = i2c_create_slave(i2c_init_bus(NULL, "dummy"), "pxa2xx-i2c-slave", 0); + s->slave = FROM_I2C_SLAVE(PXA2xxI2CSlaveState, I2C_SLAVE_FROM_QDEV(dev)); + s->slave->host = s; + + s->irq = irq; + s->bus = i2c_init_bus(NULL, "i2c"); + s->offset = base - (base & (~region_size) & TARGET_PAGE_MASK); + + iomemtype = cpu_register_io_memory(pxa2xx_i2c_readfn, + pxa2xx_i2c_writefn, s, DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(base & ~region_size, + region_size + 1, iomemtype); + + vmstate_register(NULL, base, &vmstate_pxa2xx_i2c, s); + + return s; +} + +i2c_bus *pxa2xx_i2c_bus(PXA2xxI2CState *s) +{ + return s->bus; +} + +/* 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]); + + pxa2xx_dma_request(i2s->dma, PXA2XX_RX_RQ_I2S, rfs); + pxa2xx_dma_request(i2s->dma, PXA2XX_TX_RQ_I2S, 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 uint32_t pxa2xx_i2s_read(void *opaque, target_phys_addr_t addr) +{ + 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, target_phys_addr_t addr, + uint32_t value) +{ + 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 ^ 4) & 5) == 5; /* 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 CPUReadMemoryFunc * const pxa2xx_i2s_readfn[] = { + pxa2xx_i2s_read, + pxa2xx_i2s_read, + pxa2xx_i2s_read, +}; + +static CPUWriteMemoryFunc * const pxa2xx_i2s_writefn[] = { + pxa2xx_i2s_write, + pxa2xx_i2s_write, + pxa2xx_i2s_write, +}; + +static void pxa2xx_i2s_save(QEMUFile *f, void *opaque) +{ + PXA2xxI2SState *s = (PXA2xxI2SState *) opaque; + + qemu_put_be32s(f, &s->control[0]); + qemu_put_be32s(f, &s->control[1]); + qemu_put_be32s(f, &s->status); + qemu_put_be32s(f, &s->mask); + qemu_put_be32s(f, &s->clk); + + qemu_put_be32(f, s->enable); + qemu_put_be32(f, s->rx_len); + qemu_put_be32(f, s->tx_len); + qemu_put_be32(f, s->fifo_len); +} + +static int pxa2xx_i2s_load(QEMUFile *f, void *opaque, int version_id) +{ + PXA2xxI2SState *s = (PXA2xxI2SState *) opaque; + + qemu_get_be32s(f, &s->control[0]); + qemu_get_be32s(f, &s->control[1]); + qemu_get_be32s(f, &s->status); + qemu_get_be32s(f, &s->mask); + qemu_get_be32s(f, &s->clk); + + s->enable = qemu_get_be32(f); + s->rx_len = qemu_get_be32(f); + s->tx_len = qemu_get_be32(f); + s->fifo_len = qemu_get_be32(f); + + return 0; +} + +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(target_phys_addr_t base, + qemu_irq irq, PXA2xxDMAState *dma) +{ + int iomemtype; + PXA2xxI2SState *s = (PXA2xxI2SState *) + qemu_mallocz(sizeof(PXA2xxI2SState)); + + s->irq = irq; + s->dma = dma; + s->data_req = pxa2xx_i2s_data_req; + + pxa2xx_i2s_reset(s); + + iomemtype = cpu_register_io_memory(pxa2xx_i2s_readfn, + pxa2xx_i2s_writefn, s, DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(base, 0x100000, iomemtype); + + register_savevm(NULL, "pxa2xx_i2s", base, 0, + pxa2xx_i2s_save, pxa2xx_i2s_load, s); + + return s; +} + +/* PXA Fast Infra-red Communications Port */ +struct PXA2xxFIrState { + qemu_irq irq; + PXA2xxDMAState *dma; + int enable; + CharDriverState *chr; + + uint8_t control[3]; + uint8_t status[2]; + + int rx_len; + int rx_start; + uint8_t rx_fifo[64]; +}; + +static void pxa2xx_fir_reset(PXA2xxFIrState *s) +{ + 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 */ + + pxa2xx_dma_request(s->dma, PXA2XX_RX_RQ_ICP, (s->status[0] >> 4) & 1); + pxa2xx_dma_request(s->dma, PXA2XX_TX_RQ_ICP, (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 uint32_t pxa2xx_fir_read(void *opaque, target_phys_addr_t addr) +{ + 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, target_phys_addr_t addr, + uint32_t value) +{ + PXA2xxFIrState *s = (PXA2xxFIrState *) opaque; + 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_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 CPUReadMemoryFunc * const pxa2xx_fir_readfn[] = { + pxa2xx_fir_read, + pxa2xx_fir_read, + pxa2xx_fir_read, +}; + +static CPUWriteMemoryFunc * const pxa2xx_fir_writefn[] = { + pxa2xx_fir_write, + pxa2xx_fir_write, + pxa2xx_fir_write, +}; + +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_save(QEMUFile *f, void *opaque) +{ + PXA2xxFIrState *s = (PXA2xxFIrState *) opaque; + int i; + + qemu_put_be32(f, s->enable); + + qemu_put_8s(f, &s->control[0]); + qemu_put_8s(f, &s->control[1]); + qemu_put_8s(f, &s->control[2]); + qemu_put_8s(f, &s->status[0]); + qemu_put_8s(f, &s->status[1]); + + 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) & 63]); +} + +static int pxa2xx_fir_load(QEMUFile *f, void *opaque, int version_id) +{ + PXA2xxFIrState *s = (PXA2xxFIrState *) opaque; + int i; + + s->enable = qemu_get_be32(f); + + qemu_get_8s(f, &s->control[0]); + qemu_get_8s(f, &s->control[1]); + qemu_get_8s(f, &s->control[2]); + qemu_get_8s(f, &s->status[0]); + qemu_get_8s(f, &s->status[1]); + + s->rx_len = qemu_get_byte(f); + s->rx_start = 0; + for (i = 0; i < s->rx_len; i ++) + s->rx_fifo[i] = qemu_get_byte(f); + + return 0; +} + +static PXA2xxFIrState *pxa2xx_fir_init(target_phys_addr_t base, + qemu_irq irq, PXA2xxDMAState *dma, + CharDriverState *chr) +{ + int iomemtype; + PXA2xxFIrState *s = (PXA2xxFIrState *) + qemu_mallocz(sizeof(PXA2xxFIrState)); + + s->irq = irq; + s->dma = dma; + s->chr = chr; + + pxa2xx_fir_reset(s); + + iomemtype = cpu_register_io_memory(pxa2xx_fir_readfn, + pxa2xx_fir_writefn, s, DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(base, 0x1000, iomemtype); + + if (chr) + qemu_chr_add_handlers(chr, pxa2xx_fir_is_empty, + pxa2xx_fir_rx, pxa2xx_fir_event, s); + + register_savevm(NULL, "pxa2xx_fir", 0, 0, pxa2xx_fir_save, + pxa2xx_fir_load, s); + + return s; +} + +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(s->env); + /* TODO: reset peripherals */ + } +} + +/* Initialise a PXA270 integrated chip (ARM based core). */ +PXA2xxState *pxa270_init(unsigned int sdram_size, const char *revision) +{ + PXA2xxState *s; + int iomemtype, i; + DriveInfo *dinfo; + s = (PXA2xxState *) qemu_mallocz(sizeof(PXA2xxState)); + + if (revision && strncmp(revision, "pxa27", 5)) { + fprintf(stderr, "Machine requires a PXA27x processor.\n"); + exit(1); + } + if (!revision) + revision = "pxa270"; + + s->env = cpu_init(revision); + if (!s->env) { + fprintf(stderr, "Unable to find CPU definition\n"); + exit(1); + } + s->reset = qemu_allocate_irqs(pxa2xx_reset, s, 1)[0]; + + /* SDRAM & Internal Memory Storage */ + cpu_register_physical_memory(PXA2XX_SDRAM_BASE, + sdram_size, qemu_ram_alloc(NULL, "pxa270.sdram", + sdram_size) | IO_MEM_RAM); + cpu_register_physical_memory(PXA2XX_INTERNAL_BASE, + 0x40000, qemu_ram_alloc(NULL, "pxa270.internal", + 0x40000) | IO_MEM_RAM); + + s->pic = pxa2xx_pic_init(0x40d00000, s->env); + + s->dma = pxa27x_dma_init(0x40000000, s->pic[PXA2XX_PIC_DMA]); + + pxa27x_timer_init(0x40a00000, &s->pic[PXA2XX_PIC_OST_0], + s->pic[PXA27X_PIC_OST_4_11]); + + s->gpio = pxa2xx_gpio_init(0x40e00000, s->env, 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(0x41100000, dinfo->bdrv, + s->pic[PXA2XX_PIC_MMC], s->dma); + + for (i = 0; pxa270_serial[i].io_base; i ++) + if (serial_hds[i]) +#ifdef TARGET_WORDS_BIGENDIAN + serial_mm_init(pxa270_serial[i].io_base, 2, + s->pic[pxa270_serial[i].irqn], 14857000/16, + serial_hds[i], 1, 1); +#else + serial_mm_init(pxa270_serial[i].io_base, 2, + s->pic[pxa270_serial[i].irqn], 14857000/16, + serial_hds[i], 1, 0); +#endif + else + break; + if (serial_hds[i]) + s->fir = pxa2xx_fir_init(0x40800000, s->pic[PXA2XX_PIC_ICP], + s->dma, serial_hds[i]); + + s->lcd = pxa2xx_lcdc_init(0x44000000, s->pic[PXA2XX_PIC_LCD]); + + s->cm_base = 0x41300000; + s->cm_regs[CCCR >> 2] = 0x02000210; /* 416.0 MHz */ + s->clkcfg = 0x00000009; /* Turbo mode active */ + iomemtype = cpu_register_io_memory(pxa2xx_cm_readfn, + pxa2xx_cm_writefn, s, DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(s->cm_base, 0x1000, iomemtype); + register_savevm(NULL, "pxa2xx_cm", 0, 0, pxa2xx_cm_save, pxa2xx_cm_load, s); + + cpu_arm_set_cp_io(s->env, 14, pxa2xx_cp14_read, pxa2xx_cp14_write, 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 */ + iomemtype = cpu_register_io_memory(pxa2xx_mm_readfn, + pxa2xx_mm_writefn, s, DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(s->mm_base, 0x1000, iomemtype); + register_savevm(NULL, "pxa2xx_mm", 0, 0, pxa2xx_mm_save, pxa2xx_mm_load, s); + + s->pm_base = 0x40f00000; + iomemtype = cpu_register_io_memory(pxa2xx_pm_readfn, + pxa2xx_pm_writefn, s, DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(s->pm_base, 0x100, iomemtype); + register_savevm(NULL, "pxa2xx_pm", 0, 0, pxa2xx_pm_save, pxa2xx_pm_load, s); + + for (i = 0; pxa27x_ssp[i].io_base; i ++); + s->ssp = (SSIBus **)qemu_mallocz(sizeof(SSIBus *) * i); + for (i = 0; pxa27x_ssp[i].io_base; i ++) { + DeviceState *dev; + dev = sysbus_create_simple("pxa2xx-ssp", pxa27x_ssp[i].io_base, + 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, + s->pic[PXA2XX_PIC_USBH1]); + } + + s->pcmcia[0] = pxa2xx_pcmcia_init(0x20000000); + s->pcmcia[1] = pxa2xx_pcmcia_init(0x30000000); + + s->rtc_base = 0x40900000; + iomemtype = cpu_register_io_memory(pxa2xx_rtc_readfn, + pxa2xx_rtc_writefn, s, DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(s->rtc_base, 0x1000, iomemtype); + pxa2xx_rtc_init(s); + register_savevm(NULL, "pxa2xx_rtc", 0, 0, pxa2xx_rtc_save, + pxa2xx_rtc_load, s); + + s->i2c[0] = pxa2xx_i2c_init(0x40301600, s->pic[PXA2XX_PIC_I2C], 0xffff); + s->i2c[1] = pxa2xx_i2c_init(0x40f00100, s->pic[PXA2XX_PIC_PWRI2C], 0xff); + + s->i2s = pxa2xx_i2s_init(0x40400000, s->pic[PXA2XX_PIC_I2S], s->dma); + + s->kp = pxa27x_keypad_init(0x41500000, 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(unsigned int sdram_size) +{ + PXA2xxState *s; + int iomemtype, i; + DriveInfo *dinfo; + + s = (PXA2xxState *) qemu_mallocz(sizeof(PXA2xxState)); + + s->env = cpu_init("pxa255"); + if (!s->env) { + fprintf(stderr, "Unable to find CPU definition\n"); + exit(1); + } + s->reset = qemu_allocate_irqs(pxa2xx_reset, s, 1)[0]; + + /* SDRAM & Internal Memory Storage */ + cpu_register_physical_memory(PXA2XX_SDRAM_BASE, sdram_size, + qemu_ram_alloc(NULL, "pxa255.sdram", + sdram_size) | IO_MEM_RAM); + cpu_register_physical_memory(PXA2XX_INTERNAL_BASE, PXA2XX_INTERNAL_SIZE, + qemu_ram_alloc(NULL, "pxa255.internal", + PXA2XX_INTERNAL_SIZE) | IO_MEM_RAM); + + s->pic = pxa2xx_pic_init(0x40d00000, s->env); + + s->dma = pxa255_dma_init(0x40000000, s->pic[PXA2XX_PIC_DMA]); + + pxa25x_timer_init(0x40a00000, &s->pic[PXA2XX_PIC_OST_0]); + + s->gpio = pxa2xx_gpio_init(0x40e00000, s->env, 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(0x41100000, dinfo->bdrv, + s->pic[PXA2XX_PIC_MMC], s->dma); + + for (i = 0; pxa255_serial[i].io_base; i ++) + if (serial_hds[i]) { +#ifdef TARGET_WORDS_BIGENDIAN + serial_mm_init(pxa255_serial[i].io_base, 2, + s->pic[pxa255_serial[i].irqn], 14745600/16, + serial_hds[i], 1, 1); +#else + serial_mm_init(pxa255_serial[i].io_base, 2, + s->pic[pxa255_serial[i].irqn], 14745600/16, + serial_hds[i], 1, 0); +#endif + } else { + break; + } + if (serial_hds[i]) + s->fir = pxa2xx_fir_init(0x40800000, s->pic[PXA2XX_PIC_ICP], + s->dma, serial_hds[i]); + + s->lcd = pxa2xx_lcdc_init(0x44000000, s->pic[PXA2XX_PIC_LCD]); + + s->cm_base = 0x41300000; + s->cm_regs[CCCR >> 2] = 0x02000210; /* 416.0 MHz */ + s->clkcfg = 0x00000009; /* Turbo mode active */ + iomemtype = cpu_register_io_memory(pxa2xx_cm_readfn, + pxa2xx_cm_writefn, s, DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(s->cm_base, 0x1000, iomemtype); + register_savevm(NULL, "pxa2xx_cm", 0, 0, pxa2xx_cm_save, pxa2xx_cm_load, s); + + cpu_arm_set_cp_io(s->env, 14, pxa2xx_cp14_read, pxa2xx_cp14_write, 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 */ + iomemtype = cpu_register_io_memory(pxa2xx_mm_readfn, + pxa2xx_mm_writefn, s, DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(s->mm_base, 0x1000, iomemtype); + register_savevm(NULL, "pxa2xx_mm", 0, 0, pxa2xx_mm_save, pxa2xx_mm_load, s); + + s->pm_base = 0x40f00000; + iomemtype = cpu_register_io_memory(pxa2xx_pm_readfn, + pxa2xx_pm_writefn, s, DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(s->pm_base, 0x100, iomemtype); + register_savevm(NULL, "pxa2xx_pm", 0, 0, pxa2xx_pm_save, pxa2xx_pm_load, s); + + for (i = 0; pxa255_ssp[i].io_base; i ++); + s->ssp = (SSIBus **)qemu_mallocz(sizeof(SSIBus *) * i); + for (i = 0; pxa255_ssp[i].io_base; i ++) { + DeviceState *dev; + dev = sysbus_create_simple("pxa2xx-ssp", pxa255_ssp[i].io_base, + 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, + s->pic[PXA2XX_PIC_USBH1]); + } + + s->pcmcia[0] = pxa2xx_pcmcia_init(0x20000000); + s->pcmcia[1] = pxa2xx_pcmcia_init(0x30000000); + + s->rtc_base = 0x40900000; + iomemtype = cpu_register_io_memory(pxa2xx_rtc_readfn, + pxa2xx_rtc_writefn, s, DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(s->rtc_base, 0x1000, iomemtype); + pxa2xx_rtc_init(s); + register_savevm(NULL, "pxa2xx_rtc", 0, 0, pxa2xx_rtc_save, + pxa2xx_rtc_load, s); + + s->i2c[0] = pxa2xx_i2c_init(0x40301600, s->pic[PXA2XX_PIC_I2C], 0xffff); + s->i2c[1] = pxa2xx_i2c_init(0x40f00100, s->pic[PXA2XX_PIC_PWRI2C], 0xff); + + s->i2s = pxa2xx_i2s_init(0x40400000, s->pic[PXA2XX_PIC_I2S], s->dma); + + /* 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_register_devices(void) +{ + i2c_register_slave(&pxa2xx_i2c_slave_info); + sysbus_register_dev("pxa2xx-ssp", sizeof(PXA2xxSSPState), pxa2xx_ssp_init); +} + +device_init(pxa2xx_register_devices) diff --git a/hw/pxa2xx_dma.c b/hw/pxa2xx_dma.c new file mode 100644 index 0000000..b512d34 --- /dev/null +++ b/hw/pxa2xx_dma.c @@ -0,0 +1,549 @@ +/* + * Intel XScale PXA255/270 DMA controller. + * + * Copyright (c) 2006 Openedhand Ltd. + * Copyright (c) 2006 Thorsten Zitterell + * Written by Andrzej Zaborowski + * + * This code is licenced under the GPL. + */ + +#include "hw.h" +#include "pxa.h" + +typedef struct { + target_phys_addr_t descr; + target_phys_addr_t src; + target_phys_addr_t dest; + uint32_t cmd; + uint32_t state; + int request; +} PXA2xxDMAChannel; + +/* Allow the DMA to be used as a PIC. */ +typedef void (*pxa2xx_dma_handler_t)(void *opaque, int irq, int level); + +struct PXA2xxDMAState { + pxa2xx_dma_handler_t handler; + 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; + + /* Flag to avoid recursive DMA invocations. */ + int running; +}; + +#define PXA255_DMA_NUM_CHANNELS 16 +#define PXA27X_DMA_NUM_CHANNELS 32 + +#define PXA2XX_DMA_NUM_REQUESTS 75 + +#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]; + target_phys_addr_t 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, (uint8_t *) 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 uint32_t pxa2xx_dma_read(void *opaque, target_phys_addr_t offset) +{ + PXA2xxDMAState *s = (PXA2xxDMAState *) opaque; + unsigned int channel; + + 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, + target_phys_addr_t offset, uint32_t value) +{ + PXA2xxDMAState *s = (PXA2xxDMAState *) opaque; + unsigned int channel; + + 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__, 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 uint32_t pxa2xx_dma_readbad(void *opaque, target_phys_addr_t offset) +{ + hw_error("%s: Bad access width\n", __FUNCTION__); + return 5; +} + +static void pxa2xx_dma_writebad(void *opaque, + target_phys_addr_t offset, uint32_t value) +{ + hw_error("%s: Bad access width\n", __FUNCTION__); +} + +static CPUReadMemoryFunc * const pxa2xx_dma_readfn[] = { + pxa2xx_dma_readbad, + pxa2xx_dma_readbad, + pxa2xx_dma_read +}; + +static CPUWriteMemoryFunc * const pxa2xx_dma_writefn[] = { + pxa2xx_dma_writebad, + pxa2xx_dma_writebad, + pxa2xx_dma_write +}; + +static void pxa2xx_dma_save(QEMUFile *f, void *opaque) +{ + PXA2xxDMAState *s = (PXA2xxDMAState *) opaque; + int i; + + qemu_put_be32(f, s->channels); + + qemu_put_be32s(f, &s->stopintr); + qemu_put_be32s(f, &s->eorintr); + qemu_put_be32s(f, &s->rasintr); + qemu_put_be32s(f, &s->startintr); + qemu_put_be32s(f, &s->endintr); + qemu_put_be32s(f, &s->align); + qemu_put_be32s(f, &s->pio); + + qemu_put_buffer(f, s->req, PXA2XX_DMA_NUM_REQUESTS); + for (i = 0; i < s->channels; i ++) { + qemu_put_betl(f, s->chan[i].descr); + qemu_put_betl(f, s->chan[i].src); + qemu_put_betl(f, s->chan[i].dest); + qemu_put_be32s(f, &s->chan[i].cmd); + qemu_put_be32s(f, &s->chan[i].state); + qemu_put_be32(f, s->chan[i].request); + }; +} + +static int pxa2xx_dma_load(QEMUFile *f, void *opaque, int version_id) +{ + PXA2xxDMAState *s = (PXA2xxDMAState *) opaque; + int i; + + if (qemu_get_be32(f) != s->channels) + return -EINVAL; + + qemu_get_be32s(f, &s->stopintr); + qemu_get_be32s(f, &s->eorintr); + qemu_get_be32s(f, &s->rasintr); + qemu_get_be32s(f, &s->startintr); + qemu_get_be32s(f, &s->endintr); + qemu_get_be32s(f, &s->align); + qemu_get_be32s(f, &s->pio); + + qemu_get_buffer(f, s->req, PXA2XX_DMA_NUM_REQUESTS); + for (i = 0; i < s->channels; i ++) { + s->chan[i].descr = qemu_get_betl(f); + s->chan[i].src = qemu_get_betl(f); + s->chan[i].dest = qemu_get_betl(f); + qemu_get_be32s(f, &s->chan[i].cmd); + qemu_get_be32s(f, &s->chan[i].state); + s->chan[i].request = qemu_get_be32(f); + }; + + return 0; +} + +static PXA2xxDMAState *pxa2xx_dma_init(target_phys_addr_t base, + qemu_irq irq, int channels) +{ + int i, iomemtype; + PXA2xxDMAState *s; + s = (PXA2xxDMAState *) + qemu_mallocz(sizeof(PXA2xxDMAState)); + + s->channels = channels; + s->chan = qemu_mallocz(sizeof(PXA2xxDMAChannel) * s->channels); + s->irq = irq; + s->handler = (pxa2xx_dma_handler_t) pxa2xx_dma_request; + s->req = qemu_mallocz(sizeof(uint8_t) * PXA2XX_DMA_NUM_REQUESTS); + + 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); + + iomemtype = cpu_register_io_memory(pxa2xx_dma_readfn, + pxa2xx_dma_writefn, s, DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(base, 0x00010000, iomemtype); + + register_savevm(NULL, "pxa2xx_dma", 0, 0, pxa2xx_dma_save, pxa2xx_dma_load, s); + + return s; +} + +PXA2xxDMAState *pxa27x_dma_init(target_phys_addr_t base, + qemu_irq irq) +{ + return pxa2xx_dma_init(base, irq, PXA27X_DMA_NUM_CHANNELS); +} + +PXA2xxDMAState *pxa255_dma_init(target_phys_addr_t base, + qemu_irq irq) +{ + return pxa2xx_dma_init(base, irq, PXA255_DMA_NUM_CHANNELS); +} + +void pxa2xx_dma_request(PXA2xxDMAState *s, int req_num, int on) +{ + 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); + } +} diff --git a/hw/pxa2xx_gpio.c b/hw/pxa2xx_gpio.c new file mode 100644 index 0000000..789965d --- /dev/null +++ b/hw/pxa2xx_gpio.c @@ -0,0 +1,340 @@ +/* + * Intel XScale PXA255/270 GPIO controller emulation. + * + * Copyright (c) 2006 Openedhand Ltd. + * Written by Andrzej Zaborowski + * + * This code is licensed under the GPL. + */ + +#include "hw.h" +#include "sysbus.h" +#include "pxa.h" + +#define PXA2XX_GPIO_BANKS 4 + +typedef struct PXA2xxGPIOInfo PXA2xxGPIOInfo; +struct PXA2xxGPIOInfo { + SysBusDevice busdev; + qemu_irq irq0, irq1, irqX; + int lines; + int ncpu; + CPUState *cpu_env; + + /* 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 gpsr[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; + int bank; + uint32_t mask; + + if (line >= s->lines) { + printf("%s: No GPIO pin %i\n", __FUNCTION__, line); + return; + } + + bank = line >> 5; + mask = 1 << (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 (s->cpu_env->halted && (mask & ~s->dir[bank] & pxa2xx_gpio_wake[bank])) + cpu_interrupt(s->cpu_env, 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 = ffs(diff) - 1; + line = bit + 32 * i; + qemu_set_irq(s->handler[line], (level >> bit) & 1); + } + + s->prev_level[i] = level; + } +} + +static uint32_t pxa2xx_gpio_read(void *opaque, target_phys_addr_t offset) +{ + 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 */ + printf("%s: Read from a write-only register " REG_FMT "\n", + __FUNCTION__, offset); + return s->gpsr[bank]; /* Return last written value. */ + + case GPCR: /* GPIO Pin-Output Clear registers */ + printf("%s: Read from a write-only register " REG_FMT "\n", + __FUNCTION__, offset); + return 31337; /* Specified as unpredictable in the docs. */ + + 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, + target_phys_addr_t offset, uint32_t value) +{ + 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); + s->gpsr[bank] = value; + 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 CPUReadMemoryFunc * const pxa2xx_gpio_readfn[] = { + pxa2xx_gpio_read, + pxa2xx_gpio_read, + pxa2xx_gpio_read +}; + +static CPUWriteMemoryFunc * const pxa2xx_gpio_writefn[] = { + pxa2xx_gpio_write, + pxa2xx_gpio_write, + pxa2xx_gpio_write +}; + +DeviceState *pxa2xx_gpio_init(target_phys_addr_t base, + CPUState *env, qemu_irq *pic, int lines) +{ + DeviceState *dev; + + dev = qdev_create(NULL, "pxa2xx-gpio"); + qdev_prop_set_int32(dev, "lines", lines); + qdev_prop_set_int32(dev, "ncpu", env->cpu_index); + qdev_init_nofail(dev); + + sysbus_mmio_map(sysbus_from_qdev(dev), 0, base); + sysbus_connect_irq(sysbus_from_qdev(dev), 0, pic[PXA2XX_PIC_GPIO_0]); + sysbus_connect_irq(sysbus_from_qdev(dev), 1, pic[PXA2XX_PIC_GPIO_1]); + sysbus_connect_irq(sysbus_from_qdev(dev), 2, pic[PXA2XX_PIC_GPIO_X]); + + return dev; +} + +static int pxa2xx_gpio_initfn(SysBusDevice *dev) +{ + int iomemtype; + PXA2xxGPIOInfo *s; + + s = FROM_SYSBUS(PXA2xxGPIOInfo, dev); + + s->cpu_env = qemu_get_cpu(s->ncpu); + + qdev_init_gpio_in(&dev->qdev, pxa2xx_gpio_set, s->lines); + qdev_init_gpio_out(&dev->qdev, s->handler, s->lines); + + iomemtype = cpu_register_io_memory(pxa2xx_gpio_readfn, + pxa2xx_gpio_writefn, s, DEVICE_NATIVE_ENDIAN); + + sysbus_init_mmio(dev, 0x1000, iomemtype); + sysbus_init_irq(dev, &s->irq0); + sysbus_init_irq(dev, &s->irq1); + sysbus_init_irq(dev, &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 = FROM_SYSBUS(PXA2xxGPIOInfo, sysbus_from_qdev(dev)); + s->read_notify = handler; +} + +static const VMStateDescription vmstate_pxa2xx_gpio_regs = { + .name = "pxa2xx-gpio", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField []) { + VMSTATE_INT32(lines, PXA2xxGPIOInfo), + 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_END_OF_LIST(), + }, +}; + +static SysBusDeviceInfo pxa2xx_gpio_info = { + .init = pxa2xx_gpio_initfn, + .qdev.name = "pxa2xx-gpio", + .qdev.desc = "PXA2xx GPIO controller", + .qdev.size = sizeof(PXA2xxGPIOInfo), + .qdev.props = (Property []) { + DEFINE_PROP_INT32("lines", PXA2xxGPIOInfo, lines, 0), + DEFINE_PROP_INT32("ncpu", PXA2xxGPIOInfo, ncpu, 0), + DEFINE_PROP_END_OF_LIST(), + } +}; + +static void pxa2xx_gpio_register(void) +{ + sysbus_register_withprop(&pxa2xx_gpio_info); +} +device_init(pxa2xx_gpio_register); diff --git a/hw/pxa2xx_keypad.c b/hw/pxa2xx_keypad.c new file mode 100644 index 0000000..4c99917 --- /dev/null +++ b/hw/pxa2xx_keypad.c @@ -0,0 +1,336 @@ +/* + * Intel PXA27X Keypad Controller emulation. + * + * Copyright (c) 2007 MontaVista Software, Inc + * Written by Armin Kuster + * or + * + * This code is licensed under the GPLv2. + */ + +#include "hw.h" +#include "pxa.h" +#include "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 { + qemu_irq irq; + struct keymap *map; + + uint32_t kpc; + uint32_t kpdk; + uint32_t kprec; + uint32_t kpmk; + uint32_t kpas; + uint32_t kpasmkp0; + uint32_t kpasmkp1; + uint32_t kpasmkp2; + uint32_t kpasmkp3; + uint32_t kpkdi; +}; + +static void pxa27x_keyboard_event (PXA2xxKeyPadState *kp, int keycode) +{ + int row, col,rel; + + if(!(kp->kpc & KPC_ME)) /* skip if not enabled */ + return; + + if(kp->kpc & KPC_AS || kp->kpc & KPC_ASACT) { + if(kp->kpc & KPC_AS) + kp->kpc &= ~(KPC_AS); + + rel = (keycode & 0x80) ? 1 : 0; /* key release from qemu */ + keycode &= ~(0x80); /* strip qemu key release bit */ + row = kp->map[keycode].row; + col = kp->map[keycode].column; + if(row == -1 || col == -1) + return; + switch (col) { + case 0: + case 1: + if(rel) + kp->kpasmkp0 = ~(0xffffffff); + else + kp->kpasmkp0 |= KPASMKPx_MKC(row,col); + break; + case 2: + case 3: + if(rel) + kp->kpasmkp1 = ~(0xffffffff); + else + kp->kpasmkp1 |= KPASMKPx_MKC(row,col); + break; + case 4: + case 5: + if(rel) + kp->kpasmkp2 = ~(0xffffffff); + else + kp->kpasmkp2 |= KPASMKPx_MKC(row,col); + break; + case 6: + case 7: + if(rel) + kp->kpasmkp3 = ~(0xffffffff); + else + kp->kpasmkp3 |= KPASMKPx_MKC(row,col); + break; + } /* switch */ + goto out; + } + return; + +out: + if(kp->kpc & KPC_MIE) { + kp->kpc |= KPC_MI; + qemu_irq_raise(kp->irq); + } + return; +} + +static uint32_t pxa2xx_keypad_read(void *opaque, target_phys_addr_t offset) +{ + 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->kpasmkp0; + break; + case KPASMKP1: + return s->kpasmkp1; + break; + case KPASMKP2: + return s->kpasmkp2; + break; + case KPASMKP3: + return s->kpasmkp3; + 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, + target_phys_addr_t offset, uint32_t value) +{ + PXA2xxKeyPadState *s = (PXA2xxKeyPadState *) opaque; + + switch (offset) { + case KPC: + s->kpc = value; + 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->kpasmkp0 = value; + break; + case KPASMKP1: + s->kpasmkp1 = value; + break; + case KPASMKP2: + s->kpasmkp2 = value; + break; + case KPASMKP3: + s->kpasmkp3 = value; + break; + case KPKDI: + s->kpkdi = value; + break; + + default: + hw_error("%s: Bad offset " REG_FMT "\n", __FUNCTION__, offset); + } +} + +static CPUReadMemoryFunc * const pxa2xx_keypad_readfn[] = { + pxa2xx_keypad_read, + pxa2xx_keypad_read, + pxa2xx_keypad_read +}; + +static CPUWriteMemoryFunc * const pxa2xx_keypad_writefn[] = { + pxa2xx_keypad_write, + pxa2xx_keypad_write, + pxa2xx_keypad_write +}; + +static void pxa2xx_keypad_save(QEMUFile *f, void *opaque) +{ + PXA2xxKeyPadState *s = (PXA2xxKeyPadState *) opaque; + + qemu_put_be32s(f, &s->kpc); + qemu_put_be32s(f, &s->kpdk); + qemu_put_be32s(f, &s->kprec); + qemu_put_be32s(f, &s->kpmk); + qemu_put_be32s(f, &s->kpas); + qemu_put_be32s(f, &s->kpasmkp0); + qemu_put_be32s(f, &s->kpasmkp1); + qemu_put_be32s(f, &s->kpasmkp2); + qemu_put_be32s(f, &s->kpasmkp3); + qemu_put_be32s(f, &s->kpkdi); + +} + +static int pxa2xx_keypad_load(QEMUFile *f, void *opaque, int version_id) +{ + PXA2xxKeyPadState *s = (PXA2xxKeyPadState *) opaque; + + qemu_get_be32s(f, &s->kpc); + qemu_get_be32s(f, &s->kpdk); + qemu_get_be32s(f, &s->kprec); + qemu_get_be32s(f, &s->kpmk); + qemu_get_be32s(f, &s->kpas); + qemu_get_be32s(f, &s->kpasmkp0); + qemu_get_be32s(f, &s->kpasmkp1); + qemu_get_be32s(f, &s->kpasmkp2); + qemu_get_be32s(f, &s->kpasmkp3); + qemu_get_be32s(f, &s->kpkdi); + + return 0; +} + +PXA2xxKeyPadState *pxa27x_keypad_init(target_phys_addr_t base, + qemu_irq irq) +{ + int iomemtype; + PXA2xxKeyPadState *s; + + s = (PXA2xxKeyPadState *) qemu_mallocz(sizeof(PXA2xxKeyPadState)); + s->irq = irq; + + iomemtype = cpu_register_io_memory(pxa2xx_keypad_readfn, + pxa2xx_keypad_writefn, s, DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(base, 0x00100000, iomemtype); + + register_savevm(NULL, "pxa2xx_keypad", 0, 0, + pxa2xx_keypad_save, pxa2xx_keypad_load, s); + + return s; +} + +void pxa27x_register_keypad(PXA2xxKeyPadState *kp, 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/pxa2xx_lcd.c b/hw/pxa2xx_lcd.c new file mode 100644 index 0000000..5b2b07e --- /dev/null +++ b/hw/pxa2xx_lcd.c @@ -0,0 +1,982 @@ +/* + * Intel XScale PXA255/270 LCDC emulation. + * + * Copyright (c) 2006 Openedhand Ltd. + * Written by Andrzej Zaborowski + * + * This code is licensed under the GPLv2. + */ + +#include "hw.h" +#include "console.h" +#include "pxa.h" +#include "pixel_ops.h" +/* FIXME: For graphic_rotate. Should probably be done in common code. */ +#include "sysemu.h" +#include "framebuffer.h" + +struct PXA2xxLCDState { + qemu_irq irq; + int irqlevel; + + int invalidated; + DisplayState *ds; + 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 { + target_phys_addr_t branch; + int up; + uint8_t palette[1024]; + uint8_t pbuffer[1024]; + void (*redraw)(PXA2xxLCDState *s, target_phys_addr_t addr, + int *miny, int *maxy); + + target_phys_addr_t descriptor; + target_phys_addr_t source; + uint32_t id; + uint32_t command; + } dma_ch[7]; + + qemu_irq vsync_cb; + int orientation; +}; + +typedef struct __attribute__ ((__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; +} + +/* Set Read Status interrupt high and poke associated registers */ +static inline void pxa2xx_dma_rdst_set(PXA2xxLCDState *s) +{ + s->status[0] |= LCSR0_RDST; + if (s->irqlevel && !(s->control[0] & LCCR0_RDSTM)) + s->status[0] |= LCSR0_SINT; +} + +/* Load new Frame Descriptors from DMA */ +static void pxa2xx_descriptor_load(PXA2xxLCDState *s) +{ + PXAFrameDescriptor desc; + target_phys_addr_t 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)) + continue; + + cpu_physical_memory_read(descptr, (void *)&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 uint32_t pxa2xx_lcdc_read(void *opaque, target_phys_addr_t offset) +{ + 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, + target_phys_addr_t offset, uint32_t value) +{ + 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 CPUReadMemoryFunc * const pxa2xx_lcdc_readfn[] = { + pxa2xx_lcdc_read, + pxa2xx_lcdc_read, + pxa2xx_lcdc_read +}; + +static CPUWriteMemoryFunc * const pxa2xx_lcdc_writefn[] = { + pxa2xx_lcdc_write, + pxa2xx_lcdc_write, + pxa2xx_lcdc_write +}; + +/* Load new palette for a given DMA channel, convert to internal format */ +static void pxa2xx_palette_parse(PXA2xxLCDState *s, int ch, int bpp) +{ + int i, n, format, r, g, b, alpha; + uint32_t *dest, *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 = (uint32_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 = *src & 0xff; + else { + r = (*src & 0xf800) >> 8; + g = (*src & 0x07e0) >> 3; + b = (*src & 0x001f) << 3; + } + break; + case 1: /* 16 bpp plus transparency */ + alpha = *src & (1 << 24); + if (s->control[0] & LCCR0_CMS) + r = g = b = *src & 0xff; + else { + r = (*src & 0xf800) >> 8; + g = (*src & 0x07e0) >> 3; + b = (*src & 0x001f) << 3; + } + break; + case 2: /* 18 bpp plus transparency */ + alpha = *src & (1 << 24); + if (s->control[0] & LCCR0_CMS) + r = g = b = *src & 0xff; + else { + r = (*src & 0xf80000) >> 16; + g = (*src & 0x00fc00) >> 8; + b = (*src & 0x0000f8); + } + break; + case 3: /* 24 bpp plus transparency */ + alpha = *src & (1 << 24); + if (s->control[0] & LCCR0_CMS) + r = g = b = *src & 0xff; + else { + r = (*src & 0xff0000) >> 16; + g = (*src & 0x00ff00) >> 8; + b = (*src & 0x0000ff); + } + break; + } + switch (ds_get_bits_per_pixel(s->ds)) { + 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; + } + src ++; + dest ++; + } +} + +static void pxa2xx_lcdc_dma0_redraw_horiz(PXA2xxLCDState *s, + target_phys_addr_t addr, int *miny, int *maxy) +{ + 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; + framebuffer_update_display(s->ds, + addr, 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_vert(PXA2xxLCDState *s, + target_phys_addr_t addr, int *miny, int *maxy) +{ + 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; + framebuffer_update_display(s->ds, + addr, 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) + qemu_console_resize(s->ds, height, width); + else + qemu_console_resize(s->ds, width, height); + s->invalidated = 1; + s->xres = width; + s->yres = height; + } +} + +static void pxa2xx_update_display(void *opaque) +{ + PXA2xxLCDState *s = (PXA2xxLCDState *) opaque; + target_phys_addr_t 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)) { + 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) { + if (s->orientation) + dpy_update(s->ds, miny, 0, maxy - miny, s->xres); + else + dpy_update(s->ds, 0, miny, s->xres, maxy - miny); + } + 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_screen_dump(void *opaque, const char *filename) +{ + /* TODO */ +} + +static void pxa2xx_lcdc_orientation(void *opaque, int angle) +{ + PXA2xxLCDState *s = (PXA2xxLCDState *) opaque; + + if (angle) { + s->dma_ch[0].redraw = pxa2xx_lcdc_dma0_redraw_vert; + } else { + s->dma_ch[0].redraw = pxa2xx_lcdc_dma0_redraw_horiz; + } + + s->orientation = angle; + s->xres = s->yres = -1; + pxa2xx_lcdc_resize(s); +} + +static void pxa2xx_lcdc_save(QEMUFile *f, void *opaque) +{ + PXA2xxLCDState *s = (PXA2xxLCDState *) opaque; + int i; + + qemu_put_be32(f, s->irqlevel); + qemu_put_be32(f, s->transp); + + for (i = 0; i < 6; i ++) + qemu_put_be32s(f, &s->control[i]); + for (i = 0; i < 2; i ++) + qemu_put_be32s(f, &s->status[i]); + for (i = 0; i < 2; i ++) + qemu_put_be32s(f, &s->ovl1c[i]); + for (i = 0; i < 2; i ++) + qemu_put_be32s(f, &s->ovl2c[i]); + qemu_put_be32s(f, &s->ccr); + qemu_put_be32s(f, &s->cmdcr); + qemu_put_be32s(f, &s->trgbr); + qemu_put_be32s(f, &s->tcr); + qemu_put_be32s(f, &s->liidr); + qemu_put_8s(f, &s->bscntr); + + for (i = 0; i < 7; i ++) { + qemu_put_betl(f, s->dma_ch[i].branch); + qemu_put_byte(f, s->dma_ch[i].up); + qemu_put_buffer(f, s->dma_ch[i].pbuffer, sizeof(s->dma_ch[i].pbuffer)); + + qemu_put_betl(f, s->dma_ch[i].descriptor); + qemu_put_betl(f, s->dma_ch[i].source); + qemu_put_be32s(f, &s->dma_ch[i].id); + qemu_put_be32s(f, &s->dma_ch[i].command); + } +} + +static int pxa2xx_lcdc_load(QEMUFile *f, void *opaque, int version_id) +{ + PXA2xxLCDState *s = (PXA2xxLCDState *) opaque; + int i; + + s->irqlevel = qemu_get_be32(f); + s->transp = qemu_get_be32(f); + + for (i = 0; i < 6; i ++) + qemu_get_be32s(f, &s->control[i]); + for (i = 0; i < 2; i ++) + qemu_get_be32s(f, &s->status[i]); + for (i = 0; i < 2; i ++) + qemu_get_be32s(f, &s->ovl1c[i]); + for (i = 0; i < 2; i ++) + qemu_get_be32s(f, &s->ovl2c[i]); + qemu_get_be32s(f, &s->ccr); + qemu_get_be32s(f, &s->cmdcr); + qemu_get_be32s(f, &s->trgbr); + qemu_get_be32s(f, &s->tcr); + qemu_get_be32s(f, &s->liidr); + qemu_get_8s(f, &s->bscntr); + + for (i = 0; i < 7; i ++) { + s->dma_ch[i].branch = qemu_get_betl(f); + s->dma_ch[i].up = qemu_get_byte(f); + qemu_get_buffer(f, s->dma_ch[i].pbuffer, sizeof(s->dma_ch[i].pbuffer)); + + s->dma_ch[i].descriptor = qemu_get_betl(f); + s->dma_ch[i].source = qemu_get_betl(f); + qemu_get_be32s(f, &s->dma_ch[i].id); + qemu_get_be32s(f, &s->dma_ch[i].command); + } + + s->bpp = LCCR3_BPP(s->control[3]); + s->xres = s->yres = s->pal_for = -1; + + return 0; +} + +#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" + +PXA2xxLCDState *pxa2xx_lcdc_init(target_phys_addr_t base, qemu_irq irq) +{ + int iomemtype; + PXA2xxLCDState *s; + + s = (PXA2xxLCDState *) qemu_mallocz(sizeof(PXA2xxLCDState)); + s->invalidated = 1; + s->irq = irq; + + pxa2xx_lcdc_orientation(s, graphic_rotate); + + iomemtype = cpu_register_io_memory(pxa2xx_lcdc_readfn, + pxa2xx_lcdc_writefn, s, DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(base, 0x00100000, iomemtype); + + s->ds = graphic_console_init(pxa2xx_update_display, + pxa2xx_invalidate_display, + pxa2xx_screen_dump, NULL, s); + + switch (ds_get_bits_per_pixel(s->ds)) { + 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); + } + + register_savevm(NULL, "pxa2xx_lcdc", 0, 0, + pxa2xx_lcdc_save, pxa2xx_lcdc_load, s); + + return s; +} + +void pxa2xx_lcd_vsync_notifier(PXA2xxLCDState *s, qemu_irq handler) +{ + s->vsync_cb = handler; +} diff --git a/hw/pxa2xx_mmci.c b/hw/pxa2xx_mmci.c new file mode 100644 index 0000000..24d409d --- /dev/null +++ b/hw/pxa2xx_mmci.c @@ -0,0 +1,547 @@ +/* + * Intel XScale PXA255/270 MultiMediaCard/SD/SDIO Controller emulation. + * + * Copyright (c) 2006 Openedhand Ltd. + * Written by Andrzej Zaborowski + * + * This code is licensed under the GPLv2. + */ + +#include "hw.h" +#include "pxa.h" +#include "sd.h" + +struct PXA2xxMMCIState { + qemu_irq irq; + void *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; + int ac_width; +}; + +#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; + + pxa2xx_dma_request(s->dma, + PXA2XX_RX_RQ_MMCI, !!(s->intreq & INT_RXFIFO_REQ)); + pxa2xx_dma_request(s->dma, + PXA2XX_TX_RQ_MMCI, !!(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 uint32_t pxa2xx_mmci_read(void *opaque, target_phys_addr_t offset) +{ + 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 (s->ac_width -- && s->rx_len) { + ret |= s->rx_fifo[s->rx_start ++] << (s->ac_width << 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, + target_phys_addr_t offset, uint32_t value) +{ + 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 (s->ac_width -- && s->tx_len < 0x20) + s->tx_fifo[(s->tx_start + (s->tx_len ++)) & 0x1f] = + (value >> (s->ac_width << 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 uint32_t pxa2xx_mmci_readb(void *opaque, target_phys_addr_t offset) +{ + PXA2xxMMCIState *s = (PXA2xxMMCIState *) opaque; + s->ac_width = 1; + return pxa2xx_mmci_read(opaque, offset); +} + +static uint32_t pxa2xx_mmci_readh(void *opaque, target_phys_addr_t offset) +{ + PXA2xxMMCIState *s = (PXA2xxMMCIState *) opaque; + s->ac_width = 2; + return pxa2xx_mmci_read(opaque, offset); +} + +static uint32_t pxa2xx_mmci_readw(void *opaque, target_phys_addr_t offset) +{ + PXA2xxMMCIState *s = (PXA2xxMMCIState *) opaque; + s->ac_width = 4; + return pxa2xx_mmci_read(opaque, offset); +} + +static CPUReadMemoryFunc * const pxa2xx_mmci_readfn[] = { + pxa2xx_mmci_readb, + pxa2xx_mmci_readh, + pxa2xx_mmci_readw +}; + +static void pxa2xx_mmci_writeb(void *opaque, + target_phys_addr_t offset, uint32_t value) +{ + PXA2xxMMCIState *s = (PXA2xxMMCIState *) opaque; + s->ac_width = 1; + pxa2xx_mmci_write(opaque, offset, value); +} + +static void pxa2xx_mmci_writeh(void *opaque, + target_phys_addr_t offset, uint32_t value) +{ + PXA2xxMMCIState *s = (PXA2xxMMCIState *) opaque; + s->ac_width = 2; + pxa2xx_mmci_write(opaque, offset, value); +} + +static void pxa2xx_mmci_writew(void *opaque, + target_phys_addr_t offset, uint32_t value) +{ + PXA2xxMMCIState *s = (PXA2xxMMCIState *) opaque; + s->ac_width = 4; + pxa2xx_mmci_write(opaque, offset, value); +} + +static CPUWriteMemoryFunc * const pxa2xx_mmci_writefn[] = { + pxa2xx_mmci_writeb, + pxa2xx_mmci_writeh, + pxa2xx_mmci_writew +}; + +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(target_phys_addr_t base, + BlockDriverState *bd, qemu_irq irq, void *dma) +{ + int iomemtype; + PXA2xxMMCIState *s; + + s = (PXA2xxMMCIState *) qemu_mallocz(sizeof(PXA2xxMMCIState)); + s->irq = irq; + s->dma = dma; + + iomemtype = cpu_register_io_memory(pxa2xx_mmci_readfn, + pxa2xx_mmci_writefn, s, DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(base, 0x00100000, iomemtype); + + /* Instantiate the actual storage */ + s->card = sd_init(bd, 0); + + 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/pxa2xx_pcmcia.c b/hw/pxa2xx_pcmcia.c new file mode 100644 index 0000000..50d4649 --- /dev/null +++ b/hw/pxa2xx_pcmcia.c @@ -0,0 +1,215 @@ +/* + * Intel XScale PXA255/270 PC Card and CompactFlash Interface. + * + * Copyright (c) 2006 Openedhand Ltd. + * Written by Andrzej Zaborowski + * + * This code is licensed under the GPLv2. + */ + +#include "hw.h" +#include "pcmcia.h" +#include "pxa.h" + +struct PXA2xxPCMCIAState { + PCMCIASocket slot; + PCMCIACardState *card; + + qemu_irq irq; + qemu_irq cd_irq; +}; + +static uint32_t pxa2xx_pcmcia_common_read(void *opaque, + target_phys_addr_t offset) +{ + PXA2xxPCMCIAState *s = (PXA2xxPCMCIAState *) opaque; + + if (s->slot.attached) { + return s->card->common_read(s->card->state, offset); + } + + return 0; +} + +static void pxa2xx_pcmcia_common_write(void *opaque, + target_phys_addr_t offset, uint32_t value) +{ + PXA2xxPCMCIAState *s = (PXA2xxPCMCIAState *) opaque; + + if (s->slot.attached) { + s->card->common_write(s->card->state, offset, value); + } +} + +static uint32_t pxa2xx_pcmcia_attr_read(void *opaque, + target_phys_addr_t offset) +{ + PXA2xxPCMCIAState *s = (PXA2xxPCMCIAState *) opaque; + + if (s->slot.attached) { + return s->card->attr_read(s->card->state, offset); + } + + return 0; +} + +static void pxa2xx_pcmcia_attr_write(void *opaque, + target_phys_addr_t offset, uint32_t value) +{ + PXA2xxPCMCIAState *s = (PXA2xxPCMCIAState *) opaque; + + if (s->slot.attached) { + s->card->attr_write(s->card->state, offset, value); + } +} + +static uint32_t pxa2xx_pcmcia_io_read(void *opaque, + target_phys_addr_t offset) +{ + PXA2xxPCMCIAState *s = (PXA2xxPCMCIAState *) opaque; + + if (s->slot.attached) { + return s->card->io_read(s->card->state, offset); + } + + return 0; +} + +static void pxa2xx_pcmcia_io_write(void *opaque, + target_phys_addr_t offset, uint32_t value) +{ + PXA2xxPCMCIAState *s = (PXA2xxPCMCIAState *) opaque; + + if (s->slot.attached) { + s->card->io_write(s->card->state, offset, value); + } +} + +static CPUReadMemoryFunc * const pxa2xx_pcmcia_common_readfn[] = { + pxa2xx_pcmcia_common_read, + pxa2xx_pcmcia_common_read, + pxa2xx_pcmcia_common_read, +}; + +static CPUWriteMemoryFunc * const pxa2xx_pcmcia_common_writefn[] = { + pxa2xx_pcmcia_common_write, + pxa2xx_pcmcia_common_write, + pxa2xx_pcmcia_common_write, +}; + +static CPUReadMemoryFunc * const pxa2xx_pcmcia_attr_readfn[] = { + pxa2xx_pcmcia_attr_read, + pxa2xx_pcmcia_attr_read, + pxa2xx_pcmcia_attr_read, +}; + +static CPUWriteMemoryFunc * const pxa2xx_pcmcia_attr_writefn[] = { + pxa2xx_pcmcia_attr_write, + pxa2xx_pcmcia_attr_write, + pxa2xx_pcmcia_attr_write, +}; + +static CPUReadMemoryFunc * const pxa2xx_pcmcia_io_readfn[] = { + pxa2xx_pcmcia_io_read, + pxa2xx_pcmcia_io_read, + pxa2xx_pcmcia_io_read, +}; + +static CPUWriteMemoryFunc * const pxa2xx_pcmcia_io_writefn[] = { + pxa2xx_pcmcia_io_write, + pxa2xx_pcmcia_io_write, + pxa2xx_pcmcia_io_write, +}; + +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(target_phys_addr_t base) +{ + int iomemtype; + PXA2xxPCMCIAState *s; + + s = (PXA2xxPCMCIAState *) + qemu_mallocz(sizeof(PXA2xxPCMCIAState)); + + /* Socket I/O Memory Space */ + iomemtype = cpu_register_io_memory(pxa2xx_pcmcia_io_readfn, + pxa2xx_pcmcia_io_writefn, s, DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(base | 0x00000000, 0x04000000, iomemtype); + + /* Then next 64 MB is reserved */ + + /* Socket Attribute Memory Space */ + iomemtype = cpu_register_io_memory(pxa2xx_pcmcia_attr_readfn, + pxa2xx_pcmcia_attr_writefn, s, DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(base | 0x08000000, 0x04000000, iomemtype); + + /* Socket Common Memory Space */ + iomemtype = cpu_register_io_memory(pxa2xx_pcmcia_common_readfn, + pxa2xx_pcmcia_common_writefn, s, DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(base | 0x0c000000, 0x04000000, iomemtype); + + if (base == 0x30000000) + s->slot.slot_string = "PXA PC Card Socket 1"; + else + s->slot.slot_string = "PXA PC Card Socket 0"; + s->slot.irq = qemu_allocate_irqs(pxa2xx_pcmcia_set_irq, s, 1)[0]; + pcmcia_socket_register(&s->slot); + + return s; +} + +/* Insert a new card into a slot */ +int pxa2xx_pcmcia_attach(void *opaque, PCMCIACardState *card) +{ + PXA2xxPCMCIAState *s = (PXA2xxPCMCIAState *) opaque; + if (s->slot.attached) + return -EEXIST; + + if (s->cd_irq) { + qemu_irq_raise(s->cd_irq); + } + + s->card = card; + + s->slot.attached = 1; + s->card->slot = &s->slot; + s->card->attach(s->card->state); + + return 0; +} + +/* Eject card from the slot */ +int pxa2xx_pcmcia_dettach(void *opaque) +{ + PXA2xxPCMCIAState *s = (PXA2xxPCMCIAState *) opaque; + if (!s->slot.attached) + return -ENOENT; + + s->card->detach(s->card->state); + s->card->slot = NULL; + s->card = NULL; + + s->slot.attached = 0; + + 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; +} diff --git a/hw/pxa2xx_pic.c b/hw/pxa2xx_pic.c new file mode 100644 index 0000000..a36da23 --- /dev/null +++ b/hw/pxa2xx_pic.c @@ -0,0 +1,313 @@ +/* + * Intel XScale PXA Programmable Interrupt Controller. + * + * Copyright (c) 2006 Openedhand Ltd. + * Copyright (c) 2006 Thorsten Zitterell + * Written by Andrzej Zaborowski + * + * This code is licenced under the GPL. + */ + +#include "hw.h" +#include "pxa.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 + +typedef struct { + CPUState *cpu_env; + 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; + + if (s->cpu_env->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(s->cpu_env, 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(s->cpu_env, CPU_INTERRUPT_FIQ); + else + cpu_reset_interrupt(s->cpu_env, CPU_INTERRUPT_FIQ); + + if ((mask[0] & ~s->is_fiq[0]) || (mask[1] & ~s->is_fiq[1])) + cpu_interrupt(s->cpu_env, CPU_INTERRUPT_HARD); + else + cpu_reset_interrupt(s->cpu_env, 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] & (1 << 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 |= (1 << 31) | (irq << 16); + } + } + } + + return ichp; +} + +static uint32_t pxa2xx_pic_mem_read(void *opaque, target_phys_addr_t offset) +{ + 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, target_phys_addr_t offset, + uint32_t value) +{ + 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 uint32_t pxa2xx_pic_cp_read(void *opaque, int op2, int reg, int crm) +{ + target_phys_addr_t offset; + + if (pxa2xx_cp_reg_map[reg] == -1) { + printf("%s: Bad register 0x%x\n", __FUNCTION__, reg); + return 0; + } + + offset = pxa2xx_cp_reg_map[reg]; + return pxa2xx_pic_mem_read(opaque, offset); +} + +static void pxa2xx_pic_cp_write(void *opaque, int op2, int reg, int crm, + uint32_t value) +{ + target_phys_addr_t offset; + + if (pxa2xx_cp_reg_map[reg] == -1) { + printf("%s: Bad register 0x%x\n", __FUNCTION__, reg); + return; + } + + offset = pxa2xx_cp_reg_map[reg]; + pxa2xx_pic_mem_write(opaque, offset, value); +} + +static CPUReadMemoryFunc * const pxa2xx_pic_readfn[] = { + pxa2xx_pic_mem_read, + pxa2xx_pic_mem_read, + pxa2xx_pic_mem_read, +}; + +static CPUWriteMemoryFunc * const pxa2xx_pic_writefn[] = { + pxa2xx_pic_mem_write, + pxa2xx_pic_mem_write, + pxa2xx_pic_mem_write, +}; + +static void pxa2xx_pic_save(QEMUFile *f, void *opaque) +{ + PXA2xxPICState *s = (PXA2xxPICState *) opaque; + int i; + + for (i = 0; i < 2; i ++) + qemu_put_be32s(f, &s->int_enabled[i]); + for (i = 0; i < 2; i ++) + qemu_put_be32s(f, &s->int_pending[i]); + for (i = 0; i < 2; i ++) + qemu_put_be32s(f, &s->is_fiq[i]); + qemu_put_be32s(f, &s->int_idle); + for (i = 0; i < PXA2XX_PIC_SRCS; i ++) + qemu_put_be32s(f, &s->priority[i]); +} + +static int pxa2xx_pic_load(QEMUFile *f, void *opaque, int version_id) +{ + PXA2xxPICState *s = (PXA2xxPICState *) opaque; + int i; + + for (i = 0; i < 2; i ++) + qemu_get_be32s(f, &s->int_enabled[i]); + for (i = 0; i < 2; i ++) + qemu_get_be32s(f, &s->int_pending[i]); + for (i = 0; i < 2; i ++) + qemu_get_be32s(f, &s->is_fiq[i]); + qemu_get_be32s(f, &s->int_idle); + for (i = 0; i < PXA2XX_PIC_SRCS; i ++) + qemu_get_be32s(f, &s->priority[i]); + + pxa2xx_pic_update(opaque); + return 0; +} + +qemu_irq *pxa2xx_pic_init(target_phys_addr_t base, CPUState *env) +{ + PXA2xxPICState *s; + int iomemtype; + qemu_irq *qi; + + s = (PXA2xxPICState *) + qemu_mallocz(sizeof(PXA2xxPICState)); + if (!s) + return NULL; + + s->cpu_env = env; + + 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; + + qi = qemu_allocate_irqs(pxa2xx_pic_set_irq, s, PXA2XX_PIC_SRCS); + + /* Enable IC memory-mapped registers access. */ + iomemtype = cpu_register_io_memory(pxa2xx_pic_readfn, + pxa2xx_pic_writefn, s, DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(base, 0x00100000, iomemtype); + + /* Enable IC coprocessor access. */ + cpu_arm_set_cp_io(env, 6, pxa2xx_pic_cp_read, pxa2xx_pic_cp_write, s); + + register_savevm(NULL, "pxa2xx_pic", 0, 0, pxa2xx_pic_save, + pxa2xx_pic_load, s); + + return qi; +} diff --git a/hw/pxa2xx_template.h b/hw/pxa2xx_template.h new file mode 100644 index 0000000..1cbe36c --- /dev/null +++ b/hw/pxa2xx_template.h @@ -0,0 +1,435 @@ +/* + * Intel XScale PXA255/270 LCDC emulation. + * + * Copyright (c) 2006 Openedhand Ltd. + * Written by Andrzej Zaborowski + * + * 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) *to = from; SKIP_PIXEL(to) +#elif BITS == 15 || BITS == 16 +# define COPY_PIXEL(to, from) *(uint16_t *) to = from; SKIP_PIXEL(to) +#elif BITS == 24 +# define COPY_PIXEL(to, from) \ + *(uint16_t *) to = from; *(to + 2) = (from) >> 16; SKIP_PIXEL(to) +#elif BITS == 32 +# define COPY_PIXEL(to, from) *(uint32_t *) to = from; SKIP_PIXEL(to) +#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/pxa2xx_timer.c b/hw/pxa2xx_timer.c new file mode 100644 index 0000000..b556d11 --- /dev/null +++ b/hw/pxa2xx_timer.c @@ -0,0 +1,490 @@ +/* + * Intel XScale PXA255/270 OS Timers. + * + * Copyright (c) 2006 Openedhand Ltd. + * Copyright (c) 2006 Thorsten Zitterell + * + * This code is licenced under the GPL. + */ + +#include "hw.h" +#include "qemu-timer.h" +#include "sysemu.h" +#include "pxa.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, +}; + +typedef struct { + uint32_t value; + int level; + qemu_irq irq; + QEMUTimer *qtimer; + int num; + void *info; +} PXA2xxTimer0; + +typedef struct { + PXA2xxTimer0 tm; + int32_t oldclock; + int32_t clock; + uint64_t lastload; + uint32_t freq; + uint32_t control; +} PXA2xxTimer4; + +typedef struct { + int32_t clock; + int32_t oldclock; + uint64_t lastload; + uint32_t freq; + PXA2xxTimer0 timer[4]; + PXA2xxTimer4 *tm4; + uint32_t events; + uint32_t irq_enabled; + uint32_t reset3; + uint32_t snapshot; +} pxa2xx_timer_info; + +static void pxa2xx_timer_update(void *opaque, uint64_t now_qemu) +{ + pxa2xx_timer_info *s = (pxa2xx_timer_info *) 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); + qemu_mod_timer(s->timer[i].qtimer, new_qemu); + } +} + +static void pxa2xx_timer_update4(void *opaque, uint64_t now_qemu, int n) +{ + pxa2xx_timer_info *s = (pxa2xx_timer_info *) 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) { + qemu_del_timer(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); + qemu_mod_timer(s->tm4[n].tm.qtimer, new_qemu); +} + +static uint32_t pxa2xx_timer_read(void *opaque, target_phys_addr_t offset) +{ + pxa2xx_timer_info *s = (pxa2xx_timer_info *) opaque; + int tm = 0; + + switch (offset) { + case OSMR3: tm ++; + case OSMR2: tm ++; + case OSMR1: tm ++; + case OSMR0: + return s->timer[tm].value; + case OSMR11: tm ++; + case OSMR10: tm ++; + case OSMR9: tm ++; + case OSMR8: tm ++; + case OSMR7: tm ++; + case OSMR6: tm ++; + case OSMR5: tm ++; + case OSMR4: + if (!s->tm4) + goto badreg; + return s->tm4[tm].tm.value; + case OSCR: + return s->clock + muldiv64(qemu_get_clock(vm_clock) - + s->lastload, s->freq, get_ticks_per_sec()); + case OSCR11: tm ++; + case OSCR10: tm ++; + case OSCR9: tm ++; + case OSCR8: tm ++; + case OSCR7: tm ++; + case OSCR6: tm ++; + case OSCR5: tm ++; + case OSCR4: + if (!s->tm4) + 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_get_clock(vm_clock) - + 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_get_clock(vm_clock) - + 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 ++; + case OMCR10: tm ++; + case OMCR9: tm ++; + case OMCR8: tm ++; + case OMCR7: tm ++; + case OMCR6: tm ++; + case OMCR5: tm ++; + case OMCR4: + if (!s->tm4) + 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, target_phys_addr_t offset, + uint32_t value) +{ + int i, tm = 0; + pxa2xx_timer_info *s = (pxa2xx_timer_info *) opaque; + + switch (offset) { + case OSMR3: tm ++; + case OSMR2: tm ++; + case OSMR1: tm ++; + case OSMR0: + s->timer[tm].value = value; + pxa2xx_timer_update(s, qemu_get_clock(vm_clock)); + break; + case OSMR11: tm ++; + case OSMR10: tm ++; + case OSMR9: tm ++; + case OSMR8: tm ++; + case OSMR7: tm ++; + case OSMR6: tm ++; + case OSMR5: tm ++; + case OSMR4: + if (!s->tm4) + goto badreg; + s->tm4[tm].tm.value = value; + pxa2xx_timer_update4(s, qemu_get_clock(vm_clock), tm); + break; + case OSCR: + s->oldclock = s->clock; + s->lastload = qemu_get_clock(vm_clock); + s->clock = value; + pxa2xx_timer_update(s, s->lastload); + break; + case OSCR11: tm ++; + case OSCR10: tm ++; + case OSCR9: tm ++; + case OSCR8: tm ++; + case OSCR7: tm ++; + case OSCR6: tm ++; + case OSCR5: tm ++; + case OSCR4: + if (!s->tm4) + goto badreg; + s->tm4[tm].oldclock = s->tm4[tm].clock; + s->tm4[tm].lastload = qemu_get_clock(vm_clock); + 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 */ + s->events &= ~value; + for (i = 0; i < 4; i ++, value >>= 1) { + if (s->timer[i].level && (value & 1)) { + s->timer[i].level = 0; + qemu_irq_lower(s->timer[i].irq); + } + } + if (s->tm4) { + for (i = 0; i < 8; i ++, value >>= 1) + if (s->tm4[i].tm.level && (value & 1)) + s->tm4[i].tm.level = 0; + if (!(s->events & 0xff0)) + qemu_irq_lower(s->tm4->tm.irq); + } + break; + case OWER: /* XXX: Reset on OSMR3 match? */ + s->reset3 = value; + break; + case OMCR7: tm ++; + case OMCR6: tm ++; + case OMCR5: tm ++; + case OMCR4: + if (!s->tm4) + 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_get_clock(vm_clock), tm); + } + break; + case OMCR11: tm ++; + case OMCR10: tm ++; + case OMCR9: tm ++; + case OMCR8: tm += 4; + if (!s->tm4) + 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_get_clock(vm_clock), tm); + } + break; + default: + badreg: + hw_error("pxa2xx_timer_write: Bad offset " REG_FMT "\n", offset); + } +} + +static CPUReadMemoryFunc * const pxa2xx_timer_readfn[] = { + pxa2xx_timer_read, + pxa2xx_timer_read, + pxa2xx_timer_read, +}; + +static CPUWriteMemoryFunc * const pxa2xx_timer_writefn[] = { + pxa2xx_timer_write, + pxa2xx_timer_write, + pxa2xx_timer_write, +}; + +static void pxa2xx_timer_tick(void *opaque) +{ + PXA2xxTimer0 *t = (PXA2xxTimer0 *) opaque; + pxa2xx_timer_info *i = (pxa2xx_timer_info *) t->info; + + if (i->irq_enabled & (1 << t->num)) { + t->level = 1; + 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; + pxa2xx_timer_info *i = (pxa2xx_timer_info *) 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_get_clock(vm_clock), t->tm.num - 4); +} + +static void pxa2xx_timer_save(QEMUFile *f, void *opaque) +{ + pxa2xx_timer_info *s = (pxa2xx_timer_info *) opaque; + int i; + + qemu_put_be32s(f, (uint32_t *) &s->clock); + qemu_put_be32s(f, (uint32_t *) &s->oldclock); + qemu_put_be64s(f, &s->lastload); + + for (i = 0; i < 4; i ++) { + qemu_put_be32s(f, &s->timer[i].value); + qemu_put_be32(f, s->timer[i].level); + } + if (s->tm4) + for (i = 0; i < 8; i ++) { + qemu_put_be32s(f, &s->tm4[i].tm.value); + qemu_put_be32(f, s->tm4[i].tm.level); + qemu_put_sbe32s(f, &s->tm4[i].oldclock); + qemu_put_sbe32s(f, &s->tm4[i].clock); + qemu_put_be64s(f, &s->tm4[i].lastload); + qemu_put_be32s(f, &s->tm4[i].freq); + qemu_put_be32s(f, &s->tm4[i].control); + } + + qemu_put_be32s(f, &s->events); + qemu_put_be32s(f, &s->irq_enabled); + qemu_put_be32s(f, &s->reset3); + qemu_put_be32s(f, &s->snapshot); +} + +static int pxa2xx_timer_load(QEMUFile *f, void *opaque, int version_id) +{ + pxa2xx_timer_info *s = (pxa2xx_timer_info *) opaque; + int64_t now; + int i; + + qemu_get_be32s(f, (uint32_t *) &s->clock); + qemu_get_be32s(f, (uint32_t *) &s->oldclock); + qemu_get_be64s(f, &s->lastload); + + now = qemu_get_clock(vm_clock); + for (i = 0; i < 4; i ++) { + qemu_get_be32s(f, &s->timer[i].value); + s->timer[i].level = qemu_get_be32(f); + } + pxa2xx_timer_update(s, now); + + if (s->tm4) + for (i = 0; i < 8; i ++) { + qemu_get_be32s(f, &s->tm4[i].tm.value); + s->tm4[i].tm.level = qemu_get_be32(f); + qemu_get_sbe32s(f, &s->tm4[i].oldclock); + qemu_get_sbe32s(f, &s->tm4[i].clock); + qemu_get_be64s(f, &s->tm4[i].lastload); + qemu_get_be32s(f, &s->tm4[i].freq); + qemu_get_be32s(f, &s->tm4[i].control); + pxa2xx_timer_update4(s, now, i); + } + + qemu_get_be32s(f, &s->events); + qemu_get_be32s(f, &s->irq_enabled); + qemu_get_be32s(f, &s->reset3); + qemu_get_be32s(f, &s->snapshot); + + return 0; +} + +static pxa2xx_timer_info *pxa2xx_timer_init(target_phys_addr_t base, + qemu_irq *irqs) +{ + int i; + int iomemtype; + pxa2xx_timer_info *s; + + s = (pxa2xx_timer_info *) qemu_mallocz(sizeof(pxa2xx_timer_info)); + s->irq_enabled = 0; + s->oldclock = 0; + s->clock = 0; + s->lastload = qemu_get_clock(vm_clock); + s->reset3 = 0; + + for (i = 0; i < 4; i ++) { + s->timer[i].value = 0; + s->timer[i].irq = irqs[i]; + s->timer[i].info = s; + s->timer[i].num = i; + s->timer[i].level = 0; + s->timer[i].qtimer = qemu_new_timer(vm_clock, + pxa2xx_timer_tick, &s->timer[i]); + } + + iomemtype = cpu_register_io_memory(pxa2xx_timer_readfn, + pxa2xx_timer_writefn, s, DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(base, 0x00001000, iomemtype); + + register_savevm(NULL, "pxa2xx_timer", 0, 0, + pxa2xx_timer_save, pxa2xx_timer_load, s); + + return s; +} + +void pxa25x_timer_init(target_phys_addr_t base, qemu_irq *irqs) +{ + pxa2xx_timer_info *s = pxa2xx_timer_init(base, irqs); + s->freq = PXA25X_FREQ; + s->tm4 = NULL; +} + +void pxa27x_timer_init(target_phys_addr_t base, + qemu_irq *irqs, qemu_irq irq4) +{ + pxa2xx_timer_info *s = pxa2xx_timer_init(base, irqs); + int i; + s->freq = PXA27X_FREQ; + s->tm4 = (PXA2xxTimer4 *) qemu_mallocz(8 * + sizeof(PXA2xxTimer4)); + for (i = 0; i < 8; i ++) { + s->tm4[i].tm.value = 0; + s->tm4[i].tm.irq = irq4; + s->tm4[i].tm.info = s; + s->tm4[i].tm.num = i + 4; + s->tm4[i].tm.level = 0; + s->tm4[i].freq = 0; + s->tm4[i].control = 0x0; + s->tm4[i].tm.qtimer = qemu_new_timer(vm_clock, + pxa2xx_timer_tick4, &s->tm4[i]); + } +} diff --git a/hw/qdev-addr.c b/hw/qdev-addr.c new file mode 100644 index 0000000..305c2d3 --- /dev/null +++ b/hw/qdev-addr.c @@ -0,0 +1,32 @@ +#include "qdev.h" +#include "qdev-addr.h" +#include "targphys.h" + +/* --- target physical address --- */ + +static int parse_taddr(DeviceState *dev, Property *prop, const char *str) +{ + target_phys_addr_t *ptr = qdev_get_prop_ptr(dev, prop); + + *ptr = strtoull(str, NULL, 16); + return 0; +} + +static int print_taddr(DeviceState *dev, Property *prop, char *dest, size_t len) +{ + target_phys_addr_t *ptr = qdev_get_prop_ptr(dev, prop); + return snprintf(dest, len, "0x" TARGET_FMT_plx, *ptr); +} + +PropertyInfo qdev_prop_taddr = { + .name = "taddr", + .type = PROP_TYPE_TADDR, + .size = sizeof(target_phys_addr_t), + .parse = parse_taddr, + .print = print_taddr, +}; + +void qdev_prop_set_taddr(DeviceState *dev, const char *name, target_phys_addr_t value) +{ + qdev_prop_set(dev, name, &value, PROP_TYPE_TADDR); +} diff --git a/hw/qdev-addr.h b/hw/qdev-addr.h new file mode 100644 index 0000000..a0ddf38 --- /dev/null +++ b/hw/qdev-addr.h @@ -0,0 +1,5 @@ +#define DEFINE_PROP_TADDR(_n, _s, _f, _d) \ + DEFINE_PROP_DEFAULT(_n, _s, _f, _d, qdev_prop_taddr, target_phys_addr_t) + +extern PropertyInfo qdev_prop_taddr; +void qdev_prop_set_taddr(DeviceState *dev, const char *name, target_phys_addr_t value); diff --git a/hw/qdev-properties.c b/hw/qdev-properties.c new file mode 100644 index 0000000..a493087 --- /dev/null +++ b/hw/qdev-properties.c @@ -0,0 +1,777 @@ +#include "net.h" +#include "qdev.h" +#include "qerror.h" +#include "blockdev.h" + +void *qdev_get_prop_ptr(DeviceState *dev, Property *prop) +{ + void *ptr = dev; + ptr += prop->offset; + return ptr; +} + +static uint32_t qdev_get_prop_mask(Property *prop) +{ + assert(prop->info->type == PROP_TYPE_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 qdev_prop_cpy(DeviceState *dev, Property *props, void *src) +{ + if (props->info->type == PROP_TYPE_BIT) { + bool *defval = src; + bit_prop_set(dev, props, *defval); + } else { + char *dst = qdev_get_prop_ptr(dev, props); + memcpy(dst, src, props->info->size); + } +} + +/* Bit */ +static int parse_bit(DeviceState *dev, Property *prop, const char *str) +{ + if (!strncasecmp(str, "on", 2)) + bit_prop_set(dev, prop, true); + else if (!strncasecmp(str, "off", 3)) + bit_prop_set(dev, prop, false); + else + return -EINVAL; + return 0; +} + +static int print_bit(DeviceState *dev, Property *prop, char *dest, size_t len) +{ + uint8_t *p = qdev_get_prop_ptr(dev, prop); + return snprintf(dest, len, (*p & qdev_get_prop_mask(prop)) ? "on" : "off"); +} + +PropertyInfo qdev_prop_bit = { + .name = "on/off", + .type = PROP_TYPE_BIT, + .size = sizeof(uint32_t), + .parse = parse_bit, + .print = print_bit, +}; + +/* --- 8bit integer --- */ + +static int parse_uint8(DeviceState *dev, Property *prop, const char *str) +{ + uint8_t *ptr = qdev_get_prop_ptr(dev, prop); + char *end; + + /* accept both hex and decimal */ + *ptr = strtoul(str, &end, 0); + if ((*end != '\0') || (end == str)) { + return -EINVAL; + } + + return 0; +} + +static int print_uint8(DeviceState *dev, Property *prop, char *dest, size_t len) +{ + uint8_t *ptr = qdev_get_prop_ptr(dev, prop); + return snprintf(dest, len, "%" PRIu8, *ptr); +} + +PropertyInfo qdev_prop_uint8 = { + .name = "uint8", + .type = PROP_TYPE_UINT8, + .size = sizeof(uint8_t), + .parse = parse_uint8, + .print = print_uint8, +}; + +/* --- 16bit integer --- */ + +static int parse_uint16(DeviceState *dev, Property *prop, const char *str) +{ + uint16_t *ptr = qdev_get_prop_ptr(dev, prop); + char *end; + + /* accept both hex and decimal */ + *ptr = strtoul(str, &end, 0); + if ((*end != '\0') || (end == str)) { + return -EINVAL; + } + + return 0; +} + +static int print_uint16(DeviceState *dev, Property *prop, char *dest, size_t len) +{ + uint16_t *ptr = qdev_get_prop_ptr(dev, prop); + return snprintf(dest, len, "%" PRIu16, *ptr); +} + +PropertyInfo qdev_prop_uint16 = { + .name = "uint16", + .type = PROP_TYPE_UINT16, + .size = sizeof(uint16_t), + .parse = parse_uint16, + .print = print_uint16, +}; + +/* --- 32bit integer --- */ + +static int parse_uint32(DeviceState *dev, Property *prop, const char *str) +{ + uint32_t *ptr = qdev_get_prop_ptr(dev, prop); + char *end; + + /* accept both hex and decimal */ + *ptr = strtoul(str, &end, 0); + if ((*end != '\0') || (end == str)) { + return -EINVAL; + } + + return 0; +} + +static int print_uint32(DeviceState *dev, Property *prop, char *dest, size_t len) +{ + uint32_t *ptr = qdev_get_prop_ptr(dev, prop); + return snprintf(dest, len, "%" PRIu32, *ptr); +} + +PropertyInfo qdev_prop_uint32 = { + .name = "uint32", + .type = PROP_TYPE_UINT32, + .size = sizeof(uint32_t), + .parse = parse_uint32, + .print = print_uint32, +}; + +static int parse_int32(DeviceState *dev, Property *prop, const char *str) +{ + int32_t *ptr = qdev_get_prop_ptr(dev, prop); + char *end; + + *ptr = strtol(str, &end, 10); + if ((*end != '\0') || (end == str)) { + return -EINVAL; + } + + return 0; +} + +static int print_int32(DeviceState *dev, Property *prop, char *dest, size_t len) +{ + int32_t *ptr = qdev_get_prop_ptr(dev, prop); + return snprintf(dest, len, "%" PRId32, *ptr); +} + +PropertyInfo qdev_prop_int32 = { + .name = "int32", + .type = PROP_TYPE_INT32, + .size = sizeof(int32_t), + .parse = parse_int32, + .print = print_int32, +}; + +/* --- 32bit hex value --- */ + +static int parse_hex32(DeviceState *dev, Property *prop, const char *str) +{ + uint32_t *ptr = qdev_get_prop_ptr(dev, prop); + char *end; + + *ptr = strtoul(str, &end, 16); + if ((*end != '\0') || (end == str)) { + return -EINVAL; + } + + return 0; +} + +static int print_hex32(DeviceState *dev, Property *prop, char *dest, size_t len) +{ + uint32_t *ptr = qdev_get_prop_ptr(dev, prop); + return snprintf(dest, len, "0x%" PRIx32, *ptr); +} + +PropertyInfo qdev_prop_hex32 = { + .name = "hex32", + .type = PROP_TYPE_UINT32, + .size = sizeof(uint32_t), + .parse = parse_hex32, + .print = print_hex32, +}; + +/* --- 64bit integer --- */ + +static int parse_uint64(DeviceState *dev, Property *prop, const char *str) +{ + uint64_t *ptr = qdev_get_prop_ptr(dev, prop); + char *end; + + /* accept both hex and decimal */ + *ptr = strtoull(str, &end, 0); + if ((*end != '\0') || (end == str)) { + return -EINVAL; + } + + return 0; +} + +static int print_uint64(DeviceState *dev, Property *prop, char *dest, size_t len) +{ + uint64_t *ptr = qdev_get_prop_ptr(dev, prop); + return snprintf(dest, len, "%" PRIu64, *ptr); +} + +PropertyInfo qdev_prop_uint64 = { + .name = "uint64", + .type = PROP_TYPE_UINT64, + .size = sizeof(uint64_t), + .parse = parse_uint64, + .print = print_uint64, +}; + +/* --- 64bit hex value --- */ + +static int parse_hex64(DeviceState *dev, Property *prop, const char *str) +{ + uint64_t *ptr = qdev_get_prop_ptr(dev, prop); + char *end; + + *ptr = strtoull(str, &end, 16); + if ((*end != '\0') || (end == str)) { + return -EINVAL; + } + + return 0; +} + +static int print_hex64(DeviceState *dev, Property *prop, char *dest, size_t len) +{ + uint64_t *ptr = qdev_get_prop_ptr(dev, prop); + return snprintf(dest, len, "0x%" PRIx64, *ptr); +} + +PropertyInfo qdev_prop_hex64 = { + .name = "hex64", + .type = PROP_TYPE_UINT64, + .size = sizeof(uint64_t), + .parse = parse_hex64, + .print = print_hex64, +}; + +/* --- string --- */ + +static int parse_string(DeviceState *dev, Property *prop, const char *str) +{ + char **ptr = qdev_get_prop_ptr(dev, prop); + + if (*ptr) + qemu_free(*ptr); + *ptr = qemu_strdup(str); + return 0; +} + +static void free_string(DeviceState *dev, Property *prop) +{ + qemu_free(*(char **)qdev_get_prop_ptr(dev, prop)); +} + +static int print_string(DeviceState *dev, Property *prop, char *dest, size_t len) +{ + char **ptr = qdev_get_prop_ptr(dev, prop); + if (!*ptr) + return snprintf(dest, len, ""); + return snprintf(dest, len, "\"%s\"", *ptr); +} + +PropertyInfo qdev_prop_string = { + .name = "string", + .type = PROP_TYPE_STRING, + .size = sizeof(char*), + .parse = parse_string, + .print = print_string, + .free = free_string, +}; + +/* --- drive --- */ + +static int parse_drive(DeviceState *dev, Property *prop, const char *str) +{ + BlockDriverState **ptr = qdev_get_prop_ptr(dev, prop); + BlockDriverState *bs; + + bs = bdrv_find(str); + if (bs == NULL) + return -ENOENT; + if (bdrv_attach(bs, dev) < 0) + return -EEXIST; + *ptr = bs; + return 0; +} + +static void free_drive(DeviceState *dev, Property *prop) +{ + BlockDriverState **ptr = qdev_get_prop_ptr(dev, prop); + + if (*ptr) { + bdrv_detach(*ptr, dev); + blockdev_auto_del(*ptr); + } +} + +static int print_drive(DeviceState *dev, Property *prop, char *dest, size_t len) +{ + BlockDriverState **ptr = qdev_get_prop_ptr(dev, prop); + return snprintf(dest, len, "%s", + *ptr ? bdrv_get_device_name(*ptr) : ""); +} + +PropertyInfo qdev_prop_drive = { + .name = "drive", + .type = PROP_TYPE_DRIVE, + .size = sizeof(BlockDriverState *), + .parse = parse_drive, + .print = print_drive, + .free = free_drive, +}; + +/* --- character device --- */ + +static int parse_chr(DeviceState *dev, Property *prop, const char *str) +{ + CharDriverState **ptr = qdev_get_prop_ptr(dev, prop); + + *ptr = qemu_chr_find(str); + if (*ptr == NULL) + return -ENOENT; + return 0; +} + +static int print_chr(DeviceState *dev, Property *prop, char *dest, size_t len) +{ + CharDriverState **ptr = qdev_get_prop_ptr(dev, prop); + + if (*ptr && (*ptr)->label) { + return snprintf(dest, len, "%s", (*ptr)->label); + } else { + return snprintf(dest, len, ""); + } +} + +PropertyInfo qdev_prop_chr = { + .name = "chr", + .type = PROP_TYPE_CHR, + .size = sizeof(CharDriverState*), + .parse = parse_chr, + .print = print_chr, +}; + +/* --- netdev device --- */ + +static int parse_netdev(DeviceState *dev, Property *prop, const char *str) +{ + VLANClientState **ptr = qdev_get_prop_ptr(dev, prop); + + *ptr = qemu_find_netdev(str); + if (*ptr == NULL) + return -ENOENT; + if ((*ptr)->peer) { + return -EEXIST; + } + return 0; +} + +static int print_netdev(DeviceState *dev, Property *prop, char *dest, size_t len) +{ + VLANClientState **ptr = qdev_get_prop_ptr(dev, prop); + + if (*ptr && (*ptr)->name) { + return snprintf(dest, len, "%s", (*ptr)->name); + } else { + return snprintf(dest, len, ""); + } +} + +PropertyInfo qdev_prop_netdev = { + .name = "netdev", + .type = PROP_TYPE_NETDEV, + .size = sizeof(VLANClientState*), + .parse = parse_netdev, + .print = print_netdev, +}; + +/* --- vlan --- */ + +static int parse_vlan(DeviceState *dev, Property *prop, const char *str) +{ + VLANState **ptr = qdev_get_prop_ptr(dev, prop); + int id; + + if (sscanf(str, "%d", &id) != 1) + return -EINVAL; + *ptr = qemu_find_vlan(id, 1); + if (*ptr == NULL) + return -ENOENT; + return 0; +} + +static int print_vlan(DeviceState *dev, Property *prop, char *dest, size_t len) +{ + VLANState **ptr = qdev_get_prop_ptr(dev, prop); + + if (*ptr) { + return snprintf(dest, len, "%d", (*ptr)->id); + } else { + return snprintf(dest, len, ""); + } +} + +PropertyInfo qdev_prop_vlan = { + .name = "vlan", + .type = PROP_TYPE_VLAN, + .size = sizeof(VLANClientState*), + .parse = parse_vlan, + .print = print_vlan, +}; + +/* --- pointer --- */ + +/* Not a proper property, just for dirty hacks. TODO Remove it! */ +PropertyInfo qdev_prop_ptr = { + .name = "ptr", + .type = PROP_TYPE_PTR, + .size = sizeof(void*), +}; + +/* --- mac address --- */ + +/* + * accepted syntax versions: + * 01:02:03:04:05:06 + * 01-02-03-04-05-06 + */ +static int parse_mac(DeviceState *dev, Property *prop, const char *str) +{ + MACAddr *mac = qdev_get_prop_ptr(dev, prop); + int i, pos; + char *p; + + for (i = 0, pos = 0; i < 6; i++, pos += 3) { + if (!qemu_isxdigit(str[pos])) + return -EINVAL; + if (!qemu_isxdigit(str[pos+1])) + return -EINVAL; + if (i == 5) { + if (str[pos+2] != '\0') + return -EINVAL; + } else { + if (str[pos+2] != ':' && str[pos+2] != '-') + return -EINVAL; + } + mac->a[i] = strtol(str+pos, &p, 16); + } + return 0; +} + +static int print_mac(DeviceState *dev, Property *prop, char *dest, size_t len) +{ + MACAddr *mac = qdev_get_prop_ptr(dev, prop); + + return snprintf(dest, len, "%02x:%02x:%02x:%02x:%02x:%02x", + mac->a[0], mac->a[1], mac->a[2], + mac->a[3], mac->a[4], mac->a[5]); +} + +PropertyInfo qdev_prop_macaddr = { + .name = "macaddr", + .type = PROP_TYPE_MACADDR, + .size = sizeof(MACAddr), + .parse = parse_mac, + .print = print_mac, +}; + +/* --- pci address --- */ + +/* + * bus-local address, i.e. "$slot" or "$slot.$fn" + */ +static int parse_pci_devfn(DeviceState *dev, Property *prop, const char *str) +{ + uint32_t *ptr = qdev_get_prop_ptr(dev, prop); + unsigned int slot, fn, n; + + if (sscanf(str, "%x.%x%n", &slot, &fn, &n) != 2) { + fn = 0; + if (sscanf(str, "%x%n", &slot, &n) != 1) { + return -EINVAL; + } + } + if (str[n] != '\0') + return -EINVAL; + if (fn > 7) + return -EINVAL; + *ptr = slot << 3 | fn; + return 0; +} + +static int print_pci_devfn(DeviceState *dev, Property *prop, char *dest, size_t len) +{ + uint32_t *ptr = qdev_get_prop_ptr(dev, prop); + + if (*ptr == -1) { + return snprintf(dest, len, ""); + } else { + return snprintf(dest, len, "%02x.%x", *ptr >> 3, *ptr & 7); + } +} + +PropertyInfo qdev_prop_pci_devfn = { + .name = "pci-devfn", + .type = PROP_TYPE_UINT32, + .size = sizeof(uint32_t), + .parse = parse_pci_devfn, + .print = print_pci_devfn, +}; + +/* --- 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) +{ + Property *prop; + + /* device properties */ + prop = qdev_prop_walk(dev->info->props, name); + if (prop) + return prop; + + /* bus properties */ + prop = qdev_prop_walk(dev->parent_bus->info->props, name); + if (prop) + return prop; + + return NULL; +} + +int qdev_prop_exists(DeviceState *dev, const char *name) +{ + return qdev_prop_find(dev, name) ? true : false; +} + +int qdev_prop_parse(DeviceState *dev, const char *name, const char *value) +{ + Property *prop; + int ret; + + prop = qdev_prop_find(dev, name); + /* + * TODO Properties without a parse method are just for dirty + * hacks. qdev_prop_ptr is the only such PropertyInfo. It's + * marked for removal. The test !prop->info->parse should be + * removed along with it. + */ + if (!prop || !prop->info->parse) { + qerror_report(QERR_PROPERTY_NOT_FOUND, dev->info->name, name); + return -1; + } + ret = prop->info->parse(dev, prop, value); + if (ret < 0) { + switch (ret) { + case -EEXIST: + qerror_report(QERR_PROPERTY_VALUE_IN_USE, + dev->info->name, name, value); + break; + default: + case -EINVAL: + qerror_report(QERR_PROPERTY_VALUE_BAD, + dev->info->name, name, value); + break; + case -ENOENT: + qerror_report(QERR_PROPERTY_VALUE_NOT_FOUND, + dev->info->name, name, value); + break; + } + return -1; + } + return 0; +} + +void qdev_prop_set(DeviceState *dev, const char *name, void *src, enum PropertyType type) +{ + Property *prop; + + prop = qdev_prop_find(dev, name); + if (!prop) { + fprintf(stderr, "%s: property \"%s.%s\" not found\n", + __FUNCTION__, dev->info->name, name); + abort(); + } + if (prop->info->type != type) { + fprintf(stderr, "%s: property \"%s.%s\" type mismatch\n", + __FUNCTION__, dev->info->name, name); + abort(); + } + qdev_prop_cpy(dev, prop, src); +} + +void qdev_prop_set_bit(DeviceState *dev, const char *name, bool value) +{ + qdev_prop_set(dev, name, &value, PROP_TYPE_BIT); +} + +void qdev_prop_set_uint8(DeviceState *dev, const char *name, uint8_t value) +{ + qdev_prop_set(dev, name, &value, PROP_TYPE_UINT8); +} + +void qdev_prop_set_uint16(DeviceState *dev, const char *name, uint16_t value) +{ + qdev_prop_set(dev, name, &value, PROP_TYPE_UINT16); +} + +void qdev_prop_set_uint32(DeviceState *dev, const char *name, uint32_t value) +{ + qdev_prop_set(dev, name, &value, PROP_TYPE_UINT32); +} + +void qdev_prop_set_int32(DeviceState *dev, const char *name, int32_t value) +{ + qdev_prop_set(dev, name, &value, PROP_TYPE_INT32); +} + +void qdev_prop_set_uint64(DeviceState *dev, const char *name, uint64_t value) +{ + qdev_prop_set(dev, name, &value, PROP_TYPE_UINT64); +} + +void qdev_prop_set_string(DeviceState *dev, const char *name, char *value) +{ + qdev_prop_set(dev, name, &value, PROP_TYPE_STRING); +} + +int qdev_prop_set_drive(DeviceState *dev, const char *name, BlockDriverState *value) +{ + int res; + + res = bdrv_attach(value, dev); + if (res < 0) { + error_report("Can't attach drive %s to %s.%s: %s", + bdrv_get_device_name(value), + dev->id ? dev->id : dev->info->name, + name, strerror(-res)); + return -1; + } + qdev_prop_set(dev, name, &value, PROP_TYPE_DRIVE); + return 0; +} + +void qdev_prop_set_drive_nofail(DeviceState *dev, const char *name, BlockDriverState *value) +{ + if (qdev_prop_set_drive(dev, name, value) < 0) { + exit(1); + } +} +void qdev_prop_set_chr(DeviceState *dev, const char *name, CharDriverState *value) +{ + qdev_prop_set(dev, name, &value, PROP_TYPE_CHR); +} + +void qdev_prop_set_netdev(DeviceState *dev, const char *name, VLANClientState *value) +{ + qdev_prop_set(dev, name, &value, PROP_TYPE_NETDEV); +} + +void qdev_prop_set_vlan(DeviceState *dev, const char *name, VLANState *value) +{ + qdev_prop_set(dev, name, &value, PROP_TYPE_VLAN); +} + +void qdev_prop_set_macaddr(DeviceState *dev, const char *name, uint8_t *value) +{ + qdev_prop_set(dev, name, value, PROP_TYPE_MACADDR); +} + +void qdev_prop_set_ptr(DeviceState *dev, const char *name, void *value) +{ + qdev_prop_set(dev, name, &value, PROP_TYPE_PTR); +} + +void qdev_prop_set_defaults(DeviceState *dev, Property *props) +{ + if (!props) + return; + while (props->name) { + if (props->defval) { + qdev_prop_cpy(dev, props, props->defval); + } + props++; + } +} + +static QTAILQ_HEAD(, GlobalProperty) global_props = QTAILQ_HEAD_INITIALIZER(global_props); + +static 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); + } +} + +void qdev_prop_set_globals(DeviceState *dev) +{ + GlobalProperty *prop; + + QTAILQ_FOREACH(prop, &global_props, next) { + if (strcmp(dev->info->name, prop->driver) != 0 && + strcmp(dev->info->bus_info->name, prop->driver) != 0) { + continue; + } + if (qdev_prop_parse(dev, prop->property, prop->value) != 0) { + exit(1); + } + } +} + +static int qdev_add_one_global(QemuOpts *opts, void *opaque) +{ + GlobalProperty *g; + + g = qemu_mallocz(sizeof(*g)); + g->driver = qemu_opt_get(opts, "driver"); + g->property = qemu_opt_get(opts, "property"); + g->value = qemu_opt_get(opts, "value"); + qdev_prop_register_global(g); + return 0; +} + +void qemu_add_globals(void) +{ + qemu_opts_foreach(qemu_find_opts("global"), qdev_add_one_global, NULL, 0); +} diff --git a/hw/qdev.c b/hw/qdev.c new file mode 100644 index 0000000..c7fec44 --- /dev/null +++ b/hw/qdev.c @@ -0,0 +1,926 @@ +/* + * 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 . + */ + +/* 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 "net.h" +#include "qdev.h" +#include "sysemu.h" +#include "monitor.h" + +static int qdev_hotplug = 0; +static bool qdev_hot_added = false; +static bool qdev_hot_removed = false; + +/* This is a nasty hack to allow passing a NULL bus to qdev_create. */ +static BusState *main_system_bus; + +DeviceInfo *device_info_list; + +static BusState *qbus_find_recursive(BusState *bus, const char *name, + const BusInfo *info); +static BusState *qbus_find(const char *path); + +/* Register a new device type. */ +void qdev_register(DeviceInfo *info) +{ + assert(info->size >= sizeof(DeviceState)); + assert(!info->next); + + info->next = device_info_list; + device_info_list = info; +} + +static DeviceInfo *qdev_find_info(BusInfo *bus_info, const char *name) +{ + DeviceInfo *info; + + /* first check device names */ + for (info = device_info_list; info != NULL; info = info->next) { + if (bus_info && info->bus_info != bus_info) + continue; + if (strcmp(info->name, name) != 0) + continue; + return info; + } + + /* failing that check the aliases */ + for (info = device_info_list; info != NULL; info = info->next) { + if (bus_info && info->bus_info != bus_info) + continue; + if (!info->alias) + continue; + if (strcmp(info->alias, name) != 0) + continue; + return info; + } + return NULL; +} + +static DeviceState *qdev_create_from_info(BusState *bus, DeviceInfo *info) +{ + DeviceState *dev; + + assert(bus->info == info->bus_info); + dev = qemu_mallocz(info->size); + dev->info = info; + dev->parent_bus = bus; + qdev_prop_set_defaults(dev, dev->info->props); + qdev_prop_set_defaults(dev, dev->parent_bus->info->props); + qdev_prop_set_globals(dev); + QLIST_INSERT_HEAD(&bus->children, dev, sibling); + if (qdev_hotplug) { + assert(bus->allow_hotplug); + dev->hotplugged = 1; + qdev_hot_added = true; + } + dev->instance_id_alias = -1; + dev->state = DEV_STATE_CREATED; + return dev; +} + +/* Create a new device. This only initializes the device state structure + and allows properties to be set. qdev_init should be called to + initialize the actual device emulation. */ +DeviceState *qdev_create(BusState *bus, const char *name) +{ + DeviceInfo *info; + + if (!bus) { + bus = sysbus_get_default(); + } + + info = qdev_find_info(bus->info, name); + if (!info) { + hw_error("Unknown device '%s' for bus '%s'\n", name, bus->info->name); + } + + return qdev_create_from_info(bus, info); +} + +static void qdev_print_devinfo(DeviceInfo *info) +{ + error_printf("name \"%s\", bus %s", + info->name, info->bus_info->name); + if (info->alias) { + error_printf(", alias \"%s\"", info->alias); + } + if (info->desc) { + error_printf(", desc \"%s\"", info->desc); + } + if (info->no_user) { + error_printf(", no-user"); + } + error_printf("\n"); +} + +static int set_property(const char *name, const char *value, void *opaque) +{ + DeviceState *dev = opaque; + + if (strcmp(name, "driver") == 0) + return 0; + if (strcmp(name, "bus") == 0) + return 0; + + if (qdev_prop_parse(dev, name, value) == -1) { + return -1; + } + return 0; +} + +int qdev_device_help(QemuOpts *opts) +{ + const char *driver; + DeviceInfo *info; + Property *prop; + + driver = qemu_opt_get(opts, "driver"); + if (driver && !strcmp(driver, "?")) { + for (info = device_info_list; info != NULL; info = info->next) { + if (info->no_user) { + continue; /* not available, don't show */ + } + qdev_print_devinfo(info); + } + return 1; + } + + if (!qemu_opt_get(opts, "?")) { + return 0; + } + + info = qdev_find_info(NULL, driver); + if (!info) { + return 0; + } + + for (prop = info->props; prop && prop->name; prop++) { + /* + * TODO Properties without a parser are just for dirty hacks. + * qdev_prop_ptr is the only such PropertyInfo. It's marked + * for removal. This conditional should be removed along with + * it. + */ + if (!prop->info->parse) { + continue; /* no way to set it, don't show */ + } + error_printf("%s.%s=%s\n", info->name, prop->name, prop->info->name); + } + return 1; +} + +DeviceState *qdev_device_add(QemuOpts *opts) +{ + const char *driver, *path, *id; + DeviceInfo *info; + DeviceState *qdev; + BusState *bus; + + driver = qemu_opt_get(opts, "driver"); + if (!driver) { + qerror_report(QERR_MISSING_PARAMETER, "driver"); + return NULL; + } + + /* find driver */ + info = qdev_find_info(NULL, driver); + if (!info || info->no_user) { + qerror_report(QERR_INVALID_PARAMETER_VALUE, "driver", "a driver name"); + error_printf_unless_qmp("Try with argument '?' for a list.\n"); + return NULL; + } + + /* find bus */ + path = qemu_opt_get(opts, "bus"); + if (path != NULL) { + bus = qbus_find(path); + if (!bus) { + return NULL; + } + if (bus->info != info->bus_info) { + qerror_report(QERR_BAD_BUS_FOR_DEVICE, + driver, bus->info->name); + return NULL; + } + } else { + bus = qbus_find_recursive(main_system_bus, NULL, info->bus_info); + if (!bus) { + qerror_report(QERR_NO_BUS_FOR_DEVICE, + info->name, info->bus_info->name); + return NULL; + } + } + if (qdev_hotplug && !bus->allow_hotplug) { + qerror_report(QERR_BUS_NO_HOTPLUG, bus->name); + return NULL; + } + + /* create device, set properties */ + qdev = qdev_create_from_info(bus, info); + id = qemu_opts_id(opts); + if (id) { + qdev->id = id; + } + if (qemu_opt_foreach(opts, set_property, qdev, 1) != 0) { + qdev_free(qdev); + return NULL; + } + if (qdev_init(qdev) < 0) { + qerror_report(QERR_DEVICE_INIT_FAILED, driver); + return NULL; + } + qdev->opts = opts; + return qdev; +} + +/* Initialize a device. Device properties should be set before calling + this function. IRQs and MMIO regions should be connected/mapped after + calling this function. + On failure, destroy the device and return negative value. + Return 0 on success. */ +int qdev_init(DeviceState *dev) +{ + int rc; + + assert(dev->state == DEV_STATE_CREATED); + rc = dev->info->init(dev, dev->info); + if (rc < 0) { + qdev_free(dev); + return rc; + } + if (dev->info->vmsd) { + vmstate_register_with_alias_id(dev, -1, dev->info->vmsd, dev, + dev->instance_id_alias, + dev->alias_required_for_version); + } + dev->state = DEV_STATE_INITIALIZED; + return 0; +} + +void qdev_set_legacy_instance_id(DeviceState *dev, int alias_id, + int required_for_version) +{ + assert(dev->state == DEV_STATE_CREATED); + dev->instance_id_alias = alias_id; + dev->alias_required_for_version = required_for_version; +} + +int qdev_unplug(DeviceState *dev) +{ + if (!dev->parent_bus->allow_hotplug) { + qerror_report(QERR_BUS_NO_HOTPLUG, dev->parent_bus->name); + return -1; + } + assert(dev->info->unplug != NULL); + + qdev_hot_removed = true; + + return dev->info->unplug(dev); +} + +static int qdev_reset_one(DeviceState *dev, void *opaque) +{ + if (dev->info->reset) { + dev->info->reset(dev); + } + + return 0; +} + +BusState *sysbus_get_default(void) +{ + if (!main_system_bus) { + main_system_bus = qbus_create(&system_bus_info, NULL, + "main-system-bus"); + } + return main_system_bus; +} + +static int qbus_reset_one(BusState *bus, void *opaque) +{ + if (bus->info->reset) { + return bus->info->reset(bus); + } + return 0; +} + +void qdev_reset_all(DeviceState *dev) +{ + qdev_walk_children(dev, qdev_reset_one, qbus_reset_one, NULL); +} + +void qbus_reset_all_fn(void *opaque) +{ + BusState *bus = opaque; + qbus_walk_children(bus, qdev_reset_one, qbus_reset_one, NULL); +} + +/* can be used as ->unplug() callback for the simple cases */ +int qdev_simple_unplug_cb(DeviceState *dev) +{ + /* just zap it */ + qdev_free(dev); + return 0; +} + +/* Like qdev_init(), but terminate program via hw_error() instead of + returning an error value. 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) +{ + DeviceInfo *info = dev->info; + + if (qdev_init(dev) < 0) { + error_report("Initialization of device %s failed\n", info->name); + exit(1); + } +} + +/* Unlink device from bus and free the structure. */ +void qdev_free(DeviceState *dev) +{ + BusState *bus; + Property *prop; + + if (dev->state == DEV_STATE_INITIALIZED) { + while (dev->num_child_bus) { + bus = QLIST_FIRST(&dev->child_bus); + qbus_free(bus); + } + if (dev->info->vmsd) + vmstate_unregister(dev, dev->info->vmsd, dev); + if (dev->info->exit) + dev->info->exit(dev); + if (dev->opts) + qemu_opts_del(dev->opts); + } + QLIST_REMOVE(dev, sibling); + for (prop = dev->info->props; prop && prop->name; prop++) { + if (prop->info->free) { + prop->info->free(dev, prop); + } + } + qemu_free(dev); +} + +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; +} + +/* Get a character (serial) device interface. */ +CharDriverState *qdev_init_chardev(DeviceState *dev) +{ + static int next_serial; + + /* FIXME: This function needs to go away: use chardev properties! */ + return serial_hds[next_serial++]; +} + +BusState *qdev_get_parent_bus(DeviceState *dev) +{ + return dev->parent_bus; +} + +void qdev_init_gpio_in(DeviceState *dev, qemu_irq_handler handler, int n) +{ + assert(dev->num_gpio_in == 0); + dev->num_gpio_in = n; + dev->gpio_in = qemu_allocate_irqs(handler, dev, n); +} + +void qdev_init_gpio_out(DeviceState *dev, qemu_irq *pins, int n) +{ + assert(dev->num_gpio_out == 0); + dev->num_gpio_out = n; + dev->gpio_out = pins; +} + +qemu_irq qdev_get_gpio_in(DeviceState *dev, int n) +{ + assert(n >= 0 && n < dev->num_gpio_in); + return dev->gpio_in[n]; +} + +void qdev_connect_gpio_out(DeviceState * dev, int n, qemu_irq pin) +{ + assert(n >= 0 && n < dev->num_gpio_out); + dev->gpio_out[n] = pin; +} + +void qdev_set_nic_properties(DeviceState *dev, NICInfo *nd) +{ + qdev_prop_set_macaddr(dev, "mac", nd->macaddr); + if (nd->vlan) + qdev_prop_set_vlan(dev, "vlan", nd->vlan); + if (nd->netdev) + qdev_prop_set_netdev(dev, "netdev", nd->netdev); + if (nd->nvectors != DEV_NVECTORS_UNSPECIFIED && + qdev_prop_exists(dev, "vectors")) { + qdev_prop_set_uint32(dev, "vectors", nd->nvectors); + } +} + +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 *devfn, + qbus_walkerfn *busfn, void *opaque) +{ + DeviceState *dev; + int err; + + if (busfn) { + err = busfn(bus, opaque); + if (err) { + return err; + } + } + + QLIST_FOREACH(dev, &bus->children, sibling) { + err = qdev_walk_children(dev, devfn, busfn, opaque); + if (err < 0) { + return err; + } + } + + return 0; +} + +int qdev_walk_children(DeviceState *dev, qdev_walkerfn *devfn, + qbus_walkerfn *busfn, void *opaque) +{ + BusState *bus; + int err; + + if (devfn) { + err = devfn(dev, opaque); + if (err) { + return err; + } + } + + QLIST_FOREACH(bus, &dev->child_bus, sibling) { + err = qbus_walk_children(bus, devfn, busfn, opaque); + if (err < 0) { + return err; + } + } + + return 0; +} + +static BusState *qbus_find_recursive(BusState *bus, const char *name, + const BusInfo *info) +{ + DeviceState *dev; + BusState *child, *ret; + int match = 1; + + if (name && (strcmp(bus->name, name) != 0)) { + match = 0; + } + if (info && (bus->info != info)) { + match = 0; + } + if (match) { + return bus; + } + + QLIST_FOREACH(dev, &bus->children, sibling) { + QLIST_FOREACH(child, &dev->child_bus, sibling) { + ret = qbus_find_recursive(child, name, info); + if (ret) { + return ret; + } + } + } + return NULL; +} + +DeviceState *qdev_find_recursive(BusState *bus, const char *id) +{ + DeviceState *dev, *ret; + BusState *child; + + QLIST_FOREACH(dev, &bus->children, sibling) { + 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_list_bus(DeviceState *dev) +{ + BusState *child; + const char *sep = " "; + + error_printf("child busses at \"%s\":", + dev->id ? dev->id : dev->info->name); + QLIST_FOREACH(child, &dev->child_bus, sibling) { + error_printf("%s\"%s\"", sep, child->name); + sep = ", "; + } + error_printf("\n"); +} + +static void qbus_list_dev(BusState *bus) +{ + DeviceState *dev; + const char *sep = " "; + + error_printf("devices at \"%s\":", bus->name); + QLIST_FOREACH(dev, &bus->children, sibling) { + error_printf("%s\"%s\"", sep, dev->info->name); + if (dev->id) + error_printf("/\"%s\"", dev->id); + sep = ", "; + } + error_printf("\n"); +} + +static BusState *qbus_find_bus(DeviceState *dev, char *elem) +{ + BusState *child; + + QLIST_FOREACH(child, &dev->child_bus, sibling) { + if (strcmp(child->name, elem) == 0) { + return child; + } + } + return NULL; +} + +static DeviceState *qbus_find_dev(BusState *bus, char *elem) +{ + DeviceState *dev; + + /* + * try to match in order: + * (1) instance id, if present + * (2) driver name + * (3) driver alias, if present + */ + QLIST_FOREACH(dev, &bus->children, sibling) { + if (dev->id && strcmp(dev->id, elem) == 0) { + return dev; + } + } + QLIST_FOREACH(dev, &bus->children, sibling) { + if (strcmp(dev->info->name, elem) == 0) { + return dev; + } + } + QLIST_FOREACH(dev, &bus->children, sibling) { + if (dev->info->alias && strcmp(dev->info->alias, elem) == 0) { + return dev; + } + } + return NULL; +} + +static BusState *qbus_find(const char *path) +{ + DeviceState *dev; + BusState *bus; + char elem[128]; + int pos, len; + + /* find start element */ + if (path[0] == '/') { + bus = main_system_bus; + pos = 0; + } else { + if (sscanf(path, "%127[^/]%n", elem, &len) != 1) { + assert(!path[0]); + elem[0] = len = 0; + } + bus = qbus_find_recursive(main_system_bus, elem, NULL); + if (!bus) { + qerror_report(QERR_BUS_NOT_FOUND, elem); + return NULL; + } + pos = len; + } + + for (;;) { + assert(path[pos] == '/' || !path[pos]); + while (path[pos] == '/') { + pos++; + } + if (path[pos] == '\0') { + return bus; + } + + /* find device */ + if (sscanf(path+pos, "%127[^/]%n", elem, &len) != 1) { + assert(0); + elem[0] = len = 0; + } + pos += len; + dev = qbus_find_dev(bus, elem); + if (!dev) { + qerror_report(QERR_DEVICE_NOT_FOUND, elem); + if (!monitor_cur_is_qmp()) { + qbus_list_dev(bus); + } + return NULL; + } + + assert(path[pos] == '/' || !path[pos]); + while (path[pos] == '/') { + pos++; + } + if (path[pos] == '\0') { + /* last specified element is a device. If it has exactly + * one child bus accept it nevertheless */ + switch (dev->num_child_bus) { + case 0: + qerror_report(QERR_DEVICE_NO_BUS, elem); + return NULL; + case 1: + return QLIST_FIRST(&dev->child_bus); + default: + qerror_report(QERR_DEVICE_MULTIPLE_BUSSES, elem); + if (!monitor_cur_is_qmp()) { + qbus_list_bus(dev); + } + return NULL; + } + } + + /* find bus */ + if (sscanf(path+pos, "%127[^/]%n", elem, &len) != 1) { + assert(0); + elem[0] = len = 0; + } + pos += len; + bus = qbus_find_bus(dev, elem); + if (!bus) { + qerror_report(QERR_BUS_NOT_FOUND, elem); + if (!monitor_cur_is_qmp()) { + qbus_list_bus(dev); + } + return NULL; + } + } +} + +void qbus_create_inplace(BusState *bus, BusInfo *info, + DeviceState *parent, const char *name) +{ + char *buf; + int i,len; + + bus->info = info; + bus->parent = parent; + + if (name) { + /* use supplied name */ + bus->name = qemu_strdup(name); + } else if (parent && parent->id) { + /* parent device has id -> use it for bus name */ + len = strlen(parent->id) + 16; + buf = qemu_malloc(len); + snprintf(buf, len, "%s.%d", parent->id, parent->num_child_bus); + bus->name = buf; + } else { + /* no id -> use lowercase bus type for bus name */ + len = strlen(info->name) + 16; + buf = qemu_malloc(len); + len = snprintf(buf, len, "%s.%d", info->name, + parent ? parent->num_child_bus : 0); + for (i = 0; i < len; i++) + buf[i] = qemu_tolower(buf[i]); + bus->name = buf; + } + + QLIST_INIT(&bus->children); + if (parent) { + QLIST_INSERT_HEAD(&parent->child_bus, bus, sibling); + parent->num_child_bus++; + } else if (bus != main_system_bus) { + /* 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); + } +} + +BusState *qbus_create(BusInfo *info, DeviceState *parent, const char *name) +{ + BusState *bus; + + bus = qemu_mallocz(info->size); + bus->qdev_allocated = 1; + qbus_create_inplace(bus, info, parent, name); + return bus; +} + +void qbus_free(BusState *bus) +{ + DeviceState *dev; + + while ((dev = QLIST_FIRST(&bus->children)) != NULL) { + qdev_free(dev); + } + if (bus->parent) { + QLIST_REMOVE(bus, sibling); + bus->parent->num_child_bus--; + } else { + assert(bus != main_system_bus); /* main_system_bus is never freed */ + qemu_unregister_reset(qbus_reset_all_fn, bus); + } + qemu_free((void*)bus->name); + if (bus->qdev_allocated) { + qemu_free(bus); + } +} + +#define qdev_printf(fmt, ...) monitor_printf(mon, "%*s" fmt, indent, "", ## __VA_ARGS__) +static void qbus_print(Monitor *mon, BusState *bus, int indent); + +static void qdev_print_props(Monitor *mon, DeviceState *dev, Property *props, + const char *prefix, int indent) +{ + char buf[64]; + + if (!props) + return; + while (props->name) { + /* + * TODO Properties without a print method are just for dirty + * hacks. qdev_prop_ptr is the only such PropertyInfo. It's + * marked for removal. The test props->info->print should be + * removed along with it. + */ + if (props->info->print) { + props->info->print(dev, props, buf, sizeof(buf)); + qdev_printf("%s-prop: %s = %s\n", prefix, props->name, buf); + } + props++; + } +} + +static void qdev_print(Monitor *mon, DeviceState *dev, int indent) +{ + BusState *child; + qdev_printf("dev: %s, id \"%s\"\n", dev->info->name, + dev->id ? dev->id : ""); + indent += 2; + if (dev->num_gpio_in) { + qdev_printf("gpio-in %d\n", dev->num_gpio_in); + } + if (dev->num_gpio_out) { + qdev_printf("gpio-out %d\n", dev->num_gpio_out); + } + qdev_print_props(mon, dev, dev->info->props, "dev", indent); + qdev_print_props(mon, dev, dev->parent_bus->info->props, "bus", indent); + if (dev->parent_bus->info->print_dev) + dev->parent_bus->info->print_dev(mon, dev, indent); + QLIST_FOREACH(child, &dev->child_bus, sibling) { + qbus_print(mon, child, indent); + } +} + +static void qbus_print(Monitor *mon, BusState *bus, int indent) +{ + struct DeviceState *dev; + + qdev_printf("bus: %s\n", bus->name); + indent += 2; + qdev_printf("type %s\n", bus->info->name); + QLIST_FOREACH(dev, &bus->children, sibling) { + qdev_print(mon, dev, indent); + } +} +#undef qdev_printf + +void do_info_qtree(Monitor *mon) +{ + if (main_system_bus) + qbus_print(mon, main_system_bus, 0); +} + +void do_info_qdm(Monitor *mon) +{ + DeviceInfo *info; + + for (info = device_info_list; info != NULL; info = info->next) { + qdev_print_devinfo(info); + } +} + +int do_device_add(Monitor *mon, const QDict *qdict, QObject **ret_data) +{ + QemuOpts *opts; + + opts = qemu_opts_from_qdict(qemu_find_opts("device"), qdict); + if (!opts) { + return -1; + } + if (!monitor_cur_is_qmp() && qdev_device_help(opts)) { + qemu_opts_del(opts); + return 0; + } + if (!qdev_device_add(opts)) { + qemu_opts_del(opts); + return -1; + } + return 0; +} + +int do_device_del(Monitor *mon, const QDict *qdict, QObject **ret_data) +{ + const char *id = qdict_get_str(qdict, "id"); + DeviceState *dev; + + dev = qdev_find_recursive(main_system_bus, id); + if (NULL == dev) { + qerror_report(QERR_DEVICE_NOT_FOUND, id); + return -1; + } + return qdev_unplug(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); + if (dev->parent_bus->info->get_fw_dev_path) { + d = dev->parent_bus->info->get_fw_dev_path(dev); + l += snprintf(p + l, size - l, "%s", d); + qemu_free(d); + } else { + l += snprintf(p + l, size - l, "%s", dev->info->name); + } + } + 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 strdup(path); +} diff --git a/hw/qdev.h b/hw/qdev.h new file mode 100644 index 0000000..9808f85 --- /dev/null +++ b/hw/qdev.h @@ -0,0 +1,328 @@ +#ifndef QDEV_H +#define QDEV_H + +#include "hw.h" +#include "qemu-queue.h" +#include "qemu-char.h" +#include "qemu-option.h" + +typedef struct Property Property; + +typedef struct PropertyInfo PropertyInfo; + +typedef struct CompatProperty CompatProperty; + +typedef struct DeviceInfo DeviceInfo; + +typedef struct BusState BusState; + +typedef struct BusInfo BusInfo; + +enum DevState { + DEV_STATE_CREATED = 1, + DEV_STATE_INITIALIZED, +}; + +enum { + DEV_NVECTORS_UNSPECIFIED = -1, +}; + +/* This structure should not be accessed directly. We declare it here + so that it can be embedded in individual device state structures. */ +struct DeviceState { + const char *id; + enum DevState state; + QemuOpts *opts; + int hotplugged; + DeviceInfo *info; + BusState *parent_bus; + int num_gpio_out; + qemu_irq *gpio_out; + int num_gpio_in; + qemu_irq *gpio_in; + QLIST_HEAD(, BusState) child_bus; + int num_child_bus; + QLIST_ENTRY(DeviceState) sibling; + int instance_id_alias; + int alias_required_for_version; +}; + +typedef void (*bus_dev_printfn)(Monitor *mon, DeviceState *dev, int indent); +typedef char *(*bus_get_dev_path)(DeviceState *dev); +/* + * This callback is used to create Open Firmware device path in accordance with + * OF spec http://forthworks.com/standards/of1275.pdf. Indicidual bus bindings + * can be found here http://playground.sun.com/1275/bindings/. + */ +typedef char *(*bus_get_fw_dev_path)(DeviceState *dev); +typedef int (qbus_resetfn)(BusState *bus); + +struct BusInfo { + const char *name; + size_t size; + bus_dev_printfn print_dev; + bus_get_dev_path get_dev_path; + bus_get_fw_dev_path get_fw_dev_path; + qbus_resetfn *reset; + Property *props; +}; + +struct BusState { + DeviceState *parent; + BusInfo *info; + const char *name; + int allow_hotplug; + int qdev_allocated; + QLIST_HEAD(, DeviceState) children; + QLIST_ENTRY(BusState) sibling; +}; + +struct Property { + const char *name; + PropertyInfo *info; + int offset; + int bitnr; + void *defval; +}; + +enum PropertyType { + PROP_TYPE_UNSPEC = 0, + PROP_TYPE_UINT8, + PROP_TYPE_UINT16, + PROP_TYPE_UINT32, + PROP_TYPE_INT32, + PROP_TYPE_UINT64, + PROP_TYPE_TADDR, + PROP_TYPE_MACADDR, + PROP_TYPE_DRIVE, + PROP_TYPE_CHR, + PROP_TYPE_STRING, + PROP_TYPE_NETDEV, + PROP_TYPE_VLAN, + PROP_TYPE_PTR, + PROP_TYPE_BIT, +}; + +struct PropertyInfo { + const char *name; + size_t size; + enum PropertyType type; + int (*parse)(DeviceState *dev, Property *prop, const char *str); + int (*print)(DeviceState *dev, Property *prop, char *dest, size_t len); + void (*free)(DeviceState *dev, Property *prop); +}; + +typedef struct GlobalProperty { + const char *driver; + const char *property; + const char *value; + QTAILQ_ENTRY(GlobalProperty) next; +} GlobalProperty; + +/*** Board API. This should go away once we have a machine config file. ***/ + +DeviceState *qdev_create(BusState *bus, const char *name); +int qdev_device_help(QemuOpts *opts); +DeviceState *qdev_device_add(QemuOpts *opts); +int qdev_init(DeviceState *dev) QEMU_WARN_UNUSED_RESULT; +void qdev_init_nofail(DeviceState *dev); +void qdev_set_legacy_instance_id(DeviceState *dev, int alias_id, + int required_for_version); +int qdev_unplug(DeviceState *dev); +void qdev_free(DeviceState *dev); +int qdev_simple_unplug_cb(DeviceState *dev); +void qdev_machine_creation_done(void); +bool qdev_machine_modified(void); + +qemu_irq qdev_get_gpio_in(DeviceState *dev, int n); +void qdev_connect_gpio_out(DeviceState *dev, int n, qemu_irq pin); + +BusState *qdev_get_child_bus(DeviceState *dev, const char *name); + +/*** Device API. ***/ + +typedef int (*qdev_initfn)(DeviceState *dev, DeviceInfo *info); +typedef int (*qdev_event)(DeviceState *dev); +typedef void (*qdev_resetfn)(DeviceState *dev); + +struct DeviceInfo { + const char *name; + const char *fw_name; + const char *alias; + const char *desc; + size_t size; + Property *props; + int no_user; + + /* callbacks */ + qdev_resetfn reset; + + /* device state */ + const VMStateDescription *vmsd; + + /* Private to qdev / bus. */ + qdev_initfn init; + qdev_event unplug; + qdev_event exit; + BusInfo *bus_info; + struct DeviceInfo *next; +}; +extern DeviceInfo *device_info_list; + +void qdev_register(DeviceInfo *info); + +/* Register device properties. */ +/* GPIO inputs also double as IRQ sinks. */ +void qdev_init_gpio_in(DeviceState *dev, qemu_irq_handler handler, int n); +void qdev_init_gpio_out(DeviceState *dev, qemu_irq *pins, int n); + +CharDriverState *qdev_init_chardev(DeviceState *dev); + +BusState *qdev_get_parent_bus(DeviceState *dev); + +/*** BUS API. ***/ + +DeviceState *qdev_find_recursive(BusState *bus, const char *id); + +/* Returns 0 to walk children, > 0 to skip walk, < 0 to terminate walk. */ +typedef int (qbus_walkerfn)(BusState *bus, void *opaque); +typedef int (qdev_walkerfn)(DeviceState *dev, void *opaque); + +void qbus_create_inplace(BusState *bus, BusInfo *info, + DeviceState *parent, const char *name); +BusState *qbus_create(BusInfo *info, DeviceState *parent, const char *name); +/* Returns > 0 if either devfn or busfn skip walk somewhere in cursion, + * < 0 if either devfn or busfn terminate walk somewhere in cursion, + * 0 otherwise. */ +int qbus_walk_children(BusState *bus, qdev_walkerfn *devfn, + qbus_walkerfn *busfn, void *opaque); +int qdev_walk_children(DeviceState *dev, qdev_walkerfn *devfn, + qbus_walkerfn *busfn, void *opaque); +void qdev_reset_all(DeviceState *dev); +void qbus_reset_all_fn(void *opaque); + +void qbus_free(BusState *bus); + +#define FROM_QBUS(type, dev) DO_UPCAST(type, qbus, dev) + +/* This should go away once we get rid of the NULL bus hack */ +BusState *sysbus_get_default(void); + +/*** monitor commands ***/ + +void do_info_qtree(Monitor *mon); +void do_info_qdm(Monitor *mon); +int do_device_add(Monitor *mon, const QDict *qdict, QObject **ret_data); +int do_device_del(Monitor *mon, const QDict *qdict, QObject **ret_data); + +/*** qdev-properties.c ***/ + +extern PropertyInfo qdev_prop_bit; +extern PropertyInfo qdev_prop_uint8; +extern PropertyInfo qdev_prop_uint16; +extern PropertyInfo qdev_prop_uint32; +extern PropertyInfo qdev_prop_int32; +extern PropertyInfo qdev_prop_uint64; +extern PropertyInfo qdev_prop_hex32; +extern PropertyInfo qdev_prop_hex64; +extern PropertyInfo qdev_prop_string; +extern PropertyInfo qdev_prop_chr; +extern PropertyInfo qdev_prop_ptr; +extern PropertyInfo qdev_prop_macaddr; +extern PropertyInfo qdev_prop_drive; +extern PropertyInfo qdev_prop_netdev; +extern PropertyInfo qdev_prop_vlan; +extern PropertyInfo qdev_prop_pci_devfn; + +#define DEFINE_PROP(_name, _state, _field, _prop, _type) { \ + .name = (_name), \ + .info = &(_prop), \ + .offset = offsetof(_state, _field) \ + + type_check(_type,typeof_field(_state, _field)), \ + } +#define DEFINE_PROP_DEFAULT(_name, _state, _field, _defval, _prop, _type) { \ + .name = (_name), \ + .info = &(_prop), \ + .offset = offsetof(_state, _field) \ + + type_check(_type,typeof_field(_state, _field)), \ + .defval = (_type[]) { _defval }, \ + } +#define DEFINE_PROP_BIT(_name, _state, _field, _bit, _defval) { \ + .name = (_name), \ + .info = &(qdev_prop_bit), \ + .bitnr = (_bit), \ + .offset = offsetof(_state, _field) \ + + type_check(uint32_t,typeof_field(_state, _field)), \ + .defval = (bool[]) { (_defval) }, \ + } + +#define DEFINE_PROP_UINT8(_n, _s, _f, _d) \ + DEFINE_PROP_DEFAULT(_n, _s, _f, _d, qdev_prop_uint8, uint8_t) +#define DEFINE_PROP_UINT16(_n, _s, _f, _d) \ + DEFINE_PROP_DEFAULT(_n, _s, _f, _d, qdev_prop_uint16, uint16_t) +#define DEFINE_PROP_UINT32(_n, _s, _f, _d) \ + DEFINE_PROP_DEFAULT(_n, _s, _f, _d, qdev_prop_uint32, uint32_t) +#define DEFINE_PROP_INT32(_n, _s, _f, _d) \ + DEFINE_PROP_DEFAULT(_n, _s, _f, _d, qdev_prop_int32, int32_t) +#define DEFINE_PROP_UINT64(_n, _s, _f, _d) \ + DEFINE_PROP_DEFAULT(_n, _s, _f, _d, qdev_prop_uint64, uint64_t) +#define DEFINE_PROP_HEX32(_n, _s, _f, _d) \ + DEFINE_PROP_DEFAULT(_n, _s, _f, _d, qdev_prop_hex32, uint32_t) +#define DEFINE_PROP_HEX64(_n, _s, _f, _d) \ + DEFINE_PROP_DEFAULT(_n, _s, _f, _d, qdev_prop_hex64, uint64_t) +#define DEFINE_PROP_PCI_DEVFN(_n, _s, _f, _d) \ + DEFINE_PROP_DEFAULT(_n, _s, _f, _d, qdev_prop_pci_devfn, uint32_t) + +#define DEFINE_PROP_PTR(_n, _s, _f) \ + DEFINE_PROP(_n, _s, _f, qdev_prop_ptr, void*) +#define DEFINE_PROP_CHR(_n, _s, _f) \ + DEFINE_PROP(_n, _s, _f, qdev_prop_chr, CharDriverState*) +#define DEFINE_PROP_STRING(_n, _s, _f) \ + DEFINE_PROP(_n, _s, _f, qdev_prop_string, char*) +#define DEFINE_PROP_NETDEV(_n, _s, _f) \ + DEFINE_PROP(_n, _s, _f, qdev_prop_netdev, VLANClientState*) +#define DEFINE_PROP_VLAN(_n, _s, _f) \ + DEFINE_PROP(_n, _s, _f, qdev_prop_vlan, VLANState*) +#define DEFINE_PROP_DRIVE(_n, _s, _f) \ + DEFINE_PROP(_n, _s, _f, qdev_prop_drive, BlockDriverState *) +#define DEFINE_PROP_MACADDR(_n, _s, _f) \ + DEFINE_PROP(_n, _s, _f, qdev_prop_macaddr, MACAddr) + +#define DEFINE_PROP_END_OF_LIST() \ + {} + +/* Set properties between creation and init. */ +void *qdev_get_prop_ptr(DeviceState *dev, Property *prop); +int qdev_prop_exists(DeviceState *dev, const char *name); +int qdev_prop_parse(DeviceState *dev, const char *name, const char *value); +void qdev_prop_set(DeviceState *dev, const char *name, void *src, enum PropertyType type); +void qdev_prop_set_bit(DeviceState *dev, const char *name, bool value); +void qdev_prop_set_uint8(DeviceState *dev, const char *name, uint8_t value); +void qdev_prop_set_uint16(DeviceState *dev, const char *name, uint16_t value); +void qdev_prop_set_uint32(DeviceState *dev, const char *name, uint32_t value); +void qdev_prop_set_int32(DeviceState *dev, const char *name, int32_t value); +void qdev_prop_set_uint64(DeviceState *dev, const char *name, uint64_t value); +void qdev_prop_set_string(DeviceState *dev, const char *name, char *value); +void qdev_prop_set_chr(DeviceState *dev, const char *name, CharDriverState *value); +void qdev_prop_set_netdev(DeviceState *dev, const char *name, VLANClientState *value); +void qdev_prop_set_vlan(DeviceState *dev, const char *name, VLANState *value); +int qdev_prop_set_drive(DeviceState *dev, const char *name, BlockDriverState *value) QEMU_WARN_UNUSED_RESULT; +void qdev_prop_set_drive_nofail(DeviceState *dev, const char *name, BlockDriverState *value); +void qdev_prop_set_macaddr(DeviceState *dev, const char *name, uint8_t *value); +/* FIXME: Remove opaque pointer properties. */ +void qdev_prop_set_ptr(DeviceState *dev, const char *name, void *value); +void qdev_prop_set_defaults(DeviceState *dev, Property *props); + +void qdev_prop_register_global_list(GlobalProperty *props); +void qdev_prop_set_globals(DeviceState *dev); + +static inline const char *qdev_fw_name(DeviceState *dev) +{ + return dev->info->fw_name ? : dev->info->alias ? : dev->info->name; +} + +char *qdev_get_fw_dev_path(DeviceState *dev); +/* This is a nasty hack to allow passing a NULL bus to qdev_create. */ +extern struct BusInfo system_bus_info; + +#endif diff --git a/hw/qxl-logger.c b/hw/qxl-logger.c new file mode 100644 index 0000000..76f43e6 --- /dev/null +++ b/hw/qxl-logger.c @@ -0,0 +1,248 @@ +/* + * qxl command logging -- for debug purposes + * + * Copyright (C) 2010 Red Hat, Inc. + * + * maintained by Gerd Hoffmann + * + * 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 . + */ + +#include "qxl.h" + +static const char *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 *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 *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 *qxl_surface_cmd[] = { + [ QXL_SURFACE_CMD_CREATE ] = "create", + [ QXL_SURFACE_CMD_DESTROY ] = "destroy", +}; + +static const char *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 *qxl_cursor_cmd[] = { + [ QXL_CURSOR_SET ] = "set", + [ QXL_CURSOR_MOVE ] = "move", + [ QXL_CURSOR_HIDE ] = "hide", + [ QXL_CURSOR_TRAIL ] = "trail", +}; + +static const char *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 *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 void qxl_log_image(PCIQXLDevice *qxl, QXLPHYSICAL addr, int group_id) +{ + QXLImage *image; + QXLImageDescriptor *desc; + + image = qxl_phys2virt(qxl, addr, group_id); + 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, ")"); +} + +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 void qxl_log_cmd_draw_copy(PCIQXLDevice *qxl, QXLCopy *copy, int group_id) +{ + fprintf(stderr, " src %" PRIx64, + copy->src_bitmap); + qxl_log_image(qxl, copy->src_bitmap, group_id); + fprintf(stderr, " area"); + qxl_log_rect(©->src_area); + fprintf(stderr, " rop %d", copy->rop_descriptor); +} + +static void 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: + qxl_log_cmd_draw_copy(qxl, &draw->u.copy, group_id); + break; + } +} + +static void 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: + qxl_log_cmd_draw_copy(qxl, &draw->u.copy, group_id); + break; + } +} + +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); + } +} + +void 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); + 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; + } +} + +void qxl_log_command(PCIQXLDevice *qxl, const char *ring, QXLCommandExt *ext) +{ + bool compat = ext->flags & QXL_COMMAND_FLAG_COMPAT; + void *data; + + if (!qxl->cmdlog) { + return; + } + fprintf(stderr, "qxl-%d/%s:", 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); + switch (ext->cmd.type) { + case QXL_CMD_DRAW: + if (!compat) { + qxl_log_cmd_draw(qxl, data, ext->group_id); + } else { + qxl_log_cmd_draw_compat(qxl, data, ext->group_id); + } + 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"); +} diff --git a/hw/qxl-render.c b/hw/qxl-render.c new file mode 100644 index 0000000..58965e0 --- /dev/null +++ b/hw/qxl-render.c @@ -0,0 +1,226 @@ +/* + * qxl local rendering (aka display on sdl/vnc) + * + * Copyright (C) 2010 Red Hat, Inc. + * + * maintained by Gerd Hoffmann + * + * 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 . + */ + +#include "qxl.h" + +static void qxl_flip(PCIQXLDevice *qxl, QXLRect *rect) +{ + uint8_t *src = qxl->guest_primary.data; + uint8_t *dst = qxl->guest_primary.flipped; + int len, i; + + src += (qxl->guest_primary.surface.height - rect->top - 1) * + qxl->guest_primary.stride; + dst += rect->top * qxl->guest_primary.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.stride; + src -= qxl->guest_primary.stride; + } +} + +void qxl_render_resize(PCIQXLDevice *qxl) +{ + QXLSurfaceCreate *sc = &qxl->guest_primary.surface; + + qxl->guest_primary.stride = 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; + } +} + +void qxl_render_update(PCIQXLDevice *qxl) +{ + VGACommonState *vga = &qxl->vga; + QXLRect dirty[32], update; + void *ptr; + int i; + + if (qxl->guest_primary.resized) { + qxl->guest_primary.resized = 0; + + if (qxl->guest_primary.flipped) { + qemu_free(qxl->guest_primary.flipped); + qxl->guest_primary.flipped = NULL; + } + qemu_free_displaysurface(vga->ds); + + qxl->guest_primary.data = qemu_get_ram_ptr(qxl->vga.vram_offset); + if (qxl->guest_primary.stride < 0) { + /* spice surface is upside down -> need extra buffer to flip */ + qxl->guest_primary.stride = -qxl->guest_primary.stride; + qxl->guest_primary.flipped = qemu_malloc(qxl->guest_primary.surface.width * + qxl->guest_primary.stride); + ptr = qxl->guest_primary.flipped; + } else { + ptr = qxl->guest_primary.data; + } + dprint(qxl, 1, "%s: %dx%d, stride %d, bpp %d, depth %d, flip %s\n", + __FUNCTION__, + qxl->guest_primary.surface.width, + qxl->guest_primary.surface.height, + qxl->guest_primary.stride, + qxl->guest_primary.bytes_pp, + qxl->guest_primary.bits_pp, + qxl->guest_primary.flipped ? "yes" : "no"); + vga->ds->surface = + qemu_create_displaysurface_from(qxl->guest_primary.surface.width, + qxl->guest_primary.surface.height, + qxl->guest_primary.bits_pp, + qxl->guest_primary.stride, + ptr); + dpy_resize(vga->ds); + } + + if (!qxl->guest_primary.commands) { + return; + } + qxl->guest_primary.commands = 0; + + update.left = 0; + update.right = qxl->guest_primary.surface.width; + update.top = 0; + update.bottom = qxl->guest_primary.surface.height; + + memset(dirty, 0, sizeof(dirty)); + qxl->ssd.worker->update_area(qxl->ssd.worker, 0, &update, + dirty, ARRAY_SIZE(dirty), 1); + + for (i = 0; i < ARRAY_SIZE(dirty); i++) { + if (qemu_spice_rect_is_empty(dirty+i)) { + break; + } + if (qxl->guest_primary.flipped) { + qxl_flip(qxl, dirty+i); + } + dpy_update(vga->ds, + dirty[i].left, dirty[i].top, + dirty[i].right - dirty[i].left, + dirty[i].bottom - dirty[i].top); + } +} + +static QEMUCursor *qxl_cursor(PCIQXLDevice *qxl, QXLCursor *cursor) +{ + QEMUCursor *c; + uint8_t *image, *mask; + int 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 = cursor->header.width * cursor->header.height * sizeof(uint32_t); + 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 */ +void qxl_render_cursor(PCIQXLDevice *qxl, QXLCommandExt *ext) +{ + QXLCursorCmd *cmd = qxl_phys2virt(qxl, ext->cmd.data, ext->group_id); + QXLCursor *cursor; + QEMUCursor *c; + int x = -1, y = -1; + + if (!qxl->ssd.ds->mouse_set || !qxl->ssd.ds->cursor_define) { + return; + } + + 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: + x = cmd->u.set.position.x; + y = cmd->u.set.position.y; + cursor = qxl_phys2virt(qxl, cmd->u.set.shape, ext->group_id); + if (cursor->chunk.data_size != cursor->data_size) { + fprintf(stderr, "%s: multiple chunks\n", __FUNCTION__); + return; + } + c = qxl_cursor(qxl, cursor); + if (c == NULL) { + c = cursor_builtin_left_ptr(); + } + qemu_mutex_lock_iothread(); + qxl->ssd.ds->cursor_define(c); + qxl->ssd.ds->mouse_set(x, y, 1); + qemu_mutex_unlock_iothread(); + cursor_put(c); + break; + case QXL_CURSOR_MOVE: + x = cmd->u.position.x; + y = cmd->u.position.y; + qemu_mutex_lock_iothread(); + qxl->ssd.ds->mouse_set(x, y, 1); + qemu_mutex_unlock_iothread(); + break; + } +} diff --git a/hw/qxl.c b/hw/qxl.c new file mode 100644 index 0000000..fe4212b --- /dev/null +++ b/hw/qxl.c @@ -0,0 +1,1531 @@ +/* + * Copyright (C) 2010 Red Hat, Inc. + * + * written by Yaniv Kamay, Izik Eidus, Gerd Hoffmann + * maintained by Gerd Hoffmann + * + * 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 . + */ + +#include + +#include "qemu-common.h" +#include "qemu-timer.h" +#include "qemu-queue.h" +#include "monitor.h" +#include "sysemu.h" + +#include "qxl.h" + +#undef SPICE_RING_PROD_ITEM +#define SPICE_RING_PROD_ITEM(r, ret) { \ + typeof(r) start = r; \ + typeof(r) end = r + 1; \ + uint32_t prod = (r)->prod & SPICE_RING_INDEX_MASK(r); \ + typeof(&(r)->items[prod]) m_item = &(r)->items[prod]; \ + if (!((uint8_t*)m_item >= (uint8_t*)(start) && (uint8_t*)(m_item + 1) <= (uint8_t*)(end))) { \ + abort(); \ + } \ + ret = &m_item->el; \ + } + +#undef SPICE_RING_CONS_ITEM +#define SPICE_RING_CONS_ITEM(r, ret) { \ + typeof(r) start = r; \ + typeof(r) end = r + 1; \ + uint32_t cons = (r)->cons & SPICE_RING_INDEX_MASK(r); \ + typeof(&(r)->items[cons]) m_item = &(r)->items[cons]; \ + if (!((uint8_t*)m_item >= (uint8_t*)(start) && (uint8_t*)(m_item + 1) <= (uint8_t*)(end))) { \ + abort(); \ + } \ + ret = &m_item->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(y_res, x_res, 1), \ + QXL_MODE_16_32(x_res, y_res, 2), \ + QXL_MODE_16_32(y_res, x_res, 3) + +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), +#if VGA_RAM_SIZE >= (16 * 1024 * 1024) + /* these modes need more than 8 MB video memory */ + QXL_MODE_EX(1920, 1200), + QXL_MODE_EX(1920, 1440), + QXL_MODE_EX(2048, 1536), + QXL_MODE_EX(2560, 1440), + QXL_MODE_EX(2560, 1600), +#endif +#if VGA_RAM_SIZE >= (32 * 1024 * 1024) + /* these modes need more than 16 MB video memory */ + QXL_MODE_EX(2560, 2048), + QXL_MODE_EX(2800, 2100), + QXL_MODE_EX(3200, 2400), +#endif +}; + +static PCIQXLDevice *qxl0; + +static void qxl_send_events(PCIQXLDevice *d, uint32_t events); +static void qxl_destroy_primary(PCIQXLDevice *d); +static void qxl_reset_memslots(PCIQXLDevice *d); +static void qxl_reset_surfaces(PCIQXLDevice *d); +static void qxl_ring_set_dirty(PCIQXLDevice *qxl); + +static inline uint32_t msb_mask(uint32_t val) +{ + uint32_t mask; + + do { + mask = ~(val - 1) & val; + val &= ~mask; + } while (mask < val); + + return mask; +} + +static ram_addr_t qxl_rom_size(void) +{ + uint32_t rom_size = sizeof(QXLRom) + sizeof(QXLModes) + sizeof(qxl_modes); + rom_size = MAX(rom_size, TARGET_PAGE_SIZE); + rom_size = msb_mask(rom_size * 2 - 1); + return rom_size; +} + +static void init_qxl_rom(PCIQXLDevice *d) +{ + QXLRom *rom = qemu_get_ram_ptr(d->rom_offset); + QXLModes *modes = (QXLModes *)(rom + 1); + uint32_t ram_header_size; + uint32_t surface0_area_size; + uint32_t num_pages; + uint32_t fb, maxfb = 0; + int i; + + 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(NUM_SURFACES); + + modes->n_modes = cpu_to_le32(ARRAY_SIZE(qxl_modes)); + for (i = 0; i < modes->n_modes; i++) { + fb = qxl_modes[i].y_res * qxl_modes[i].stride; + if (maxfb < fb) { + maxfb = fb; + } + modes->modes[i].id = cpu_to_le32(i); + modes->modes[i].x_res = cpu_to_le32(qxl_modes[i].x_res); + modes->modes[i].y_res = cpu_to_le32(qxl_modes[i].y_res); + modes->modes[i].bits = cpu_to_le32(qxl_modes[i].bits); + modes->modes[i].stride = cpu_to_le32(qxl_modes[i].stride); + modes->modes[i].x_mili = cpu_to_le32(qxl_modes[i].x_mili); + modes->modes[i].y_mili = cpu_to_le32(qxl_modes[i].y_mili); + modes->modes[i].orientation = cpu_to_le32(qxl_modes[i].orientation); + } + if (maxfb < VGA_RAM_SIZE && d->id == 0) + maxfb = VGA_RAM_SIZE; + + ram_header_size = ALIGN(sizeof(QXLRam), 4096); + surface0_area_size = ALIGN(maxfb, 4096); + num_pages = d->vga.vram_size; + num_pages -= ram_header_size; + num_pages -= surface0_area_size; + num_pages = num_pages / TARGET_PAGE_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); + 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->ram->release_ring, item); + *item = 0; + qxl_ring_set_dirty(d); +} + +/* can be called from spice server thread context */ +static void qxl_set_dirty(ram_addr_t addr, ram_addr_t end) +{ + while (addr < end) { + cpu_physical_memory_set_dirty(addr); + addr += TARGET_PAGE_SIZE; + } +} + +static void qxl_rom_set_dirty(PCIQXLDevice *qxl) +{ + ram_addr_t addr = qxl->rom_offset; + qxl_set_dirty(addr, addr + qxl->rom_size); +} + +/* called from spice server thread context only */ +static void qxl_ram_set_dirty(PCIQXLDevice *qxl, void *ptr) +{ + ram_addr_t addr = qxl->vga.vram_offset; + void *base = qxl->vga.vram_ptr; + intptr_t offset; + + offset = ptr - base; + offset &= ~(TARGET_PAGE_SIZE-1); + assert(offset < qxl->vga.vram_size); + qxl_set_dirty(addr + offset, addr + offset + TARGET_PAGE_SIZE); +} + +/* can be called from spice server thread context */ +static void qxl_ring_set_dirty(PCIQXLDevice *qxl) +{ + ram_addr_t addr = qxl->vga.vram_offset + qxl->shadow_rom.ram_header_offset; + ram_addr_t end = qxl->vga.vram_offset + qxl->vga.vram_size; + qxl_set_dirty(addr, end); +} + +/* + * keep track of some command state, for savevm/loadvm. + * called from spice server thread context only + */ +static void 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); + uint32_t id = le32_to_cpu(cmd->surface_id); + PANIC_ON(id >= NUM_SURFACES); + 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--; + } + break; + } + case QXL_CMD_CURSOR: + { + QXLCursorCmd *cmd = qxl_phys2virt(qxl, ext->cmd.data, ext->group_id); + if (cmd->type == QXL_CURSOR_SET) { + qxl->guest_cursor = ext->cmd.data; + } + break; + } + } +} + +/* spice display interface callbacks */ + +static void interface_attach_worker(QXLInstance *sin, QXLWorker *qxl_worker) +{ + PCIQXLDevice *qxl = container_of(sin, PCIQXLDevice, ssd.qxl); + + dprint(qxl, 1, "%s:\n", __FUNCTION__); + qxl->ssd.worker = qxl_worker; +} + +static void interface_set_compression_level(QXLInstance *sin, int level) +{ + PCIQXLDevice *qxl = container_of(sin, PCIQXLDevice, ssd.qxl); + + dprint(qxl, 1, "%s: %d\n", __FUNCTION__, 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); + + 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); + + dprint(qxl, 1, "%s:\n", __FUNCTION__); + 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) << TARGET_PAGE_BITS; + info->n_surfaces = NUM_SURFACES; +} + +/* 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; + + switch (qxl->mode) { + case QXL_MODE_VGA: + dprint(qxl, 2, "%s: vga\n", __FUNCTION__); + update = qemu_spice_create_update(&qxl->ssd); + if (update == NULL) { + return false; + } + *ext = update->ext; + qxl_log_command(qxl, "vga", ext); + return true; + case QXL_MODE_COMPAT: + case QXL_MODE_NATIVE: + case QXL_MODE_UNDEFINED: + dprint(qxl, 2, "%s: %s\n", __FUNCTION__, + qxl->cmdflags ? "compat" : "native"); + ring = &qxl->ram->cmd_ring; + if (SPICE_RING_IS_EMPTY(ring)) { + return false; + } + SPICE_RING_CONS_ITEM(ring, cmd); + 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); + 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; + + 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); + dprint(d, 2, "free: push %d items, notify %s, ring %d/%d [%d,%d]\n", + d->num_free_res, notify ? "yes" : "no", + ring->prod - ring->cons, ring->num_items, + ring->prod, ring->cons); + if (notify) { + qxl_send_events(d, QXL_INTERRUPT_DISPLAY); + } + SPICE_RING_PROD_ITEM(ring, item); + *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, + struct 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 */ + qemu_spice_destroy_update(&qxl->ssd, (void*)ext.info->id); + return; + } + + /* + * ext->info points into guest-visible memory + * pci bar 0, $command.release_info + */ + ring = &qxl->ram->release_ring; + SPICE_RING_PROD_ITEM(ring, item); + 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++; + dprint(qxl, 3, "%4d\r", 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; + + 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(ring, cmd); + 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); + } + 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; + + 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) +{ + fprintf(stderr, "%s: abort()\n", __FUNCTION__); + abort(); +} + +/* called from spice server thread context only */ +static int interface_flush_resources(QXLInstance *sin) +{ + PCIQXLDevice *qxl = container_of(sin, PCIQXLDevice, ssd.qxl); + int ret; + + dprint(qxl, 1, "free: guest flush (have %d)\n", qxl->num_free_res); + ret = qxl->num_free_res; + if (ret) { + qxl_push_free_res(qxl, 1); + } + return ret; +} + +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, +}; + +static void qxl_enter_vga_mode(PCIQXLDevice *d) +{ + if (d->mode == QXL_MODE_VGA) { + return; + } + dprint(d, 1, "%s\n", __FUNCTION__); + qemu_spice_create_host_primary(&d->ssd); + d->mode = QXL_MODE_VGA; + memset(&d->ssd.dirty, 0, sizeof(d->ssd.dirty)); +} + +static void qxl_exit_vga_mode(PCIQXLDevice *d) +{ + if (d->mode != QXL_MODE_VGA) { + return; + } + dprint(d, 1, "%s\n", __FUNCTION__); + qxl_destroy_primary(d); +} + +static void qxl_set_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); + qemu_set_irq(d->pci.irq[0], level); + qxl_ring_set_dirty(d); +} + +static void qxl_write_config(PCIDevice *d, uint32_t address, + uint32_t val, int len) +{ + PCIQXLDevice *qxl = DO_UPCAST(PCIQXLDevice, pci, d); + VGACommonState *vga = &qxl->vga; + + vga_dirty_log_stop(vga); + pci_default_write_config(d, address, val, len); + if (vga->map_addr && qxl->pci.io_regions[0].addr == -1) { + vga->map_addr = 0; + } + vga_dirty_log_start(vga); +} + +static void qxl_check_state(PCIQXLDevice *d) +{ + QXLRam *ram = d->ram; + + assert(SPICE_RING_IS_EMPTY(&ram->cmd_ring)); + assert(SPICE_RING_IS_EMPTY(&ram->cursor_ring)); +} + +static void qxl_reset_state(PCIQXLDevice *d) +{ + QXLRam *ram = d->ram; + QXLRom *rom = d->rom; + + assert(SPICE_RING_IS_EMPTY(&ram->cmd_ring)); + assert(SPICE_RING_IS_EMPTY(&ram->cursor_ring)); + 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)); +} + +static void qxl_soft_reset(PCIQXLDevice *d) +{ + dprint(d, 1, "%s:\n", __FUNCTION__); + qxl_check_state(d); + + if (d->id == 0) { + qxl_enter_vga_mode(d); + } else { + d->mode = QXL_MODE_UNDEFINED; + } +} + +static void qxl_hard_reset(PCIQXLDevice *d, int loadvm) +{ + dprint(d, 1, "%s: start%s\n", __FUNCTION__, + loadvm ? " (loadvm)" : ""); + + qemu_mutex_unlock_iothread(); + d->ssd.worker->reset_cursor(d->ssd.worker); + d->ssd.worker->reset_image_cache(d->ssd.worker); + qemu_mutex_lock_iothread(); + 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); + + dprint(d, 1, "%s: done\n", __FUNCTION__); +} + +static void qxl_reset_handler(DeviceState *dev) +{ + PCIQXLDevice *d = DO_UPCAST(PCIQXLDevice, pci.qdev, 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); + + if (qxl->mode != QXL_MODE_VGA) { + dprint(qxl, 1, "%s\n", __FUNCTION__); + qxl_destroy_primary(qxl); + qxl_soft_reset(qxl); + } + vga_ioport_write(opaque, addr, val); +} + +static void qxl_add_memslot(PCIQXLDevice *d, uint32_t slot_id, uint64_t delta) +{ + static const int regions[] = { + QXL_RAM_RANGE_INDEX, + QXL_VRAM_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); + + dprint(d, 1, "%s: slot %d: guest phys 0x%" PRIx64 " - 0x%" PRIx64 "\n", + __FUNCTION__, slot_id, + guest_start, guest_end); + + PANIC_ON(slot_id >= NUM_MEMSLOTS); + PANIC_ON(guest_start > guest_end); + + 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; + } + PANIC_ON(i == ARRAY_SIZE(regions)); /* finished loop without match */ + + switch (pci_region) { + case QXL_RAM_RANGE_INDEX: + virt_start = (intptr_t)qemu_get_ram_ptr(d->vga.vram_offset); + break; + case QXL_VRAM_RANGE_INDEX: + virt_start = (intptr_t)qemu_get_ram_ptr(d->vram_offset); + break; + default: + /* should not happen */ + abort(); + } + + 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); + + dprint(d, 1, "%s: slot %d: host virt 0x%" PRIx64 " - 0x%" PRIx64 "\n", + __FUNCTION__, memslot.slot_id, + memslot.virt_start, memslot.virt_end); + + d->ssd.worker->add_memslot(d->ssd.worker, &memslot); + 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; +} + +static void qxl_del_memslot(PCIQXLDevice *d, uint32_t slot_id) +{ + dprint(d, 1, "%s: slot %d\n", __FUNCTION__, slot_id); + d->ssd.worker->del_memslot(d->ssd.worker, MEMSLOT_GROUP_HOST, slot_id); + d->guest_slots[slot_id].active = 0; +} + +static void qxl_reset_memslots(PCIQXLDevice *d) +{ + dprint(d, 1, "%s:\n", __FUNCTION__); + d->ssd.worker->reset_memslots(d->ssd.worker); + memset(&d->guest_slots, 0, sizeof(d->guest_slots)); +} + +static void qxl_reset_surfaces(PCIQXLDevice *d) +{ + dprint(d, 1, "%s:\n", __FUNCTION__); + d->mode = QXL_MODE_UNDEFINED; + qemu_mutex_unlock_iothread(); + d->ssd.worker->destroy_surfaces(d->ssd.worker); + qemu_mutex_lock_iothread(); + memset(&d->guest_surfaces.cmds, 0, sizeof(d->guest_surfaces.cmds)); +} + +/* called from spice server thread context only */ +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*)offset; + case MEMSLOT_GROUP_GUEST: + PANIC_ON(slot > NUM_MEMSLOTS); + PANIC_ON(!qxl->guest_slots[slot].active); + PANIC_ON(offset < qxl->guest_slots[slot].delta); + offset -= qxl->guest_slots[slot].delta; + PANIC_ON(offset > qxl->guest_slots[slot].size) + return qxl->guest_slots[slot].ptr + offset; + default: + PANIC_ON(1); + } +} + +static void qxl_create_guest_primary(PCIQXLDevice *qxl, int loadvm) +{ + QXLDevSurfaceCreate surface; + QXLSurfaceCreate *sc = &qxl->guest_primary.surface; + + assert(qxl->mode != QXL_MODE_NATIVE); + qxl_exit_vga_mode(qxl); + + dprint(qxl, 1, "%s: %dx%d\n", __FUNCTION__, + le32_to_cpu(sc->width), le32_to_cpu(sc->height)); + + 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); + + 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; + qxl->ssd.worker->create_primary_surface(qxl->ssd.worker, 0, &surface); + + /* for local rendering */ + qxl_render_resize(qxl); +} + +static void qxl_destroy_primary(PCIQXLDevice *d) +{ + if (d->mode == QXL_MODE_UNDEFINED) { + return; + } + + dprint(d, 1, "%s\n", __FUNCTION__); + + d->mode = QXL_MODE_UNDEFINED; + qemu_mutex_unlock_iothread(); + d->ssd.worker->destroy_primary_surface(d->ssd.worker, 0); + qemu_mutex_lock_iothread(); +} + +static void qxl_set_mode(PCIQXLDevice *d, 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 + }; + 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, + }; + + dprint(d, 1, "%s: mode %d [ %d x %d @ %d bpp devmem 0x%lx ]\n", __FUNCTION__, + modenr, mode->x_res, mode->y_res, mode->bits, devmem); + if (!loadvm) { + qxl_hard_reset(d, 0); + } + + d->guest_slots[0].slot = slot; + qxl_add_memslot(d, 0, devmem); + + d->guest_primary.surface = surface; + qxl_create_guest_primary(d, 0); + + d->mode = QXL_MODE_COMPAT; + d->cmdflags = QXL_COMMAND_FLAG_COMPAT; +#ifdef QXL_COMMAND_FLAG_COMPAT_16BPP /* new in spice 0.6.1 */ + if (mode->bits == 16) { + d->cmdflags |= QXL_COMMAND_FLAG_COMPAT_16BPP; + } +#endif + 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, uint32_t addr, uint32_t val) +{ + PCIQXLDevice *d = opaque; + uint32_t io_port = addr - d->io_base; + + 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: + break; + default: + if (d->mode == QXL_MODE_NATIVE || d->mode == QXL_MODE_COMPAT) + break; + dprint(d, 1, "%s: unexpected port 0x%x in vga mode\n", __FUNCTION__, io_port); + return; + } + + switch (io_port) { + case QXL_IO_UPDATE_AREA: + { + QXLRect update = d->ram->update_area; + qemu_mutex_unlock_iothread(); + d->ssd.worker->update_area(d->ssd.worker, d->ram->update_surface, + &update, NULL, 0, 0); + qemu_mutex_lock_iothread(); + break; + } + case QXL_IO_NOTIFY_CMD: + d->ssd.worker->wakeup(d->ssd.worker); + break; + case QXL_IO_NOTIFY_CURSOR: + d->ssd.worker->wakeup(d->ssd.worker); + break; + case QXL_IO_UPDATE_IRQ: + qxl_set_irq(d); + break; + case QXL_IO_NOTIFY_OOM: + if (!SPICE_RING_IS_EMPTY(&d->ram->release_ring)) { + break; + } + pthread_yield(); + if (!SPICE_RING_IS_EMPTY(&d->ram->release_ring)) { + break; + } + d->oom_running = 1; + d->ssd.worker->oom(d->ssd.worker); + d->oom_running = 0; + break; + case QXL_IO_SET_MODE: + dprint(d, 1, "QXL_SET_MODE %d\n", val); + qxl_set_mode(d, val, 0); + break; + case QXL_IO_LOG: + if (d->guestdebug) { + fprintf(stderr, "qxl/guest: %s", d->ram->log_buf); + } + break; + case QXL_IO_RESET: + dprint(d, 1, "QXL_IO_RESET\n"); + qxl_hard_reset(d, 0); + break; + case QXL_IO_MEMSLOT_ADD: + PANIC_ON(val >= NUM_MEMSLOTS); + PANIC_ON(d->guest_slots[val].active); + d->guest_slots[val].slot = d->ram->mem_slot; + qxl_add_memslot(d, val, 0); + break; + case QXL_IO_MEMSLOT_DEL: + qxl_del_memslot(d, val); + break; + case QXL_IO_CREATE_PRIMARY: + PANIC_ON(val != 0); + dprint(d, 1, "QXL_IO_CREATE_PRIMARY\n"); + d->guest_primary.surface = d->ram->create_surface; + qxl_create_guest_primary(d, 0); + break; + case QXL_IO_DESTROY_PRIMARY: + PANIC_ON(val != 0); + dprint(d, 1, "QXL_IO_DESTROY_PRIMARY\n"); + qxl_destroy_primary(d); + break; + case QXL_IO_DESTROY_SURFACE_WAIT: + d->ssd.worker->destroy_surface_wait(d->ssd.worker, val); + break; + case QXL_IO_DESTROY_ALL_SURFACES: + d->ssd.worker->destroy_surfaces(d->ssd.worker); + break; + default: + fprintf(stderr, "%s: ioport=0x%x, abort()\n", __FUNCTION__, io_port); + abort(); + } +} + +static uint32_t ioport_read(void *opaque, uint32_t addr) +{ + PCIQXLDevice *d = opaque; + + dprint(d, 1, "%s: unexpected\n", __FUNCTION__); + return 0xff; +} + +static void qxl_map(PCIDevice *pci, int region_num, + pcibus_t addr, pcibus_t size, int type) +{ + static const char *names[] = { + [ QXL_IO_RANGE_INDEX ] = "ioports", + [ QXL_RAM_RANGE_INDEX ] = "devram", + [ QXL_ROM_RANGE_INDEX ] = "rom", + [ QXL_VRAM_RANGE_INDEX ] = "vram", + }; + PCIQXLDevice *qxl = DO_UPCAST(PCIQXLDevice, pci, pci); + + dprint(qxl, 1, "%s: bar %d [%s] addr 0x%lx size 0x%lx\n", __FUNCTION__, + region_num, names[region_num], addr, size); + + switch (region_num) { + case QXL_IO_RANGE_INDEX: + register_ioport_write(addr, size, 1, ioport_write, pci); + register_ioport_read(addr, size, 1, ioport_read, pci); + qxl->io_base = addr; + break; + case QXL_RAM_RANGE_INDEX: + cpu_register_physical_memory(addr, size, qxl->vga.vram_offset | IO_MEM_RAM); + qxl->vga.map_addr = addr; + qxl->vga.map_end = addr + size; + if (qxl->id == 0) { + vga_dirty_log_start(&qxl->vga); + } + break; + case QXL_ROM_RANGE_INDEX: + cpu_register_physical_memory(addr, size, qxl->rom_offset | IO_MEM_ROM); + break; + case QXL_VRAM_RANGE_INDEX: + cpu_register_physical_memory(addr, size, qxl->vram_offset | IO_MEM_RAM); + break; + } +} + +static void pipe_read(void *opaque) +{ + PCIQXLDevice *d = opaque; + char dummy; + int len; + + do { + len = read(d->pipe[0], &dummy, sizeof(dummy)); + } while (len == sizeof(dummy)); + qxl_set_irq(d); +} + +/* called from spice server thread context only */ +static void qxl_send_events(PCIQXLDevice *d, uint32_t events) +{ + uint32_t old_pending; + uint32_t le_events = cpu_to_le32(events); + + assert(d->ssd.running); + old_pending = __sync_fetch_and_or(&d->ram->int_pending, le_events); + if ((old_pending & le_events) == le_events) { + return; + } + if (pthread_self() == d->main) { + qxl_set_irq(d); + } else { + if (write(d->pipe[1], d, 1) != 1) { + dprint(d, 1, "%s: write to pipe failed\n", __FUNCTION__); + } + } +} + +static void init_pipe_signaling(PCIQXLDevice *d) +{ + if (pipe(d->pipe) < 0) { + dprint(d, 1, "%s: pipe creation failed\n", __FUNCTION__); + return; + } +#ifdef CONFIG_IOTHREAD + fcntl(d->pipe[0], F_SETFL, O_NONBLOCK); +#else + fcntl(d->pipe[0], F_SETFL, O_NONBLOCK /* | O_ASYNC */); +#endif + fcntl(d->pipe[1], F_SETFL, O_NONBLOCK); + fcntl(d->pipe[0], F_SETOWN, getpid()); + + d->main = pthread_self(); + qemu_set_fd_handler(d->pipe[0], pipe_read, NULL, d); +} + +/* graphics console */ + +static void qxl_hw_update(void *opaque) +{ + PCIQXLDevice *qxl = opaque; + VGACommonState *vga = &qxl->vga; + + switch (qxl->mode) { + case QXL_MODE_VGA: + vga->update(vga); + break; + case QXL_MODE_COMPAT: + case QXL_MODE_NATIVE: + qxl_render_update(qxl); + break; + default: + break; + } +} + +static void qxl_hw_invalidate(void *opaque) +{ + PCIQXLDevice *qxl = opaque; + VGACommonState *vga = &qxl->vga; + + vga->invalidate(vga); +} + +static void qxl_hw_screen_dump(void *opaque, const char *filename) +{ + PCIQXLDevice *qxl = opaque; + VGACommonState *vga = &qxl->vga; + + switch (qxl->mode) { + case QXL_MODE_COMPAT: + case QXL_MODE_NATIVE: + qxl_render_update(qxl); + ppm_save(filename, qxl->ssd.ds->surface); + break; + case QXL_MODE_VGA: + vga->screen_dump(vga, filename); + break; + default: + break; + } +} + +static void qxl_hw_text_update(void *opaque, console_ch_t *chardata) +{ + PCIQXLDevice *qxl = opaque; + VGACommonState *vga = &qxl->vga; + + if (qxl->mode == QXL_MODE_VGA) { + vga->text_update(vga, chardata); + return; + } +} + +static void qxl_vm_change_state_handler(void *opaque, int running, int reason) +{ + PCIQXLDevice *qxl = opaque; + qemu_spice_vm_change_state_handler(&qxl->ssd, running, reason); + + if (!running && qxl->mode == QXL_MODE_NATIVE) { + /* dirty all vram (which holds surfaces) to make sure it is saved */ + /* FIXME #1: should go out during "live" stage */ + /* FIXME #2: we only need to save the areas which are actually used */ + ram_addr_t addr = qxl->vram_offset; + qxl_set_dirty(addr, addr + qxl->vram_size); + } +} + +/* display change listener */ + +static void display_update(struct DisplayState *ds, int x, int y, int w, int h) +{ + if (qxl0->mode == QXL_MODE_VGA) { + qemu_spice_display_update(&qxl0->ssd, x, y, w, h); + } +} + +static void display_resize(struct DisplayState *ds) +{ + if (qxl0->mode == QXL_MODE_VGA) { + qemu_spice_display_resize(&qxl0->ssd); + } +} + +static void display_refresh(struct DisplayState *ds) +{ + if (qxl0->mode == QXL_MODE_VGA) { + qemu_spice_display_refresh(&qxl0->ssd); + } +} + +static DisplayChangeListener display_listener = { + .dpy_update = display_update, + .dpy_resize = display_resize, + .dpy_refresh = display_refresh, +}; + +static int qxl_init_common(PCIQXLDevice *qxl) +{ + uint8_t* config = qxl->pci.config; + uint32_t pci_device_id; + uint32_t pci_device_rev; + uint32_t io_size; + + qxl->mode = QXL_MODE_UNDEFINED; + qxl->generation = 1; + qxl->num_memslots = NUM_MEMSLOTS; + qxl->num_surfaces = NUM_SURFACES; + + switch (qxl->revision) { + case 1: /* spice 0.4 -- qxl-1 */ + pci_device_id = QXL_DEVICE_ID_STABLE; + pci_device_rev = QXL_REVISION_STABLE_V04; + break; + case 2: /* spice 0.6 -- qxl-2 */ + pci_device_id = QXL_DEVICE_ID_STABLE; + pci_device_rev = QXL_REVISION_STABLE_V06; + break; + default: /* experimental */ + pci_device_id = QXL_DEVICE_ID_DEVEL; + pci_device_rev = 1; + break; + } + + pci_config_set_vendor_id(config, REDHAT_PCI_VENDOR_ID); + pci_config_set_device_id(config, pci_device_id); + pci_set_byte(&config[PCI_REVISION_ID], pci_device_rev); + pci_set_byte(&config[PCI_INTERRUPT_PIN], 1); + + qxl->rom_size = qxl_rom_size(); + qxl->rom_offset = qemu_ram_alloc(&qxl->pci.qdev, "qxl.vrom", qxl->rom_size); + init_qxl_rom(qxl); + init_qxl_ram(qxl); + + if (qxl->vram_size < 16 * 1024 * 1024) { + qxl->vram_size = 16 * 1024 * 1024; + } + if (qxl->revision == 1) { + qxl->vram_size = 4096; + } + qxl->vram_size = msb_mask(qxl->vram_size * 2 - 1); + qxl->vram_offset = qemu_ram_alloc(&qxl->pci.qdev, "qxl.vram", qxl->vram_size); + + io_size = msb_mask(QXL_IO_RANGE_SIZE * 2 - 1); + if (qxl->revision == 1) { + io_size = 8; + } + + pci_register_bar(&qxl->pci, QXL_IO_RANGE_INDEX, + io_size, PCI_BASE_ADDRESS_SPACE_IO, qxl_map); + + pci_register_bar(&qxl->pci, QXL_ROM_RANGE_INDEX, + qxl->rom_size, PCI_BASE_ADDRESS_SPACE_MEMORY, + qxl_map); + + pci_register_bar(&qxl->pci, QXL_RAM_RANGE_INDEX, + qxl->vga.vram_size, PCI_BASE_ADDRESS_SPACE_MEMORY, + qxl_map); + + pci_register_bar(&qxl->pci, QXL_VRAM_RANGE_INDEX, qxl->vram_size, + PCI_BASE_ADDRESS_SPACE_MEMORY, qxl_map); + + qxl->ssd.qxl.base.sif = &qxl_interface.base; + qxl->ssd.qxl.id = qxl->id; + qemu_spice_add_interface(&qxl->ssd.qxl.base); + qemu_add_vm_change_state_handler(qxl_vm_change_state_handler, qxl); + + init_pipe_signaling(qxl); + qxl_reset_state(qxl); + + return 0; +} + +static int qxl_init_primary(PCIDevice *dev) +{ + PCIQXLDevice *qxl = DO_UPCAST(PCIQXLDevice, pci, dev); + VGACommonState *vga = &qxl->vga; + ram_addr_t ram_size = msb_mask(qxl->vga.vram_size * 2 - 1); + + qxl->id = 0; + + if (ram_size < 32 * 1024 * 1024) { + ram_size = 32 * 1024 * 1024; + } + vga_common_init(vga, ram_size); + vga_init(vga); + register_ioport_write(0x3c0, 16, 1, qxl_vga_ioport_write, vga); + register_ioport_write(0x3b4, 2, 1, qxl_vga_ioport_write, vga); + register_ioport_write(0x3d4, 2, 1, qxl_vga_ioport_write, vga); + register_ioport_write(0x3ba, 1, 1, qxl_vga_ioport_write, vga); + register_ioport_write(0x3da, 1, 1, qxl_vga_ioport_write, vga); + + vga->ds = graphic_console_init(qxl_hw_update, qxl_hw_invalidate, + qxl_hw_screen_dump, qxl_hw_text_update, qxl); + qxl->ssd.ds = vga->ds; + qxl->ssd.bufsize = (16 * 1024 * 1024); + qxl->ssd.buf = qemu_malloc(qxl->ssd.bufsize); + + qxl0 = qxl; + register_displaychangelistener(vga->ds, &display_listener); + + pci_config_set_class(dev->config, PCI_CLASS_DISPLAY_VGA); + return qxl_init_common(qxl); +} + +static int qxl_init_secondary(PCIDevice *dev) +{ + static int device_id = 1; + PCIQXLDevice *qxl = DO_UPCAST(PCIQXLDevice, pci, dev); + ram_addr_t ram_size = msb_mask(qxl->vga.vram_size * 2 - 1); + + qxl->id = device_id++; + + if (ram_size < 16 * 1024 * 1024) { + ram_size = 16 * 1024 * 1024; + } + qxl->vga.vram_size = ram_size; + qxl->vga.vram_offset = qemu_ram_alloc(&qxl->pci.qdev, "qxl.vgavram", + qxl->vga.vram_size); + qxl->vga.vram_ptr = qemu_get_ram_ptr(qxl->vga.vram_offset); + + pci_config_set_class(dev->config, PCI_CLASS_DISPLAY_OTHER); + return qxl_init_common(qxl); +} + +static void qxl_pre_save(void *opaque) +{ + PCIQXLDevice* d = opaque; + uint8_t *ram_start = d->vga.vram_ptr; + + dprint(d, 1, "%s:\n", __FUNCTION__); + 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; + + dprint(d, 1, "%s: start\n", __FUNCTION__); + qxl_hard_reset(d, 1); + qxl_exit_vga_mode(d); + dprint(d, 1, "%s: done\n", __FUNCTION__); + return 0; +} + +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, i, newmode; + + dprint(d, 1, "%s: start\n", __FUNCTION__); + + 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); + + dprint(d, 1, "%s: restore mode\n", __FUNCTION__); + newmode = d->mode; + d->mode = QXL_MODE_UNDEFINED; + switch (newmode) { + case QXL_MODE_UNDEFINED: + break; + case QXL_MODE_VGA: + qxl_enter_vga_mode(d); + break; + case QXL_MODE_NATIVE: + for (i = 0; i < NUM_MEMSLOTS; i++) { + if (!d->guest_slots[i].active) { + continue; + } + qxl_add_memslot(d, i, 0); + } + qxl_create_guest_primary(d, 1); + + /* replay surface-create and cursor-set commands */ + cmds = qemu_mallocz(sizeof(QXLCommandExt) * (NUM_SURFACES + 1)); + for (in = 0, out = 0; in < 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++; + } + cmds[out].cmd.data = d->guest_cursor; + cmds[out].cmd.type = QXL_CMD_CURSOR; + cmds[out].group_id = MEMSLOT_GROUP_GUEST; + out++; + d->ssd.worker->loadvm_commands(d->ssd.worker, cmds, out); + qemu_free(cmds); + + break; + case QXL_MODE_COMPAT: + qxl_set_mode(d, d->shadow_rom.mode, 1); + break; + } + dprint(d, 1, "%s: done\n", __FUNCTION__); + + return 0; +} + +#define QXL_SAVE_VERSION 21 + +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 = { + .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(num_surfaces, PCIQXLDevice), + VMSTATE_ARRAY(guest_surfaces.cmds, PCIQXLDevice, NUM_SURFACES, 0, + vmstate_info_uint64, uint64_t), + VMSTATE_UINT64(guest_cursor, PCIQXLDevice), + VMSTATE_END_OF_LIST() + }, +}; + +static PCIDeviceInfo qxl_info_primary = { + .qdev.name = "qxl-vga", + .qdev.desc = "Spice QXL GPU (primary, vga compatible)", + .qdev.size = sizeof(PCIQXLDevice), + .qdev.reset = qxl_reset_handler, + .qdev.vmsd = &qxl_vmstate, + .no_hotplug = 1, + .init = qxl_init_primary, + .config_write = qxl_write_config, + .romfile = "vgabios-qxl.bin", + .qdev.props = (Property[]) { + DEFINE_PROP_UINT32("ram_size", PCIQXLDevice, vga.vram_size, 64 * 1024 * 1024), + DEFINE_PROP_UINT32("vram_size", PCIQXLDevice, vram_size, 64 * 1024 * 1024), + DEFINE_PROP_UINT32("revision", PCIQXLDevice, revision, 2), + DEFINE_PROP_UINT32("debug", PCIQXLDevice, debug, 0), + DEFINE_PROP_UINT32("guestdebug", PCIQXLDevice, guestdebug, 0), + DEFINE_PROP_UINT32("cmdlog", PCIQXLDevice, cmdlog, 0), + DEFINE_PROP_END_OF_LIST(), + } +}; + +static PCIDeviceInfo qxl_info_secondary = { + .qdev.name = "qxl", + .qdev.desc = "Spice QXL GPU (secondary)", + .qdev.size = sizeof(PCIQXLDevice), + .qdev.reset = qxl_reset_handler, + .qdev.vmsd = &qxl_vmstate, + .init = qxl_init_secondary, + .qdev.props = (Property[]) { + DEFINE_PROP_UINT32("ram_size", PCIQXLDevice, vga.vram_size, 64 * 1024 * 1024), + DEFINE_PROP_UINT32("vram_size", PCIQXLDevice, vram_size, 64 * 1024 * 1024), + DEFINE_PROP_UINT32("revision", PCIQXLDevice, revision, 2), + DEFINE_PROP_UINT32("debug", PCIQXLDevice, debug, 0), + DEFINE_PROP_UINT32("guestdebug", PCIQXLDevice, guestdebug, 0), + DEFINE_PROP_UINT32("cmdlog", PCIQXLDevice, cmdlog, 0), + DEFINE_PROP_END_OF_LIST(), + } +}; + +static void qxl_register(void) +{ + pci_qdev_register(&qxl_info_primary); + pci_qdev_register(&qxl_info_secondary); +} + +device_init(qxl_register); diff --git a/hw/qxl.h b/hw/qxl.h new file mode 100644 index 0000000..f6c450d --- /dev/null +++ b/hw/qxl.h @@ -0,0 +1,108 @@ +#include "qemu-common.h" + +#include "console.h" +#include "hw.h" +#include "pci.h" +#include "vga_int.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, +}; + +typedef struct PCIQXLDevice { + PCIDevice pci; + SimpleSpiceDisplay ssd; + int id; + uint32_t debug; + uint32_t guestdebug; + uint32_t cmdlog; + enum qxl_mode mode; + uint32_t cmdflags; + int generation; + uint32_t revision; + + int32_t num_memslots; + int32_t num_surfaces; + + 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 stride; + uint32_t bits_pp; + uint32_t bytes_pp; + uint8_t *data, *flipped; + } guest_primary; + + struct surfaces { + QXLPHYSICAL cmds[NUM_SURFACES]; + uint32_t count; + uint32_t max; + } guest_surfaces; + QXLPHYSICAL guest_cursor; + + /* thread signaling */ + pthread_t main; + int pipe[2]; + + /* ram pci bar */ + QXLRam *ram; + VGACommonState vga; + uint32_t num_free_res; + QXLReleaseInfo *last_release; + uint32_t last_release_offset; + uint32_t oom_running; + + /* rom pci bar */ + QXLRom shadow_rom; + QXLRom *rom; + QXLModes *modes; + uint32_t rom_size; + uint64_t rom_offset; + + /* vram pci bar */ + uint32_t vram_size; + uint64_t vram_offset; + + /* io bar */ + uint32_t io_base; +} PCIQXLDevice; + +#define PANIC_ON(x) if ((x)) { \ + printf("%s: PANIC %s failed\n", __FUNCTION__, #x); \ + exit(-1); \ +} + +#define dprint(_qxl, _level, _fmt, ...) \ + do { \ + if (_qxl->debug >= _level) { \ + fprintf(stderr, "qxl-%d: ", _qxl->id); \ + fprintf(stderr, _fmt, ## __VA_ARGS__); \ + } \ + } while (0) + +/* qxl.c */ +void *qxl_phys2virt(PCIQXLDevice *qxl, QXLPHYSICAL phys, int group_id); + +/* qxl-logger.c */ +void qxl_log_cmd_cursor(PCIQXLDevice *qxl, QXLCursorCmd *cmd, int group_id); +void 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); +void qxl_render_cursor(PCIQXLDevice *qxl, QXLCommandExt *ext); diff --git a/hw/r2d.c b/hw/r2d.c new file mode 100644 index 0000000..a0f8c1f --- /dev/null +++ b/hw/r2d.c @@ -0,0 +1,341 @@ +/* + * 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 "sysbus.h" +#include "hw.h" +#include "sh.h" +#include "devices.h" +#include "sysemu.h" +#include "boards.h" +#include "pci.h" +#include "net.h" +#include "sh7750_regs.h" +#include "ide.h" +#include "loader.h" +#include "usb.h" +#include "flash.h" +#include "blockdev.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; +} 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 uint32_t r2d_fpga_read(void *opaque, target_phys_addr_t addr) +{ + 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, target_phys_addr_t addr, uint32_t value) +{ + 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 CPUReadMemoryFunc * const r2d_fpga_readfn[] = { + r2d_fpga_read, + r2d_fpga_read, + NULL, +}; + +static CPUWriteMemoryFunc * const r2d_fpga_writefn[] = { + r2d_fpga_write, + r2d_fpga_write, + NULL, +}; + +static qemu_irq *r2d_fpga_init(target_phys_addr_t base, qemu_irq irl) +{ + int iomemtype; + r2d_fpga_t *s; + + s = qemu_mallocz(sizeof(r2d_fpga_t)); + + s->irl = irl; + + iomemtype = cpu_register_io_memory(r2d_fpga_readfn, + r2d_fpga_writefn, s, + DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(base, 0x40, iomemtype); + return qemu_allocate_irqs(r2d_fpga_irq_set, s, NR_IRQS); +} + +typedef struct ResetData { + CPUState *env; + uint32_t vector; +} ResetData; + +static void main_cpu_reset(void *opaque) +{ + ResetData *s = (ResetData *)opaque; + CPUState *env = s->env; + + cpu_reset(env); + env->pc = s->vector; +} + +static struct __attribute__((__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(ram_addr_t ram_size, + const char *boot_device, + const char *kernel_filename, const char *kernel_cmdline, + const char *initrd_filename, const char *cpu_model) +{ + CPUState *env; + ResetData *reset_info; + struct SH7750State *s; + ram_addr_t sdram_addr; + qemu_irq *irq; + DriveInfo *dinfo; + int i; + + if (!cpu_model) + cpu_model = "SH7751R"; + + env = cpu_init(cpu_model); + if (!env) { + fprintf(stderr, "Unable to find CPU definition\n"); + exit(1); + } + reset_info = qemu_mallocz(sizeof(ResetData)); + reset_info->env = env; + reset_info->vector = env->pc; + qemu_register_reset(main_cpu_reset, reset_info); + + /* Allocate memory space */ + sdram_addr = qemu_ram_alloc(NULL, "r2d.sdram", SDRAM_SIZE); + cpu_register_physical_memory(SDRAM_BASE, SDRAM_SIZE, sdram_addr); + /* Register peripherals */ + s = sh7750_init(env); + irq = r2d_fpga_init(0x04000000, sh7750_irl(s)); + sysbus_create_varargs("sh_pci", 0x1e200000, irq[PCI_INTA], irq[PCI_INTB], + irq[PCI_INTC], irq[PCI_INTD], NULL); + + sm501_init(0x10000000, SM501_VRAM_SIZE, irq[SM501], serial_hds[2]); + + /* onboard CF (True IDE mode, Master only). */ + dinfo = drive_get(IF_IDE, 0, 0); + mmio_ide_init(0x14001000, 0x1400080c, irq[CF_IDE], 1, + dinfo, NULL); + + /* onboard flash memory */ + dinfo = drive_get(IF_PFLASH, 0, 0); + pflash_cfi02_register(0x0, qemu_ram_alloc(NULL, "r2d.flash", FLASH_SIZE), + dinfo ? dinfo->bdrv : 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], "rtl8139", i==0 ? "2" : NULL); + + /* USB keyboard */ + usbdevice_create("keyboard"); + + /* 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 */ + stl_phys(SH7750_BCR1, 1<<3); /* cs3 SDRAM */ + stw_phys(SH7750_BCR2, 3<<(3*2)); /* 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) { + 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/rc4030.c b/hw/rc4030.c new file mode 100644 index 0000000..0a9d98d --- /dev/null +++ b/hw/rc4030.c @@ -0,0 +1,830 @@ +/* + * QEMU JAZZ RC4030 chipset + * + * Copyright (c) 2007-2009 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.h" +#include "mips.h" +#include "qemu-timer.h" + +/********************************************************/ +/* debug rc4030 */ + +//#define DEBUG_RC4030 +//#define DEBUG_RC4030_DMA + +#ifdef DEBUG_RC4030 +#define DPRINTF(fmt, ...) \ +do { printf("rc4030: " fmt , ## __VA_ARGS__); } while (0) +static const char* irq_names[] = { "parallel", "floppy", "sound", "video", + "network", "scsi", "keyboard", "mouse", "serial0", "serial1" }; +#else +#define DPRINTF(fmt, ...) +#endif + +#define RC4030_ERROR(fmt, ...) \ +do { fprintf(stderr, "rc4030 ERROR: %s: " fmt, __func__ , ## __VA_ARGS__); } while (0) + +/********************************************************/ +/* rc4030 emulation */ + +typedef struct dma_pagetable_entry { + int32_t frame; + int32_t owner; +} __attribute__((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 + +typedef struct rc4030State +{ + 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 offset210; + 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; +} rc4030State; + +static void set_next_tick(rc4030State *s) +{ + qemu_irq_lower(s->timer_irq); + uint32_t tm_hz; + + tm_hz = 1000 / (s->itr + 1); + + qemu_mod_timer(s->periodic_timer, qemu_get_clock(vm_clock) + + get_ticks_per_sec() / tm_hz); +} + +/* called for accesses to rc4030 */ +static uint32_t rc4030_readl(void *opaque, target_phys_addr_t addr) +{ + 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; + /* Offset 0x0210 */ + case 0x0210: + val = s->offset210; + 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: + RC4030_ERROR("invalid read [" TARGET_FMT_plx "]\n", addr); + val = 0; + break; + } + + if ((addr & ~3) != 0x230) { + DPRINTF("read 0x%02x at " TARGET_FMT_plx "\n", val, addr); + } + + return val; +} + +static uint32_t rc4030_readw(void *opaque, target_phys_addr_t addr) +{ + uint32_t v = rc4030_readl(opaque, addr & ~0x3); + if (addr & 0x2) + return v >> 16; + else + return v & 0xffff; +} + +static uint32_t rc4030_readb(void *opaque, target_phys_addr_t addr) +{ + uint32_t v = rc4030_readl(opaque, addr & ~0x3); + return (v >> (8 * (addr & 0x3))) & 0xff; +} + +static void rc4030_writel(void *opaque, target_phys_addr_t addr, uint32_t val) +{ + rc4030State *s = opaque; + addr &= 0x3fff; + + DPRINTF("write 0x%02x at " TARGET_FMT_plx "\n", val, addr); + + switch (addr & ~0x3) { + /* Global config register */ + case 0x0000: + s->config = val; + break; + /* DMA transl. table base */ + case 0x0018: + s->dma_tl_base = val; + break; + /* DMA transl. table limit */ + case 0x0020: + s->dma_tl_limit = 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) { + target_phys_addr_t dest = s->cache_ptag & ~0x1; + dest += (s->cache_maint & 0x3) << 3; + cpu_physical_memory_rw(dest, (uint8_t*)&val, 4, 1); + } + 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; + /* Offset 0x0210 */ + case 0x0210: + s->offset210 = 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: + RC4030_ERROR("invalid write of 0x%02x at [" TARGET_FMT_plx "]\n", val, addr); + break; + } +} + +static void rc4030_writew(void *opaque, target_phys_addr_t addr, uint32_t val) +{ + uint32_t old_val = rc4030_readl(opaque, addr & ~0x3); + + if (addr & 0x2) + val = (val << 16) | (old_val & 0x0000ffff); + else + val = val | (old_val & 0xffff0000); + rc4030_writel(opaque, addr & ~0x3, val); +} + +static void rc4030_writeb(void *opaque, target_phys_addr_t addr, uint32_t val) +{ + uint32_t old_val = rc4030_readl(opaque, addr & ~0x3); + + switch (addr & 3) { + case 0: + val = val | (old_val & 0xffffff00); + break; + case 1: + val = (val << 8) | (old_val & 0xffff00ff); + break; + case 2: + val = (val << 16) | (old_val & 0xff00ffff); + break; + case 3: + val = (val << 24) | (old_val & 0x00ffffff); + break; + } + rc4030_writel(opaque, addr & ~0x3, val); +} + +static CPUReadMemoryFunc * const rc4030_read[3] = { + rc4030_readb, + rc4030_readw, + rc4030_readl, +}; + +static CPUWriteMemoryFunc * const rc4030_write[3] = { + rc4030_writeb, + rc4030_writew, + rc4030_writel, +}; + +static void update_jazz_irq(rc4030State *s) +{ + uint16_t pending; + + pending = s->isr_jazz & s->imr_jazz; + +#ifdef DEBUG_RC4030 + if (s->isr_jazz != 0) { + uint32_t irq = 0; + DPRINTF("pending irqs:"); + for (irq = 0; irq < ARRAY_SIZE(irq_names); irq++) { + if (s->isr_jazz & (1 << irq)) { + printf(" %s", irq_names[irq]); + if (!(s->imr_jazz & (1 << irq))) { + printf("(ignored)"); + } + } + } + printf("\n"); + } +#endif + + 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 uint32_t jazzio_readw(void *opaque, target_phys_addr_t addr) +{ + 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) { + DPRINTF("returning irq %s\n", irq_names[irq]); + val = (irq + 1) << 2; + break; + } + irq++; + pending >>= 1; + } + break; + } + /* Local bus int enable mask */ + case 0x02: + val = s->imr_jazz; + break; + default: + RC4030_ERROR("(jazz io controller) invalid read [" TARGET_FMT_plx "]\n", addr); + val = 0; + } + + DPRINTF("(jazz io controller) read 0x%04x at " TARGET_FMT_plx "\n", val, addr); + + return val; +} + +static uint32_t jazzio_readb(void *opaque, target_phys_addr_t addr) +{ + uint32_t v; + v = jazzio_readw(opaque, addr & ~0x1); + return (v >> (8 * (addr & 0x1))) & 0xff; +} + +static uint32_t jazzio_readl(void *opaque, target_phys_addr_t addr) +{ + uint32_t v; + v = jazzio_readw(opaque, addr); + v |= jazzio_readw(opaque, addr + 2) << 16; + return v; +} + +static void jazzio_writew(void *opaque, target_phys_addr_t addr, uint32_t val) +{ + rc4030State *s = opaque; + addr &= 0xfff; + + DPRINTF("(jazz io controller) write 0x%04x at " TARGET_FMT_plx "\n", val, addr); + + switch (addr) { + /* Local bus int enable mask */ + case 0x02: + s->imr_jazz = val; + update_jazz_irq(s); + break; + default: + RC4030_ERROR("(jazz io controller) invalid write of 0x%04x at [" TARGET_FMT_plx "]\n", val, addr); + break; + } +} + +static void jazzio_writeb(void *opaque, target_phys_addr_t addr, uint32_t val) +{ + uint32_t old_val = jazzio_readw(opaque, addr & ~0x1); + + switch (addr & 1) { + case 0: + val = val | (old_val & 0xff00); + break; + case 1: + val = (val << 8) | (old_val & 0x00ff); + break; + } + jazzio_writew(opaque, addr & ~0x1, val); +} + +static void jazzio_writel(void *opaque, target_phys_addr_t addr, uint32_t val) +{ + jazzio_writew(opaque, addr, val & 0xffff); + jazzio_writew(opaque, addr + 2, (val >> 16) & 0xffff); +} + +static CPUReadMemoryFunc * const jazzio_read[3] = { + jazzio_readb, + jazzio_readw, + jazzio_readl, +}; + +static CPUWriteMemoryFunc * const jazzio_write[3] = { + jazzio_writeb, + jazzio_writew, + jazzio_writel, +}; + +static void rc4030_reset(void *opaque) +{ + rc4030State *s = opaque; + 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)); + s->dma_tl_base = s->dma_tl_limit = 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->offset210 = 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->offset210 = 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->offset210); + 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); +} + +void rc4030_dma_memory_rw(void *opaque, target_phys_addr_t addr, uint8_t *buf, int len, int is_write) +{ + rc4030State *s = opaque; + target_phys_addr_t entry_addr; + target_phys_addr_t phys_addr; + dma_pagetable_entry entry; + int index; + int ncpy, i; + + i = 0; + for (;;) { + if (i == len) { + break; + } + + ncpy = DMA_PAGESIZE - (addr & (DMA_PAGESIZE - 1)); + if (ncpy > len - i) + ncpy = len - i; + + /* Get DMA translation table entry */ + index = addr / DMA_PAGESIZE; + if (index >= s->dma_tl_limit / sizeof(dma_pagetable_entry)) { + break; + } + entry_addr = s->dma_tl_base + index * sizeof(dma_pagetable_entry); + /* XXX: not sure. should we really use only lowest bits? */ + entry_addr &= 0x7fffffff; + cpu_physical_memory_rw(entry_addr, (uint8_t *)&entry, sizeof(entry), 0); + + /* Read/write data at right place */ + phys_addr = entry.frame + (addr & (DMA_PAGESIZE - 1)); + cpu_physical_memory_rw(phys_addr, &buf[i], ncpy, is_write); + + i += ncpy; + addr += ncpy; + } +} + +static void rc4030_do_dma(void *opaque, int n, uint8_t *buf, int len, int is_write) +{ + rc4030State *s = opaque; + target_phys_addr_t 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 */ + rc4030_dma_memory_rw(opaque, dma_addr, buf, len, is_write); + + s->dma_regs[n][DMA_REG_ENABLE] |= DMA_FLAG_TC_INTR; + s->dma_regs[n][DMA_REG_COUNT] -= len; + +#ifdef DEBUG_RC4030_DMA + { + int i, j; + printf("rc4030 dma: Copying %d bytes %s host %p\n", + len, is_write ? "from" : "to", buf); + for (i = 0; i < len; i += 16) { + int n = 16; + if (n > len - i) { + n = len - i; + } + for (j = 0; j < n; j++) + printf("%02x ", buf[i + j]); + while (j++ < 16) + printf(" "); + printf("| "); + for (j = 0; j < n; j++) + printf("%c", isprint(buf[i + j]) ? buf[i + j] : '.'); + printf("\n"); + } + } +#endif +} + +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 *)qemu_mallocz(sizeof(rc4030_dma) * n); + p = (struct rc4030DMAState *)qemu_mallocz(sizeof(struct rc4030DMAState) * n); + for (i = 0; i < n; i++) { + p->opaque = opaque; + p->n = i; + s[i] = p; + p++; + } + return s; +} + +void *rc4030_init(qemu_irq timer, qemu_irq jazz_bus, + qemu_irq **irqs, rc4030_dma **dmas) +{ + rc4030State *s; + int s_chipset, s_jazzio; + + s = qemu_mallocz(sizeof(rc4030State)); + + *irqs = qemu_allocate_irqs(rc4030_irq_jazz_request, s, 16); + *dmas = rc4030_allocate_dmas(s, 4); + + s->periodic_timer = qemu_new_timer(vm_clock, rc4030_periodic_timer, s); + s->timer_irq = timer; + s->jazz_bus_irq = jazz_bus; + + qemu_register_reset(rc4030_reset, s); + register_savevm(NULL, "rc4030", 0, 2, rc4030_save, rc4030_load, s); + rc4030_reset(s); + + s_chipset = cpu_register_io_memory(rc4030_read, rc4030_write, s, + DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(0x80000000, 0x300, s_chipset); + s_jazzio = cpu_register_io_memory(jazzio_read, jazzio_write, s, + DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(0xf0000000, 0x00001000, s_jazzio); + + return s; +} diff --git a/hw/realview.c b/hw/realview.c new file mode 100644 index 0000000..6eb6c6a --- /dev/null +++ b/hw/realview.c @@ -0,0 +1,459 @@ +/* + * ARM RealView Baseboard System emulation. + * + * Copyright (c) 2006-2007 CodeSourcery. + * Written by Paul Brook + * + * This code is licenced under the GPL. + */ + +#include "sysbus.h" +#include "arm-misc.h" +#include "primecell.h" +#include "devices.h" +#include "pci.h" +#include "usb-ohci.h" +#include "net.h" +#include "sysemu.h" +#include "boards.h" +#include "bitbang_i2c.h" +#include "sysbus.h" +#include "blockdev.h" + +#define SMP_BOOT_ADDR 0xe0000000 + +typedef struct { + SysBusDevice busdev; + bitbang_i2c_interface *bitbang; + int out; + int in; +} RealViewI2CState; + +static uint32_t realview_i2c_read(void *opaque, target_phys_addr_t offset) +{ + RealViewI2CState *s = (RealViewI2CState *)opaque; + + if (offset == 0) { + return (s->out & 1) | (s->in << 1); + } else { + hw_error("realview_i2c_read: Bad offset 0x%x\n", (int)offset); + return -1; + } +} + +static void realview_i2c_write(void *opaque, target_phys_addr_t offset, + uint32_t value) +{ + RealViewI2CState *s = (RealViewI2CState *)opaque; + + switch (offset) { + case 0: + s->out |= value & 3; + break; + case 4: + s->out &= ~value; + break; + default: + hw_error("realview_i2c_write: Bad offset 0x%x\n", (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 CPUReadMemoryFunc * const realview_i2c_readfn[] = { + realview_i2c_read, + realview_i2c_read, + realview_i2c_read +}; + +static CPUWriteMemoryFunc * const realview_i2c_writefn[] = { + realview_i2c_write, + realview_i2c_write, + realview_i2c_write +}; + +static int realview_i2c_init(SysBusDevice *dev) +{ + RealViewI2CState *s = FROM_SYSBUS(RealViewI2CState, dev); + i2c_bus *bus; + int iomemtype; + + bus = i2c_init_bus(&dev->qdev, "i2c"); + s->bitbang = bitbang_i2c_init(bus); + iomemtype = cpu_register_io_memory(realview_i2c_readfn, + realview_i2c_writefn, s, + DEVICE_NATIVE_ENDIAN); + sysbus_init_mmio(dev, 0x1000, iomemtype); + return 0; +} + +static SysBusDeviceInfo realview_i2c_info = { + .init = realview_i2c_init, + .qdev.name = "realview_i2c", + .qdev.size = sizeof(RealViewI2CState), +}; + +static void realview_register_devices(void) +{ + sysbus_register_withprop(&realview_i2c_info); +} + +/* Board init. */ + +static struct arm_boot_info realview_binfo = { + .smp_loader_start = SMP_BOOT_ADDR, +}; + +static void secondary_cpu_reset(void *opaque) +{ + CPUState *env = opaque; + + cpu_reset(env); + /* Set entry point for secondary CPUs. This assumes we're using + the init code from arm_boot.c. Real hardware resets all CPUs + the same. */ + env->regs[15] = SMP_BOOT_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(ram_addr_t ram_size, + const char *boot_device, + const char *kernel_filename, const char *kernel_cmdline, + const char *initrd_filename, const char *cpu_model, + enum realview_board_type board_type) +{ + CPUState *env = NULL; + ram_addr_t ram_offset; + DeviceState *dev; + SysBusDevice *busdev; + qemu_irq *irqp; + qemu_irq pic[64]; + PCIBus *pci_bus; + NICInfo *nd; + i2c_bus *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; + + switch (board_type) { + case BOARD_EB: + break; + case BOARD_EB_MPCORE: + is_mpcore = 1; + break; + case BOARD_PB_A8: + is_pb = 1; + break; + case BOARD_PBX_A9: + is_mpcore = 1; + is_pb = 1; + break; + } + for (n = 0; n < smp_cpus; n++) { + env = cpu_init(cpu_model); + if (!env) { + fprintf(stderr, "Unable to find CPU definition\n"); + exit(1); + } + irqp = arm_pic_init_cpu(env); + cpu_irq[n] = irqp[ARM_PIC_CPU_IRQ]; + if (n > 0) { + qemu_register_reset(secondary_cpu_reset, 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. */ + low_ram_size = ram_size - 0x20000000; + ram_size = 0x20000000; + ram_offset = qemu_ram_alloc(NULL, "realview.lowmem", low_ram_size); + cpu_register_physical_memory(0x20000000, low_ram_size, + ram_offset | IO_MEM_RAM); + } + + ram_offset = qemu_ram_alloc(NULL, "realview.highmem", ram_size); + low_ram_size = ram_size; + if (low_ram_size > 0x10000000) + low_ram_size = 0x10000000; + /* SDRAM at address zero. */ + cpu_register_physical_memory(0, low_ram_size, ram_offset | IO_MEM_RAM); + if (is_pb) { + /* And again at a high address. */ + cpu_register_physical_memory(0x70000000, ram_size, + ram_offset | IO_MEM_RAM); + } else { + ram_size = low_ram_size; + } + + sys_id = is_pb ? 0x01780500 : 0xc1400400; + arm_sysctl_init(0x10000000, sys_id, proc_id); + + 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 = sysbus_from_qdev(dev); + if (is_pb) { + realview_binfo.smp_priv_base = 0x1f000000; + } else { + realview_binfo.smp_priv_base = 0x10100000; + } + sysbus_mmio_map(busdev, 0, realview_binfo.smp_priv_base); + for (n = 0; n < smp_cpus; n++) { + sysbus_connect_irq(busdev, n, cpu_irq[n]); + } + } 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); + } + + 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("pl110_versatile", 0x10020000, pic[23]); + + sysbus_create_varargs("pl181", 0x10005000, pic[17], pic[18], NULL); + + sysbus_create_simple("pl031", 0x10017000, pic[10]); + + if (!is_pb) { + dev = sysbus_create_varargs("realview_pci", 0x60000000, + pic[48], pic[49], pic[50], pic[51], NULL); + pci_bus = (PCIBus *)qdev_get_child_bus(dev, "pci"); + if (usb_enabled) { + usb_ohci_init_pci(pci_bus, -1); + } + 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 ((!nd->model && !done_nic) + || 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 { + pci_nic_init_nofail(nd, "rtl8139", NULL); + } + } + + dev = sysbus_create_simple("realview_i2c", 0x10002000, NULL); + i2c = (i2c_bus *)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. */ + /* 0x61000000 PCI Self Config. */ + /* 0x62000000 PCI Config. */ + /* 0x63000000 PCI IO. */ + /* 0x64000000 PCI mem 0. */ + /* 0x68000000 PCI mem 1. */ + /* 0x6c000000 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. */ + ram_offset = qemu_ram_alloc(NULL, "realview.hack", 0x1000); + cpu_register_physical_memory(SMP_BOOT_ADDR, 0x1000, + ram_offset | IO_MEM_RAM); + + realview_binfo.ram_size = ram_size; + realview_binfo.kernel_filename = kernel_filename; + realview_binfo.kernel_cmdline = kernel_cmdline; + realview_binfo.initrd_filename = 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(first_cpu, &realview_binfo); +} + +static void realview_eb_init(ram_addr_t ram_size, + const char *boot_device, + const char *kernel_filename, const char *kernel_cmdline, + const char *initrd_filename, const char *cpu_model) +{ + if (!cpu_model) { + cpu_model = "arm926"; + } + realview_init(ram_size, boot_device, kernel_filename, kernel_cmdline, + initrd_filename, cpu_model, BOARD_EB); +} + +static void realview_eb_mpcore_init(ram_addr_t ram_size, + const char *boot_device, + const char *kernel_filename, const char *kernel_cmdline, + const char *initrd_filename, const char *cpu_model) +{ + if (!cpu_model) { + cpu_model = "arm11mpcore"; + } + realview_init(ram_size, boot_device, kernel_filename, kernel_cmdline, + initrd_filename, cpu_model, BOARD_EB_MPCORE); +} + +static void realview_pb_a8_init(ram_addr_t ram_size, + const char *boot_device, + const char *kernel_filename, const char *kernel_cmdline, + const char *initrd_filename, const char *cpu_model) +{ + if (!cpu_model) { + cpu_model = "cortex-a8"; + } + realview_init(ram_size, boot_device, kernel_filename, kernel_cmdline, + initrd_filename, cpu_model, BOARD_PB_A8); +} + +static void realview_pbx_a9_init(ram_addr_t ram_size, + const char *boot_device, + const char *kernel_filename, const char *kernel_cmdline, + const char *initrd_filename, const char *cpu_model) +{ + if (!cpu_model) { + cpu_model = "cortex-a9"; + } + realview_init(ram_size, boot_device, kernel_filename, kernel_cmdline, + initrd_filename, cpu_model, BOARD_PBX_A9); +} + +static QEMUMachine realview_eb_machine = { + .name = "realview-eb", + .desc = "ARM RealView Emulation Baseboard (ARM926EJ-S)", + .init = realview_eb_init, + .use_scsi = 1, +}; + +static QEMUMachine realview_eb_mpcore_machine = { + .name = "realview-eb-mpcore", + .desc = "ARM RealView Emulation Baseboard (ARM11MPCore)", + .init = realview_eb_mpcore_init, + .use_scsi = 1, + .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, + .use_scsi = 1, + .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); +device_init(realview_register_devices) diff --git a/hw/realview_gic.c b/hw/realview_gic.c new file mode 100644 index 0000000..db908b6 --- /dev/null +++ b/hw/realview_gic.c @@ -0,0 +1,79 @@ +/* + * ARM RealView Emulation Baseboard Interrupt Controller + * + * Copyright (c) 2006-2007 CodeSourcery. + * Written by Paul Brook + * + * This code is licenced under the GPL. + */ + +#include "sysbus.h" + +#define GIC_NIRQ 96 +#define NCPU 1 + +/* Only a single "CPU" interface is present. */ +static inline int +gic_get_current_cpu(void) +{ + return 0; +} + +#include "arm_gic.c" + +typedef struct { + gic_state gic; + int iomemtype; +} RealViewGICState; + +static uint32_t realview_gic_cpu_read(void *opaque, target_phys_addr_t offset) +{ + gic_state *s = (gic_state *)opaque; + return gic_cpu_read(s, gic_get_current_cpu(), offset); +} + +static void realview_gic_cpu_write(void *opaque, target_phys_addr_t offset, + uint32_t value) +{ + gic_state *s = (gic_state *)opaque; + gic_cpu_write(s, gic_get_current_cpu(), offset, value); +} + +static CPUReadMemoryFunc * const realview_gic_cpu_readfn[] = { + realview_gic_cpu_read, + realview_gic_cpu_read, + realview_gic_cpu_read +}; + +static CPUWriteMemoryFunc * const realview_gic_cpu_writefn[] = { + realview_gic_cpu_write, + realview_gic_cpu_write, + realview_gic_cpu_write +}; + +static void realview_gic_map(SysBusDevice *dev, target_phys_addr_t base) +{ + RealViewGICState *s = FROM_SYSBUSGIC(RealViewGICState, dev); + cpu_register_physical_memory(base, 0x1000, s->iomemtype); + cpu_register_physical_memory(base + 0x1000, 0x1000, s->gic.iomemtype); +} + +static int realview_gic_init(SysBusDevice *dev) +{ + RealViewGICState *s = FROM_SYSBUSGIC(RealViewGICState, dev); + + gic_init(&s->gic); + s->iomemtype = cpu_register_io_memory(realview_gic_cpu_readfn, + realview_gic_cpu_writefn, s, + DEVICE_NATIVE_ENDIAN); + sysbus_init_mmio_cb(dev, 0x2000, realview_gic_map); + return 0; +} + +static void realview_gic_register_devices(void) +{ + sysbus_register_dev("realview_gic", sizeof(RealViewGICState), + realview_gic_init); +} + +device_init(realview_gic_register_devices) diff --git a/hw/rtl8139.c b/hw/rtl8139.c new file mode 100644 index 0000000..a22530c --- /dev/null +++ b/hw/rtl8139.c @@ -0,0 +1,3431 @@ +/** + * 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) + */ + +#include "hw.h" +#include "pci.h" +#include "qemu-timer.h" +#include "net.h" +#include "loader.h" +#include "sysemu.h" + +/* debug RTL8139 card */ +//#define DEBUG_RTL8139 1 + +#define PCI_FREQUENCY 33000000L + +/* debug RTL8139 card C+ mode only */ +//#define DEBUG_RTL8139CP 1 + +/* Calculate CRCs properly on Rx packets */ +#define RTL8139_CALCULATE_RXCRC 1 + +#if defined(RTL8139_CALCULATE_RXCRC) +/* For crc32 */ +#include +#endif + +#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 ) ) + +#if defined (DEBUG_RTL8139) +# define DEBUG_PRINT(x) do { printf x ; } while (0) +#else +# define DEBUG_PRINT(x) +#endif + +/* 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, + 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_Lock = 0x00, + Cfg9346_Unlock = 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); + +/* Writes tally counters to specified physical memory address */ +static void RTL8139TallyCounters_physical_memory_write(target_phys_addr_t tc_addr, RTL8139TallyCounters* counters); + +typedef struct RTL8139State { + PCIDevice dev; + 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; + int rtl8139_mmio_io_addr; + + /* 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; + int64_t TimerExpire; + + /* Support migration to/from old versions */ + int rtl8139_mmio_io_addr_dummy; +} RTL8139State; + +static void rtl8139_set_next_tctr_time(RTL8139State *s, int64_t current_time); + +static void prom9346_decode_command(EEprom9346 *eeprom, uint8_t command) +{ + DEBUG_PRINT(("RTL8139: 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; + DEBUG_PRINT(("RTL8139: 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 */ + DEBUG_PRINT(("RTL8139: 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: + DEBUG_PRINT(("RTL8139: eeprom write enabled\n")); + break; + case Chip9346_op_write_all: + DEBUG_PRINT(("RTL8139: eeprom begin write all\n")); + break; + case Chip9346_op_write_disable: + DEBUG_PRINT(("RTL8139: eeprom write disabled\n")); + break; + } + break; + } +} + +static void prom9346_shift_clock(EEprom9346 *eeprom) +{ + int bit = eeprom->eedi?1:0; + + ++ eeprom->tick; + + DEBUG_PRINT(("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; + DEBUG_PRINT(("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; + + DEBUG_PRINT(("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; + + DEBUG_PRINT(("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) + { + DEBUG_PRINT(("RTL8139: 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; + } + DEBUG_PRINT(("RTL8139: 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; + + DEBUG_PRINT(("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; + + DEBUG_PRINT(("=== eeprom: begin access, enter command mode\n")); + } + + if (!eecs) + { + DEBUG_PRINT(("=== eeprom: end access\n")); + return; + } + + if (!old_eesk && eesk) + { + /* SK front rules */ + prom9346_shift_clock(eeprom); + } +} + +static void rtl8139_update_irq(RTL8139State *s) +{ + int isr; + isr = (s->IntrStatus & s->IntrMask) & 0xffff; + + DEBUG_PRINT(("RTL8139: Set IRQ to %d (%04x %04x)\n", + isr ? 1 : 0, s->IntrStatus, s->IntrMask)); + + qemu_set_irq(s->dev.irq[0], (isr != 0)); +} + +#define POLYNOMIAL 0x04c11db6 + +/* From FreeBSD */ +/* XXX: optimize */ +static int 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 >> 26); +} + +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) +{ + 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))) + { + DEBUG_PRINT((">>> RTL8139: rx packet wrapped in buffer at %d\n", size-wrapped)); + + if (size > wrapped) + { + cpu_physical_memory_write( s->RxBuf + s->RxBufAddr, + buf, size-wrapped ); + } + + /* reset buffer pointer */ + s->RxBufAddr = 0; + + cpu_physical_memory_write( s->RxBuf + s->RxBufAddr, + buf + (size-wrapped), wrapped ); + + s->RxBufAddr = wrapped; + + return; + } + } + + /* non-wrapping path or overwrapping enabled */ + cpu_physical_memory_write( s->RxBuf + s->RxBufAddr, buf, size ); + + s->RxBufAddr += size; +} + +#define MIN_BUF_SIZE 60 +static inline target_phys_addr_t rtl8139_addr64(uint32_t low, uint32_t high) +{ +#if TARGET_PHYS_ADDR_BITS > 32 + return low | ((target_phys_addr_t)high << 32); +#else + return low; +#endif +} + +static int rtl8139_can_receive(VLANClientState *nc) +{ + RTL8139State *s = DO_UPCAST(NICState, nc, nc)->opaque; + 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)) { + /* ??? 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); + } +} + +static ssize_t rtl8139_do_receive(VLANClientState *nc, const uint8_t *buf, size_t size_, int do_interrupt) +{ + RTL8139State *s = DO_UPCAST(NICState, nc, nc)->opaque; + int size = size_; + + uint32_t packet_header = 0; + + uint8_t buf1[60]; + static const uint8_t broadcast_macaddr[6] = + { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; + + DEBUG_PRINT((">>> RTL8139: received len=%d\n", size)); + + /* test if board clock is stopped */ + if (!s->clock_enabled) + { + DEBUG_PRINT(("RTL8139: stopped ==========================\n")); + return -1; + } + + /* first check if receiver is enabled */ + + if (!rtl8139_receiver_enabled(s)) + { + DEBUG_PRINT(("RTL8139: receiver disabled ================\n")); + return -1; + } + + /* XXX: check this */ + if (s->RxConfig & AcceptAllPhys) { + /* promiscuous: receive all */ + DEBUG_PRINT((">>> RTL8139: packet received in promiscuous mode\n")); + + } else { + if (!memcmp(buf, broadcast_macaddr, 6)) { + /* broadcast address */ + if (!(s->RxConfig & AcceptBroadcast)) + { + DEBUG_PRINT((">>> RTL8139: broadcast packet rejected\n")); + + /* update tally counter */ + ++s->tally_counters.RxERR; + + return size; + } + + packet_header |= RxBroadcast; + + DEBUG_PRINT((">>> RTL8139: broadcast packet received\n")); + + /* update tally counter */ + ++s->tally_counters.RxOkBrd; + + } else if (buf[0] & 0x01) { + /* multicast */ + if (!(s->RxConfig & AcceptMulticast)) + { + DEBUG_PRINT((">>> RTL8139: 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)))) + { + DEBUG_PRINT((">>> RTL8139: multicast address mismatch\n")); + + /* update tally counter */ + ++s->tally_counters.RxERR; + + return size; + } + + packet_header |= RxMulticast; + + DEBUG_PRINT((">>> RTL8139: 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)) + { + DEBUG_PRINT((">>> RTL8139: rejecting physical address matching packet\n")); + + /* update tally counter */ + ++s->tally_counters.RxERR; + + return size; + } + + packet_header |= RxPhysical; + + DEBUG_PRINT((">>> RTL8139: physical address matching packet received\n")); + + /* update tally counter */ + ++s->tally_counters.RxOkPhy; + + } else { + + DEBUG_PRINT((">>> RTL8139: unknown packet\n")); + + /* update tally counter */ + ++s->tally_counters.RxERR; + + 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; + } + + if (rtl8139_cp_receiver_enabled(s)) + { + DEBUG_PRINT(("RTL8139: 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; + target_phys_addr_t cplus_rx_ring_desc; + + cplus_rx_ring_desc = rtl8139_addr64(s->RxRingAddrLO, s->RxRingAddrHI); + cplus_rx_ring_desc += 16 * descriptor; + + DEBUG_PRINT(("RTL8139: +++ C+ mode reading RX descriptor %d from host memory at %08x %08x = %016" PRIx64 "\n", + descriptor, s->RxRingAddrHI, s->RxRingAddrLO, (uint64_t)cplus_rx_ring_desc)); + + uint32_t val, rxdw0,rxdw1,rxbufLO,rxbufHI; + + cpu_physical_memory_read(cplus_rx_ring_desc, (uint8_t *)&val, 4); + rxdw0 = le32_to_cpu(val); + cpu_physical_memory_read(cplus_rx_ring_desc+4, (uint8_t *)&val, 4); + rxdw1 = le32_to_cpu(val); + cpu_physical_memory_read(cplus_rx_ring_desc+8, (uint8_t *)&val, 4); + rxbufLO = le32_to_cpu(val); + cpu_physical_memory_read(cplus_rx_ring_desc+12, (uint8_t *)&val, 4); + rxbufHI = le32_to_cpu(val); + + DEBUG_PRINT(("RTL8139: +++ C+ mode RX descriptor %d %08x %08x %08x %08x\n", + descriptor, + rxdw0, rxdw1, rxbufLO, rxbufHI)); + + if (!(rxdw0 & CP_RX_OWN)) + { + DEBUG_PRINT(("RTL8139: 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; + + /* TODO: scatter the packet over available receive ring descriptors space */ + + if (size+4 > rx_space) + { + DEBUG_PRINT(("RTL8139: 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_; + } + + target_phys_addr_t rx_addr = rtl8139_addr64(rxbufLO, rxbufHI); + + /* receive/copy to target memory */ + cpu_physical_memory_write( rx_addr, buf, size ); + + if (s->CpCmd & CPlusRxChkSum) + { + /* do some packet checksumming */ + } + + /* write checksum */ +#if defined (RTL8139_CALCULATE_RXCRC) + val = cpu_to_le32(crc32(0, buf, size)); +#else + val = 0; +#endif + cpu_physical_memory_write( 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); + + /* reset VLAN tag flag */ + rxdw1 &= ~CP_RX_TAVA; + + /* update ring data */ + val = cpu_to_le32(rxdw0); + cpu_physical_memory_write(cplus_rx_ring_desc, (uint8_t *)&val, 4); + val = cpu_to_le32(rxdw1); + cpu_physical_memory_write(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; + } + + DEBUG_PRINT(("RTL8139: done C+ Rx mode ----------------\n")); + + } + else + { + DEBUG_PRINT(("RTL8139: 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 */ + + if (avail != 0 && size + 8 >= avail) + { + DEBUG_PRINT(("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 size_; + } + + 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 */ +#if defined (RTL8139_CALCULATE_RXCRC) + val = cpu_to_le32(crc32(0, buf, size)); +#else + val = 0; +#endif + + rtl8139_write_buffer(s, (uint8_t *)&val, 4); + + /* correct buffer write pointer */ + s->RxBufAddr = MOD2((s->RxBufAddr + 3) & ~0x3, s->RxBufferSize); + + /* now we can signal we have received something */ + + DEBUG_PRINT((" 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(VLANClientState *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 = container_of(d, RTL8139State, dev.qdev); + int i; + + /* restore MAC address */ + memcpy(s->phys, s->conf.macaddr.a, 6); + + /* reset interrupt mask */ + s->IntrStatus = 0; + s->IntrMask = 0; + + rtl8139_update_irq(s); + + /* 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; + + /* 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 */ + s->BasicModeStatus |= 0x0004; /* link is up */ + + 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; + + /* 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_physical_memory_write(target_phys_addr_t tc_addr, RTL8139TallyCounters* tally_counters) +{ + uint16_t val16; + uint32_t val32; + uint64_t val64; + + val64 = cpu_to_le64(tally_counters->TxOk); + cpu_physical_memory_write(tc_addr + 0, (uint8_t *)&val64, 8); + + val64 = cpu_to_le64(tally_counters->RxOk); + cpu_physical_memory_write(tc_addr + 8, (uint8_t *)&val64, 8); + + val64 = cpu_to_le64(tally_counters->TxERR); + cpu_physical_memory_write(tc_addr + 16, (uint8_t *)&val64, 8); + + val32 = cpu_to_le32(tally_counters->RxERR); + cpu_physical_memory_write(tc_addr + 24, (uint8_t *)&val32, 4); + + val16 = cpu_to_le16(tally_counters->MissPkt); + cpu_physical_memory_write(tc_addr + 28, (uint8_t *)&val16, 2); + + val16 = cpu_to_le16(tally_counters->FAE); + cpu_physical_memory_write(tc_addr + 30, (uint8_t *)&val16, 2); + + val32 = cpu_to_le32(tally_counters->Tx1Col); + cpu_physical_memory_write(tc_addr + 32, (uint8_t *)&val32, 4); + + val32 = cpu_to_le32(tally_counters->TxMCol); + cpu_physical_memory_write(tc_addr + 36, (uint8_t *)&val32, 4); + + val64 = cpu_to_le64(tally_counters->RxOkPhy); + cpu_physical_memory_write(tc_addr + 40, (uint8_t *)&val64, 8); + + val64 = cpu_to_le64(tally_counters->RxOkBrd); + cpu_physical_memory_write(tc_addr + 48, (uint8_t *)&val64, 8); + + val32 = cpu_to_le32(tally_counters->RxOkMul); + cpu_physical_memory_write(tc_addr + 56, (uint8_t *)&val32, 4); + + val16 = cpu_to_le16(tally_counters->TxAbt); + cpu_physical_memory_write(tc_addr + 60, (uint8_t *)&val16, 2); + + val16 = cpu_to_le16(tally_counters->TxUndrn); + cpu_physical_memory_write(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, + .minimum_version_id_old = 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) +{ + val &= 0xff; + + DEBUG_PRINT(("RTL8139: ChipCmd write val=0x%08x\n", val)); + + if (val & CmdReset) + { + DEBUG_PRINT(("RTL8139: ChipCmd reset\n")); + rtl8139_reset(&s->dev.qdev); + } + if (val & CmdRxEnb) + { + DEBUG_PRINT(("RTL8139: ChipCmd enable receiver\n")); + + s->currCPlusRxDesc = 0; + } + if (val & CmdTxEnb) + { + DEBUG_PRINT(("RTL8139: ChipCmd enable transmitter\n")); + + s->currCPlusTxDesc = 0; + } + + /* mask unwriteable 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) + { + DEBUG_PRINT(("RTL8139: receiver buffer data available 0x%04x\n", unread)); + return 0; + } + + DEBUG_PRINT(("RTL8139: 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; + + DEBUG_PRINT(("RTL8139: ChipCmd read val=0x%04x\n", ret)); + + return ret; +} + +static void rtl8139_CpCmd_write(RTL8139State *s, uint32_t val) +{ + val &= 0xffff; + + DEBUG_PRINT(("RTL8139C+ command register write(w) val=0x%04x\n", val)); + + s->cplus_enabled = 1; + + /* mask unwriteable bits */ + val = SET_MASKED(val, 0xff84, s->CpCmd); + + s->CpCmd = val; +} + +static uint32_t rtl8139_CpCmd_read(RTL8139State *s) +{ + uint32_t ret = s->CpCmd; + + DEBUG_PRINT(("RTL8139C+ command register read(w) val=0x%04x\n", ret)); + + return ret; +} + +static void rtl8139_IntrMitigate_write(RTL8139State *s, uint32_t val) +{ + DEBUG_PRINT(("RTL8139C+ IntrMitigate register write(w) val=0x%04x\n", val)); +} + +static uint32_t rtl8139_IntrMitigate_read(RTL8139State *s) +{ + uint32_t ret = 0; + + DEBUG_PRINT(("RTL8139C+ IntrMitigate register read(w) val=0x%04x\n", ret)); + + return ret; +} + +static int rtl8139_config_writeable(RTL8139State *s) +{ + if (s->Cfg9346 & Cfg9346_Unlock) + { + return 1; + } + + DEBUG_PRINT(("RTL8139: Configuration registers are write-protected\n")); + + return 0; +} + +static void rtl8139_BasicModeCtrl_write(RTL8139State *s, uint32_t val) +{ + val &= 0xffff; + + DEBUG_PRINT(("RTL8139: BasicModeCtrl register write(w) val=0x%04x\n", val)); + + /* mask unwriteable bits */ + uint32_t mask = 0x4cff; + + if (1 || !rtl8139_config_writeable(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; + + DEBUG_PRINT(("RTL8139: BasicModeCtrl register read(w) val=0x%04x\n", ret)); + + return ret; +} + +static void rtl8139_BasicModeStatus_write(RTL8139State *s, uint32_t val) +{ + val &= 0xffff; + + DEBUG_PRINT(("RTL8139: BasicModeStatus register write(w) val=0x%04x\n", val)); + + /* mask unwriteable bits */ + val = SET_MASKED(val, 0xff3f, s->BasicModeStatus); + + s->BasicModeStatus = val; +} + +static uint32_t rtl8139_BasicModeStatus_read(RTL8139State *s) +{ + uint32_t ret = s->BasicModeStatus; + + DEBUG_PRINT(("RTL8139: BasicModeStatus register read(w) val=0x%04x\n", ret)); + + return ret; +} + +static void rtl8139_Cfg9346_write(RTL8139State *s, uint32_t val) +{ + val &= 0xff; + + DEBUG_PRINT(("RTL8139: Cfg9346 write val=0x%02x\n", val)); + + /* mask unwriteable 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(&s->dev.qdev); + } + + 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; + } + } + + DEBUG_PRINT(("RTL8139: Cfg9346 read val=0x%02x\n", ret)); + + return ret; +} + +static void rtl8139_Config0_write(RTL8139State *s, uint32_t val) +{ + val &= 0xff; + + DEBUG_PRINT(("RTL8139: Config0 write val=0x%02x\n", val)); + + if (!rtl8139_config_writeable(s)) + return; + + /* mask unwriteable bits */ + val = SET_MASKED(val, 0xf8, s->Config0); + + s->Config0 = val; +} + +static uint32_t rtl8139_Config0_read(RTL8139State *s) +{ + uint32_t ret = s->Config0; + + DEBUG_PRINT(("RTL8139: Config0 read val=0x%02x\n", ret)); + + return ret; +} + +static void rtl8139_Config1_write(RTL8139State *s, uint32_t val) +{ + val &= 0xff; + + DEBUG_PRINT(("RTL8139: Config1 write val=0x%02x\n", val)); + + if (!rtl8139_config_writeable(s)) + return; + + /* mask unwriteable bits */ + val = SET_MASKED(val, 0xC, s->Config1); + + s->Config1 = val; +} + +static uint32_t rtl8139_Config1_read(RTL8139State *s) +{ + uint32_t ret = s->Config1; + + DEBUG_PRINT(("RTL8139: Config1 read val=0x%02x\n", ret)); + + return ret; +} + +static void rtl8139_Config3_write(RTL8139State *s, uint32_t val) +{ + val &= 0xff; + + DEBUG_PRINT(("RTL8139: Config3 write val=0x%02x\n", val)); + + if (!rtl8139_config_writeable(s)) + return; + + /* mask unwriteable bits */ + val = SET_MASKED(val, 0x8F, s->Config3); + + s->Config3 = val; +} + +static uint32_t rtl8139_Config3_read(RTL8139State *s) +{ + uint32_t ret = s->Config3; + + DEBUG_PRINT(("RTL8139: Config3 read val=0x%02x\n", ret)); + + return ret; +} + +static void rtl8139_Config4_write(RTL8139State *s, uint32_t val) +{ + val &= 0xff; + + DEBUG_PRINT(("RTL8139: Config4 write val=0x%02x\n", val)); + + if (!rtl8139_config_writeable(s)) + return; + + /* mask unwriteable bits */ + val = SET_MASKED(val, 0x0a, s->Config4); + + s->Config4 = val; +} + +static uint32_t rtl8139_Config4_read(RTL8139State *s) +{ + uint32_t ret = s->Config4; + + DEBUG_PRINT(("RTL8139: Config4 read val=0x%02x\n", ret)); + + return ret; +} + +static void rtl8139_Config5_write(RTL8139State *s, uint32_t val) +{ + val &= 0xff; + + DEBUG_PRINT(("RTL8139: Config5 write val=0x%02x\n", val)); + + /* mask unwriteable bits */ + val = SET_MASKED(val, 0x80, s->Config5); + + s->Config5 = val; +} + +static uint32_t rtl8139_Config5_read(RTL8139State *s) +{ + uint32_t ret = s->Config5; + + DEBUG_PRINT(("RTL8139: Config5 read val=0x%02x\n", ret)); + + return ret; +} + +static void rtl8139_TxConfig_write(RTL8139State *s, uint32_t val) +{ + if (!rtl8139_transmitter_enabled(s)) + { + DEBUG_PRINT(("RTL8139: transmitter disabled; no TxConfig write val=0x%08x\n", val)); + return; + } + + DEBUG_PRINT(("RTL8139: 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) +{ + DEBUG_PRINT(("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; + + DEBUG_PRINT(("RTL8139: TxConfig read val=0x%04x\n", ret)); + + return ret; +} + +static void rtl8139_RxConfig_write(RTL8139State *s, uint32_t val) +{ + DEBUG_PRINT(("RTL8139: RxConfig write val=0x%08x\n", val)); + + /* mask unwriteable 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)); + + DEBUG_PRINT(("RTL8139: RxConfig write reset buffer size to %d\n", s->RxBufferSize)); +} + +static uint32_t rtl8139_RxConfig_read(RTL8139State *s) +{ + uint32_t ret = s->RxConfig; + + DEBUG_PRINT(("RTL8139: RxConfig read val=0x%08x\n", ret)); + + return ret; +} + +static void rtl8139_transfer_frame(RTL8139State *s, const uint8_t *buf, int size, int do_interrupt) +{ + if (!size) + { + DEBUG_PRINT(("RTL8139: +++ empty ethernet frame\n")); + return; + } + + if (TxLoopBack == (s->TxConfig & TxLoopBack)) + { + DEBUG_PRINT(("RTL8139: +++ transmit loopback mode\n")); + rtl8139_do_receive(&s->nic->nc, buf, size, do_interrupt); + } + else + { + qemu_send_packet(&s->nic->nc, buf, size); + } +} + +static int rtl8139_transmit_one(RTL8139State *s, int descriptor) +{ + if (!rtl8139_transmitter_enabled(s)) + { + DEBUG_PRINT(("RTL8139: +++ cannot transmit from descriptor %d: transmitter disabled\n", + descriptor)); + return 0; + } + + if (s->TxStatus[descriptor] & TxHostOwns) + { + DEBUG_PRINT(("RTL8139: +++ cannot transmit from descriptor %d: owned by host (%08x)\n", + descriptor, s->TxStatus[descriptor])); + return 0; + } + + DEBUG_PRINT(("RTL8139: +++ transmitting from descriptor %d\n", descriptor)); + + int txsize = s->TxStatus[descriptor] & 0x1fff; + uint8_t txbuffer[0x2000]; + + DEBUG_PRINT(("RTL8139: +++ transmit reading %d bytes from host memory at 0x%08x\n", + txsize, s->TxAddr[descriptor])); + + cpu_physical_memory_read(s->TxAddr[descriptor], txbuffer, txsize); + + /* Mark descriptor as transferred */ + s->TxStatus[descriptor] |= TxHostOwns; + s->TxStatus[descriptor] |= TxStatOK; + + rtl8139_transfer_frame(s, txbuffer, txsize, 0); + + DEBUG_PRINT(("RTL8139: +++ 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)) + { + DEBUG_PRINT(("RTL8139: +++ C+ mode: transmitter disabled\n")); + return 0; + } + + if (!rtl8139_cp_transmitter_enabled(s)) + { + DEBUG_PRINT(("RTL8139: +++ C+ mode: C+ transmitter disabled\n")); + return 0 ; + } + + int descriptor = s->currCPlusTxDesc; + + target_phys_addr_t cplus_tx_ring_desc = + rtl8139_addr64(s->TxAddr[0], s->TxAddr[1]); + + /* Normal priority ring */ + cplus_tx_ring_desc += 16 * descriptor; + + DEBUG_PRINT(("RTL8139: +++ C+ mode reading TX descriptor %d from host memory at %08x0x%08x = 0x%8lx\n", + descriptor, s->TxAddr[1], s->TxAddr[0], cplus_tx_ring_desc)); + + uint32_t val, txdw0,txdw1,txbufLO,txbufHI; + + cpu_physical_memory_read(cplus_tx_ring_desc, (uint8_t *)&val, 4); + txdw0 = le32_to_cpu(val); + /* TODO: implement VLAN tagging support, VLAN tag data is read to txdw1 */ + cpu_physical_memory_read(cplus_tx_ring_desc+4, (uint8_t *)&val, 4); + txdw1 = le32_to_cpu(val); + cpu_physical_memory_read(cplus_tx_ring_desc+8, (uint8_t *)&val, 4); + txbufLO = le32_to_cpu(val); + cpu_physical_memory_read(cplus_tx_ring_desc+12, (uint8_t *)&val, 4); + txbufHI = le32_to_cpu(val); + + DEBUG_PRINT(("RTL8139: +++ C+ mode TX descriptor %d %08x %08x %08x %08x\n", + descriptor, + txdw0, txdw1, txbufLO, txbufHI)); + + /* TODO: the following discard cast should clean clang analyzer output */ + (void)txdw1; + +/* 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 tag available flag */ +#define CP_RX_TAGC (1<<17) +/* w1 bits 0...15 : VLAN tag */ +#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)) + { + DEBUG_PRINT(("RTL8139: C+ Tx mode : descriptor %d is owned by host\n", descriptor)); + return 0 ; + } + + DEBUG_PRINT(("RTL8139: +++ C+ Tx mode : transmitting from descriptor %d\n", descriptor)); + + if (txdw0 & CP_TX_FS) + { + DEBUG_PRINT(("RTL8139: +++ 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; + target_phys_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 = qemu_malloc(s->cplus_txbuffer_len); + s->cplus_txbuffer_offset = 0; + + DEBUG_PRINT(("RTL8139: +++ C+ mode transmission buffer allocated space %d\n", s->cplus_txbuffer_len)); + } + + while (s->cplus_txbuffer && s->cplus_txbuffer_offset + txsize >= s->cplus_txbuffer_len) + { + s->cplus_txbuffer_len += CP_TX_BUFFER_SIZE; + s->cplus_txbuffer = qemu_realloc(s->cplus_txbuffer, s->cplus_txbuffer_len); + + DEBUG_PRINT(("RTL8139: +++ C+ mode transmission buffer space changed to %d\n", s->cplus_txbuffer_len)); + } + + if (!s->cplus_txbuffer) + { + /* out of memory */ + + DEBUG_PRINT(("RTL8139: +++ C+ mode transmiter failed to reallocate %d bytes\n", s->cplus_txbuffer_len)); + + /* update tally counter */ + ++s->tally_counters.TxERR; + ++s->tally_counters.TxAbt; + + return 0; + } + + /* append more data to the packet */ + + DEBUG_PRINT(("RTL8139: +++ C+ mode transmit reading %d bytes from host memory at %016" PRIx64 " to offset %d\n", + txsize, (uint64_t)tx_addr, s->cplus_txbuffer_offset)); + + cpu_physical_memory_read(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); + cpu_physical_memory_write(cplus_tx_ring_desc, (uint8_t *)&val, 4); + /* TODO: implement VLAN tagging support, VLAN tag data is read to txdw1 */ +// val = cpu_to_le32(txdw1); +// cpu_physical_memory_write(cplus_tx_ring_desc+4, &val, 4); + + /* Now decide if descriptor being processed is holding the last segment of packet */ + if (txdw0 & CP_TX_LS) + { + DEBUG_PRINT(("RTL8139: +++ 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; + + /* 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)) + { + DEBUG_PRINT(("RTL8139: +++ C+ mode offloaded task checksum\n")); + + #define ETH_P_IP 0x0800 /* Internet Protocol packet */ + #define ETH_HLEN 14 + #define ETH_MTU 1500 + + /* 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) + { + DEBUG_PRINT(("RTL8139: +++ 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) { + DEBUG_PRINT(("RTL8139: +++ C+ mode packet has bad IP version %d expected %d\n", IP_HEADER_VERSION(ip), IP_HEADER_VERSION_4)); + ip = NULL; + } else { + hlen = IP_HEADER_LENGTH(ip); + ip_protocol = ip->ip_p; + ip_data_len = be16_to_cpu(ip->ip_len) - hlen; + } + } + + if (ip) + { + if (txdw0 & CP_TX_IPCS) + { + DEBUG_PRINT(("RTL8139: +++ C+ mode need IP checksum\n")); + + if (hleneth_payload_len) {/* min header length */ + /* bad packet header len */ + /* or packet too short */ + } + else + { + ip->ip_sum = 0; + ip->ip_sum = ip_checksum(ip, hlen); + DEBUG_PRINT(("RTL8139: +++ C+ mode IP header len=%d checksum=%04x\n", hlen, ip->ip_sum)); + } + } + + if ((txdw0 & CP_TX_LGSEN) && ip_protocol == IP_PROTO_TCP) + { +#if defined (DEBUG_RTL8139) + int large_send_mss = (txdw0 >> 16) & CP_TC_LGSEN_MSS_MASK; +#endif + DEBUG_PRINT(("RTL8139: +++ 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); + + /* 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; + + DEBUG_PRINT(("RTL8139: +++ 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; + } + + DEBUG_PRINT(("RTL8139: +++ 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); + + DEBUG_PRINT(("RTL8139: +++ 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); + DEBUG_PRINT(("RTL8139: +++ 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); + DEBUG_PRINT(("RTL8139: +++ 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; + DEBUG_PRINT(("RTL8139: +++ C+ mode TSO transferring packet size %d\n", tso_send_size)); + rtl8139_transfer_frame(s, saved_buffer, tso_send_size, 0); + + /* 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)) + { + DEBUG_PRINT(("RTL8139: +++ 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) + { + DEBUG_PRINT(("RTL8139: +++ 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); + DEBUG_PRINT(("RTL8139: +++ 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) + { + DEBUG_PRINT(("RTL8139: +++ 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); + DEBUG_PRINT(("RTL8139: +++ 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); + } + } + } + + /* update tally counter */ + ++s->tally_counters.TxOk; + + DEBUG_PRINT(("RTL8139: +++ C+ mode transmitting %d bytes packet\n", saved_size)); + + rtl8139_transfer_frame(s, saved_buffer, saved_size, 1); + + /* 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 + { + qemu_free(saved_buffer); + } + } + else + { + DEBUG_PRINT(("RTL8139: +++ 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) + { + DEBUG_PRINT(("RTL8139: 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) + { + DEBUG_PRINT(("RTL8139: 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) + { + DEBUG_PRINT(("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)) + { + target_phys_addr_t tc_addr = rtl8139_addr64(s->TxStatus[0] & ~0x3f, s->TxStatus[1]); + + /* dump tally counters to specified memory location */ + RTL8139TallyCounters_physical_memory_write( tc_addr, &s->tally_counters); + + /* mark dump completed */ + s->TxStatus[0] &= ~0x8; + } + + return; + } + + DEBUG_PRINT(("RTL8139: 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_read(RTL8139State *s, uint32_t txRegOffset) +{ + uint32_t ret = s->TxStatus[txRegOffset/4]; + + DEBUG_PRINT(("RTL8139: TxStatus read offset=0x%x val=0x%08x\n", txRegOffset, ret)); + + 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) ; + + + DEBUG_PRINT(("RTL8139: TSAD read val=0x%04x\n", ret)); + + return ret; +} + +static uint16_t rtl8139_CSCR_read(RTL8139State *s) +{ + uint16_t ret = s->CSCR; + + DEBUG_PRINT(("RTL8139: CSCR read val=0x%04x\n", ret)); + + return ret; +} + +static void rtl8139_TxAddr_write(RTL8139State *s, uint32_t txAddrOffset, uint32_t val) +{ + DEBUG_PRINT(("RTL8139: 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]; + + DEBUG_PRINT(("RTL8139: TxAddr read offset=0x%x val=0x%08x\n", txAddrOffset, ret)); + + return ret; +} + +static void rtl8139_RxBufPtr_write(RTL8139State *s, uint32_t val) +{ + DEBUG_PRINT(("RTL8139: RxBufPtr write val=0x%04x\n", val)); + + /* this value is off by 16 */ + s->RxBufPtr = MOD2(val + 0x10, s->RxBufferSize); + + DEBUG_PRINT((" 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; + + DEBUG_PRINT(("RTL8139: 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; + + DEBUG_PRINT(("RTL8139: RxBufAddr read val=0x%04x\n", ret)); + + return ret; +} + +static void rtl8139_RxBuf_write(RTL8139State *s, uint32_t val) +{ + DEBUG_PRINT(("RTL8139: 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; + + DEBUG_PRINT(("RTL8139: RxBuf read val=0x%08x\n", ret)); + + return ret; +} + +static void rtl8139_IntrMask_write(RTL8139State *s, uint32_t val) +{ + DEBUG_PRINT(("RTL8139: IntrMask write(w) val=0x%04x\n", val)); + + /* mask unwriteable bits */ + val = SET_MASKED(val, 0x1e00, s->IntrMask); + + s->IntrMask = val; + + rtl8139_set_next_tctr_time(s, qemu_get_clock(vm_clock)); + rtl8139_update_irq(s); + +} + +static uint32_t rtl8139_IntrMask_read(RTL8139State *s) +{ + uint32_t ret = s->IntrMask; + + DEBUG_PRINT(("RTL8139: IntrMask read(w) val=0x%04x\n", ret)); + + return ret; +} + +static void rtl8139_IntrStatus_write(RTL8139State *s, uint32_t val) +{ + DEBUG_PRINT(("RTL8139: 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 unwriteable 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; + /* + * Computing if we miss an interrupt here is not that correct but + * considered that we should have had already an interrupt + * and probably emulated is slower is better to assume this resetting was + * done before testing on previous rtl8139_update_irq lead to IRQ loosing + */ + rtl8139_set_next_tctr_time(s, qemu_get_clock(vm_clock)); + rtl8139_update_irq(s); + +#endif +} + +static uint32_t rtl8139_IntrStatus_read(RTL8139State *s) +{ + rtl8139_set_next_tctr_time(s, qemu_get_clock(vm_clock)); + + uint32_t ret = s->IntrStatus; + + DEBUG_PRINT(("RTL8139: 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) +{ + DEBUG_PRINT(("RTL8139: MultiIntr write(w) val=0x%04x\n", val)); + + /* mask unwriteable bits */ + val = SET_MASKED(val, 0xf000, s->MultiIntr); + + s->MultiIntr = val; +} + +static uint32_t rtl8139_MultiIntr_read(RTL8139State *s) +{ + uint32_t ret = s->MultiIntr; + + DEBUG_PRINT(("RTL8139: 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; + + addr &= 0xff; + + switch (addr) + { + case MAC0 ... MAC0+5: + s->phys[addr - MAC0] = val; + 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 */ + DEBUG_PRINT(("RTL8139: not implemented write(b) to MediaStatus val=0x%02x\n", val)); + break; + + case HltClk: + DEBUG_PRINT(("RTL8139: 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: + DEBUG_PRINT(("RTL8139C+ TxThresh write(b) val=0x%02x\n", val)); + s->TxThresh = val; + break; + + case TxPoll: + DEBUG_PRINT(("RTL8139C+ TxPoll write(b) val=0x%02x\n", val)); + if (val & (1 << 7)) + { + DEBUG_PRINT(("RTL8139C+ TxPoll high priority transmission (not implemented)\n")); + //rtl8139_cplus_transmit(s); + } + if (val & (1 << 6)) + { + DEBUG_PRINT(("RTL8139C+ TxPoll normal priority transmission\n")); + rtl8139_cplus_transmit(s); + } + + break; + + default: + DEBUG_PRINT(("RTL8139: 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; + + addr &= 0xfe; + + 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: + DEBUG_PRINT(("RTL8139: NWayAdvert write(w) val=0x%04x\n", val)); + s->NWayAdvert = val; + break; + case NWayLPAR: + DEBUG_PRINT(("RTL8139: forbidden NWayLPAR write(w) val=0x%04x\n", val)); + break; + case NWayExpansion: + DEBUG_PRINT(("RTL8139: 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: + DEBUG_PRINT(("RTL8139: 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, int64_t current_time) +{ + int64_t pci_time, next_time; + uint32_t low_pci; + + DEBUG_PRINT(("RTL8139: entered rtl8139_set_next_tctr_time\n")); + + if (s->TimerExpire && current_time >= s->TimerExpire) { + s->IntrStatus |= PCSTimeout; + rtl8139_update_irq(s); + } + + /* Set QEMU timer only if needed that is + * - TimerInt <> 0 (we have a timer) + * - mask = 1 (we want an interrupt timer) + * - irq = 0 (irq is not already active) + * If any of above change we need to compute timer again + * Also we must check if timer is passed without QEMU timer + */ + s->TimerExpire = 0; + if (!s->TimerInt) { + return; + } + + pci_time = muldiv64(current_time - s->TCTR_base, PCI_FREQUENCY, + get_ticks_per_sec()); + low_pci = pci_time & 0xffffffff; + pci_time = pci_time - low_pci + s->TimerInt; + if (low_pci >= s->TimerInt) { + pci_time += 0x100000000LL; + } + next_time = s->TCTR_base + muldiv64(pci_time, get_ticks_per_sec(), + PCI_FREQUENCY); + s->TimerExpire = next_time; + + if ((s->IntrMask & PCSTimeout) != 0 && (s->IntrStatus & PCSTimeout) == 0) { + qemu_mod_timer(s->timer, next_time); + } +} + +static void rtl8139_io_writel(void *opaque, uint8_t addr, uint32_t val) +{ + RTL8139State *s = opaque; + + addr &= 0xfc; + + switch (addr) + { + case RxMissed: + DEBUG_PRINT(("RTL8139: 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: + DEBUG_PRINT(("RTL8139: C+ RxRing low bits write val=0x%08x\n", val)); + s->RxRingAddrLO = val; + break; + + case RxRingAddrHI: + DEBUG_PRINT(("RTL8139: C+ RxRing high bits write val=0x%08x\n", val)); + s->RxRingAddrHI = val; + break; + + case Timer: + DEBUG_PRINT(("RTL8139: TCTR Timer reset on write\n")); + s->TCTR_base = qemu_get_clock(vm_clock); + rtl8139_set_next_tctr_time(s, s->TCTR_base); + break; + + case FlashReg: + DEBUG_PRINT(("RTL8139: FlashReg TimerInt write val=0x%08x\n", val)); + if (s->TimerInt != val) { + s->TimerInt = val; + rtl8139_set_next_tctr_time(s, qemu_get_clock(vm_clock)); + } + break; + + default: + DEBUG_PRINT(("RTL8139: 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; + + addr &= 0xff; + + 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 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: + ret = 0xd0; + DEBUG_PRINT(("RTL8139: MediaStatus read 0x%x\n", ret)); + break; + + case HltClk: + ret = s->clock_enabled; + DEBUG_PRINT(("RTL8139: HltClk read 0x%x\n", ret)); + break; + + case PCIRevisionID: + ret = RTL8139_PCI_REVID; + DEBUG_PRINT(("RTL8139: PCI Revision ID read 0x%x\n", ret)); + break; + + case TxThresh: + ret = s->TxThresh; + DEBUG_PRINT(("RTL8139C+ 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; + DEBUG_PRINT(("RTL8139C TxConfig at 0x43 read(b) val=0x%02x\n", ret)); + break; + + default: + DEBUG_PRINT(("RTL8139: 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; + + addr &= 0xfe; /* mask lower bit */ + + switch (addr) + { + 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; + DEBUG_PRINT(("RTL8139: NWayAdvert read(w) val=0x%04x\n", ret)); + break; + case NWayLPAR: + ret = s->NWayLPAR; + DEBUG_PRINT(("RTL8139: NWayLPAR read(w) val=0x%04x\n", ret)); + break; + case NWayExpansion: + ret = s->NWayExpansion; + DEBUG_PRINT(("RTL8139: 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: + DEBUG_PRINT(("RTL8139: 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; + + DEBUG_PRINT(("RTL8139: 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; + + addr &= 0xfc; /* also mask low 2 bits */ + + switch (addr) + { + case RxMissed: + ret = s->RxMissed; + + DEBUG_PRINT(("RTL8139: 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_read(s, addr-TxStatus0); + 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; + DEBUG_PRINT(("RTL8139: C+ RxRing low bits read val=0x%08x\n", ret)); + break; + + case RxRingAddrHI: + ret = s->RxRingAddrHI; + DEBUG_PRINT(("RTL8139: C+ RxRing high bits read val=0x%08x\n", ret)); + break; + + case Timer: + ret = muldiv64(qemu_get_clock(vm_clock) - s->TCTR_base, + PCI_FREQUENCY, get_ticks_per_sec()); + DEBUG_PRINT(("RTL8139: TCTR Timer read val=0x%08x\n", ret)); + break; + + case FlashReg: + ret = s->TimerInt; + DEBUG_PRINT(("RTL8139: FlashReg TimerInt read val=0x%08x\n", ret)); + break; + + default: + DEBUG_PRINT(("RTL8139: 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; + + DEBUG_PRINT(("RTL8139: read(l) addr=0x%x val=%08x\n", addr, ret)); + break; + } + + return ret; +} + +/* */ + +static void rtl8139_ioport_writeb(void *opaque, uint32_t addr, uint32_t val) +{ + rtl8139_io_writeb(opaque, addr & 0xFF, val); +} + +static void rtl8139_ioport_writew(void *opaque, uint32_t addr, uint32_t val) +{ + rtl8139_io_writew(opaque, addr & 0xFF, val); +} + +static void rtl8139_ioport_writel(void *opaque, uint32_t addr, uint32_t val) +{ + rtl8139_io_writel(opaque, addr & 0xFF, val); +} + +static uint32_t rtl8139_ioport_readb(void *opaque, uint32_t addr) +{ + return rtl8139_io_readb(opaque, addr & 0xFF); +} + +static uint32_t rtl8139_ioport_readw(void *opaque, uint32_t addr) +{ + return rtl8139_io_readw(opaque, addr & 0xFF); +} + +static uint32_t rtl8139_ioport_readl(void *opaque, uint32_t addr) +{ + return rtl8139_io_readl(opaque, addr & 0xFF); +} + +/* */ + +static void rtl8139_mmio_writeb(void *opaque, target_phys_addr_t addr, uint32_t val) +{ + rtl8139_io_writeb(opaque, addr & 0xFF, val); +} + +static void rtl8139_mmio_writew(void *opaque, target_phys_addr_t addr, uint32_t val) +{ + rtl8139_io_writew(opaque, addr & 0xFF, val); +} + +static void rtl8139_mmio_writel(void *opaque, target_phys_addr_t addr, uint32_t val) +{ + rtl8139_io_writel(opaque, addr & 0xFF, val); +} + +static uint32_t rtl8139_mmio_readb(void *opaque, target_phys_addr_t addr) +{ + return rtl8139_io_readb(opaque, addr & 0xFF); +} + +static uint32_t rtl8139_mmio_readw(void *opaque, target_phys_addr_t addr) +{ + uint32_t val = rtl8139_io_readw(opaque, addr & 0xFF); + return val; +} + +static uint32_t rtl8139_mmio_readl(void *opaque, target_phys_addr_t 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, qemu_get_clock(vm_clock)); + if (version_id < 4) { + s->cplus_enabled = s->CpCmd != 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, + .minimum_version_id_old = 1, + .fields = (VMStateField []) { + VMSTATE_END_OF_LIST() + } +}; + +static void rtl8139_pre_save(void *opaque) +{ + RTL8139State* s = opaque; + int64_t current_time = qemu_get_clock(vm_clock); + + /* set IntrStatus correctly */ + rtl8139_set_next_tctr_time(s, current_time); + s->TCTR = muldiv64(current_time - s->TCTR_base, PCI_FREQUENCY, + get_ticks_per_sec()); + s->rtl8139_mmio_io_addr_dummy = s->rtl8139_mmio_io_addr; +} + +static const VMStateDescription vmstate_rtl8139 = { + .name = "rtl8139", + .version_id = 4, + .minimum_version_id = 3, + .minimum_version_id_old = 3, + .post_load = rtl8139_post_load, + .pre_save = rtl8139_pre_save, + .fields = (VMStateField []) { + VMSTATE_PCI_DEVICE(dev, 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 = (VMStateSubsection []) { + { + .vmsd = &vmstate_rtl8139_hotplug_ready, + .needed = rtl8139_hotplug_ready_needed, + }, { + /* empty */ + } + } +}; + +/***********************************************************/ +/* PCI RTL8139 definitions */ + +static void rtl8139_mmio_map(PCIDevice *pci_dev, int region_num, + pcibus_t addr, pcibus_t size, int type) +{ + RTL8139State *s = DO_UPCAST(RTL8139State, dev, pci_dev); + + cpu_register_physical_memory(addr + 0, 0x100, s->rtl8139_mmio_io_addr); +} + +static void rtl8139_ioport_map(PCIDevice *pci_dev, int region_num, + pcibus_t addr, pcibus_t size, int type) +{ + RTL8139State *s = DO_UPCAST(RTL8139State, dev, pci_dev); + + register_ioport_write(addr, 0x100, 1, rtl8139_ioport_writeb, s); + register_ioport_read( addr, 0x100, 1, rtl8139_ioport_readb, s); + + register_ioport_write(addr, 0x100, 2, rtl8139_ioport_writew, s); + register_ioport_read( addr, 0x100, 2, rtl8139_ioport_readw, s); + + register_ioport_write(addr, 0x100, 4, rtl8139_ioport_writel, s); + register_ioport_read( addr, 0x100, 4, rtl8139_ioport_readl, s); +} + +static CPUReadMemoryFunc * const rtl8139_mmio_read[3] = { + rtl8139_mmio_readb, + rtl8139_mmio_readw, + rtl8139_mmio_readl, +}; + +static CPUWriteMemoryFunc * const rtl8139_mmio_write[3] = { + rtl8139_mmio_writeb, + rtl8139_mmio_writew, + rtl8139_mmio_writel, +}; + +static void rtl8139_timer(void *opaque) +{ + RTL8139State *s = opaque; + + if (!s->clock_enabled) + { + DEBUG_PRINT(("RTL8139: >>> timer: clock is not running\n")); + return; + } + + s->IntrStatus |= PCSTimeout; + rtl8139_update_irq(s); + rtl8139_set_next_tctr_time(s, qemu_get_clock(vm_clock)); +} + +static void rtl8139_cleanup(VLANClientState *nc) +{ + RTL8139State *s = DO_UPCAST(NICState, nc, nc)->opaque; + + s->nic = NULL; +} + +static int pci_rtl8139_uninit(PCIDevice *dev) +{ + RTL8139State *s = DO_UPCAST(RTL8139State, dev, dev); + + cpu_unregister_io_memory(s->rtl8139_mmio_io_addr); + if (s->cplus_txbuffer) { + qemu_free(s->cplus_txbuffer); + s->cplus_txbuffer = NULL; + } + qemu_del_timer(s->timer); + qemu_free_timer(s->timer); + qemu_del_vlan_client(&s->nic->nc); + return 0; +} + +static NetClientInfo net_rtl8139_info = { + .type = NET_CLIENT_TYPE_NIC, + .size = sizeof(NICState), + .can_receive = rtl8139_can_receive, + .receive = rtl8139_receive, + .cleanup = rtl8139_cleanup, +}; + +static int pci_rtl8139_init(PCIDevice *dev) +{ + RTL8139State * s = DO_UPCAST(RTL8139State, dev, dev); + uint8_t *pci_conf; + + pci_conf = s->dev.config; + pci_config_set_vendor_id(pci_conf, PCI_VENDOR_ID_REALTEK); + pci_config_set_device_id(pci_conf, PCI_DEVICE_ID_REALTEK_8139); + pci_conf[PCI_REVISION_ID] = RTL8139_PCI_REVID; /* >=0x20 is for 8139C+ */ + pci_config_set_class(pci_conf, PCI_CLASS_NETWORK_ETHERNET); + pci_conf[PCI_INTERRUPT_PIN] = 1; /* interrupt pin 0 */ + /* TODO: start of capability list, but no capability + * list bit in status register, and offset 0xdc seems unused. */ + pci_conf[PCI_CAPABILITY_LIST] = 0xdc; + + /* I/O handler for memory-mapped I/O */ + s->rtl8139_mmio_io_addr = + cpu_register_io_memory(rtl8139_mmio_read, rtl8139_mmio_write, s, + DEVICE_LITTLE_ENDIAN); + + pci_register_bar(&s->dev, 0, 0x100, + PCI_BASE_ADDRESS_SPACE_IO, rtl8139_ioport_map); + + pci_register_bar(&s->dev, 1, 0x100, + PCI_BASE_ADDRESS_SPACE_MEMORY, rtl8139_mmio_map); + + qemu_macaddr_default_if_unset(&s->conf.macaddr); + + s->nic = qemu_new_nic(&net_rtl8139_info, &s->conf, + dev->qdev.info->name, dev->qdev.id, s); + qemu_format_nic_info_str(&s->nic->nc, s->conf.macaddr.a); + + s->cplus_txbuffer = NULL; + s->cplus_txbuffer_len = 0; + s->cplus_txbuffer_offset = 0; + + s->TimerExpire = 0; + s->timer = qemu_new_timer(vm_clock, rtl8139_timer, s); + rtl8139_set_next_tctr_time(s, qemu_get_clock(vm_clock)); + + add_boot_device_path(s->conf.bootindex, &dev->qdev, "/ethernet-phy@0"); + + return 0; +} + +static PCIDeviceInfo rtl8139_info = { + .qdev.name = "rtl8139", + .qdev.size = sizeof(RTL8139State), + .qdev.reset = rtl8139_reset, + .qdev.vmsd = &vmstate_rtl8139, + .init = pci_rtl8139_init, + .exit = pci_rtl8139_uninit, + .romfile = "pxe-rtl8139.bin", + .qdev.props = (Property[]) { + DEFINE_NIC_PROPERTIES(RTL8139State, conf), + DEFINE_PROP_END_OF_LIST(), + } +}; + +static void rtl8139_register_devices(void) +{ + pci_qdev_register(&rtl8139_info); +} + +device_init(rtl8139_register_devices) diff --git a/hw/s390-virtio-bus.c b/hw/s390-virtio-bus.c new file mode 100644 index 0000000..784dc01 --- /dev/null +++ b/hw/s390-virtio-bus.c @@ -0,0 +1,409 @@ +/* + * QEMU S390 virtio target + * + * Copyright (c) 2009 Alexander Graf + * + * 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 . + */ + +#include "hw.h" +#include "block.h" +#include "sysemu.h" +#include "net.h" +#include "boards.h" +#include "monitor.h" +#include "loader.h" +#include "elf.h" +#include "hw/virtio.h" +#include "hw/virtio-serial.h" +#include "hw/virtio-net.h" +#include "hw/sysbus.h" +#include "kvm.h" + +#include "hw/s390-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 + +struct BusInfo s390_virtio_bus_info = { + .name = "s390-virtio", + .size = sizeof(VirtIOS390Bus), +}; + +typedef struct { + DeviceInfo qdev; + int (*init)(VirtIOS390Device *dev); +} VirtIOS390DeviceInfo; + + +static const VirtIOBindings virtio_s390_bindings; + +static ram_addr_t s390_virtio_device_num_vq(VirtIOS390Device *dev); + +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(&s390_virtio_bus_info, 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; + + /* Allocate RAM for VirtIO device pages (descriptors, queues, rings) */ + *ram_size += S390_DEVICE_PAGES * TARGET_PAGE_SIZE; + + return bus; +} + +static int 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 += vdev->config_len; + + bus->dev_offs += dev_len; + + virtio_bind_device(vdev, &virtio_s390_bindings, dev); + dev->host_features = vdev->get_features(vdev, dev->host_features); + s390_virtio_device_sync(dev); + + return 0; +} + +static int s390_virtio_net_init(VirtIOS390Device *dev) +{ + VirtIODevice *vdev; + + vdev = virtio_net_init((DeviceState *)dev, &dev->nic, &dev->net); + if (!vdev) { + return -1; + } + + return s390_virtio_device_init(dev, vdev); +} + +static int s390_virtio_blk_init(VirtIOS390Device *dev) +{ + VirtIODevice *vdev; + + vdev = virtio_blk_init((DeviceState *)dev, &dev->block); + if (!vdev) { + return -1; + } + + return s390_virtio_device_init(dev, vdev); +} + +static int s390_virtio_serial_init(VirtIOS390Device *dev) +{ + VirtIOS390Bus *bus; + VirtIODevice *vdev; + int r; + + bus = DO_UPCAST(VirtIOS390Bus, bus, dev->qdev.parent_bus); + + vdev = virtio_serial_init((DeviceState *)dev, dev->max_virtserial_ports); + if (!vdev) { + return -1; + } + + r = s390_virtio_device_init(dev, vdev); + if (!r) { + bus->console = dev; + } + + return r; +} + +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 ldq_phys(token_off); +} + +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_PCI_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 */ + stb_phys(dev->dev_offs + VIRTIO_DEV_OFFS_TYPE, dev->vdev->device_id); + + stb_phys(dev->dev_offs + VIRTIO_DEV_OFFS_NUM_VQ, s390_virtio_device_num_vq(dev)); + stb_phys(dev->dev_offs + VIRTIO_DEV_OFFS_FEATURE_LEN, dev->feat_len); + + stb_phys(dev->dev_offs + VIRTIO_DEV_OFFS_CONFIG_LEN, dev->vdev->config_len); + + num_vq = s390_virtio_device_num_vq(dev); + stb_phys(dev->dev_offs + VIRTIO_DEV_OFFS_NUM_VQ, num_vq); + + /* 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); + stq_phys(vq + VIRTIO_VQCONFIG_OFFS_ADDRESS, vring); + stw_phys(vq + VIRTIO_VQCONFIG_OFFS_NUM, virtio_queue_get_num(dev->vdev, i)); + } + + cur_offs = dev->dev_offs; + cur_offs += VIRTIO_DEV_OFFS_CONFIG; + cur_offs += num_vq * VIRTIO_VQCONFIG_LEN; + + /* Sync feature bitmap */ + stl_phys(cur_offs, dev->host_features); + + dev->feat_offs = cur_offs + dev->feat_len; + cur_offs += dev->feat_len * 2; + + /* Sync config space */ + if (dev->vdev->get_config) { + dev->vdev->get_config(dev->vdev, dev->vdev->config); + } + + cpu_physical_memory_rw(cur_offs, dev->vdev->config, dev->vdev->config_len, 1); + 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, ldub_phys(dev->dev_offs + VIRTIO_DEV_OFFS_STATUS)); + + /* Update guest supported feature bitmap */ + + features = ldl_phys(dev->feat_offs); + if (vdev->set_features) { + vdev->set_features(vdev, features); + } + vdev->guest_features = features; +} + +VirtIOS390Device *s390_virtio_bus_console(VirtIOS390Bus *bus) +{ + return bus->console; +} + +/* Find a device by vring address */ +VirtIOS390Device *s390_virtio_bus_find_vring(VirtIOS390Bus *bus, + ram_addr_t mem, + int *vq_num) +{ + VirtIOS390Device *_dev; + DeviceState *dev; + int i; + + QLIST_FOREACH(dev, &bus->bus.children, sibling) { + _dev = (VirtIOS390Device *)dev; + for(i = 0; i < VIRTIO_PCI_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) +{ + VirtIOS390Device *_dev; + DeviceState *dev; + + QLIST_FOREACH(dev, &bus->bus.children, sibling) { + _dev = (VirtIOS390Device *)dev; + if (_dev->dev_offs == mem) { + return _dev; + } + } + + return NULL; +} + +static void virtio_s390_notify(void *opaque, uint16_t vector) +{ + VirtIOS390Device *dev = (VirtIOS390Device*)opaque; + uint64_t token = s390_virtio_device_vq_token(dev, vector); + + /* XXX kvm dependency! */ + kvm_s390_virtio_irq(s390_cpu_addr2state(0), 0, token); +} + +static unsigned virtio_s390_get_features(void *opaque) +{ + VirtIOS390Device *dev = (VirtIOS390Device*)opaque; + return dev->host_features; +} + +/**************** S390 Virtio Bus Device Descriptions *******************/ + +static const VirtIOBindings virtio_s390_bindings = { + .notify = virtio_s390_notify, + .get_features = virtio_s390_get_features, +}; + +static VirtIOS390DeviceInfo s390_virtio_net = { + .init = s390_virtio_net_init, + .qdev.name = "virtio-net-s390", + .qdev.size = sizeof(VirtIOS390Device), + .qdev.props = (Property[]) { + DEFINE_NIC_PROPERTIES(VirtIOS390Device, nic), + DEFINE_PROP_UINT32("x-txtimer", VirtIOS390Device, + net.txtimer, TX_TIMER_INTERVAL), + DEFINE_PROP_INT32("x-txburst", VirtIOS390Device, + net.txburst, TX_BURST), + DEFINE_PROP_STRING("tx", VirtIOS390Device, net.tx), + DEFINE_PROP_END_OF_LIST(), + }, +}; + +static VirtIOS390DeviceInfo s390_virtio_blk = { + .init = s390_virtio_blk_init, + .qdev.name = "virtio-blk-s390", + .qdev.size = sizeof(VirtIOS390Device), + .qdev.props = (Property[]) { + DEFINE_BLOCK_PROPERTIES(VirtIOS390Device, block), + DEFINE_PROP_END_OF_LIST(), + }, +}; + +static VirtIOS390DeviceInfo s390_virtio_serial = { + .init = s390_virtio_serial_init, + .qdev.name = "virtio-serial-s390", + .qdev.alias = "virtio-serial", + .qdev.size = sizeof(VirtIOS390Device), + .qdev.props = (Property[]) { + DEFINE_PROP_UINT32("max_ports", VirtIOS390Device, max_virtserial_ports, + 31), + DEFINE_PROP_END_OF_LIST(), + }, +}; + +static int s390_virtio_busdev_init(DeviceState *dev, DeviceInfo *info) +{ + VirtIOS390DeviceInfo *_info = (VirtIOS390DeviceInfo *)info; + VirtIOS390Device *_dev = (VirtIOS390Device *)dev; + + return _info->init(_dev); +} + +static void s390_virtio_bus_register_withprop(VirtIOS390DeviceInfo *info) +{ + info->qdev.init = s390_virtio_busdev_init; + info->qdev.bus_info = &s390_virtio_bus_info; + + assert(info->qdev.size >= sizeof(VirtIOS390Device)); + qdev_register(&info->qdev); +} + +static void s390_virtio_register(void) +{ + s390_virtio_bus_register_withprop(&s390_virtio_serial); + s390_virtio_bus_register_withprop(&s390_virtio_blk); + s390_virtio_bus_register_withprop(&s390_virtio_net); +} +device_init(s390_virtio_register); + + +/***************** 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 SysBusDeviceInfo s390_virtio_bridge_info = { + .init = s390_virtio_bridge_init, + .qdev.name = "s390-virtio-bridge", + .qdev.size = sizeof(SysBusDevice), + .qdev.no_user = 1, +}; + +static void s390_virtio_register_devices(void) +{ + sysbus_register_withprop(&s390_virtio_bridge_info); +} + +device_init(s390_virtio_register_devices) diff --git a/hw/s390-virtio-bus.h b/hw/s390-virtio-bus.h new file mode 100644 index 0000000..33379a3 --- /dev/null +++ b/hw/s390-virtio-bus.h @@ -0,0 +1,69 @@ +/* + * QEMU S390x VirtIO BUS definitions + * + * Copyright (c) 2009 Alexander Graf + * + * 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 . + */ + +#include "virtio-net.h" + +#define VIRTIO_DEV_OFFS_TYPE 0 /* 8 bits */ +#define VIRTIO_DEV_OFFS_NUM_VQ 1 /* 8 bits */ +#define VIRTIO_DEV_OFFS_FEATURE_LEN 2 /* 8 bits */ +#define VIRTIO_DEV_OFFS_CONFIG_LEN 3 /* 8 bits */ +#define VIRTIO_DEV_OFFS_STATUS 4 /* 8 bits */ +#define VIRTIO_DEV_OFFS_CONFIG 5 /* dynamic */ + +#define VIRTIO_VQCONFIG_OFFS_TOKEN 0 /* 64 bits */ +#define VIRTIO_VQCONFIG_OFFS_ADDRESS 8 /* 64 bits */ +#define VIRTIO_VQCONFIG_OFFS_NUM 16 /* 16 bits */ +#define VIRTIO_VQCONFIG_LEN 24 + +#define VIRTIO_RING_LEN (TARGET_PAGE_SIZE * 3) +#define S390_DEVICE_PAGES 256 + +typedef struct VirtIOS390Device { + DeviceState qdev; + ram_addr_t dev_offs; + ram_addr_t feat_offs; + uint8_t feat_len; + VirtIODevice *vdev; + BlockConf block; + NICConf nic; + uint32_t host_features; + /* Max. number of ports we can have for a the virtio-serial device */ + uint32_t max_virtserial_ports; + virtio_net_conf net; +} VirtIOS390Device; + +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); + +VirtIOS390Device *s390_virtio_bus_console(VirtIOS390Bus *bus); +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); diff --git a/hw/s390-virtio.c b/hw/s390-virtio.c new file mode 100644 index 0000000..f29b624 --- /dev/null +++ b/hw/s390-virtio.c @@ -0,0 +1,289 @@ +/* + * QEMU S390 virtio target + * + * Copyright (c) 2009 Alexander Graf + * + * 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 . + */ + +#include "hw.h" +#include "block.h" +#include "blockdev.h" +#include "sysemu.h" +#include "net.h" +#include "boards.h" +#include "monitor.h" +#include "loader.h" +#include "elf.h" +#include "hw/virtio.h" +#include "hw/sysbus.h" +#include "kvm.h" + +#include "hw/s390-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 KVM_S390_VIRTIO_NOTIFY 0 +#define KVM_S390_VIRTIO_RESET 1 +#define KVM_S390_VIRTIO_SET_STATUS 2 + +#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_START 0x009000UL +#define ZIPL_LOAD_ADDR 0x009000UL +#define ZIPL_FILENAME "s390-zipl.rom" + +#define MAX_BLK_DEVS 10 + +static VirtIOS390Bus *s390_bus; +static CPUState **ipi_states; + +void irq_info(Monitor *mon); +void pic_info(Monitor *mon); + +void irq_info(Monitor *mon) +{ +} + +void pic_info(Monitor *mon) +{ +} + +CPUState *s390_cpu_addr2state(uint16_t cpu_addr) +{ + if (cpu_addr >= smp_cpus) { + return NULL; + } + + return ipi_states[cpu_addr]; +} + +int s390_virtio_hypercall(CPUState *env) +{ + int r = 0, i; + target_ulong mem = env->regs[2]; + + dprintf("KVM hypercall: %ld\n", env->regs[1]); + switch (env->regs[1]) { + case KVM_S390_VIRTIO_NOTIFY: + if (mem > ram_size) { + VirtIOS390Device *dev = s390_virtio_bus_find_vring(s390_bus, + mem, &i); + if (dev) { + virtio_queue_notify(dev->vdev, i); + } else { + r = -EINVAL; + } + } else { + /* Early printk */ + } + break; + case KVM_S390_VIRTIO_RESET: + { + VirtIOS390Device *dev; + + dev = s390_virtio_bus_find_mem(s390_bus, mem); + virtio_reset(dev->vdev); + s390_virtio_device_sync(dev); + break; + } + case KVM_S390_VIRTIO_SET_STATUS: + { + VirtIOS390Device *dev; + + dev = s390_virtio_bus_find_mem(s390_bus, mem); + if (dev) { + s390_virtio_device_update_status(dev); + } else { + r = -EINVAL; + } + break; + } + default: + r = -EINVAL; + break; + } + + env->regs[2] = r; + return 0; +} + +/* PC hardware initialisation */ +static void s390_init(ram_addr_t ram_size, + const char *boot_device, + const char *kernel_filename, + const char *kernel_cmdline, + const char *initrd_filename, + const char *cpu_model) +{ + CPUState *env = NULL; + ram_addr_t ram_addr; + ram_addr_t kernel_size = 0; + ram_addr_t initrd_offset; + ram_addr_t initrd_size = 0; + int i; + + /* XXX we only work on KVM for now */ + + if (!kvm_enabled()) { + fprintf(stderr, "The S390 target only works with KVM enabled\n"); + exit(1); + } + + /* get a BUS */ + s390_bus = s390_virtio_bus_init(&ram_size); + + /* allocate RAM */ + ram_addr = qemu_ram_alloc(NULL, "s390.ram", ram_size); + cpu_register_physical_memory(0, ram_size, ram_addr); + + /* init CPUs */ + if (cpu_model == NULL) { + cpu_model = "host"; + } + + ipi_states = qemu_malloc(sizeof(CPUState *) * smp_cpus); + + for (i = 0; i < smp_cpus; i++) { + CPUState *tmp_env; + + tmp_env = cpu_init(cpu_model); + if (!env) { + env = tmp_env; + } + ipi_states[i] = tmp_env; + tmp_env->halted = 1; + tmp_env->exception_index = EXCP_HLT; + } + + env->halted = 0; + env->exception_index = 0; + + if (kernel_filename) { + kernel_size = load_image(kernel_filename, qemu_get_ram_ptr(0)); + + if (lduw_phys(KERN_IMAGE_START) != 0x0dd0) { + fprintf(stderr, "Specified image is not an s390 boot image\n"); + exit(1); + } + + env->psw.addr = KERN_IMAGE_START; + env->psw.mask = 0x0000000180000000ULL; + } else { + ram_addr_t bios_size = 0; + char *bios_filename; + + /* Load zipl bootloader */ + if (bios_name == NULL) { + bios_name = ZIPL_FILENAME; + } + + bios_filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, bios_name); + bios_size = load_image(bios_filename, qemu_get_ram_ptr(ZIPL_LOAD_ADDR)); + + if ((long)bios_size < 0) { + hw_error("could not load bootloader '%s'\n", bios_name); + } + + if (bios_size > 4096) { + hw_error("stage1 bootloader is > 4k\n"); + } + + env->psw.addr = ZIPL_START; + env->psw.mask = 0x0000000180000000ULL; + } + + if (initrd_filename) { + initrd_offset = INITRD_START; + while (kernel_size + 0x100000 > initrd_offset) { + initrd_offset += 0x100000; + } + initrd_size = load_image(initrd_filename, qemu_get_ram_ptr(initrd_offset)); + + stq_phys(INITRD_PARM_START, initrd_offset); + stq_phys(INITRD_PARM_SIZE, initrd_size); + } + + if (kernel_cmdline) { + cpu_physical_memory_rw(KERN_PARM_AREA, (uint8_t *)kernel_cmdline, + strlen(kernel_cmdline), 1); + } + + /* Create VirtIO network adapters */ + for(i = 0; i < nb_nics; i++) { + NICInfo *nd = &nd_table[i]; + DeviceState *dev; + + if (!nd->model) { + nd->model = qemu_strdup("virtio"); + } + + if (strcmp(nd->model, "virtio")) { + fprintf(stderr, "S390 only supports VirtIO nics\n"); + exit(1); + } + + dev = qdev_create((BusState *)s390_bus, "virtio-net-s390"); + qdev_set_nic_properties(dev, nd); + qdev_init_nofail(dev); + } + + /* Create VirtIO disk drives */ + for(i = 0; i < MAX_BLK_DEVS; i++) { + DriveInfo *dinfo; + DeviceState *dev; + + dinfo = drive_get(IF_IDE, 0, i); + if (!dinfo) { + continue; + } + + dev = qdev_create((BusState *)s390_bus, "virtio-blk-s390"); + qdev_prop_set_drive_nofail(dev, "drive", dinfo->bdrv); + qdev_init_nofail(dev); + } +} + +static QEMUMachine s390_machine = { + .name = "s390-virtio", + .alias = "s390", + .desc = "VirtIO based S390 machine", + .init = s390_init, + .no_serial = 1, + .no_parallel = 1, + .use_virtcon = 1, + .no_vga = 1, + .max_cpus = 255, + .is_default = 1, +}; + +static void s390_machine_init(void) +{ + qemu_register_machine(&s390_machine); +} + +machine_init(s390_machine_init); diff --git a/hw/sb16.c b/hw/sb16.c new file mode 100644 index 0000000..c9d37ad --- /dev/null +++ b/hw/sb16.c @@ -0,0 +1,1420 @@ +/* + * 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.h" +#include "audiodev.h" +#include "audio/audio.h" +#include "isa.h" +#include "qdev.h" +#include "qemu-timer.h" +#include "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."; + +typedef struct SB16State { + ISADevice dev; + 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) { + qemu_mod_timer ( + s->aux_ts, + qemu_get_clock (vm_clock) + 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; + return; +} + +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 (0 == s->needed_bytes) { + 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_WRITE_PROTO (mixer_write_indexw) +{ + mixer_write_indexb (opaque, nport, val & 0xff); + mixer_write_datab (opaque, nport, (val >> 8) & 0xff); +} + +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 (0 == s->dma_auto) { + 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 (0 == s->dma_auto) { + 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, + .minimum_version_id_old = 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 int sb16_initfn (ISADevice *dev) +{ + static const uint8_t dsp_write_ports[] = {0x6, 0xc}; + static const uint8_t dsp_read_ports[] = {0x6, 0xa, 0xc, 0xd, 0xe, 0xf}; + SB16State *s; + int i; + + s = DO_UPCAST (SB16State, dev, dev); + + s->cmd = -1; + isa_init_irq (dev, &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 = qemu_new_timer (vm_clock, aux_timer, s); + if (!s->aux_ts) { + dolog ("warning: Could not create auxiliary timer\n"); + } + + for (i = 0; i < ARRAY_SIZE (dsp_write_ports); i++) { + register_ioport_write (s->port + dsp_write_ports[i], 1, 1, dsp_write, s); + isa_init_ioport(dev, s->port + dsp_write_ports[i]); + } + + for (i = 0; i < ARRAY_SIZE (dsp_read_ports); i++) { + register_ioport_read (s->port + dsp_read_ports[i], 1, 1, dsp_read, s); + isa_init_ioport(dev, s->port + dsp_read_ports[i]); + } + + register_ioport_write (s->port + 0x4, 1, 1, mixer_write_indexb, s); + register_ioport_write (s->port + 0x4, 1, 2, mixer_write_indexw, s); + isa_init_ioport(dev, s->port + 0x4); + register_ioport_read (s->port + 0x5, 1, 1, mixer_read, s); + register_ioport_write (s->port + 0x5, 1, 1, mixer_write_datab, s); + isa_init_ioport(dev, s->port + 0x5); + + 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); + return 0; +} + +int SB16_init (qemu_irq *pic) +{ + isa_create_simple ("sb16"); + return 0; +} + +static ISADeviceInfo sb16_info = { + .qdev.name = "sb16", + .qdev.desc = "Creative Sound Blaster 16", + .qdev.size = sizeof (SB16State), + .qdev.vmsd = &vmstate_sb16, + .init = sb16_initfn, + .qdev.props = (Property[]) { + DEFINE_PROP_HEX32 ("version", SB16State, ver, 0x0405), /* 4.5 */ + DEFINE_PROP_HEX32 ("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_register (void) +{ + isa_qdev_register (&sb16_info); +} +device_init (sb16_register) diff --git a/hw/sbi.c b/hw/sbi.c new file mode 100644 index 0000000..53f66f2 --- /dev/null +++ b/hw/sbi.c @@ -0,0 +1,148 @@ +/* + * QEMU Sparc SBI interrupt controller emulation + * + * Based on slavio_intctl, 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 "sysbus.h" + +//#define DEBUG_IRQ + +#ifdef DEBUG_IRQ +#define DPRINTF(fmt, ...) \ + do { printf("IRQ: " fmt , ## __VA_ARGS__); } while (0) +#else +#define DPRINTF(fmt, ...) +#endif + +#define MAX_CPUS 16 + +#define SBI_NREGS 16 + +typedef struct SBIState { + SysBusDevice busdev; + uint32_t regs[SBI_NREGS]; + uint32_t intreg_pending[MAX_CPUS]; + qemu_irq cpu_irqs[MAX_CPUS]; + uint32_t pil_out[MAX_CPUS]; +} SBIState; + +#define SBI_SIZE (SBI_NREGS * 4) + +static void sbi_set_irq(void *opaque, int irq, int level) +{ +} + +static uint32_t sbi_mem_readl(void *opaque, target_phys_addr_t addr) +{ + SBIState *s = opaque; + uint32_t saddr, ret; + + saddr = addr >> 2; + switch (saddr) { + default: + ret = s->regs[saddr]; + break; + } + DPRINTF("read system reg 0x" TARGET_FMT_plx " = %x\n", addr, ret); + + return ret; +} + +static void sbi_mem_writel(void *opaque, target_phys_addr_t addr, uint32_t val) +{ + SBIState *s = opaque; + uint32_t saddr; + + saddr = addr >> 2; + DPRINTF("write system reg 0x" TARGET_FMT_plx " = %x\n", addr, val); + switch (saddr) { + default: + s->regs[saddr] = val; + break; + } +} + +static CPUReadMemoryFunc * const sbi_mem_read[3] = { + NULL, + NULL, + sbi_mem_readl, +}; + +static CPUWriteMemoryFunc * const sbi_mem_write[3] = { + NULL, + NULL, + sbi_mem_writel, +}; + +static const VMStateDescription vmstate_sbi = { + .name ="sbi", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField []) { + VMSTATE_UINT32_ARRAY(intreg_pending, SBIState, MAX_CPUS), + VMSTATE_END_OF_LIST() + } +}; + +static void sbi_reset(DeviceState *d) +{ + SBIState *s = container_of(d, SBIState, busdev.qdev); + unsigned int i; + + for (i = 0; i < MAX_CPUS; i++) { + s->intreg_pending[i] = 0; + } +} + +static int sbi_init1(SysBusDevice *dev) +{ + SBIState *s = FROM_SYSBUS(SBIState, dev); + int sbi_io_memory; + unsigned int i; + + qdev_init_gpio_in(&dev->qdev, sbi_set_irq, 32 + MAX_CPUS); + for (i = 0; i < MAX_CPUS; i++) { + sysbus_init_irq(dev, &s->cpu_irqs[i]); + } + + sbi_io_memory = cpu_register_io_memory(sbi_mem_read, sbi_mem_write, s, + DEVICE_NATIVE_ENDIAN); + sysbus_init_mmio(dev, SBI_SIZE, sbi_io_memory); + + return 0; +} + +static SysBusDeviceInfo sbi_info = { + .init = sbi_init1, + .qdev.name = "sbi", + .qdev.size = sizeof(SBIState), + .qdev.vmsd = &vmstate_sbi, + .qdev.reset = sbi_reset, +}; + +static void sbi_register_devices(void) +{ + sysbus_register_withprop(&sbi_info); +} + +device_init(sbi_register_devices) diff --git a/hw/scsi-bus.c b/hw/scsi-bus.c new file mode 100644 index 0000000..ceeb4ec --- /dev/null +++ b/hw/scsi-bus.c @@ -0,0 +1,547 @@ +#include "hw.h" +#include "qemu-error.h" +#include "scsi.h" +#include "scsi-defs.h" +#include "qdev.h" +#include "blockdev.h" + +static char *scsibus_get_fw_dev_path(DeviceState *dev); + +static struct BusInfo scsi_bus_info = { + .name = "SCSI", + .size = sizeof(SCSIBus), + .get_fw_dev_path = scsibus_get_fw_dev_path, + .props = (Property[]) { + DEFINE_PROP_UINT32("scsi-id", SCSIDevice, id, -1), + DEFINE_PROP_END_OF_LIST(), + }, +}; +static int next_scsi_bus; + +/* Create a scsi bus, and attach devices to it. */ +void scsi_bus_new(SCSIBus *bus, DeviceState *host, int tcq, int ndev, + scsi_completionfn complete) +{ + qbus_create_inplace(&bus->qbus, &scsi_bus_info, host, NULL); + bus->busnr = next_scsi_bus++; + bus->tcq = tcq; + bus->ndev = ndev; + bus->complete = complete; + bus->qbus.allow_hotplug = 1; +} + +static int scsi_qdev_init(DeviceState *qdev, DeviceInfo *base) +{ + SCSIDevice *dev = DO_UPCAST(SCSIDevice, qdev, qdev); + SCSIDeviceInfo *info = DO_UPCAST(SCSIDeviceInfo, qdev, base); + SCSIBus *bus = DO_UPCAST(SCSIBus, qbus, dev->qdev.parent_bus); + int rc = -1; + + if (dev->id == -1) { + for (dev->id = 0; dev->id < bus->ndev; dev->id++) { + if (bus->devs[dev->id] == NULL) + break; + } + } + if (dev->id >= bus->ndev) { + error_report("bad scsi device id: %d", dev->id); + goto err; + } + + if (bus->devs[dev->id]) { + qdev_free(&bus->devs[dev->id]->qdev); + } + bus->devs[dev->id] = dev; + + dev->info = info; + QTAILQ_INIT(&dev->requests); + rc = dev->info->init(dev); + if (rc != 0) { + bus->devs[dev->id] = NULL; + } + +err: + return rc; +} + +static int scsi_qdev_exit(DeviceState *qdev) +{ + SCSIDevice *dev = DO_UPCAST(SCSIDevice, qdev, qdev); + SCSIBus *bus = DO_UPCAST(SCSIBus, qbus, dev->qdev.parent_bus); + + assert(bus->devs[dev->id] != NULL); + if (bus->devs[dev->id]->info->destroy) { + bus->devs[dev->id]->info->destroy(bus->devs[dev->id]); + } + bus->devs[dev->id] = NULL; + return 0; +} + +void scsi_qdev_register(SCSIDeviceInfo *info) +{ + info->qdev.bus_info = &scsi_bus_info; + info->qdev.init = scsi_qdev_init; + info->qdev.unplug = qdev_simple_unplug_cb; + info->qdev.exit = scsi_qdev_exit; + qdev_register(&info->qdev); +} + +/* handle legacy '-drive if=scsi,...' cmd line args */ +SCSIDevice *scsi_bus_legacy_add_drive(SCSIBus *bus, BlockDriverState *bdrv, + int unit, bool removable) +{ + const char *driver; + DeviceState *dev; + + driver = bdrv_is_sg(bdrv) ? "scsi-generic" : "scsi-disk"; + dev = qdev_create(&bus->qbus, driver); + qdev_prop_set_uint32(dev, "scsi-id", unit); + if (qdev_prop_exists(dev, "removable")) { + qdev_prop_set_bit(dev, "removable", removable); + } + if (qdev_prop_set_drive(dev, "drive", bdrv) < 0) { + qdev_free(dev); + return NULL; + } + if (qdev_init(dev) < 0) + return NULL; + return DO_UPCAST(SCSIDevice, qdev, dev); +} + +int scsi_bus_legacy_handle_cmdline(SCSIBus *bus) +{ + Location loc; + DriveInfo *dinfo; + int res = 0, unit; + + loc_push_none(&loc); + for (unit = 0; unit < bus->ndev; unit++) { + dinfo = drive_get(IF_SCSI, bus->busnr, unit); + if (dinfo == NULL) { + continue; + } + qemu_opts_loc_restore(dinfo->opts); + if (!scsi_bus_legacy_add_drive(bus, dinfo->bdrv, unit, false)) { + res = -1; + break; + } + } + loc_pop(&loc); + return res; +} + +SCSIRequest *scsi_req_alloc(size_t size, SCSIDevice *d, uint32_t tag, uint32_t lun) +{ + SCSIRequest *req; + + req = qemu_mallocz(size); + req->bus = scsi_bus_from_device(d); + req->dev = d; + req->tag = tag; + req->lun = lun; + req->status = -1; + req->enqueued = true; + QTAILQ_INSERT_TAIL(&d->requests, req, next); + return req; +} + +SCSIRequest *scsi_req_find(SCSIDevice *d, uint32_t tag) +{ + SCSIRequest *req; + + QTAILQ_FOREACH(req, &d->requests, next) { + if (req->tag == tag) { + return req; + } + } + return NULL; +} + +static void scsi_req_dequeue(SCSIRequest *req) +{ + if (req->enqueued) { + QTAILQ_REMOVE(&req->dev->requests, req, next); + req->enqueued = false; + } +} + +void scsi_req_free(SCSIRequest *req) +{ + scsi_req_dequeue(req); + qemu_free(req); +} + +static int scsi_req_length(SCSIRequest *req, uint8_t *cmd) +{ + switch (cmd[0] >> 5) { + case 0: + req->cmd.xfer = cmd[4]; + req->cmd.len = 6; + /* length 0 means 256 blocks */ + if (req->cmd.xfer == 0) + req->cmd.xfer = 256; + break; + case 1: + case 2: + req->cmd.xfer = cmd[8] | (cmd[7] << 8); + req->cmd.len = 10; + break; + case 4: + req->cmd.xfer = cmd[13] | (cmd[12] << 8) | (cmd[11] << 16) | (cmd[10] << 24); + req->cmd.len = 16; + break; + case 5: + req->cmd.xfer = cmd[9] | (cmd[8] << 8) | (cmd[7] << 16) | (cmd[6] << 24); + req->cmd.len = 12; + break; + default: + return -1; + } + + switch(cmd[0]) { + case TEST_UNIT_READY: + case REZERO_UNIT: + case START_STOP: + case SEEK_6: + case WRITE_FILEMARKS: + case SPACE: + case RESERVE: + case RELEASE: + case ERASE: + case ALLOW_MEDIUM_REMOVAL: + case VERIFY: + case SEEK_10: + case SYNCHRONIZE_CACHE: + case LOCK_UNLOCK_CACHE: + case LOAD_UNLOAD: + case SET_CD_SPEED: + case SET_LIMITS: + case WRITE_LONG: + case MOVE_MEDIUM: + case UPDATE_BLOCK: + req->cmd.xfer = 0; + break; + case MODE_SENSE: + break; + case WRITE_SAME: + req->cmd.xfer = 1; + break; + case READ_CAPACITY: + req->cmd.xfer = 8; + break; + case READ_BLOCK_LIMITS: + req->cmd.xfer = 6; + break; + case READ_POSITION: + req->cmd.xfer = 20; + break; + case SEND_VOLUME_TAG: + req->cmd.xfer *= 40; + break; + case MEDIUM_SCAN: + req->cmd.xfer *= 8; + break; + case WRITE_10: + case WRITE_VERIFY: + case WRITE_6: + case WRITE_12: + case WRITE_VERIFY_12: + case WRITE_16: + case WRITE_VERIFY_16: + req->cmd.xfer *= req->dev->blocksize; + break; + case READ_10: + case READ_6: + case READ_REVERSE: + case RECOVER_BUFFERED_DATA: + case READ_12: + case READ_16: + req->cmd.xfer *= req->dev->blocksize; + break; + case INQUIRY: + req->cmd.xfer = cmd[4] | (cmd[3] << 8); + break; + case MAINTENANCE_OUT: + case MAINTENANCE_IN: + if (req->dev->type == TYPE_ROM) { + /* GPCMD_REPORT_KEY and GPCMD_SEND_KEY from multi media commands */ + req->cmd.xfer = cmd[9] | (cmd[8] << 8); + } + break; + } + return 0; +} + +static int scsi_req_stream_length(SCSIRequest *req, uint8_t *cmd) +{ + switch(cmd[0]) { + /* stream commands */ + case READ_6: + case READ_REVERSE: + case RECOVER_BUFFERED_DATA: + case WRITE_6: + req->cmd.len = 6; + req->cmd.xfer = cmd[4] | (cmd[3] << 8) | (cmd[2] << 16); + if (cmd[1] & 0x01) /* fixed */ + req->cmd.xfer *= req->dev->blocksize; + break; + case REWIND: + case START_STOP: + req->cmd.len = 6; + req->cmd.xfer = 0; + break; + /* generic commands */ + default: + return scsi_req_length(req, cmd); + } + return 0; +} + +static void scsi_req_xfer_mode(SCSIRequest *req) +{ + switch (req->cmd.buf[0]) { + case WRITE_6: + case WRITE_10: + case WRITE_VERIFY: + case WRITE_12: + case WRITE_VERIFY_12: + case WRITE_16: + case WRITE_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: + case WRITE_SAME: + case SEARCH_HIGH_12: + case SEARCH_EQUAL_12: + case SEARCH_LOW_12: + case SET_WINDOW: + case MEDIUM_SCAN: + case SEND_VOLUME_TAG: + case WRITE_LONG_2: + case PERSISTENT_RESERVE_OUT: + case MAINTENANCE_OUT: + req->cmd.mode = SCSI_XFER_TO_DEV; + break; + default: + if (req->cmd.xfer) + req->cmd.mode = SCSI_XFER_FROM_DEV; + else { + req->cmd.mode = SCSI_XFER_NONE; + } + break; + } +} + +static uint64_t scsi_req_lba(SCSIRequest *req) +{ + uint8_t *buf = req->cmd.buf; + uint64_t lba; + + switch (buf[0] >> 5) { + case 0: + lba = (uint64_t) buf[3] | ((uint64_t) buf[2] << 8) | + (((uint64_t) buf[1] & 0x1f) << 16); + break; + case 1: + case 2: + lba = (uint64_t) buf[5] | ((uint64_t) buf[4] << 8) | + ((uint64_t) buf[3] << 16) | ((uint64_t) buf[2] << 24); + break; + case 4: + lba = (uint64_t) buf[9] | ((uint64_t) buf[8] << 8) | + ((uint64_t) buf[7] << 16) | ((uint64_t) buf[6] << 24) | + ((uint64_t) buf[5] << 32) | ((uint64_t) buf[4] << 40) | + ((uint64_t) buf[3] << 48) | ((uint64_t) buf[2] << 56); + break; + case 5: + lba = (uint64_t) buf[5] | ((uint64_t) buf[4] << 8) | + ((uint64_t) buf[3] << 16) | ((uint64_t) buf[2] << 24); + break; + default: + lba = -1; + + } + return lba; +} + +int scsi_req_parse(SCSIRequest *req, uint8_t *buf) +{ + int rc; + + if (req->dev->type == TYPE_TAPE) { + rc = scsi_req_stream_length(req, buf); + } else { + rc = scsi_req_length(req, buf); + } + if (rc != 0) + return rc; + + memcpy(req->cmd.buf, buf, req->cmd.len); + scsi_req_xfer_mode(req); + req->cmd.lba = scsi_req_lba(req); + return 0; +} + +static const char *scsi_command_name(uint8_t cmd) +{ + static const char *names[] = { + [ TEST_UNIT_READY ] = "TEST_UNIT_READY", + [ REZERO_UNIT ] = "REZERO_UNIT", + /* REWIND and REZERO_UNIT use the same operation code */ + [ REQUEST_SENSE ] = "REQUEST_SENSE", + [ FORMAT_UNIT ] = "FORMAT_UNIT", + [ READ_BLOCK_LIMITS ] = "READ_BLOCK_LIMITS", + [ REASSIGN_BLOCKS ] = "REASSIGN_BLOCKS", + [ READ_6 ] = "READ_6", + [ WRITE_6 ] = "WRITE_6", + [ SEEK_6 ] = "SEEK_6", + [ 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", + [ RECEIVE_DIAGNOSTIC ] = "RECEIVE_DIAGNOSTIC", + [ SEND_DIAGNOSTIC ] = "SEND_DIAGNOSTIC", + [ ALLOW_MEDIUM_REMOVAL ] = "ALLOW_MEDIUM_REMOVAL", + + [ SET_WINDOW ] = "SET_WINDOW", + [ READ_CAPACITY ] = "READ_CAPACITY", + [ READ_10 ] = "READ_10", + [ WRITE_10 ] = "WRITE_10", + [ SEEK_10 ] = "SEEK_10", + [ WRITE_VERIFY ] = "WRITE_VERIFY", + [ VERIFY ] = "VERIFY", + [ SEARCH_HIGH ] = "SEARCH_HIGH", + [ SEARCH_EQUAL ] = "SEARCH_EQUAL", + [ SEARCH_LOW ] = "SEARCH_LOW", + [ SET_LIMITS ] = "SET_LIMITS", + [ PRE_FETCH ] = "PRE_FETCH", + /* 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", + [ MEDIUM_SCAN ] = "MEDIUM_SCAN", + [ COMPARE ] = "COMPARE", + [ COPY_VERIFY ] = "COPY_VERIFY", + [ WRITE_BUFFER ] = "WRITE_BUFFER", + [ READ_BUFFER ] = "READ_BUFFER", + [ UPDATE_BLOCK ] = "UPDATE_BLOCK", + [ READ_LONG ] = "READ_LONG", + [ WRITE_LONG ] = "WRITE_LONG", + [ CHANGE_DEFINITION ] = "CHANGE_DEFINITION", + [ WRITE_SAME ] = "WRITE_SAME", + [ READ_TOC ] = "READ_TOC", + [ 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", + [ MOVE_MEDIUM ] = "MOVE_MEDIUM", + [ READ_12 ] = "READ_12", + [ WRITE_12 ] = "WRITE_12", + [ WRITE_VERIFY_12 ] = "WRITE_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", + [ WRITE_LONG_2 ] = "WRITE_LONG_2", + + [ REPORT_DENSITY_SUPPORT ] = "REPORT_DENSITY_SUPPORT", + [ GET_CONFIGURATION ] = "GET_CONFIGURATION", + [ READ_16 ] = "READ_16", + [ WRITE_16 ] = "WRITE_16", + [ WRITE_VERIFY_16 ] = "WRITE_VERIFY_16", + [ SERVICE_ACTION_IN ] = "SERVICE_ACTION_IN", + [ REPORT_LUNS ] = "REPORT_LUNS", + [ LOAD_UNLOAD ] = "LOAD_UNLOAD", + [ SET_CD_SPEED ] = "SET_CD_SPEED", + [ BLANK ] = "BLANK", + }; + + if (cmd >= ARRAY_SIZE(names) || names[cmd] == NULL) + return "*UNKNOWN*"; + return names[cmd]; +} + +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) +{ + assert(req->status != -1); + scsi_req_dequeue(req); + req->bus->complete(req->bus, SCSI_REASON_DONE, + req->tag, + req->status); +} + +static char *scsibus_get_fw_dev_path(DeviceState *dev) +{ + SCSIDevice *d = (SCSIDevice*)dev; + SCSIBus *bus = scsi_bus_from_device(d); + char path[100]; + int i; + + for (i = 0; i < bus->ndev; i++) { + if (bus->devs[i] == d) { + break; + } + } + + assert(i != bus->ndev); + + snprintf(path, sizeof(path), "%s@%x", qdev_fw_name(dev), i); + + return strdup(path); +} diff --git a/hw/scsi-defs.h b/hw/scsi-defs.h new file mode 100644 index 0000000..413cce0 --- /dev/null +++ b/hw/scsi-defs.h @@ -0,0 +1,166 @@ +/* Copyright (C) 1998, 1999 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C 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. + + The GNU C 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 . +*/ + +/* + * This header file contains public constants and structures used by + * the scsi code for linux. + */ + +/* + * SCSI opcodes + */ + +#define TEST_UNIT_READY 0x00 +#define REZERO_UNIT 0x01 +#define REQUEST_SENSE 0x03 +#define FORMAT_UNIT 0x04 +#define READ_BLOCK_LIMITS 0x05 +#define REASSIGN_BLOCKS 0x07 +#define READ_6 0x08 +#define WRITE_6 0x0a +#define SEEK_6 0x0b +#define READ_REVERSE 0x0f +#define WRITE_FILEMARKS 0x10 +#define SPACE 0x11 +#define INQUIRY 0x12 +#define RECOVER_BUFFERED_DATA 0x14 +#define MODE_SELECT 0x15 +#define RESERVE 0x16 +#define RELEASE 0x17 +#define COPY 0x18 +#define ERASE 0x19 +#define MODE_SENSE 0x1a +#define START_STOP 0x1b +#define RECEIVE_DIAGNOSTIC 0x1c +#define SEND_DIAGNOSTIC 0x1d +#define ALLOW_MEDIUM_REMOVAL 0x1e + +#define SET_WINDOW 0x24 +#define READ_CAPACITY 0x25 +#define READ_10 0x28 +#define WRITE_10 0x2a +#define SEEK_10 0x2b +#define WRITE_VERIFY 0x2e +#define VERIFY 0x2f +#define SEARCH_HIGH 0x30 +#define SEARCH_EQUAL 0x31 +#define SEARCH_LOW 0x32 +#define SET_LIMITS 0x33 +#define PRE_FETCH 0x34 +#define READ_POSITION 0x34 +#define SYNCHRONIZE_CACHE 0x35 +#define LOCK_UNLOCK_CACHE 0x36 +#define READ_DEFECT_DATA 0x37 +#define MEDIUM_SCAN 0x38 +#define COMPARE 0x39 +#define COPY_VERIFY 0x3a +#define WRITE_BUFFER 0x3b +#define READ_BUFFER 0x3c +#define UPDATE_BLOCK 0x3d +#define READ_LONG 0x3e +#define WRITE_LONG 0x3f +#define CHANGE_DEFINITION 0x40 +#define WRITE_SAME 0x41 +#define READ_TOC 0x43 +#define LOG_SELECT 0x4c +#define LOG_SENSE 0x4d +#define MODE_SELECT_10 0x55 +#define RESERVE_10 0x56 +#define RELEASE_10 0x57 +#define MODE_SENSE_10 0x5a +#define PERSISTENT_RESERVE_IN 0x5e +#define PERSISTENT_RESERVE_OUT 0x5f +#define WRITE_SAME_16 0x93 +#define MAINTENANCE_IN 0xa3 +#define MAINTENANCE_OUT 0xa4 +#define MOVE_MEDIUM 0xa5 +#define READ_12 0xa8 +#define WRITE_12 0xaa +#define WRITE_VERIFY_12 0xae +#define SEARCH_HIGH_12 0xb0 +#define SEARCH_EQUAL_12 0xb1 +#define SEARCH_LOW_12 0xb2 +#define READ_ELEMENT_STATUS 0xb8 +#define SEND_VOLUME_TAG 0xb6 +#define WRITE_LONG_2 0xea + +/* from hw/scsi-generic.c */ +#define REWIND 0x01 +#define REPORT_DENSITY_SUPPORT 0x44 +#define GET_CONFIGURATION 0x46 +#define READ_16 0x88 +#define WRITE_16 0x8a +#define WRITE_VERIFY_16 0x8e +#define SERVICE_ACTION_IN 0x9e +#define REPORT_LUNS 0xa0 +#define LOAD_UNLOAD 0xa6 +#define SET_CD_SPEED 0xbb +#define BLANK 0xa1 + +/* + * SAM Status codes + */ + +#define GOOD 0x00 +#define CHECK_CONDITION 0x02 +#define CONDITION_GOOD 0x04 +#define BUSY 0x08 +#define INTERMEDIATE_GOOD 0x10 +#define INTERMEDIATE_C_GOOD 0x14 +#define RESERVATION_CONFLICT 0x18 +#define COMMAND_TERMINATED 0x22 +#define TASK_SET_FULL 0x28 +#define ACA_ACTIVE 0x30 +#define TASK_ABORTED 0x40 + +#define STATUS_MASK 0x3e + +/* + * SENSE KEYS + */ + +#define NO_SENSE 0x00 +#define RECOVERED_ERROR 0x01 +#define NOT_READY 0x02 +#define MEDIUM_ERROR 0x03 +#define HARDWARE_ERROR 0x04 +#define ILLEGAL_REQUEST 0x05 +#define UNIT_ATTENTION 0x06 +#define DATA_PROTECT 0x07 +#define BLANK_CHECK 0x08 +#define COPY_ABORTED 0x0a +#define ABORTED_COMMAND 0x0b +#define VOLUME_OVERFLOW 0x0d +#define MISCOMPARE 0x0e + + +/* + * DEVICE TYPES + */ + +#define TYPE_DISK 0x00 +#define TYPE_TAPE 0x01 +#define TYPE_PROCESSOR 0x03 /* HP scanners use this */ +#define TYPE_WORM 0x04 /* Treated as ROM by our system */ +#define TYPE_ROM 0x05 +#define TYPE_SCANNER 0x06 +#define TYPE_MOD 0x07 /* Magneto-optical disk - + * - treated as TYPE_DISK */ +#define TYPE_MEDIUM_CHANGER 0x08 +#define TYPE_ENCLOSURE 0x0d /* Enclosure Services Device */ +#define TYPE_NO_LUN 0x7f + diff --git a/hw/scsi-disk.c b/hw/scsi-disk.c new file mode 100644 index 0000000..488eedd --- /dev/null +++ b/hw/scsi-disk.c @@ -0,0 +1,1309 @@ +/* + * 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 licenced 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 + +#define BADF(fmt, ...) \ +do { fprintf(stderr, "scsi-disk: " fmt , ## __VA_ARGS__); } while (0) + +#include "qemu-common.h" +#include "qemu-error.h" +#include "scsi.h" +#include "scsi-defs.h" +#include "sysemu.h" +#include "blockdev.h" + +#define SCSI_DMA_BUF_SIZE 131072 +#define SCSI_MAX_INQUIRY_LEN 256 + +#define SCSI_REQ_STATUS_RETRY 0x01 +#define SCSI_REQ_STATUS_RETRY_TYPE_MASK 0x06 +#define SCSI_REQ_STATUS_RETRY_READ 0x00 +#define SCSI_REQ_STATUS_RETRY_WRITE 0x02 +#define SCSI_REQ_STATUS_RETRY_FLUSH 0x04 + +typedef struct SCSIDiskState SCSIDiskState; + +typedef struct SCSISense { + uint8_t key; +} SCSISense; + +typedef struct SCSIDiskReq { + SCSIRequest req; + /* ??? We should probably keep track of whether the data transfer is + a read or a write. Currently we rely on the host getting it right. */ + /* Both sector and sector_count are in terms of qemu 512 byte blocks. */ + uint64_t sector; + uint32_t sector_count; + struct iovec iov; + QEMUIOVector qiov; + uint32_t status; +} SCSIDiskReq; + +struct SCSIDiskState +{ + SCSIDevice qdev; + BlockDriverState *bs; + /* The qemu block layer uses a fixed 512 byte sector size. + This is the number of 512 byte blocks in a single scsi sector. */ + int cluster_size; + uint32_t removable; + uint64_t max_lba; + QEMUBH *bh; + char *version; + char *serial; + SCSISense sense; +}; + +static int scsi_handle_rw_error(SCSIDiskReq *r, int error, int type); +static int scsi_disk_emulate_command(SCSIDiskReq *r, uint8_t *outbuf); + +static SCSIDiskReq *scsi_new_request(SCSIDiskState *s, uint32_t tag, + uint32_t lun) +{ + SCSIRequest *req; + SCSIDiskReq *r; + + req = scsi_req_alloc(sizeof(SCSIDiskReq), &s->qdev, tag, lun); + r = DO_UPCAST(SCSIDiskReq, req, req); + r->iov.iov_base = qemu_blockalign(s->bs, SCSI_DMA_BUF_SIZE); + return r; +} + +static void scsi_remove_request(SCSIDiskReq *r) +{ + qemu_vfree(r->iov.iov_base); + scsi_req_free(&r->req); +} + +static SCSIDiskReq *scsi_find_request(SCSIDiskState *s, uint32_t tag) +{ + return DO_UPCAST(SCSIDiskReq, req, scsi_req_find(&s->qdev, tag)); +} + +static void scsi_disk_clear_sense(SCSIDiskState *s) +{ + memset(&s->sense, 0, sizeof(s->sense)); +} + +static void scsi_disk_set_sense(SCSIDiskState *s, uint8_t key) +{ + s->sense.key = key; +} + +static void scsi_req_set_status(SCSIDiskReq *r, int status, int sense_code) +{ + SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, r->req.dev); + + r->req.status = status; + scsi_disk_set_sense(s, sense_code); +} + +/* Helper function for command completion. */ +static void scsi_command_complete(SCSIDiskReq *r, int status, int sense) +{ + DPRINTF("Command complete tag=0x%x status=%d sense=%d\n", + r->req.tag, status, sense); + scsi_req_set_status(r, status, sense); + scsi_req_complete(&r->req); + scsi_remove_request(r); +} + +/* Cancel a pending data transfer. */ +static void scsi_cancel_io(SCSIDevice *d, uint32_t tag) +{ + SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, d); + SCSIDiskReq *r; + DPRINTF("Cancel tag=0x%x\n", tag); + r = scsi_find_request(s, tag); + if (r) { + if (r->req.aiocb) + bdrv_aio_cancel(r->req.aiocb); + r->req.aiocb = NULL; + scsi_remove_request(r); + } +} + +static void scsi_read_complete(void * opaque, int ret) +{ + SCSIDiskReq *r = (SCSIDiskReq *)opaque; + int n; + + r->req.aiocb = NULL; + + if (ret) { + if (scsi_handle_rw_error(r, -ret, SCSI_REQ_STATUS_RETRY_READ)) { + return; + } + } + + DPRINTF("Data ready tag=0x%x len=%zd\n", r->req.tag, r->iov.iov_len); + + n = r->iov.iov_len / 512; + r->sector += n; + r->sector_count -= n; + r->req.bus->complete(r->req.bus, SCSI_REASON_DATA, r->req.tag, r->iov.iov_len); +} + + +static void scsi_read_request(SCSIDiskReq *r) +{ + SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, r->req.dev); + uint32_t n; + + if (r->sector_count == (uint32_t)-1) { + DPRINTF("Read buf_len=%zd\n", r->iov.iov_len); + r->sector_count = 0; + r->req.bus->complete(r->req.bus, SCSI_REASON_DATA, r->req.tag, r->iov.iov_len); + return; + } + DPRINTF("Read sector_count=%d\n", r->sector_count); + if (r->sector_count == 0) { + scsi_command_complete(r, GOOD, NO_SENSE); + return; + } + + /* No data transfer may already be in progress */ + assert(r->req.aiocb == NULL); + + n = r->sector_count; + if (n > SCSI_DMA_BUF_SIZE / 512) + n = SCSI_DMA_BUF_SIZE / 512; + + r->iov.iov_len = n * 512; + qemu_iovec_init_external(&r->qiov, &r->iov, 1); + r->req.aiocb = bdrv_aio_readv(s->bs, r->sector, &r->qiov, n, + scsi_read_complete, r); + if (r->req.aiocb == NULL) { + scsi_read_complete(r, -EIO); + } +} + +/* Read more data from scsi device into buffer. */ +static void scsi_read_data(SCSIDevice *d, uint32_t tag) +{ + SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, d); + SCSIDiskReq *r; + + r = scsi_find_request(s, tag); + if (!r) { + BADF("Bad read tag 0x%x\n", tag); + /* ??? This is the wrong error. */ + scsi_command_complete(r, CHECK_CONDITION, HARDWARE_ERROR); + return; + } + + scsi_read_request(r); +} + +static int scsi_handle_rw_error(SCSIDiskReq *r, int error, int type) +{ + int is_read = (type == SCSI_REQ_STATUS_RETRY_READ); + SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, r->req.dev); + BlockErrorAction action = bdrv_get_on_error(s->bs, is_read); + + if (action == BLOCK_ERR_IGNORE) { + bdrv_mon_event(s->bs, BDRV_ACTION_IGNORE, is_read); + return 0; + } + + if ((error == ENOSPC && action == BLOCK_ERR_STOP_ENOSPC) + || action == BLOCK_ERR_STOP_ANY) { + + type &= SCSI_REQ_STATUS_RETRY_TYPE_MASK; + r->status |= SCSI_REQ_STATUS_RETRY | type; + + bdrv_mon_event(s->bs, BDRV_ACTION_STOP, is_read); + vm_stop(0); + } else { + if (type == SCSI_REQ_STATUS_RETRY_READ) { + r->req.bus->complete(r->req.bus, SCSI_REASON_DATA, r->req.tag, 0); + } + scsi_command_complete(r, CHECK_CONDITION, + HARDWARE_ERROR); + bdrv_mon_event(s->bs, BDRV_ACTION_REPORT, is_read); + } + + return 1; +} + +static void scsi_write_complete(void * opaque, int ret) +{ + SCSIDiskReq *r = (SCSIDiskReq *)opaque; + uint32_t len; + uint32_t n; + + r->req.aiocb = NULL; + + if (ret) { + if (scsi_handle_rw_error(r, -ret, SCSI_REQ_STATUS_RETRY_WRITE)) { + return; + } + } + + n = r->iov.iov_len / 512; + r->sector += n; + r->sector_count -= n; + if (r->sector_count == 0) { + scsi_command_complete(r, GOOD, NO_SENSE); + } else { + len = r->sector_count * 512; + if (len > SCSI_DMA_BUF_SIZE) { + len = SCSI_DMA_BUF_SIZE; + } + r->iov.iov_len = len; + DPRINTF("Write complete tag=0x%x more=%d\n", r->req.tag, len); + r->req.bus->complete(r->req.bus, SCSI_REASON_DATA, r->req.tag, len); + } +} + +static void scsi_write_request(SCSIDiskReq *r) +{ + 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); + + n = r->iov.iov_len / 512; + if (n) { + qemu_iovec_init_external(&r->qiov, &r->iov, 1); + r->req.aiocb = bdrv_aio_writev(s->bs, r->sector, &r->qiov, n, + scsi_write_complete, r); + if (r->req.aiocb == NULL) { + scsi_write_complete(r, -EIO); + } + } else { + /* Invoke completion routine to fetch data from host. */ + scsi_write_complete(r, 0); + } +} + +/* Write data to a scsi device. Returns nonzero on failure. + The transfer may complete asynchronously. */ +static int scsi_write_data(SCSIDevice *d, uint32_t tag) +{ + SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, d); + SCSIDiskReq *r; + + DPRINTF("Write data tag=0x%x\n", tag); + r = scsi_find_request(s, tag); + if (!r) { + BADF("Bad write tag 0x%x\n", tag); + scsi_command_complete(r, CHECK_CONDITION, HARDWARE_ERROR); + return 1; + } + + scsi_write_request(r); + + return 0; +} + +static void scsi_dma_restart_bh(void *opaque) +{ + SCSIDiskState *s = opaque; + SCSIRequest *req; + SCSIDiskReq *r; + + qemu_bh_delete(s->bh); + s->bh = NULL; + + QTAILQ_FOREACH(req, &s->qdev.requests, next) { + r = DO_UPCAST(SCSIDiskReq, req, req); + if (r->status & SCSI_REQ_STATUS_RETRY) { + int status = r->status; + int ret; + + r->status &= + ~(SCSI_REQ_STATUS_RETRY | SCSI_REQ_STATUS_RETRY_TYPE_MASK); + + switch (status & SCSI_REQ_STATUS_RETRY_TYPE_MASK) { + case SCSI_REQ_STATUS_RETRY_READ: + scsi_read_request(r); + break; + case SCSI_REQ_STATUS_RETRY_WRITE: + scsi_write_request(r); + break; + case SCSI_REQ_STATUS_RETRY_FLUSH: + ret = scsi_disk_emulate_command(r, r->iov.iov_base); + if (ret == 0) { + scsi_command_complete(r, GOOD, NO_SENSE); + } + } + } + } +} + +static void scsi_dma_restart_cb(void *opaque, int running, int reason) +{ + SCSIDiskState *s = opaque; + + if (!running) + return; + + if (!s->bh) { + s->bh = qemu_bh_new(scsi_dma_restart_bh, s); + qemu_bh_schedule(s->bh); + } +} + +/* Return a pointer to the data buffer. */ +static uint8_t *scsi_get_buf(SCSIDevice *d, uint32_t tag) +{ + SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, d); + SCSIDiskReq *r; + + r = scsi_find_request(s, tag); + if (!r) { + BADF("Bad buffer tag 0x%x\n", tag); + return NULL; + } + 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; + + if (req->cmd.buf[1] & 0x2) { + /* Command support data - optional, not implemented */ + BADF("optional INQUIRY command support request not implemented\n"); + return -1; + } + + if (req->cmd.buf[1] & 0x1) { + /* Vital product data */ + uint8_t page_code = req->cmd.buf[2]; + if (req->cmd.xfer < 4) { + BADF("Error: Inquiry (EVPD[%02X]) buffer size %zd is " + "less than 4\n", page_code, req->cmd.xfer); + return -1; + } + + if (bdrv_get_type_hint(s->bs) == BDRV_TYPE_CDROM) { + outbuf[buflen++] = 5; + } else { + outbuf[buflen++] = 0; + } + outbuf[buflen++] = page_code ; // this page + outbuf[buflen++] = 0x00; + + switch (page_code) { + case 0x00: /* Supported page codes, mandatory */ + { + int pages; + DPRINTF("Inquiry EVPD[Supported pages] " + "buffer size %zd\n", req->cmd.xfer); + pages = buflen++; + outbuf[buflen++] = 0x00; // list of supported pages (this page) + outbuf[buflen++] = 0x80; // unit serial number + outbuf[buflen++] = 0x83; // device identification + if (bdrv_get_type_hint(s->bs) != BDRV_TYPE_CDROM) { + outbuf[buflen++] = 0xb0; // block limits + outbuf[buflen++] = 0xb2; // thin provisioning + } + outbuf[pages] = buflen - pages - 1; // number of pages + break; + } + case 0x80: /* Device serial number, optional */ + { + int l = strlen(s->serial); + + if (l > req->cmd.xfer) + l = req->cmd.xfer; + if (l > 20) + l = 20; + + DPRINTF("Inquiry EVPD[Serial number] " + "buffer size %zd\n", req->cmd.xfer); + outbuf[buflen++] = l; + memcpy(outbuf+buflen, s->serial, l); + buflen += l; + break; + } + + case 0x83: /* Device identification page, mandatory */ + { + int max_len = 255 - 8; + int id_len = strlen(bdrv_get_device_name(s->bs)); + + if (id_len > max_len) + id_len = max_len; + DPRINTF("Inquiry EVPD[Device identification] " + "buffer size %zd\n", req->cmd.xfer); + + outbuf[buflen++] = 4 + id_len; + 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, bdrv_get_device_name(s->bs), id_len); + buflen += id_len; + 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; + + if (bdrv_get_type_hint(s->bs) == BDRV_TYPE_CDROM) { + DPRINTF("Inquiry (EVPD[%02X] not supported for CDROM\n", + page_code); + return -1; + } + /* required VPD size with unmap support */ + outbuf[3] = buflen = 0x3c; + + memset(outbuf + 4, 0, buflen - 4); + + /* optimal transfer length granularity */ + outbuf[6] = (min_io_size >> 8) & 0xff; + outbuf[7] = min_io_size & 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; + + /* 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; + break; + } + case 0xb2: /* thin provisioning */ + { + outbuf[3] = buflen = 8; + outbuf[4] = 0; + outbuf[5] = 0x40; /* write same with unmap supported */ + outbuf[6] = 0; + outbuf[7] = 0; + break; + } + default: + BADF("Error: unsupported Inquiry (EVPD[%02X]) " + "buffer size %zd\n", page_code, req->cmd.xfer); + return -1; + } + /* done with EVPD */ + return buflen; + } + + /* Standard INQUIRY data */ + if (req->cmd.buf[2] != 0) { + BADF("Error: Inquiry (STANDARD) page or code " + "is non-zero [%02X]\n", req->cmd.buf[2]); + return -1; + } + + /* PAGE CODE == 0 */ + if (req->cmd.xfer < 5) { + BADF("Error: Inquiry (STANDARD) buffer size %zd " + "is less than 5\n", req->cmd.xfer); + return -1; + } + + buflen = req->cmd.xfer; + if (buflen > SCSI_MAX_INQUIRY_LEN) + buflen = SCSI_MAX_INQUIRY_LEN; + + memset(outbuf, 0, buflen); + + if (req->lun || req->cmd.buf[1] >> 5) { + outbuf[0] = 0x7f; /* LUN not supported */ + return buflen; + } + + if (bdrv_get_type_hint(s->bs) == BDRV_TYPE_CDROM) { + outbuf[0] = 5; + outbuf[1] = 0x80; + memcpy(&outbuf[16], "QEMU CD-ROM ", 16); + } else { + outbuf[0] = 0; + outbuf[1] = s->removable ? 0x80 : 0; + memcpy(&outbuf[16], "QEMU HARDDISK ", 16); + } + memcpy(&outbuf[8], "QEMU ", 8); + 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; /* Format 2 */ + + 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->tcq ? 0x02 : 0); + return buflen; +} + +static int mode_sense_page(SCSIRequest *req, int page, uint8_t *p, + int page_control) +{ + SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, req->dev); + BlockDriverState *bdrv = s->bs; + int cylinders, heads, secs; + + /* + * 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. + */ + switch (page) { + case 4: /* Rigid disk device geometry page. */ + p[0] = 4; + p[1] = 0x16; + if (page_control == 1) { /* Changeable Values */ + return p[1] + 2; + } + /* if a geometry hint is available, use it */ + bdrv_get_geometry_hint(bdrv, &cylinders, &heads, &secs); + p[2] = (cylinders >> 16) & 0xff; + p[3] = (cylinders >> 8) & 0xff; + p[4] = cylinders & 0xff; + p[5] = heads & 0xff; + /* Write precomp start cylinder, disabled */ + p[6] = (cylinders >> 16) & 0xff; + p[7] = (cylinders >> 8) & 0xff; + p[8] = cylinders & 0xff; + /* Reduced current start cylinder, disabled */ + p[9] = (cylinders >> 16) & 0xff; + p[10] = (cylinders >> 8) & 0xff; + p[11] = cylinders & 0xff; + /* Device step rate [ns], 200ns */ + p[12] = 0; + p[13] = 200; + /* Landing zone cylinder */ + p[14] = 0xff; + p[15] = 0xff; + p[16] = 0xff; + /* Medium rotation rate [rpm], 5400 rpm */ + p[20] = (5400 >> 8) & 0xff; + p[21] = 5400 & 0xff; + return p[1] + 2; + + case 5: /* Flexible disk device geometry page. */ + p[0] = 5; + p[1] = 0x1e; + if (page_control == 1) { /* Changeable Values */ + return p[1] + 2; + } + /* Transfer rate [kbit/s], 5Mbit/s */ + p[2] = 5000 >> 8; + p[3] = 5000 & 0xff; + /* if a geometry hint is available, use it */ + bdrv_get_geometry_hint(bdrv, &cylinders, &heads, &secs); + p[4] = heads & 0xff; + p[5] = secs & 0xff; + p[6] = s->cluster_size * 2; + p[8] = (cylinders >> 8) & 0xff; + p[9] = cylinders & 0xff; + /* Write precomp start cylinder, disabled */ + p[10] = (cylinders >> 8) & 0xff; + p[11] = cylinders & 0xff; + /* Reduced current start cylinder, disabled */ + p[12] = (cylinders >> 8) & 0xff; + p[13] = cylinders & 0xff; + /* Device step rate [100us], 100us */ + p[14] = 0; + p[15] = 1; + /* Device step pulse width [us], 1us */ + p[16] = 1; + /* Device head settle delay [100us], 100us */ + p[17] = 0; + p[18] = 1; + /* Motor on delay [0.1s], 0.1s */ + p[19] = 1; + /* Motor off delay [0.1s], 0.1s */ + p[20] = 1; + /* Medium rotation rate [rpm], 5400 rpm */ + p[28] = (5400 >> 8) & 0xff; + p[29] = 5400 & 0xff; + return p[1] + 2; + + case 8: /* Caching page. */ + p[0] = 8; + p[1] = 0x12; + if (page_control == 1) { /* Changeable Values */ + return p[1] + 2; + } + if (bdrv_enable_write_cache(s->bs)) { + p[2] = 4; /* WCE */ + } + return p[1] + 2; + + case 0x2a: /* CD Capabilities and Mechanical Status page. */ + if (bdrv_get_type_hint(bdrv) != BDRV_TYPE_CDROM) + return 0; + p[0] = 0x2a; + p[1] = 0x14; + if (page_control == 1) { /* Changeable Values */ + return p[1] + 2; + } + p[2] = 3; // CD-R & CD-RW read + p[3] = 0; // Writing not supported + p[4] = 0x7f; /* Audio, composite, digital out, + mode 2 form 1&2, multi session */ + p[5] = 0xff; /* CD DA, DA accurate, RW supported, + RW corrected, C2 errors, ISRC, + UPC, Bar code */ + p[6] = 0x2d | (bdrv_is_locked(s->bs)? 2 : 0); + /* Locking supported, jumper present, eject, tray */ + p[7] = 0; /* no volume & mute control, no + changer */ + p[8] = (50 * 176) >> 8; // 50x read speed + p[9] = (50 * 176) & 0xff; + p[10] = 0 >> 8; // No volume + p[11] = 0 & 0xff; + p[12] = 2048 >> 8; // 2M buffer + p[13] = 2048 & 0xff; + p[14] = (16 * 176) >> 8; // 16x read speed current + p[15] = (16 * 176) & 0xff; + p[18] = (16 * 176) >> 8; // 16x write speed + p[19] = (16 * 176) & 0xff; + p[20] = (16 * 176) >> 8; // 16x write speed current + p[21] = (16 * 176) & 0xff; + return p[1] + 2; + + default: + return 0; + } +} + +static int scsi_disk_emulate_mode_sense(SCSIRequest *req, uint8_t *outbuf) +{ + SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, req->dev); + uint64_t nb_sectors; + int page, dbd, buflen, page_control; + uint8_t *p; + uint8_t dev_specific_param; + + dbd = req->cmd.buf[1] & 0x8; + page = req->cmd.buf[2] & 0x3f; + page_control = (req->cmd.buf[2] & 0xc0) >> 6; + DPRINTF("Mode Sense(%d) (page %d, xfer %zd, page_control %d)\n", + (req->cmd.buf[0] == MODE_SENSE) ? 6 : 10, page, req->cmd.xfer, page_control); + memset(outbuf, 0, req->cmd.xfer); + p = outbuf; + + if (bdrv_is_read_only(s->bs)) { + dev_specific_param = 0x80; /* Readonly. */ + } else { + dev_specific_param = 0x00; + } + + if (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; + } + + bdrv_get_geometry(s->bs, &nb_sectors); + if (!dbd && nb_sectors) { + if (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->cluster_size; + 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->cluster_size * 2; + p[7] = 0; + p += 8; + } + + if (page_control == 3) { /* Saved Values */ + return -1; /* ILLEGAL_REQUEST */ + } + + switch (page) { + case 0x04: + case 0x05: + case 0x08: + case 0x2a: + p += mode_sense_page(req, page, p, page_control); + break; + case 0x3f: + p += mode_sense_page(req, 0x08, p, page_control); + p += mode_sense_page(req, 0x2a, p, page_control); + break; + default: + return -1; /* ILLEGAL_REQUEST */ + } + + 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 (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; + } + if (buflen > req->cmd.xfer) + buflen = req->cmd.xfer; + 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]; + bdrv_get_geometry(s->bs, &nb_sectors); + DPRINTF("Read TOC (track %d format %d msf %d)\n", start_track, format, msf >> 1); + nb_sectors /= s->cluster_size; + 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; + } + if (toclen > req->cmd.xfer) + toclen = req->cmd.xfer; + return toclen; +} + +static int scsi_disk_emulate_command(SCSIDiskReq *r, uint8_t *outbuf) +{ + SCSIRequest *req = &r->req; + SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, req->dev); + uint64_t nb_sectors; + int buflen = 0; + int ret; + + switch (req->cmd.buf[0]) { + case TEST_UNIT_READY: + if (!bdrv_is_inserted(s->bs)) + goto not_ready; + break; + case REQUEST_SENSE: + if (req->cmd.xfer < 4) + goto illegal_request; + memset(outbuf, 0, 4); + buflen = 4; + if (s->sense.key == NOT_READY && req->cmd.xfer >= 18) { + memset(outbuf, 0, 18); + buflen = 18; + outbuf[7] = 10; + /* asc 0x3a, ascq 0: Medium not present */ + outbuf[12] = 0x3a; + outbuf[13] = 0; + } + outbuf[0] = 0xf0; + outbuf[1] = 0; + outbuf[2] = s->sense.key; + scsi_disk_clear_sense(s); + 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(req, 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 (bdrv_get_type_hint(s->bs) == BDRV_TYPE_CDROM && (req->cmd.buf[4] & 2)) { + /* load/eject medium */ + bdrv_eject(s->bs, !(req->cmd.buf[4] & 1)); + } + break; + case ALLOW_MEDIUM_REMOVAL: + bdrv_set_locked(s->bs, req->cmd.buf[4] & 1); + break; + case READ_CAPACITY: + /* The normal LEN field for this command is zero. */ + memset(outbuf, 0, 8); + bdrv_get_geometry(s->bs, &nb_sectors); + if (!nb_sectors) + goto not_ready; + nb_sectors /= s->cluster_size; + /* Returned value is the address of the last sector. */ + nb_sectors--; + /* Remember the new size for read/write sanity checking. */ + s->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->cluster_size * 2; + outbuf[7] = 0; + buflen = 8; + break; + case SYNCHRONIZE_CACHE: + ret = bdrv_flush(s->bs); + if (ret < 0) { + if (scsi_handle_rw_error(r, -ret, SCSI_REQ_STATUS_RETRY_FLUSH)) { + return -1; + } + } + break; + case GET_CONFIGURATION: + memset(outbuf, 0, 8); + /* ??? This should probably return much more information. For now + just return the basic header indicating the CD-ROM profile. */ + outbuf[7] = 8; // CD-ROM + buflen = 8; + break; + case SERVICE_ACTION_IN: + /* Service Action In subcommands. */ + if ((req->cmd.buf[1] & 31) == 0x10) { + DPRINTF("SAI READ CAPACITY(16)\n"); + memset(outbuf, 0, req->cmd.xfer); + bdrv_get_geometry(s->bs, &nb_sectors); + if (!nb_sectors) + goto not_ready; + nb_sectors /= s->cluster_size; + /* Returned value is the address of the last sector. */ + nb_sectors--; + /* Remember the new size for read/write sanity checking. */ + s->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->cluster_size * 2; + 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. */ + buflen = req->cmd.xfer; + break; + } + DPRINTF("Unsupported Service Action In\n"); + goto illegal_request; + case REPORT_LUNS: + if (req->cmd.xfer < 16) + goto illegal_request; + memset(outbuf, 0, 16); + outbuf[3] = 8; + buflen = 16; + break; + case VERIFY: + break; + case REZERO_UNIT: + DPRINTF("Rezero Unit\n"); + if (!bdrv_is_inserted(s->bs)) { + goto not_ready; + } + break; + default: + goto illegal_request; + } + scsi_req_set_status(r, GOOD, NO_SENSE); + return buflen; + +not_ready: + scsi_command_complete(r, CHECK_CONDITION, NOT_READY); + return -1; + +illegal_request: + scsi_command_complete(r, CHECK_CONDITION, ILLEGAL_REQUEST); + return -1; +} + +/* 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(SCSIDevice *d, uint32_t tag, + uint8_t *buf, int lun) +{ + SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, d); + uint32_t len; + int is_write; + uint8_t command; + uint8_t *outbuf; + SCSIDiskReq *r; + int rc; + + command = buf[0]; + r = scsi_find_request(s, tag); + if (r) { + BADF("Tag 0x%x already in use\n", tag); + scsi_cancel_io(d, tag); + } + /* ??? Tags are not unique for different luns. We only implement a + single lun, so this should not matter. */ + r = scsi_new_request(s, tag, lun); + outbuf = (uint8_t *)r->iov.iov_base; + is_write = 0; + DPRINTF("Command: lun=%d tag=0x%x data=0x%02x", lun, tag, buf[0]); + + if (scsi_req_parse(&r->req, buf) != 0) { + BADF("Unsupported command length, command %x\n", command); + goto fail; + } +#ifdef DEBUG_SCSI + { + int i; + for (i = 1; i < r->req.cmd.len; i++) { + printf(" 0x%02x", buf[i]); + } + printf("\n"); + } +#endif + + if (lun || buf[1] >> 5) { + /* Only LUN 0 supported. */ + DPRINTF("Unimplemented LUN %d\n", lun ? lun : buf[1] >> 5); + if (command != REQUEST_SENSE && command != INQUIRY) + goto fail; + } + switch (command) { + case TEST_UNIT_READY: + case REQUEST_SENSE: + 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 READ_CAPACITY: + case SYNCHRONIZE_CACHE: + case READ_TOC: + case GET_CONFIGURATION: + case SERVICE_ACTION_IN: + case REPORT_LUNS: + case VERIFY: + case REZERO_UNIT: + rc = scsi_disk_emulate_command(r, outbuf); + if (rc < 0) { + return 0; + } + + r->iov.iov_len = rc; + break; + case READ_6: + case READ_10: + case READ_12: + case READ_16: + len = r->req.cmd.xfer / d->blocksize; + DPRINTF("Read (sector %" PRId64 ", count %d)\n", r->req.cmd.lba, len); + if (r->req.cmd.lba > s->max_lba) + goto illegal_lba; + r->sector = r->req.cmd.lba * s->cluster_size; + r->sector_count = len * s->cluster_size; + break; + case WRITE_6: + case WRITE_10: + case WRITE_12: + case WRITE_16: + case WRITE_VERIFY: + case WRITE_VERIFY_12: + case WRITE_VERIFY_16: + len = r->req.cmd.xfer / d->blocksize; + DPRINTF("Write %s(sector %" PRId64 ", count %d)\n", + (command & 0xe) == 0xe ? "And Verify " : "", + r->req.cmd.lba, len); + if (r->req.cmd.lba > s->max_lba) + goto illegal_lba; + r->sector = r->req.cmd.lba * s->cluster_size; + r->sector_count = len * s->cluster_size; + is_write = 1; + break; + case MODE_SELECT: + DPRINTF("Mode Select(6) (len %lu)\n", (long)r->req.cmd.xfer); + /* We don't support mode parameter changes. + Allow the mode parameter header + block descriptors only. */ + if (r->req.cmd.xfer > 12) { + goto fail; + } + break; + case MODE_SELECT_10: + DPRINTF("Mode Select(10) (len %lu)\n", (long)r->req.cmd.xfer); + /* We don't support mode parameter changes. + Allow the mode parameter header + block descriptors only. */ + if (r->req.cmd.xfer > 16) { + goto fail; + } + break; + case SEEK_6: + case SEEK_10: + DPRINTF("Seek(%d) (sector %" PRId64 ")\n", command == SEEK_6 ? 6 : 10, + r->req.cmd.lba); + if (r->req.cmd.lba > s->max_lba) { + goto illegal_lba; + } + break; + case WRITE_SAME_16: + len = r->req.cmd.xfer / d->blocksize; + + DPRINTF("WRITE SAME(16) (sector %" PRId64 ", count %d)\n", + r->req.cmd.lba, len); + + if (r->req.cmd.lba > s->max_lba) { + goto illegal_lba; + } + + /* + * We only support WRITE SAME with the unmap bit set for now. + */ + if (!(buf[1] & 0x8)) { + goto fail; + } + + rc = bdrv_discard(s->bs, r->req.cmd.lba * s->cluster_size, + len * s->cluster_size); + if (rc < 0) { + /* XXX: better error code ?*/ + goto fail; + } + + break; + default: + DPRINTF("Unknown SCSI command (%2.2x)\n", buf[0]); + fail: + scsi_command_complete(r, CHECK_CONDITION, ILLEGAL_REQUEST); + return 0; + illegal_lba: + scsi_command_complete(r, CHECK_CONDITION, HARDWARE_ERROR); + return 0; + } + if (r->sector_count == 0 && r->iov.iov_len == 0) { + scsi_command_complete(r, GOOD, NO_SENSE); + } + len = r->sector_count * 512 + r->iov.iov_len; + if (is_write) { + return -len; + } else { + if (!r->sector_count) + r->sector_count = -1; + return len; + } +} + +static void scsi_disk_purge_requests(SCSIDiskState *s) +{ + SCSIDiskReq *r; + + while (!QTAILQ_EMPTY(&s->qdev.requests)) { + r = DO_UPCAST(SCSIDiskReq, req, QTAILQ_FIRST(&s->qdev.requests)); + if (r->req.aiocb) { + bdrv_aio_cancel(r->req.aiocb); + } + scsi_remove_request(r); + } +} + +static void scsi_disk_reset(DeviceState *dev) +{ + SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev.qdev, dev); + uint64_t nb_sectors; + + scsi_disk_purge_requests(s); + + bdrv_get_geometry(s->bs, &nb_sectors); + nb_sectors /= s->cluster_size; + if (nb_sectors) { + nb_sectors--; + } + s->max_lba = nb_sectors; +} + +static void scsi_destroy(SCSIDevice *dev) +{ + SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, dev); + + scsi_disk_purge_requests(s); + blockdev_mark_auto_del(s->qdev.conf.bs); +} + +static int scsi_disk_initfn(SCSIDevice *dev) +{ + SCSIDiskState *s = DO_UPCAST(SCSIDiskState, qdev, dev); + int is_cd; + DriveInfo *dinfo; + + if (!s->qdev.conf.bs) { + error_report("scsi-disk: drive property not set"); + return -1; + } + s->bs = s->qdev.conf.bs; + is_cd = bdrv_get_type_hint(s->bs) == BDRV_TYPE_CDROM; + + if (!is_cd && !bdrv_is_inserted(s->bs)) { + error_report("Device needs media, but drive is empty"); + return -1; + } + + if (!s->serial) { + /* try to fall back to value set with legacy -drive serial=... */ + dinfo = drive_get_by_blockdev(s->bs); + s->serial = qemu_strdup(*dinfo->serial ? dinfo->serial : "0"); + } + + if (!s->version) { + s->version = qemu_strdup(QEMU_VERSION); + } + + if (bdrv_is_sg(s->bs)) { + error_report("scsi-disk: unwanted /dev/sg*"); + return -1; + } + + if (is_cd) { + s->qdev.blocksize = 2048; + } else { + s->qdev.blocksize = s->qdev.conf.logical_block_size; + } + s->cluster_size = s->qdev.blocksize / 512; + s->bs->buffer_alignment = s->qdev.blocksize; + + s->qdev.type = TYPE_DISK; + qemu_add_vm_change_state_handler(scsi_dma_restart_cb, s); + bdrv_set_removable(s->bs, is_cd); + add_boot_device_path(s->qdev.conf.bootindex, &dev->qdev, ",0"); + return 0; +} + +static SCSIDeviceInfo scsi_disk_info = { + .qdev.name = "scsi-disk", + .qdev.fw_name = "disk", + .qdev.desc = "virtual scsi disk or cdrom", + .qdev.size = sizeof(SCSIDiskState), + .qdev.reset = scsi_disk_reset, + .init = scsi_disk_initfn, + .destroy = scsi_destroy, + .send_command = scsi_send_command, + .read_data = scsi_read_data, + .write_data = scsi_write_data, + .cancel_io = scsi_cancel_io, + .get_buf = scsi_get_buf, + .qdev.props = (Property[]) { + DEFINE_BLOCK_PROPERTIES(SCSIDiskState, qdev.conf), + DEFINE_PROP_STRING("ver", SCSIDiskState, version), + DEFINE_PROP_STRING("serial", SCSIDiskState, serial), + DEFINE_PROP_BIT("removable", SCSIDiskState, removable, 0, false), + DEFINE_PROP_END_OF_LIST(), + }, +}; + +static void scsi_disk_register_devices(void) +{ + scsi_qdev_register(&scsi_disk_info); +} +device_init(scsi_disk_register_devices) diff --git a/hw/scsi-generic.c b/hw/scsi-generic.c new file mode 100644 index 0000000..9be1cca --- /dev/null +++ b/hw/scsi-generic.c @@ -0,0 +1,576 @@ +/* + * 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 + * + * This code is licenced under the LGPL. + * + */ + +#include "qemu-common.h" +#include "qemu-error.h" +#include "scsi.h" +#include "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 +#include +#include +#include +#include +#include "scsi-defs.h" + +#define SCSI_SENSE_BUF_SIZE 96 + +#define SG_ERR_DRIVER_TIMEOUT 0x06 +#define SG_ERR_DRIVER_SENSE 0x08 + +#ifndef MAX_UINT +#define MAX_UINT ((unsigned int)-1) +#endif + +typedef struct SCSIGenericState SCSIGenericState; + +typedef struct SCSIGenericReq { + SCSIRequest req; + uint8_t *buf; + int buflen; + int len; + sg_io_hdr_t io_header; +} SCSIGenericReq; + +struct SCSIGenericState +{ + SCSIDevice qdev; + BlockDriverState *bs; + int lun; + int driver_status; + uint8_t sensebuf[SCSI_SENSE_BUF_SIZE]; + uint8_t senselen; +}; + +static SCSIGenericReq *scsi_new_request(SCSIDevice *d, uint32_t tag, uint32_t lun) +{ + SCSIRequest *req; + + req = scsi_req_alloc(sizeof(SCSIGenericReq), d, tag, lun); + return DO_UPCAST(SCSIGenericReq, req, req); +} + +static void scsi_remove_request(SCSIGenericReq *r) +{ + qemu_free(r->buf); + scsi_req_free(&r->req); +} + +static SCSIGenericReq *scsi_find_request(SCSIGenericState *s, uint32_t tag) +{ + return DO_UPCAST(SCSIGenericReq, req, scsi_req_find(&s->qdev, tag)); +} + +/* Helper function for command completion. */ +static void scsi_command_complete(void *opaque, int ret) +{ + SCSIGenericReq *r = (SCSIGenericReq *)opaque; + SCSIGenericState *s = DO_UPCAST(SCSIGenericState, qdev, r->req.dev); + + s->driver_status = r->io_header.driver_status; + if (s->driver_status & SG_ERR_DRIVER_SENSE) + s->senselen = r->io_header.sb_len_wr; + + if (ret != 0) + r->req.status = BUSY; + else { + if (s->driver_status & SG_ERR_DRIVER_TIMEOUT) { + r->req.status = BUSY; + BADF("Driver Timeout\n"); + } else if (r->io_header.status) + r->req.status = r->io_header.status; + else if (s->driver_status & SG_ERR_DRIVER_SENSE) + r->req.status = CHECK_CONDITION; + else + r->req.status = GOOD; + } + DPRINTF("Command complete 0x%p tag=0x%x status=%d\n", + r, r->req.tag, r->req.status); + + scsi_req_complete(&r->req); + scsi_remove_request(r); +} + +/* Cancel a pending data transfer. */ +static void scsi_cancel_io(SCSIDevice *d, uint32_t tag) +{ + DPRINTF("scsi_cancel_io 0x%x\n", tag); + SCSIGenericState *s = DO_UPCAST(SCSIGenericState, qdev, d); + SCSIGenericReq *r; + DPRINTF("Cancel tag=0x%x\n", tag); + r = scsi_find_request(s, tag); + if (r) { + if (r->req.aiocb) + bdrv_aio_cancel(r->req.aiocb); + r->req.aiocb = NULL; + scsi_remove_request(r); + } +} + +static int execute_command(BlockDriverState *bdrv, + SCSIGenericReq *r, int direction, + BlockDriverCompletionFunc *complete) +{ + SCSIGenericState *s = DO_UPCAST(SCSIGenericState, qdev, r->req.dev); + + 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(s->sensebuf); + r->io_header.sbp = s->sensebuf; + r->io_header.timeout = MAX_UINT; + r->io_header.usr_ptr = r; + r->io_header.flags |= SG_FLAG_DIRECT_IO; + + r->req.aiocb = bdrv_aio_ioctl(bdrv, SG_IO, &r->io_header, complete, r); + if (r->req.aiocb == NULL) { + BADF("execute_command: read failed !\n"); + return -1; + } + + return 0; +} + +static void scsi_read_complete(void * opaque, int ret) +{ + SCSIGenericReq *r = (SCSIGenericReq *)opaque; + int len; + + if (ret) { + DPRINTF("IO error ret %d\n", ret); + 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; + r->req.bus->complete(r->req.bus, SCSI_REASON_DATA, r->req.tag, len); + if (len == 0) + scsi_command_complete(r, 0); +} + +/* Read more data from scsi device into buffer. */ +static void scsi_read_data(SCSIDevice *d, uint32_t tag) +{ + SCSIGenericState *s = DO_UPCAST(SCSIGenericState, qdev, d); + SCSIGenericReq *r; + int ret; + + DPRINTF("scsi_read_data 0x%x\n", tag); + r = scsi_find_request(s, tag); + if (!r) { + BADF("Bad read tag 0x%x\n", tag); + /* ??? This is the wrong error. */ + scsi_command_complete(r, -EINVAL); + return; + } + + if (r->len == -1) { + scsi_command_complete(r, 0); + return; + } + + if (r->req.cmd.buf[0] == REQUEST_SENSE && s->driver_status & SG_ERR_DRIVER_SENSE) + { + s->senselen = MIN(r->len, s->senselen); + memcpy(r->buf, s->sensebuf, s->senselen); + r->io_header.driver_status = 0; + r->io_header.status = 0; + r->io_header.dxfer_len = s->senselen; + r->len = -1; + DPRINTF("Data ready tag=0x%x len=%d\n", r->req.tag, s->senselen); + DPRINTF("Sense: %d %d %d %d %d %d %d %d\n", + r->buf[0], r->buf[1], r->buf[2], r->buf[3], + r->buf[4], r->buf[5], r->buf[6], r->buf[7]); + r->req.bus->complete(r->req.bus, SCSI_REASON_DATA, r->req.tag, s->senselen); + return; + } + + ret = execute_command(s->bs, r, SG_DXFER_FROM_DEV, scsi_read_complete); + if (ret == -1) { + scsi_command_complete(r, -EINVAL); + return; + } +} + +static void scsi_write_complete(void * opaque, int ret) +{ + SCSIGenericReq *r = (SCSIGenericReq *)opaque; + SCSIGenericState *s = DO_UPCAST(SCSIGenericState, qdev, r->req.dev); + + DPRINTF("scsi_write_complete() ret = %d\n", ret); + if (ret) { + DPRINTF("IO error\n"); + scsi_command_complete(r, ret); + return; + } + + if (r->req.cmd.buf[0] == MODE_SELECT && r->req.cmd.buf[4] == 12 && + s->qdev.type == TYPE_TAPE) { + s->qdev.blocksize = (r->buf[9] << 16) | (r->buf[10] << 8) | r->buf[11]; + DPRINTF("block size %d\n", s->qdev.blocksize); + } + + scsi_command_complete(r, ret); +} + +/* Write data to a scsi device. Returns nonzero on failure. + The transfer may complete asynchronously. */ +static int scsi_write_data(SCSIDevice *d, uint32_t tag) +{ + SCSIGenericState *s = DO_UPCAST(SCSIGenericState, qdev, d); + SCSIGenericReq *r; + int ret; + + DPRINTF("scsi_write_data 0x%x\n", tag); + r = scsi_find_request(s, tag); + if (!r) { + BADF("Bad write tag 0x%x\n", tag); + /* ??? This is the wrong error. */ + scsi_command_complete(r, -EINVAL); + return 0; + } + + if (r->len == 0) { + r->len = r->buflen; + r->req.bus->complete(r->req.bus, SCSI_REASON_DATA, r->req.tag, r->len); + return 0; + } + + ret = execute_command(s->bs, r, SG_DXFER_TO_DEV, scsi_write_complete); + if (ret == -1) { + scsi_command_complete(r, -EINVAL); + return 1; + } + + return 0; +} + +/* Return a pointer to the data buffer. */ +static uint8_t *scsi_get_buf(SCSIDevice *d, uint32_t tag) +{ + SCSIGenericState *s = DO_UPCAST(SCSIGenericState, qdev, d); + SCSIGenericReq *r; + r = scsi_find_request(s, tag); + if (!r) { + BADF("Bad buffer tag 0x%x\n", tag); + return NULL; + } + return r->buf; +} + +static void scsi_req_fixup(SCSIRequest *req) +{ + switch(req->cmd.buf[0]) { + case WRITE_10: + req->cmd.buf[1] &= ~0x08; /* disable FUA */ + break; + case READ_10: + req->cmd.buf[1] &= ~0x08; /* disable FUA */ + break; + case REWIND: + case START_STOP: + if (req->dev->type == TYPE_TAPE) { + /* force IMMED, otherwise qemu waits end of command */ + req->cmd.buf[1] = 0x01; + } + break; + } +} + +/* 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(SCSIDevice *d, uint32_t tag, + uint8_t *cmd, int lun) +{ + SCSIGenericState *s = DO_UPCAST(SCSIGenericState, qdev, d); + SCSIGenericReq *r; + SCSIBus *bus; + int ret; + + if (cmd[0] != REQUEST_SENSE && + (lun != s->lun || (cmd[1] >> 5) != s->lun)) { + DPRINTF("Unimplemented LUN %d\n", lun ? lun : cmd[1] >> 5); + + s->sensebuf[0] = 0x70; + s->sensebuf[1] = 0x00; + s->sensebuf[2] = ILLEGAL_REQUEST; + s->sensebuf[3] = 0x00; + s->sensebuf[4] = 0x00; + s->sensebuf[5] = 0x00; + s->sensebuf[6] = 0x00; + s->senselen = 7; + s->driver_status = SG_ERR_DRIVER_SENSE; + bus = scsi_bus_from_device(d); + bus->complete(bus, SCSI_REASON_DONE, tag, CHECK_CONDITION); + return 0; + } + + r = scsi_find_request(s, tag); + if (r) { + BADF("Tag 0x%x already in use %p\n", tag, r); + scsi_cancel_io(d, tag); + } + r = scsi_new_request(d, tag, lun); + + if (-1 == scsi_req_parse(&r->req, cmd)) { + BADF("Unsupported command length, command %x\n", cmd[0]); + scsi_remove_request(r); + return 0; + } + scsi_req_fixup(&r->req); + + DPRINTF("Command: lun=%d tag=0x%x len %zd data=0x%02x", lun, tag, + r->req.cmd.xfer, cmd[0]); + +#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) { + if (r->buf != NULL) + qemu_free(r->buf); + r->buflen = 0; + r->buf = NULL; + ret = execute_command(s->bs, r, SG_DXFER_NONE, scsi_command_complete); + if (ret == -1) { + scsi_command_complete(r, -EINVAL); + return 0; + } + return 0; + } + + if (r->buflen != r->req.cmd.xfer) { + if (r->buf != NULL) + qemu_free(r->buf); + r->buf = qemu_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; + } + + return r->req.cmd.xfer; +} + +static int get_blocksize(BlockDriverState *bdrv) +{ + uint8_t cmd[10]; + uint8_t buf[8]; + uint8_t sensebuf[8]; + sg_io_hdr_t io_header; + int ret; + + memset(cmd, 0, sizeof(cmd)); + memset(buf, 0, sizeof(buf)); + cmd[0] = READ_CAPACITY; + + 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 = bdrv_ioctl(bdrv, SG_IO, &io_header); + if (ret < 0) + return -1; + + return (buf[4] << 24) | (buf[5] << 16) | (buf[6] << 8) | buf[7]; +} + +static int get_stream_blocksize(BlockDriverState *bdrv) +{ + 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 = bdrv_ioctl(bdrv, SG_IO, &io_header); + if (ret < 0) + return -1; + + return (buf[9] << 16) | (buf[10] << 8) | buf[11]; +} + +static void scsi_generic_purge_requests(SCSIGenericState *s) +{ + SCSIGenericReq *r; + + while (!QTAILQ_EMPTY(&s->qdev.requests)) { + r = DO_UPCAST(SCSIGenericReq, req, QTAILQ_FIRST(&s->qdev.requests)); + if (r->req.aiocb) { + bdrv_aio_cancel(r->req.aiocb); + } + scsi_remove_request(r); + } +} + +static void scsi_generic_reset(DeviceState *dev) +{ + SCSIGenericState *s = DO_UPCAST(SCSIGenericState, qdev.qdev, dev); + + scsi_generic_purge_requests(s); +} + +static void scsi_destroy(SCSIDevice *d) +{ + SCSIGenericState *s = DO_UPCAST(SCSIGenericState, qdev, d); + + scsi_generic_purge_requests(s); + blockdev_mark_auto_del(s->qdev.conf.bs); +} + +static int scsi_generic_initfn(SCSIDevice *dev) +{ + SCSIGenericState *s = DO_UPCAST(SCSIGenericState, qdev, dev); + int sg_version; + struct sg_scsi_id scsiid; + + if (!s->qdev.conf.bs) { + error_report("scsi-generic: drive property not set"); + return -1; + } + s->bs = s->qdev.conf.bs; + + /* check we are really using a /dev/sg* file */ + if (!bdrv_is_sg(s->bs)) { + error_report("scsi-generic: not /dev/sg*"); + return -1; + } + + if (bdrv_get_on_error(s->bs, 0) != BLOCK_ERR_STOP_ENOSPC) { + error_report("Device doesn't support drive option werror"); + return -1; + } + if (bdrv_get_on_error(s->bs, 1) != BLOCK_ERR_REPORT) { + error_report("Device doesn't support drive option rerror"); + return -1; + } + + /* check we are using a driver managing SG_IO (version 3 and after */ + if (bdrv_ioctl(s->bs, SG_GET_VERSION_NUM, &sg_version) < 0 || + sg_version < 30000) { + error_report("scsi-generic: scsi generic interface too old"); + return -1; + } + + /* get LUN of the /dev/sg? */ + if (bdrv_ioctl(s->bs, SG_GET_SCSI_ID, &scsiid)) { + error_report("scsi-generic: SG_GET_SCSI_ID ioctl failed"); + return -1; + } + + /* define device state */ + s->lun = scsiid.lun; + DPRINTF("LUN %d\n", s->lun); + s->qdev.type = scsiid.scsi_type; + DPRINTF("device type %d\n", s->qdev.type); + if (s->qdev.type == TYPE_TAPE) { + s->qdev.blocksize = get_stream_blocksize(s->bs); + if (s->qdev.blocksize == -1) + s->qdev.blocksize = 0; + } else { + s->qdev.blocksize = get_blocksize(s->bs); + /* removable media returns 0 if not present */ + if (s->qdev.blocksize <= 0) { + if (s->qdev.type == TYPE_ROM || s->qdev.type == TYPE_WORM) + s->qdev.blocksize = 2048; + else + s->qdev.blocksize = 512; + } + } + DPRINTF("block size %d\n", s->qdev.blocksize); + s->driver_status = 0; + memset(s->sensebuf, 0, sizeof(s->sensebuf)); + bdrv_set_removable(s->bs, 0); + return 0; +} + +static SCSIDeviceInfo scsi_generic_info = { + .qdev.name = "scsi-generic", + .qdev.desc = "pass through generic scsi device (/dev/sg*)", + .qdev.size = sizeof(SCSIGenericState), + .qdev.reset = scsi_generic_reset, + .init = scsi_generic_initfn, + .destroy = scsi_destroy, + .send_command = scsi_send_command, + .read_data = scsi_read_data, + .write_data = scsi_write_data, + .cancel_io = scsi_cancel_io, + .get_buf = scsi_get_buf, + .qdev.props = (Property[]) { + DEFINE_BLOCK_PROPERTIES(SCSIGenericState, qdev.conf), + DEFINE_PROP_END_OF_LIST(), + }, +}; + +static void scsi_generic_register_devices(void) +{ + scsi_qdev_register(&scsi_generic_info); +} +device_init(scsi_generic_register_devices) + +#endif /* __linux__ */ diff --git a/hw/scsi.h b/hw/scsi.h new file mode 100644 index 0000000..d3b5d56 --- /dev/null +++ b/hw/scsi.h @@ -0,0 +1,110 @@ +#ifndef QEMU_HW_SCSI_H +#define QEMU_HW_SCSI_H + +#include "qdev.h" +#include "block.h" +#include "block_int.h" + +#define MAX_SCSI_DEVS 255 + +#define SCSI_CMD_BUF_SIZE 16 + +/* scsi-disk.c */ +enum scsi_reason { + SCSI_REASON_DONE, /* Command complete. */ + SCSI_REASON_DATA /* Transfer complete, more data required. */ +}; + +typedef struct SCSIBus SCSIBus; +typedef struct SCSIDevice SCSIDevice; +typedef struct SCSIDeviceInfo SCSIDeviceInfo; +typedef void (*scsi_completionfn)(SCSIBus *bus, int reason, uint32_t tag, + uint32_t arg); + +enum SCSIXferMode { + SCSI_XFER_NONE, /* TEST_UNIT_READY, ... */ + SCSI_XFER_FROM_DEV, /* READ, INQUIRY, MODE_SENSE, ... */ + SCSI_XFER_TO_DEV, /* WRITE, MODE_SELECT, ... */ +}; + +typedef struct SCSIRequest { + SCSIBus *bus; + SCSIDevice *dev; + uint32_t tag; + uint32_t lun; + uint32_t status; + struct { + uint8_t buf[SCSI_CMD_BUF_SIZE]; + int len; + size_t xfer; + uint64_t lba; + enum SCSIXferMode mode; + } cmd; + BlockDriverAIOCB *aiocb; + bool enqueued; + QTAILQ_ENTRY(SCSIRequest) next; +} SCSIRequest; + +struct SCSIDevice +{ + DeviceState qdev; + uint32_t id; + BlockConf conf; + SCSIDeviceInfo *info; + QTAILQ_HEAD(, SCSIRequest) requests; + int blocksize; + int type; +}; + +/* cdrom.c */ +int cdrom_read_toc(int nb_sectors, uint8_t *buf, int msf, int start_track); +int cdrom_read_toc_raw(int nb_sectors, uint8_t *buf, int msf, int session_num); + +/* scsi-bus.c */ +typedef int (*scsi_qdev_initfn)(SCSIDevice *dev); +struct SCSIDeviceInfo { + DeviceInfo qdev; + scsi_qdev_initfn init; + void (*destroy)(SCSIDevice *s); + int32_t (*send_command)(SCSIDevice *s, uint32_t tag, uint8_t *buf, + int lun); + void (*read_data)(SCSIDevice *s, uint32_t tag); + int (*write_data)(SCSIDevice *s, uint32_t tag); + void (*cancel_io)(SCSIDevice *s, uint32_t tag); + uint8_t *(*get_buf)(SCSIDevice *s, uint32_t tag); +}; + +typedef void (*SCSIAttachFn)(DeviceState *host, BlockDriverState *bdrv, + int unit); +struct SCSIBus { + BusState qbus; + int busnr; + + int tcq, ndev; + scsi_completionfn complete; + + SCSIDevice *devs[MAX_SCSI_DEVS]; +}; + +void scsi_bus_new(SCSIBus *bus, DeviceState *host, int tcq, int ndev, + scsi_completionfn complete); +void scsi_qdev_register(SCSIDeviceInfo *info); + +static inline SCSIBus *scsi_bus_from_device(SCSIDevice *d) +{ + return DO_UPCAST(SCSIBus, qbus, d->qdev.parent_bus); +} + +SCSIDevice *scsi_bus_legacy_add_drive(SCSIBus *bus, BlockDriverState *bdrv, + int unit, bool removable); +int scsi_bus_legacy_handle_cmdline(SCSIBus *bus); + +SCSIRequest *scsi_req_alloc(size_t size, SCSIDevice *d, uint32_t tag, uint32_t lun); +SCSIRequest *scsi_req_find(SCSIDevice *d, uint32_t tag); +void scsi_req_free(SCSIRequest *req); + +int scsi_req_parse(SCSIRequest *req, uint8_t *buf); +void scsi_req_print(SCSIRequest *req); +void scsi_req_complete(SCSIRequest *req); + +#endif diff --git a/hw/sd.c b/hw/sd.c new file mode 100644 index 0000000..5e29752 --- /dev/null +++ b/hw/sd.c @@ -0,0 +1,1660 @@ +/* + * SD Memory Card emulation as defined in the "SD Memory Card Physical + * layer specification, Version 1.10." + * + * Copyright (c) 2006 Andrzej Zaborowski + * 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.h" +#include "block.h" +#include "block_int.h" +#include "sd.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 + +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_rsp_type_t; + +struct SDState { + enum { + sd_inactive, + sd_card_identification_mode, + sd_data_transfer_mode, + } mode; + enum { + 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, + } state; + 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; + int wp_switch; + int *wp_groups; + uint64_t size; + int blk_len; + uint32_t erase_start; + uint32_t erase_end; + uint8_t pwd[16]; + int pwd_len; + int function_group[6]; + + int spi; + int current_cmd; + int blk_written; + uint64_t data_start; + uint32_t data_offset; + uint8_t data[512]; + qemu_irq readonly_cb; + qemu_irq inserted_cb; + BlockDriverState *bdrv; + uint8_t *buf; + + int enable; +}; + +static void sd_set_status(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; + } + + sd->card_status &= ~CURRENT_STATE; + sd->card_status |= sd->state << 9; +} + +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 sd_cmd_type_t sd_acmd_type[64] = { + sd_none, sd_none, sd_none, sd_none, sd_none, sd_none, sd_ac, sd_none, + sd_none, sd_none, sd_none, sd_none, sd_none, sd_adtc, sd_none, sd_none, + sd_none, sd_none, sd_none, sd_none, sd_none, sd_none, sd_adtc, sd_ac, + 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_none, sd_none, sd_none, sd_none, + sd_none, sd_bcr, sd_ac, sd_none, sd_none, sd_none, sd_none, sd_none, + sd_none, sd_none, sd_none, sd_adtc, sd_none, sd_none, sd_none, sd_none, + sd_none, 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 Memort Card */ + } +} + +static void sd_set_rca(SDState *sd) +{ + sd->rca += 0x4567; +} + +#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 last_status) +{ + uint32_t mask = CARD_STATUS_B ^ ILLEGAL_COMMAND; + uint32_t status; + + status = (sd->card_status & ~mask) | (last_status & mask); + sd->card_status &= ~CARD_STATUS_C | APP_CMD; + + 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); + + 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 void sd_reset(SDState *sd, BlockDriverState *bdrv) +{ + uint64_t size; + uint64_t sect; + + if (bdrv) { + bdrv_get_geometry(bdrv, §); + } else { + sect = 0; + } + sect <<= 9; + + size = sect + 1; + + sect = (size >> (HWBLOCK_SHIFT + SECTOR_SHIFT + WPGROUP_SHIFT)) + 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); + + sd->bdrv = bdrv; + + if (sd->wp_groups) + qemu_free(sd->wp_groups); + sd->wp_switch = bdrv ? bdrv_is_read_only(bdrv) : 0; + sd->wp_groups = (int *) qemu_mallocz(sizeof(int) * sect); + memset(sd->function_group, 0, sizeof(int) * 6); + sd->erase_start = 0; + sd->erase_end = 0; + sd->size = size; + sd->blk_len = 0x200; + sd->pwd_len = 0; +} + +static void sd_cardchange(void *opaque, int reason) +{ + SDState *sd = opaque; + + if (!(reason & CHANGE_MEDIA)) { + return; + } + + qemu_set_irq(sd->inserted_cb, bdrv_is_inserted(sd->bdrv)); + if (bdrv_is_inserted(sd->bdrv)) { + sd_reset(sd, sd->bdrv); + qemu_set_irq(sd->readonly_cb, sd->wp_switch); + } +} + +/* 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(BlockDriverState *bs, int is_spi) +{ + SDState *sd; + + sd = (SDState *) qemu_mallocz(sizeof(SDState)); + sd->buf = qemu_blockalign(bs, 512); + sd->spi = is_spi; + sd->enable = 1; + sd_reset(sd, bs); + if (sd->bdrv) { + bdrv_set_change_cb(sd->bdrv, sd_cardchange, 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, bdrv_is_read_only(sd->bdrv)); + qemu_set_irq(insert, bdrv_is_inserted(sd->bdrv)); +} + +static void sd_erase(SDState *sd) +{ + int i, start, end; + if (!sd->erase_start || !sd->erase_end) { + sd->card_status |= ERASE_SEQ_ERROR; + return; + } + + start = sd->erase_start >> + (HWBLOCK_SHIFT + SECTOR_SHIFT + WPGROUP_SHIFT); + end = sd->erase_end >> + (HWBLOCK_SHIFT + SECTOR_SHIFT + WPGROUP_SHIFT); + sd->erase_start = 0; + sd->erase_end = 0; + sd->csd[14] |= 0x40; + + for (i = start; i <= end; i ++) + if (sd->wp_groups[i]) + 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 = addr >> (HWBLOCK_SHIFT + SECTOR_SHIFT + WPGROUP_SHIFT); + + for (i = 0; i < 32; i ++, wpnum ++, addr += WPGROUP_SIZE) + if (addr < sd->size && sd->wp_groups[wpnum]) + 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 int sd_wp_addr(SDState *sd, uint32_t addr) +{ + return sd->wp_groups[addr >> + (HWBLOCK_SHIFT + SECTOR_SHIFT + WPGROUP_SHIFT)]; +} + +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; + } + memset(sd->wp_groups, 0, sizeof(int) * (sd->size >> + (HWBLOCK_SHIFT + SECTOR_SHIFT + WPGROUP_SHIFT))); + 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; + + 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, sd->bdrv); + 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 */ + sd->card_status |= ILLEGAL_COMMAND; + return sd_r0; + + 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 >> ffs(req.arg & ~0xff))) + 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; + sd->wp_groups[addr >> (HWBLOCK_SHIFT + + SECTOR_SHIFT + WPGROUP_SHIFT)] = 1; + /* 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; + sd->wp_groups[addr >> (HWBLOCK_SHIFT + + SECTOR_SHIFT + WPGROUP_SHIFT)] = 0; + /* 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; + + /* Application specific commands (Class 8) */ + case 55: /* CMD55: APP_CMD */ + if (sd->rca != rca) + return sd_r0; + + 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: + sd->card_status |= ILLEGAL_COMMAND; + + fprintf(stderr, "SD: Unknown CMD%i\n", req.cmd); + return sd_r0; + + unimplemented_cmd: + /* Commands that are recognised but not yet implemented in SPI mode. */ + sd->card_status |= ILLEGAL_COMMAND; + fprintf(stderr, "SD: CMD%i not implemented in SPI mode\n", req.cmd); + return sd_r0; + } + + sd->card_status |= ILLEGAL_COMMAND; + fprintf(stderr, "SD: CMD%i in a wrong state\n", req.cmd); + return sd_r0; +} + +static sd_rsp_type_t sd_app_command(SDState *sd, + SDRequest req) +{ + DPRINTF("ACMD%d 0x%08x\n", req.cmd, req.arg); + 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. */ + if (req.arg) + 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. */ + sd->card_status &= ~APP_CMD; + return sd_normal_command(sd, req); + } + + fprintf(stderr, "SD: ACMD%i in a wrong state\n", req.cmd); + return sd_r0; +} + +int sd_do_command(SDState *sd, SDRequest *req, + uint8_t *response) { + uint32_t last_status = sd->card_status; + sd_rsp_type_t rtype; + int rsplen; + + if (!sd->bdrv || !bdrv_is_inserted(sd->bdrv) || !sd->enable) { + return 0; + } + + if (sd_req_crc_validate(req)) { + sd->card_status &= ~COM_CRC_ERROR; + return 0; + } + + sd->card_status &= ~CARD_STATUS_B; + sd_set_status(sd); + + if (last_status & CARD_IS_LOCKED) + if (((last_status & APP_CMD) && + req->cmd == 41) || + (!(last_status & APP_CMD) && + (sd_cmd_class[req->cmd] == 0 || + sd_cmd_class[req->cmd] == 7 || + req->cmd == 16 || req->cmd == 55))) { + sd->card_status |= ILLEGAL_COMMAND; + fprintf(stderr, "SD: Card is locked\n"); + return 0; + } + + if (last_status & APP_CMD) { + rtype = sd_app_command(sd, *req); + sd->card_status &= ~APP_CMD; + } else + rtype = sd_normal_command(sd, *req); + + sd->current_cmd = req->cmd; + + switch (rtype) { + case sd_r1: + case sd_r1b: + sd_response_r1_make(sd, response, last_status); + 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: + default: + rsplen = 0; + break; + } + + if (sd->card_status & ILLEGAL_COMMAND) + rsplen = 0; + +#ifdef DEBUG_SD + if (rsplen) { + int i; + DPRINTF("Response:"); + for (i = 0; i < rsplen; i++) + printf(" %02x", response[i]); + printf(" 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->bdrv || bdrv_read(sd->bdrv, addr >> 9, sd->buf, 1) == -1) { + 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 (bdrv_read(sd->bdrv, end >> 9, sd->buf, 1) == -1) { + 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->bdrv || bdrv_read(sd->bdrv, addr >> 9, sd->buf, 1) == -1) { + 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 (bdrv_write(sd->bdrv, addr >> 9, sd->buf, 1) == -1) { + fprintf(stderr, "sd_blk_write: write error on host side\n"); + return; + } + + if (bdrv_read(sd->bdrv, end >> 9, sd->buf, 1) == -1) { + fprintf(stderr, "sd_blk_write: read error on host side\n"); + return; + } + memcpy(sd->buf, sd->data + 512 - (addr & 511), end & 511); + if (bdrv_write(sd->bdrv, end >> 9, sd->buf, 1) == -1) + fprintf(stderr, "sd_blk_write: write error on host side\n"); + } else { + memcpy(sd->buf + (addr & 511), sd->data, len); + if (!sd->bdrv || bdrv_write(sd->bdrv, addr >> 9, sd->buf, 1) == -1) + 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->bdrv || !bdrv_is_inserted(sd->bdrv) || !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 */ + 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; + 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->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->bdrv || !bdrv_is_inserted(sd->bdrv) || !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; +} + +int sd_data_ready(SDState *sd) +{ + return sd->state == sd_sendingdata_state; +} + +void sd_enable(SDState *sd, int enable) +{ + sd->enable = enable; +} diff --git a/hw/sd.h b/hw/sd.h new file mode 100644 index 0000000..ac4b7c4 --- /dev/null +++ b/hw/sd.h @@ -0,0 +1,79 @@ +/* + * SD Memory Card emulation. Mostly correct for MMC too. + * + * Copyright (c) 2006 Andrzej Zaborowski + * + * 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 __hw_sd_h +#define __hw_sd_h 1 + +#define OUT_OF_RANGE (1 << 31) +#define ADDRESS_ERROR (1 << 30) +#define BLOCK_LEN_ERROR (1 << 29) +#define ERASE_SEQ_ERROR (1 << 28) +#define ERASE_PARAM (1 << 27) +#define WP_VIOLATION (1 << 26) +#define CARD_IS_LOCKED (1 << 25) +#define LOCK_UNLOCK_FAILED (1 << 24) +#define COM_CRC_ERROR (1 << 23) +#define ILLEGAL_COMMAND (1 << 22) +#define CARD_ECC_FAILED (1 << 21) +#define CC_ERROR (1 << 20) +#define SD_ERROR (1 << 19) +#define CID_CSD_OVERWRITE (1 << 16) +#define WP_ERASE_SKIP (1 << 15) +#define CARD_ECC_DISABLED (1 << 14) +#define ERASE_RESET (1 << 13) +#define CURRENT_STATE (7 << 9) +#define READY_FOR_DATA (1 << 8) +#define APP_CMD (1 << 5) +#define AKE_SEQ_ERROR (1 << 3) + +typedef enum { + sd_none = -1, + sd_bc = 0, /* broadcast -- no response */ + sd_bcr, /* broadcast with response */ + sd_ac, /* addressed -- no data transfer */ + sd_adtc, /* addressed with data transfer */ +} sd_cmd_type_t; + +typedef struct { + uint8_t cmd; + uint32_t arg; + uint8_t crc; +} SDRequest; + +typedef struct SDState SDState; + +SDState *sd_init(BlockDriverState *bs, int is_spi); +int sd_do_command(SDState *sd, SDRequest *req, + uint8_t *response); +void sd_write_data(SDState *sd, uint8_t value); +uint8_t sd_read_data(SDState *sd); +void sd_set_cb(SDState *sd, qemu_irq readonly, qemu_irq insert); +int sd_data_ready(SDState *sd); +void sd_enable(SDState *sd, int enable); + +#endif /* __hw_sd_h */ diff --git a/hw/serial.c b/hw/serial.c new file mode 100644 index 0000000..2c4af61 --- /dev/null +++ b/hw/serial.c @@ -0,0 +1,991 @@ +/* + * 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.h" +#include "qemu-char.h" +#include "isa.h" +#include "pc.h" +#include "qemu-timer.h" +#include "sysemu.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 UART_FIFO_LENGTH 16 /* 16550A Fifo Length */ + +#define XMIT_FIFO 0 +#define RECV_FIFO 1 +#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 + +typedef struct SerialFIFO { + uint8_t data[UART_FIFO_LENGTH]; + uint8_t count; + uint8_t itl; /* Interrupt Trigger Level */ + uint8_t tail; + uint8_t head; +} SerialFIFO; + +struct SerialState { + uint16_t divider; + uint8_t rbr; /* receive register */ + uint8_t thr; /* transmit holding register */ + uint8_t tsr; /* transmit shift register */ + uint8_t ier; + uint8_t iir; /* read only */ + uint8_t lcr; + uint8_t mcr; + uint8_t lsr; /* read only */ + uint8_t msr; /* read only */ + uint8_t scr; + uint8_t fcr; + uint8_t fcr_vmstate; /* we can't write directly this value + it has side effects */ + /* NOTE: this hidden state is necessary for tx irq generation as + it can be reset while reading iir */ + int thr_ipending; + qemu_irq irq; + CharDriverState *chr; + int last_break_enable; + int it_shift; + int baudbase; + int tsr_retry; + + uint64_t last_xmit_ts; /* Time when the last byte was successfully sent out of the tsr */ + SerialFIFO recv_fifo; + SerialFIFO xmit_fifo; + + struct QEMUTimer *fifo_timeout_timer; + int timeout_ipending; /* timeout interrupt pending state */ + struct QEMUTimer *transmit_timer; + + + uint64_t char_transmit_time; /* time to transmit a char in ticks*/ + int poll_msl; + + struct QEMUTimer *modem_status_poll; +}; + +typedef struct ISASerialState { + ISADevice dev; + uint32_t index; + uint32_t iobase; + uint32_t isairq; + SerialState state; +} ISASerialState; + +static void serial_receive1(void *opaque, const uint8_t *buf, int size); + +static void fifo_clear(SerialState *s, int fifo) +{ + SerialFIFO *f = (fifo) ? &s->recv_fifo : &s->xmit_fifo; + memset(f->data, 0, UART_FIFO_LENGTH); + f->count = 0; + f->head = 0; + f->tail = 0; +} + +static int fifo_put(SerialState *s, int fifo, uint8_t chr) +{ + SerialFIFO *f = (fifo) ? &s->recv_fifo : &s->xmit_fifo; + + /* Receive overruns do not overwrite FIFO contents. */ + if (fifo == XMIT_FIFO || f->count < UART_FIFO_LENGTH) { + + f->data[f->head++] = chr; + + if (f->head == UART_FIFO_LENGTH) + f->head = 0; + } + + if (f->count < UART_FIFO_LENGTH) + f->count++; + else if (fifo == RECV_FIFO) + s->lsr |= UART_LSR_OE; + + return 1; +} + +static uint8_t fifo_get(SerialState *s, int fifo) +{ + SerialFIFO *f = (fifo) ? &s->recv_fifo : &s->xmit_fifo; + uint8_t c; + + if(f->count == 0) + return 0; + + c = f->data[f->tail++]; + if (f->tail == UART_FIFO_LENGTH) + f->tail = 0; + f->count--; + + return c; +} + +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.count >= 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_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; + + qemu_del_timer(s->modem_status_poll); + + if (qemu_chr_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) + qemu_mod_timer(s->modem_status_poll, qemu_get_clock(vm_clock) + get_ticks_per_sec() / 100); +} + +static void serial_xmit(void *opaque) +{ + SerialState *s = opaque; + uint64_t new_xmit_ts = qemu_get_clock(vm_clock); + + if (s->tsr_retry <= 0) { + if (s->fcr & UART_FCR_FE) { + s->tsr = fifo_get(s,XMIT_FIFO); + if (!s->xmit_fifo.count) + s->lsr |= UART_LSR_THRE; + } else { + s->tsr = s->thr; + s->lsr |= UART_LSR_THRE; + } + } + + 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_write(s->chr, &s->tsr, 1) != 1) { + if ((s->tsr_retry > 0) && (s->tsr_retry <= MAX_XMIT_RETRY)) { + s->tsr_retry++; + qemu_mod_timer(s->transmit_timer, new_xmit_ts + s->char_transmit_time); + return; + } else if (s->poll_msl < 0) { + /* If we exceed MAX_XMIT_RETRY and the backend is not a real serial port, then + drop any further failed writes instantly, until we get one that goes through. + This is to prevent guests that log to unconnected pipes or pty's from stalling. */ + s->tsr_retry = -1; + } + } + else { + s->tsr_retry = 0; + } + + s->last_xmit_ts = qemu_get_clock(vm_clock); + if (!(s->lsr & UART_LSR_THRE)) + qemu_mod_timer(s->transmit_timer, s->last_xmit_ts + s->char_transmit_time); + + if (s->lsr & UART_LSR_THRE) { + s->lsr |= UART_LSR_TEMT; + s->thr_ipending = 1; + serial_update_irq(s); + } +} + + +static void serial_ioport_write(void *opaque, uint32_t addr, uint32_t val) +{ + SerialState *s = opaque; + + addr &= 7; + DPRINTF("write addr=0x%02x val=0x%02x\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) { + fifo_put(s, XMIT_FIFO, s->thr); + s->thr_ipending = 0; + s->lsr &= ~UART_LSR_TEMT; + s->lsr &= ~UART_LSR_THRE; + serial_update_irq(s); + } else { + s->thr_ipending = 0; + s->lsr &= ~UART_LSR_THRE; + serial_update_irq(s); + } + serial_xmit(s); + } + break; + case 1: + if (s->lcr & UART_LCR_DLAB) { + s->divider = (s->divider & 0x00ff) | (val << 8); + serial_update_parameters(s); + } else { + 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 (s->poll_msl >= 0) { + if (s->ier & UART_IER_MSI) { + s->poll_msl = 1; + serial_update_msl(s); + } else { + qemu_del_timer(s->modem_status_poll); + s->poll_msl = 0; + } + } + if (s->lsr & UART_LSR_THRE) { + s->thr_ipending = 1; + serial_update_irq(s); + } + } + break; + case 2: + val = val & 0xFF; + + if (s->fcr == val) + break; + + /* 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) { + qemu_del_timer(s->fifo_timeout_timer); + s->timeout_ipending=0; + fifo_clear(s,RECV_FIFO); + } + + if (val & UART_FCR_XFR) { + fifo_clear(s,XMIT_FIFO); + } + + 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; + + /* Set fcr - or at least the bits in it that are supposed to "stick" */ + s->fcr = 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_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_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_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 */ + qemu_mod_timer(s->modem_status_poll, qemu_get_clock(vm_clock) + s->char_transmit_time); + } + } + break; + case 5: + break; + case 6: + break; + case 7: + s->scr = val; + break; + } +} + +static uint32_t serial_ioport_read(void *opaque, uint32_t addr) +{ + 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 = fifo_get(s,RECV_FIFO); + if (s->recv_fifo.count == 0) + s->lsr &= ~(UART_LSR_DR | UART_LSR_BI); + else + qemu_mod_timer(s->fifo_timeout_timer, qemu_get_clock (vm_clock) + 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%02x 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.count < 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.count <= s->recv_fifo.itl) ? s->recv_fifo.itl - s->recv_fifo.count : 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 */ + fifo_put(s, RECV_FIFO, '\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.count) { + 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->fcr & UART_FCR_FE) { + int i; + for (i = 0; i < size; i++) { + fifo_put(s, RECV_FIFO, buf[i]); + } + s->lsr |= UART_LSR_DR; + /* call the timeout receive callback in 4 char transmit time */ + qemu_mod_timer(s->fifo_timeout_timer, qemu_get_clock (vm_clock) + 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_post_load(void *opaque, int version_id) +{ + SerialState *s = opaque; + + if (version_id < 3) { + s->fcr_vmstate = 0; + } + /* Initialize fcr via setter to perform essential side-effects */ + serial_ioport_write(s, 0x02, s->fcr_vmstate); + serial_update_parameters(s); + return 0; +} + +static const VMStateDescription vmstate_serial = { + .name = "serial", + .version_id = 3, + .minimum_version_id = 2, + .pre_save = serial_pre_save, + .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() + } +}; + +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; + + fifo_clear(s,RECV_FIFO); + fifo_clear(s,XMIT_FIFO); + + s->last_xmit_ts = qemu_get_clock(vm_clock); + + s->thr_ipending = 0; + s->last_break_enable = 0; + qemu_irq_lower(s->irq); +} + +static void serial_init_core(SerialState *s) +{ + if (!s->chr) { + fprintf(stderr, "Can't create serial device, empty char device\n"); + exit(1); + } + + s->modem_status_poll = qemu_new_timer(vm_clock, (QEMUTimerCB *) serial_update_msl, s); + + s->fifo_timeout_timer = qemu_new_timer(vm_clock, (QEMUTimerCB *) fifo_timeout_int, s); + s->transmit_timer = qemu_new_timer(vm_clock, (QEMUTimerCB *) serial_xmit, s); + + qemu_register_reset(serial_reset, s); + + qemu_chr_add_handlers(s->chr, serial_can_receive1, serial_receive1, + serial_event, s); +} + +/* Change the main reference oscillator frequency. */ +void serial_set_frequency(SerialState *s, uint32_t frequency) +{ + s->baudbase = frequency; + serial_update_parameters(s); +} + +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 int serial_isa_initfn(ISADevice *dev) +{ + static int index; + ISASerialState *isa = DO_UPCAST(ISASerialState, dev, dev); + SerialState *s = &isa->state; + + if (isa->index == -1) + isa->index = index; + if (isa->index >= MAX_SERIAL_PORTS) + return -1; + 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(dev, &s->irq, isa->isairq); + serial_init_core(s); + qdev_set_legacy_instance_id(&dev->qdev, isa->iobase, 3); + + register_ioport_write(isa->iobase, 8, 1, serial_ioport_write, s); + register_ioport_read(isa->iobase, 8, 1, serial_ioport_read, s); + isa_init_ioport_range(dev, isa->iobase, 8); + return 0; +} + +SerialState *serial_isa_init(int index, CharDriverState *chr) +{ + ISADevice *dev; + + dev = isa_create("isa-serial"); + qdev_prop_set_uint32(&dev->qdev, "index", index); + qdev_prop_set_chr(&dev->qdev, "chardev", chr); + if (qdev_init(&dev->qdev) < 0) + return NULL; + return &DO_UPCAST(ISASerialState, dev, dev)->state; +} + +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() + } +}; + +SerialState *serial_init(int base, qemu_irq irq, int baudbase, + CharDriverState *chr) +{ + SerialState *s; + + s = qemu_mallocz(sizeof(SerialState)); + + s->irq = irq; + s->baudbase = baudbase; + s->chr = chr; + serial_init_core(s); + + vmstate_register(NULL, base, &vmstate_serial, s); + + register_ioport_write(base, 8, 1, serial_ioport_write, s); + register_ioport_read(base, 8, 1, serial_ioport_read, s); + return s; +} + +/* Memory mapped interface */ +static uint32_t serial_mm_readb(void *opaque, target_phys_addr_t addr) +{ + SerialState *s = opaque; + + return serial_ioport_read(s, addr >> s->it_shift) & 0xFF; +} + +static void serial_mm_writeb(void *opaque, target_phys_addr_t addr, + uint32_t value) +{ + SerialState *s = opaque; + + serial_ioport_write(s, addr >> s->it_shift, value & 0xFF); +} + +static uint32_t serial_mm_readw_be(void *opaque, target_phys_addr_t addr) +{ + SerialState *s = opaque; + uint32_t val; + + val = serial_ioport_read(s, addr >> s->it_shift) & 0xFFFF; + val = bswap16(val); + return val; +} + +static uint32_t serial_mm_readw_le(void *opaque, target_phys_addr_t addr) +{ + SerialState *s = opaque; + uint32_t val; + + val = serial_ioport_read(s, addr >> s->it_shift) & 0xFFFF; + return val; +} + +static void serial_mm_writew_be(void *opaque, target_phys_addr_t addr, + uint32_t value) +{ + SerialState *s = opaque; + + value = bswap16(value); + serial_ioport_write(s, addr >> s->it_shift, value & 0xFFFF); +} + +static void serial_mm_writew_le(void *opaque, target_phys_addr_t addr, + uint32_t value) +{ + SerialState *s = opaque; + + serial_ioport_write(s, addr >> s->it_shift, value & 0xFFFF); +} + +static uint32_t serial_mm_readl_be(void *opaque, target_phys_addr_t addr) +{ + SerialState *s = opaque; + uint32_t val; + + val = serial_ioport_read(s, addr >> s->it_shift); + val = bswap32(val); + return val; +} + +static uint32_t serial_mm_readl_le(void *opaque, target_phys_addr_t addr) +{ + SerialState *s = opaque; + uint32_t val; + + val = serial_ioport_read(s, addr >> s->it_shift); + return val; +} + +static void serial_mm_writel_be(void *opaque, target_phys_addr_t addr, + uint32_t value) +{ + SerialState *s = opaque; + + value = bswap32(value); + serial_ioport_write(s, addr >> s->it_shift, value); +} + +static void serial_mm_writel_le(void *opaque, target_phys_addr_t addr, + uint32_t value) +{ + SerialState *s = opaque; + + serial_ioport_write(s, addr >> s->it_shift, value); +} + +static CPUReadMemoryFunc * const serial_mm_read_be[] = { + &serial_mm_readb, + &serial_mm_readw_be, + &serial_mm_readl_be, +}; + +static CPUWriteMemoryFunc * const serial_mm_write_be[] = { + &serial_mm_writeb, + &serial_mm_writew_be, + &serial_mm_writel_be, +}; + +static CPUReadMemoryFunc * const serial_mm_read_le[] = { + &serial_mm_readb, + &serial_mm_readw_le, + &serial_mm_readl_le, +}; + +static CPUWriteMemoryFunc * const serial_mm_write_le[] = { + &serial_mm_writeb, + &serial_mm_writew_le, + &serial_mm_writel_le, +}; + +SerialState *serial_mm_init (target_phys_addr_t base, int it_shift, + qemu_irq irq, int baudbase, + CharDriverState *chr, int ioregister, + int be) +{ + SerialState *s; + int s_io_memory; + + s = qemu_mallocz(sizeof(SerialState)); + + s->it_shift = it_shift; + s->irq = irq; + s->baudbase = baudbase; + s->chr = chr; + + serial_init_core(s); + vmstate_register(NULL, base, &vmstate_serial, s); + + if (ioregister) { + if (be) { + s_io_memory = cpu_register_io_memory(serial_mm_read_be, + serial_mm_write_be, s, + DEVICE_NATIVE_ENDIAN); + } else { + s_io_memory = cpu_register_io_memory(serial_mm_read_le, + serial_mm_write_le, s, + DEVICE_NATIVE_ENDIAN); + } + cpu_register_physical_memory(base, 8 << it_shift, s_io_memory); + } + serial_update_msl(s); + return s; +} + +static ISADeviceInfo serial_isa_info = { + .qdev.name = "isa-serial", + .qdev.size = sizeof(ISASerialState), + .qdev.vmsd = &vmstate_isa_serial, + .init = serial_isa_initfn, + .qdev.props = (Property[]) { + DEFINE_PROP_UINT32("index", ISASerialState, index, -1), + DEFINE_PROP_HEX32("iobase", ISASerialState, iobase, -1), + DEFINE_PROP_UINT32("irq", ISASerialState, isairq, -1), + DEFINE_PROP_CHR("chardev", ISASerialState, state.chr), + DEFINE_PROP_END_OF_LIST(), + }, +}; + +static void serial_register_devices(void) +{ + isa_qdev_register(&serial_isa_info); +} + +device_init(serial_register_devices) diff --git a/hw/sh.h b/hw/sh.h new file mode 100644 index 0000000..d30e9f5 --- /dev/null +++ b/hw/sh.h @@ -0,0 +1,54 @@ +#ifndef QEMU_SH_H +#define QEMU_SH_H +/* Definitions for SH board emulation. */ + +#include "sh_intc.h" + +#define A7ADDR(x) ((x) & 0x1fffffff) +#define P4ADDR(x) ((x) | 0xe0000000) + +/* sh7750.c */ +struct SH7750State; + +struct SH7750State *sh7750_init(CPUState * cpu); + +typedef struct { + /* The callback will be triggered if any of the designated lines change */ + uint16_t portamask_trigger; + uint16_t portbmask_trigger; + /* Return 0 if no action was taken */ + int (*port_change_cb) (uint16_t porta, uint16_t portb, + uint16_t * periph_pdtra, + uint16_t * periph_portdira, + uint16_t * periph_pdtrb, + uint16_t * periph_portdirb); +} sh7750_io_device; + +int sh7750_register_io_device(struct SH7750State *s, + sh7750_io_device * device); +/* sh_timer.c */ +#define TMU012_FEAT_TOCR (1 << 0) +#define TMU012_FEAT_3CHAN (1 << 1) +#define TMU012_FEAT_EXTCLK (1 << 2) +void tmu012_init(target_phys_addr_t base, int feat, uint32_t freq, + qemu_irq ch0_irq, qemu_irq ch1_irq, + qemu_irq ch2_irq0, qemu_irq ch2_irq1); + + +/* sh_serial.c */ +#define SH_SERIAL_FEAT_SCIF (1 << 0) +void sh_serial_init (target_phys_addr_t 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); + +/* sh7750.c */ +qemu_irq sh7750_irl(struct SH7750State *s); + +/* tc58128.c */ +int tc58128_init(struct SH7750State *s, const char *zone1, const char *zone2); + +#endif diff --git a/hw/sh7750.c b/hw/sh7750.c new file mode 100644 index 0000000..19d5bf8 --- /dev/null +++ b/hw/sh7750.c @@ -0,0 +1,818 @@ +/* + * 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 +#include "hw.h" +#include "sh.h" +#include "sysemu.h" +#include "sh7750_regs.h" +#include "sh7750_regnames.h" +#include "sh_intc.h" +#include "exec-all.h" +#include "cpu.h" + +#define NB_DEVICES 4 + +typedef struct SH7750State { + /* CPU */ + CPUSH4State *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->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, target_phys_addr_t addr) +{ + fprintf(stderr, "%s to %s (0x" TARGET_FMT_plx ") not supported\n", + kind, regname(addr), addr); +} + +static void ignore_access(const char *kind, target_phys_addr_t addr) +{ + fprintf(stderr, "%s to %s (0x" TARGET_FMT_plx ") ignored\n", + kind, regname(addr), addr); +} + +static uint32_t sh7750_mem_readb(void *opaque, target_phys_addr_t addr) +{ + switch (addr) { + default: + error_access("byte read", addr); + abort(); + } +} + +static uint32_t sh7750_mem_readw(void *opaque, target_phys_addr_t 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, target_phys_addr_t addr) +{ + SH7750State *s = opaque; + + 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->mmucr; + case SH7750_PTEH_A7: + return s->cpu->pteh; + case SH7750_PTEL_A7: + return s->cpu->ptel; + case SH7750_TTB_A7: + return s->cpu->ttb; + case SH7750_TEA_A7: + return s->cpu->tea; + case SH7750_TRA_A7: + return s->cpu->tra; + case SH7750_EXPEVT_A7: + return s->cpu->expevt; + case SH7750_INTEVT_A7: + return s->cpu->intevt; + case SH7750_CCR_A7: + return s->ccr; + case 0x1f000030: /* Processor version */ + return s->cpu->pvr; + case 0x1f000040: /* Cache version */ + return s->cpu->cvr; + case 0x1f000044: /* Processor revision */ + return s->cpu->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, target_phys_addr_t 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, target_phys_addr_t 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, target_phys_addr_t 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); + } + s->cpu->mmucr = mem_value & ~MMUCR_TI; + return; + case SH7750_PTEH_A7: + /* If asid changes, clear all registered tlb entries. */ + if ((s->cpu->pteh & 0xff) != (mem_value & 0xff)) + tlb_flush(s->cpu, 1); + s->cpu->pteh = mem_value; + return; + case SH7750_PTEL_A7: + s->cpu->ptel = mem_value; + return; + case SH7750_PTEA_A7: + s->cpu->ptea = mem_value & 0x0000000f; + return; + case SH7750_TTB_A7: + s->cpu->ttb = mem_value; + return; + case SH7750_TEA_A7: + s->cpu->tea = mem_value; + return; + case SH7750_TRA_A7: + s->cpu->tra = mem_value & 0x000007ff; + return; + case SH7750_EXPEVT_A7: + s->cpu->expevt = mem_value & 0x000007ff; + return; + case SH7750_INTEVT_A7: + s->cpu->intevt = mem_value & 0x000007ff; + return; + case SH7750_CCR_A7: + s->ccr = mem_value; + return; + default: + error_access("long write", addr); + abort(); + } +} + +static CPUReadMemoryFunc * const sh7750_mem_read[] = { + sh7750_mem_readb, + sh7750_mem_readw, + sh7750_mem_readl +}; + +static CPUWriteMemoryFunc * const sh7750_mem_write[] = { + sh7750_mem_writeb, + sh7750_mem_writew, + sh7750_mem_writel +}; + +/* 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 uint32_t invalid_read(void *opaque, target_phys_addr_t addr) +{ + abort(); + + return 0; +} + +static uint32_t sh7750_mmct_readl(void *opaque, target_phys_addr_t addr) +{ + SH7750State *s = opaque; + uint32_t ret = 0; + + 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, addr); + break; + case MM_ITLB_DATA: + ret = cpu_sh4_read_mmaped_itlb_data(s->cpu, 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, addr); + break; + case MM_UTLB_DATA: + ret = cpu_sh4_read_mmaped_utlb_data(s->cpu, addr); + break; + default: + abort(); + } + + return ret; +} + +static void invalid_write(void *opaque, target_phys_addr_t addr, + uint32_t mem_value) +{ + abort(); +} + +static void sh7750_mmct_writel(void *opaque, target_phys_addr_t addr, + uint32_t mem_value) +{ + SH7750State *s = opaque; + + 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, addr, mem_value); + break; + case MM_ITLB_DATA: + cpu_sh4_write_mmaped_itlb_data(s->cpu, 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, addr, mem_value); + break; + case MM_UTLB_DATA: + cpu_sh4_write_mmaped_utlb_data(s->cpu, addr, mem_value); + break; + default: + abort(); + break; + } +} + +static CPUReadMemoryFunc * const sh7750_mmct_read[] = { + invalid_read, + invalid_read, + sh7750_mmct_readl +}; + +static CPUWriteMemoryFunc * const sh7750_mmct_write[] = { + invalid_write, + invalid_write, + sh7750_mmct_writel +}; + +SH7750State *sh7750_init(CPUSH4State * cpu) +{ + SH7750State *s; + int sh7750_io_memory; + int sh7750_mm_cache_and_tlb; /* memory mapped cache and tlb */ + + s = qemu_mallocz(sizeof(SH7750State)); + s->cpu = cpu; + s->periph_freq = 60000000; /* 60MHz */ + sh7750_io_memory = cpu_register_io_memory(sh7750_mem_read, + sh7750_mem_write, s, + DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory_offset(0x1f000000, 0x1000, + sh7750_io_memory, 0x1f000000); + cpu_register_physical_memory_offset(0xff000000, 0x1000, + sh7750_io_memory, 0x1f000000); + cpu_register_physical_memory_offset(0x1f800000, 0x1000, + sh7750_io_memory, 0x1f800000); + cpu_register_physical_memory_offset(0xff800000, 0x1000, + sh7750_io_memory, 0x1f800000); + cpu_register_physical_memory_offset(0x1fc00000, 0x1000, + sh7750_io_memory, 0x1fc00000); + cpu_register_physical_memory_offset(0xffc00000, 0x1000, + sh7750_io_memory, 0x1fc00000); + + sh7750_mm_cache_and_tlb = cpu_register_io_memory(sh7750_mmct_read, + sh7750_mmct_write, s, + DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(0xf0000000, 0x08000000, + sh7750_mm_cache_and_tlb); + + sh_intc_init(&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->intc_handle = &s->intc; + + sh_serial_init(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(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(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->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->id & (SH_CPU_SH7750R | SH_CPU_SH7751R)) { + sh_intc_register_sources(&s->intc, + _INTC_ARRAY(vectors_dma8), + _INTC_ARRAY(groups_dma8)); + } + + if (cpu->id & (SH_CPU_SH7750R | SH_CPU_SH7751 | SH_CPU_SH7751R)) { + sh_intc_register_sources(&s->intc, + _INTC_ARRAY(vectors_tmu34), + NULL, 0); + tmu012_init(0x1e100000, 0, s->periph_freq, + s->intc.irqs[TMU3], + s->intc.irqs[TMU4], + NULL, NULL); + } + + if (cpu->id & (SH_CPU_SH7751_ALL)) { + sh_intc_register_sources(&s->intc, + _INTC_ARRAY(vectors_pci), + _INTC_ARRAY(groups_pci)); + } + + if (cpu->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_irqs(sh_intc_set_irl, sh_intc_source(&s->intc, IRL), + 1)[0]; +} diff --git a/hw/sh7750_regnames.c b/hw/sh7750_regnames.c new file mode 100644 index 0000000..5a5a2d8 --- /dev/null +++ b/hw/sh7750_regnames.c @@ -0,0 +1,97 @@ +#include "hw.h" +#include "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 ""; +} diff --git a/hw/sh7750_regnames.h b/hw/sh7750_regnames.h new file mode 100644 index 0000000..7463709 --- /dev/null +++ b/hw/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/sh7750_regs.h b/hw/sh7750_regs.h new file mode 100644 index 0000000..5a23a2c --- /dev/null +++ b/hw/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 + * Victor V. Vengerov + * + * 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 /* Accessable only in + priveleged mode */ +#define SH7750_A7_BASE 0x1f000000 /* Accessable 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 instuction - 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/sh_intc.c b/hw/sh_intc.c new file mode 100644 index 0000000..0734da9 --- /dev/null +++ b/hw/sh_intc.c @@ -0,0 +1,481 @@ +/* + * 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 licenced under the GPL. + */ + +#include "sh_intc.h" +#include "hw.h" +#include "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 uint32_t sh_intc_read(void *opaque, target_phys_addr_t offset) +{ + 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, target_phys_addr_t offset, + uint32_t value) +{ + 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 CPUReadMemoryFunc * const sh_intc_readfn[] = { + sh_intc_read, + sh_intc_read, + sh_intc_read +}; + +static CPUWriteMemoryFunc * const sh_intc_writefn[] = { + sh_intc_write, + sh_intc_write, + sh_intc_write +}; + +struct intc_source *sh_intc_source(struct intc_desc *desc, intc_enum id) +{ + if (id) + return desc->sources + id; + + return NULL; +} + +static void sh_intc_register(struct intc_desc *desc, + unsigned long address) +{ + if (address) { + cpu_register_physical_memory_offset(P4ADDR(address), 4, + desc->iomemtype, INTC_A7(address)); + cpu_register_physical_memory_offset(A7ADDR(address), 4, + desc->iomemtype, INTC_A7(address)); + } +} + +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(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; + + 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; + + i = sizeof(struct intc_source) * nr_sources; + desc->sources = qemu_mallocz(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); + + desc->iomemtype = cpu_register_io_memory(sh_intc_readfn, + sh_intc_writefn, desc, + DEVICE_NATIVE_ENDIAN); + if (desc->mask_regs) { + for (i = 0; i < desc->nr_mask_regs; i++) { + struct intc_mask_reg *mr = desc->mask_regs + i; + + sh_intc_register(desc, mr->set_reg); + sh_intc_register(desc, mr->clr_reg); + } + } + + if (desc->prio_regs) { + for (i = 0; i < desc->nr_prio_regs; i++) { + struct intc_prio_reg *pr = desc->prio_regs + i; + + sh_intc_register(desc, pr->set_reg); + sh_intc_register(desc, pr->clr_reg); + } + } + + return 0; +} + +/* Assert level 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/sh_intc.h b/hw/sh_intc.h new file mode 100644 index 0000000..c117d6f --- /dev/null +++ b/hw/sh_intc.h @@ -0,0 +1,80 @@ +#ifndef __SH_INTC_H__ +#define __SH_INTC_H__ + +#include "qemu-common.h" +#include "irq.h" + +typedef unsigned char intc_enum; + +struct intc_vect { + intc_enum enum_id; + unsigned short vect; +}; + +#define INTC_VECT(enum_id, vect) { enum_id, vect } + +struct intc_group { + intc_enum enum_id; + intc_enum enum_ids[32]; +}; + +#define INTC_GROUP(enum_id, ...) { enum_id, { __VA_ARGS__ } } + +struct intc_mask_reg { + unsigned long set_reg, clr_reg, reg_width; + intc_enum enum_ids[32]; + unsigned long value; +}; + +struct intc_prio_reg { + unsigned long set_reg, clr_reg, reg_width, field_width; + intc_enum enum_ids[16]; + unsigned long value; +}; + +#define _INTC_ARRAY(a) a, ARRAY_SIZE(a) + +struct intc_source { + unsigned short vect; + intc_enum next_enum_id; + + int asserted; /* emulates the interrupt signal line from device to intc */ + int enable_count; + int enable_max; + int pending; /* emulates the result of signal and masking */ + struct intc_desc *parent; +}; + +struct intc_desc { + qemu_irq *irqs; + struct intc_source *sources; + int nr_sources; + struct intc_mask_reg *mask_regs; + int nr_mask_regs; + struct intc_prio_reg *prio_regs; + int nr_prio_regs; + int iomemtype; + int pending; /* number of interrupt sources that has pending set */ +}; + +int sh_intc_get_pending_vector(struct intc_desc *desc, int imask); +struct intc_source *sh_intc_source(struct intc_desc *desc, intc_enum id); +void sh_intc_toggle_source(struct intc_source *source, + int enable_adj, int assert_adj); + +void sh_intc_register_sources(struct intc_desc *desc, + struct intc_vect *vectors, + int nr_vectors, + struct intc_group *groups, + int nr_groups); + +int sh_intc_init(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); + +void sh_intc_set_irl(void *opaque, int n, int level); + +#endif /* __SH_INTC_H__ */ diff --git a/hw/sh_pci.c b/hw/sh_pci.c new file mode 100644 index 0000000..e99d8db --- /dev/null +++ b/hw/sh_pci.c @@ -0,0 +1,161 @@ +/* + * 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 "sysbus.h" +#include "sh.h" +#include "pci.h" +#include "pci_host.h" +#include "bswap.h" + +typedef struct SHPCIState { + SysBusDevice busdev; + PCIBus *bus; + PCIDevice *dev; + qemu_irq irq[4]; + int memconfig; + uint32_t par; + uint32_t mbr; + uint32_t iobr; +} SHPCIState; + +static void sh_pci_reg_write (void *p, target_phys_addr_t addr, uint32_t val) +{ + SHPCIState *pcic = p; + 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)) { + cpu_register_physical_memory(pcic->iobr & 0xfffc0000, 0x40000, + IO_MEM_UNASSIGNED); + pcic->iobr = val & 0xfffc0001; + isa_mmio_init(pcic->iobr & 0xfffc0000, 0x40000); + } + break; + case 0x220: + pci_data_write(pcic->bus, pcic->par, val, 4); + break; + } +} + +static uint32_t sh_pci_reg_read (void *p, target_phys_addr_t addr) +{ + SHPCIState *pcic = p; + 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(pcic->bus, pcic->par, 4); + } + return 0; +} + +typedef struct { + CPUReadMemoryFunc * const r[3]; + CPUWriteMemoryFunc * const w[3]; +} MemOp; + +static MemOp sh_pci_reg = { + { NULL, NULL, sh_pci_reg_read }, + { NULL, NULL, sh_pci_reg_write }, +}; + +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 void sh_pci_map(SysBusDevice *dev, target_phys_addr_t base) +{ + SHPCIState *s = FROM_SYSBUS(SHPCIState, dev); + + cpu_register_physical_memory(P4ADDR(base), 0x224, s->memconfig); + cpu_register_physical_memory(A7ADDR(base), 0x224, s->memconfig); + + s->iobr = 0xfe240000; + isa_mmio_init(s->iobr, 0x40000); +} + +static int sh_pci_init_device(SysBusDevice *dev) +{ + SHPCIState *s; + int i; + + s = FROM_SYSBUS(SHPCIState, dev); + for (i = 0; i < 4; i++) { + sysbus_init_irq(dev, &s->irq[i]); + } + s->bus = pci_register_bus(&s->busdev.qdev, "pci", + sh_pci_set_irq, sh_pci_map_irq, + s->irq, PCI_DEVFN(0, 0), 4); + s->memconfig = cpu_register_io_memory(sh_pci_reg.r, sh_pci_reg.w, + s, DEVICE_NATIVE_ENDIAN); + sysbus_init_mmio_cb(dev, 0x224, sh_pci_map); + s->dev = pci_create_simple(s->bus, PCI_DEVFN(0, 0), "sh_pci_host"); + return 0; +} + +static int sh_pci_host_init(PCIDevice *d) +{ + pci_config_set_vendor_id(d->config, PCI_VENDOR_ID_HITACHI); + pci_config_set_device_id(d->config, PCI_DEVICE_ID_HITACHI_SH7751R); + 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 PCIDeviceInfo sh_pci_host_info = { + .qdev.name = "sh_pci_host", + .qdev.size = sizeof(PCIDevice), + .init = sh_pci_host_init, +}; + +static void sh_pci_register_devices(void) +{ + sysbus_register_dev("sh_pci", sizeof(SHPCIState), + sh_pci_init_device); + pci_qdev_register(&sh_pci_host_info); +} + +device_init(sh_pci_register_devices) diff --git a/hw/sh_serial.c b/hw/sh_serial.c new file mode 100644 index 0000000..191f4a6 --- /dev/null +++ b/hw/sh_serial.c @@ -0,0 +1,401 @@ +/* + * 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.h" +#include "sh.h" +#include "qemu-char.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 { + 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, uint32_t offs, uint32_t val) +{ + 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_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%02x\n", offs); + abort(); +} + +static uint32_t sh_serial_read(void *opaque, uint32_t offs) +{ + 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; +#if 0 + case 0x18: + ret = s->fcr; + break; +#endif + 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%02x\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 CPUReadMemoryFunc * const sh_serial_readfn[] = { + &sh_serial_read, + &sh_serial_read, + &sh_serial_read, +}; + +static CPUWriteMemoryFunc * const sh_serial_writefn[] = { + &sh_serial_write, + &sh_serial_write, + &sh_serial_write, +}; + +void sh_serial_init (target_phys_addr_t 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; + int s_io_memory; + + s = qemu_mallocz(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); + + s_io_memory = cpu_register_io_memory(sh_serial_readfn, + sh_serial_writefn, s, + DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(P4ADDR(base), 0x28, s_io_memory); + cpu_register_physical_memory(A7ADDR(base), 0x28, s_io_memory); + + s->chr = chr; + + if (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/sh_timer.c b/hw/sh_timer.c new file mode 100644 index 0000000..5eec6b7 --- /dev/null +++ b/hw/sh_timer.c @@ -0,0 +1,327 @@ +/* + * SuperH Timer modules. + * + * Copyright (c) 2007 Magnus Damm + * Based on arm_timer.c by Paul Brook + * Copyright (c) 2005-2006 CodeSourcery. + * + * This code is licenced under the GPL. + */ + +#include "hw.h" +#include "sh.h" +#include "qemu-timer.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, target_phys_addr_t 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, target_phys_addr_t 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 *)qemu_mallocz(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 { + void *timer[3]; + int level[3]; + uint32_t tocr; + uint32_t tstr; + int feat; +} tmu012_state; + +static uint32_t tmu012_read(void *opaque, target_phys_addr_t offset) +{ + 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, target_phys_addr_t offset, + uint32_t value) +{ + 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 CPUReadMemoryFunc * const tmu012_readfn[] = { + tmu012_read, + tmu012_read, + tmu012_read +}; + +static CPUWriteMemoryFunc * const tmu012_writefn[] = { + tmu012_write, + tmu012_write, + tmu012_write +}; + +void tmu012_init(target_phys_addr_t base, int feat, uint32_t freq, + qemu_irq ch0_irq, qemu_irq ch1_irq, + qemu_irq ch2_irq0, qemu_irq ch2_irq1) +{ + int iomemtype; + tmu012_state *s; + int timer_feat = (feat & TMU012_FEAT_EXTCLK) ? TIMER_FEAT_EXTCLK : 0; + + s = (tmu012_state *)qemu_mallocz(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 */ + iomemtype = cpu_register_io_memory(tmu012_readfn, + tmu012_writefn, s, + DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(P4ADDR(base), 0x00001000, iomemtype); + cpu_register_physical_memory(A7ADDR(base), 0x00001000, iomemtype); + /* ??? Save/restore. */ +} diff --git a/hw/sharpsl.h b/hw/sharpsl.h new file mode 100644 index 0000000..0b3a774 --- /dev/null +++ b/hw/sharpsl.h @@ -0,0 +1,17 @@ +/* + * Common declarations for the Zaurii. + * + * This file is licensed under the GNU GPL. + */ +#ifndef QEMU_SHARPSL_H +#define QEMU_SHARPSL_H + +#define zaurus_printf(format, ...) \ + fprintf(stderr, "%s: " format, __FUNCTION__, ##__VA_ARGS__) + +/* zaurus.c */ + +#define SL_PXA_PARAM_BASE 0xa0000a00 +void sl_bootparam_write(target_phys_addr_t ptr); + +#endif diff --git a/hw/shix.c b/hw/shix.c new file mode 100644 index 0000000..638bf16 --- /dev/null +++ b/hw/shix.c @@ -0,0 +1,104 @@ +/* + * 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.h" +#include "pc.h" +#include "sh.h" +#include "sysemu.h" +#include "boards.h" +#include "loader.h" + +#define BIOS_FILENAME "shix_bios.bin" +#define BIOS_ADDRESS 0xA0000000 + +void irq_info(Monitor *mon) +{ + /* XXXXX */ +} + +void pic_info(Monitor *mon) +{ + /* XXXXX */ +} + +static void shix_init(ram_addr_t ram_size, + const char *boot_device, + const char *kernel_filename, const char *kernel_cmdline, + const char *initrd_filename, const char *cpu_model) +{ + int ret; + CPUState *env; + struct SH7750State *s; + + if (!cpu_model) + cpu_model = "any"; + + printf("Initializing CPU\n"); + env = cpu_init(cpu_model); + + /* Allocate memory space */ + printf("Allocating ROM\n"); + cpu_register_physical_memory(0x00000000, 0x00004000, IO_MEM_ROM); + printf("Allocating SDRAM 1\n"); + cpu_register_physical_memory(0x08000000, 0x01000000, 0x00004000); + printf("Allocating SDRAM 2\n"); + cpu_register_physical_memory(0x0c000000, 0x01000000, 0x01004000); + + /* Load BIOS in 0 (and access it through P2, 0xA0000000) */ + if (bios_name == NULL) + bios_name = BIOS_FILENAME; + printf("%s: load BIOS '%s'\n", __func__, bios_name); + ret = load_image_targphys(bios_name, 0, 0x4000); + if (ret < 0) { /* Check bios size */ + fprintf(stderr, "ret=%d\n", ret); + fprintf(stderr, "qemu: could not load SHIX bios '%s'\n", + bios_name); + exit(1); + } + + /* Register peripherals */ + s = sh7750_init(env); + /* XXXXX Check success */ + tc58128_init(s, "shix_linux_nand.bin", NULL); + fprintf(stderr, "initialization terminated\n"); +} + +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/slavio_intctl.c b/hw/slavio_intctl.c new file mode 100644 index 0000000..a83e5b8 --- /dev/null +++ b/hw/slavio_intctl.c @@ -0,0 +1,463 @@ +/* + * 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 "sun4m.h" +#include "monitor.h" +#include "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 { + uint32_t intreg_pending; + struct SLAVIO_INTCTLState *master; + uint32_t cpu; + uint32_t irl_out; +} SLAVIO_CPUINTCTLState; + +typedef struct SLAVIO_INTCTLState { + SysBusDevice busdev; + uint32_t intregm_pending; + uint32_t intregm_disabled; + uint32_t target_cpu; +#ifdef DEBUG_IRQ_COUNT + uint64_t irq_count[32]; +#endif + qemu_irq cpu_irqs[MAX_CPUS][MAX_PILS]; + SLAVIO_CPUINTCTLState slaves[MAX_CPUS]; +} 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 uint32_t slavio_intctl_mem_readl(void *opaque, target_phys_addr_t addr) +{ + 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, target_phys_addr_t addr, + uint32_t val) +{ + 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 CPUReadMemoryFunc * const slavio_intctl_mem_read[3] = { + NULL, + NULL, + slavio_intctl_mem_readl, +}; + +static CPUWriteMemoryFunc * const slavio_intctl_mem_write[3] = { + NULL, + NULL, + slavio_intctl_mem_writel, +}; + +// master system interrupt controller +static uint32_t slavio_intctlm_mem_readl(void *opaque, target_phys_addr_t addr) +{ + 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, target_phys_addr_t addr, + uint32_t val) +{ + 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 CPUReadMemoryFunc * const slavio_intctlm_mem_read[3] = { + NULL, + NULL, + slavio_intctlm_mem_readl, +}; + +static CPUWriteMemoryFunc * const slavio_intctlm_mem_write[3] = { + NULL, + NULL, + slavio_intctlm_mem_writel, +}; + +void slavio_pic_info(Monitor *mon, DeviceState *dev) +{ + SysBusDevice *sd; + SLAVIO_INTCTLState *s; + int i; + + sd = sysbus_from_qdev(dev); + s = FROM_SYSBUS(SLAVIO_INTCTLState, sd); + 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 + SysBusDevice *sd; + SLAVIO_INTCTLState *s; + int i; + int64_t count; + + sd = sysbus_from_qdev(dev); + s = FROM_SYSBUS(SLAVIO_INTCTLState, sd); + 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 & (1 << 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, + .minimum_version_id_old = 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, + .minimum_version_id_old = 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 = container_of(d, SLAVIO_INTCTLState, busdev.qdev); + 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 *dev) +{ + SLAVIO_INTCTLState *s = FROM_SYSBUS(SLAVIO_INTCTLState, dev); + int io_memory; + unsigned int i, j; + + qdev_init_gpio_in(&dev->qdev, slavio_set_irq_all, 32 + MAX_CPUS); + io_memory = cpu_register_io_memory(slavio_intctlm_mem_read, + slavio_intctlm_mem_write, s, + DEVICE_NATIVE_ENDIAN); + sysbus_init_mmio(dev, INTCTLM_SIZE, io_memory); + + for (i = 0; i < MAX_CPUS; i++) { + for (j = 0; j < MAX_PILS; j++) { + sysbus_init_irq(dev, &s->cpu_irqs[i][j]); + } + io_memory = cpu_register_io_memory(slavio_intctl_mem_read, + slavio_intctl_mem_write, + &s->slaves[i], + DEVICE_NATIVE_ENDIAN); + sysbus_init_mmio(dev, INTCTL_SIZE, io_memory); + s->slaves[i].cpu = i; + s->slaves[i].master = s; + } + + return 0; +} + +static SysBusDeviceInfo slavio_intctl_info = { + .init = slavio_intctl_init1, + .qdev.name = "slavio_intctl", + .qdev.size = sizeof(SLAVIO_INTCTLState), + .qdev.vmsd = &vmstate_intctl, + .qdev.reset = slavio_intctl_reset, +}; + +static void slavio_intctl_register_devices(void) +{ + sysbus_register_withprop(&slavio_intctl_info); +} + +device_init(slavio_intctl_register_devices) diff --git a/hw/slavio_misc.c b/hw/slavio_misc.c new file mode 100644 index 0000000..198360d --- /dev/null +++ b/hw/slavio_misc.c @@ -0,0 +1,499 @@ +/* + * 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.h" +#include "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. + */ + +typedef struct MiscState { + SysBusDevice busdev; + qemu_irq irq; + uint32_t dummy; + uint8_t config; + uint8_t aux1, aux2; + uint8_t diag, mctrl; + uint8_t sysctrl; + uint16_t leds; + qemu_irq fdc_tc; +} MiscState; + +typedef struct APCState { + SysBusDevice busdev; + qemu_irq cpu_halt; +} APCState; + +#define MISC_SIZE 1 +#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 = container_of(d, MiscState, busdev.qdev); + + // 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, target_phys_addr_t addr, + uint32_t val) +{ + MiscState *s = opaque; + + trace_slavio_cfg_mem_writeb(val & 0xff); + s->config = val & 0xff; + slavio_misc_update_irq(s); +} + +static uint32_t slavio_cfg_mem_readb(void *opaque, target_phys_addr_t addr) +{ + MiscState *s = opaque; + uint32_t ret = 0; + + ret = s->config; + trace_slavio_cfg_mem_readb(ret); + return ret; +} + +static CPUReadMemoryFunc * const slavio_cfg_mem_read[3] = { + slavio_cfg_mem_readb, + NULL, + NULL, +}; + +static CPUWriteMemoryFunc * const slavio_cfg_mem_write[3] = { + slavio_cfg_mem_writeb, + NULL, + NULL, +}; + +static void slavio_diag_mem_writeb(void *opaque, target_phys_addr_t addr, + uint32_t val) +{ + MiscState *s = opaque; + + trace_slavio_diag_mem_writeb(val & 0xff); + s->diag = val & 0xff; +} + +static uint32_t slavio_diag_mem_readb(void *opaque, target_phys_addr_t addr) +{ + MiscState *s = opaque; + uint32_t ret = 0; + + ret = s->diag; + trace_slavio_diag_mem_readb(ret); + return ret; +} + +static CPUReadMemoryFunc * const slavio_diag_mem_read[3] = { + slavio_diag_mem_readb, + NULL, + NULL, +}; + +static CPUWriteMemoryFunc * const slavio_diag_mem_write[3] = { + slavio_diag_mem_writeb, + NULL, + NULL, +}; + +static void slavio_mdm_mem_writeb(void *opaque, target_phys_addr_t addr, + uint32_t val) +{ + MiscState *s = opaque; + + trace_slavio_mdm_mem_writeb(val & 0xff); + s->mctrl = val & 0xff; +} + +static uint32_t slavio_mdm_mem_readb(void *opaque, target_phys_addr_t addr) +{ + MiscState *s = opaque; + uint32_t ret = 0; + + ret = s->mctrl; + trace_slavio_mdm_mem_readb(ret); + return ret; +} + +static CPUReadMemoryFunc * const slavio_mdm_mem_read[3] = { + slavio_mdm_mem_readb, + NULL, + NULL, +}; + +static CPUWriteMemoryFunc * const slavio_mdm_mem_write[3] = { + slavio_mdm_mem_writeb, + NULL, + NULL, +}; + +static void slavio_aux1_mem_writeb(void *opaque, target_phys_addr_t addr, + uint32_t val) +{ + 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 uint32_t slavio_aux1_mem_readb(void *opaque, target_phys_addr_t addr) +{ + MiscState *s = opaque; + uint32_t ret = 0; + + ret = s->aux1; + trace_slavio_aux1_mem_readb(ret); + return ret; +} + +static CPUReadMemoryFunc * const slavio_aux1_mem_read[3] = { + slavio_aux1_mem_readb, + NULL, + NULL, +}; + +static CPUWriteMemoryFunc * const slavio_aux1_mem_write[3] = { + slavio_aux1_mem_writeb, + NULL, + NULL, +}; + +static void slavio_aux2_mem_writeb(void *opaque, target_phys_addr_t addr, + uint32_t val) +{ + 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 uint32_t slavio_aux2_mem_readb(void *opaque, target_phys_addr_t addr) +{ + MiscState *s = opaque; + uint32_t ret = 0; + + ret = s->aux2; + trace_slavio_aux2_mem_readb(ret); + return ret; +} + +static CPUReadMemoryFunc * const slavio_aux2_mem_read[3] = { + slavio_aux2_mem_readb, + NULL, + NULL, +}; + +static CPUWriteMemoryFunc * const slavio_aux2_mem_write[3] = { + slavio_aux2_mem_writeb, + NULL, + NULL, +}; + +static void apc_mem_writeb(void *opaque, target_phys_addr_t addr, uint32_t val) +{ + APCState *s = opaque; + + trace_apc_mem_writeb(val & 0xff); + qemu_irq_raise(s->cpu_halt); +} + +static uint32_t apc_mem_readb(void *opaque, target_phys_addr_t addr) +{ + uint32_t ret = 0; + + trace_apc_mem_readb(ret); + return ret; +} + +static CPUReadMemoryFunc * const apc_mem_read[3] = { + apc_mem_readb, + NULL, + NULL, +}; + +static CPUWriteMemoryFunc * const apc_mem_write[3] = { + apc_mem_writeb, + NULL, + NULL, +}; + +static uint32_t slavio_sysctrl_mem_readl(void *opaque, target_phys_addr_t addr) +{ + 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, target_phys_addr_t addr, + uint32_t val) +{ + 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 CPUReadMemoryFunc * const slavio_sysctrl_mem_read[3] = { + NULL, + NULL, + slavio_sysctrl_mem_readl, +}; + +static CPUWriteMemoryFunc * const slavio_sysctrl_mem_write[3] = { + NULL, + NULL, + slavio_sysctrl_mem_writel, +}; + +static uint32_t slavio_led_mem_readw(void *opaque, target_phys_addr_t addr) +{ + 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, target_phys_addr_t addr, + uint32_t val) +{ + MiscState *s = opaque; + + trace_slavio_led_mem_readw(val & 0xffff); + switch (addr) { + case 0: + s->leds = val; + break; + default: + break; + } +} + +static CPUReadMemoryFunc * const slavio_led_mem_read[3] = { + NULL, + slavio_led_mem_readw, + NULL, +}; + +static CPUWriteMemoryFunc * const slavio_led_mem_write[3] = { + NULL, + slavio_led_mem_writew, + NULL, +}; + +static const VMStateDescription vmstate_misc = { + .name ="slavio_misc", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 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 = FROM_SYSBUS(APCState, dev); + int io; + + sysbus_init_irq(dev, &s->cpu_halt); + + /* Power management (APC) XXX: not a Slavio device */ + io = cpu_register_io_memory(apc_mem_read, apc_mem_write, s, + DEVICE_NATIVE_ENDIAN); + sysbus_init_mmio(dev, MISC_SIZE, io); + return 0; +} + +static int slavio_misc_init1(SysBusDevice *dev) +{ + MiscState *s = FROM_SYSBUS(MiscState, dev); + int io; + + sysbus_init_irq(dev, &s->irq); + sysbus_init_irq(dev, &s->fdc_tc); + + /* 8 bit registers */ + /* Slavio control */ + io = cpu_register_io_memory(slavio_cfg_mem_read, + slavio_cfg_mem_write, s, + DEVICE_NATIVE_ENDIAN); + sysbus_init_mmio(dev, MISC_SIZE, io); + + /* Diagnostics */ + io = cpu_register_io_memory(slavio_diag_mem_read, + slavio_diag_mem_write, s, + DEVICE_NATIVE_ENDIAN); + sysbus_init_mmio(dev, MISC_SIZE, io); + + /* Modem control */ + io = cpu_register_io_memory(slavio_mdm_mem_read, + slavio_mdm_mem_write, s, + DEVICE_NATIVE_ENDIAN); + sysbus_init_mmio(dev, MISC_SIZE, io); + + /* 16 bit registers */ + /* ss600mp diag LEDs */ + io = cpu_register_io_memory(slavio_led_mem_read, + slavio_led_mem_write, s, + DEVICE_NATIVE_ENDIAN); + sysbus_init_mmio(dev, MISC_SIZE, io); + + /* 32 bit registers */ + /* System control */ + io = cpu_register_io_memory(slavio_sysctrl_mem_read, + slavio_sysctrl_mem_write, s, + DEVICE_NATIVE_ENDIAN); + sysbus_init_mmio(dev, SYSCTRL_SIZE, io); + + /* AUX 1 (Misc System Functions) */ + io = cpu_register_io_memory(slavio_aux1_mem_read, + slavio_aux1_mem_write, s, + DEVICE_NATIVE_ENDIAN); + sysbus_init_mmio(dev, MISC_SIZE, io); + + /* AUX 2 (Software Powerdown Control) */ + io = cpu_register_io_memory(slavio_aux2_mem_read, + slavio_aux2_mem_write, s, + DEVICE_NATIVE_ENDIAN); + sysbus_init_mmio(dev, MISC_SIZE, io); + + qdev_init_gpio_in(&dev->qdev, slavio_set_power_fail, 1); + + return 0; +} + +static SysBusDeviceInfo slavio_misc_info = { + .init = slavio_misc_init1, + .qdev.name = "slavio_misc", + .qdev.size = sizeof(MiscState), + .qdev.vmsd = &vmstate_misc, + .qdev.reset = slavio_misc_reset, +}; + +static SysBusDeviceInfo apc_info = { + .init = apc_init1, + .qdev.name = "apc", + .qdev.size = sizeof(MiscState), +}; + +static void slavio_misc_register_devices(void) +{ + sysbus_register_withprop(&slavio_misc_info); + sysbus_register_withprop(&apc_info); +} + +device_init(slavio_misc_register_devices) diff --git a/hw/slavio_timer.c b/hw/slavio_timer.c new file mode 100644 index 0000000..5511313 --- /dev/null +++ b/hw/slavio_timer.c @@ -0,0 +1,424 @@ +/* + * 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 "sun4m.h" +#include "qemu-timer.h" +#include "sysbus.h" +#include "trace.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; + uint64_t limit; + // processor only + uint32_t running; +} CPUTimerState; + +typedef struct SLAVIO_TIMERState { + SysBusDevice busdev; + uint32_t num_cpus; + CPUTimerState cputimer[MAX_CPUS + 1]; + uint32_t cputimer_mode; +} SLAVIO_TIMERState; + +typedef struct TimerContext { + 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 uint32_t slavio_timer_mem_readl(void *opaque, target_phys_addr_t addr) +{ + 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->running; + } 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, target_phys_addr_t addr, + uint32_t val) +{ + 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) && !t->running) { + trace_slavio_timer_mem_writel_status_start(timer_index); + ptimer_run(t->timer, 0); + t->running = 1; + } else if (!(val & 1) && t->running) { + trace_slavio_timer_mem_writel_status_stop(timer_index); + ptimer_stop(t->timer); + t->running = 0; + } + } + 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 + ptimer_stop(curr_timer->timer); + curr_timer->running = 0; + // 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 + // stop the user timer if it is running + if (curr_timer->running) { + ptimer_stop(curr_timer->timer); + } + // start the counter + ptimer_run(curr_timer->timer, 0); + curr_timer->running = 1; + // 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 CPUReadMemoryFunc * const slavio_timer_mem_read[3] = { + NULL, + NULL, + slavio_timer_mem_readl, +}; + +static CPUWriteMemoryFunc * const slavio_timer_mem_write[3] = { + NULL, + NULL, + slavio_timer_mem_writel, +}; + +static const VMStateDescription vmstate_timer = { + .name ="timer", + .version_id = 3, + .minimum_version_id = 3, + .minimum_version_id_old = 3, + .fields = (VMStateField []) { + VMSTATE_UINT64(limit, CPUTimerState), + VMSTATE_UINT32(count, CPUTimerState), + VMSTATE_UINT32(counthigh, CPUTimerState), + VMSTATE_UINT32(reached, CPUTimerState), + VMSTATE_UINT32(running, 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, + .minimum_version_id_old = 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 = container_of(d, SLAVIO_TIMERState, busdev.qdev); + 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->running = 1; + } + } + s->cputimer_mode = 0; +} + +static int slavio_timer_init1(SysBusDevice *dev) +{ + int io; + SLAVIO_TIMERState *s = FROM_SYSBUS(SLAVIO_TIMERState, dev); + QEMUBH *bh; + unsigned int i; + TimerContext *tc; + + for (i = 0; i <= MAX_CPUS; i++) { + tc = qemu_mallocz(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); + + io = cpu_register_io_memory(slavio_timer_mem_read, + slavio_timer_mem_write, tc, + DEVICE_NATIVE_ENDIAN); + if (i == 0) { + sysbus_init_mmio(dev, SYS_TIMER_SIZE, io); + } else { + sysbus_init_mmio(dev, CPU_TIMER_SIZE, io); + } + + sysbus_init_irq(dev, &s->cputimer[i].irq); + } + + return 0; +} + +static SysBusDeviceInfo slavio_timer_info = { + .init = slavio_timer_init1, + .qdev.name = "slavio_timer", + .qdev.size = sizeof(SLAVIO_TIMERState), + .qdev.vmsd = &vmstate_slavio_timer, + .qdev.reset = slavio_timer_reset, + .qdev.props = (Property[]) { + DEFINE_PROP_UINT32("num_cpus", SLAVIO_TIMERState, num_cpus, 0), + DEFINE_PROP_END_OF_LIST(), + } +}; + +static void slavio_timer_register_devices(void) +{ + sysbus_register_withprop(&slavio_timer_info); +} + +device_init(slavio_timer_register_devices) diff --git a/hw/sm501.c b/hw/sm501.c new file mode 100644 index 0000000..0f0bf96 --- /dev/null +++ b/hw/sm501.c @@ -0,0 +1,1457 @@ +/* + * 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 +#include "hw.h" +#include "pc.h" +#include "console.h" +#include "devices.h" +#include "sysbus.h" +#include "qdev-addr.h" +#include "range.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 */ + DisplayState *ds; + + /* status & internal resources */ + target_phys_addr_t base; + uint32_t local_mem_size_index; + uint8_t * local_mem; + ram_addr_t local_mem_offset; + 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) +{ + uint16_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 uint32_t sm501_system_config_read(void *opaque, target_phys_addr_t addr) +{ + 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, + target_phys_addr_t addr, uint32_t value) +{ + SM501State * s = (SM501State *)opaque; + SM501_DPRINTF("sm501 system config regs : write addr=%x, val=%x\n", + addr, 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, value); + abort(); + } +} + +static CPUReadMemoryFunc * const sm501_system_config_readfn[] = { + NULL, + NULL, + &sm501_system_config_read, +}; + +static CPUWriteMemoryFunc * const sm501_system_config_writefn[] = { + NULL, + NULL, + &sm501_system_config_write, +}; + +static uint32_t sm501_palette_read(void *opaque, target_phys_addr_t 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, + target_phys_addr_t 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 uint32_t sm501_disp_ctrl_read(void *opaque, target_phys_addr_t addr) +{ + 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, + target_phys_addr_t addr, + uint32_t value) +{ + SM501State * s = (SM501State *)opaque; + SM501_DPRINTF("sm501 disp ctrl regs : write addr=%x, val=%x\n", + addr, 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, value); + abort(); + } +} + +static CPUReadMemoryFunc * const sm501_disp_ctrl_readfn[] = { + NULL, + NULL, + &sm501_disp_ctrl_read, +}; + +static CPUWriteMemoryFunc * const sm501_disp_ctrl_writefn[] = { + NULL, + NULL, + &sm501_disp_ctrl_write, +}; + +static uint32_t sm501_2d_engine_read(void *opaque, target_phys_addr_t addr) +{ + 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, + target_phys_addr_t addr, uint32_t value) +{ + SM501State * s = (SM501State *)opaque; + SM501_DPRINTF("sm501 2d engine regs : write addr=%x, val=%x\n", + addr, 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, value); + abort(); + } +} + +static CPUReadMemoryFunc * const sm501_2d_engine_readfn[] = { + NULL, + NULL, + &sm501_2d_engine_read, +}; + +static CPUWriteMemoryFunc * const sm501_2d_engine_writefn[] = { + NULL, + NULL, + &sm501_2d_engine_write, +}; + +/* draw line functions for all console modes */ + +#include "pixel_ops.h" + +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(DisplayState *s) +{ + switch(ds_get_bits_per_pixel(s)) { + default: + case 8: + return 0; + case 15: + return 1; + case 16: + return 2; + case 32: + if (is_surface_bgr(s->surface)) + return 4; + else + return 3; + } +} + +static void sm501_draw_crt(SM501State * s) +{ + 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 = ds_get_bytes_per_pixel(s->ds) + (ds_get_bits_per_pixel(s->ds) % 8 ? 1 : 0); + 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(s->ds); + 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 = s->local_mem_offset; + + /* 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->ds, width, height); + s->last_width = width; + s->last_height = height; + full_update = 1; + } + + /* draw each line according to conditions */ + 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 & TARGET_PAGE_MASK; + ram_addr_t page1 = (offset + width * src_bpp - 1) & TARGET_PAGE_MASK; + ram_addr_t page; + + /* check dirty flags for each line */ + for (page = page0; page <= page1; page += TARGET_PAGE_SIZE) + if (cpu_physical_memory_get_dirty(page, VGA_DIRTY_FLAG)) + update = 1; + + /* draw line and change status */ + if (update) { + uint8_t * d = &(ds_get_data(s->ds)[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_update(s->ds, 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_update(s->ds, 0, y_start, width, y - y_start); + + /* clear dirty flags */ + if (page_min != ~0l) { + cpu_physical_memory_reset_dirty(page_min, page_max + TARGET_PAGE_SIZE, + VGA_DIRTY_FLAG); + } +} + +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); +} + +void sm501_init(uint32_t base, uint32_t local_mem_bytes, qemu_irq irq, + CharDriverState *chr) +{ + SM501State * s; + DeviceState *dev; + int sm501_system_config_index; + int sm501_disp_ctrl_index; + int sm501_2d_engine_index; + + /* allocate management data region */ + s = (SM501State *)qemu_mallocz(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 */ + s->local_mem_offset = qemu_ram_alloc(NULL, "sm501.local", local_mem_bytes); + s->local_mem = qemu_get_ram_ptr(s->local_mem_offset); + cpu_register_physical_memory(base, local_mem_bytes, s->local_mem_offset); + + /* map mmio */ + sm501_system_config_index + = cpu_register_io_memory(sm501_system_config_readfn, + sm501_system_config_writefn, s, + DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(base + MMIO_BASE_OFFSET, + 0x6c, sm501_system_config_index); + sm501_disp_ctrl_index = cpu_register_io_memory(sm501_disp_ctrl_readfn, + sm501_disp_ctrl_writefn, s, + DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(base + MMIO_BASE_OFFSET + SM501_DC, + 0x1000, sm501_disp_ctrl_index); + sm501_2d_engine_index = cpu_register_io_memory(sm501_2d_engine_readfn, + sm501_2d_engine_writefn, s, + DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(base + MMIO_BASE_OFFSET + SM501_2D_ENGINE, + 0x54, sm501_2d_engine_index); + + /* bridge to usb host emulation module */ + dev = qdev_create(NULL, "sysbus-ohci"); + qdev_prop_set_uint32(dev, "num-ports", 2); + qdev_prop_set_taddr(dev, "dma-offset", base); + qdev_init_nofail(dev); + sysbus_mmio_map(sysbus_from_qdev(dev), 0, + base + MMIO_BASE_OFFSET + SM501_USB_HOST); + sysbus_connect_irq(sysbus_from_qdev(dev), 0, irq); + + /* bridge to serial emulation module */ + if (chr) { +#ifdef TARGET_WORDS_BIGENDIAN + serial_mm_init(base + MMIO_BASE_OFFSET + SM501_UART0, 2, + NULL, /* TODO : chain irq to IRL */ + 115200, chr, 1, 1); +#else + serial_mm_init(base + MMIO_BASE_OFFSET + SM501_UART0, 2, + NULL, /* TODO : chain irq to IRL */ + 115200, chr, 1, 0); +#endif + } + + /* create qemu graphic console */ + s->ds = graphic_console_init(sm501_update_display, NULL, + NULL, NULL, s); +} diff --git a/hw/sm501_template.h b/hw/sm501_template.h new file mode 100644 index 0000000..d1ceef9 --- /dev/null +++ b/hw/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_raw(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_raw(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_raw(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) { + cpu_physical_memory_rw(cursor_addr, &bitset, 1, 0); + 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/smbios.c b/hw/smbios.c new file mode 100644 index 0000000..a3ae1de --- /dev/null +++ b/hw/smbios.c @@ -0,0 +1,239 @@ +/* + * SMBIOS Support + * + * Copyright (C) 2009 Hewlett-Packard Development Company, L.P. + * + * Authors: + * Alex Williamson + * + * This work is licensed under the terms of the GNU GPL, version 2. See + * the COPYING file in the top-level directory. + * + */ + +#include "sysemu.h" +#include "smbios.h" +#include "loader.h" + +/* + * Structures shared with the BIOS + */ +struct smbios_header { + uint16_t length; + uint8_t type; +} __attribute__((__packed__)); + +struct smbios_field { + struct smbios_header header; + uint8_t type; + uint16_t offset; + uint8_t data[]; +} __attribute__((__packed__)); + +struct smbios_table { + struct smbios_header header; + uint8_t data[]; +} __attribute__((__packed__)); + +#define SMBIOS_FIELD_ENTRY 0 +#define SMBIOS_TABLE_ENTRY 1 + + +static uint8_t *smbios_entries; +static size_t smbios_entries_len; +static int smbios_type4_count = 0; + +static void smbios_validate_table(void) +{ + if (smbios_type4_count && smbios_type4_count != smp_cpus) { + fprintf(stderr, + "Number of SMBIOS Type 4 tables must match cpu count.\n"); + exit(1); + } +} + +uint8_t *smbios_get_table(size_t *length) +{ + smbios_validate_table(); + *length = smbios_entries_len; + return smbios_entries; +} + +/* + * To avoid unresolvable overlaps in data, don't allow both + * tables and fields for the same smbios type. + */ +static void smbios_check_collision(int type, int entry) +{ + uint16_t *num_entries = (uint16_t *)smbios_entries; + struct smbios_header *header; + char *p; + int i; + + if (!num_entries) + return; + + p = (char *)(num_entries + 1); + + for (i = 0; i < *num_entries; i++) { + header = (struct smbios_header *)p; + if (entry == SMBIOS_TABLE_ENTRY && header->type == SMBIOS_FIELD_ENTRY) { + struct smbios_field *field = (void *)header; + if (type == field->type) { + fprintf(stderr, "SMBIOS type %d field already defined, " + "cannot add table\n", type); + exit(1); + } + } else if (entry == SMBIOS_FIELD_ENTRY && + header->type == SMBIOS_TABLE_ENTRY) { + struct smbios_structure_header *table = (void *)(header + 1); + if (type == table->type) { + fprintf(stderr, "SMBIOS type %d table already defined, " + "cannot add field\n", type); + exit(1); + } + } + p += le16_to_cpu(header->length); + } +} + +void smbios_add_field(int type, int offset, int len, void *data) +{ + struct smbios_field *field; + + smbios_check_collision(type, SMBIOS_FIELD_ENTRY); + + if (!smbios_entries) { + smbios_entries_len = sizeof(uint16_t); + smbios_entries = qemu_mallocz(smbios_entries_len); + } + smbios_entries = qemu_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_build_type_0_fields(const char *t) +{ + char buf[1024]; + + if (get_param_value(buf, sizeof(buf), "vendor", t)) + smbios_add_field(0, offsetof(struct smbios_type_0, vendor_str), + strlen(buf) + 1, buf); + if (get_param_value(buf, sizeof(buf), "version", t)) + smbios_add_field(0, offsetof(struct smbios_type_0, bios_version_str), + strlen(buf) + 1, buf); + if (get_param_value(buf, sizeof(buf), "date", t)) + smbios_add_field(0, offsetof(struct smbios_type_0, + bios_release_date_str), + strlen(buf) + 1, buf); + if (get_param_value(buf, sizeof(buf), "release", t)) { + int major, minor; + sscanf(buf, "%d.%d", &major, &minor); + smbios_add_field(0, offsetof(struct smbios_type_0, + system_bios_major_release), 1, &major); + smbios_add_field(0, offsetof(struct smbios_type_0, + system_bios_minor_release), 1, &minor); + } +} + +static void smbios_build_type_1_fields(const char *t) +{ + char buf[1024]; + + if (get_param_value(buf, sizeof(buf), "manufacturer", t)) + smbios_add_field(1, offsetof(struct smbios_type_1, manufacturer_str), + strlen(buf) + 1, buf); + if (get_param_value(buf, sizeof(buf), "product", t)) + smbios_add_field(1, offsetof(struct smbios_type_1, product_name_str), + strlen(buf) + 1, buf); + if (get_param_value(buf, sizeof(buf), "version", t)) + smbios_add_field(1, offsetof(struct smbios_type_1, version_str), + strlen(buf) + 1, buf); + if (get_param_value(buf, sizeof(buf), "serial", t)) + smbios_add_field(1, offsetof(struct smbios_type_1, serial_number_str), + strlen(buf) + 1, buf); + if (get_param_value(buf, sizeof(buf), "uuid", t)) { + if (qemu_uuid_parse(buf, qemu_uuid) != 0) { + fprintf(stderr, "Invalid SMBIOS UUID string\n"); + exit(1); + } + } + if (get_param_value(buf, sizeof(buf), "sku", t)) + smbios_add_field(1, offsetof(struct smbios_type_1, sku_number_str), + strlen(buf) + 1, buf); + if (get_param_value(buf, sizeof(buf), "family", t)) + smbios_add_field(1, offsetof(struct smbios_type_1, family_str), + strlen(buf) + 1, buf); +} + +int smbios_entry_add(const char *t) +{ + char buf[1024]; + + if (get_param_value(buf, sizeof(buf), "file", t)) { + struct smbios_structure_header *header; + struct smbios_table *table; + int size = get_image_size(buf); + + if (size == -1 || size < sizeof(struct smbios_structure_header)) { + fprintf(stderr, "Cannot read smbios file %s\n", buf); + exit(1); + } + + if (!smbios_entries) { + smbios_entries_len = sizeof(uint16_t); + smbios_entries = qemu_mallocz(smbios_entries_len); + } + + smbios_entries = qemu_realloc(smbios_entries, smbios_entries_len + + sizeof(*table) + size); + table = (struct smbios_table *)(smbios_entries + smbios_entries_len); + table->header.type = SMBIOS_TABLE_ENTRY; + table->header.length = cpu_to_le16(sizeof(*table) + size); + + if (load_image(buf, table->data) != size) { + fprintf(stderr, "Failed to load smbios file %s", buf); + exit(1); + } + + header = (struct smbios_structure_header *)(table->data); + smbios_check_collision(header->type, SMBIOS_TABLE_ENTRY); + if (header->type == 4) { + smbios_type4_count++; + } + + smbios_entries_len += sizeof(*table) + size; + (*(uint16_t *)smbios_entries) = + cpu_to_le16(le16_to_cpu(*(uint16_t *)smbios_entries) + 1); + return 0; + } + + if (get_param_value(buf, sizeof(buf), "type", t)) { + unsigned long type = strtoul(buf, NULL, 0); + switch (type) { + case 0: + smbios_build_type_0_fields(t); + return 0; + case 1: + smbios_build_type_1_fields(t); + return 0; + default: + fprintf(stderr, "Don't know how to build fields for SMBIOS type " + "%ld\n", type); + exit(1); + } + } + + fprintf(stderr, "smbios: must specify type= or file=\n"); + return -1; +} diff --git a/hw/smbios.h b/hw/smbios.h new file mode 100644 index 0000000..3a5169d --- /dev/null +++ b/hw/smbios.h @@ -0,0 +1,162 @@ +#ifndef QEMU_SMBIOS_H +#define QEMU_SMBIOS_H +/* + * SMBIOS Support + * + * Copyright (C) 2009 Hewlett-Packard Development Company, L.P. + * + * Authors: + * Alex Williamson + * + * This work is licensed under the terms of the GNU GPL, version 2. See + * the COPYING file in the top-level directory. + * + */ + +int smbios_entry_add(const char *t); +void smbios_add_field(int type, int offset, int len, void *data); +uint8_t *smbios_get_table(size_t *length); + +/* + * SMBIOS spec defined tables + */ + +/* This goes at the beginning of every SMBIOS structure. */ +struct smbios_structure_header { + uint8_t type; + uint8_t length; + uint16_t handle; +} __attribute__((__packed__)); + +/* SMBIOS type 0 - BIOS Information */ +struct smbios_type_0 { + struct smbios_structure_header header; + uint8_t vendor_str; + uint8_t bios_version_str; + uint16_t bios_starting_address_segment; + uint8_t bios_release_date_str; + uint8_t bios_rom_size; + uint8_t bios_characteristics[8]; + uint8_t bios_characteristics_extension_bytes[2]; + uint8_t system_bios_major_release; + uint8_t system_bios_minor_release; + uint8_t embedded_controller_major_release; + uint8_t embedded_controller_minor_release; +} __attribute__((__packed__)); + +/* SMBIOS type 1 - System Information */ +struct smbios_type_1 { + struct smbios_structure_header header; + uint8_t manufacturer_str; + uint8_t product_name_str; + uint8_t version_str; + uint8_t serial_number_str; + uint8_t uuid[16]; + uint8_t wake_up_type; + uint8_t sku_number_str; + uint8_t family_str; +} __attribute__((__packed__)); + +/* SMBIOS type 3 - System Enclosure (v2.3) */ +struct smbios_type_3 { + struct smbios_structure_header header; + uint8_t manufacturer_str; + uint8_t type; + uint8_t version_str; + uint8_t serial_number_str; + uint8_t asset_tag_number_str; + uint8_t boot_up_state; + uint8_t power_supply_state; + uint8_t thermal_state; + uint8_t security_status; + uint32_t oem_defined; + uint8_t height; + uint8_t number_of_power_cords; + uint8_t contained_element_count; + // contained elements follow +} __attribute__((__packed__)); + +/* SMBIOS type 4 - Processor Information (v2.0) */ +struct smbios_type_4 { + struct smbios_structure_header header; + uint8_t socket_designation_str; + uint8_t processor_type; + uint8_t processor_family; + uint8_t processor_manufacturer_str; + uint32_t processor_id[2]; + uint8_t processor_version_str; + uint8_t voltage; + uint16_t external_clock; + uint16_t max_speed; + uint16_t current_speed; + uint8_t status; + uint8_t processor_upgrade; + uint16_t l1_cache_handle; + uint16_t l2_cache_handle; + uint16_t l3_cache_handle; +} __attribute__((__packed__)); + +/* SMBIOS type 16 - Physical Memory Array + * Associated with one type 17 (Memory Device). + */ +struct smbios_type_16 { + struct smbios_structure_header header; + uint8_t location; + uint8_t use; + uint8_t error_correction; + uint32_t maximum_capacity; + uint16_t memory_error_information_handle; + uint16_t number_of_memory_devices; +} __attribute__((__packed__)); +/* SMBIOS type 17 - Memory Device + * Associated with one type 19 + */ +struct smbios_type_17 { + struct smbios_structure_header header; + uint16_t physical_memory_array_handle; + uint16_t memory_error_information_handle; + uint16_t total_width; + uint16_t data_width; + uint16_t size; + uint8_t form_factor; + uint8_t device_set; + uint8_t device_locator_str; + uint8_t bank_locator_str; + uint8_t memory_type; + uint16_t type_detail; +} __attribute__((__packed__)); + +/* SMBIOS type 19 - Memory Array Mapped Address */ +struct smbios_type_19 { + struct smbios_structure_header header; + uint32_t starting_address; + uint32_t ending_address; + uint16_t memory_array_handle; + uint8_t partition_width; +} __attribute__((__packed__)); + +/* SMBIOS type 20 - Memory Device Mapped Address */ +struct smbios_type_20 { + struct smbios_structure_header header; + uint32_t starting_address; + uint32_t ending_address; + uint16_t memory_device_handle; + uint16_t memory_array_mapped_address_handle; + uint8_t partition_row_position; + uint8_t interleave_position; + uint8_t interleaved_data_depth; +} __attribute__((__packed__)); + +/* SMBIOS type 32 - System Boot Information */ +struct smbios_type_32 { + struct smbios_structure_header header; + uint8_t reserved[6]; + uint8_t boot_status; +} __attribute__((__packed__)); + +/* SMBIOS type 127 -- End-of-table */ +struct smbios_type_127 { + struct smbios_structure_header header; +} __attribute__((__packed__)); + +#endif /*QEMU_SMBIOS_H */ diff --git a/hw/smbus.c b/hw/smbus.c new file mode 100644 index 0000000..e464539 --- /dev/null +++ b/hw/smbus.c @@ -0,0 +1,318 @@ +/* + * QEMU SMBus device emulation. + * + * Copyright (c) 2007 CodeSourcery. + * Written by Paul Brook + * + * This code is licenced under the LGPL. + */ + +/* TODO: Implement PEC. */ + +#include "hw.h" +#include "i2c.h" +#include "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) +{ + SMBusDeviceInfo *t = container_of(dev->i2c.info, SMBusDeviceInfo, i2c); + + DPRINTF("Quick Command %d\n", recv); + if (t->quick_cmd) + t->quick_cmd(dev, recv); +} + +static void smbus_do_write(SMBusDevice *dev) +{ + SMBusDeviceInfo *t = container_of(dev->i2c.info, SMBusDeviceInfo, i2c); + + if (dev->data_len == 0) { + smbus_do_quick_cmd(dev, 0); + } else if (dev->data_len == 1) { + DPRINTF("Send Byte\n"); + if (t->send_byte) { + t->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 (t->write_data) { + t->write_data(dev, dev->command, dev->data_buf + 1, + dev->data_len - 1); + } + } +} + +static void smbus_i2c_event(i2c_slave *s, enum i2c_event event) +{ + SMBusDevice *dev = FROM_I2C_SLAVE(SMBusDevice, 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(i2c_slave *s) +{ + SMBusDeviceInfo *t = container_of(s->info, SMBusDeviceInfo, i2c); + SMBusDevice *dev = FROM_I2C_SLAVE(SMBusDevice, s); + int ret; + + switch (dev->mode) { + case SMBUS_RECV_BYTE: + if (t->receive_byte) { + ret = t->receive_byte(dev); + } else { + ret = 0; + } + DPRINTF("Receive Byte %02x\n", ret); + dev->mode = SMBUS_DONE; + break; + case SMBUS_READ_DATA: + if (t->read_data) { + ret = t->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(i2c_slave *s, uint8_t data) +{ + SMBusDevice *dev = FROM_I2C_SLAVE(SMBusDevice, 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(i2c_slave *i2c) +{ + SMBusDeviceInfo *t = container_of(i2c->info, SMBusDeviceInfo, i2c); + SMBusDevice *dev = FROM_I2C_SLAVE(SMBusDevice, i2c); + + return t->init(dev); +} + +void smbus_register_device(SMBusDeviceInfo *info) +{ + assert(info->i2c.qdev.size >= sizeof(SMBusDevice)); + info->i2c.init = smbus_device_init; + info->i2c.event = smbus_i2c_event; + info->i2c.recv = smbus_i2c_recv; + info->i2c.send = smbus_i2c_send; + i2c_register_slave(&info->i2c); +} + +/* Master device commands. */ +void smbus_quick_command(i2c_bus *bus, uint8_t addr, int read) +{ + i2c_start_transfer(bus, addr, read); + i2c_end_transfer(bus); +} + +uint8_t smbus_receive_byte(i2c_bus *bus, uint8_t addr) +{ + uint8_t data; + + i2c_start_transfer(bus, addr, 1); + data = i2c_recv(bus); + i2c_nack(bus); + i2c_end_transfer(bus); + return data; +} + +void smbus_send_byte(i2c_bus *bus, uint8_t addr, uint8_t data) +{ + i2c_start_transfer(bus, addr, 0); + i2c_send(bus, data); + i2c_end_transfer(bus); +} + +uint8_t smbus_read_byte(i2c_bus *bus, uint8_t addr, uint8_t command) +{ + uint8_t data; + i2c_start_transfer(bus, addr, 0); + i2c_send(bus, command); + i2c_start_transfer(bus, addr, 1); + data = i2c_recv(bus); + i2c_nack(bus); + i2c_end_transfer(bus); + return data; +} + +void smbus_write_byte(i2c_bus *bus, uint8_t addr, uint8_t command, uint8_t data) +{ + i2c_start_transfer(bus, addr, 0); + i2c_send(bus, command); + i2c_send(bus, data); + i2c_end_transfer(bus); +} + +uint16_t smbus_read_word(i2c_bus *bus, uint8_t addr, uint8_t command) +{ + uint16_t data; + i2c_start_transfer(bus, addr, 0); + 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; +} + +void smbus_write_word(i2c_bus *bus, uint8_t addr, uint8_t command, uint16_t data) +{ + i2c_start_transfer(bus, addr, 0); + i2c_send(bus, command); + i2c_send(bus, data & 0xff); + i2c_send(bus, data >> 8); + i2c_end_transfer(bus); +} + +int smbus_read_block(i2c_bus *bus, uint8_t addr, uint8_t command, uint8_t *data) +{ + int len; + int i; + + i2c_start_transfer(bus, addr, 0); + 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; +} + +void smbus_write_block(i2c_bus *bus, uint8_t addr, uint8_t command, uint8_t *data, + int len) +{ + int i; + + if (len > 32) + len = 32; + + i2c_start_transfer(bus, addr, 0); + i2c_send(bus, command); + i2c_send(bus, len); + for (i = 0; i < len; i++) + i2c_send(bus, data[i]); + i2c_end_transfer(bus); +} diff --git a/hw/smbus.h b/hw/smbus.h new file mode 100644 index 0000000..571c52d --- /dev/null +++ b/hw/smbus.h @@ -0,0 +1,68 @@ +/* + * QEMU SMBus API + * + * 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 "i2c.h" + +struct SMBusDevice { + /* The SMBus protocol is implemented on top of I2C. */ + i2c_slave i2c; + + /* Remaining fields for internal use only. */ + int mode; + int data_len; + uint8_t data_buf[34]; /* command + len + 32 bytes of data. */ + uint8_t command; +}; + +typedef struct { + I2CSlaveInfo i2c; + int (*init)(SMBusDevice *dev); + void (*quick_cmd)(SMBusDevice *dev, uint8_t read); + void (*send_byte)(SMBusDevice *dev, uint8_t val); + uint8_t (*receive_byte)(SMBusDevice *dev); + /* We can't distinguish between a word write and a block write with + length 1, so pass the whole data block including the length byte + (if present). The device is responsible figuring out what type of + command this is. */ + void (*write_data)(SMBusDevice *dev, uint8_t cmd, uint8_t *buf, int len); + /* Likewise we can't distinguish between different reads, or even know + the length of the read until the read is complete, so read data a + byte at a time. The device is responsible for adding the length + byte on block reads. */ + uint8_t (*read_data)(SMBusDevice *dev, uint8_t cmd, int n); +} SMBusDeviceInfo; + +void smbus_register_device(SMBusDeviceInfo *info); + +/* Master device commands. */ +void smbus_quick_command(i2c_bus *bus, uint8_t addr, int read); +uint8_t smbus_receive_byte(i2c_bus *bus, uint8_t addr); +void smbus_send_byte(i2c_bus *bus, uint8_t addr, uint8_t data); +uint8_t smbus_read_byte(i2c_bus *bus, uint8_t addr, uint8_t command); +void smbus_write_byte(i2c_bus *bus, uint8_t addr, uint8_t command, uint8_t data); +uint16_t smbus_read_word(i2c_bus *bus, uint8_t addr, uint8_t command); +void smbus_write_word(i2c_bus *bus, uint8_t addr, uint8_t command, uint16_t data); +int smbus_read_block(i2c_bus *bus, uint8_t addr, uint8_t command, uint8_t *data); +void smbus_write_block(i2c_bus *bus, uint8_t addr, uint8_t command, uint8_t *data, + int len); diff --git a/hw/smbus_eeprom.c b/hw/smbus_eeprom.c new file mode 100644 index 0000000..52463e0 --- /dev/null +++ b/hw/smbus_eeprom.c @@ -0,0 +1,127 @@ +/* + * 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.h" +#include "i2c.h" +#include "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 + /* An 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_init(SMBusDevice *dev) +{ + SMBusEEPROMDevice *eeprom = (SMBusEEPROMDevice *)dev; + + eeprom->offset = 0; + return 0; +} + +static SMBusDeviceInfo smbus_eeprom_info = { + .i2c.qdev.name = "smbus-eeprom", + .i2c.qdev.size = sizeof(SMBusEEPROMDevice), + .i2c.qdev.props = (Property[]) { + DEFINE_PROP_PTR("data", SMBusEEPROMDevice, data), + DEFINE_PROP_END_OF_LIST(), + }, + .init = smbus_eeprom_init, + .quick_cmd = eeprom_quick_cmd, + .send_byte = eeprom_send_byte, + .receive_byte = eeprom_receive_byte, + .write_data = eeprom_write_data, + .read_data = eeprom_read_data +}; + +static void smbus_eeprom_register_devices(void) +{ + smbus_register_device(&smbus_eeprom_info); +} + +device_init(smbus_eeprom_register_devices) diff --git a/hw/smc91c111.c b/hw/smc91c111.c new file mode 100644 index 0000000..dafea5c --- /dev/null +++ b/hw/smc91c111.c @@ -0,0 +1,798 @@ +/* + * SMSC 91C111 Ethernet interface emulation + * + * Copyright (c) 2005 CodeSourcery, LLC. + * Written by Paul Brook + * + * This code is licenced under the GPL + */ + +#include "sysbus.h" +#include "net.h" +#include "devices.h" +/* For crc32 */ +#include + +/* Number of 2k memory pages available. */ +#define NUM_PACKETS 4 + +typedef struct { + SysBusDevice busdev; + 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; + int mmio_index; +} 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); +} + +/* 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(&s->nic->nc, 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(smc91c111_state *s) +{ + 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, target_phys_addr_t 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(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; + 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, target_phys_addr_t 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, target_phys_addr_t offset, + uint32_t value) +{ + smc91c111_writeb(opaque, offset, value & 0xff); + smc91c111_writeb(opaque, offset + 1, value >> 8); +} + +static void smc91c111_writel(void *opaque, target_phys_addr_t 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, target_phys_addr_t 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, target_phys_addr_t offset) +{ + uint32_t val; + val = smc91c111_readw(opaque, offset); + val |= smc91c111_readw(opaque, offset + 2) << 16; + return val; +} + +static int smc91c111_can_receive(VLANClientState *nc) +{ + smc91c111_state *s = DO_UPCAST(NICState, nc, nc)->opaque; + + 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(VLANClientState *nc, const uint8_t *buf, size_t size) +{ + smc91c111_state *s = DO_UPCAST(NICState, nc, nc)->opaque; + 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 CPUReadMemoryFunc * const smc91c111_readfn[] = { + smc91c111_readb, + smc91c111_readw, + smc91c111_readl +}; + +static CPUWriteMemoryFunc * const smc91c111_writefn[] = { + smc91c111_writeb, + smc91c111_writew, + smc91c111_writel +}; + +static void smc91c111_cleanup(VLANClientState *nc) +{ + smc91c111_state *s = DO_UPCAST(NICState, nc, nc)->opaque; + + s->nic = NULL; +} + +static NetClientInfo net_smc91c111_info = { + .type = NET_CLIENT_TYPE_NIC, + .size = sizeof(NICState), + .can_receive = smc91c111_can_receive, + .receive = smc91c111_receive, + .cleanup = smc91c111_cleanup, +}; + +static int smc91c111_init1(SysBusDevice *dev) +{ + smc91c111_state *s = FROM_SYSBUS(smc91c111_state, dev); + + s->mmio_index = cpu_register_io_memory(smc91c111_readfn, + smc91c111_writefn, s, + DEVICE_NATIVE_ENDIAN); + sysbus_init_mmio(dev, 16, s->mmio_index); + sysbus_init_irq(dev, &s->irq); + qemu_macaddr_default_if_unset(&s->conf.macaddr); + + smc91c111_reset(s); + + s->nic = qemu_new_nic(&net_smc91c111_info, &s->conf, + dev->qdev.info->name, dev->qdev.id, s); + qemu_format_nic_info_str(&s->nic->nc, s->conf.macaddr.a); + /* ??? Save/restore. */ + return 0; +} + +static SysBusDeviceInfo smc91c111_info = { + .init = smc91c111_init1, + .qdev.name = "smc91c111", + .qdev.size = sizeof(smc91c111_state), + .qdev.vmsd = &vmstate_smc91c111, + .qdev.props = (Property[]) { + DEFINE_NIC_PROPERTIES(smc91c111_state, conf), + DEFINE_PROP_END_OF_LIST(), + } +}; + +static void smc91c111_register_devices(void) +{ + sysbus_register_withprop(&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, "smc91c111"); + qdev_set_nic_properties(dev, nd); + qdev_init_nofail(dev); + s = sysbus_from_qdev(dev); + sysbus_mmio_map(s, 0, base); + sysbus_connect_irq(s, 0, irq); +} + +device_init(smc91c111_register_devices) diff --git a/hw/soc_dma.c b/hw/soc_dma.c new file mode 100644 index 0000000..23ec516 --- /dev/null +++ b/hw/soc_dma.c @@ -0,0 +1,366 @@ +/* + * On-chip DMA controller framework. + * + * Copyright (C) 2008 Nokia Corporation + * Written by Andrzej Zaborowski + * + * 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 . + */ +#include "qemu-common.h" +#include "qemu-timer.h" +#include "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 = qemu_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; + target_phys_addr_t 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_get_clock(vm_clock); + struct dma_s *dma = (struct dma_s *) ch->dma; + + qemu_mod_timer(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, + target_phys_addr_t 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) + qemu_del_timer(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 = qemu_mallocz(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 = qemu_new_timer(vm_clock, 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, target_phys_addr_t 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 = qemu_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, + target_phys_addr_t virt_base, size_t size) +{ + struct memmap_entry_s *entry; + struct dma_s *dma = (struct dma_s *) soc; + + dma->memmap = qemu_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/soc_dma.h b/hw/soc_dma.h new file mode 100644 index 0000000..c0ebb8d --- /dev/null +++ b/hw/soc_dma.h @@ -0,0 +1,113 @@ +/* + * On-chip DMA controller framework. + * + * Copyright (C) 2008 Nokia Corporation + * Written by Andrzej Zaborowski + * + * 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 . + */ + +struct soc_dma_s; +struct soc_dma_ch_s; +typedef void (*soc_dma_io_t)(void *opaque, uint8_t *buf, int len); +typedef void (*soc_dma_transfer_t)(struct soc_dma_ch_s *ch); + +enum soc_dma_port_type { + soc_dma_port_mem, + soc_dma_port_fifo, + soc_dma_port_other, +}; + +enum soc_dma_access_type { + soc_dma_access_const, + soc_dma_access_linear, + soc_dma_access_other, +}; + +struct soc_dma_ch_s { + /* Private */ + struct soc_dma_s *dma; + int num; + QEMUTimer *timer; + + /* Set by soc_dma.c */ + int enable; + int update; + + /* This should be set by dma->setup_fn(). */ + int bytes; + /* Initialised by the DMA module, call soc_dma_ch_update after writing. */ + enum soc_dma_access_type type[2]; + target_phys_addr_t vaddr[2]; /* Updated by .transfer_fn(). */ + /* Private */ + void *paddr[2]; + soc_dma_io_t io_fn[2]; + void *io_opaque[2]; + + int running; + soc_dma_transfer_t transfer_fn; + + /* Set and used by the DMA module. */ + void *opaque; +}; + +struct soc_dma_s { + /* Following fields are set by the SoC DMA module and can be used + * by anybody. */ + uint64_t drqbmp; /* Is zeroed by soc_dma_reset() */ + qemu_irq *drq; + void *opaque; + int64_t freq; + soc_dma_transfer_t transfer_fn; + soc_dma_transfer_t setup_fn; + /* Set by soc_dma_init() for use by the DMA module. */ + struct soc_dma_ch_s *ch; +}; + +/* Call to activate or stop a DMA channel. */ +void soc_dma_set_request(struct soc_dma_ch_s *ch, int level); +/* Call after every write to one of the following fields and before + * calling soc_dma_set_request(ch, 1): + * ch->type[0...1], + * ch->vaddr[0...1], + * ch->paddr[0...1], + * or after a soc_dma_port_add_fifo() or soc_dma_port_add_mem(). */ +void soc_dma_ch_update(struct soc_dma_ch_s *ch); + +/* The SoC should call this when the DMA module is being reset. */ +void soc_dma_reset(struct soc_dma_s *s); +struct soc_dma_s *soc_dma_init(int n); + +void soc_dma_port_add_fifo(struct soc_dma_s *dma, target_phys_addr_t virt_base, + soc_dma_io_t fn, void *opaque, int out); +void soc_dma_port_add_mem(struct soc_dma_s *dma, uint8_t *phys_base, + target_phys_addr_t virt_base, size_t size); + +static inline void soc_dma_port_add_fifo_in(struct soc_dma_s *dma, + target_phys_addr_t virt_base, soc_dma_io_t fn, void *opaque) +{ + return soc_dma_port_add_fifo(dma, virt_base, fn, opaque, 0); +} + +static inline void soc_dma_port_add_fifo_out(struct soc_dma_s *dma, + target_phys_addr_t virt_base, soc_dma_io_t fn, void *opaque) +{ + return soc_dma_port_add_fifo(dma, virt_base, fn, opaque, 1); +} + +static inline void soc_dma_port_add_mem_ram(struct soc_dma_s *dma, + ram_addr_t offset, target_phys_addr_t virt_base, size_t size) +{ + return soc_dma_port_add_mem(dma, qemu_get_ram_ptr(offset), virt_base, size); +} diff --git a/hw/sparc32_dma.c b/hw/sparc32_dma.c new file mode 100644 index 0000000..e75694b --- /dev/null +++ b/hw/sparc32_dma.c @@ -0,0 +1,305 @@ +/* + * 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.h" +#include "sparc32_dma.h" +#include "sun4m.h" +#include "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 + +typedef struct DMAState DMAState; + +struct DMAState { + SysBusDevice busdev; + 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, target_phys_addr_t 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, target_phys_addr_t 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 uint32_t dma_mem_readl(void *opaque, target_phys_addr_t addr) +{ + 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_writel(void *opaque, target_phys_addr_t addr, uint32_t val) +{ + 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 CPUReadMemoryFunc * const dma_mem_read[3] = { + NULL, + NULL, + dma_mem_readl, +}; + +static CPUWriteMemoryFunc * const dma_mem_write[3] = { + NULL, + NULL, + dma_mem_writel, +}; + +static void dma_reset(DeviceState *d) +{ + DMAState *s = container_of(d, DMAState, busdev.qdev); + + 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, + .minimum_version_id_old = 2, + .fields = (VMStateField []) { + VMSTATE_UINT32_ARRAY(dmaregs, DMAState, DMA_REGS), + VMSTATE_END_OF_LIST() + } +}; + +static int sparc32_dma_init1(SysBusDevice *dev) +{ + DMAState *s = FROM_SYSBUS(DMAState, dev); + int dma_io_memory; + int reg_size; + + sysbus_init_irq(dev, &s->irq); + + dma_io_memory = cpu_register_io_memory(dma_mem_read, dma_mem_write, s, + DEVICE_NATIVE_ENDIAN); + reg_size = s->is_ledma ? DMA_ETH_SIZE : DMA_SIZE; + sysbus_init_mmio(dev, reg_size, dma_io_memory); + + qdev_init_gpio_in(&dev->qdev, dma_set_irq, 1); + qdev_init_gpio_out(&dev->qdev, s->gpio, 2); + + return 0; +} + +static SysBusDeviceInfo sparc32_dma_info = { + .init = sparc32_dma_init1, + .qdev.name = "sparc32_dma", + .qdev.size = sizeof(DMAState), + .qdev.vmsd = &vmstate_dma, + .qdev.reset = dma_reset, + .qdev.props = (Property[]) { + 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_register_devices(void) +{ + sysbus_register_withprop(&sparc32_dma_info); +} + +device_init(sparc32_dma_register_devices) diff --git a/hw/sparc32_dma.h b/hw/sparc32_dma.h new file mode 100644 index 0000000..8b72c37 --- /dev/null +++ b/hw/sparc32_dma.h @@ -0,0 +1,12 @@ +#ifndef SPARC32_DMA_H +#define SPARC32_DMA_H + +/* sparc32_dma.c */ +void ledma_memory_read(void *opaque, target_phys_addr_t addr, + uint8_t *buf, int len, int do_bswap); +void ledma_memory_write(void *opaque, target_phys_addr_t addr, + uint8_t *buf, int len, int do_bswap); +void espdma_memory_read(void *opaque, uint8_t *buf, int len); +void espdma_memory_write(void *opaque, uint8_t *buf, int len); + +#endif diff --git a/hw/spitz.c b/hw/spitz.c new file mode 100644 index 0000000..5b1e42d --- /dev/null +++ b/hw/spitz.c @@ -0,0 +1,1117 @@ +/* + * PXA270-based Clamshell PDA platforms. + * + * Copyright (c) 2006 Openedhand Ltd. + * Written by Andrzej Zaborowski + * + * This code is licensed under the GNU GPL v2. + */ + +#include "hw.h" +#include "pxa.h" +#include "arm-misc.h" +#include "sysemu.h" +#include "pcmcia.h" +#include "i2c.h" +#include "ssi.h" +#include "flash.h" +#include "qemu-timer.h" +#include "devices.h" +#include "sharpsl.h" +#include "console.h" +#include "block.h" +#include "audio/audio.h" +#include "boards.h" +#include "blockdev.h" +#include "sysbus.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) + +typedef struct { + SysBusDevice busdev; + NANDFlashState *nand; + uint8_t ctl; + uint8_t manf_id; + uint8_t chip_id; + ECCState ecc; +} SLNANDState; + +static uint32_t sl_readb(void *opaque, target_phys_addr_t addr) +{ + 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: + return ecc_digest(&s->ecc, nand_getio(s->nand)); + + default: + zaurus_printf("Bad register offset " REG_FMT "\n", (unsigned long)addr); + } + return 0; +} + +static uint32_t sl_readl(void *opaque, target_phys_addr_t addr) +{ + SLNANDState *s = (SLNANDState *) opaque; + + if (addr == FLASH_FLASHIO) + return ecc_digest(&s->ecc, nand_getio(s->nand)) | + (ecc_digest(&s->ecc, nand_getio(s->nand)) << 16); + + return sl_readb(opaque, addr); +} + +static void sl_writeb(void *opaque, target_phys_addr_t addr, + uint32_t value) +{ + 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 CPUReadMemoryFunc * const sl_readfn[] = { + sl_readb, + sl_readb, + sl_readl, +}; +static CPUWriteMemoryFunc * const sl_writefn[] = { + sl_writeb, + sl_writeb, + sl_writeb, +}; + +static void sl_flash_register(PXA2xxState *cpu, int size) +{ + DeviceState *dev; + + dev = qdev_create(NULL, "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(sysbus_from_qdev(dev), 0, FLASH_BASE); +} + +static int sl_nand_init(SysBusDevice *dev) { + int iomemtype; + SLNANDState *s; + + s = FROM_SYSBUS(SLNANDState, dev); + + s->ctl = 0; + s->nand = nand_init(s->manf_id, s->chip_id); + + iomemtype = cpu_register_io_memory(sl_readfn, + sl_writefn, s, DEVICE_NATIVE_ENDIAN); + + sysbus_init_mmio(dev, 0x40, iomemtype); + + 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, +}; + +typedef struct { + SysBusDevice busdev; + 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 SHIFT (1 << 7) +#define CTRL (1 << 8) +#define 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 | SHIFT) : + (keycode & ~SHIFT))]; + + if (code != mapcode) { +#if 0 + if ((code & SHIFT) && !(s->modifiers & 1)) + QUEUE_KEY(0x2a | (keycode & 0x80)); + if ((code & CTRL ) && !(s->modifiers & 4)) + QUEUE_KEY(0x1d | (keycode & 0x80)); + if ((code & FN ) && !(s->modifiers & 8)) + QUEUE_KEY(0x38 | (keycode & 0x80)); + if ((code & FN ) && (s->modifiers & 1)) + QUEUE_KEY(0x2a | (~keycode & 0x80)); + if ((code & 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 & SHIFT) && !((s->modifiers | s->imodifiers) & 1)) { + QUEUE_KEY(0x2a); + s->imodifiers |= 1; + } + if ((code & CTRL ) && !((s->modifiers | s->imodifiers) & 4)) { + QUEUE_KEY(0x1d); + s->imodifiers |= 4; + } + if ((code & FN ) && !((s->modifiers | s->imodifiers) & 8)) { + QUEUE_KEY(0x38); + s->imodifiers |= 8; + } + if ((code & FN ) && (s->modifiers & 1) && + !(s->imodifiers & 0x10)) { + QUEUE_KEY(0x2a | 0x80); + s->imodifiers |= 0x10; + } + if ((code & 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; + } + + qemu_mod_timer(s->kbdtimer, qemu_get_clock(vm_clock) + + 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 | SHIFT ] = 0x02 | SHIFT; /* exclam */ + s->pre_map[0x28 | SHIFT ] = 0x03 | SHIFT; /* quotedbl */ + s->pre_map[0x04 | SHIFT ] = 0x04 | SHIFT; /* numbersign */ + s->pre_map[0x05 | SHIFT ] = 0x05 | SHIFT; /* dollar */ + s->pre_map[0x06 | SHIFT ] = 0x06 | SHIFT; /* percent */ + s->pre_map[0x08 | SHIFT ] = 0x07 | SHIFT; /* ampersand */ + s->pre_map[0x28 ] = 0x08 | SHIFT; /* apostrophe */ + s->pre_map[0x0a | SHIFT ] = 0x09 | SHIFT; /* parenleft */ + s->pre_map[0x0b | SHIFT ] = 0x0a | SHIFT; /* parenright */ + s->pre_map[0x29 | SHIFT ] = 0x0b | SHIFT; /* asciitilde */ + s->pre_map[0x03 | SHIFT ] = 0x0c | SHIFT; /* at */ + s->pre_map[0xd3 ] = 0x0e | FN; /* Delete */ + s->pre_map[0x3a ] = 0x0f | FN; /* Caps_Lock */ + s->pre_map[0x07 | SHIFT ] = 0x11 | FN; /* asciicircum */ + s->pre_map[0x0d ] = 0x12 | FN; /* equal */ + s->pre_map[0x0d | SHIFT ] = 0x13 | FN; /* plus */ + s->pre_map[0x1a ] = 0x14 | FN; /* bracketleft */ + s->pre_map[0x1b ] = 0x15 | FN; /* bracketright */ + s->pre_map[0x1a | SHIFT ] = 0x16 | FN; /* braceleft */ + s->pre_map[0x1b | SHIFT ] = 0x17 | FN; /* braceright */ + s->pre_map[0x27 ] = 0x22 | FN; /* semicolon */ + s->pre_map[0x27 | SHIFT ] = 0x23 | FN; /* colon */ + s->pre_map[0x09 | SHIFT ] = 0x24 | FN; /* asterisk */ + s->pre_map[0x2b ] = 0x25 | FN; /* backslash */ + s->pre_map[0x2b | SHIFT ] = 0x26 | FN; /* bar */ + s->pre_map[0x0c | SHIFT ] = 0x30 | FN; /* underscore */ + s->pre_map[0x33 | SHIFT ] = 0x33 | FN; /* less */ + s->pre_map[0x35 ] = 0x33 | SHIFT; /* slash */ + s->pre_map[0x34 | SHIFT ] = 0x34 | FN; /* greater */ + s->pre_map[0x35 | SHIFT ] = 0x34 | SHIFT; /* question */ + s->pre_map[0x49 ] = 0x48 | FN; /* Page_Up */ + s->pre_map[0x51 ] = 0x50 | FN; /* Page_Down */ + + s->modifiers = 0; + s->imodifiers = 0; + s->fifopos = 0; + s->fifolen = 0; +} + +#undef SHIFT +#undef CTRL +#undef 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("spitz-keyboard", -1, NULL); + s = FROM_SYSBUS(SpitzKeyboardState, sysbus_from_qdev(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)); + + qemu_mod_timer(s->kbdtimer, qemu_get_clock(vm_clock)); + + qemu_add_kbd_event_handler(spitz_keyboard_handler, s); +} + +static int spitz_keyboard_init(SysBusDevice *dev) +{ + SpitzKeyboardState *s; + int i, j; + + s = FROM_SYSBUS(SpitzKeyboardState, dev); + + 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 = qemu_new_timer(vm_clock, spitz_keyboard_tick, s); + qdev_init_gpio_in(&dev->qdev, spitz_keyboard_strobe, SPITZ_KEY_STROBE_NUM); + qdev_init_gpio_out(&dev->qdev, 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 *dev) +{ + CorgiSSPState *s = FROM_SSI_SLAVE(CorgiSSPState, dev); + + qdev_init_gpio_in(&dev->qdev, corgi_ssp_gpio_cs, 3); + s->bus[0] = ssi_create_bus(&dev->qdev, "ssi0"); + s->bus[1] = ssi_create_bus(&dev->qdev, "ssi1"); + s->bus[2] = ssi_create_bus(&dev->qdev, "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; + BlockDriverState *bs; + DriveInfo *dinfo; + + dinfo = drive_get(IF_IDE, 0, 0); + if (!dinfo) + return; + bs = dinfo->bdrv; + if (bdrv_is_inserted(bs) && !bdrv_is_removable(bs)) { + 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) +{ + i2c_slave *wm = (i2c_slave *) 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. */ + i2c_bus *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_irqs(spitz_wm8750_addr, wm, 1)[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_irqs(spitz_lcd_hsync_handler, cpu, 1)[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(ram_addr_t ram_size, + const char *kernel_filename, + const char *kernel_cmdline, const char *initrd_filename, + const char *cpu_model, enum spitz_model_e model, int arm_id) +{ + PXA2xxState *cpu; + DeviceState *scp0, *scp1 = NULL; + + if (!cpu_model) + cpu_model = (model == terrier) ? "pxa270-c5" : "pxa270-c0"; + + /* Setup CPU & memory */ + cpu = pxa270_init(spitz_binfo.ram_size, cpu_model); + + sl_flash_register(cpu, (model == spitz) ? FLASH_128M : FLASH_1024M); + + cpu_register_physical_memory(0, SPITZ_ROM, + qemu_ram_alloc(NULL, "spitz.rom", SPITZ_ROM) | IO_MEM_ROM); + + /* Setup peripherals */ + spitz_keyboard_register(cpu); + + spitz_ssp_attach(cpu); + + scp0 = sysbus_create_simple("scoop", 0x10800000, NULL); + if (model != akita) { + scp1 = sysbus_create_simple("scoop", 0x08800040, NULL); + } + + spitz_scoop_gpio_setup(cpu, scp0, scp1); + + spitz_gpio_setup(cpu, (model == akita) ? 1 : 2); + + spitz_i2c_setup(cpu); + + if (model == akita) + spitz_akita_i2c_setup(cpu); + + if (model == terrier) + /* A 6.0 GB microdrive is permanently sitting in CF slot 1. */ + spitz_microdrive_attach(cpu, 1); + else if (model != akita) + /* A 4.0 GB microdrive is permanently sitting in CF slot 0. */ + spitz_microdrive_attach(cpu, 0); + + spitz_binfo.kernel_filename = kernel_filename; + spitz_binfo.kernel_cmdline = kernel_cmdline; + spitz_binfo.initrd_filename = initrd_filename; + spitz_binfo.board_id = arm_id; + arm_load_kernel(cpu->env, &spitz_binfo); + sl_bootparam_write(SL_PXA_PARAM_BASE); +} + +static void spitz_init(ram_addr_t ram_size, + const char *boot_device, + const char *kernel_filename, const char *kernel_cmdline, + const char *initrd_filename, const char *cpu_model) +{ + spitz_common_init(ram_size, kernel_filename, + kernel_cmdline, initrd_filename, cpu_model, spitz, 0x2c9); +} + +static void borzoi_init(ram_addr_t ram_size, + const char *boot_device, + const char *kernel_filename, const char *kernel_cmdline, + const char *initrd_filename, const char *cpu_model) +{ + spitz_common_init(ram_size, kernel_filename, + kernel_cmdline, initrd_filename, cpu_model, borzoi, 0x33f); +} + +static void akita_init(ram_addr_t ram_size, + const char *boot_device, + const char *kernel_filename, const char *kernel_cmdline, + const char *initrd_filename, const char *cpu_model) +{ + spitz_common_init(ram_size, kernel_filename, + kernel_cmdline, initrd_filename, cpu_model, akita, 0x2e8); +} + +static void terrier_init(ram_addr_t ram_size, + const char *boot_device, + const char *kernel_filename, const char *kernel_cmdline, + const char *initrd_filename, const char *cpu_model) +{ + spitz_common_init(ram_size, kernel_filename, + kernel_cmdline, initrd_filename, cpu_model, 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, + .minimum_version_id_old = 0, + .fields = (VMStateField []) { + VMSTATE_UINT8(ctl, SLNANDState), + VMSTATE_STRUCT(ecc, SLNANDState, 0, vmstate_ecc_state, ECCState), + VMSTATE_END_OF_LIST(), + }, +}; + +static SysBusDeviceInfo sl_nand_info = { + .init = sl_nand_init, + .qdev.name = "sl-nand", + .qdev.size = sizeof(SLNANDState), + .qdev.vmsd = &vmstate_sl_nand_info, + .qdev.props = (Property []) { + 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 VMStateDescription vmstate_spitz_kbd = { + .name = "spitz-keyboard", + .version_id = 1, + .minimum_version_id = 0, + .minimum_version_id_old = 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 SysBusDeviceInfo spitz_keyboard_info = { + .init = spitz_keyboard_init, + .qdev.name = "spitz-keyboard", + .qdev.size = sizeof(SpitzKeyboardState), + .qdev.vmsd = &vmstate_spitz_kbd, + .qdev.props = (Property []) { + DEFINE_PROP_END_OF_LIST(), + }, +}; + +static const VMStateDescription vmstate_corgi_ssp_regs = { + .name = "corgi-ssp", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField []) { + VMSTATE_UINT32_ARRAY(enable, CorgiSSPState, 3), + VMSTATE_END_OF_LIST(), + } +}; + +static SSISlaveInfo corgi_ssp_info = { + .qdev.name = "corgi-ssp", + .qdev.size = sizeof(CorgiSSPState), + .qdev.vmsd = &vmstate_corgi_ssp_regs, + .init = corgi_ssp_init, + .transfer = corgi_ssp_transfer +}; + +static const VMStateDescription vmstate_spitz_lcdtg_regs = { + .name = "spitz-lcdtg", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField []) { + VMSTATE_UINT32(bl_intensity, SpitzLCDTG), + VMSTATE_UINT32(bl_power, SpitzLCDTG), + VMSTATE_END_OF_LIST(), + } +}; + +static SSISlaveInfo spitz_lcdtg_info = { + .qdev.name = "spitz-lcdtg", + .qdev.size = sizeof(SpitzLCDTG), + .qdev.vmsd = &vmstate_spitz_lcdtg_regs, + .init = spitz_lcdtg_init, + .transfer = spitz_lcdtg_transfer +}; + +static void spitz_register_devices(void) +{ + ssi_register_slave(&corgi_ssp_info); + ssi_register_slave(&spitz_lcdtg_info); + sysbus_register_withprop(&spitz_keyboard_info); + sysbus_register_withprop(&sl_nand_info); +} + +device_init(spitz_register_devices) diff --git a/hw/ssd0303.c b/hw/ssd0303.c new file mode 100644 index 0000000..108c068 --- /dev/null +++ b/hw/ssd0303.c @@ -0,0 +1,312 @@ +/* + * SSD0303 OLED controller with OSRAM Pictiva 96x16 display. + * + * Copyright (c) 2006-2007 CodeSourcery. + * Written by Paul Brook + * + * This code is licenced 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 "i2c.h" +#include "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 +}; + +typedef struct { + i2c_slave i2c; + DisplayState *ds; + 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(i2c_slave *i2c) +{ + BADF("Reads not implemented\n"); + return -1; +} + +static int ssd0303_send(i2c_slave *i2c, uint8_t data) +{ + ssd0303_state *s = (ssd0303_state *)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 colum 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 multipled 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(i2c_slave *i2c, enum i2c_event event) +{ + ssd0303_state *s = (ssd0303_state *)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; + 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 (ds_get_bits_per_pixel(s->ds)) { + 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 = ds_get_data(s->ds); + 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_update(s->ds, 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, + .minimum_version_id_old = 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(i2c, ssd0303_state), + VMSTATE_END_OF_LIST() + } +}; + +static int ssd0303_init(i2c_slave *i2c) +{ + ssd0303_state *s = FROM_I2C_SLAVE(ssd0303_state, i2c); + + s->ds = graphic_console_init(ssd0303_update_display, + ssd0303_invalidate_display, + NULL, NULL, s); + qemu_console_resize(s->ds, 96 * MAGNIFY, 16 * MAGNIFY); + return 0; +} + +static I2CSlaveInfo ssd0303_info = { + .qdev.name = "ssd0303", + .qdev.size = sizeof(ssd0303_state), + .qdev.vmsd = &vmstate_ssd0303, + .init = ssd0303_init, + .event = ssd0303_event, + .recv = ssd0303_recv, + .send = ssd0303_send +}; + +static void ssd0303_register_devices(void) +{ + i2c_register_slave(&ssd0303_info); +} + +device_init(ssd0303_register_devices) diff --git a/hw/ssd0323.c b/hw/ssd0323.c new file mode 100644 index 0000000..8643961 --- /dev/null +++ b/hw/ssd0323.c @@ -0,0 +1,355 @@ +/* + * SSD0323 OLED controller with OSRAM Pictiva 128x64 display. + * + * Copyright (c) 2006-2007 CodeSourcery. + * Written by Paul Brook + * + * This code is licenced 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 "ssi.h" +#include "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__); exit(1);} 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; + DisplayState *ds; + + 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; + 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 (ds_get_bits_per_pixel(s->ds)) { + 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 (ds_get_bits_per_pixel(s->ds)) { + 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 = ds_get_data(s->ds); + 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_update(s->ds, 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) +{ + 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)); +} + +static int ssd0323_load(QEMUFile *f, void *opaque, int version_id) +{ + ssd0323_state *s = (ssd0323_state *)opaque; + int i; + + if (version_id != 1) + return -EINVAL; + + s->cmd_len = qemu_get_be32(f); + 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); + s->row_start = qemu_get_be32(f); + s->row_end = qemu_get_be32(f); + s->col = qemu_get_be32(f); + s->col_start = qemu_get_be32(f); + s->col_end = qemu_get_be32(f); + s->redraw = qemu_get_be32(f); + s->remap = qemu_get_be32(f); + s->mode = qemu_get_be32(f); + qemu_get_buffer(f, s->framebuffer, sizeof(s->framebuffer)); + + return 0; +} + +static int ssd0323_init(SSISlave *dev) +{ + ssd0323_state *s = FROM_SSI_SLAVE(ssd0323_state, dev); + + s->col_end = 63; + s->row_end = 79; + s->ds = graphic_console_init(ssd0323_update_display, + ssd0323_invalidate_display, + NULL, NULL, s); + qemu_console_resize(s->ds, 128 * MAGNIFY, 64 * MAGNIFY); + + qdev_init_gpio_in(&dev->qdev, ssd0323_cd, 1); + + register_savevm(&dev->qdev, "ssd0323_oled", -1, 1, + ssd0323_save, ssd0323_load, s); + return 0; +} + +static SSISlaveInfo ssd0323_info = { + .qdev.name = "ssd0323", + .qdev.size = sizeof(ssd0323_state), + .init = ssd0323_init, + .transfer = ssd0323_transfer +}; + +static void ssd03232_register_devices(void) +{ + ssi_register_slave(&ssd0323_info); +} + +device_init(ssd03232_register_devices) diff --git a/hw/ssi-sd.c b/hw/ssi-sd.c new file mode 100644 index 0000000..fb4b649 --- /dev/null +++ b/hw/ssi-sd.c @@ -0,0 +1,256 @@ +/* + * SSI to SD card adapter. + * + * Copyright (c) 2007-2009 CodeSourcery. + * Written by Paul Brook + * + * This code is licenced under the GNU GPL v2. + */ + +#include "blockdev.h" +#include "ssi.h" +#include "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) +{ + 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); +} + +static int ssi_sd_load(QEMUFile *f, void *opaque, int version_id) +{ + 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); + s->response_pos = qemu_get_be32(f); + s->stopping = qemu_get_be32(f); + + return 0; +} + +static int ssi_sd_init(SSISlave *dev) +{ + ssi_sd_state *s = FROM_SSI_SLAVE(ssi_sd_state, dev); + DriveInfo *dinfo; + + s->mode = SSI_SD_CMD; + dinfo = drive_get_next(IF_SD); + s->sd = sd_init(dinfo ? dinfo->bdrv : NULL, 1); + register_savevm(&dev->qdev, "ssi_sd", -1, 1, ssi_sd_save, ssi_sd_load, s); + return 0; +} + +static SSISlaveInfo ssi_sd_info = { + .qdev.name = "ssi-sd", + .qdev.size = sizeof(ssi_sd_state), + .init = ssi_sd_init, + .transfer = ssi_sd_transfer +}; + +static void ssi_sd_register_devices(void) +{ + ssi_register_slave(&ssi_sd_info); +} + +device_init(ssi_sd_register_devices) diff --git a/hw/ssi.c b/hw/ssi.c new file mode 100644 index 0000000..cfe7c07 --- /dev/null +++ b/hw/ssi.c @@ -0,0 +1,70 @@ +/* + * QEMU Synchronous Serial Interface support + * + * Copyright (c) 2009 CodeSourcery. + * Written by Paul Brook + * + * This code is licenced under the GNU GPL v2. + */ + +#include "ssi.h" + +struct SSIBus { + BusState qbus; +}; + +static struct BusInfo ssi_bus_info = { + .name = "SSI", + .size = sizeof(SSIBus), +}; + +static int ssi_slave_init(DeviceState *dev, DeviceInfo *base_info) +{ + SSISlaveInfo *info = container_of(base_info, SSISlaveInfo, qdev); + SSISlave *s = SSI_SLAVE_FROM_QDEV(dev); + SSIBus *bus; + + bus = FROM_QBUS(SSIBus, qdev_get_parent_bus(dev)); + if (QLIST_FIRST(&bus->qbus.children) != dev + || QLIST_NEXT(dev, sibling) != NULL) { + hw_error("Too many devices on SSI bus"); + } + + s->info = info; + return info->init(s); +} + +void ssi_register_slave(SSISlaveInfo *info) +{ + assert(info->qdev.size >= sizeof(SSISlave)); + info->qdev.init = ssi_slave_init; + info->qdev.bus_info = &ssi_bus_info; + qdev_register(&info->qdev); +} + +DeviceState *ssi_create_slave(SSIBus *bus, const char *name) +{ + DeviceState *dev; + dev = qdev_create(&bus->qbus, name); + qdev_init_nofail(dev); + return dev; +} + +SSIBus *ssi_create_bus(DeviceState *parent, const char *name) +{ + BusState *bus; + bus = qbus_create(&ssi_bus_info, parent, name); + return FROM_QBUS(SSIBus, bus); +} + +uint32_t ssi_transfer(SSIBus *bus, uint32_t val) +{ + DeviceState *dev; + SSISlave *slave; + dev = QLIST_FIRST(&bus->qbus.children); + if (!dev) { + return 0; + } + slave = SSI_SLAVE_FROM_QDEV(dev); + return slave->info->transfer(slave, val); +} diff --git a/hw/ssi.h b/hw/ssi.h new file mode 100644 index 0000000..24610a8 --- /dev/null +++ b/hw/ssi.h @@ -0,0 +1,45 @@ +/* QEMU Synchronous Serial Interface support. */ + +/* In principle SSI is a point-point interface. As such the qemu + implementation has a single slave device on a "bus". + However it is fairly common for boards to have multiple slaves + connected to a single master, and select devices with an external + chip select. This is implemented in qemu by having an explicit mux device. + It is assumed that master and slave are both using the same transfer width. + */ + +#ifndef QEMU_SSI_H +#define QEMU_SSI_H + +#include "qdev.h" + +typedef struct SSISlave SSISlave; + +/* Slave devices. */ +typedef struct { + DeviceInfo qdev; + int (*init)(SSISlave *dev); + uint32_t (*transfer)(SSISlave *dev, uint32_t val); +} SSISlaveInfo; + +struct SSISlave { + DeviceState qdev; + SSISlaveInfo *info; +}; + +#define SSI_SLAVE_FROM_QDEV(dev) DO_UPCAST(SSISlave, qdev, dev) +#define FROM_SSI_SLAVE(type, dev) DO_UPCAST(type, ssidev, dev) + +void ssi_register_slave(SSISlaveInfo *info); + +DeviceState *ssi_create_slave(SSIBus *bus, const char *name); + +/* Master interface. */ +SSIBus *ssi_create_bus(DeviceState *parent, const char *name); + +uint32_t ssi_transfer(SSIBus *bus, uint32_t val); + +/* max111x.c */ +void max111x_set_input(DeviceState *dev, int line, uint8_t value); + +#endif diff --git a/hw/stellaris.c b/hw/stellaris.c new file mode 100644 index 0000000..b903273 --- /dev/null +++ b/hw/stellaris.c @@ -0,0 +1,1474 @@ +/* + * Luminary Micro Stellaris peripherals + * + * Copyright (c) 2006 CodeSourcery. + * Written by Paul Brook + * + * This code is licenced under the GPL. + */ + +#include "sysbus.h" +#include "ssi.h" +#include "arm-misc.h" +#include "devices.h" +#include "qemu-timer.h" +#include "i2c.h" +#include "net.h" +#include "sysemu.h" +#include "boards.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 + +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. */ + +typedef struct gptm_state { + SysBusDevice busdev; + 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) +{ + qemu_del_timer(s->timer[n]); +} + +static void gptm_reload(gptm_state *s, int n, int reset) +{ + int64_t tick; + if (reset) + tick = qemu_get_clock(vm_clock); + 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; + qemu_mod_timer(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 uint32_t gptm_read(void *opaque, target_phys_addr_t offset) +{ + 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->control == 1) + return s->rtc; + case 0x4c: /* TBR */ + hw_error("TODO: Timer value read\n"); + default: + hw_error("gptm_read: Bad offset 0x%x\n", (int)offset); + return 0; + } +} + +static void gptm_write(void *opaque, target_phys_addr_t offset, uint32_t value) +{ + 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 CPUReadMemoryFunc * const gptm_readfn[] = { + gptm_read, + gptm_read, + gptm_read +}; + +static CPUWriteMemoryFunc * const gptm_writefn[] = { + gptm_write, + gptm_write, + gptm_write +}; + +static void gptm_save(QEMUFile *f, void *opaque) +{ + gptm_state *s = (gptm_state *)opaque; + + qemu_put_be32(f, s->config); + qemu_put_be32(f, s->mode[0]); + qemu_put_be32(f, s->mode[1]); + qemu_put_be32(f, s->control); + qemu_put_be32(f, s->state); + qemu_put_be32(f, s->mask); + qemu_put_be32(f, s->mode[0]); + qemu_put_be32(f, s->mode[0]); + qemu_put_be32(f, s->load[0]); + qemu_put_be32(f, s->load[1]); + qemu_put_be32(f, s->match[0]); + qemu_put_be32(f, s->match[1]); + qemu_put_be32(f, s->prescale[0]); + qemu_put_be32(f, s->prescale[1]); + qemu_put_be32(f, s->match_prescale[0]); + qemu_put_be32(f, s->match_prescale[1]); + qemu_put_be32(f, s->rtc); + qemu_put_be64(f, s->tick[0]); + qemu_put_be64(f, s->tick[1]); + qemu_put_timer(f, s->timer[0]); + qemu_put_timer(f, s->timer[1]); +} + +static int gptm_load(QEMUFile *f, void *opaque, int version_id) +{ + gptm_state *s = (gptm_state *)opaque; + + if (version_id != 1) + return -EINVAL; + + s->config = qemu_get_be32(f); + s->mode[0] = qemu_get_be32(f); + s->mode[1] = qemu_get_be32(f); + s->control = qemu_get_be32(f); + s->state = qemu_get_be32(f); + s->mask = qemu_get_be32(f); + s->mode[0] = qemu_get_be32(f); + s->mode[0] = qemu_get_be32(f); + s->load[0] = qemu_get_be32(f); + s->load[1] = qemu_get_be32(f); + s->match[0] = qemu_get_be32(f); + s->match[1] = qemu_get_be32(f); + s->prescale[0] = qemu_get_be32(f); + s->prescale[1] = qemu_get_be32(f); + s->match_prescale[0] = qemu_get_be32(f); + s->match_prescale[1] = qemu_get_be32(f); + s->rtc = qemu_get_be32(f); + s->tick[0] = qemu_get_be64(f); + s->tick[1] = qemu_get_be64(f); + qemu_get_timer(f, s->timer[0]); + qemu_get_timer(f, s->timer[1]); + + return 0; +} + +static int stellaris_gptm_init(SysBusDevice *dev) +{ + int iomemtype; + gptm_state *s = FROM_SYSBUS(gptm_state, dev); + + sysbus_init_irq(dev, &s->irq); + qdev_init_gpio_out(&dev->qdev, &s->trigger, 1); + + iomemtype = cpu_register_io_memory(gptm_readfn, + gptm_writefn, s, + DEVICE_NATIVE_ENDIAN); + sysbus_init_mmio(dev, 0x1000, iomemtype); + + s->opaque[0] = s->opaque[1] = s; + s->timer[0] = qemu_new_timer(vm_clock, gptm_tick, &s->opaque[0]); + s->timer[1] = qemu_new_timer(vm_clock, gptm_tick, &s->opaque[1]); + register_savevm(&dev->qdev, "stellaris_gptm", -1, 1, + gptm_save, gptm_load, s); + return 0; +} + + +/* System controller. */ + +typedef struct { + uint32_t pborctl; + uint32_t ldopctl; + uint32_t int_status; + uint32_t int_mask; + uint32_t resc; + uint32_t rcc; + 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 */ +}; + +static uint32_t ssys_read(void *opaque, target_phys_addr_t offset) +{ + 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; + if (s->board->did0 & (1 << 16)) { + return pllcfg_fury[xtal]; + } else { + return pllcfg_sandstorm[xtal]; + } + } + 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 void ssys_calculate_system_clock(ssys_state *s) +{ + system_clock_scale = 5 * (((s->rcc >> 23) & 0xf) + 1); +} + +static void ssys_write(void *opaque, target_phys_addr_t offset, uint32_t value) +{ + 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 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 CPUReadMemoryFunc * const ssys_readfn[] = { + ssys_read, + ssys_read, + ssys_read +}; + +static CPUWriteMemoryFunc * const ssys_writefn[] = { + ssys_write, + ssys_write, + ssys_write +}; + +static void ssys_reset(void *opaque) +{ + ssys_state *s = (ssys_state *)opaque; + + s->pborctl = 0x7ffd; + s->rcc = 0x078e3ac0; + s->rcgc[0] = 1; + s->scgc[0] = 1; + s->dcgc[0] = 1; +} + +static void ssys_save(QEMUFile *f, void *opaque) +{ + ssys_state *s = (ssys_state *)opaque; + + qemu_put_be32(f, s->pborctl); + qemu_put_be32(f, s->ldopctl); + qemu_put_be32(f, s->int_mask); + qemu_put_be32(f, s->int_status); + qemu_put_be32(f, s->resc); + qemu_put_be32(f, s->rcc); + qemu_put_be32(f, s->rcgc[0]); + qemu_put_be32(f, s->rcgc[1]); + qemu_put_be32(f, s->rcgc[2]); + qemu_put_be32(f, s->scgc[0]); + qemu_put_be32(f, s->scgc[1]); + qemu_put_be32(f, s->scgc[2]); + qemu_put_be32(f, s->dcgc[0]); + qemu_put_be32(f, s->dcgc[1]); + qemu_put_be32(f, s->dcgc[2]); + qemu_put_be32(f, s->clkvclr); + qemu_put_be32(f, s->ldoarst); +} + +static int ssys_load(QEMUFile *f, void *opaque, int version_id) +{ + ssys_state *s = (ssys_state *)opaque; + + if (version_id != 1) + return -EINVAL; + + s->pborctl = qemu_get_be32(f); + s->ldopctl = qemu_get_be32(f); + s->int_mask = qemu_get_be32(f); + s->int_status = qemu_get_be32(f); + s->resc = qemu_get_be32(f); + s->rcc = qemu_get_be32(f); + s->rcgc[0] = qemu_get_be32(f); + s->rcgc[1] = qemu_get_be32(f); + s->rcgc[2] = qemu_get_be32(f); + s->scgc[0] = qemu_get_be32(f); + s->scgc[1] = qemu_get_be32(f); + s->scgc[2] = qemu_get_be32(f); + s->dcgc[0] = qemu_get_be32(f); + s->dcgc[1] = qemu_get_be32(f); + s->dcgc[2] = qemu_get_be32(f); + s->clkvclr = qemu_get_be32(f); + s->ldoarst = qemu_get_be32(f); + ssys_calculate_system_clock(s); + + return 0; +} + +static int stellaris_sys_init(uint32_t base, qemu_irq irq, + stellaris_board_info * board, + uint8_t *macaddr) +{ + int iomemtype; + ssys_state *s; + + s = (ssys_state *)qemu_mallocz(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); + + iomemtype = cpu_register_io_memory(ssys_readfn, + ssys_writefn, s, + DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(base, 0x00001000, iomemtype); + ssys_reset(s); + register_savevm(NULL, "stellaris_sys", -1, 1, ssys_save, ssys_load, s); + return 0; +} + + +/* I2C controller. */ + +typedef struct { + SysBusDevice busdev; + i2c_bus *bus; + qemu_irq irq; + 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 uint32_t stellaris_i2c_read(void *opaque, target_phys_addr_t offset) +{ + 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, target_phys_addr_t offset, + uint32_t value) +{ + 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 CPUReadMemoryFunc * const stellaris_i2c_readfn[] = { + stellaris_i2c_read, + stellaris_i2c_read, + stellaris_i2c_read +}; + +static CPUWriteMemoryFunc * const stellaris_i2c_writefn[] = { + stellaris_i2c_write, + stellaris_i2c_write, + stellaris_i2c_write +}; + +static void stellaris_i2c_save(QEMUFile *f, void *opaque) +{ + stellaris_i2c_state *s = (stellaris_i2c_state *)opaque; + + qemu_put_be32(f, s->msa); + qemu_put_be32(f, s->mcs); + qemu_put_be32(f, s->mdr); + qemu_put_be32(f, s->mtpr); + qemu_put_be32(f, s->mimr); + qemu_put_be32(f, s->mris); + qemu_put_be32(f, s->mcr); +} + +static int stellaris_i2c_load(QEMUFile *f, void *opaque, int version_id) +{ + stellaris_i2c_state *s = (stellaris_i2c_state *)opaque; + + if (version_id != 1) + return -EINVAL; + + s->msa = qemu_get_be32(f); + s->mcs = qemu_get_be32(f); + s->mdr = qemu_get_be32(f); + s->mtpr = qemu_get_be32(f); + s->mimr = qemu_get_be32(f); + s->mris = qemu_get_be32(f); + s->mcr = qemu_get_be32(f); + + return 0; +} + +static int stellaris_i2c_init(SysBusDevice * dev) +{ + stellaris_i2c_state *s = FROM_SYSBUS(stellaris_i2c_state, dev); + i2c_bus *bus; + int iomemtype; + + sysbus_init_irq(dev, &s->irq); + bus = i2c_init_bus(&dev->qdev, "i2c"); + s->bus = bus; + + iomemtype = cpu_register_io_memory(stellaris_i2c_readfn, + stellaris_i2c_writefn, s, + DEVICE_NATIVE_ENDIAN); + sysbus_init_mmio(dev, 0x1000, iomemtype); + /* ??? For now we only implement the master interface. */ + stellaris_i2c_reset(s); + register_savevm(&dev->qdev, "stellaris_i2c", -1, 1, + stellaris_i2c_save, stellaris_i2c_load, 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 + +typedef struct +{ + SysBusDevice busdev; + 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 uint32_t stellaris_adc_read(void *opaque, target_phys_addr_t offset) +{ + 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, target_phys_addr_t offset, + uint32_t value) +{ + 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 %x\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 CPUReadMemoryFunc * const stellaris_adc_readfn[] = { + stellaris_adc_read, + stellaris_adc_read, + stellaris_adc_read +}; + +static CPUWriteMemoryFunc * const stellaris_adc_writefn[] = { + stellaris_adc_write, + stellaris_adc_write, + stellaris_adc_write +}; + +static void stellaris_adc_save(QEMUFile *f, void *opaque) +{ + stellaris_adc_state *s = (stellaris_adc_state *)opaque; + int i; + int j; + + qemu_put_be32(f, s->actss); + qemu_put_be32(f, s->ris); + qemu_put_be32(f, s->im); + qemu_put_be32(f, s->emux); + qemu_put_be32(f, s->ostat); + qemu_put_be32(f, s->ustat); + qemu_put_be32(f, s->sspri); + qemu_put_be32(f, s->sac); + for (i = 0; i < 4; i++) { + qemu_put_be32(f, s->fifo[i].state); + for (j = 0; j < 16; j++) { + qemu_put_be32(f, s->fifo[i].data[j]); + } + qemu_put_be32(f, s->ssmux[i]); + qemu_put_be32(f, s->ssctl[i]); + } + qemu_put_be32(f, s->noise); +} + +static int stellaris_adc_load(QEMUFile *f, void *opaque, int version_id) +{ + stellaris_adc_state *s = (stellaris_adc_state *)opaque; + int i; + int j; + + if (version_id != 1) + return -EINVAL; + + s->actss = qemu_get_be32(f); + s->ris = qemu_get_be32(f); + s->im = qemu_get_be32(f); + s->emux = qemu_get_be32(f); + s->ostat = qemu_get_be32(f); + s->ustat = qemu_get_be32(f); + s->sspri = qemu_get_be32(f); + s->sac = qemu_get_be32(f); + for (i = 0; i < 4; i++) { + s->fifo[i].state = qemu_get_be32(f); + for (j = 0; j < 16; j++) { + s->fifo[i].data[j] = qemu_get_be32(f); + } + s->ssmux[i] = qemu_get_be32(f); + s->ssctl[i] = qemu_get_be32(f); + } + s->noise = qemu_get_be32(f); + + return 0; +} + +static int stellaris_adc_init(SysBusDevice *dev) +{ + stellaris_adc_state *s = FROM_SYSBUS(stellaris_adc_state, dev); + int iomemtype; + int n; + + for (n = 0; n < 4; n++) { + sysbus_init_irq(dev, &s->irq[n]); + } + + iomemtype = cpu_register_io_memory(stellaris_adc_readfn, + stellaris_adc_writefn, s, + DEVICE_NATIVE_ENDIAN); + sysbus_init_mmio(dev, 0x1000, iomemtype); + stellaris_adc_reset(s); + qdev_init_gpio_in(&dev->qdev, stellaris_adc_trigger, 1); + register_savevm(&dev->qdev, "stellaris_adc", -1, 1, + stellaris_adc_save, stellaris_adc_load, s); + return 0; +} + +/* 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. */ + +typedef struct { + SSISlave ssidev; + qemu_irq irq; + int current_dev; + SSIBus *bus[2]; +} stellaris_ssi_bus_state; + +static void stellaris_ssi_bus_select(void *opaque, int irq, int level) +{ + stellaris_ssi_bus_state *s = (stellaris_ssi_bus_state *)opaque; + + s->current_dev = level; +} + +static uint32_t stellaris_ssi_bus_transfer(SSISlave *dev, uint32_t val) +{ + stellaris_ssi_bus_state *s = FROM_SSI_SLAVE(stellaris_ssi_bus_state, dev); + + return ssi_transfer(s->bus[s->current_dev], val); +} + +static void stellaris_ssi_bus_save(QEMUFile *f, void *opaque) +{ + stellaris_ssi_bus_state *s = (stellaris_ssi_bus_state *)opaque; + + qemu_put_be32(f, s->current_dev); +} + +static int stellaris_ssi_bus_load(QEMUFile *f, void *opaque, int version_id) +{ + stellaris_ssi_bus_state *s = (stellaris_ssi_bus_state *)opaque; + + if (version_id != 1) + return -EINVAL; + + s->current_dev = qemu_get_be32(f); + + return 0; +} + +static int stellaris_ssi_bus_init(SSISlave *dev) +{ + stellaris_ssi_bus_state *s = FROM_SSI_SLAVE(stellaris_ssi_bus_state, dev); + + s->bus[0] = ssi_create_bus(&dev->qdev, "ssi0"); + s->bus[1] = ssi_create_bus(&dev->qdev, "ssi1"); + qdev_init_gpio_in(&dev->qdev, stellaris_ssi_bus_select, 1); + + register_savevm(&dev->qdev, "stellaris_ssi_bus", -1, 1, + stellaris_ssi_bus_save, stellaris_ssi_bus_load, 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; + i2c_bus *i2c; + DeviceState *dev; + int i; + int j; + + flash_size = ((board->dc0 & 0xffff) + 1) << 1; + sram_size = (board->dc0 >> 18) + 1; + pic = armv7m_init(flash_size, sram_size, kernel_filename, cpu_model); + + if (board->dc1 & (1 << 16)) { + dev = sysbus_create_varargs("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("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); + + for (i = 0; i < 7; i++) { + if (board->dc4 & (1 << i)) { + gpio_dev[i] = sysbus_create_simple("pl061", 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("stellaris-i2c", 0x40020000, pic[8]); + i2c = (i2c_bus *)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) { + DeviceState *mux; + void *bus; + + bus = qdev_get_child_bus(dev, "ssi"); + mux = ssi_create_slave(bus, "evb6965-ssi"); + gpio_out[GPIO_D][0] = qdev_get_gpio_in(mux, 0); + + bus = qdev_get_child_bus(mux, "ssi0"); + ssi_create_slave(bus, "ssi-sd"); + + bus = qdev_get_child_bus(mux, "ssi1"); + dev = ssi_create_slave(bus, "ssd0323"); + gpio_out[GPIO_C][7] = qdev_get_gpio_in(dev, 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(sysbus_from_qdev(enet), 0, 0x40048000); + sysbus_connect_irq(sysbus_from_qdev(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(ram_addr_t ram_size, + const char *boot_device, + const char *kernel_filename, const char *kernel_cmdline, + const char *initrd_filename, const char *cpu_model) +{ + stellaris_init(kernel_filename, cpu_model, &stellaris_boards[0]); +} + +static void lm3s6965evb_init(ram_addr_t ram_size, + const char *boot_device, + const char *kernel_filename, const char *kernel_cmdline, + const char *initrd_filename, const char *cpu_model) +{ + 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 SSISlaveInfo stellaris_ssi_bus_info = { + .qdev.name = "evb6965-ssi", + .qdev.size = sizeof(stellaris_ssi_bus_state), + .init = stellaris_ssi_bus_init, + .transfer = stellaris_ssi_bus_transfer +}; + +static void stellaris_register_devices(void) +{ + sysbus_register_dev("stellaris-i2c", sizeof(stellaris_i2c_state), + stellaris_i2c_init); + sysbus_register_dev("stellaris-gptm", sizeof(gptm_state), + stellaris_gptm_init); + sysbus_register_dev("stellaris-adc", sizeof(stellaris_adc_state), + stellaris_adc_init); + ssi_register_slave(&stellaris_ssi_bus_info); +} + +device_init(stellaris_register_devices) diff --git a/hw/stellaris_enet.c b/hw/stellaris_enet.c new file mode 100644 index 0000000..6a0583a --- /dev/null +++ b/hw/stellaris_enet.c @@ -0,0 +1,443 @@ +/* + * Luminary Micro Stellaris Ethernet Controller + * + * Copyright (c) 2007 CodeSourcery. + * Written by Paul Brook + * + * This code is licenced under the GPL. + */ +#include "sysbus.h" +#include "net.h" +#include + +//#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 + +typedef struct { + SysBusDevice busdev; + 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; + int tx_frame_len; + int 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. */ + struct { + uint8_t data[2048]; + int len; + } rx[31]; + uint8_t *rx_fifo; + int rx_fifo_len; + int next_packet; + NICState *nic; + NICConf conf; + qemu_irq irq; + int mmio_index; +} stellaris_enet_state; + +static void stellaris_enet_update(stellaris_enet_state *s) +{ + qemu_set_irq(s->irq, (s->ris & s->im) != 0); +} + +/* TODO: Implement MAC address filtering. */ +static ssize_t stellaris_enet_receive(VLANClientState *nc, const uint8_t *buf, size_t size) +{ + stellaris_enet_state *s = DO_UPCAST(NICState, nc, nc)->opaque; + int n; + uint8_t *p; + uint32_t crc; + + if ((s->rctl & SE_RCTL_RXEN) == 0) + return -1; + if (s->np >= 31) { + DPRINTF("Packet dropped\n"); + return -1; + } + + DPRINTF("Received packet len=%d\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(VLANClientState *nc) +{ + stellaris_enet_state *s = DO_UPCAST(NICState, nc, nc)->opaque; + + if ((s->rctl & SE_RCTL_RXEN) == 0) + return 1; + + return (s->np < 31); +} + +static uint32_t stellaris_enet_read(void *opaque, target_phys_addr_t offset) +{ + 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 */ + if (s->rx_fifo_len == 0) { + if (s->np == 0) { + BADF("RX underflow\n"); + return 0; + } + s->rx_fifo_len = s->rx[s->next_packet].len; + s->rx_fifo = s->rx[s->next_packet].data; + DPRINTF("RX FIFO start packet len=%d\n", s->rx_fifo_len); + } + val = s->rx_fifo[0] | (s->rx_fifo[1] << 8) | (s->rx_fifo[2] << 16) + | (s->rx_fifo[3] << 24); + s->rx_fifo += 4; + s->rx_fifo_len -= 4; + if (s->rx_fifo_len <= 0) { + s->rx_fifo_len = 0; + s->next_packet++; + if (s->next_packet >= 31) + s->next_packet = 0; + s->np--; + DPRINTF("RX done np=%d\n", s->np); + } + return val; + case 0x14: /* IA0 */ + 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 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, target_phys_addr_t offset, + uint32_t value) +{ + stellaris_enet_state *s = (stellaris_enet_state *)opaque; + + switch (offset) { + case 0x00: /* IACK */ + s->ris &= ~value; + DPRINTF("IRQ ack %02x/%02x\n", value, s->ris); + stellaris_enet_update(s); + /* Clearing TXER also resets the TX fifo. */ + if (value & SE_INT_TXER) + s->tx_frame_len = -1; + break; + case 0x04: /* IM */ + DPRINTF("IRQ mask %02x/%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->rx_fifo_len = 0; + s->np = 0; + stellaris_enet_update(s); + } + break; + case 0x0c: /* TCTL */ + s->tctl = value; + break; + case 0x10: /* DATA */ + if (s->tx_frame_len == -1) { + s->tx_frame_len = value & 0xffff; + if (s->tx_frame_len > 2032) { + DPRINTF("TX frame too long (%d)\n", s->tx_frame_len); + s->tx_frame_len = 0; + s->ris |= SE_INT_TXER; + stellaris_enet_update(s); + } else { + DPRINTF("Start TX frame len=%d\n", s->tx_frame_len); + /* The value written does not include the ethernet header. */ + s->tx_frame_len += 14; + if ((s->tctl & SE_TCTL_CRC) == 0) + s->tx_frame_len += 4; + s->tx_fifo_len = 0; + s->tx_fifo[s->tx_fifo_len++] = value >> 16; + s->tx_fifo[s->tx_fifo_len++] = value >> 24; + } + } else { + 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 (s->tx_fifo_len >= s->tx_frame_len) { + /* We don't implement explicit CRC, so just chop it off. */ + if ((s->tctl & SE_TCTL_CRC) == 0) + s->tx_frame_len -= 4; + if ((s->tctl & SE_TCTL_PADEN) && s->tx_frame_len < 60) { + memset(&s->tx_fifo[s->tx_frame_len], 0, 60 - s->tx_frame_len); + s->tx_fifo_len = 60; + } + qemu_send_packet(&s->nic->nc, s->tx_fifo, s->tx_frame_len); + s->tx_frame_len = -1; + s->ris |= SE_INT_TXEMP; + stellaris_enet_update(s); + DPRINTF("Done TX\n"); + } + } + 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 0x30: /* MRXD */ + case 0x34: /* NP */ + case 0x38: /* TR */ + /* Ignored. */ + case 0x3c: /* Undocuented: Timestamp? */ + /* Ignored. */ + break; + default: + hw_error("stellaris_enet_write: Bad offset %x\n", (int)offset); + } +} + +static CPUReadMemoryFunc * const stellaris_enet_readfn[] = { + stellaris_enet_read, + stellaris_enet_read, + stellaris_enet_read +}; + +static CPUWriteMemoryFunc * const stellaris_enet_writefn[] = { + stellaris_enet_write, + stellaris_enet_write, + stellaris_enet_write +}; +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_frame_len = -1; +} + +static void stellaris_enet_save(QEMUFile *f, void *opaque) +{ + stellaris_enet_state *s = (stellaris_enet_state *)opaque; + int i; + + qemu_put_be32(f, s->ris); + qemu_put_be32(f, s->im); + qemu_put_be32(f, s->rctl); + qemu_put_be32(f, s->tctl); + qemu_put_be32(f, s->thr); + qemu_put_be32(f, s->mctl); + qemu_put_be32(f, s->mdv); + qemu_put_be32(f, s->mtxd); + qemu_put_be32(f, s->mrxd); + qemu_put_be32(f, s->np); + qemu_put_be32(f, s->tx_frame_len); + qemu_put_be32(f, s->tx_fifo_len); + qemu_put_buffer(f, s->tx_fifo, sizeof(s->tx_fifo)); + for (i = 0; i < 31; i++) { + qemu_put_be32(f, s->rx[i].len); + qemu_put_buffer(f, s->rx[i].data, sizeof(s->rx[i].data)); + + } + qemu_put_be32(f, s->next_packet); + qemu_put_be32(f, s->rx_fifo - s->rx[s->next_packet].data); + qemu_put_be32(f, s->rx_fifo_len); +} + +static int stellaris_enet_load(QEMUFile *f, void *opaque, int version_id) +{ + stellaris_enet_state *s = (stellaris_enet_state *)opaque; + int i; + + if (version_id != 1) + return -EINVAL; + + s->ris = qemu_get_be32(f); + s->im = qemu_get_be32(f); + s->rctl = qemu_get_be32(f); + s->tctl = qemu_get_be32(f); + s->thr = qemu_get_be32(f); + s->mctl = qemu_get_be32(f); + s->mdv = qemu_get_be32(f); + s->mtxd = qemu_get_be32(f); + s->mrxd = qemu_get_be32(f); + s->np = qemu_get_be32(f); + s->tx_frame_len = qemu_get_be32(f); + s->tx_fifo_len = qemu_get_be32(f); + qemu_get_buffer(f, s->tx_fifo, sizeof(s->tx_fifo)); + for (i = 0; i < 31; i++) { + s->rx[i].len = qemu_get_be32(f); + qemu_get_buffer(f, s->rx[i].data, sizeof(s->rx[i].data)); + + } + s->next_packet = qemu_get_be32(f); + s->rx_fifo = s->rx[s->next_packet].data + qemu_get_be32(f); + s->rx_fifo_len = qemu_get_be32(f); + + return 0; +} + +static void stellaris_enet_cleanup(VLANClientState *nc) +{ + stellaris_enet_state *s = DO_UPCAST(NICState, nc, nc)->opaque; + + unregister_savevm(&s->busdev.qdev, "stellaris_enet", s); + + cpu_unregister_io_memory(s->mmio_index); + + qemu_free(s); +} + +static NetClientInfo net_stellaris_enet_info = { + .type = NET_CLIENT_TYPE_NIC, + .size = sizeof(NICState), + .can_receive = stellaris_enet_can_receive, + .receive = stellaris_enet_receive, + .cleanup = stellaris_enet_cleanup, +}; + +static int stellaris_enet_init(SysBusDevice *dev) +{ + stellaris_enet_state *s = FROM_SYSBUS(stellaris_enet_state, dev); + + s->mmio_index = cpu_register_io_memory(stellaris_enet_readfn, + stellaris_enet_writefn, s, + DEVICE_NATIVE_ENDIAN); + sysbus_init_mmio(dev, 0x1000, s->mmio_index); + sysbus_init_irq(dev, &s->irq); + qemu_macaddr_default_if_unset(&s->conf.macaddr); + + s->nic = qemu_new_nic(&net_stellaris_enet_info, &s->conf, + dev->qdev.info->name, dev->qdev.id, s); + qemu_format_nic_info_str(&s->nic->nc, s->conf.macaddr.a); + + stellaris_enet_reset(s); + register_savevm(&s->busdev.qdev, "stellaris_enet", -1, 1, + stellaris_enet_save, stellaris_enet_load, s); + return 0; +} + +static SysBusDeviceInfo stellaris_enet_info = { + .init = stellaris_enet_init, + .qdev.name = "stellaris_enet", + .qdev.size = sizeof(stellaris_enet_state), + .qdev.props = (Property[]) { + DEFINE_NIC_PROPERTIES(stellaris_enet_state, conf), + DEFINE_PROP_END_OF_LIST(), + } +}; + +static void stellaris_enet_register_devices(void) +{ + sysbus_register_withprop(&stellaris_enet_info); +} + +device_init(stellaris_enet_register_devices) diff --git a/hw/stellaris_input.c b/hw/stellaris_input.c new file mode 100644 index 0000000..16aae96 --- /dev/null +++ b/hw/stellaris_input.c @@ -0,0 +1,91 @@ +/* + * Gamepad style buttons connected to IRQ/GPIO lines + * + * Copyright (c) 2007 CodeSourcery. + * Written by Paul Brook + * + * This code is licenced under the GPL. + */ +#include "hw.h" +#include "devices.h" +#include "console.h" + +typedef struct { + qemu_irq irq; + int keycode; + int 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 void stellaris_gamepad_save(QEMUFile *f, void *opaque) +{ + gamepad_state *s = (gamepad_state *)opaque; + int i; + + qemu_put_be32(f, s->extension); + for (i = 0; i < s->num_buttons; i++) + qemu_put_byte(f, s->buttons[i].pressed); +} + +static int stellaris_gamepad_load(QEMUFile *f, void *opaque, int version_id) +{ + gamepad_state *s = (gamepad_state *)opaque; + int i; + + if (version_id != 1) + return -EINVAL; + + s->extension = qemu_get_be32(f); + for (i = 0; i < s->num_buttons; i++) + s->buttons[i].pressed = qemu_get_byte(f); + + return 0; +} + +/* 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 *)qemu_mallocz(sizeof (gamepad_state)); + s->buttons = (gamepad_button *)qemu_mallocz(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); + register_savevm(NULL, "stellaris_gamepad", -1, 1, + stellaris_gamepad_save, stellaris_gamepad_load, s); +} diff --git a/hw/sun4c_intctl.c b/hw/sun4c_intctl.c new file mode 100644 index 0000000..5c7fdef --- /dev/null +++ b/hw/sun4c_intctl.c @@ -0,0 +1,224 @@ +/* + * QEMU Sparc Sun4c interrupt controller emulation + * + * Based on slavio_intctl, 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.h" +#include "sun4m.h" +#include "monitor.h" +#include "sysbus.h" + +//#define DEBUG_IRQ_COUNT +//#define DEBUG_IRQ + +#ifdef DEBUG_IRQ +#define DPRINTF(fmt, ...) \ + do { printf("IRQ: " fmt , ## __VA_ARGS__); } while (0) +#else +#define DPRINTF(fmt, ...) +#endif + +/* + * Registers of interrupt controller in sun4c. + * + */ + +#define MAX_PILS 16 + +typedef struct Sun4c_INTCTLState { + SysBusDevice busdev; +#ifdef DEBUG_IRQ_COUNT + uint64_t irq_count; +#endif + qemu_irq cpu_irqs[MAX_PILS]; + const uint32_t *intbit_to_level; + uint32_t pil_out; + uint8_t reg; + uint8_t pending; +} Sun4c_INTCTLState; + +#define INTCTL_SIZE 1 + +static void sun4c_check_interrupts(void *opaque); + +static uint32_t sun4c_intctl_mem_readb(void *opaque, target_phys_addr_t addr) +{ + Sun4c_INTCTLState *s = opaque; + uint32_t ret; + + ret = s->reg; + DPRINTF("read reg 0x" TARGET_FMT_plx " = %x\n", addr, ret); + + return ret; +} + +static void sun4c_intctl_mem_writeb(void *opaque, target_phys_addr_t addr, + uint32_t val) +{ + Sun4c_INTCTLState *s = opaque; + + DPRINTF("write reg 0x" TARGET_FMT_plx " = %x\n", addr, val); + val &= 0xbf; + s->reg = val; + sun4c_check_interrupts(s); +} + +static CPUReadMemoryFunc * const sun4c_intctl_mem_read[3] = { + sun4c_intctl_mem_readb, + NULL, + NULL, +}; + +static CPUWriteMemoryFunc * const sun4c_intctl_mem_write[3] = { + sun4c_intctl_mem_writeb, + NULL, + NULL, +}; + +void sun4c_pic_info(Monitor *mon, void *opaque) +{ + Sun4c_INTCTLState *s = opaque; + + monitor_printf(mon, "master: pending 0x%2.2x, enabled 0x%2.2x\n", + s->pending, s->reg); +} + +void sun4c_irq_info(Monitor *mon, void *opaque) +{ +#ifndef DEBUG_IRQ_COUNT + monitor_printf(mon, "irq statistic code not compiled.\n"); +#else + Sun4c_INTCTLState *s = opaque; + int64_t count; + + monitor_printf(mon, "IRQ statistics:\n"); + count = s->irq_count; + if (count > 0) + monitor_printf(mon, " %" PRId64 "\n", count); +#endif +} + +static const uint32_t intbit_to_level[] = { 0, 1, 4, 6, 8, 10, 0, 14, }; + +static void sun4c_check_interrupts(void *opaque) +{ + Sun4c_INTCTLState *s = opaque; + uint32_t pil_pending; + unsigned int i; + + pil_pending = 0; + if (s->pending && !(s->reg & 0x80000000)) { + for (i = 0; i < 8; i++) { + if (s->pending & (1 << i)) + pil_pending |= 1 << intbit_to_level[i]; + } + } + + for (i = 0; i < MAX_PILS; i++) { + if (pil_pending & (1 << i)) { + if (!(s->pil_out & (1 << i))) + qemu_irq_raise(s->cpu_irqs[i]); + } else { + if (s->pil_out & (1 << i)) + qemu_irq_lower(s->cpu_irqs[i]); + } + } + s->pil_out = pil_pending; +} + +/* + * "irq" here is the bit number in the system interrupt register + */ +static void sun4c_set_irq(void *opaque, int irq, int level) +{ + Sun4c_INTCTLState *s = opaque; + uint32_t mask = 1 << irq; + uint32_t pil = intbit_to_level[irq]; + + DPRINTF("Set irq %d -> pil %d level %d\n", irq, pil, + level); + if (pil > 0) { + if (level) { +#ifdef DEBUG_IRQ_COUNT + s->irq_count++; +#endif + s->pending |= mask; + } else { + s->pending &= ~mask; + } + sun4c_check_interrupts(s); + } +} + +static const VMStateDescription vmstate_sun4c_intctl = { + .name ="sun4c_intctl", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField []) { + VMSTATE_UINT8(reg, Sun4c_INTCTLState), + VMSTATE_UINT8(pending, Sun4c_INTCTLState), + VMSTATE_END_OF_LIST() + } +}; + +static void sun4c_intctl_reset(DeviceState *d) +{ + Sun4c_INTCTLState *s = container_of(d, Sun4c_INTCTLState, busdev.qdev); + + s->reg = 1; + s->pending = 0; +} + +static int sun4c_intctl_init1(SysBusDevice *dev) +{ + Sun4c_INTCTLState *s = FROM_SYSBUS(Sun4c_INTCTLState, dev); + int io_memory; + unsigned int i; + + io_memory = cpu_register_io_memory(sun4c_intctl_mem_read, + sun4c_intctl_mem_write, s, + DEVICE_NATIVE_ENDIAN); + sysbus_init_mmio(dev, INTCTL_SIZE, io_memory); + qdev_init_gpio_in(&dev->qdev, sun4c_set_irq, 8); + + for (i = 0; i < MAX_PILS; i++) { + sysbus_init_irq(dev, &s->cpu_irqs[i]); + } + + return 0; +} + +static SysBusDeviceInfo sun4c_intctl_info = { + .init = sun4c_intctl_init1, + .qdev.name = "sun4c_intctl", + .qdev.size = sizeof(Sun4c_INTCTLState), + .qdev.vmsd = &vmstate_sun4c_intctl, + .qdev.reset = sun4c_intctl_reset, +}; + +static void sun4c_intctl_register_devices(void) +{ + sysbus_register_withprop(&sun4c_intctl_info); +} + +device_init(sun4c_intctl_register_devices) diff --git a/hw/sun4m.c b/hw/sun4m.c new file mode 100644 index 0000000..30e8a21 --- /dev/null +++ b/hw/sun4m.c @@ -0,0 +1,1821 @@ +/* + * 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 "sysbus.h" +#include "qemu-timer.h" +#include "sun4m.h" +#include "nvram.h" +#include "sparc32_dma.h" +#include "fdc.h" +#include "sysemu.h" +#include "net.h" +#include "boards.h" +#include "firmware_abi.h" +#include "esp.h" +#include "pc.h" +#include "isa.h" +#include "fw_cfg.h" +#include "escc.h" +#include "empty_slot.h" +#include "qdev-addr.h" +#include "loader.h" +#include "elf.h" +#include "blockdev.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 + * + * Sun4d architecture was used in the following machines: + * + * SPARCcenter 2000 + * SPARCserver 1000 + * + * Sun4c architecture was used in the following machines: + * SPARCstation 1/1+, SPARCserver 1/1+ + * SPARCstation SLC + * SPARCstation IPC + * SPARCstation ELC + * SPARCstation IPX + * + * 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 MAX_CPUS 16 +#define MAX_PILS 16 +#define MAX_VSIMMS 4 + +#define ESCC_CLOCK 4915200 + +struct sun4m_hwdef { + target_phys_addr_t iommu_base, iommu_pad_base, iommu_pad_len, slavio_base; + target_phys_addr_t intctl_base, counter_base, nvram_base, ms_kb_base; + target_phys_addr_t serial_base, fd_base; + target_phys_addr_t afx_base, idreg_base, dma_base, esp_base, le_base; + target_phys_addr_t tcx_base, cs_base, apc_base, aux1_base, aux2_base; + target_phys_addr_t bpp_base, dbri_base, sx_base; + struct { + target_phys_addr_t reg_base, vram_base; + } vsimm[MAX_VSIMMS]; + target_phys_addr_t ecc_base; + uint32_t ecc_version; + uint8_t nvram_machine_id; + uint16_t machine_id; + uint32_t iommu_version; + uint64_t max_mem; + const char * const default_cpu_model; +}; + +#define MAX_IOUNITS 5 + +struct sun4d_hwdef { + target_phys_addr_t iounit_bases[MAX_IOUNITS], slavio_base; + target_phys_addr_t counter_base, nvram_base, ms_kb_base; + target_phys_addr_t serial_base; + target_phys_addr_t espdma_base, esp_base; + target_phys_addr_t ledma_base, le_base; + target_phys_addr_t tcx_base; + target_phys_addr_t sbi_base; + uint8_t nvram_machine_id; + uint16_t machine_id; + uint32_t iounit_version; + uint64_t max_mem; + const char * const default_cpu_model; +}; + +struct sun4c_hwdef { + target_phys_addr_t iommu_base, slavio_base; + target_phys_addr_t intctl_base, counter_base, nvram_base, ms_kb_base; + target_phys_addr_t serial_base, fd_base; + target_phys_addr_t idreg_base, dma_base, esp_base, le_base; + target_phys_addr_t tcx_base, aux1_base; + uint8_t nvram_machine_id; + uint16_t machine_id; + uint32_t iommu_version; + uint64_t max_mem; + const char * const default_cpu_model; +}; + +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 int fw_cfg_boot_set(void *opaque, const char *boot_device) +{ + fw_cfg_add_i16(opaque, FW_CFG_BOOT_DEVICE, boot_device[0]); + return 0; +} + +static void nvram_init(M48t59State *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; + + 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++) + m48t59_write(nvram, i, image[i]); +} + +static DeviceState *slavio_intctl; + +void pic_info(Monitor *mon) +{ + if (slavio_intctl) + slavio_pic_info(mon, slavio_intctl); +} + +void irq_info(Monitor *mon) +{ + if (slavio_intctl) + slavio_irq_info(mon, slavio_intctl); +} + +void cpu_check_irqs(CPUState *env) +{ + 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) { + trace_sun4m_cpu_interrupt(i); + cpu_interrupt(env, CPU_INTERRUPT_HARD); + } + break; + } + } + } else if (!env->pil_in && (env->interrupt_index & ~15) == TT_EXTINT) { + trace_sun4m_cpu_reset_interrupt(env->interrupt_index & 15); + env->interrupt_index = 0; + cpu_reset_interrupt(env, CPU_INTERRUPT_HARD); + } +} + +static void cpu_set_irq(void *opaque, int irq, int level) +{ + CPUState *env = opaque; + + if (level) { + trace_sun4m_cpu_set_irq_raise(irq); + env->halted = 0; + env->pil_in |= 1 << irq; + cpu_check_irqs(env); + } 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) +{ + CPUState *env = opaque; + + cpu_reset(env); + env->halted = 0; +} + +static void secondary_cpu_reset(void *opaque) +{ + CPUState *env = opaque; + + cpu_reset(env); + env->halted = 1; +} + +static void cpu_halt_signal(void *opaque, int irq, int level) +{ + if (level && cpu_single_env) + cpu_interrupt(cpu_single_env, 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(target_phys_addr_t 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 = sysbus_from_qdev(dev); + sysbus_connect_irq(s, 0, irq); + sysbus_mmio_map(s, 0, addr); + + return s; +} + +static void *sparc32_dma_init(target_phys_addr_t 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 = sysbus_from_qdev(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, target_phys_addr_t 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 = sysbus_from_qdev(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(target_phys_addr_t addr, + target_phys_addr_t addrg, + qemu_irq **parent_irq) +{ + DeviceState *dev; + SysBusDevice *s; + unsigned int i, j; + + dev = qdev_create(NULL, "slavio_intctl"); + qdev_init_nofail(dev); + + s = sysbus_from_qdev(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(target_phys_addr_t 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 = sysbus_from_qdev(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 + (target_phys_addr_t)CPU_TIMER_OFFSET(i)); + sysbus_connect_irq(s, i + 1, cpu_irqs[i]); + } +} + +#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(target_phys_addr_t base, + target_phys_addr_t aux1_base, + target_phys_addr_t aux2_base, qemu_irq irq, + qemu_irq fdc_tc) +{ + DeviceState *dev; + SysBusDevice *s; + + dev = qdev_create(NULL, "slavio_misc"); + qdev_init_nofail(dev); + s = sysbus_from_qdev(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); + qemu_system_powerdown = qdev_get_gpio_in(dev, 0); +} + +static void ecc_init(target_phys_addr_t 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 = sysbus_from_qdev(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(target_phys_addr_t power_base, qemu_irq cpu_halt) +{ + DeviceState *dev; + SysBusDevice *s; + + dev = qdev_create(NULL, "apc"); + qdev_init_nofail(dev); + s = sysbus_from_qdev(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(target_phys_addr_t addr, int vram_size, int width, + int height, int depth) +{ + DeviceState *dev; + SysBusDevice *s; + + dev = qdev_create(NULL, "SUNW,tcx"); + qdev_prop_set_taddr(dev, "addr", addr); + 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_init_nofail(dev); + s = sysbus_from_qdev(dev); + /* 8-bit plane */ + sysbus_mmio_map(s, 0, addr + 0x00800000ULL); + /* DAC */ + sysbus_mmio_map(s, 1, addr + 0x00200000ULL); + /* TEC (dummy) */ + sysbus_mmio_map(s, 2, addr + 0x00700000ULL); + /* THC 24 bit: NetBSD writes here even with 8-bit display: dummy */ + sysbus_mmio_map(s, 3, addr + 0x00301000ULL); + if (depth == 24) { + /* 24-bit plane */ + sysbus_mmio_map(s, 4, addr + 0x02000000ULL); + /* Control plane */ + sysbus_mmio_map(s, 5, addr + 0x0a000000ULL); + } else { + /* THC 8 bit (dummy) */ + sysbus_mmio_map(s, 4, addr + 0x00300000ULL); + } +} + +/* NCR89C100/MACIO Internal ID register */ +static const uint8_t idreg_data[] = { 0xfe, 0x81, 0x01, 0x03 }; + +static void idreg_init(target_phys_addr_t addr) +{ + DeviceState *dev; + SysBusDevice *s; + + dev = qdev_create(NULL, "macio_idreg"); + qdev_init_nofail(dev); + s = sysbus_from_qdev(dev); + + sysbus_mmio_map(s, 0, addr); + cpu_physical_memory_write_rom(addr, idreg_data, sizeof(idreg_data)); +} + +static int idreg_init1(SysBusDevice *dev) +{ + ram_addr_t idreg_offset; + + idreg_offset = qemu_ram_alloc(NULL, "sun4m.idreg", sizeof(idreg_data)); + sysbus_init_mmio(dev, sizeof(idreg_data), idreg_offset | IO_MEM_ROM); + return 0; +} + +static SysBusDeviceInfo idreg_info = { + .init = idreg_init1, + .qdev.name = "macio_idreg", + .qdev.size = sizeof(SysBusDevice), +}; + +static void idreg_register_devices(void) +{ + sysbus_register_withprop(&idreg_info); +} + +device_init(idreg_register_devices); + +/* SS-5 TCX AFX register */ +static void afx_init(target_phys_addr_t addr) +{ + DeviceState *dev; + SysBusDevice *s; + + dev = qdev_create(NULL, "tcx_afx"); + qdev_init_nofail(dev); + s = sysbus_from_qdev(dev); + + sysbus_mmio_map(s, 0, addr); +} + +static int afx_init1(SysBusDevice *dev) +{ + ram_addr_t afx_offset; + + afx_offset = qemu_ram_alloc(NULL, "sun4m.afx", 4); + sysbus_init_mmio(dev, 4, afx_offset | IO_MEM_RAM); + return 0; +} + +static SysBusDeviceInfo afx_info = { + .init = afx_init1, + .qdev.name = "tcx_afx", + .qdev.size = sizeof(SysBusDevice), +}; + +static void afx_register_devices(void) +{ + sysbus_register_withprop(&afx_info); +} + +device_init(afx_register_devices); + +/* Boot PROM (OpenBIOS) */ +static uint64_t translate_prom_address(void *opaque, uint64_t addr) +{ + target_phys_addr_t *base_addr = (target_phys_addr_t *)opaque; + return addr + *base_addr - PROM_VADDR; +} + +static void prom_init(target_phys_addr_t addr, const char *bios_name) +{ + DeviceState *dev; + SysBusDevice *s; + char *filename; + int ret; + + dev = qdev_create(NULL, "openprom"); + qdev_init_nofail(dev); + s = sysbus_from_qdev(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); + } + qemu_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) +{ + ram_addr_t prom_offset; + + prom_offset = qemu_ram_alloc(NULL, "sun4m.prom", PROM_SIZE_MAX); + sysbus_init_mmio(dev, PROM_SIZE_MAX, prom_offset | IO_MEM_ROM); + return 0; +} + +static SysBusDeviceInfo prom_info = { + .init = prom_init1, + .qdev.name = "openprom", + .qdev.size = sizeof(SysBusDevice), + .qdev.props = (Property[]) { + {/* end of property list */} + } +}; + +static void prom_register_devices(void) +{ + sysbus_register_withprop(&prom_info); +} + +device_init(prom_register_devices); + +typedef struct RamDevice +{ + SysBusDevice busdev; + uint64_t size; +} RamDevice; + +/* System RAM */ +static int ram_init1(SysBusDevice *dev) +{ + ram_addr_t RAM_size, ram_offset; + RamDevice *d = FROM_SYSBUS(RamDevice, dev); + + RAM_size = d->size; + + ram_offset = qemu_ram_alloc(NULL, "sun4m.ram", RAM_size); + sysbus_init_mmio(dev, RAM_size, ram_offset); + return 0; +} + +static void ram_init(target_phys_addr_t 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 = sysbus_from_qdev(dev); + + d = FROM_SYSBUS(RamDevice, s); + d->size = RAM_size; + qdev_init_nofail(dev); + + sysbus_mmio_map(s, 0, addr); +} + +static SysBusDeviceInfo ram_info = { + .init = ram_init1, + .qdev.name = "memory", + .qdev.size = sizeof(RamDevice), + .qdev.props = (Property[]) { + DEFINE_PROP_UINT64("size", RamDevice, size, 0), + DEFINE_PROP_END_OF_LIST(), + } +}; + +static void ram_register_devices(void) +{ + sysbus_register_withprop(&ram_info); +} + +device_init(ram_register_devices); + +static void cpu_devinit(const char *cpu_model, unsigned int id, + uint64_t prom_addr, qemu_irq **cpu_irqs) +{ + CPUState *env; + + env = cpu_init(cpu_model); + if (!env) { + fprintf(stderr, "qemu: Unable to find Sparc CPU definition\n"); + exit(1); + } + + cpu_sparc_set_id(env, id); + if (id == 0) { + qemu_register_reset(main_cpu_reset, env); + } else { + qemu_register_reset(secondary_cpu_reset, env); + env->halted = 1; + } + *cpu_irqs = qemu_allocate_irqs(cpu_set_irq, env, MAX_PILS); + env->prom_addr = prom_addr; +} + +static void sun4m_hw_init(const struct sun4m_hwdef *hwdef, ram_addr_t RAM_size, + const char *boot_device, + const char *kernel_filename, + const char *kernel_cmdline, + const char *initrd_filename, const char *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; + qemu_irq *cpu_halt; + unsigned long kernel_size; + DriveInfo *fd[MAX_FD]; + void *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, RAM_size, hwdef->max_mem); + /* models without ECC don't trap when missing ram is accessed */ + if (!hwdef->ecc_base) { + empty_slot_init(RAM_size, hwdef->max_mem - 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) { + fprintf(stderr, "qemu: Unsupported depth: %d\n", graphic_depth); + exit (1); + } + num_vsimms = 0; + if (num_vsimms == 0) { + tcx_init(hwdef->tcx_base, 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, 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); + + cpu_halt = qemu_allocate_irqs(cpu_halt_signal, NULL, 1); + slavio_misc_init(hwdef->slavio_base, hwdef->aux1_base, hwdef->aux2_base, + slavio_irq[30], fdc_tc); + + if (hwdef->apc_base) { + apc_init(hwdef->apc_base, cpu_halt[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); + } + + 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(kernel_filename, initrd_filename, + RAM_size); + + nvram_init(nvram, (uint8_t *)&nd_table[0].macaddr, kernel_cmdline, + boot_device, 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(0, 0, CFG_ADDR, CFG_ADDR + 2); + fw_cfg_add_i32(fw_cfg, FW_CFG_ID, 1); + 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_i32(fw_cfg, FW_CFG_KERNEL_ADDR, KERNEL_LOAD_ADDR); + 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_ADDR); + pstrcpy_targphys("cmdline", CMDLINE_ADDR, TARGET_PAGE_SIZE, kernel_cmdline); + fw_cfg_add_bytes(fw_cfg, FW_CFG_CMDLINE_DATA, + (uint8_t*)strdup(kernel_cmdline), + strlen(kernel_cmdline) + 1); + fw_cfg_add_i32(fw_cfg, FW_CFG_CMDLINE_SIZE, + strlen(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, boot_device[0]); + qemu_register_boot_set(fw_cfg_boot_set, fw_cfg); +} + +enum { + ss2_id = 0, + ss5_id = 32, + vger_id, + lx_id, + ss4_id, + scls_id, + sbook_id, + ss10_id = 64, + ss20_id, + ss600mp_id, + ss1000_id = 96, + ss2000_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(ram_addr_t RAM_size, + const char *boot_device, + const char *kernel_filename, const char *kernel_cmdline, + const char *initrd_filename, const char *cpu_model) +{ + sun4m_hw_init(&sun4m_hwdefs[0], RAM_size, boot_device, kernel_filename, + kernel_cmdline, initrd_filename, cpu_model); +} + +/* SPARCstation 10 hardware initialisation */ +static void ss10_init(ram_addr_t RAM_size, + const char *boot_device, + const char *kernel_filename, const char *kernel_cmdline, + const char *initrd_filename, const char *cpu_model) +{ + sun4m_hw_init(&sun4m_hwdefs[1], RAM_size, boot_device, kernel_filename, + kernel_cmdline, initrd_filename, cpu_model); +} + +/* SPARCserver 600MP hardware initialisation */ +static void ss600mp_init(ram_addr_t RAM_size, + const char *boot_device, + const char *kernel_filename, + const char *kernel_cmdline, + const char *initrd_filename, const char *cpu_model) +{ + sun4m_hw_init(&sun4m_hwdefs[2], RAM_size, boot_device, kernel_filename, + kernel_cmdline, initrd_filename, cpu_model); +} + +/* SPARCstation 20 hardware initialisation */ +static void ss20_init(ram_addr_t RAM_size, + const char *boot_device, + const char *kernel_filename, const char *kernel_cmdline, + const char *initrd_filename, const char *cpu_model) +{ + sun4m_hw_init(&sun4m_hwdefs[3], RAM_size, boot_device, kernel_filename, + kernel_cmdline, initrd_filename, cpu_model); +} + +/* SPARCstation Voyager hardware initialisation */ +static void vger_init(ram_addr_t RAM_size, + const char *boot_device, + const char *kernel_filename, const char *kernel_cmdline, + const char *initrd_filename, const char *cpu_model) +{ + sun4m_hw_init(&sun4m_hwdefs[4], RAM_size, boot_device, kernel_filename, + kernel_cmdline, initrd_filename, cpu_model); +} + +/* SPARCstation LX hardware initialisation */ +static void ss_lx_init(ram_addr_t RAM_size, + const char *boot_device, + const char *kernel_filename, const char *kernel_cmdline, + const char *initrd_filename, const char *cpu_model) +{ + sun4m_hw_init(&sun4m_hwdefs[5], RAM_size, boot_device, kernel_filename, + kernel_cmdline, initrd_filename, cpu_model); +} + +/* SPARCstation 4 hardware initialisation */ +static void ss4_init(ram_addr_t RAM_size, + const char *boot_device, + const char *kernel_filename, const char *kernel_cmdline, + const char *initrd_filename, const char *cpu_model) +{ + sun4m_hw_init(&sun4m_hwdefs[6], RAM_size, boot_device, kernel_filename, + kernel_cmdline, initrd_filename, cpu_model); +} + +/* SPARCClassic hardware initialisation */ +static void scls_init(ram_addr_t RAM_size, + const char *boot_device, + const char *kernel_filename, const char *kernel_cmdline, + const char *initrd_filename, const char *cpu_model) +{ + sun4m_hw_init(&sun4m_hwdefs[7], RAM_size, boot_device, kernel_filename, + kernel_cmdline, initrd_filename, cpu_model); +} + +/* SPARCbook hardware initialisation */ +static void sbook_init(ram_addr_t RAM_size, + const char *boot_device, + const char *kernel_filename, const char *kernel_cmdline, + const char *initrd_filename, const char *cpu_model) +{ + sun4m_hw_init(&sun4m_hwdefs[8], RAM_size, boot_device, kernel_filename, + kernel_cmdline, initrd_filename, cpu_model); +} + +static QEMUMachine ss5_machine = { + .name = "SS-5", + .desc = "Sun4m platform, SPARCstation 5", + .init = ss5_init, + .use_scsi = 1, + .is_default = 1, +}; + +static QEMUMachine ss10_machine = { + .name = "SS-10", + .desc = "Sun4m platform, SPARCstation 10", + .init = ss10_init, + .use_scsi = 1, + .max_cpus = 4, +}; + +static QEMUMachine ss600mp_machine = { + .name = "SS-600MP", + .desc = "Sun4m platform, SPARCserver 600MP", + .init = ss600mp_init, + .use_scsi = 1, + .max_cpus = 4, +}; + +static QEMUMachine ss20_machine = { + .name = "SS-20", + .desc = "Sun4m platform, SPARCstation 20", + .init = ss20_init, + .use_scsi = 1, + .max_cpus = 4, +}; + +static QEMUMachine voyager_machine = { + .name = "Voyager", + .desc = "Sun4m platform, SPARCstation Voyager", + .init = vger_init, + .use_scsi = 1, +}; + +static QEMUMachine ss_lx_machine = { + .name = "LX", + .desc = "Sun4m platform, SPARCstation LX", + .init = ss_lx_init, + .use_scsi = 1, +}; + +static QEMUMachine ss4_machine = { + .name = "SS-4", + .desc = "Sun4m platform, SPARCstation 4", + .init = ss4_init, + .use_scsi = 1, +}; + +static QEMUMachine scls_machine = { + .name = "SPARCClassic", + .desc = "Sun4m platform, SPARCClassic", + .init = scls_init, + .use_scsi = 1, +}; + +static QEMUMachine sbook_machine = { + .name = "SPARCbook", + .desc = "Sun4m platform, SPARCbook", + .init = sbook_init, + .use_scsi = 1, +}; + +static const struct sun4d_hwdef sun4d_hwdefs[] = { + /* SS-1000 */ + { + .iounit_bases = { + 0xfe0200000ULL, + 0xfe1200000ULL, + 0xfe2200000ULL, + 0xfe3200000ULL, + -1, + }, + .tcx_base = 0x820000000ULL, + .slavio_base = 0xf00000000ULL, + .ms_kb_base = 0xf00240000ULL, + .serial_base = 0xf00200000ULL, + .nvram_base = 0xf00280000ULL, + .counter_base = 0xf00300000ULL, + .espdma_base = 0x800081000ULL, + .esp_base = 0x800080000ULL, + .ledma_base = 0x800040000ULL, + .le_base = 0x800060000ULL, + .sbi_base = 0xf02800000ULL, + .nvram_machine_id = 0x80, + .machine_id = ss1000_id, + .iounit_version = 0x03000000, + .max_mem = 0xf00000000ULL, + .default_cpu_model = "TI SuperSparc II", + }, + /* SS-2000 */ + { + .iounit_bases = { + 0xfe0200000ULL, + 0xfe1200000ULL, + 0xfe2200000ULL, + 0xfe3200000ULL, + 0xfe4200000ULL, + }, + .tcx_base = 0x820000000ULL, + .slavio_base = 0xf00000000ULL, + .ms_kb_base = 0xf00240000ULL, + .serial_base = 0xf00200000ULL, + .nvram_base = 0xf00280000ULL, + .counter_base = 0xf00300000ULL, + .espdma_base = 0x800081000ULL, + .esp_base = 0x800080000ULL, + .ledma_base = 0x800040000ULL, + .le_base = 0x800060000ULL, + .sbi_base = 0xf02800000ULL, + .nvram_machine_id = 0x80, + .machine_id = ss2000_id, + .iounit_version = 0x03000000, + .max_mem = 0xf00000000ULL, + .default_cpu_model = "TI SuperSparc II", + }, +}; + +static DeviceState *sbi_init(target_phys_addr_t addr, qemu_irq **parent_irq) +{ + DeviceState *dev; + SysBusDevice *s; + unsigned int i; + + dev = qdev_create(NULL, "sbi"); + qdev_init_nofail(dev); + + s = sysbus_from_qdev(dev); + + for (i = 0; i < MAX_CPUS; i++) { + sysbus_connect_irq(s, i, *parent_irq[i]); + } + + sysbus_mmio_map(s, 0, addr); + + return dev; +} + +static void sun4d_hw_init(const struct sun4d_hwdef *hwdef, ram_addr_t RAM_size, + const char *boot_device, + const char *kernel_filename, + const char *kernel_cmdline, + const char *initrd_filename, const char *cpu_model) +{ + unsigned int i; + void *iounits[MAX_IOUNITS], *espdma, *ledma, *nvram; + qemu_irq *cpu_irqs[MAX_CPUS], sbi_irq[32], sbi_cpu_irq[MAX_CPUS], + espdma_irq, ledma_irq; + qemu_irq esp_reset, dma_enable; + unsigned long kernel_size; + void *fw_cfg; + DeviceState *dev; + + /* 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, RAM_size, hwdef->max_mem); + + prom_init(hwdef->slavio_base, bios_name); + + dev = sbi_init(hwdef->sbi_base, cpu_irqs); + + for (i = 0; i < 32; i++) { + sbi_irq[i] = qdev_get_gpio_in(dev, i); + } + for (i = 0; i < MAX_CPUS; i++) { + sbi_cpu_irq[i] = qdev_get_gpio_in(dev, 32 + i); + } + + for (i = 0; i < MAX_IOUNITS; i++) + if (hwdef->iounit_bases[i] != (target_phys_addr_t)-1) + iounits[i] = iommu_init(hwdef->iounit_bases[i], + hwdef->iounit_version, + sbi_irq[0]); + + espdma = sparc32_dma_init(hwdef->espdma_base, sbi_irq[3], + iounits[0], &espdma_irq, 0); + + /* should be lebuffer instead */ + ledma = sparc32_dma_init(hwdef->ledma_base, sbi_irq[4], + iounits[0], &ledma_irq, 0); + + if (graphic_depth != 8 && graphic_depth != 24) { + fprintf(stderr, "qemu: Unsupported depth: %d\n", graphic_depth); + exit (1); + } + tcx_init(hwdef->tcx_base, 0x00100000, graphic_width, graphic_height, + graphic_depth); + + lance_init(&nd_table[0], hwdef->le_base, ledma, ledma_irq); + + nvram = m48t59_init(sbi_irq[0], hwdef->nvram_base, 0, 0x2000, 8); + + slavio_timer_init_all(hwdef->counter_base, sbi_irq[10], sbi_cpu_irq, smp_cpus); + + slavio_serial_ms_kbd_init(hwdef->ms_kb_base, sbi_irq[12], + 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, sbi_irq[12], sbi_irq[12], + serial_hds[0], serial_hds[1], ESCC_CLOCK, 1); + + 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); + + kernel_size = sun4m_load_kernel(kernel_filename, initrd_filename, + RAM_size); + + nvram_init(nvram, (uint8_t *)&nd_table[0].macaddr, kernel_cmdline, + boot_device, RAM_size, kernel_size, graphic_width, + graphic_height, graphic_depth, hwdef->nvram_machine_id, + "Sun4d"); + + fw_cfg = fw_cfg_init(0, 0, CFG_ADDR, CFG_ADDR + 2); + fw_cfg_add_i32(fw_cfg, FW_CFG_ID, 1); + 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_i32(fw_cfg, FW_CFG_KERNEL_ADDR, KERNEL_LOAD_ADDR); + 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_ADDR); + pstrcpy_targphys("cmdline", CMDLINE_ADDR, TARGET_PAGE_SIZE, kernel_cmdline); + fw_cfg_add_bytes(fw_cfg, FW_CFG_CMDLINE_DATA, + (uint8_t*)strdup(kernel_cmdline), + strlen(kernel_cmdline) + 1); + } else { + fw_cfg_add_i32(fw_cfg, FW_CFG_KERNEL_CMDLINE, 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, boot_device[0]); + qemu_register_boot_set(fw_cfg_boot_set, fw_cfg); +} + +/* SPARCserver 1000 hardware initialisation */ +static void ss1000_init(ram_addr_t RAM_size, + const char *boot_device, + const char *kernel_filename, const char *kernel_cmdline, + const char *initrd_filename, const char *cpu_model) +{ + sun4d_hw_init(&sun4d_hwdefs[0], RAM_size, boot_device, kernel_filename, + kernel_cmdline, initrd_filename, cpu_model); +} + +/* SPARCcenter 2000 hardware initialisation */ +static void ss2000_init(ram_addr_t RAM_size, + const char *boot_device, + const char *kernel_filename, const char *kernel_cmdline, + const char *initrd_filename, const char *cpu_model) +{ + sun4d_hw_init(&sun4d_hwdefs[1], RAM_size, boot_device, kernel_filename, + kernel_cmdline, initrd_filename, cpu_model); +} + +static QEMUMachine ss1000_machine = { + .name = "SS-1000", + .desc = "Sun4d platform, SPARCserver 1000", + .init = ss1000_init, + .use_scsi = 1, + .max_cpus = 8, +}; + +static QEMUMachine ss2000_machine = { + .name = "SS-2000", + .desc = "Sun4d platform, SPARCcenter 2000", + .init = ss2000_init, + .use_scsi = 1, + .max_cpus = 20, +}; + +static const struct sun4c_hwdef sun4c_hwdefs[] = { + /* SS-2 */ + { + .iommu_base = 0xf8000000, + .tcx_base = 0xfe000000, + .slavio_base = 0xf6000000, + .intctl_base = 0xf5000000, + .counter_base = 0xf3000000, + .ms_kb_base = 0xf0000000, + .serial_base = 0xf1000000, + .nvram_base = 0xf2000000, + .fd_base = 0xf7200000, + .dma_base = 0xf8400000, + .esp_base = 0xf8800000, + .le_base = 0xf8c00000, + .aux1_base = 0xf7400003, + .nvram_machine_id = 0x55, + .machine_id = ss2_id, + .max_mem = 0x10000000, + .default_cpu_model = "Cypress CY7C601", + }, +}; + +static DeviceState *sun4c_intctl_init(target_phys_addr_t addr, + qemu_irq *parent_irq) +{ + DeviceState *dev; + SysBusDevice *s; + unsigned int i; + + dev = qdev_create(NULL, "sun4c_intctl"); + qdev_init_nofail(dev); + + s = sysbus_from_qdev(dev); + + for (i = 0; i < MAX_PILS; i++) { + sysbus_connect_irq(s, i, parent_irq[i]); + } + sysbus_mmio_map(s, 0, addr); + + return dev; +} + +static void sun4c_hw_init(const struct sun4c_hwdef *hwdef, ram_addr_t RAM_size, + const char *boot_device, + const char *kernel_filename, + const char *kernel_cmdline, + const char *initrd_filename, const char *cpu_model) +{ + void *iommu, *espdma, *ledma, *nvram; + qemu_irq *cpu_irqs, slavio_irq[8], espdma_irq, ledma_irq; + qemu_irq esp_reset, dma_enable; + qemu_irq fdc_tc; + unsigned long kernel_size; + DriveInfo *fd[MAX_FD]; + void *fw_cfg; + DeviceState *dev; + unsigned int i; + + /* init CPU */ + if (!cpu_model) + cpu_model = hwdef->default_cpu_model; + + cpu_devinit(cpu_model, 0, hwdef->slavio_base, &cpu_irqs); + + /* set up devices */ + ram_init(0, RAM_size, hwdef->max_mem); + + prom_init(hwdef->slavio_base, bios_name); + + dev = sun4c_intctl_init(hwdef->intctl_base, cpu_irqs); + + for (i = 0; i < 8; i++) { + slavio_irq[i] = qdev_get_gpio_in(dev, i); + } + + iommu = iommu_init(hwdef->iommu_base, hwdef->iommu_version, + slavio_irq[1]); + + espdma = sparc32_dma_init(hwdef->dma_base, slavio_irq[2], + iommu, &espdma_irq, 0); + + ledma = sparc32_dma_init(hwdef->dma_base + 16ULL, + slavio_irq[3], iommu, &ledma_irq, 1); + + if (graphic_depth != 8 && graphic_depth != 24) { + fprintf(stderr, "qemu: Unsupported depth: %d\n", graphic_depth); + exit (1); + } + tcx_init(hwdef->tcx_base, 0x00100000, graphic_width, graphic_height, + graphic_depth); + + lance_init(&nd_table[0], hwdef->le_base, ledma, ledma_irq); + + nvram = m48t59_init(slavio_irq[0], hwdef->nvram_base, 0, 0x800, 2); + + slavio_serial_ms_kbd_init(hwdef->ms_kb_base, slavio_irq[1], + 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[1], + slavio_irq[1], serial_hds[0], serial_hds[1], + ESCC_CLOCK, 1); + + slavio_misc_init(0, hwdef->aux1_base, 0, slavio_irq[1], fdc_tc); + + if (hwdef->fd_base != (target_phys_addr_t)-1) { + /* 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[1], hwdef->fd_base, fd, + &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); + + kernel_size = sun4m_load_kernel(kernel_filename, initrd_filename, + RAM_size); + + nvram_init(nvram, (uint8_t *)&nd_table[0].macaddr, kernel_cmdline, + boot_device, RAM_size, kernel_size, graphic_width, + graphic_height, graphic_depth, hwdef->nvram_machine_id, + "Sun4c"); + + fw_cfg = fw_cfg_init(0, 0, CFG_ADDR, CFG_ADDR + 2); + fw_cfg_add_i32(fw_cfg, FW_CFG_ID, 1); + 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_i32(fw_cfg, FW_CFG_KERNEL_ADDR, KERNEL_LOAD_ADDR); + 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_ADDR); + pstrcpy_targphys("cmdline", CMDLINE_ADDR, TARGET_PAGE_SIZE, kernel_cmdline); + fw_cfg_add_bytes(fw_cfg, FW_CFG_CMDLINE_DATA, + (uint8_t*)strdup(kernel_cmdline), + strlen(kernel_cmdline) + 1); + } else { + fw_cfg_add_i32(fw_cfg, FW_CFG_KERNEL_CMDLINE, 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, boot_device[0]); + qemu_register_boot_set(fw_cfg_boot_set, fw_cfg); +} + +/* SPARCstation 2 hardware initialisation */ +static void ss2_init(ram_addr_t RAM_size, + const char *boot_device, + const char *kernel_filename, const char *kernel_cmdline, + const char *initrd_filename, const char *cpu_model) +{ + sun4c_hw_init(&sun4c_hwdefs[0], RAM_size, boot_device, kernel_filename, + kernel_cmdline, initrd_filename, cpu_model); +} + +static QEMUMachine ss2_machine = { + .name = "SS-2", + .desc = "Sun4c platform, SPARCstation 2", + .init = ss2_init, + .use_scsi = 1, +}; + +static void ss2_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); + qemu_register_machine(&ss1000_machine); + qemu_register_machine(&ss2000_machine); + qemu_register_machine(&ss2_machine); +} + +machine_init(ss2_machine_init); diff --git a/hw/sun4m.h b/hw/sun4m.h new file mode 100644 index 0000000..ce97ee5 --- /dev/null +++ b/hw/sun4m.h @@ -0,0 +1,36 @@ +#ifndef SUN4M_H +#define SUN4M_H + +#include "qemu-common.h" + +/* Devices used by sparc32 system. */ + +/* iommu.c */ +void sparc_iommu_memory_rw(void *opaque, target_phys_addr_t addr, + uint8_t *buf, int len, int is_write); +static inline void sparc_iommu_memory_read(void *opaque, + target_phys_addr_t addr, + uint8_t *buf, int len) +{ + sparc_iommu_memory_rw(opaque, addr, buf, len, 0); +} + +static inline void sparc_iommu_memory_write(void *opaque, + target_phys_addr_t addr, + uint8_t *buf, int len) +{ + sparc_iommu_memory_rw(opaque, addr, buf, len, 1); +} + +/* slavio_intctl.c */ +void slavio_pic_info(Monitor *mon, DeviceState *dev); +void slavio_irq_info(Monitor *mon, DeviceState *dev); + +/* sun4c_intctl.c */ +void sun4c_pic_info(Monitor *mon, void *opaque); +void sun4c_irq_info(Monitor *mon, void *opaque); + +/* sparc32_dma.c */ +#include "sparc32_dma.h" + +#endif diff --git a/hw/sun4m_iommu.c b/hw/sun4m_iommu.c new file mode 100644 index 0000000..bba69ee --- /dev/null +++ b/hw/sun4m_iommu.c @@ -0,0 +1,378 @@ +/* + * 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 "sun4m.h" +#include "sysbus.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 /* Writeable */ +#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) + +typedef struct IOMMUState { + SysBusDevice busdev; + uint32_t regs[IOMMU_NREGS]; + target_phys_addr_t iostart; + uint32_t version; + qemu_irq irq; +} IOMMUState; + +static uint32_t iommu_mem_readl(void *opaque, target_phys_addr_t addr) +{ + IOMMUState *s = opaque; + target_phys_addr_t 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_writel(void *opaque, target_phys_addr_t addr, + uint32_t val) +{ + IOMMUState *s = opaque; + target_phys_addr_t 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 CPUReadMemoryFunc * const iommu_mem_read[3] = { + NULL, + NULL, + iommu_mem_readl, +}; + +static CPUWriteMemoryFunc * const iommu_mem_write[3] = { + NULL, + NULL, + iommu_mem_writel, +}; + +static uint32_t iommu_page_get_flags(IOMMUState *s, target_phys_addr_t addr) +{ + uint32_t ret; + target_phys_addr_t iopte; + target_phys_addr_t pa = addr; + + iopte = s->regs[IOMMU_BASE] << 4; + addr &= ~s->iostart; + iopte += (addr >> (IOMMU_PAGE_SHIFT - 2)) & ~3; + cpu_physical_memory_read(iopte, (uint8_t *)&ret, 4); + tswap32s(&ret); + trace_sun4m_iommu_page_get_flags(pa, iopte, ret); + return ret; +} + +static target_phys_addr_t iommu_translate_pa(target_phys_addr_t addr, + uint32_t pte) +{ + target_phys_addr_t 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, target_phys_addr_t 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, target_phys_addr_t addr, + uint8_t *buf, int len, int is_write) +{ + int l; + uint32_t flags; + target_phys_addr_t 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, + .minimum_version_id_old = 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 = container_of(d, IOMMUState, busdev.qdev); + + 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 = FROM_SYSBUS(IOMMUState, dev); + int io; + + sysbus_init_irq(dev, &s->irq); + + io = cpu_register_io_memory(iommu_mem_read, iommu_mem_write, s, + DEVICE_NATIVE_ENDIAN); + sysbus_init_mmio(dev, IOMMU_NREGS * sizeof(uint32_t), io); + + return 0; +} + +static SysBusDeviceInfo iommu_info = { + .init = iommu_init1, + .qdev.name = "iommu", + .qdev.size = sizeof(IOMMUState), + .qdev.vmsd = &vmstate_iommu, + .qdev.reset = iommu_reset, + .qdev.props = (Property[]) { + DEFINE_PROP_HEX32("version", IOMMUState, version, 0), + DEFINE_PROP_END_OF_LIST(), + } +}; + +static void iommu_register_devices(void) +{ + sysbus_register_withprop(&iommu_info); +} + +device_init(iommu_register_devices) diff --git a/hw/sun4u.c b/hw/sun4u.c new file mode 100644 index 0000000..90b1ce2 --- /dev/null +++ b/hw/sun4u.c @@ -0,0 +1,942 @@ +/* + * 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.h" +#include "pci.h" +#include "apb_pci.h" +#include "pc.h" +#include "nvram.h" +#include "fdc.h" +#include "net.h" +#include "qemu-timer.h" +#include "sysemu.h" +#include "boards.h" +#include "firmware_abi.h" +#include "fw_cfg.h" +#include "sysbus.h" +#include "ide.h" +#include "loader.h" +#include "elf.h" +#include "blockdev.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 INITRD_LOAD_ADDR 0x00300000 +#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 MAX_PILS 16 + +#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; +}; + +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 int fw_cfg_boot_set(void *opaque, const char *boot_device) +{ + fw_cfg_add_i16(opaque, FW_CFG_BOOT_DEVICE, boot_device[0]); + return 0; +} + +static int sun4u_NVRAM_set_params(M48t59State *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; + + 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++) + m48t59_write(nvram, i, image[i]); + + return 0; +} +static unsigned long sun4u_load_kernel(const char *kernel_filename, + const char *initrd_filename, + ram_addr_t RAM_size, long *initrd_size) +{ + int linux_boot; + unsigned int i; + long 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, NULL, 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 + 8) == 0x48647253) { /* HdrS */ + stl_p(ptr + 24, INITRD_LOAD_ADDR + KERNEL_LOAD_ADDR - 0x4000); + stl_p(ptr + 28, *initrd_size); + break; + } + } + } + } + return kernel_size; +} + +void pic_info(Monitor *mon) +{ +} + +void irq_info(Monitor *mon) +{ +} + +void cpu_check_irqs(CPUState *env) +{ + uint32_t pil = env->pil_in | + (env->softint & ~(SOFTINT_TIMER | SOFTINT_STIMER)); + + /* 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; + } + + if (!pil) { + if (env->interrupt_request & CPU_INTERRUPT_HARD) { + CPUIRQ_DPRINTF("Reset CPU IRQ (current interrupt %x)\n", + env->interrupt_index); + env->interrupt_index = 0; + cpu_reset_interrupt(env, 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 (env->tl > 0 && cpu_tsptr(env)->tt > new_interrupt) { + 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(env, CPU_INTERRUPT_HARD); + } + break; + } + } + } else { + CPUIRQ_DPRINTF("Interrupts disabled, pil=%08x pil_in=%08x softint=%08x " + "current interrupt %x\n", + pil, env->pil_in, env->softint, env->interrupt_index); + } +} + +static void cpu_kick_irq(CPUState *env) +{ + env->halted = 0; + cpu_check_irqs(env); +} + +static void cpu_set_irq(void *opaque, int irq, int level) +{ + CPUState *env = opaque; + + if (level) { + CPUIRQ_DPRINTF("Raise CPU IRQ %d\n", irq); + env->halted = 0; + env->pil_in |= 1 << irq; + cpu_check_irqs(env); + } else { + CPUIRQ_DPRINTF("Lower CPU IRQ %d\n", irq); + env->pil_in &= ~(1 << irq); + cpu_check_irqs(env); + } +} + +typedef struct ResetData { + CPUState *env; + 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); + + qemu_put_timer(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); + + qemu_get_timer(f, s->qtimer); +} + +static CPUTimer* cpu_timer_create(const char* name, CPUState *env, + QEMUBHFunc *cb, uint32_t frequency, + uint64_t disabled_mask) +{ + CPUTimer *timer = qemu_mallocz(sizeof (CPUTimer)); + + timer->name = name; + timer->frequency = frequency; + timer->disabled_mask = disabled_mask; + + timer->disabled = 1; + timer->clock_offset = qemu_get_clock(vm_clock); + + timer->qtimer = qemu_new_timer(vm_clock, cb, env); + + return timer; +} + +static void cpu_timer_reset(CPUTimer *timer) +{ + timer->disabled = 1; + timer->clock_offset = qemu_get_clock(vm_clock); + + qemu_del_timer(timer->qtimer); +} + +static void main_cpu_reset(void *opaque) +{ + ResetData *s = (ResetData *)opaque; + CPUState *env = s->env; + static unsigned int nr_resets; + + cpu_reset(env); + + 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) +{ + CPUState *env = opaque; + + 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(env); +} + +static void stick_irq(void *opaque) +{ + CPUState *env = opaque; + + 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(env); +} + +static void hstick_irq(void *opaque) +{ + CPUState *env = opaque; + + 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(env); +} + +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_get_clock(vm_clock) - + 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_get_clock(vm_clock) - 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_get_clock(vm_clock); + + 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); + qemu_del_timer(timer->qtimer); + } else if (timer->disabled) { + qemu_del_timer(timer->qtimer); + } else { + qemu_mod_timer(timer->qtimer, expires); + } +} + +static void ebus_mmio_mapfunc(PCIDevice *pci_dev, int region_num, + pcibus_t addr, pcibus_t size, int type) +{ + EBUS_DPRINTF("Mapping region %d registers at %" FMT_PCIBUS "\n", + region_num, addr); + switch (region_num) { + case 0: + isa_mmio_init(addr, 0x1000000); + break; + case 1: + isa_mmio_init(addr, 0x800000); + break; + } +} + +static void dummy_isa_irq_handler(void *opaque, int n, int level) +{ +} + +/* EBUS (Eight bit bus) bridge */ +static void +pci_ebus_init(PCIBus *bus, int devfn) +{ + qemu_irq *isa_irq; + + pci_create_simple(bus, devfn, "ebus"); + isa_irq = qemu_allocate_irqs(dummy_isa_irq_handler, NULL, 16); + isa_bus_irqs(isa_irq); +} + +static int +pci_ebus_init1(PCIDevice *s) +{ + isa_bus_new(&s->qdev); + + pci_config_set_vendor_id(s->config, PCI_VENDOR_ID_SUN); + pci_config_set_device_id(s->config, PCI_DEVICE_ID_SUN_EBUS); + s->config[0x04] = 0x06; // command = bus master, pci mem + s->config[0x05] = 0x00; + s->config[0x06] = 0xa0; // status = fast back-to-back, 66MHz, no error + s->config[0x07] = 0x03; // status = medium devsel + s->config[0x08] = 0x01; // revision + s->config[0x09] = 0x00; // programming i/f + pci_config_set_class(s->config, PCI_CLASS_BRIDGE_OTHER); + s->config[0x0D] = 0x0a; // latency_timer + + pci_register_bar(s, 0, 0x1000000, PCI_BASE_ADDRESS_SPACE_MEMORY, + ebus_mmio_mapfunc); + pci_register_bar(s, 1, 0x800000, PCI_BASE_ADDRESS_SPACE_MEMORY, + ebus_mmio_mapfunc); + return 0; +} + +static PCIDeviceInfo ebus_info = { + .qdev.name = "ebus", + .qdev.size = sizeof(PCIDevice), + .init = pci_ebus_init1, +}; + +static void pci_ebus_register(void) +{ + pci_qdev_register(&ebus_info); +} + +device_init(pci_ebus_register); + +static uint64_t translate_prom_address(void *opaque, uint64_t addr) +{ + target_phys_addr_t *base_addr = (target_phys_addr_t *)opaque; + return addr + *base_addr - PROM_VADDR; +} + +/* Boot PROM (OpenBIOS) */ +static void prom_init(target_phys_addr_t addr, const char *bios_name) +{ + DeviceState *dev; + SysBusDevice *s; + char *filename; + int ret; + + dev = qdev_create(NULL, "openprom"); + qdev_init_nofail(dev); + s = sysbus_from_qdev(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); + } + qemu_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) +{ + ram_addr_t prom_offset; + + prom_offset = qemu_ram_alloc(NULL, "sun4u.prom", PROM_SIZE_MAX); + sysbus_init_mmio(dev, PROM_SIZE_MAX, prom_offset | IO_MEM_ROM); + return 0; +} + +static SysBusDeviceInfo prom_info = { + .init = prom_init1, + .qdev.name = "openprom", + .qdev.size = sizeof(SysBusDevice), + .qdev.props = (Property[]) { + {/* end of property list */} + } +}; + +static void prom_register_devices(void) +{ + sysbus_register_withprop(&prom_info); +} + +device_init(prom_register_devices); + + +typedef struct RamDevice +{ + SysBusDevice busdev; + uint64_t size; +} RamDevice; + +/* System RAM */ +static int ram_init1(SysBusDevice *dev) +{ + ram_addr_t RAM_size, ram_offset; + RamDevice *d = FROM_SYSBUS(RamDevice, dev); + + RAM_size = d->size; + + ram_offset = qemu_ram_alloc(NULL, "sun4u.ram", RAM_size); + sysbus_init_mmio(dev, RAM_size, ram_offset); + return 0; +} + +static void ram_init(target_phys_addr_t addr, ram_addr_t RAM_size) +{ + DeviceState *dev; + SysBusDevice *s; + RamDevice *d; + + /* allocate RAM */ + dev = qdev_create(NULL, "memory"); + s = sysbus_from_qdev(dev); + + d = FROM_SYSBUS(RamDevice, s); + d->size = RAM_size; + qdev_init_nofail(dev); + + sysbus_mmio_map(s, 0, addr); +} + +static SysBusDeviceInfo ram_info = { + .init = ram_init1, + .qdev.name = "memory", + .qdev.size = sizeof(RamDevice), + .qdev.props = (Property[]) { + DEFINE_PROP_UINT64("size", RamDevice, size, 0), + DEFINE_PROP_END_OF_LIST(), + } +}; + +static void ram_register_devices(void) +{ + sysbus_register_withprop(&ram_info); +} + +device_init(ram_register_devices); + +static CPUState *cpu_devinit(const char *cpu_model, const struct hwdef *hwdef) +{ + CPUState *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) + cpu_model = hwdef->default_cpu_model; + env = cpu_init(cpu_model); + if (!env) { + fprintf(stderr, "Unable to find Sparc CPU definition\n"); + exit(1); + } + + env->tick = cpu_timer_create("tick", env, tick_irq, + tick_frequency, TICK_NPT_MASK); + + env->stick = cpu_timer_create("stick", env, stick_irq, + stick_frequency, TICK_INT_DIS); + + env->hstick = cpu_timer_create("hstick", env, hstick_irq, + hstick_frequency, TICK_INT_DIS); + + reset_info = qemu_mallocz(sizeof(ResetData)); + reset_info->env = env; + reset_info->prom_addr = hwdef->prom_addr; + qemu_register_reset(main_cpu_reset, reset_info); + + return env; +} + +static void sun4uv_init(ram_addr_t RAM_size, + const char *boot_devices, + const char *kernel_filename, const char *kernel_cmdline, + const char *initrd_filename, const char *cpu_model, + const struct hwdef *hwdef) +{ + CPUState *env; + M48t59State *nvram; + unsigned int i; + long initrd_size, kernel_size; + PCIBus *pci_bus, *pci_bus2, *pci_bus3; + qemu_irq *irq; + DriveInfo *hd[MAX_IDE_BUS * MAX_IDE_DEVS]; + DriveInfo *fd[MAX_FD]; + void *fw_cfg; + + /* init CPUs */ + env = cpu_devinit(cpu_model, hwdef); + + /* set up devices */ + ram_init(0, RAM_size); + + prom_init(hwdef->prom_addr, bios_name); + + + irq = qemu_allocate_irqs(cpu_set_irq, env, MAX_PILS); + pci_bus = pci_apb_init(APB_SPECIAL_BASE, APB_MEM_BASE, irq, &pci_bus2, + &pci_bus3); + isa_mem_base = APB_PCI_IO_BASE; + pci_vga_init(pci_bus); + + // XXX Should be pci_bus3 + pci_ebus_init(pci_bus, -1); + + i = 0; + if (hwdef->console_serial_base) { + serial_mm_init(hwdef->console_serial_base, 0, NULL, 115200, + serial_hds[i], 1, 1); + i++; + } + for(; i < MAX_SERIAL_PORTS; i++) { + if (serial_hds[i]) { + serial_isa_init(i, serial_hds[i]); + } + } + + for(i = 0; i < MAX_PARALLEL_PORTS; i++) { + if (parallel_hds[i]) { + parallel_init(i, parallel_hds[i]); + } + } + + for(i = 0; i < nb_nics; i++) + pci_nic_init_nofail(&nd_table[i], "ne2k_pci", NULL); + + if (drive_get_max_bus(IF_IDE) >= MAX_IDE_BUS) { + fprintf(stderr, "qemu: too many IDE bus\n"); + exit(1); + } + for(i = 0; i < MAX_IDE_BUS * MAX_IDE_DEVS; i++) { + hd[i] = drive_get(IF_IDE, i / MAX_IDE_DEVS, + i % MAX_IDE_DEVS); + } + + pci_cmd646_ide_init(pci_bus, hd, 1); + + isa_create_simple("i8042"); + for(i = 0; i < MAX_FD; i++) { + fd[i] = drive_get(IF_FLOPPY, 0, i); + } + fdctrl_init_isa(fd); + nvram = m48t59_init_isa(0x0074, NVRAM_SIZE, 59); + + initrd_size = 0; + kernel_size = sun4u_load_kernel(kernel_filename, initrd_filename, + ram_size, &initrd_size); + + sun4u_NVRAM_set_params(nvram, NVRAM_SIZE, "Sun4u", RAM_size, boot_devices, + KERNEL_LOAD_ADDR, kernel_size, + kernel_cmdline, + INITRD_LOAD_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(BIOS_CFG_IOPORT, BIOS_CFG_IOPORT + 1, 0, 0); + fw_cfg_add_i32(fw_cfg, FW_CFG_ID, 1); + 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_i32(fw_cfg, FW_CFG_KERNEL_ADDR, KERNEL_LOAD_ADDR); + fw_cfg_add_i32(fw_cfg, FW_CFG_KERNEL_SIZE, kernel_size); + if (kernel_cmdline) { + fw_cfg_add_i32(fw_cfg, FW_CFG_CMDLINE_SIZE, + strlen(kernel_cmdline) + 1); + fw_cfg_add_bytes(fw_cfg, FW_CFG_CMDLINE_DATA, + (uint8_t*)strdup(kernel_cmdline), + strlen(kernel_cmdline) + 1); + } else { + 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, initrd_size); + fw_cfg_add_i16(fw_cfg, FW_CFG_BOOT_DEVICE, boot_devices[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(ram_addr_t RAM_size, + const char *boot_devices, + const char *kernel_filename, const char *kernel_cmdline, + const char *initrd_filename, const char *cpu_model) +{ + sun4uv_init(RAM_size, boot_devices, kernel_filename, + kernel_cmdline, initrd_filename, cpu_model, &hwdefs[0]); +} + +/* Sun4v hardware initialisation */ +static void sun4v_init(ram_addr_t RAM_size, + const char *boot_devices, + const char *kernel_filename, const char *kernel_cmdline, + const char *initrd_filename, const char *cpu_model) +{ + sun4uv_init(RAM_size, boot_devices, kernel_filename, + kernel_cmdline, initrd_filename, cpu_model, &hwdefs[1]); +} + +/* Niagara hardware initialisation */ +static void niagara_init(ram_addr_t RAM_size, + const char *boot_devices, + const char *kernel_filename, const char *kernel_cmdline, + const char *initrd_filename, const char *cpu_model) +{ + sun4uv_init(RAM_size, boot_devices, kernel_filename, + kernel_cmdline, initrd_filename, cpu_model, &hwdefs[2]); +} + +static QEMUMachine sun4u_machine = { + .name = "sun4u", + .desc = "Sun4u platform", + .init = sun4u_init, + .max_cpus = 1, // XXX for now + .is_default = 1, +}; + +static QEMUMachine sun4v_machine = { + .name = "sun4v", + .desc = "Sun4v platform", + .init = sun4v_init, + .max_cpus = 1, // XXX for now +}; + +static QEMUMachine niagara_machine = { + .name = "Niagara", + .desc = "Sun4v platform, Niagara", + .init = niagara_init, + .max_cpus = 1, // XXX for now +}; + +static void sun4u_machine_init(void) +{ + qemu_register_machine(&sun4u_machine); + qemu_register_machine(&sun4v_machine); + qemu_register_machine(&niagara_machine); +} + +machine_init(sun4u_machine_init); diff --git a/hw/syborg.c b/hw/syborg.c new file mode 100644 index 0000000..758c69a --- /dev/null +++ b/hw/syborg.c @@ -0,0 +1,112 @@ +/* + * Syborg (Symbian Virtual Platform) reference board + * + * Copyright (c) 2009 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 "sysbus.h" +#include "boards.h" +#include "arm-misc.h" +#include "sysemu.h" +#include "net.h" + +static struct arm_boot_info syborg_binfo; + +static void syborg_init(ram_addr_t ram_size, + const char *boot_device, + const char *kernel_filename, const char *kernel_cmdline, + const char *initrd_filename, const char *cpu_model) +{ + CPUState *env; + qemu_irq *cpu_pic; + qemu_irq pic[64]; + ram_addr_t ram_addr; + DeviceState *dev; + int i; + + if (!cpu_model) + cpu_model = "cortex-a8"; + env = cpu_init(cpu_model); + if (!env) { + fprintf(stderr, "Unable to find CPU definition\n"); + exit(1); + } + + /* RAM at address zero. */ + ram_addr = qemu_ram_alloc(NULL, "syborg.ram", ram_size); + cpu_register_physical_memory(0, ram_size, ram_addr | IO_MEM_RAM); + + cpu_pic = arm_pic_init_cpu(env); + dev = sysbus_create_simple("syborg,interrupt", 0xC0000000, + cpu_pic[ARM_PIC_CPU_IRQ]); + for (i = 0; i < 64; i++) { + pic[i] = qdev_get_gpio_in(dev, i); + } + + sysbus_create_simple("syborg,rtc", 0xC0001000, NULL); + + dev = qdev_create(NULL, "syborg,timer"); + qdev_prop_set_uint32(dev, "frequency", 1000000); + qdev_init_nofail(dev); + sysbus_mmio_map(sysbus_from_qdev(dev), 0, 0xC0002000); + sysbus_connect_irq(sysbus_from_qdev(dev), 0, pic[1]); + + sysbus_create_simple("syborg,keyboard", 0xC0003000, pic[2]); + sysbus_create_simple("syborg,pointer", 0xC0004000, pic[3]); + sysbus_create_simple("syborg,framebuffer", 0xC0005000, pic[4]); + sysbus_create_simple("syborg,serial", 0xC0006000, pic[5]); + sysbus_create_simple("syborg,serial", 0xC0007000, pic[6]); + sysbus_create_simple("syborg,serial", 0xC0008000, pic[7]); + sysbus_create_simple("syborg,serial", 0xC0009000, pic[8]); + + if (nd_table[0].vlan || nd_table[0].netdev) { + DeviceState *dev; + SysBusDevice *s; + + qemu_check_nic_model(&nd_table[0], "virtio"); + dev = qdev_create(NULL, "syborg,virtio-net"); + qdev_set_nic_properties(dev, &nd_table[0]); + qdev_init_nofail(dev); + s = sysbus_from_qdev(dev); + sysbus_mmio_map(s, 0, 0xc000c000); + sysbus_connect_irq(s, 0, pic[9]); + } + + syborg_binfo.ram_size = ram_size; + syborg_binfo.kernel_filename = kernel_filename; + syborg_binfo.kernel_cmdline = kernel_cmdline; + syborg_binfo.initrd_filename = initrd_filename; + syborg_binfo.board_id = 0; + arm_load_kernel(env, &syborg_binfo); +} + +static QEMUMachine syborg_machine = { + .name = "syborg", + .desc = "Syborg (Symbian Virtual Platform)", + .init = syborg_init, +}; + +static void syborg_machine_init(void) +{ + qemu_register_machine(&syborg_machine); +} + +machine_init(syborg_machine_init); diff --git a/hw/syborg.h b/hw/syborg.h new file mode 100644 index 0000000..b82ce4a --- /dev/null +++ b/hw/syborg.h @@ -0,0 +1,18 @@ +#ifndef _SYBORG_H +#define _SYBORG_H + +#define SYBORG_ID_PLATFORM 0xc51d1000 +#define SYBORG_ID_INT 0xc51d0000 +#define SYBORG_ID_SERIAL 0xc51d0001 +#define SYBORG_ID_KEYBOARD 0xc51d0002 +#define SYBORG_ID_TIMER 0xc51d0003 +#define SYBORG_ID_RTC 0xc51d0004 +#define SYBORG_ID_MOUSE 0xc51d0005 +#define SYBORG_ID_TOUCHSCREEN 0xc51d0006 +#define SYBORG_ID_FRAMEBUFFER 0xc51d0007 +#define SYBORG_ID_HOSTFS 0xc51d0008 +#define SYBORG_ID_SNAPSHOT 0xc51d0009 +#define SYBORG_ID_VIRTIO 0xc51d000a +#define SYBORG_ID_NAND 0xc51d000b + +#endif diff --git a/hw/syborg_fb.c b/hw/syborg_fb.c new file mode 100644 index 0000000..7e37364 --- /dev/null +++ b/hw/syborg_fb.c @@ -0,0 +1,551 @@ +/* + * Syborg Framebuffer + * + * Copyright (c) 2009 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 "sysbus.h" +#include "console.h" +#include "syborg.h" +#include "framebuffer.h" + +//#define DEBUG_SYBORG_FB + +#ifdef DEBUG_SYBORG_FB +#define DPRINTF(fmt, ...) \ +do { printf("syborg_fb: " fmt , ## __VA_ARGS__); } while (0) +#define BADF(fmt, ...) \ +do { fprintf(stderr, "syborg_fb: error: " fmt , ## __VA_ARGS__); \ + exit(1);} while (0) +#else +#define DPRINTF(fmt, ...) do {} while(0) +#define BADF(fmt, ...) \ +do { fprintf(stderr, "syborg_fb: error: " fmt , ## __VA_ARGS__);} while (0) +#endif + +enum { + FB_ID = 0, + FB_BASE = 1, + FB_HEIGHT = 2, + FB_WIDTH = 3, + FB_ORIENTATION = 4, + FB_BLANK = 5, + FB_INT_MASK = 6, + FB_INTERRUPT_CAUSE = 7, + FB_BPP = 8, + FB_COLOR_ORDER = 9, + FB_BYTE_ORDER = 10, + FB_PIXEL_ORDER = 11, + FB_ROW_PITCH = 12, + FB_ENABLED = 13, + FB_PALETTE_START = 0x400 >> 2, + FB_PALETTE_END = FB_PALETTE_START+256-1, +}; + +#define FB_INT_VSYNC (1U << 0) +#define FB_INT_BASE_UPDATE_DONE (1U << 1) + +typedef struct { + SysBusDevice busdev; + DisplayState *ds; + /*QEMUConsole *console;*/ + uint32_t need_update : 1; + uint32_t need_int : 1; + uint32_t enabled : 1; + uint32_t int_status; + uint32_t int_enable; + qemu_irq irq; + + uint32_t base; + uint32_t pitch; + uint32_t rows; + uint32_t cols; + int blank; + int bpp; + int rgb; /* 0 = BGR, 1 = RGB */ + int endian; /* 0 = Little, 1 = Big */ + uint32_t raw_palette[256]; + uint32_t palette[256]; +} SyborgFBState; + +enum { + BPP_SRC_1, + BPP_SRC_2, + BPP_SRC_4, + BPP_SRC_8, + BPP_SRC_16, + BPP_SRC_32, + /* TODO: Implement these. */ + BPP_SRC_15 = -1, + BPP_SRC_24 = -2 +}; + +#include "pixel_ops.h" + +#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" + +/* Update interrupts. */ +static void syborg_fb_update(SyborgFBState *s) +{ + if ((s->int_status & s->int_enable) != 0) { + DPRINTF("Raise IRQ\n"); + qemu_irq_raise(s->irq); + } else { + DPRINTF("Lower IRQ\n"); + qemu_irq_lower(s->irq); + } +} + +static int syborg_fb_enabled(const SyborgFBState *s) +{ + return s->enabled; +} + +static void syborg_fb_update_palette(SyborgFBState *s) +{ + int n, i; + uint32_t raw; + unsigned int r, g, b; + + switch (s->bpp) { + case BPP_SRC_1: n = 2; break; + case BPP_SRC_2: n = 4; break; + case BPP_SRC_4: n = 16; break; + case BPP_SRC_8: n = 256; break; + default: return; + } + + for (i = 0; i < n; i++) { + raw = s->raw_palette[i]; + r = (raw >> 16) & 0xff; + g = (raw >> 8) & 0xff; + b = raw & 0xff; + switch (ds_get_bits_per_pixel(s->ds)) { + case 8: + s->palette[i] = rgb_to_pixel8(r, g, b); + break; + case 15: + s->palette[i] = rgb_to_pixel15(r, g, b); + break; + case 16: + s->palette[i] = rgb_to_pixel16(r, g, b); + break; + case 24: + case 32: + s->palette[i] = rgb_to_pixel32(r, g, b); + break; + default: + abort(); + } + } + +} + +static void syborg_fb_update_display(void *opaque) +{ + SyborgFBState *s = (SyborgFBState *)opaque; + drawfn* fntable; + drawfn fn; + int dest_width; + int src_width; + int bpp_offset; + int first; + int last; + + if (!syborg_fb_enabled(s)) + return; + + switch (ds_get_bits_per_pixel(s->ds)) { + 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, "syborg_fb: Bad color depth\n"); + exit(1); + } + + if (s->need_int) { + s->int_status |= FB_INT_BASE_UPDATE_DONE; + syborg_fb_update(s); + s->need_int = 0; + } + + if (s->rgb) { + bpp_offset = 18; + } else { + bpp_offset = 0; + } + if (s->endian) { + bpp_offset += 6; + } + + fn = fntable[s->bpp + bpp_offset]; + + if (s->pitch) { + src_width = s->pitch; + } else { + src_width = s->cols; + switch (s->bpp) { + case BPP_SRC_1: + src_width >>= 3; + break; + case BPP_SRC_2: + src_width >>= 2; + break; + case BPP_SRC_4: + src_width >>= 1; + break; + case BPP_SRC_8: + break; + case BPP_SRC_15: + case BPP_SRC_16: + src_width <<= 1; + break; + case BPP_SRC_24: + src_width *= 3; + break; + case BPP_SRC_32: + src_width <<= 2; + break; + } + } + dest_width *= s->cols; + first = 0; + /* TODO: Implement blanking. */ + if (!s->blank) { + if (s->need_update && s->bpp <= BPP_SRC_8) { + syborg_fb_update_palette(s); + } + framebuffer_update_display(s->ds, + s->base, s->cols, s->rows, + src_width, dest_width, 0, + s->need_update, + fn, s->palette, + &first, &last); + if (first >= 0) { + dpy_update(s->ds, 0, first, s->cols, last - first + 1); + } + + s->int_status |= FB_INT_VSYNC; + syborg_fb_update(s); + } + + s->need_update = 0; +} + +static void syborg_fb_invalidate_display(void * opaque) +{ + SyborgFBState *s = (SyborgFBState *)opaque; + s->need_update = 1; +} + +static uint32_t syborg_fb_read(void *opaque, target_phys_addr_t offset) +{ + SyborgFBState *s = opaque; + + DPRINTF("read reg %d\n", (int)offset); + offset &= 0xfff; + switch (offset >> 2) { + case FB_ID: + return SYBORG_ID_FRAMEBUFFER; + + case FB_BASE: + return s->base; + + case FB_HEIGHT: + return s->rows; + + case FB_WIDTH: + return s->cols; + + case FB_ORIENTATION: + return 0; + + case FB_BLANK: + return s->blank; + + case FB_INT_MASK: + return s->int_enable; + + case FB_INTERRUPT_CAUSE: + return s->int_status; + + case FB_BPP: + switch (s->bpp) { + case BPP_SRC_1: return 1; + case BPP_SRC_2: return 2; + case BPP_SRC_4: return 4; + case BPP_SRC_8: return 8; + case BPP_SRC_15: return 15; + case BPP_SRC_16: return 16; + case BPP_SRC_24: return 24; + case BPP_SRC_32: return 32; + default: return 0; + } + + case FB_COLOR_ORDER: + return s->rgb; + + case FB_BYTE_ORDER: + return s->endian; + + case FB_PIXEL_ORDER: + return 0; + + case FB_ROW_PITCH: + return s->pitch; + + case FB_ENABLED: + return s->enabled; + + default: + if ((offset >> 2) >= FB_PALETTE_START + && (offset >> 2) <= FB_PALETTE_END) { + return s->raw_palette[(offset >> 2) - FB_PALETTE_START]; + } else { + cpu_abort (cpu_single_env, "syborg_fb_read: Bad offset %x\n", + (int)offset); + } + return 0; + } +} + +static void syborg_fb_write(void *opaque, target_phys_addr_t offset, + uint32_t val) +{ + SyborgFBState *s = opaque; + + DPRINTF("write reg %d = %d\n", (int)offset, val); + s->need_update = 1; + offset &= 0xfff; + switch (offset >> 2) { + case FB_BASE: + s->base = val; + s->need_int = 1; + s->need_update = 1; + syborg_fb_update(s); + break; + + case FB_HEIGHT: + s->rows = val; + break; + + case FB_WIDTH: + s->cols = val; + break; + + case FB_ORIENTATION: + /* TODO: Implement rotation. */ + break; + + case FB_BLANK: + s->blank = val & 1; + break; + + case FB_INT_MASK: + s->int_enable = val; + syborg_fb_update(s); + break; + + case FB_INTERRUPT_CAUSE: + s->int_status &= ~val; + syborg_fb_update(s); + break; + + case FB_BPP: + switch (val) { + case 1: val = BPP_SRC_1; break; + case 2: val = BPP_SRC_2; break; + case 4: val = BPP_SRC_4; break; + case 8: val = BPP_SRC_8; break; + /* case 15: val = BPP_SRC_15; break; */ + case 16: val = BPP_SRC_16; break; + /* case 24: val = BPP_SRC_24; break; */ + case 32: val = BPP_SRC_32; break; + default: val = s->bpp; break; + } + s->bpp = val; + break; + + case FB_COLOR_ORDER: + s->rgb = (val != 0); + break; + + case FB_BYTE_ORDER: + s->endian = (val != 0); + break; + + case FB_PIXEL_ORDER: + /* TODO: Implement this. */ + break; + + case FB_ROW_PITCH: + s->pitch = val; + break; + + case FB_ENABLED: + s->enabled = val; + break; + + default: + if ((offset >> 2) >= FB_PALETTE_START + && (offset >> 2) <= FB_PALETTE_END) { + s->raw_palette[(offset >> 2) - FB_PALETTE_START] = val; + } else { + cpu_abort (cpu_single_env, "syborg_fb_write: Bad offset %x\n", + (int)offset); + } + break; + } +} + +static CPUReadMemoryFunc * const syborg_fb_readfn[] = { + syborg_fb_read, + syborg_fb_read, + syborg_fb_read +}; + +static CPUWriteMemoryFunc * const syborg_fb_writefn[] = { + syborg_fb_write, + syborg_fb_write, + syborg_fb_write +}; + +static void syborg_fb_save(QEMUFile *f, void *opaque) +{ + SyborgFBState *s = opaque; + int i; + + qemu_put_be32(f, s->need_int); + qemu_put_be32(f, s->int_status); + qemu_put_be32(f, s->int_enable); + qemu_put_be32(f, s->enabled); + qemu_put_be32(f, s->base); + qemu_put_be32(f, s->pitch); + qemu_put_be32(f, s->rows); + qemu_put_be32(f, s->cols); + qemu_put_be32(f, s->bpp); + qemu_put_be32(f, s->rgb); + for (i = 0; i < 256; i++) { + qemu_put_be32(f, s->raw_palette[i]); + } +} + +static int syborg_fb_load(QEMUFile *f, void *opaque, int version_id) +{ + SyborgFBState *s = opaque; + int i; + + if (version_id != 1) + return -EINVAL; + + s->need_int = qemu_get_be32(f); + s->int_status = qemu_get_be32(f); + s->int_enable = qemu_get_be32(f); + s->enabled = qemu_get_be32(f); + s->base = qemu_get_be32(f); + s->pitch = qemu_get_be32(f); + s->rows = qemu_get_be32(f); + s->cols = qemu_get_be32(f); + s->bpp = qemu_get_be32(f); + s->rgb = qemu_get_be32(f); + for (i = 0; i < 256; i++) { + s->raw_palette[i] = qemu_get_be32(f); + } + s->need_update = 1; + + return 0; +} + +static int syborg_fb_init(SysBusDevice *dev) +{ + SyborgFBState *s = FROM_SYSBUS(SyborgFBState, dev); + int iomemtype; + + sysbus_init_irq(dev, &s->irq); + iomemtype = cpu_register_io_memory(syborg_fb_readfn, + syborg_fb_writefn, s, + DEVICE_NATIVE_ENDIAN); + sysbus_init_mmio(dev, 0x1000, iomemtype); + + s->ds = graphic_console_init(syborg_fb_update_display, + syborg_fb_invalidate_display, + NULL, NULL, s); + + if (s->cols != 0 && s->rows != 0) { + qemu_console_resize(s->ds, s->cols, s->rows); + } + + if (!s->cols) + s->cols = ds_get_width(s->ds); + if (!s->rows) + s->rows = ds_get_height(s->ds); + + register_savevm(&dev->qdev, "syborg_framebuffer", -1, 1, + syborg_fb_save, syborg_fb_load, s); + return 0; +} + +static SysBusDeviceInfo syborg_fb_info = { + .init = syborg_fb_init, + .qdev.name = "syborg,framebuffer", + .qdev.size = sizeof(SyborgFBState), + .qdev.props = (Property[]) { + DEFINE_PROP_UINT32("width", SyborgFBState, cols, 0), + DEFINE_PROP_UINT32("height", SyborgFBState, rows, 0), + DEFINE_PROP_END_OF_LIST(), + } +}; + +static void syborg_fb_register_devices(void) +{ + sysbus_register_withprop(&syborg_fb_info); +} + +device_init(syborg_fb_register_devices) diff --git a/hw/syborg_interrupt.c b/hw/syborg_interrupt.c new file mode 100644 index 0000000..5217983 --- /dev/null +++ b/hw/syborg_interrupt.c @@ -0,0 +1,238 @@ +/* + * Syborg interrupt controller. + * + * Copyright (c) 2008 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 "sysbus.h" +#include "syborg.h" + +//#define DEBUG_SYBORG_INT + +#ifdef DEBUG_SYBORG_INT +#define DPRINTF(fmt, ...) \ +do { printf("syborg_int: " fmt , ## __VA_ARGS__); } while (0) +#define BADF(fmt, ...) \ +do { fprintf(stderr, "syborg_int: error: " fmt , ## __VA_ARGS__); \ + exit(1);} while (0) +#else +#define DPRINTF(fmt, ...) do {} while(0) +#define BADF(fmt, ...) \ +do { fprintf(stderr, "syborg_int: error: " fmt , ## __VA_ARGS__);} while (0) +#endif +enum { + INT_ID = 0, + INT_STATUS = 1, /* number of pending interrupts */ + INT_CURRENT = 2, /* next interrupt to be serviced */ + INT_DISABLE_ALL = 3, + INT_DISABLE = 4, + INT_ENABLE = 5, + INT_TOTAL = 6 +}; + +typedef struct { + unsigned level:1; + unsigned enabled:1; +} syborg_int_flags; + +typedef struct { + SysBusDevice busdev; + int pending_count; + uint32_t num_irqs; + syborg_int_flags *flags; + qemu_irq parent_irq; +} SyborgIntState; + +static void syborg_int_update(SyborgIntState *s) +{ + DPRINTF("pending %d\n", s->pending_count); + qemu_set_irq(s->parent_irq, s->pending_count > 0); +} + +static void syborg_int_set_irq(void *opaque, int irq, int level) +{ + SyborgIntState *s = (SyborgIntState *)opaque; + + if (s->flags[irq].level == level) + return; + + s->flags[irq].level = level; + if (s->flags[irq].enabled) { + if (level) + s->pending_count++; + else + s->pending_count--; + syborg_int_update(s); + } +} + +static uint32_t syborg_int_read(void *opaque, target_phys_addr_t offset) +{ + SyborgIntState *s = (SyborgIntState *)opaque; + int i; + + offset &= 0xfff; + switch (offset >> 2) { + case INT_ID: + return SYBORG_ID_INT; + case INT_STATUS: + DPRINTF("read status=%d\n", s->pending_count); + return s->pending_count; + + case INT_CURRENT: + for (i = 0; i < s->num_irqs; i++) { + if (s->flags[i].level & s->flags[i].enabled) { + DPRINTF("read current=%d\n", i); + return i; + } + } + DPRINTF("read current=none\n"); + return 0xffffffffu; + + default: + cpu_abort(cpu_single_env, "syborg_int_read: Bad offset %x\n", + (int)offset); + return 0; + } +} + +static void syborg_int_write(void *opaque, target_phys_addr_t offset, uint32_t value) +{ + SyborgIntState *s = (SyborgIntState *)opaque; + int i; + offset &= 0xfff; + + DPRINTF("syborg_int_write offset=%d val=%d\n", (int)offset, (int)value); + switch (offset >> 2) { + case INT_DISABLE_ALL: + s->pending_count = 0; + for (i = 0; i < s->num_irqs; i++) + s->flags[i].enabled = 0; + break; + + case INT_DISABLE: + if (value >= s->num_irqs) + break; + if (s->flags[value].enabled) { + if (s->flags[value].enabled) + s->pending_count--; + s->flags[value].enabled = 0; + } + break; + + case INT_ENABLE: + if (value >= s->num_irqs) + break; + if (!(s->flags[value].enabled)) { + if(s->flags[value].level) + s->pending_count++; + s->flags[value].enabled = 1; + } + break; + + default: + cpu_abort(cpu_single_env, "syborg_int_write: Bad offset %x\n", + (int)offset); + return; + } + syborg_int_update(s); +} + +static CPUReadMemoryFunc * const syborg_int_readfn[] = { + syborg_int_read, + syborg_int_read, + syborg_int_read +}; + +static CPUWriteMemoryFunc * const syborg_int_writefn[] = { + syborg_int_write, + syborg_int_write, + syborg_int_write +}; + +static void syborg_int_save(QEMUFile *f, void *opaque) +{ + SyborgIntState *s = (SyborgIntState *)opaque; + int i; + + qemu_put_be32(f, s->num_irqs); + qemu_put_be32(f, s->pending_count); + for (i = 0; i < s->num_irqs; i++) { + qemu_put_be32(f, s->flags[i].enabled + | ((unsigned)s->flags[i].level << 1)); + } +} + +static int syborg_int_load(QEMUFile *f, void *opaque, int version_id) +{ + SyborgIntState *s = (SyborgIntState *)opaque; + uint32_t val; + int i; + + if (version_id != 1) + return -EINVAL; + + val = qemu_get_be32(f); + if (val != s->num_irqs) + return -EINVAL; + s->pending_count = qemu_get_be32(f); + for (i = 0; i < s->num_irqs; i++) { + val = qemu_get_be32(f); + s->flags[i].enabled = val & 1; + s->flags[i].level = (val >> 1) & 1; + } + return 0; +} + +static int syborg_int_init(SysBusDevice *dev) +{ + SyborgIntState *s = FROM_SYSBUS(SyborgIntState, dev); + int iomemtype; + + sysbus_init_irq(dev, &s->parent_irq); + qdev_init_gpio_in(&dev->qdev, syborg_int_set_irq, s->num_irqs); + iomemtype = cpu_register_io_memory(syborg_int_readfn, + syborg_int_writefn, s, + DEVICE_NATIVE_ENDIAN); + sysbus_init_mmio(dev, 0x1000, iomemtype); + s->flags = qemu_mallocz(s->num_irqs * sizeof(syborg_int_flags)); + + register_savevm(&dev->qdev, "syborg_int", -1, 1, syborg_int_save, + syborg_int_load, s); + return 0; +} + +static SysBusDeviceInfo syborg_int_info = { + .init = syborg_int_init, + .qdev.name = "syborg,interrupt", + .qdev.size = sizeof(SyborgIntState), + .qdev.props = (Property[]) { + DEFINE_PROP_UINT32("num-interrupts", SyborgIntState, num_irqs, 64), + DEFINE_PROP_END_OF_LIST(), + } +}; + +static void syborg_interrupt_register_devices(void) +{ + sysbus_register_withprop(&syborg_int_info); +} + +device_init(syborg_interrupt_register_devices) diff --git a/hw/syborg_keyboard.c b/hw/syborg_keyboard.c new file mode 100644 index 0000000..d295e99 --- /dev/null +++ b/hw/syborg_keyboard.c @@ -0,0 +1,244 @@ +/* + * Syborg keyboard controller. + * + * Copyright (c) 2008 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 "sysbus.h" +#include "console.h" +#include "syborg.h" + +//#define DEBUG_SYBORG_KEYBOARD + +#ifdef DEBUG_SYBORG_KEYBOARD +#define DPRINTF(fmt, ...) \ +do { printf("syborg_keyboard: " fmt , ##args); } while (0) +#define BADF(fmt, ...) \ +do { fprintf(stderr, "syborg_keyboard: error: " fmt , ## __VA_ARGS__); \ + exit(1);} while (0) +#else +#define DPRINTF(fmt, ...) do {} while(0) +#define BADF(fmt, ...) \ +do { fprintf(stderr, "syborg_keyboard: error: " fmt , ## __VA_ARGS__); \ +} while (0) +#endif + +enum { + KBD_ID = 0, + KBD_DATA = 1, + KBD_FIFO_COUNT = 2, + KBD_INT_ENABLE = 3, + KBD_FIFO_SIZE = 4 +}; + +typedef struct { + SysBusDevice busdev; + int int_enabled; + int extension_bit; + uint32_t fifo_size; + uint32_t *key_fifo; + int read_pos, read_count; + qemu_irq irq; +} SyborgKeyboardState; + +static void syborg_keyboard_update(SyborgKeyboardState *s) +{ + int level = s->read_count && s->int_enabled; + DPRINTF("Update IRQ %d\n", level); + qemu_set_irq(s->irq, level); +} + +static uint32_t syborg_keyboard_read(void *opaque, target_phys_addr_t offset) +{ + SyborgKeyboardState *s = (SyborgKeyboardState *)opaque; + int c; + + DPRINTF("reg read %d\n", (int)offset); + offset &= 0xfff; + switch (offset >> 2) { + case KBD_ID: + return SYBORG_ID_KEYBOARD; + case KBD_FIFO_COUNT: + return s->read_count; + case KBD_DATA: + if (s->read_count == 0) { + c = -1; + DPRINTF("FIFO underflow\n"); + } else { + c = s->key_fifo[s->read_pos]; + DPRINTF("FIFO read 0x%x\n", c); + s->read_count--; + s->read_pos++; + if (s->read_pos == s->fifo_size) + s->read_pos = 0; + } + syborg_keyboard_update(s); + return c; + case KBD_INT_ENABLE: + return s->int_enabled; + case KBD_FIFO_SIZE: + return s->fifo_size; + default: + cpu_abort(cpu_single_env, "syborg_keyboard_read: Bad offset %x\n", + (int)offset); + return 0; + } +} + +static void syborg_keyboard_write(void *opaque, target_phys_addr_t offset, + uint32_t value) +{ + SyborgKeyboardState *s = (SyborgKeyboardState *)opaque; + + DPRINTF("reg write %d\n", (int)offset); + offset &= 0xfff; + switch (offset >> 2) { + case KBD_INT_ENABLE: + s->int_enabled = value; + syborg_keyboard_update(s); + break; + default: + cpu_abort(cpu_single_env, "syborg_keyboard_write: Bad offset %x\n", + (int)offset); + } +} + +static CPUReadMemoryFunc * const syborg_keyboard_readfn[] = { + syborg_keyboard_read, + syborg_keyboard_read, + syborg_keyboard_read +}; + +static CPUWriteMemoryFunc * const syborg_keyboard_writefn[] = { + syborg_keyboard_write, + syborg_keyboard_write, + syborg_keyboard_write +}; + +static void syborg_keyboard_event(void *opaque, int keycode) +{ + SyborgKeyboardState *s = (SyborgKeyboardState *)opaque; + int slot; + uint32_t val; + + /* Strip off 0xe0 prefixes and reconstruct the full scancode. */ + if (keycode == 0xe0 && !s->extension_bit) { + DPRINTF("Extension bit\n"); + s->extension_bit = 0x80; + return; + } + val = (keycode & 0x7f) | s->extension_bit; + if (keycode & 0x80) + val |= 0x80000000u; + s->extension_bit = 0; + + DPRINTF("FIFO push 0x%x\n", val); + slot = s->read_pos + s->read_count; + if (slot >= s->fifo_size) + slot -= s->fifo_size; + + if (s->read_count < s->fifo_size) { + s->read_count++; + s->key_fifo[slot] = val; + } else { + fprintf(stderr, "syborg_keyboard error! FIFO overflow\n"); + } + + syborg_keyboard_update(s); +} + +static void syborg_keyboard_save(QEMUFile *f, void *opaque) +{ + SyborgKeyboardState *s = (SyborgKeyboardState *)opaque; + int i; + + qemu_put_be32(f, s->fifo_size); + qemu_put_be32(f, s->int_enabled); + qemu_put_be32(f, s->extension_bit); + qemu_put_be32(f, s->read_pos); + qemu_put_be32(f, s->read_count); + for (i = 0; i < s->fifo_size; i++) { + qemu_put_be32(f, s->key_fifo[i]); + } +} + +static int syborg_keyboard_load(QEMUFile *f, void *opaque, int version_id) +{ + SyborgKeyboardState *s = (SyborgKeyboardState *)opaque; + uint32_t val; + int i; + + if (version_id != 1) + return -EINVAL; + + val = qemu_get_be32(f); + if (val != s->fifo_size) + return -EINVAL; + + s->int_enabled = qemu_get_be32(f); + s->extension_bit = qemu_get_be32(f); + s->read_pos = qemu_get_be32(f); + s->read_count = qemu_get_be32(f); + for (i = 0; i < s->fifo_size; i++) { + s->key_fifo[i] = qemu_get_be32(f); + } + return 0; +} + +static int syborg_keyboard_init(SysBusDevice *dev) +{ + SyborgKeyboardState *s = FROM_SYSBUS(SyborgKeyboardState, dev); + int iomemtype; + + sysbus_init_irq(dev, &s->irq); + iomemtype = cpu_register_io_memory(syborg_keyboard_readfn, + syborg_keyboard_writefn, s, + DEVICE_NATIVE_ENDIAN); + sysbus_init_mmio(dev, 0x1000, iomemtype); + if (s->fifo_size <= 0) { + fprintf(stderr, "syborg_keyboard: fifo too small\n"); + s->fifo_size = 16; + } + s->key_fifo = qemu_mallocz(s->fifo_size * sizeof(s->key_fifo[0])); + + qemu_add_kbd_event_handler(syborg_keyboard_event, s); + + register_savevm(&dev->qdev, "syborg_keyboard", -1, 1, + syborg_keyboard_save, syborg_keyboard_load, s); + return 0; +} + +static SysBusDeviceInfo syborg_keyboard_info = { + .init = syborg_keyboard_init, + .qdev.name = "syborg,keyboard", + .qdev.size = sizeof(SyborgKeyboardState), + .qdev.props = (Property[]) { + DEFINE_PROP_UINT32("fifo-size", SyborgKeyboardState, fifo_size, 16), + DEFINE_PROP_END_OF_LIST(), + } +}; + +static void syborg_keyboard_register_devices(void) +{ + sysbus_register_withprop(&syborg_keyboard_info); +} + +device_init(syborg_keyboard_register_devices) diff --git a/hw/syborg_pointer.c b/hw/syborg_pointer.c new file mode 100644 index 0000000..a886888 --- /dev/null +++ b/hw/syborg_pointer.c @@ -0,0 +1,243 @@ +/* + * Syborg pointing device (mouse/touchscreen) + * + * Copyright (c) 2008 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 "sysbus.h" +#include "console.h" +#include "syborg.h" + +enum { + POINTER_ID = 0, + POINTER_LATCH = 1, + POINTER_FIFO_COUNT = 2, + POINTER_X = 3, + POINTER_Y = 4, + POINTER_Z = 5, + POINTER_BUTTONS = 6, + POINTER_INT_ENABLE = 7, + POINTER_FIFO_SIZE = 8 +}; + +typedef struct { + int x, y, z, pointer_buttons; +} event_data; + +typedef struct { + SysBusDevice busdev; + int int_enabled; + uint32_t fifo_size; + event_data *event_fifo; + int read_pos, read_count; + qemu_irq irq; + uint32_t absolute; +} SyborgPointerState; + +static void syborg_pointer_update(SyborgPointerState *s) +{ + qemu_set_irq(s->irq, s->read_count && s->int_enabled); +} + +static uint32_t syborg_pointer_read(void *opaque, target_phys_addr_t offset) +{ + SyborgPointerState *s = (SyborgPointerState *)opaque; + + offset &= 0xfff; + switch (offset >> 2) { + case POINTER_ID: + return s->absolute ? SYBORG_ID_TOUCHSCREEN : SYBORG_ID_MOUSE; + case POINTER_FIFO_COUNT: + return s->read_count; + case POINTER_X: + return s->event_fifo[s->read_pos].x; + case POINTER_Y: + return s->event_fifo[s->read_pos].y; + case POINTER_Z: + return s->event_fifo[s->read_pos].z; + case POINTER_BUTTONS: + return s->event_fifo[s->read_pos].pointer_buttons; + case POINTER_INT_ENABLE: + return s->int_enabled; + case POINTER_FIFO_SIZE: + return s->fifo_size; + default: + cpu_abort(cpu_single_env, "syborg_pointer_read: Bad offset %x\n", + (int)offset); + return 0; + } +} + +static void syborg_pointer_write(void *opaque, target_phys_addr_t offset, + uint32_t value) +{ + SyborgPointerState *s = (SyborgPointerState *)opaque; + + offset &= 0xfff; + switch (offset >> 2) { + case POINTER_LATCH: + if (s->read_count > 0) { + s->read_count--; + if (++s->read_pos == s->fifo_size) + s->read_pos = 0; + } + break; + case POINTER_INT_ENABLE: + s->int_enabled = value; + break; + default: + cpu_abort(cpu_single_env, "syborg_pointer_write: Bad offset %x\n", + (int)offset); + } + syborg_pointer_update(s); +} + +static CPUReadMemoryFunc * const syborg_pointer_readfn[] = { + syborg_pointer_read, + syborg_pointer_read, + syborg_pointer_read +}; + +static CPUWriteMemoryFunc * const syborg_pointer_writefn[] = { + syborg_pointer_write, + syborg_pointer_write, + syborg_pointer_write +}; + +static void syborg_pointer_event(void *opaque, int dx, int dy, int dz, + int buttons_state) +{ + SyborgPointerState *s = (SyborgPointerState *)opaque; + int slot = s->read_pos + s->read_count; + + /* This first FIFO entry is used to store current register state. */ + if (s->read_count < s->fifo_size - 1) { + s->read_count++; + slot++; + } + + if (slot >= s->fifo_size) + slot -= s->fifo_size; + + if (s->read_count == s->fifo_size && !s->absolute) { + /* Merge existing entries. */ + s->event_fifo[slot].x += dx; + s->event_fifo[slot].y += dy; + s->event_fifo[slot].z += dz; + } else { + s->event_fifo[slot].x = dx; + s->event_fifo[slot].y = dy; + s->event_fifo[slot].z = dz; + } + s->event_fifo[slot].pointer_buttons = buttons_state; + + syborg_pointer_update(s); +} + +static void syborg_pointer_save(QEMUFile *f, void *opaque) +{ + SyborgPointerState *s = (SyborgPointerState *)opaque; + int i; + + qemu_put_be32(f, s->fifo_size); + qemu_put_be32(f, s->absolute); + qemu_put_be32(f, s->int_enabled); + qemu_put_be32(f, s->read_pos); + qemu_put_be32(f, s->read_count); + for (i = 0; i < s->fifo_size; i++) { + qemu_put_be32(f, s->event_fifo[i].x); + qemu_put_be32(f, s->event_fifo[i].y); + qemu_put_be32(f, s->event_fifo[i].z); + qemu_put_be32(f, s->event_fifo[i].pointer_buttons); + } +} + +static int syborg_pointer_load(QEMUFile *f, void *opaque, int version_id) +{ + SyborgPointerState *s = (SyborgPointerState *)opaque; + uint32_t val; + int i; + + if (version_id != 1) + return -EINVAL; + + val = qemu_get_be32(f); + if (val != s->fifo_size) + return -EINVAL; + + val = qemu_get_be32(f); + if (val != s->absolute) + return -EINVAL; + + s->int_enabled = qemu_get_be32(f); + s->read_pos = qemu_get_be32(f); + s->read_count = qemu_get_be32(f); + for (i = 0; i < s->fifo_size; i++) { + s->event_fifo[i].x = qemu_get_be32(f); + s->event_fifo[i].y = qemu_get_be32(f); + s->event_fifo[i].z = qemu_get_be32(f); + s->event_fifo[i].pointer_buttons = qemu_get_be32(f); + } + return 0; +} + +static int syborg_pointer_init(SysBusDevice *dev) +{ + SyborgPointerState *s = FROM_SYSBUS(SyborgPointerState, dev); + int iomemtype; + + sysbus_init_irq(dev, &s->irq); + iomemtype = cpu_register_io_memory(syborg_pointer_readfn, + syborg_pointer_writefn, s, + DEVICE_NATIVE_ENDIAN); + sysbus_init_mmio(dev, 0x1000, iomemtype); + + if (s->fifo_size <= 0) { + fprintf(stderr, "syborg_pointer: fifo too small\n"); + s->fifo_size = 16; + } + s->event_fifo = qemu_mallocz(s->fifo_size * sizeof(s->event_fifo[0])); + + qemu_add_mouse_event_handler(syborg_pointer_event, s, s->absolute, + "Syborg Pointer"); + + register_savevm(&dev->qdev, "syborg_pointer", -1, 1, + syborg_pointer_save, syborg_pointer_load, s); + return 0; +} + +static SysBusDeviceInfo syborg_pointer_info = { + .init = syborg_pointer_init, + .qdev.name = "syborg,pointer", + .qdev.size = sizeof(SyborgPointerState), + .qdev.props = (Property[]) { + DEFINE_PROP_UINT32("fifo-size", SyborgPointerState, fifo_size, 16), + DEFINE_PROP_UINT32("absolute", SyborgPointerState, absolute, 1), + DEFINE_PROP_END_OF_LIST(), + } +}; + +static void syborg_pointer_register_devices(void) +{ + sysbus_register_withprop(&syborg_pointer_info); +} + +device_init(syborg_pointer_register_devices) diff --git a/hw/syborg_rtc.c b/hw/syborg_rtc.c new file mode 100644 index 0000000..329aa42 --- /dev/null +++ b/hw/syborg_rtc.c @@ -0,0 +1,150 @@ +/* + * Syborg RTC + * + * Copyright (c) 2008 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 "sysbus.h" +#include "qemu-timer.h" +#include "syborg.h" + +enum { + RTC_ID = 0, + RTC_LATCH = 1, + RTC_DATA_LOW = 2, + RTC_DATA_HIGH = 3 +}; + +typedef struct { + SysBusDevice busdev; + int64_t offset; + int64_t data; + qemu_irq irq; +} SyborgRTCState; + +static uint32_t syborg_rtc_read(void *opaque, target_phys_addr_t offset) +{ + SyborgRTCState *s = (SyborgRTCState *)opaque; + offset &= 0xfff; + switch (offset >> 2) { + case RTC_ID: + return SYBORG_ID_RTC; + case RTC_DATA_LOW: + return (uint32_t)s->data; + case RTC_DATA_HIGH: + return (uint32_t)(s->data >> 32); + default: + cpu_abort(cpu_single_env, "syborg_rtc_read: Bad offset %x\n", + (int)offset); + return 0; + } +} + +static void syborg_rtc_write(void *opaque, target_phys_addr_t offset, uint32_t value) +{ + SyborgRTCState *s = (SyborgRTCState *)opaque; + uint64_t now; + + offset &= 0xfff; + switch (offset >> 2) { + case RTC_LATCH: + now = qemu_get_clock(vm_clock); + if (value >= 4) { + s->offset = s->data - now; + } else { + s->data = now + s->offset; + while (value) { + s->data /= 1000; + value--; + } + } + break; + case RTC_DATA_LOW: + s->data = (s->data & ~(uint64_t)0xffffffffu) | value; + break; + case RTC_DATA_HIGH: + s->data = (s->data & 0xffffffffu) | ((uint64_t)value << 32); + break; + default: + cpu_abort(cpu_single_env, "syborg_rtc_write: Bad offset %x\n", + (int)offset); + break; + } +} + +static CPUReadMemoryFunc * const syborg_rtc_readfn[] = { + syborg_rtc_read, + syborg_rtc_read, + syborg_rtc_read +}; + +static CPUWriteMemoryFunc * const syborg_rtc_writefn[] = { + syborg_rtc_write, + syborg_rtc_write, + syborg_rtc_write +}; + +static void syborg_rtc_save(QEMUFile *f, void *opaque) +{ + SyborgRTCState *s = opaque; + + qemu_put_be64(f, s->offset); + qemu_put_be64(f, s->data); +} + +static int syborg_rtc_load(QEMUFile *f, void *opaque, int version_id) +{ + SyborgRTCState *s = opaque; + + if (version_id != 1) + return -EINVAL; + + s->offset = qemu_get_be64(f); + s->data = qemu_get_be64(f); + + return 0; +} + +static int syborg_rtc_init(SysBusDevice *dev) +{ + SyborgRTCState *s = FROM_SYSBUS(SyborgRTCState, dev); + struct tm tm; + int iomemtype; + + iomemtype = cpu_register_io_memory(syborg_rtc_readfn, + syborg_rtc_writefn, s, + DEVICE_NATIVE_ENDIAN); + sysbus_init_mmio(dev, 0x1000, iomemtype); + + qemu_get_timedate(&tm, 0); + s->offset = (uint64_t)mktime(&tm) * 1000000000; + + register_savevm(&dev->qdev, "syborg_rtc", -1, 1, + syborg_rtc_save, syborg_rtc_load, s); + return 0; +} + +static void syborg_rtc_register_devices(void) +{ + sysbus_register_dev("syborg,rtc", sizeof(SyborgRTCState), syborg_rtc_init); +} + +device_init(syborg_rtc_register_devices) diff --git a/hw/syborg_serial.c b/hw/syborg_serial.c new file mode 100644 index 0000000..34ce076 --- /dev/null +++ b/hw/syborg_serial.c @@ -0,0 +1,359 @@ +/* + * Syborg serial port + * + * Copyright (c) 2008 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 "sysbus.h" +#include "qemu-char.h" +#include "syborg.h" + +//#define DEBUG_SYBORG_SERIAL + +#ifdef DEBUG_SYBORG_SERIAL +#define DPRINTF(fmt, ...) \ +do { printf("syborg_serial: " fmt , ##args); } while (0) +#define BADF(fmt, ...) \ +do { fprintf(stderr, "syborg_serial: error: " fmt , ## __VA_ARGS__); \ + exit(1);} while (0) +#else +#define DPRINTF(fmt, ...) do {} while(0) +#define BADF(fmt, ...) \ +do { fprintf(stderr, "syborg_serial: error: " fmt , ## __VA_ARGS__);} while (0) +#endif + +enum { + SERIAL_ID = 0, + SERIAL_DATA = 1, + SERIAL_FIFO_COUNT = 2, + SERIAL_INT_ENABLE = 3, + SERIAL_DMA_TX_ADDR = 4, + SERIAL_DMA_TX_COUNT = 5, /* triggers dma */ + SERIAL_DMA_RX_ADDR = 6, + SERIAL_DMA_RX_COUNT = 7, /* triggers dma */ + SERIAL_FIFO_SIZE = 8 +}; + +#define SERIAL_INT_FIFO (1u << 0) +#define SERIAL_INT_DMA_TX (1u << 1) +#define SERIAL_INT_DMA_RX (1u << 2) + +typedef struct { + SysBusDevice busdev; + uint32_t int_enable; + uint32_t fifo_size; + uint32_t *read_fifo; + int read_pos; + int read_count; + CharDriverState *chr; + qemu_irq irq; + uint32_t dma_tx_ptr; + uint32_t dma_rx_ptr; + uint32_t dma_rx_size; +} SyborgSerialState; + +static void syborg_serial_update(SyborgSerialState *s) +{ + int level; + level = 0; + if ((s->int_enable & SERIAL_INT_FIFO) && s->read_count) + level = 1; + if (s->int_enable & SERIAL_INT_DMA_TX) + level = 1; + if ((s->int_enable & SERIAL_INT_DMA_RX) && s->dma_rx_size == 0) + level = 1; + + qemu_set_irq(s->irq, level); +} + +static uint32_t fifo_pop(SyborgSerialState *s) +{ + const uint32_t c = s->read_fifo[s->read_pos]; + s->read_count--; + s->read_pos++; + if (s->read_pos == s->fifo_size) + s->read_pos = 0; + + DPRINTF("FIFO pop %x (%d)\n", c, s->read_count); + return c; +} + +static void fifo_push(SyborgSerialState *s, uint32_t new_value) +{ + int slot; + + DPRINTF("FIFO push %x (%d)\n", new_value, s->read_count); + slot = s->read_pos + s->read_count; + if (slot >= s->fifo_size) + slot -= s->fifo_size; + s->read_fifo[slot] = new_value; + s->read_count++; +} + +static void do_dma_tx(SyborgSerialState *s, uint32_t count) +{ + unsigned char ch; + + if (count == 0) + return; + + if (s->chr != NULL) { + /* optimize later. Now, 1 byte per iteration */ + while (count--) { + cpu_physical_memory_read(s->dma_tx_ptr, &ch, 1); + qemu_chr_write(s->chr, &ch, 1); + s->dma_tx_ptr++; + } + } else { + s->dma_tx_ptr += count; + } + /* QEMU char backends do not have a nonblocking mode, so we transmit all + the data imediately and the interrupt status will be unchanged. */ +} + +/* Initiate RX DMA, and transfer data from the FIFO. */ +static void dma_rx_start(SyborgSerialState *s, uint32_t len) +{ + uint32_t dest; + unsigned char ch; + + dest = s->dma_rx_ptr; + if (s->read_count < len) { + s->dma_rx_size = len - s->read_count; + len = s->read_count; + } else { + s->dma_rx_size = 0; + } + + while (len--) { + ch = fifo_pop(s); + cpu_physical_memory_write(dest, &ch, 1); + dest++; + } + s->dma_rx_ptr = dest; + syborg_serial_update(s); +} + +static uint32_t syborg_serial_read(void *opaque, target_phys_addr_t offset) +{ + SyborgSerialState *s = (SyborgSerialState *)opaque; + uint32_t c; + + offset &= 0xfff; + DPRINTF("read 0x%x\n", (int)offset); + switch(offset >> 2) { + case SERIAL_ID: + return SYBORG_ID_SERIAL; + case SERIAL_DATA: + if (s->read_count > 0) + c = fifo_pop(s); + else + c = -1; + syborg_serial_update(s); + return c; + case SERIAL_FIFO_COUNT: + return s->read_count; + case SERIAL_INT_ENABLE: + return s->int_enable; + case SERIAL_DMA_TX_ADDR: + return s->dma_tx_ptr; + case SERIAL_DMA_TX_COUNT: + return 0; + case SERIAL_DMA_RX_ADDR: + return s->dma_rx_ptr; + case SERIAL_DMA_RX_COUNT: + return s->dma_rx_size; + case SERIAL_FIFO_SIZE: + return s->fifo_size; + + default: + cpu_abort(cpu_single_env, "syborg_serial_read: Bad offset %x\n", + (int)offset); + return 0; + } +} + +static void syborg_serial_write(void *opaque, target_phys_addr_t offset, + uint32_t value) +{ + SyborgSerialState *s = (SyborgSerialState *)opaque; + unsigned char ch; + + offset &= 0xfff; + DPRINTF("Write 0x%x=0x%x\n", (int)offset, value); + switch (offset >> 2) { + case SERIAL_DATA: + ch = value; + if (s->chr) + qemu_chr_write(s->chr, &ch, 1); + break; + case SERIAL_INT_ENABLE: + s->int_enable = value; + syborg_serial_update(s); + break; + case SERIAL_DMA_TX_ADDR: + s->dma_tx_ptr = value; + break; + case SERIAL_DMA_TX_COUNT: + do_dma_tx(s, value); + break; + case SERIAL_DMA_RX_ADDR: + /* For safety, writes to this register cancel any pending DMA. */ + s->dma_rx_size = 0; + s->dma_rx_ptr = value; + break; + case SERIAL_DMA_RX_COUNT: + dma_rx_start(s, value); + break; + default: + cpu_abort(cpu_single_env, "syborg_serial_write: Bad offset %x\n", + (int)offset); + break; + } +} + +static int syborg_serial_can_receive(void *opaque) +{ + SyborgSerialState *s = (SyborgSerialState *)opaque; + + if (s->dma_rx_size) + return s->dma_rx_size; + return s->fifo_size - s->read_count; +} + +static void syborg_serial_receive(void *opaque, const uint8_t *buf, int size) +{ + SyborgSerialState *s = (SyborgSerialState *)opaque; + + if (s->dma_rx_size) { + /* Place it in the DMA buffer. */ + cpu_physical_memory_write(s->dma_rx_ptr, buf, size); + s->dma_rx_size -= size; + s->dma_rx_ptr += size; + } else { + while (size--) + fifo_push(s, *buf); + } + + syborg_serial_update(s); +} + +static void syborg_serial_event(void *opaque, int event) +{ + /* TODO: Report BREAK events? */ +} + +static CPUReadMemoryFunc * const syborg_serial_readfn[] = { + syborg_serial_read, + syborg_serial_read, + syborg_serial_read +}; + +static CPUWriteMemoryFunc * const syborg_serial_writefn[] = { + syborg_serial_write, + syborg_serial_write, + syborg_serial_write +}; + +static void syborg_serial_save(QEMUFile *f, void *opaque) +{ + SyborgSerialState *s = opaque; + int i; + + qemu_put_be32(f, s->fifo_size); + qemu_put_be32(f, s->int_enable); + qemu_put_be32(f, s->read_pos); + qemu_put_be32(f, s->read_count); + qemu_put_be32(f, s->dma_tx_ptr); + qemu_put_be32(f, s->dma_rx_ptr); + qemu_put_be32(f, s->dma_rx_size); + for (i = 0; i < s->fifo_size; i++) { + qemu_put_be32(f, s->read_fifo[i]); + } +} + +static int syborg_serial_load(QEMUFile *f, void *opaque, int version_id) +{ + SyborgSerialState *s = opaque; + int i; + + if (version_id != 1) + return -EINVAL; + + i = qemu_get_be32(f); + if (s->fifo_size != i) + return -EINVAL; + + s->int_enable = qemu_get_be32(f); + s->read_pos = qemu_get_be32(f); + s->read_count = qemu_get_be32(f); + s->dma_tx_ptr = qemu_get_be32(f); + s->dma_rx_ptr = qemu_get_be32(f); + s->dma_rx_size = qemu_get_be32(f); + for (i = 0; i < s->fifo_size; i++) { + s->read_fifo[i] = qemu_get_be32(f); + } + + return 0; +} + +static int syborg_serial_init(SysBusDevice *dev) +{ + SyborgSerialState *s = FROM_SYSBUS(SyborgSerialState, dev); + int iomemtype; + + sysbus_init_irq(dev, &s->irq); + iomemtype = cpu_register_io_memory(syborg_serial_readfn, + syborg_serial_writefn, s, + DEVICE_NATIVE_ENDIAN); + sysbus_init_mmio(dev, 0x1000, iomemtype); + s->chr = qdev_init_chardev(&dev->qdev); + if (s->chr) { + qemu_chr_add_handlers(s->chr, syborg_serial_can_receive, + syborg_serial_receive, syborg_serial_event, s); + } + if (s->fifo_size <= 0) { + fprintf(stderr, "syborg_serial: fifo too small\n"); + s->fifo_size = 16; + } + s->read_fifo = qemu_mallocz(s->fifo_size * sizeof(s->read_fifo[0])); + + register_savevm(&dev->qdev, "syborg_serial", -1, 1, + syborg_serial_save, syborg_serial_load, s); + return 0; +} + +static SysBusDeviceInfo syborg_serial_info = { + .init = syborg_serial_init, + .qdev.name = "syborg,serial", + .qdev.size = sizeof(SyborgSerialState), + .qdev.props = (Property[]) { + DEFINE_PROP_UINT32("fifo-size", SyborgSerialState, fifo_size, 16), + DEFINE_PROP_END_OF_LIST(), + } +}; + +static void syborg_serial_register_devices(void) +{ + sysbus_register_withprop(&syborg_serial_info); +} + +device_init(syborg_serial_register_devices) diff --git a/hw/syborg_timer.c b/hw/syborg_timer.c new file mode 100644 index 0000000..cedcd8e --- /dev/null +++ b/hw/syborg_timer.c @@ -0,0 +1,245 @@ +/* + * Syborg Interval Timer. + * + * Copyright (c) 2008 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 "sysbus.h" +#include "qemu-timer.h" +#include "syborg.h" + +//#define DEBUG_SYBORG_TIMER + +#ifdef DEBUG_SYBORG_TIMER +#define DPRINTF(fmt, ...) \ +do { printf("syborg_timer: " fmt , ##args); } while (0) +#define BADF(fmt, ...) \ +do { fprintf(stderr, "syborg_timer: error: " fmt , ## __VA_ARGS__); \ + exit(1);} while (0) +#else +#define DPRINTF(fmt, ...) do {} while(0) +#define BADF(fmt, ...) \ +do { fprintf(stderr, "syborg_timer: error: " fmt , ## __VA_ARGS__);} while (0) +#endif + +enum { + TIMER_ID = 0, + TIMER_RUNNING = 1, + TIMER_ONESHOT = 2, + TIMER_LIMIT = 3, + TIMER_VALUE = 4, + TIMER_INT_ENABLE = 5, + TIMER_INT_STATUS = 6, + TIMER_FREQ = 7 +}; + +typedef struct { + SysBusDevice busdev; + ptimer_state *timer; + int running; + int oneshot; + uint32_t limit; + uint32_t freq; + uint32_t int_level; + uint32_t int_enabled; + qemu_irq irq; +} SyborgTimerState; + +static void syborg_timer_update(SyborgTimerState *s) +{ + /* Update interrupt. */ + if (s->int_level && s->int_enabled) { + qemu_irq_raise(s->irq); + } else { + qemu_irq_lower(s->irq); + } +} + +static void syborg_timer_tick(void *opaque) +{ + SyborgTimerState *s = (SyborgTimerState *)opaque; + //DPRINTF("Timer Tick\n"); + s->int_level = 1; + if (s->oneshot) + s->running = 0; + syborg_timer_update(s); +} + +static uint32_t syborg_timer_read(void *opaque, target_phys_addr_t offset) +{ + SyborgTimerState *s = (SyborgTimerState *)opaque; + + DPRINTF("Reg read %d\n", (int)offset); + offset &= 0xfff; + switch (offset >> 2) { + case TIMER_ID: + return SYBORG_ID_TIMER; + case TIMER_RUNNING: + return s->running; + case TIMER_ONESHOT: + return s->oneshot; + case TIMER_LIMIT: + return s->limit; + case TIMER_VALUE: + return ptimer_get_count(s->timer); + case TIMER_INT_ENABLE: + return s->int_enabled; + case TIMER_INT_STATUS: + return s->int_level; + case TIMER_FREQ: + return s->freq; + default: + cpu_abort(cpu_single_env, "syborg_timer_read: Bad offset %x\n", + (int)offset); + return 0; + } +} + +static void syborg_timer_write(void *opaque, target_phys_addr_t offset, + uint32_t value) +{ + SyborgTimerState *s = (SyborgTimerState *)opaque; + + DPRINTF("Reg write %d\n", (int)offset); + offset &= 0xfff; + switch (offset >> 2) { + case TIMER_RUNNING: + if (value == s->running) + break; + s->running = value; + if (value) { + ptimer_run(s->timer, s->oneshot); + } else { + ptimer_stop(s->timer); + } + break; + case TIMER_ONESHOT: + if (s->running) { + ptimer_stop(s->timer); + } + s->oneshot = value; + if (s->running) { + ptimer_run(s->timer, s->oneshot); + } + break; + case TIMER_LIMIT: + s->limit = value; + ptimer_set_limit(s->timer, value, 1); + break; + case TIMER_VALUE: + ptimer_set_count(s->timer, value); + break; + case TIMER_INT_ENABLE: + s->int_enabled = value; + syborg_timer_update(s); + break; + case TIMER_INT_STATUS: + s->int_level &= ~value; + syborg_timer_update(s); + break; + default: + cpu_abort(cpu_single_env, "syborg_timer_write: Bad offset %x\n", + (int)offset); + break; + } +} + +static CPUReadMemoryFunc * const syborg_timer_readfn[] = { + syborg_timer_read, + syborg_timer_read, + syborg_timer_read +}; + +static CPUWriteMemoryFunc * const syborg_timer_writefn[] = { + syborg_timer_write, + syborg_timer_write, + syborg_timer_write +}; + +static void syborg_timer_save(QEMUFile *f, void *opaque) +{ + SyborgTimerState *s = opaque; + + qemu_put_be32(f, s->running); + qemu_put_be32(f, s->oneshot); + qemu_put_be32(f, s->limit); + qemu_put_be32(f, s->int_level); + qemu_put_be32(f, s->int_enabled); + qemu_put_ptimer(f, s->timer); +} + +static int syborg_timer_load(QEMUFile *f, void *opaque, int version_id) +{ + SyborgTimerState *s = opaque; + + if (version_id != 1) + return -EINVAL; + + s->running = qemu_get_be32(f); + s->oneshot = qemu_get_be32(f); + s->limit = qemu_get_be32(f); + s->int_level = qemu_get_be32(f); + s->int_enabled = qemu_get_be32(f); + qemu_get_ptimer(f, s->timer); + + return 0; +} + +static int syborg_timer_init(SysBusDevice *dev) +{ + SyborgTimerState *s = FROM_SYSBUS(SyborgTimerState, dev); + QEMUBH *bh; + int iomemtype; + + if (s->freq == 0) { + fprintf(stderr, "syborg_timer: Zero/unset frequency\n"); + exit(1); + } + sysbus_init_irq(dev, &s->irq); + iomemtype = cpu_register_io_memory(syborg_timer_readfn, + syborg_timer_writefn, s, + DEVICE_NATIVE_ENDIAN); + sysbus_init_mmio(dev, 0x1000, iomemtype); + + bh = qemu_bh_new(syborg_timer_tick, s); + s->timer = ptimer_init(bh); + ptimer_set_freq(s->timer, s->freq); + register_savevm(&dev->qdev, "syborg_timer", -1, 1, + syborg_timer_save, syborg_timer_load, s); + return 0; +} + +static SysBusDeviceInfo syborg_timer_info = { + .init = syborg_timer_init, + .qdev.name = "syborg,timer", + .qdev.size = sizeof(SyborgTimerState), + .qdev.props = (Property[]) { + DEFINE_PROP_UINT32("frequency",SyborgTimerState, freq, 0), + DEFINE_PROP_END_OF_LIST(), + } +}; + +static void syborg_timer_register_devices(void) +{ + sysbus_register_withprop(&syborg_timer_info); +} + +device_init(syborg_timer_register_devices) diff --git a/hw/syborg_virtio.c b/hw/syborg_virtio.c new file mode 100644 index 0000000..ee08c49 --- /dev/null +++ b/hw/syborg_virtio.c @@ -0,0 +1,314 @@ +/* + * Virtio Syborg bindings + * + * Copyright (c) 2009 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 "syborg.h" +#include "sysbus.h" +#include "virtio.h" +#include "virtio-net.h" +#include "sysemu.h" + +//#define DEBUG_SYBORG_VIRTIO + +#ifdef DEBUG_SYBORG_VIRTIO +#define DPRINTF(fmt, ...) \ +do { printf("syborg_virtio: " fmt , ## __VA_ARGS__); } while (0) +#define BADF(fmt, ...) \ +do { fprintf(stderr, "syborg_virtio: error: " fmt , ## __VA_ARGS__); \ + exit(1);} while (0) +#else +#define DPRINTF(fmt, ...) do {} while(0) +#define BADF(fmt, ...) \ +do { fprintf(stderr, "syborg_virtio: error: " fmt , ## __VA_ARGS__);} while (0) +#endif + +enum { + SYBORG_VIRTIO_ID = 0, + SYBORG_VIRTIO_DEVTYPE = 1, + SYBORG_VIRTIO_HOST_FEATURES = 2, + SYBORG_VIRTIO_GUEST_FEATURES = 3, + SYBORG_VIRTIO_QUEUE_BASE = 4, + SYBORG_VIRTIO_QUEUE_NUM = 5, + SYBORG_VIRTIO_QUEUE_SEL = 6, + SYBORG_VIRTIO_QUEUE_NOTIFY = 7, + SYBORG_VIRTIO_STATUS = 8, + SYBORG_VIRTIO_INT_ENABLE = 9, + SYBORG_VIRTIO_INT_STATUS = 10 +}; + +#define SYBORG_VIRTIO_CONFIG 0x100 + +/* Device independent interface. */ + +typedef struct { + SysBusDevice busdev; + VirtIODevice *vdev; + qemu_irq irq; + uint32_t int_enable; + uint32_t id; + NICConf nic; + uint32_t host_features; + virtio_net_conf net; +} SyborgVirtIOProxy; + +static uint32_t syborg_virtio_readl(void *opaque, target_phys_addr_t offset) +{ + SyborgVirtIOProxy *s = opaque; + VirtIODevice *vdev = s->vdev; + uint32_t ret; + + DPRINTF("readl 0x%x\n", (int)offset); + if (offset >= SYBORG_VIRTIO_CONFIG) { + return virtio_config_readl(vdev, offset - SYBORG_VIRTIO_CONFIG); + } + switch(offset >> 2) { + case SYBORG_VIRTIO_ID: + ret = SYBORG_ID_VIRTIO; + break; + case SYBORG_VIRTIO_DEVTYPE: + ret = s->id; + break; + case SYBORG_VIRTIO_HOST_FEATURES: + ret = s->host_features; + break; + case SYBORG_VIRTIO_GUEST_FEATURES: + ret = vdev->guest_features; + break; + case SYBORG_VIRTIO_QUEUE_BASE: + ret = virtio_queue_get_addr(vdev, vdev->queue_sel); + break; + case SYBORG_VIRTIO_QUEUE_NUM: + ret = virtio_queue_get_num(vdev, vdev->queue_sel); + break; + case SYBORG_VIRTIO_QUEUE_SEL: + ret = vdev->queue_sel; + break; + case SYBORG_VIRTIO_STATUS: + ret = vdev->status; + break; + case SYBORG_VIRTIO_INT_ENABLE: + ret = s->int_enable; + break; + case SYBORG_VIRTIO_INT_STATUS: + ret = vdev->isr; + break; + default: + BADF("Bad read offset 0x%x\n", (int)offset); + return 0; + } + return ret; +} + +static void syborg_virtio_writel(void *opaque, target_phys_addr_t offset, + uint32_t value) +{ + SyborgVirtIOProxy *s = opaque; + VirtIODevice *vdev = s->vdev; + + DPRINTF("writel 0x%x = 0x%x\n", (int)offset, value); + if (offset >= SYBORG_VIRTIO_CONFIG) { + return virtio_config_writel(vdev, offset - SYBORG_VIRTIO_CONFIG, + value); + } + switch (offset >> 2) { + case SYBORG_VIRTIO_GUEST_FEATURES: + if (vdev->set_features) + vdev->set_features(vdev, value); + vdev->guest_features = value; + break; + case SYBORG_VIRTIO_QUEUE_BASE: + if (value == 0) + virtio_reset(vdev); + else + virtio_queue_set_addr(vdev, vdev->queue_sel, value); + break; + case SYBORG_VIRTIO_QUEUE_SEL: + if (value < VIRTIO_PCI_QUEUE_MAX) + vdev->queue_sel = value; + break; + case SYBORG_VIRTIO_QUEUE_NOTIFY: + virtio_queue_notify(vdev, value); + break; + case SYBORG_VIRTIO_STATUS: + virtio_set_status(vdev, value & 0xFF); + if (vdev->status == 0) + virtio_reset(vdev); + break; + case SYBORG_VIRTIO_INT_ENABLE: + s->int_enable = value; + virtio_update_irq(vdev); + break; + case SYBORG_VIRTIO_INT_STATUS: + vdev->isr &= ~value; + virtio_update_irq(vdev); + break; + default: + BADF("Bad write offset 0x%x\n", (int)offset); + break; + } +} + +static uint32_t syborg_virtio_readw(void *opaque, target_phys_addr_t offset) +{ + SyborgVirtIOProxy *s = opaque; + VirtIODevice *vdev = s->vdev; + + DPRINTF("readw 0x%x\n", (int)offset); + if (offset >= SYBORG_VIRTIO_CONFIG) { + return virtio_config_readw(vdev, offset - SYBORG_VIRTIO_CONFIG); + } + BADF("Bad halfword read offset 0x%x\n", (int)offset); + return -1; +} + +static void syborg_virtio_writew(void *opaque, target_phys_addr_t offset, + uint32_t value) +{ + SyborgVirtIOProxy *s = opaque; + VirtIODevice *vdev = s->vdev; + + DPRINTF("writew 0x%x = 0x%x\n", (int)offset, value); + if (offset >= SYBORG_VIRTIO_CONFIG) { + return virtio_config_writew(vdev, offset - SYBORG_VIRTIO_CONFIG, + value); + } + BADF("Bad halfword write offset 0x%x\n", (int)offset); +} + +static uint32_t syborg_virtio_readb(void *opaque, target_phys_addr_t offset) +{ + SyborgVirtIOProxy *s = opaque; + VirtIODevice *vdev = s->vdev; + + DPRINTF("readb 0x%x\n", (int)offset); + if (offset >= SYBORG_VIRTIO_CONFIG) { + return virtio_config_readb(vdev, offset - SYBORG_VIRTIO_CONFIG); + } + BADF("Bad byte read offset 0x%x\n", (int)offset); + return -1; +} + +static void syborg_virtio_writeb(void *opaque, target_phys_addr_t offset, + uint32_t value) +{ + SyborgVirtIOProxy *s = opaque; + VirtIODevice *vdev = s->vdev; + + DPRINTF("writeb 0x%x = 0x%x\n", (int)offset, value); + if (offset >= SYBORG_VIRTIO_CONFIG) { + return virtio_config_writeb(vdev, offset - SYBORG_VIRTIO_CONFIG, + value); + } + BADF("Bad byte write offset 0x%x\n", (int)offset); +} + +static CPUReadMemoryFunc * const syborg_virtio_readfn[] = { + syborg_virtio_readb, + syborg_virtio_readw, + syborg_virtio_readl +}; + +static CPUWriteMemoryFunc * const syborg_virtio_writefn[] = { + syborg_virtio_writeb, + syborg_virtio_writew, + syborg_virtio_writel +}; + +static void syborg_virtio_update_irq(void *opaque, uint16_t vector) +{ + SyborgVirtIOProxy *proxy = opaque; + int level; + + level = proxy->int_enable & proxy->vdev->isr; + DPRINTF("IRQ %d\n", level); + qemu_set_irq(proxy->irq, level != 0); +} + +static unsigned syborg_virtio_get_features(void *opaque) +{ + SyborgVirtIOProxy *proxy = opaque; + return proxy->host_features; +} + +static VirtIOBindings syborg_virtio_bindings = { + .notify = syborg_virtio_update_irq, + .get_features = syborg_virtio_get_features, +}; + +static int syborg_virtio_init(SyborgVirtIOProxy *proxy, VirtIODevice *vdev) +{ + int iomemtype; + + proxy->vdev = vdev; + + /* Don't support multiple vectors */ + proxy->vdev->nvectors = 0; + sysbus_init_irq(&proxy->busdev, &proxy->irq); + iomemtype = cpu_register_io_memory(syborg_virtio_readfn, + syborg_virtio_writefn, proxy, + DEVICE_NATIVE_ENDIAN); + sysbus_init_mmio(&proxy->busdev, 0x1000, iomemtype); + + proxy->id = ((uint32_t)0x1af4 << 16) | vdev->device_id; + + qemu_register_reset(virtio_reset, vdev); + + virtio_bind_device(vdev, &syborg_virtio_bindings, proxy); + proxy->host_features |= (0x1 << VIRTIO_F_NOTIFY_ON_EMPTY); + proxy->host_features = vdev->get_features(vdev, proxy->host_features); + return 0; +} + +/* Device specific bindings. */ + +static int syborg_virtio_net_init(SysBusDevice *dev) +{ + VirtIODevice *vdev; + SyborgVirtIOProxy *proxy = FROM_SYSBUS(SyborgVirtIOProxy, dev); + + vdev = virtio_net_init(&dev->qdev, &proxy->nic, &proxy->net); + return syborg_virtio_init(proxy, vdev); +} + +static SysBusDeviceInfo syborg_virtio_net_info = { + .init = syborg_virtio_net_init, + .qdev.name = "syborg,virtio-net", + .qdev.size = sizeof(SyborgVirtIOProxy), + .qdev.props = (Property[]) { + DEFINE_NIC_PROPERTIES(SyborgVirtIOProxy, nic), + DEFINE_VIRTIO_NET_FEATURES(SyborgVirtIOProxy, host_features), + DEFINE_PROP_UINT32("x-txtimer", SyborgVirtIOProxy, + net.txtimer, TX_TIMER_INTERVAL), + DEFINE_PROP_INT32("x-txburst", SyborgVirtIOProxy, + net.txburst, TX_BURST), + DEFINE_PROP_STRING("tx", SyborgVirtIOProxy, net.tx), + DEFINE_PROP_END_OF_LIST(), + } +}; + +static void syborg_virtio_register_devices(void) +{ + sysbus_register_withprop(&syborg_virtio_net_info); +} + +device_init(syborg_virtio_register_devices) diff --git a/hw/sysbus.c b/hw/sysbus.c new file mode 100644 index 0000000..1583bd8 --- /dev/null +++ b/hw/sysbus.c @@ -0,0 +1,203 @@ +/* + * 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 . + */ + +#include "sysbus.h" +#include "sysemu.h" +#include "monitor.h" + +static void sysbus_dev_print(Monitor *mon, DeviceState *dev, int indent); +static char *sysbus_get_fw_dev_path(DeviceState *dev); + +struct BusInfo system_bus_info = { + .name = "System", + .size = sizeof(BusState), + .print_dev = sysbus_dev_print, + .get_fw_dev_path = sysbus_get_fw_dev_path, +}; + +void sysbus_connect_irq(SysBusDevice *dev, int n, qemu_irq irq) +{ + assert(n >= 0 && n < dev->num_irq); + dev->irqs[n] = NULL; + if (dev->irqp[n]) { + *dev->irqp[n] = irq; + } +} + +void sysbus_mmio_map(SysBusDevice *dev, int n, target_phys_addr_t addr) +{ + assert(n >= 0 && n < dev->num_mmio); + + if (dev->mmio[n].addr == addr) { + /* ??? region already mapped here. */ + return; + } + if (dev->mmio[n].addr != (target_phys_addr_t)-1) { + /* Unregister previous mapping. */ + cpu_register_physical_memory(dev->mmio[n].addr, dev->mmio[n].size, + IO_MEM_UNASSIGNED); + } + dev->mmio[n].addr = addr; + if (dev->mmio[n].cb) { + dev->mmio[n].cb(dev, addr); + } else { + cpu_register_physical_memory(addr, dev->mmio[n].size, + dev->mmio[n].iofunc); + } +} + + +/* Request an IRQ source. The actual IRQ object may be populated later. */ +void sysbus_init_irq(SysBusDevice *dev, qemu_irq *p) +{ + int n; + + assert(dev->num_irq < QDEV_MAX_IRQ); + n = dev->num_irq++; + dev->irqp[n] = p; +} + +/* Pass IRQs from a target device. */ +void sysbus_pass_irq(SysBusDevice *dev, SysBusDevice *target) +{ + int i; + assert(dev->num_irq == 0); + dev->num_irq = target->num_irq; + for (i = 0; i < dev->num_irq; i++) { + dev->irqp[i] = target->irqp[i]; + } +} + +void sysbus_init_mmio(SysBusDevice *dev, target_phys_addr_t size, + ram_addr_t iofunc) +{ + int n; + + assert(dev->num_mmio < QDEV_MAX_MMIO); + n = dev->num_mmio++; + dev->mmio[n].addr = -1; + dev->mmio[n].size = size; + dev->mmio[n].iofunc = iofunc; +} + +void sysbus_init_mmio_cb(SysBusDevice *dev, target_phys_addr_t size, + mmio_mapfunc cb) +{ + int n; + + assert(dev->num_mmio < QDEV_MAX_MMIO); + n = dev->num_mmio++; + dev->mmio[n].addr = -1; + dev->mmio[n].size = size; + dev->mmio[n].cb = cb; +} + +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, DeviceInfo *base) +{ + SysBusDeviceInfo *info = container_of(base, SysBusDeviceInfo, qdev); + + return info->init(sysbus_from_qdev(dev)); +} + +void sysbus_register_withprop(SysBusDeviceInfo *info) +{ + info->qdev.init = sysbus_device_init; + info->qdev.bus_info = &system_bus_info; + + assert(info->qdev.size >= sizeof(SysBusDevice)); + qdev_register(&info->qdev); +} + +void sysbus_register_dev(const char *name, size_t size, sysbus_initfn init) +{ + SysBusDeviceInfo *info; + + info = qemu_mallocz(sizeof(*info)); + info->qdev.name = qemu_strdup(name); + info->qdev.size = size; + info->init = init; + sysbus_register_withprop(info); +} + +DeviceState *sysbus_create_varargs(const char *name, + target_phys_addr_t addr, ...) +{ + DeviceState *dev; + SysBusDevice *s; + va_list va; + qemu_irq irq; + int n; + + dev = qdev_create(NULL, name); + s = sysbus_from_qdev(dev); + qdev_init_nofail(dev); + if (addr != (target_phys_addr_t)-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++; + } + return dev; +} + +static void sysbus_dev_print(Monitor *mon, DeviceState *dev, int indent) +{ + SysBusDevice *s = sysbus_from_qdev(dev); + int i; + + for (i = 0; i < s->num_mmio; i++) { + monitor_printf(mon, "%*smmio " TARGET_FMT_plx "/" TARGET_FMT_plx "\n", + indent, "", s->mmio[i].addr, s->mmio[i].size); + } +} + +static char *sysbus_get_fw_dev_path(DeviceState *dev) +{ + SysBusDevice *s = sysbus_from_qdev(dev); + char path[40]; + int off; + + off = snprintf(path, sizeof(path), "%s", qdev_fw_name(dev)); + + if (s->num_mmio) { + snprintf(path + off, sizeof(path) - off, "@"TARGET_FMT_plx, + s->mmio[0].addr); + } else if (s->num_pio) { + snprintf(path + off, sizeof(path) - off, "@i%04x", s->pio[0]); + } + + return strdup(path); +} diff --git a/hw/sysbus.h b/hw/sysbus.h new file mode 100644 index 0000000..e9eb618 --- /dev/null +++ b/hw/sysbus.h @@ -0,0 +1,67 @@ +#ifndef HW_SYSBUS_H +#define HW_SYSBUS_H 1 + +/* Devices attached directly to the main system bus. */ + +#include "qdev.h" + +#define QDEV_MAX_MMIO 32 +#define QDEV_MAX_PIO 32 +#define QDEV_MAX_IRQ 256 + +typedef struct SysBusDevice SysBusDevice; +typedef void (*mmio_mapfunc)(SysBusDevice *dev, target_phys_addr_t addr); + +struct SysBusDevice { + DeviceState qdev; + int num_irq; + qemu_irq irqs[QDEV_MAX_IRQ]; + qemu_irq *irqp[QDEV_MAX_IRQ]; + int num_mmio; + struct { + target_phys_addr_t addr; + target_phys_addr_t size; + mmio_mapfunc cb; + ram_addr_t iofunc; + } mmio[QDEV_MAX_MMIO]; + int num_pio; + pio_addr_t pio[QDEV_MAX_PIO]; +}; + +typedef int (*sysbus_initfn)(SysBusDevice *dev); + +/* Macros to compensate for lack of type inheritance in C. */ +#define sysbus_from_qdev(dev) ((SysBusDevice *)(dev)) +#define FROM_SYSBUS(type, dev) DO_UPCAST(type, busdev, dev) + +typedef struct { + DeviceInfo qdev; + sysbus_initfn init; +} SysBusDeviceInfo; + +void sysbus_register_dev(const char *name, size_t size, sysbus_initfn init); +void sysbus_register_withprop(SysBusDeviceInfo *info); +void *sysbus_new(void); +void sysbus_init_mmio(SysBusDevice *dev, target_phys_addr_t size, + ram_addr_t iofunc); +void sysbus_init_mmio_cb(SysBusDevice *dev, target_phys_addr_t size, + mmio_mapfunc cb); +void sysbus_init_irq(SysBusDevice *dev, qemu_irq *p); +void sysbus_pass_irq(SysBusDevice *dev, SysBusDevice *target); +void sysbus_init_ioports(SysBusDevice *dev, pio_addr_t ioport, pio_addr_t size); + + +void sysbus_connect_irq(SysBusDevice *dev, int n, qemu_irq irq); +void sysbus_mmio_map(SysBusDevice *dev, int n, target_phys_addr_t addr); + +/* Legacy helper function for creating devices. */ +DeviceState *sysbus_create_varargs(const char *name, + target_phys_addr_t addr, ...); +static inline DeviceState *sysbus_create_simple(const char *name, + target_phys_addr_t addr, + qemu_irq irq) +{ + return sysbus_create_varargs(name, addr, irq, NULL); +} + +#endif /* !HW_SYSBUS_H */ diff --git a/hw/tc58128.c b/hw/tc58128.c new file mode 100644 index 0000000..672a01c --- /dev/null +++ b/hw/tc58128.c @@ -0,0 +1,183 @@ +#include "hw.h" +#include "sh.h" +#include "sysemu.h" +#include "loader.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 = qemu_mallocz(FLASH_SIZE); + memset(dev->flash_contents, 0xff, FLASH_SIZE); + if (!dev->flash_contents) { + fprintf(stderr, "could not alloc memory for flash\n"); + exit(1); + } + if (filename) { + /* Load flash image skipping the first block */ + ret = load_image(filename, dev->flash_contents + 528 * 32); + if (ret < 0) { + fprintf(stderr, "ret=%d\n", ret); + fprintf(stderr, "qemu: could not load flash image %s\n", + 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/tc6393xb.c b/hw/tc6393xb.c new file mode 100644 index 0000000..c3fbe4e --- /dev/null +++ b/hw/tc6393xb.c @@ -0,0 +1,608 @@ +/* + * 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. + */ +#include "hw.h" +#include "pxa.h" +#include "devices.h" +#include "flash.h" +#include "console.h" +#include "pixel_ops.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 { + 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; + NANDFlashState *flash; + ECCState ecc; + + DisplayState *ds; + ram_addr_t vram_addr; + 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 = ffs(diff) - 1; + 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, target_phys_addr_t 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, target_phys_addr_t 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, target_phys_addr_t 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, target_phys_addr_t 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, target_phys_addr_t 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, target_phys_addr_t 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) +{ + switch (ds_get_bits_per_pixel(s->ds)) { + 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", ds_get_bits_per_pixel(s->ds)); + return; + } + + dpy_update(s->ds, 0, 0, s->scr_width, s->scr_height); +} + +static void tc6393xb_draw_blank(TC6393xbState *s, int full_update) +{ + int i, w; + uint8_t *d; + + if (!full_update) + return; + + w = s->scr_width * ((ds_get_bits_per_pixel(s->ds) + 7) >> 3); + d = ds_get_data(s->ds); + for(i = 0; i < s->scr_height; i++) { + memset(d, 0, w); + d += ds_get_linesize(s->ds); + } + + dpy_update(s->ds, 0, 0, s->scr_width, s->scr_height); +} + +static void tc6393xb_update_display(void *opaque) +{ + TC6393xbState *s = opaque; + 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 != ds_get_width(s->ds) || s->scr_height != ds_get_height(s->ds)) { + qemu_console_resize(s->ds, 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 uint32_t tc6393xb_readb(void *opaque, target_phys_addr_t addr) { + 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, target_phys_addr_t addr, uint32_t value) { + 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, value & 0xff); +} + +static uint32_t tc6393xb_readw(void *opaque, target_phys_addr_t addr) +{ + return (tc6393xb_readb(opaque, addr) & 0xff) | + (tc6393xb_readb(opaque, addr + 1) << 8); +} + +static uint32_t tc6393xb_readl(void *opaque, target_phys_addr_t addr) +{ + return (tc6393xb_readb(opaque, addr) & 0xff) | + ((tc6393xb_readb(opaque, addr + 1) & 0xff) << 8) | + ((tc6393xb_readb(opaque, addr + 2) & 0xff) << 16) | + ((tc6393xb_readb(opaque, addr + 3) & 0xff) << 24); +} + +static void tc6393xb_writew(void *opaque, target_phys_addr_t addr, uint32_t value) +{ + tc6393xb_writeb(opaque, addr, value); + tc6393xb_writeb(opaque, addr + 1, value >> 8); +} + +static void tc6393xb_writel(void *opaque, target_phys_addr_t addr, uint32_t value) +{ + tc6393xb_writeb(opaque, addr, value); + tc6393xb_writeb(opaque, addr + 1, value >> 8); + tc6393xb_writeb(opaque, addr + 2, value >> 16); + tc6393xb_writeb(opaque, addr + 3, value >> 24); +} + +TC6393xbState *tc6393xb_init(uint32_t base, qemu_irq irq) +{ + int iomemtype; + TC6393xbState *s; + CPUReadMemoryFunc * const tc6393xb_readfn[] = { + tc6393xb_readb, + tc6393xb_readw, + tc6393xb_readl, + }; + CPUWriteMemoryFunc * const tc6393xb_writefn[] = { + tc6393xb_writeb, + tc6393xb_writew, + tc6393xb_writel, + }; + + s = (TC6393xbState *) qemu_mallocz(sizeof(TC6393xbState)); + s->irq = irq; + s->gpio_in = qemu_allocate_irqs(tc6393xb_gpio_set, s, TC6393XB_GPIOS); + + s->l3v = *qemu_allocate_irqs(tc6393xb_l3v, s, 1); + s->blanked = 1; + + s->sub_irqs = qemu_allocate_irqs(tc6393xb_sub_irq, s, TC6393XB_NR_IRQS); + + s->flash = nand_init(NAND_MFR_TOSHIBA, 0x76); + + iomemtype = cpu_register_io_memory(tc6393xb_readfn, + tc6393xb_writefn, s, DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory(base, 0x10000, iomemtype); + + s->vram_addr = qemu_ram_alloc(NULL, "tc6393xb.vram", 0x100000); + s->vram_ptr = qemu_get_ram_ptr(s->vram_addr); + cpu_register_physical_memory(base + 0x100000, 0x100000, s->vram_addr); + s->scr_width = 480; + s->scr_height = 640; + s->ds = graphic_console_init(tc6393xb_update_display, + NULL, /* invalidate */ + NULL, /* screen_dump */ + NULL, /* text_update */ + s); + + return s; +} diff --git a/hw/tc6393xb_template.h b/hw/tc6393xb_template.h new file mode 100644 index 0000000..1ccf6e8 --- /dev/null +++ b/hw/tc6393xb_template.h @@ -0,0 +1,67 @@ +/* + * 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 . + */ + +#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) \ + addr[0] = color; addr[1] = (color) >> 8; addr[2] = (color) >> 16; +#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) +{ + int i; + uint16_t *data_buffer; + uint8_t *data_display; + + data_buffer = s->vram_ptr; + data_display = ds_get_data(s->ds); + 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 += ds_get_linesize(s->ds); +#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/tcx.c b/hw/tcx.c new file mode 100644 index 0000000..0e32830 --- /dev/null +++ b/hw/tcx.c @@ -0,0 +1,651 @@ +/* + * 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 "console.h" +#include "pixel_ops.h" +#include "sysbus.h" +#include "qdev-addr.h" + +#define MAXX 1024 +#define MAXY 768 +#define TCX_DAC_NREGS 16 +#define TCX_THC_NREGS_8 0x081c +#define TCX_THC_NREGS_24 0x1000 +#define TCX_TEC_NREGS 0x1000 + +typedef struct TCXState { + SysBusDevice busdev; + target_phys_addr_t addr; + DisplayState *ds; + uint8_t *vram; + uint32_t *vram24, *cplane; + ram_addr_t vram_offset, vram24_offset, cplane_offset; + uint32_t vram_size; + uint16_t width, height, depth; + uint8_t r[256], g[256], b[256]; + uint32_t palette[256]; + uint8_t dac_index, dac_state; +} TCXState; + +static void tcx_screen_dump(void *opaque, const char *filename); +static void tcx24_screen_dump(void *opaque, const char *filename); + +static void tcx_set_dirty(TCXState *s) +{ + unsigned int i; + + for (i = 0; i < MAXX * MAXY; i += TARGET_PAGE_SIZE) { + cpu_physical_memory_set_dirty(s->vram_offset + i); + } +} + +static void tcx24_set_dirty(TCXState *s) +{ + unsigned int i; + + for (i = 0; i < MAXX * MAXY * 4; i += TARGET_PAGE_SIZE) { + cpu_physical_memory_set_dirty(s->vram24_offset + i); + cpu_physical_memory_set_dirty(s->cplane_offset + i); + } +} + +static void update_palette_entries(TCXState *s, int start, int end) +{ + int i; + for(i = start; i < end; i++) { + switch(ds_get_bits_per_pixel(s->ds)) { + 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(s->ds->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; + } + } + if (s->depth == 24) { + tcx24_set_dirty(s); + } else { + 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]; + } +} + +/* + 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) +{ + int x, bgr, r, g, b; + uint8_t val, *p8; + uint32_t *p = (uint32_t *)d; + uint32_t dval; + + bgr = is_surface_bgr(s1->ds->surface); + for(x = 0; x < width; x++, s++, s24++) { + if ((be32_to_cpu(*cplane++) & 0xff000000) == 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 { + val = *s; + dval = s1->palette[val]; + } + *p++ = dval; + } +} + +static inline int check_dirty(ram_addr_t page, ram_addr_t page24, + ram_addr_t cpage) +{ + int ret; + unsigned int off; + + ret = cpu_physical_memory_get_dirty(page, VGA_DIRTY_FLAG); + for (off = 0; off < TARGET_PAGE_SIZE * 4; off += TARGET_PAGE_SIZE) { + ret |= cpu_physical_memory_get_dirty(page24 + off, VGA_DIRTY_FLAG); + ret |= cpu_physical_memory_get_dirty(cpage + off, VGA_DIRTY_FLAG); + } + return ret; +} + +static inline void reset_dirty(TCXState *ts, ram_addr_t page_min, + ram_addr_t page_max, ram_addr_t page24, + ram_addr_t cpage) +{ + cpu_physical_memory_reset_dirty(page_min, page_max + TARGET_PAGE_SIZE, + VGA_DIRTY_FLAG); + page_min -= ts->vram_offset; + page_max -= ts->vram_offset; + cpu_physical_memory_reset_dirty(page24 + page_min * 4, + page24 + page_max * 4 + TARGET_PAGE_SIZE, + VGA_DIRTY_FLAG); + cpu_physical_memory_reset_dirty(cpage + page_min * 4, + cpage + page_max * 4 + TARGET_PAGE_SIZE, + VGA_DIRTY_FLAG); +} + +/* Fixed line length 1024 allows us to do nice tricks not possible on + VGA... */ +static void tcx_update_display(void *opaque) +{ + TCXState *ts = opaque; + 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); + + if (ds_get_bits_per_pixel(ts->ds) == 0) + return; + page = ts->vram_offset; + y_start = -1; + page_min = -1; + page_max = 0; + d = ds_get_data(ts->ds); + s = ts->vram; + dd = ds_get_linesize(ts->ds); + ds = 1024; + + switch (ds_get_bits_per_pixel(ts->ds)) { + case 32: + f = tcx_draw_line32; + break; + case 15: + case 16: + f = tcx_draw_line16; + break; + default: + case 8: + f = tcx_draw_line8; + break; + case 0: + return; + } + + for(y = 0; y < ts->height; y += 4, page += TARGET_PAGE_SIZE) { + if (cpu_physical_memory_get_dirty(page, VGA_DIRTY_FLAG)) { + 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); + d += dd; + s += ds; + f(ts, d, s, ts->width); + d += dd; + s += ds; + f(ts, d, s, ts->width); + d += dd; + s += ds; + f(ts, d, s, ts->width); + d += dd; + s += ds; + } else { + if (y_start >= 0) { + /* flush to display */ + dpy_update(ts->ds, 0, y_start, + ts->width, y - y_start); + y_start = -1; + } + d += dd * 4; + s += ds * 4; + } + } + if (y_start >= 0) { + /* flush to display */ + dpy_update(ts->ds, 0, y_start, + ts->width, y - y_start); + } + /* reset modified pages */ + if (page_max >= page_min) { + cpu_physical_memory_reset_dirty(page_min, page_max + TARGET_PAGE_SIZE, + VGA_DIRTY_FLAG); + } +} + +static void tcx24_update_display(void *opaque) +{ + TCXState *ts = opaque; + 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 (ds_get_bits_per_pixel(ts->ds) != 32) + return; + page = ts->vram_offset; + page24 = ts->vram24_offset; + cpage = ts->cplane_offset; + y_start = -1; + page_min = -1; + page_max = 0; + d = ds_get_data(ts->ds); + s = ts->vram; + s24 = ts->vram24; + cptr = ts->cplane; + dd = ds_get_linesize(ts->ds); + ds = 1024; + + for(y = 0; y < ts->height; y += 4, page += TARGET_PAGE_SIZE, + page24 += TARGET_PAGE_SIZE, cpage += TARGET_PAGE_SIZE) { + if (check_dirty(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); + d += dd; + s += ds; + cptr += ds; + s24 += ds; + tcx24_draw_line32(ts, d, s, ts->width, cptr, s24); + d += dd; + s += ds; + cptr += ds; + s24 += ds; + tcx24_draw_line32(ts, d, s, ts->width, cptr, s24); + d += dd; + s += ds; + cptr += ds; + s24 += ds; + tcx24_draw_line32(ts, d, s, ts->width, cptr, s24); + d += dd; + s += ds; + cptr += ds; + s24 += ds; + } else { + if (y_start >= 0) { + /* flush to display */ + dpy_update(ts->ds, 0, y_start, + ts->width, y - y_start); + y_start = -1; + } + d += dd * 4; + s += ds * 4; + cptr += ds * 4; + s24 += ds * 4; + } + } + if (y_start >= 0) { + /* flush to display */ + dpy_update(ts->ds, 0, y_start, + ts->width, y - y_start); + } + /* reset modified pages */ + if (page_max >= page_min) { + 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->ds, s->width, s->height); +} + +static void tcx24_invalidate_display(void *opaque) +{ + TCXState *s = opaque; + + tcx_set_dirty(s); + tcx24_set_dirty(s); + qemu_console_resize(s->ds, s->width, s->height); +} + +static int vmstate_tcx_post_load(void *opaque, int version_id) +{ + TCXState *s = opaque; + + update_palette_entries(s, 0, 256); + if (s->depth == 24) { + tcx24_set_dirty(s); + } else { + tcx_set_dirty(s); + } + + return 0; +} + +static const VMStateDescription vmstate_tcx = { + .name ="tcx", + .version_id = 4, + .minimum_version_id = 4, + .minimum_version_id_old = 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 = container_of(d, TCXState, busdev.qdev); + + /* Initialize palette */ + memset(s->r, 0, 256); + memset(s->g, 0, 256); + memset(s->b, 0, 256); + s->r[255] = s->g[255] = s->b[255] = 255; + update_palette_entries(s, 0, 256); + memset(s->vram, 0, MAXX*MAXY); + cpu_physical_memory_reset_dirty(s->vram_offset, s->vram_offset + + MAXX * MAXY * (1 + 4 + 4), VGA_DIRTY_FLAG); + s->dac_index = 0; + s->dac_state = 0; +} + +static uint32_t tcx_dac_readl(void *opaque, target_phys_addr_t addr) +{ + return 0; +} + +static void tcx_dac_writel(void *opaque, target_phys_addr_t addr, uint32_t val) +{ + TCXState *s = opaque; + + switch (addr) { + case 0: + s->dac_index = val >> 24; + s->dac_state = 0; + break; + case 4: + switch (s->dac_state) { + case 0: + s->r[s->dac_index] = val >> 24; + update_palette_entries(s, s->dac_index, s->dac_index + 1); + s->dac_state++; + break; + case 1: + s->g[s->dac_index] = val >> 24; + update_palette_entries(s, s->dac_index, s->dac_index + 1); + s->dac_state++; + break; + case 2: + s->b[s->dac_index] = val >> 24; + update_palette_entries(s, s->dac_index, s->dac_index + 1); + s->dac_index = (s->dac_index + 1) & 255; // Index autoincrement + default: + s->dac_state = 0; + break; + } + break; + default: + break; + } + return; +} + +static CPUReadMemoryFunc * const tcx_dac_read[3] = { + NULL, + NULL, + tcx_dac_readl, +}; + +static CPUWriteMemoryFunc * const tcx_dac_write[3] = { + NULL, + NULL, + tcx_dac_writel, +}; + +static uint32_t tcx_dummy_readl(void *opaque, target_phys_addr_t addr) +{ + return 0; +} + +static void tcx_dummy_writel(void *opaque, target_phys_addr_t addr, + uint32_t val) +{ +} + +static CPUReadMemoryFunc * const tcx_dummy_read[3] = { + NULL, + NULL, + tcx_dummy_readl, +}; + +static CPUWriteMemoryFunc * const tcx_dummy_write[3] = { + NULL, + NULL, + tcx_dummy_writel, +}; + +static int tcx_init1(SysBusDevice *dev) +{ + TCXState *s = FROM_SYSBUS(TCXState, dev); + int io_memory, dummy_memory; + ram_addr_t vram_offset; + int size; + uint8_t *vram_base; + + vram_offset = qemu_ram_alloc(NULL, "tcx.vram", s->vram_size * (1 + 4 + 4)); + vram_base = qemu_get_ram_ptr(vram_offset); + s->vram_offset = vram_offset; + + /* 8-bit plane */ + s->vram = vram_base; + size = s->vram_size; + sysbus_init_mmio(dev, size, s->vram_offset); + vram_offset += size; + vram_base += size; + + /* DAC */ + io_memory = cpu_register_io_memory(tcx_dac_read, tcx_dac_write, s, + DEVICE_NATIVE_ENDIAN); + sysbus_init_mmio(dev, TCX_DAC_NREGS, io_memory); + + /* TEC (dummy) */ + dummy_memory = cpu_register_io_memory(tcx_dummy_read, tcx_dummy_write, + s, DEVICE_NATIVE_ENDIAN); + sysbus_init_mmio(dev, TCX_TEC_NREGS, dummy_memory); + /* THC: NetBSD writes here even with 8-bit display: dummy */ + sysbus_init_mmio(dev, TCX_THC_NREGS_24, dummy_memory); + + if (s->depth == 24) { + /* 24-bit plane */ + size = s->vram_size * 4; + s->vram24 = (uint32_t *)vram_base; + s->vram24_offset = vram_offset; + sysbus_init_mmio(dev, size, vram_offset); + vram_offset += size; + vram_base += size; + + /* Control plane */ + size = s->vram_size * 4; + s->cplane = (uint32_t *)vram_base; + s->cplane_offset = vram_offset; + sysbus_init_mmio(dev, size, vram_offset); + + s->ds = graphic_console_init(tcx24_update_display, + tcx24_invalidate_display, + tcx24_screen_dump, NULL, s); + } else { + /* THC 8 bit (dummy) */ + sysbus_init_mmio(dev, TCX_THC_NREGS_8, dummy_memory); + + s->ds = graphic_console_init(tcx_update_display, + tcx_invalidate_display, + tcx_screen_dump, NULL, s); + } + + qemu_console_resize(s->ds, s->width, s->height); + return 0; +} + +static void tcx_screen_dump(void *opaque, const char *filename) +{ + TCXState *s = opaque; + FILE *f; + uint8_t *d, *d1, v; + int y, x; + + f = fopen(filename, "wb"); + if (!f) + return; + fprintf(f, "P6\n%d %d\n%d\n", s->width, s->height, 255); + d1 = s->vram; + for(y = 0; y < s->height; y++) { + d = d1; + for(x = 0; x < s->width; x++) { + v = *d; + fputc(s->r[v], f); + fputc(s->g[v], f); + fputc(s->b[v], f); + d++; + } + d1 += MAXX; + } + fclose(f); + return; +} + +static void tcx24_screen_dump(void *opaque, const char *filename) +{ + TCXState *s = opaque; + FILE *f; + uint8_t *d, *d1, v; + uint32_t *s24, *cptr, dval; + int y, x; + + f = fopen(filename, "wb"); + if (!f) + return; + fprintf(f, "P6\n%d %d\n%d\n", s->width, s->height, 255); + d1 = s->vram; + s24 = s->vram24; + cptr = s->cplane; + for(y = 0; y < s->height; y++) { + d = d1; + for(x = 0; x < s->width; x++, d++, s24++) { + if ((*cptr++ & 0xff000000) == 0x03000000) { // 24-bit direct + dval = *s24 & 0x00ffffff; + fputc((dval >> 16) & 0xff, f); + fputc((dval >> 8) & 0xff, f); + fputc(dval & 0xff, f); + } else { + v = *d; + fputc(s->r[v], f); + fputc(s->g[v], f); + fputc(s->b[v], f); + } + } + d1 += MAXX; + } + fclose(f); + return; +} + +static SysBusDeviceInfo tcx_info = { + .init = tcx_init1, + .qdev.name = "SUNW,tcx", + .qdev.size = sizeof(TCXState), + .qdev.reset = tcx_reset, + .qdev.vmsd = &vmstate_tcx, + .qdev.props = (Property[]) { + DEFINE_PROP_TADDR("addr", TCXState, addr, -1), + DEFINE_PROP_HEX32("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_END_OF_LIST(), + } +}; + +static void tcx_register_devices(void) +{ + sysbus_register_withprop(&tcx_info); +} + +device_init(tcx_register_devices) diff --git a/hw/tmp105.c b/hw/tmp105.c new file mode 100644 index 0000000..f7e6f2b --- /dev/null +++ b/hw/tmp105.c @@ -0,0 +1,244 @@ +/* + * Texas Instruments TMP105 temperature sensor. + * + * Copyright (C) 2008 Nokia Corporation + * Written by Andrzej Zaborowski + * + * 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 . + */ + +#include "hw.h" +#include "i2c.h" + +typedef struct { + i2c_slave i2c; + 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; + +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); +} + +/* Units are 0.001 centigrades relative to 0 C. */ +void tmp105_set(i2c_slave *i2c, int temp) +{ + TMP105State *s = (TMP105State *) i2c; + + if (temp >= 128000 || temp < -128000) { + fprintf(stderr, "%s: values is out of range (%i.%03i C)\n", + __FUNCTION__, temp / 1000, temp % 1000); + exit(-1); + } + + s->temperature = ((int16_t) (temp * 0x800 / 128000)) << 4; + + 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 0: /* 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 1: /* Configuration */ + s->buf[s->len ++] = s->config; + break; + + case 2: /* T_LOW */ + s->buf[s->len ++] = ((uint16_t) s->limit[0]) >> 8; + s->buf[s->len ++] = ((uint16_t) s->limit[0]) >> 0; + break; + + case 3: /* 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 0: /* Temperature */ + break; + + case 1: /* Configuration */ + 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 2: /* T_LOW */ + case 3: /* 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(i2c_slave *i2c) +{ + TMP105State *s = (TMP105State *) i2c; + + if (s->len < 2) + return s->buf[s->len ++]; + else + return 0xff; +} + +static int tmp105_tx(i2c_slave *i2c, uint8_t data) +{ + TMP105State *s = (TMP105State *) i2c; + + if (!s->len ++) + s->pointer = data; + else { + if (s->len <= 2) + s->buf[s->len - 1] = data; + tmp105_write(s); + } + + return 0; +} + +static void tmp105_event(i2c_slave *i2c, enum i2c_event event) +{ + TMP105State *s = (TMP105State *) 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, + .minimum_version_id_old = 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(i2c_slave *i2c) +{ + TMP105State *s = (TMP105State *) 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(i2c_slave *i2c) +{ + TMP105State *s = FROM_I2C_SLAVE(TMP105State, i2c); + + qdev_init_gpio_out(&i2c->qdev, &s->pin, 1); + + tmp105_reset(&s->i2c); + + return 0; +} + +static I2CSlaveInfo tmp105_info = { + .qdev.name = "tmp105", + .qdev.size = sizeof(TMP105State), + .qdev.vmsd = &vmstate_tmp105, + .init = tmp105_init, + .event = tmp105_event, + .recv = tmp105_rx, + .send = tmp105_tx +}; + +static void tmp105_register_devices(void) +{ + i2c_register_slave(&tmp105_info); +} + +device_init(tmp105_register_devices) diff --git a/hw/tosa.c b/hw/tosa.c new file mode 100644 index 0000000..0bfab16 --- /dev/null +++ b/hw/tosa.c @@ -0,0 +1,277 @@ +/* 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 + * This code is licensed under the GNU GPL v2. + */ + +#include "hw.h" +#include "pxa.h" +#include "arm-misc.h" +#include "sysemu.h" +#include "devices.h" +#include "sharpsl.h" +#include "pcmcia.h" +#include "block.h" +#include "boards.h" +#include "i2c.h" +#include "ssi.h" +#include "blockdev.h" +#include "sysbus.h" + +#define TOSA_RAM 0x04000000 +#define TOSA_ROM 0x00800000 + +#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; + BlockDriverState *bs; + DriveInfo *dinfo; + + dinfo = drive_get(IF_IDE, 0, 0); + if (!dinfo) + return; + bs = dinfo->bdrv; + if (bdrv_is_inserted(bs) && !bdrv_is_removable(bs)) { + 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)); +} + +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; +} + +typedef struct { + i2c_slave i2c; + int len; + char buf[3]; +} TosaDACState; + +static int tosa_dac_send(i2c_slave *i2c, uint8_t data) +{ + TosaDACState *s = FROM_I2C_SLAVE(TosaDACState, 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(i2c_slave *i2c, enum i2c_event event) +{ + TosaDACState *s = FROM_I2C_SLAVE(TosaDACState, 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(i2c_slave *s) +{ + printf("%s: recv not supported!!!\n", __FUNCTION__); + return -1; +} + +static int tosa_dac_init(i2c_slave *i2c) +{ + /* Nothing to do. */ + return 0; +} + +static void tosa_tg_init(PXA2xxState *cpu) +{ + i2c_bus *bus = pxa2xx_i2c_bus(cpu->i2c[0]); + i2c_create_slave(bus, "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(ram_addr_t ram_size, + const char *boot_device, + const char *kernel_filename, const char *kernel_cmdline, + const char *initrd_filename, const char *cpu_model) +{ + PXA2xxState *cpu; + TC6393xbState *tmio; + DeviceState *scp0, *scp1; + + if (!cpu_model) + cpu_model = "pxa255"; + + cpu = pxa255_init(tosa_binfo.ram_size); + + cpu_register_physical_memory(0, TOSA_ROM, + qemu_ram_alloc(NULL, "tosa.rom", TOSA_ROM) | IO_MEM_ROM); + + tmio = tc6393xb_init(0x10000000, + qdev_get_gpio_in(cpu->gpio, TOSA_GPIO_TC6393XB_INT)); + + scp0 = sysbus_create_simple("scoop", 0x08800000, NULL); + scp1 = sysbus_create_simple("scoop", 0x14800040, NULL); + + tosa_gpio_setup(cpu, scp0, scp1, tmio); + + tosa_microdrive_attach(cpu); + + tosa_tg_init(cpu); + + 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(cpu->env, &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 I2CSlaveInfo tosa_dac_info = { + .qdev.name = "tosa_dac", + .qdev.size = sizeof(TosaDACState), + .init = tosa_dac_init, + .event = tosa_dac_event, + .recv = tosa_dac_recv, + .send = tosa_dac_send +}; + +static SSISlaveInfo tosa_ssp_info = { + .qdev.name = "tosa-ssp", + .qdev.size = sizeof(SSISlave), + .init = tosa_ssp_init, + .transfer = tosa_ssp_tansfer +}; + +static void tosa_register_devices(void) +{ + i2c_register_slave(&tosa_dac_info); + ssi_register_slave(&tosa_ssp_info); +} + +device_init(tosa_register_devices) diff --git a/hw/tsc2005.c b/hw/tsc2005.c new file mode 100644 index 0000000..a55853c --- /dev/null +++ b/hw/tsc2005.c @@ -0,0 +1,593 @@ +/* + * TI TSC2005 emulator. + * + * Copyright (c) 2006 Andrzej Zaborowski + * 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 . + */ + +#include "hw.h" +#include "qemu-timer.h" +#include "console.h" +#include "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) + qemu_del_timer(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_get_clock(vm_clock) + (get_ticks_per_sec() >> 7); + qemu_mod_timer(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) + qemu_del_timer(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); + + qemu_put_timer(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); + + qemu_get_timer(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 = qemu_timer_pending(s->timer); + tsc2005_pin_update(s); + + return 0; +} + +void *tsc2005_init(qemu_irq pintdav) +{ + TSC2005State *s; + + s = (TSC2005State *) + qemu_mallocz(sizeof(TSC2005State)); + s->x = 400; + s->y = 240; + s->pressure = 0; + s->precision = s->nextprecision = 0; + s->timer = qemu_new_timer(vm_clock, 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/tsc210x.c b/hw/tsc210x.c new file mode 100644 index 0000000..fca73f1 --- /dev/null +++ b/hw/tsc210x.c @@ -0,0 +1,1293 @@ +/* + * TI TSC2102 (touchscreen/sensors/audio controller) emulator. + * TI TSC2301 (touchscreen/sensors/keypad). + * + * Copyright (c) 2006 Andrzej Zaborowski + * 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 . + */ + +#include "hw.h" +#include "audio/audio.h" +#include "qemu-timer.h" +#include "console.h" +#include "omap.h" /* For I2SCodec and uWireSlave */ +#include "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 tsc2101_rates[] = { + /* Fsref / 6.0 */ + { 7350, 7, 1 }, + { 8000, 7, 0 }, + /* Fsref / 5.5 */ + { 8018, 6, 1 }, + { 8727, 6, 0 }, + /* Fsref / 5.0 */ + { 8820, 5, 1 }, + { 9600, 5, 0 }, + /* Fsref / 4.0 */ + { 11025, 4, 1 }, + { 12000, 4, 0 }, + /* Fsref / 3.0 */ + { 14700, 3, 1 }, + { 16000, 3, 0 }, + /* Fsref / 2.0 */ + { 22050, 2, 1 }, + { 24000, 2, 0 }, + /* Fsref / 1.5 */ + { 29400, 1, 1 }, + { 32000, 1, 0 }, + /* Fsref */ + { 44100, 0, 1 }, + { 48000, 0, 0 }, + + { 0, 0, 0 }, +}; + +/* { 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_get_clock(vm_clock) > + s->volume_change + TSC_SOFTSTEP_DELAY); + r_ch = (qemu_get_clock(vm_clock) > + 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_get_clock(vm_clock) > + 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) + qemu_del_timer(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) + qemu_del_timer(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_get_clock(vm_clock); + 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_get_clock(vm_clock); + + 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_get_clock(vm_clock) + (get_ticks_per_sec() >> 10); + qemu_mod_timer(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_get_clock(vm_clock); + 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); + + qemu_put_timer(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_get_clock(vm_clock); + 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); + + qemu_get_timer(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_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 = qemu_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 *) + qemu_mallocz(sizeof(TSC210xState)); + memset(s, 0, sizeof(TSC210xState)); + s->x = 160; + s->y = 160; + s->pressure = 0; + s->precision = s->nextprecision = 0; + s->timer = qemu_new_timer(vm_clock, 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 *) + qemu_mallocz(sizeof(TSC210xState)); + memset(s, 0, sizeof(TSC210xState)); + s->x = 400; + s->y = 240; + s->pressure = 0; + s->precision = s->nextprecision = 0; + s->timer = qemu_new_timer(vm_clock, 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/tusb6010.c b/hw/tusb6010.c new file mode 100644 index 0000000..0005e1c --- /dev/null +++ b/hw/tusb6010.c @@ -0,0 +1,766 @@ +/* + * Texas Instruments TUSB6010 emulation. + * Based on reverse-engineering of a linux driver. + * + * Copyright (C) 2008 Nokia Corporation + * Written by Andrzej Zaborowski + * + * 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 . + */ +#include "qemu-common.h" +#include "qemu-timer.h" +#include "usb.h" +#include "omap.h" +#include "irq.h" +#include "devices.h" + +struct TUSBState { + int iomemtype[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; +}; + +#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 + +int tusb6010_sync_io(TUSBState *s) +{ + return s->iomemtype[0]; +} + +int tusb6010_async_io(TUSBState *s) +{ + return s->iomemtype[1]; +} + +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? */ +} + +extern CPUReadMemoryFunc * const musb_read[]; +extern CPUWriteMemoryFunc * const musb_write[]; + +static uint32_t tusb_async_readb(void *opaque, target_phys_addr_t 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, target_phys_addr_t 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, target_phys_addr_t 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, target_phys_addr_t 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, target_phys_addr_t 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, target_phys_addr_t 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) + qemu_mod_timer(s->otg_timer, qemu_get_clock(vm_clock) + + muldiv64(TUSB_DEV_OTG_TIMER_VAL(value), + get_ticks_per_sec(), TUSB_DEVCLOCK)); + else + qemu_del_timer(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 CPUReadMemoryFunc * const tusb_async_readfn[] = { + tusb_async_readb, + tusb_async_readh, + tusb_async_readw, +}; + +static CPUWriteMemoryFunc * const tusb_async_writefn[] = { + tusb_async_writeb, + tusb_async_writeh, + tusb_async_writew, +}; + +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; + } +} + +TUSBState *tusb6010_init(qemu_irq intr) +{ + TUSBState *s = qemu_mallocz(sizeof(*s)); + + 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->iomemtype[1] = cpu_register_io_memory(tusb_async_readfn, + tusb_async_writefn, s, DEVICE_NATIVE_ENDIAN); + s->irq = intr; + s->otg_timer = qemu_new_timer(vm_clock, tusb_otg_tick, s); + s->pwr_timer = qemu_new_timer(vm_clock, tusb_power_tick, s); + s->musb = musb_init(qemu_allocate_irqs(tusb_musb_core_intr, s, + __musb_irq_max)); + + return s; +} + +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); + qemu_mod_timer(s->pwr_timer, + qemu_get_clock(vm_clock) + get_ticks_per_sec() / 2); + } +} diff --git a/hw/twl92230.c b/hw/twl92230.c new file mode 100644 index 0000000..e61f17f --- /dev/null +++ b/hw/twl92230.c @@ -0,0 +1,876 @@ +/* + * TI TWL92230C energy-management companion device for the OMAP24xx. + * Aka. Menelaus (N4200 MENELAUS1_V2.2) + * + * Copyright (C) 2008 Nokia Corporation + * Written by Andrzej Zaborowski + * + * 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 . + */ + +#include "hw.h" +#include "qemu-timer.h" +#include "i2c.h" +#include "sysemu.h" +#include "console.h" + +#define VERBOSE 1 + +typedef struct { + i2c_slave i2c; + + 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]; + qemu_irq *in; + uint8_t pwrbtn_state; + qemu_irq pwrbtn; +} 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_get_clock(rt_clock); + qemu_mod_timer(s->rtc.hz_tm, s->rtc.next); +} + +static inline void menelaus_rtc_stop(MenelausState *s) +{ + qemu_del_timer(s->rtc.hz_tm); + s->rtc.next -= qemu_get_clock(rt_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; + qemu_mod_timer(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(i2c_slave *i2c) +{ + MenelausState *s = (MenelausState *) 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; + + /* No interrupt generated */ + s->inputs &= ~(1 << line); + s->inputs |= level << line; +} + +static void menelaus_pwrbtn_set(void *opaque, int line, int level) +{ + MenelausState *s = (MenelausState *) opaque; + + 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(&s->i2c); + 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(i2c_slave *i2c, enum i2c_event event) +{ + MenelausState *s = (MenelausState *) i2c; + + if (event == I2C_START_SEND) + s->firstbyte = 1; +} + +static int menelaus_tx(i2c_slave *i2c, uint8_t data) +{ + MenelausState *s = (MenelausState *) 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(i2c_slave *i2c) +{ + MenelausState *s = (MenelausState *) 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, + .minimum_version_id_old = 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_get_clock(rt_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, + .minimum_version_id_old = 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(i2c, MenelausState), + VMSTATE_END_OF_LIST() + } +}; + +static int twl92230_init(i2c_slave *i2c) +{ + MenelausState *s = FROM_I2C_SLAVE(MenelausState, i2c); + + s->rtc.hz_tm = qemu_new_timer(rt_clock, menelaus_rtc_hz, s); + /* Three output pins plus one interrupt pin. */ + qdev_init_gpio_out(&i2c->qdev, s->out, 4); + qdev_init_gpio_in(&i2c->qdev, menelaus_gpio_set, 3); + s->pwrbtn = qemu_allocate_irqs(menelaus_pwrbtn_set, s, 1)[0]; + + menelaus_reset(&s->i2c); + + return 0; +} + +static I2CSlaveInfo twl92230_info = { + .qdev.name ="twl92230", + .qdev.size = sizeof(MenelausState), + .qdev.vmsd = &vmstate_menelaus, + .init = twl92230_init, + .event = menelaus_event, + .recv = menelaus_rx, + .send = menelaus_tx +}; + +static void twl92230_register_devices(void) +{ + i2c_register_slave(&twl92230_info); +} + +device_init(twl92230_register_devices) diff --git a/hw/unin_pci.c b/hw/unin_pci.c new file mode 100644 index 0000000..5f15058 --- /dev/null +++ b/hw/unin_pci.c @@ -0,0 +1,391 @@ +/* + * 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.h" +#include "ppc_mac.h" +#include "pci.h" +#include "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 }; + +typedef struct UNINState { + SysBusDevice busdev; + PCIHostState host_state; + ReadWriteHandler data_handler; +} 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 void pci_unin_save(QEMUFile* f, void *opaque) +{ + PCIDevice *d = opaque; + + pci_device_save(d, f); +} + +static int pci_unin_load(QEMUFile* f, void *opaque, int version_id) +{ + PCIDevice *d = opaque; + + if (version_id != 1) + return -EINVAL; + + return pci_device_load(d, f); +} + +static void pci_unin_reset(void *opaque) +{ +} + +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 = ffs(reg & 0xfffff800) - 1; + 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(ReadWriteHandler *handler, + pcibus_t addr, uint32_t val, int len) +{ + UNINState *s = container_of(handler, UNINState, data_handler); + UNIN_DPRINTF("write addr %" FMT_PCIBUS " len %d val %x\n", addr, len, val); + pci_data_write(s->host_state.bus, + unin_get_config_reg(s->host_state.config_reg, addr), + val, len); +} + +static uint32_t unin_data_read(ReadWriteHandler *handler, + pcibus_t addr, int len) +{ + UNINState *s = container_of(handler, UNINState, data_handler); + uint32_t val; + + val = pci_data_read(s->host_state.bus, + unin_get_config_reg(s->host_state.config_reg, addr), + len); + UNIN_DPRINTF("read addr %" FMT_PCIBUS " len %d val %x\n", addr, len, val); + return val; +} + +static int pci_unin_main_init_device(SysBusDevice *dev) +{ + UNINState *s; + int pci_mem_config, pci_mem_data; + + /* Use values found on a real PowerMac */ + /* Uninorth main bus */ + s = FROM_SYSBUS(UNINState, dev); + + pci_mem_config = pci_host_conf_register_mmio(&s->host_state, + DEVICE_LITTLE_ENDIAN); + s->data_handler.read = unin_data_read; + s->data_handler.write = unin_data_write; + pci_mem_data = cpu_register_io_memory_simple(&s->data_handler, + DEVICE_LITTLE_ENDIAN); + sysbus_init_mmio(dev, 0x1000, pci_mem_config); + sysbus_init_mmio(dev, 0x1000, pci_mem_data); + + register_savevm(&dev->qdev, "uninorth", 0, 1, + pci_unin_save, pci_unin_load, &s->host_state); + qemu_register_reset(pci_unin_reset, &s->host_state); + return 0; +} + +static int pci_u3_agp_init_device(SysBusDevice *dev) +{ + UNINState *s; + int pci_mem_config, pci_mem_data; + + /* Uninorth U3 AGP bus */ + s = FROM_SYSBUS(UNINState, dev); + + pci_mem_config = pci_host_conf_register_mmio(&s->host_state, + DEVICE_LITTLE_ENDIAN); + s->data_handler.read = unin_data_read; + s->data_handler.write = unin_data_write; + pci_mem_data = cpu_register_io_memory_simple(&s->data_handler, + DEVICE_LITTLE_ENDIAN); + sysbus_init_mmio(dev, 0x1000, pci_mem_config); + sysbus_init_mmio(dev, 0x1000, pci_mem_data); + + register_savevm(&dev->qdev, "uninorth", 0, 1, + pci_unin_save, pci_unin_load, &s->host_state); + qemu_register_reset(pci_unin_reset, &s->host_state); + + return 0; +} + +static int pci_unin_agp_init_device(SysBusDevice *dev) +{ + UNINState *s; + int pci_mem_config, pci_mem_data; + + /* Uninorth AGP bus */ + s = FROM_SYSBUS(UNINState, dev); + + pci_mem_config = pci_host_conf_register_mmio(&s->host_state, + DEVICE_LITTLE_ENDIAN); + pci_mem_data = pci_host_data_register_mmio(&s->host_state, + DEVICE_LITTLE_ENDIAN); + sysbus_init_mmio(dev, 0x1000, pci_mem_config); + sysbus_init_mmio(dev, 0x1000, pci_mem_data); + return 0; +} + +static int pci_unin_internal_init_device(SysBusDevice *dev) +{ + UNINState *s; + int pci_mem_config, pci_mem_data; + + /* Uninorth internal bus */ + s = FROM_SYSBUS(UNINState, dev); + + pci_mem_config = pci_host_conf_register_mmio(&s->host_state, + DEVICE_LITTLE_ENDIAN); + pci_mem_data = pci_host_data_register_mmio(&s->host_state, + DEVICE_LITTLE_ENDIAN); + sysbus_init_mmio(dev, 0x1000, pci_mem_config); + sysbus_init_mmio(dev, 0x1000, pci_mem_data); + return 0; +} + +PCIBus *pci_pmac_init(qemu_irq *pic) +{ + DeviceState *dev; + SysBusDevice *s; + UNINState *d; + + /* Use values found on a real PowerMac */ + /* Uninorth main bus */ + dev = qdev_create(NULL, "uni-north"); + qdev_init_nofail(dev); + s = sysbus_from_qdev(dev); + d = FROM_SYSBUS(UNINState, s); + d->host_state.bus = pci_register_bus(&d->busdev.qdev, "pci", + pci_unin_set_irq, pci_unin_map_irq, + pic, PCI_DEVFN(11, 0), 4); + +#if 0 + pci_create_simple(d->host_state.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(d->host_state.bus, PCI_DEVFN(12, 0), "dec-21154"); +#endif + + /* Uninorth AGP bus */ + pci_create_simple(d->host_state.bus, PCI_DEVFN(11, 0), "uni-north-agp"); + dev = qdev_create(NULL, "uni-north-agp"); + qdev_init_nofail(dev); + s = sysbus_from_qdev(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(d->host_state.bus, PCI_DEVFN(14, 0), "uni-north-pci"); + dev = qdev_create(NULL, "uni-north-pci"); + qdev_init_nofail(dev); + s = sysbus_from_qdev(dev); + sysbus_mmio_map(s, 0, 0xf4800000); + sysbus_mmio_map(s, 1, 0xf4c00000); +#endif + + return d->host_state.bus; +} + +PCIBus *pci_pmac_u3_init(qemu_irq *pic) +{ + DeviceState *dev; + SysBusDevice *s; + UNINState *d; + + /* Uninorth AGP bus */ + + dev = qdev_create(NULL, "u3-agp"); + qdev_init_nofail(dev); + s = sysbus_from_qdev(dev); + d = FROM_SYSBUS(UNINState, s); + + d->host_state.bus = pci_register_bus(&d->busdev.qdev, "pci", + pci_unin_set_irq, pci_unin_map_irq, + pic, PCI_DEVFN(11, 0), 4); + + sysbus_mmio_map(s, 0, 0xf0800000); + sysbus_mmio_map(s, 1, 0xf0c00000); + + pci_create_simple(d->host_state.bus, 11 << 3, "u3-agp"); + + return d->host_state.bus; +} + +static int unin_main_pci_host_init(PCIDevice *d) +{ + pci_config_set_vendor_id(d->config, PCI_VENDOR_ID_APPLE); + pci_config_set_device_id(d->config, PCI_DEVICE_ID_APPLE_UNI_N_PCI); + d->config[0x08] = 0x00; // revision + pci_config_set_class(d->config, PCI_CLASS_BRIDGE_HOST); + d->config[0x0C] = 0x08; // cache_line_size + d->config[0x0D] = 0x10; // latency_timer + d->config[0x34] = 0x00; // capabilities_pointer + return 0; +} + +static int unin_agp_pci_host_init(PCIDevice *d) +{ + pci_config_set_vendor_id(d->config, PCI_VENDOR_ID_APPLE); + pci_config_set_device_id(d->config, PCI_DEVICE_ID_APPLE_UNI_N_AGP); + d->config[0x08] = 0x00; // revision + pci_config_set_class(d->config, PCI_CLASS_BRIDGE_HOST); + d->config[0x0C] = 0x08; // cache_line_size + d->config[0x0D] = 0x10; // latency_timer + // d->config[0x34] = 0x80; // capabilities_pointer + return 0; +} + +static int u3_agp_pci_host_init(PCIDevice *d) +{ + pci_config_set_vendor_id(d->config, PCI_VENDOR_ID_APPLE); + pci_config_set_device_id(d->config, PCI_DEVICE_ID_APPLE_U3_AGP); + /* revision */ + d->config[0x08] = 0x00; + pci_config_set_class(d->config, PCI_CLASS_BRIDGE_HOST); + /* cache line size */ + d->config[0x0C] = 0x08; + /* latency timer */ + d->config[0x0D] = 0x10; + return 0; +} + +static int unin_internal_pci_host_init(PCIDevice *d) +{ + pci_config_set_vendor_id(d->config, PCI_VENDOR_ID_APPLE); + pci_config_set_device_id(d->config, PCI_DEVICE_ID_APPLE_UNI_N_I_PCI); + d->config[0x08] = 0x00; // revision + pci_config_set_class(d->config, PCI_CLASS_BRIDGE_HOST); + d->config[0x0C] = 0x08; // cache_line_size + d->config[0x0D] = 0x10; // latency_timer + d->config[0x34] = 0x00; // capabilities_pointer + return 0; +} + +static PCIDeviceInfo unin_main_pci_host_info = { + .qdev.name = "uni-north", + .qdev.size = sizeof(PCIDevice), + .init = unin_main_pci_host_init, +}; + +static PCIDeviceInfo u3_agp_pci_host_info = { + .qdev.name = "u3-agp", + .qdev.size = sizeof(PCIDevice), + .init = u3_agp_pci_host_init, +}; + +static PCIDeviceInfo unin_agp_pci_host_info = { + .qdev.name = "uni-north-agp", + .qdev.size = sizeof(PCIDevice), + .init = unin_agp_pci_host_init, +}; + +static PCIDeviceInfo unin_internal_pci_host_info = { + .qdev.name = "uni-north-pci", + .qdev.size = sizeof(PCIDevice), + .init = unin_internal_pci_host_init, +}; + +static void unin_register_devices(void) +{ + sysbus_register_dev("uni-north", sizeof(UNINState), + pci_unin_main_init_device); + pci_qdev_register(&unin_main_pci_host_info); + sysbus_register_dev("u3-agp", sizeof(UNINState), + pci_u3_agp_init_device); + pci_qdev_register(&u3_agp_pci_host_info); + sysbus_register_dev("uni-north-agp", sizeof(UNINState), + pci_unin_agp_init_device); + pci_qdev_register(&unin_agp_pci_host_info); + sysbus_register_dev("uni-north-pci", sizeof(UNINState), + pci_unin_internal_init_device); + pci_qdev_register(&unin_internal_pci_host_info); +} + +device_init(unin_register_devices) diff --git a/hw/usb-bt.c b/hw/usb-bt.c new file mode 100644 index 0000000..22e6845 --- /dev/null +++ b/hw/usb-bt.c @@ -0,0 +1,568 @@ +/* + * QEMU Bluetooth HCI USB Transport Layer v1.0 + * + * Copyright (C) 2007 OpenMoko, Inc. + * Copyright (C) 2008 Andrzej Zaborowski + * + * 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 . + */ + +#include "qemu-common.h" +#include "usb.h" +#include "usb-desc.h" +#include "net.h" +#include "bt.h" + +struct USBBtState { + USBDevice dev; + struct HCIInfo *hci; + + int altsetting; + 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 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 " QEMU_VERSION, + [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_INT, + .wMaxPacketSize = 0, + .bInterval = 0x01, + }, + { + .bEndpointAddress = USB_DIR_IN | USB_SCO_EP, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .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_INT, + .wMaxPacketSize = 0x09, + .bInterval = 0x01, + }, + { + .bEndpointAddress = USB_DIR_IN | USB_SCO_EP, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .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_INT, + .wMaxPacketSize = 0x11, + .bInterval = 0x01, + }, + { + .bEndpointAddress = USB_DIR_IN | USB_SCO_EP, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .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_INT, + .wMaxPacketSize = 0x19, + .bInterval = 0x01, + }, + { + .bEndpointAddress = USB_DIR_IN | USB_SCO_EP, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .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_INT, + .wMaxPacketSize = 0x21, + .bInterval = 0x01, + }, + { + .bEndpointAddress = USB_DIR_IN | USB_SCO_EP, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .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_INT, + .wMaxPacketSize = 0x31, + .bInterval = 0x01, + }, + { + .bEndpointAddress = USB_DIR_IN | USB_SCO_EP, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .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 = 0xc0, + .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 int usb_bt_fifo_dequeue(struct usb_hci_in_fifo_s *fifo, + USBPacket *p) +{ + int len; + + if (likely(!fifo->len)) + return USB_RET_STALL; + + len = MIN(p->len, fifo->fifo[fifo->start].len); + memcpy(p->data, fifo->fifo[fifo->start].data, len); + if (len == p->len) { + 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; + } + + return len; +} + +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), + const uint8_t *data, int len) +{ + if (fifo->len) { + memcpy(fifo->data + fifo->len, data, len); + fifo->len += len; + if (complete(fifo->data, fifo->len)) { + send(s->hci, fifo->data, fifo->len); + fifo->len = 0; + } + } else if (complete(data, len)) + send(s->hci, data, len); + else { + memcpy(fifo->data, data, len); + fifo->len = len; + } + + /* 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; + s->altsetting = 0; +} + +static int usb_bt_handle_control(USBDevice *dev, 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, 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 ret; + } + + ret = 0; + switch (request) { + case InterfaceRequest | USB_REQ_GET_STATUS: + case EndpointRequest | USB_REQ_GET_STATUS: + data[0] = 0x00; + data[1] = 0x00; + ret = 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 InterfaceRequest | USB_REQ_GET_INTERFACE: + if (value != 0 || (index & ~1) || length != 1) + goto fail; + if (index == 1) + data[0] = s->altsetting; + else + data[0] = 0; + ret = 1; + break; + case InterfaceOutRequest | USB_REQ_SET_INTERFACE: + if ((index & ~1) || length != 0 || + (index == 1 && (value < 0 || value > 4)) || + (index == 0 && value != 0)) { + printf("%s: Wrong SET_INTERFACE request (%i, %i)\n", + __FUNCTION__, index, value); + goto fail; + } + s->altsetting = value; + ret = 0; + 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, data, length); + break; + default: + fail: + ret = USB_RET_STALL; + break; + } + return ret; +} + +static int usb_bt_handle_data(USBDevice *dev, USBPacket *p) +{ + struct USBBtState *s = (struct USBBtState *) dev->opaque; + int ret = 0; + + if (!s->config) + goto fail; + + switch (p->pid) { + case USB_TOKEN_IN: + switch (p->devep & 0xf) { + case USB_EVT_EP: + ret = usb_bt_fifo_dequeue(&s->evt, p); + break; + + case USB_ACL_EP: + ret = usb_bt_fifo_dequeue(&s->acl, p); + break; + + case USB_SCO_EP: + ret = usb_bt_fifo_dequeue(&s->sco, p); + break; + + default: + goto fail; + } + break; + + case USB_TOKEN_OUT: + switch (p->devep & 0xf) { + case USB_ACL_EP: + usb_bt_fifo_out_enqueue(s, &s->outacl, s->hci->acl_send, + usb_bt_hci_acl_complete, p->data, p->len); + break; + + case USB_SCO_EP: + usb_bt_fifo_out_enqueue(s, &s->outsco, s->hci->sco_send, + usb_bt_hci_sco_complete, p->data, p->len); + break; + + default: + goto fail; + } + break; + + default: + fail: + ret = USB_RET_STALL; + break; + } + + return ret; +} + +static void usb_bt_out_hci_packet_event(void *opaque, + const uint8_t *data, int len) +{ + struct USBBtState *s = (struct USBBtState *) opaque; + + 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 int usb_bt_initfn(USBDevice *dev) +{ + usb_desc_init(dev); + return 0; +} + +USBDevice *usb_bt_init(HCIInfo *hci) +{ + USBDevice *dev; + struct USBBtState *s; + + if (!hci) + return NULL; + dev = usb_create_simple(NULL /* FIXME */, "usb-bt-dongle"); + s = DO_UPCAST(struct USBBtState, dev, dev); + s->dev.opaque = s; + + s->hci = hci; + 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); + + return dev; +} + +static struct USBDeviceInfo bt_info = { + .product_desc = "QEMU BT dongle", + .qdev.name = "usb-bt-dongle", + .qdev.size = sizeof(struct USBBtState), + .usb_desc = &desc_bluetooth, + .init = usb_bt_initfn, + .handle_packet = usb_generic_handle_packet, + .handle_reset = usb_bt_handle_reset, + .handle_control = usb_bt_handle_control, + .handle_data = usb_bt_handle_data, + .handle_destroy = usb_bt_handle_destroy, +}; + +static void usb_bt_register_devices(void) +{ + usb_qdev_register(&bt_info); +} +device_init(usb_bt_register_devices) diff --git a/hw/usb-bus.c b/hw/usb-bus.c new file mode 100644 index 0000000..abc7e61 --- /dev/null +++ b/hw/usb-bus.c @@ -0,0 +1,394 @@ +#include "hw.h" +#include "usb.h" +#include "qdev.h" +#include "sysemu.h" +#include "monitor.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 struct BusInfo usb_bus_info = { + .name = "USB", + .size = sizeof(USBBus), + .print_dev = usb_bus_dev_print, + .get_dev_path = usb_get_dev_path, + .get_fw_dev_path = usb_get_fw_dev_path, + .props = (Property[]) { + DEFINE_PROP_STRING("port", USBDevice, port_path), + DEFINE_PROP_END_OF_LIST() + }, +}; +static int next_usb_bus = 0; +static QTAILQ_HEAD(, USBBus) busses = QTAILQ_HEAD_INITIALIZER(busses); + +const VMStateDescription vmstate_usb_device = { + .name = "USBDevice", + .version_id = 1, + .minimum_version_id = 1, + .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, DeviceState *host) +{ + qbus_create_inplace(&bus->qbus, &usb_bus_info, host, NULL); + bus->busnr = next_usb_bus++; + bus->qbus.allow_hotplug = 1; /* Yes, we can */ + QTAILQ_INIT(&bus->free); + QTAILQ_INIT(&bus->used); + QTAILQ_INSERT_TAIL(&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 int usb_qdev_init(DeviceState *qdev, DeviceInfo *base) +{ + USBDevice *dev = DO_UPCAST(USBDevice, qdev, qdev); + USBDeviceInfo *info = DO_UPCAST(USBDeviceInfo, qdev, base); + int rc; + + pstrcpy(dev->product_desc, sizeof(dev->product_desc), info->product_desc); + dev->info = info; + dev->auto_attach = 1; + QLIST_INIT(&dev->strings); + rc = dev->info->init(dev); + if (rc == 0 && dev->auto_attach) + usb_device_attach(dev); + return rc; +} + +static int usb_qdev_exit(DeviceState *qdev) +{ + USBDevice *dev = DO_UPCAST(USBDevice, qdev, qdev); + + usb_device_detach(dev); + if (dev->info->handle_destroy) { + dev->info->handle_destroy(dev); + } + return 0; +} + +void usb_qdev_register(USBDeviceInfo *info) +{ + info->qdev.bus_info = &usb_bus_info; + info->qdev.init = usb_qdev_init; + info->qdev.unplug = qdev_simple_unplug_cb; + info->qdev.exit = usb_qdev_exit; + qdev_register(&info->qdev); +} + +void usb_qdev_register_many(USBDeviceInfo *info) +{ + while (info->qdev.name) { + usb_qdev_register(info); + info++; + } +} + +USBDevice *usb_create(USBBus *bus, const char *name) +{ + DeviceState *dev; + +#if 1 + /* temporary stopgap until all usb is properly qdev-ified */ + if (!bus) { + bus = usb_bus_find(-1); + if (!bus) + return NULL; + fprintf(stderr, "%s: no bus specified, using \"%s\" for \"%s\"\n", + __FUNCTION__, bus->qbus.name, name); + } +#endif + + dev = qdev_create(&bus->qbus, name); + return DO_UPCAST(USBDevice, qdev, dev); +} + +USBDevice *usb_create_simple(USBBus *bus, const char *name) +{ + USBDevice *dev = usb_create(bus, name); + if (!dev) { + hw_error("Failed to create USB device '%s'\n", name); + } + qdev_init_nofail(&dev->qdev); + return dev; +} + +void usb_register_port(USBBus *bus, USBPort *port, void *opaque, int index, + USBPortOps *ops, int speedmask) +{ + port->opaque = opaque; + port->index = index; + port->opaque = opaque; + port->index = index; + port->ops = ops; + port->speedmask = speedmask; + QTAILQ_INSERT_TAIL(&bus->free, port, next); + bus->nfree++; +} + +void usb_port_location(USBPort *downstream, USBPort *upstream, int portnr) +{ + if (upstream) { + snprintf(downstream->path, sizeof(downstream->path), "%s.%d", + upstream->path, portnr); + } else { + snprintf(downstream->path, sizeof(downstream->path), "%d", portnr); + } +} + +void usb_unregister_port(USBBus *bus, USBPort *port) +{ + if (port->dev) + qdev_free(&port->dev->qdev); + QTAILQ_REMOVE(&bus->free, port, next); + bus->nfree--; +} + +static void do_attach(USBDevice *dev) +{ + USBBus *bus = usb_bus_from_device(dev); + USBPort *port; + + if (dev->attached) { + fprintf(stderr, "Warning: tried to attach usb device %s twice\n", + dev->product_desc); + return; + } + if (dev->port_path) { + QTAILQ_FOREACH(port, &bus->free, next) { + if (strcmp(port->path, dev->port_path) == 0) { + break; + } + } + if (port == NULL) { + fprintf(stderr, "Warning: usb port %s (bus %s) not found\n", + dev->port_path, bus->qbus.name); + return; + } + } else { + port = QTAILQ_FIRST(&bus->free); + } + + dev->attached++; + QTAILQ_REMOVE(&bus->free, port, next); + bus->nfree--; + + usb_attach(port, dev); + + QTAILQ_INSERT_TAIL(&bus->used, port, next); + bus->nused++; +} + +int usb_device_attach(USBDevice *dev) +{ + USBBus *bus = usb_bus_from_device(dev); + + if (bus->nfree == 1 && dev->port_path == NULL) { + /* Create a new hub and chain it on + (unless a physical port location is specified). */ + usb_create_simple(bus, "usb-hub"); + } + do_attach(dev); + return 0; +} + +int usb_device_detach(USBDevice *dev) +{ + USBBus *bus = usb_bus_from_device(dev); + USBPort *port; + + if (!dev->attached) { + fprintf(stderr, "Warning: tried to detach unattached usb device %s\n", + dev->product_desc); + return -1; + } + dev->attached--; + + QTAILQ_FOREACH(port, &bus->used, next) { + if (port->dev == dev) + break; + } + assert(port != NULL); + + QTAILQ_REMOVE(&bus->used, port, next); + bus->nused--; + + usb_attach(port, NULL); + + QTAILQ_INSERT_TAIL(&bus->free, port, next); + bus->nfree++; + 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; + + qdev_free(&dev->qdev); + 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", + }; + if (speed >= ARRAY_SIZE(txt)) + return "?"; + return txt[speed]; +} + +static void usb_bus_dev_print(Monitor *mon, DeviceState *qdev, int indent) +{ + USBDevice *dev = DO_UPCAST(USBDevice, qdev, 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 = DO_UPCAST(USBDevice, qdev, qdev); + return qemu_strdup(dev->port->path); +} + +static char *usb_get_fw_dev_path(DeviceState *qdev) +{ + USBDevice *dev = DO_UPCAST(USBDevice, qdev, qdev); + char *fw_path, *in; + ssize_t pos = 0, fw_len; + long nr; + + fw_len = 32 + strlen(dev->port->path) * 6; + fw_path = qemu_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@%ld/", nr); + in++; + } else { + /* the device itself */ + pos += snprintf(fw_path + pos, fw_len - pos, "%s@%ld", + qdev_fw_name(qdev), nr); + break; + } + } + return fw_path; +} + +void usb_info(Monitor *mon) +{ + 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 */); + DeviceInfo *info; + USBDeviceInfo *usb; + char driver[32]; + const char *params; + int len; + + 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 (info = device_info_list; info != NULL; info = info->next) { + if (info->bus_info != &usb_bus_info) + continue; + usb = DO_UPCAST(USBDeviceInfo, qdev, info); + if (usb->usbdevice_name == NULL) + continue; + if (strcmp(usb->usbdevice_name, driver) != 0) + continue; + break; + } + if (info == NULL) { +#if 0 + /* no error because some drivers are not converted (yet) */ + error_report("usbdevice %s not found", driver); +#endif + return NULL; + } + + if (!usb->usbdevice_init) { + if (*params) { + error_report("usbdevice %s accepts no params", driver); + return NULL; + } + return usb_create_simple(bus, usb->qdev.name); + } + return usb->usbdevice_init(params); +} diff --git a/hw/usb-desc.c b/hw/usb-desc.c new file mode 100644 index 0000000..62591f2 --- /dev/null +++ b/hw/usb-desc.c @@ -0,0 +1,406 @@ +#include "usb.h" +#include "usb-desc.h" +#include "trace.h" + +/* ------------------------------------------------------------------ */ + +static uint8_t usb_lo(uint16_t val) +{ + return val & 0xff; +} + +static uint8_t usb_hi(uint16_t val) +{ + return (val >> 8) & 0xff; +} + +int usb_desc_device(const USBDescID *id, const USBDescDevice *dev, + uint8_t *dest, size_t len) +{ + uint8_t bLength = 0x12; + + if (len < bLength) { + return -1; + } + + dest[0x00] = bLength; + dest[0x01] = USB_DT_DEVICE; + + dest[0x02] = usb_lo(dev->bcdUSB); + dest[0x03] = usb_hi(dev->bcdUSB); + dest[0x04] = dev->bDeviceClass; + dest[0x05] = dev->bDeviceSubClass; + dest[0x06] = dev->bDeviceProtocol; + dest[0x07] = dev->bMaxPacketSize0; + + dest[0x08] = usb_lo(id->idVendor); + dest[0x09] = usb_hi(id->idVendor); + dest[0x0a] = usb_lo(id->idProduct); + dest[0x0b] = usb_hi(id->idProduct); + dest[0x0c] = usb_lo(id->bcdDevice); + dest[0x0d] = usb_hi(id->bcdDevice); + dest[0x0e] = id->iManufacturer; + dest[0x0f] = id->iProduct; + dest[0x10] = id->iSerialNumber; + + dest[0x11] = dev->bNumConfigurations; + + return bLength; +} + +int usb_desc_device_qualifier(const USBDescDevice *dev, + uint8_t *dest, size_t len) +{ + uint8_t bLength = 0x0a; + + if (len < bLength) { + return -1; + } + + dest[0x00] = bLength; + dest[0x01] = USB_DT_DEVICE_QUALIFIER; + + dest[0x02] = usb_lo(dev->bcdUSB); + dest[0x03] = usb_hi(dev->bcdUSB); + dest[0x04] = dev->bDeviceClass; + dest[0x05] = dev->bDeviceSubClass; + dest[0x06] = dev->bDeviceProtocol; + dest[0x07] = dev->bMaxPacketSize0; + dest[0x08] = dev->bNumConfigurations; + dest[0x09] = 0; /* reserved */ + + return bLength; +} + +int usb_desc_config(const USBDescConfig *conf, uint8_t *dest, size_t len) +{ + uint8_t bLength = 0x09; + uint16_t wTotalLength = 0; + int i, rc, count; + + if (len < bLength) { + return -1; + } + + dest[0x00] = bLength; + dest[0x01] = USB_DT_CONFIG; + dest[0x04] = conf->bNumInterfaces; + dest[0x05] = conf->bConfigurationValue; + dest[0x06] = conf->iConfiguration; + dest[0x07] = conf->bmAttributes; + dest[0x08] = conf->bMaxPower; + wTotalLength += bLength; + + count = conf->nif ? conf->nif : conf->bNumInterfaces; + for (i = 0; i < count; i++) { + rc = usb_desc_iface(conf->ifs + i, dest + wTotalLength, len - wTotalLength); + if (rc < 0) { + return rc; + } + wTotalLength += rc; + } + + dest[0x02] = usb_lo(wTotalLength); + dest[0x03] = usb_hi(wTotalLength); + return wTotalLength; +} + +int usb_desc_iface(const USBDescIface *iface, uint8_t *dest, size_t len) +{ + uint8_t bLength = 0x09; + int i, rc, pos = 0; + + if (len < bLength) { + return -1; + } + + dest[0x00] = bLength; + dest[0x01] = USB_DT_INTERFACE; + dest[0x02] = iface->bInterfaceNumber; + dest[0x03] = iface->bAlternateSetting; + dest[0x04] = iface->bNumEndpoints; + dest[0x05] = iface->bInterfaceClass; + dest[0x06] = iface->bInterfaceSubClass; + dest[0x07] = iface->bInterfaceProtocol; + dest[0x08] = 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, dest + pos, len - pos); + if (rc < 0) { + return rc; + } + pos += rc; + } + + return pos; +} + +int usb_desc_endpoint(const USBDescEndpoint *ep, uint8_t *dest, size_t len) +{ + uint8_t bLength = 0x07; + + if (len < bLength) { + return -1; + } + + dest[0x00] = bLength; + dest[0x01] = USB_DT_ENDPOINT; + dest[0x02] = ep->bEndpointAddress; + dest[0x03] = ep->bmAttributes; + dest[0x04] = usb_lo(ep->wMaxPacketSize); + dest[0x05] = usb_hi(ep->wMaxPacketSize); + dest[0x06] = ep->bInterval; + + return bLength; +} + +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 void usb_desc_setdefaults(USBDevice *dev) +{ + const USBDesc *desc = dev->info->usb_desc; + + 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; + } + dev->config = dev->device->confs; +} + +void usb_desc_init(USBDevice *dev) +{ + dev->speed = USB_SPEED_FULL; + usb_desc_setdefaults(dev); +} + +void usb_desc_attach(USBDevice *dev) +{ + const USBDesc *desc = dev->info->usb_desc; + + assert(desc != NULL); + if (desc->high && (dev->port->speedmask & USB_SPEED_MASK_HIGH)) { + dev->speed = USB_SPEED_HIGH; + } else if (desc->full && (dev->port->speedmask & USB_SPEED_MASK_FULL)) { + dev->speed = USB_SPEED_FULL; + } else { + fprintf(stderr, "usb: port/device speed mismatch for \"%s\"\n", + dev->info->product_desc); + return; + } + 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 = qemu_mallocz(sizeof(*s)); + s->index = index; + QLIST_INSERT_HEAD(&dev->strings, s, next); + } + qemu_free(s->str); + s->str = qemu_strdup(str); +} + +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 = dev->info->usb_desc->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, int value, uint8_t *dest, size_t len) +{ + const USBDesc *desc = dev->info->usb_desc; + const USBDescDevice *other_dev; + uint8_t buf[256]; + uint8_t type = value >> 8; + uint8_t index = value & 0xff; + int ret = -1; + + if (dev->speed == USB_SPEED_HIGH) { + other_dev = dev->info->usb_desc->full; + } else { + other_dev = dev->info->usb_desc->high; + } + + switch(type) { + case USB_DT_DEVICE: + ret = usb_desc_device(&desc->id, dev->device, 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, 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, buf, sizeof(buf)); + buf[0x01] = USB_DT_OTHER_SPEED_CONFIG; + } + trace_usb_desc_other_speed_config(dev->addr, index, len, ret); + 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); + } + return ret; +} + +int usb_desc_handle_control(USBDevice *dev, int request, int value, + int index, int length, uint8_t *data) +{ + const USBDesc *desc = dev->info->usb_desc; + int i, 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, value, data, length); + break; + + case DeviceRequest | USB_REQ_GET_CONFIGURATION: + data[0] = dev->config->bConfigurationValue; + ret = 1; + break; + case DeviceOutRequest | USB_REQ_SET_CONFIGURATION: + for (i = 0; i < dev->device->bNumConfigurations; i++) { + if (dev->device->confs[i].bConfigurationValue == value) { + dev->config = dev->device->confs + i; + ret = 0; + } + } + trace_usb_set_config(dev->addr, value, ret); + break; + + case DeviceRequest | USB_REQ_GET_STATUS: + data[0] = 0; + if (dev->config->bmAttributes & 0x40) { + data[0] |= 1 << USB_DEVICE_SELF_POWERED; + } + if (dev->remote_wakeup) { + data[0] |= 1 << USB_DEVICE_REMOTE_WAKEUP; + } + data[1] = 0x00; + ret = 2; + 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; + } + return ret; +} diff --git a/hw/usb-desc.h b/hw/usb-desc.h new file mode 100644 index 0000000..ac734ab --- /dev/null +++ b/hw/usb-desc.h @@ -0,0 +1,92 @@ +#ifndef QEMU_HW_USB_DESC_H +#define QEMU_HW_USB_DESC_H + +#include + +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; + + 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; +}; + +struct USBDescOther { + uint8_t length; + uint8_t *data; +}; + +typedef const char *USBDescStrings[256]; + +struct USBDesc { + USBDescID id; + const USBDescDevice *full; + const USBDescDevice *high; + const char* const *str; +}; + +/* generate usb packages from structs */ +int usb_desc_device(const USBDescID *id, const USBDescDevice *dev, + 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, uint8_t *dest, size_t len); +int usb_desc_iface(const USBDescIface *iface, uint8_t *dest, size_t len); +int usb_desc_endpoint(const USBDescEndpoint *ep, uint8_t *dest, size_t len); +int usb_desc_other(const USBDescOther *desc, 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); +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, int value, uint8_t *dest, size_t len); +int usb_desc_handle_control(USBDevice *dev, int request, int value, + int index, int length, uint8_t *data); + +#endif /* QEMU_HW_USB_DESC_H */ diff --git a/hw/usb-hid.c b/hw/usb-hid.c new file mode 100644 index 0000000..79b20df --- /dev/null +++ b/hw/usb-hid.c @@ -0,0 +1,1006 @@ +/* + * 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.h" +#include "console.h" +#include "usb.h" +#include "usb-desc.h" +#include "sysemu.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 + +#define USB_MOUSE 1 +#define USB_TABLET 2 +#define USB_KEYBOARD 3 + +typedef struct USBPointerEvent { + int32_t xdx, ydy; /* relative iff it's a mouse, otherwise absolute */ + int32_t dz, buttons_state; +} USBPointerEvent; + +#define QUEUE_LENGTH 16 /* should be enough for a triple-click */ +#define QUEUE_MASK (QUEUE_LENGTH-1u) +#define QUEUE_INCR(v) ((v)++, (v) &= QUEUE_MASK) + +typedef struct USBMouseState { + USBPointerEvent queue[QUEUE_LENGTH]; + int mouse_grabbed; + QEMUPutMouseEntry *eh_entry; +} USBMouseState; + +typedef struct USBKeyboardState { + uint32_t keycodes[QUEUE_LENGTH]; + uint16_t modifiers; + uint8_t leds; + uint8_t key[16]; + int32_t keys; +} USBKeyboardState; + +typedef struct USBHIDState { + USBDevice dev; + union { + USBMouseState ptr; + USBKeyboardState kbd; + }; + uint32_t head; /* index into circular queue */ + uint32_t n; + int kind; + int32_t protocol; + uint8_t idle; + int64_t next_idle_clock; + int changed; + void *datain_opaque; + void (*datain)(void *); +} USBHIDState; + +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 " QEMU_VERSION, + [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_tablet = { + .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 */ + 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_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 USBDescDevice desc_device_mouse = { + .bcdUSB = 0x0100, + .bMaxPacketSize0 = 8, + .bNumConfigurations = 1, + .confs = (USBDescConfig[]) { + { + .bNumInterfaces = 1, + .bConfigurationValue = 1, + .iConfiguration = STR_CONFIG_MOUSE, + .bmAttributes = 0xa0, + .bMaxPower = 50, + .ifs = &desc_iface_mouse, + }, + }, +}; + +static const USBDescDevice desc_device_tablet = { + .bcdUSB = 0x0100, + .bMaxPacketSize0 = 8, + .bNumConfigurations = 1, + .confs = (USBDescConfig[]) { + { + .bNumInterfaces = 1, + .bConfigurationValue = 1, + .iConfiguration = STR_CONFIG_TABLET, + .bmAttributes = 0xa0, + .bMaxPower = 50, + .ifs = &desc_iface_tablet, + }, + }, +}; + +static const USBDescDevice desc_device_keyboard = { + .bcdUSB = 0x0100, + .bMaxPacketSize0 = 8, + .bNumConfigurations = 1, + .confs = (USBDescConfig[]) { + { + .bNumInterfaces = 1, + .bConfigurationValue = 1, + .iConfiguration = STR_CONFIG_KEYBOARD, + .bmAttributes = 0xa0, + .bMaxPower = 50, + .ifs = &desc_iface_keyboard, + }, + }, +}; + +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, +}; + +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, +}; + +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, +}; + +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 */ +}; + +#define USB_HID_USAGE_ERROR_ROLLOVER 0x01 +#define USB_HID_USAGE_POSTFAIL 0x02 +#define USB_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 usb_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, 0x32, 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, +}; + +static void usb_hid_changed(USBHIDState *hs) +{ + hs->changed = 1; + + if (hs->datain) + hs->datain(hs->datain_opaque); + + usb_wakeup(&hs->dev); +} + +static void usb_pointer_event_clear(USBPointerEvent *e, int buttons) { + e->xdx = e->ydy = e->dz = 0; + e->buttons_state = buttons; +} + +static void usb_pointer_event_combine(USBPointerEvent *e, int xyrel, + int x1, int y1, int z1) { + if (xyrel) { + e->xdx += x1; + e->ydy += y1; + } else { + e->xdx = x1; + e->ydy = y1; + } + e->dz += z1; +} + +static void usb_pointer_event(void *opaque, + int x1, int y1, int z1, int buttons_state) +{ + USBHIDState *hs = opaque; + USBMouseState *s = &hs->ptr; + unsigned use_slot = (hs->head + hs->n - 1) & QUEUE_MASK; + unsigned previous_slot = (use_slot - 1) & QUEUE_MASK; + + /* We combine events where feasible to keep the queue small. We shouldn't + * combine anything with the first event of a particular button state, as + * that would change the location of the button state change. When the + * queue is empty, a second event is needed because we don't know if + * the first event changed the button state. */ + if (hs->n == QUEUE_LENGTH) { + /* Queue full. Discard old button state, combine motion normally. */ + s->queue[use_slot].buttons_state = buttons_state; + } else if (hs->n < 2 || + s->queue[use_slot].buttons_state != buttons_state || + s->queue[previous_slot].buttons_state != s->queue[use_slot].buttons_state) { + /* Cannot or should not combine, so add an empty item to the queue. */ + QUEUE_INCR(use_slot); + hs->n++; + usb_pointer_event_clear(&s->queue[use_slot], buttons_state); + } + usb_pointer_event_combine(&s->queue[use_slot], + hs->kind == USB_MOUSE, + x1, y1, z1); + usb_hid_changed(hs); +} + +static void usb_keyboard_event(void *opaque, int keycode) +{ + USBHIDState *hs = opaque; + USBKeyboardState *s = &hs->kbd; + int slot; + + if (hs->n == QUEUE_LENGTH) { + fprintf(stderr, "usb-kbd: warning: key event queue full\n"); + return; + } + slot = (hs->head + hs->n) & QUEUE_MASK; hs->n++; + s->keycodes[slot] = keycode; + usb_hid_changed(hs); +} + +static void usb_keyboard_process_keycode(USBHIDState *hs) +{ + USBKeyboardState *s = &hs->kbd; + uint8_t hid_code, key; + int i, keycode, slot; + + if (hs->n == 0) { + return; + } + slot = hs->head & QUEUE_MASK; QUEUE_INCR(hs->head); hs->n--; + keycode = s->keycodes[slot]; + + key = keycode & 0x7f; + hid_code = usb_hid_usage_keys[key | ((s->modifiers >> 1) & (1 << 7))]; + s->modifiers &= ~(1 << 8); + + switch (hid_code) { + case 0x00: + return; + + case 0xe0: + if (s->modifiers & (1 << 9)) { + s->modifiers ^= 3 << 8; + usb_hid_changed(hs); + return; + } + case 0xe1 ... 0xe7: + if (keycode & (1 << 7)) { + s->modifiers &= ~(1 << (hid_code & 0x0f)); + usb_hid_changed(hs); + return; + } + case 0xe8 ... 0xef: + s->modifiers |= 1 << (hid_code & 0x0f); + usb_hid_changed(hs); + return; + } + + if (keycode & (1 << 7)) { + for (i = s->keys - 1; i >= 0; i --) + if (s->key[i] == hid_code) { + s->key[i] = s->key[-- s->keys]; + s->key[s->keys] = 0x00; + break; + } + if (i < 0) + return; + } else { + for (i = s->keys - 1; i >= 0; i --) + if (s->key[i] == hid_code) + break; + if (i < 0) { + if (s->keys < sizeof(s->key)) + s->key[s->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; +} + +static int usb_pointer_poll(USBHIDState *hs, uint8_t *buf, int len) +{ + int dx, dy, dz, b, l; + int index; + USBMouseState *s = &hs->ptr; + USBPointerEvent *e; + + if (!s->mouse_grabbed) { + qemu_activate_mouse_event_handler(s->eh_entry); + s->mouse_grabbed = 1; + } + + /* When the buffer is empty, return the last event. Relative + movements will all be zero. */ + index = (hs->n ? hs->head : hs->head - 1); + e = &s->queue[index & QUEUE_MASK]; + + if (hs->kind == USB_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; + + b = 0; + if (e->buttons_state & MOUSE_EVENT_LBUTTON) + b |= 0x01; + if (e->buttons_state & MOUSE_EVENT_RBUTTON) + b |= 0x02; + if (e->buttons_state & MOUSE_EVENT_MBUTTON) + b |= 0x04; + + if (hs->n && + !e->dz && + (hs->kind == USB_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 USB_MOUSE: + if (len > l) + buf[l++] = b; + if (len > l) + buf[l++] = dx; + if (len > l) + buf[l++] = dy; + if (len > l) + buf[l++] = dz; + break; + + case USB_TABLET: + if (len > l) + buf[l++] = b; + 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; +} + +static int usb_keyboard_poll(USBHIDState *hs, uint8_t *buf, int len) +{ + USBKeyboardState *s = &hs->kbd; + if (len < 2) + return 0; + + usb_keyboard_process_keycode(hs); + + buf[0] = s->modifiers & 0xff; + buf[1] = 0; + if (s->keys > 6) + memset(buf + 2, USB_HID_USAGE_ERROR_ROLLOVER, MIN(8, len) - 2); + else + memcpy(buf + 2, s->key, MIN(8, len) - 2); + + return MIN(8, len); +} + +static int usb_keyboard_write(USBKeyboardState *s, 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 */ + s->leds = buf[0]; + if (s->leds & 0x04) + ledstate |= QEMU_SCROLL_LOCK_LED; + if (s->leds & 0x01) + ledstate |= QEMU_NUM_LOCK_LED; + if (s->leds & 0x02) + ledstate |= QEMU_CAPS_LOCK_LED; + kbd_put_ledstate(ledstate); + } + return 0; +} + +static void usb_mouse_handle_reset(USBDevice *dev) +{ + USBHIDState *s = (USBHIDState *)dev; + + memset(s->ptr.queue, 0, sizeof (s->ptr.queue)); + s->head = 0; + s->n = 0; + s->protocol = 1; +} + +static void usb_keyboard_handle_reset(USBDevice *dev) +{ + USBHIDState *s = (USBHIDState *)dev; + + qemu_add_kbd_event_handler(usb_keyboard_event, s); + memset(s->kbd.keycodes, 0, sizeof (s->kbd.keycodes)); + s->head = 0; + s->n = 0; + memset(s->kbd.key, 0, sizeof (s->kbd.key)); + s->kbd.keys = 0; + s->protocol = 1; +} + +static void usb_hid_set_next_idle(USBHIDState *s, int64_t curtime) +{ + s->next_idle_clock = curtime + (get_ticks_per_sec() * s->idle * 4) / 1000; +} + +static int usb_hid_handle_control(USBDevice *dev, int request, int value, + int index, int length, uint8_t *data) +{ + USBHIDState *s = (USBHIDState *)dev; + int ret; + + ret = usb_desc_handle_control(dev, request, value, index, length, data); + if (ret >= 0) { + return ret; + } + + ret = 0; + switch(request) { + case DeviceRequest | USB_REQ_GET_INTERFACE: + data[0] = 0; + ret = 1; + break; + case DeviceOutRequest | USB_REQ_SET_INTERFACE: + ret = 0; + break; + /* hid specific requests */ + case InterfaceRequest | USB_REQ_GET_DESCRIPTOR: + switch(value >> 8) { + case 0x22: + if (s->kind == USB_MOUSE) { + memcpy(data, qemu_mouse_hid_report_descriptor, + sizeof(qemu_mouse_hid_report_descriptor)); + ret = sizeof(qemu_mouse_hid_report_descriptor); + } else if (s->kind == USB_TABLET) { + memcpy(data, qemu_tablet_hid_report_descriptor, + sizeof(qemu_tablet_hid_report_descriptor)); + ret = sizeof(qemu_tablet_hid_report_descriptor); + } else if (s->kind == USB_KEYBOARD) { + memcpy(data, qemu_keyboard_hid_report_descriptor, + sizeof(qemu_keyboard_hid_report_descriptor)); + ret = sizeof(qemu_keyboard_hid_report_descriptor); + } + break; + default: + goto fail; + } + break; + case GET_REPORT: + if (s->kind == USB_MOUSE || s->kind == USB_TABLET) + ret = usb_pointer_poll(s, data, length); + else if (s->kind == USB_KEYBOARD) + ret = usb_keyboard_poll(s, data, length); + break; + case SET_REPORT: + if (s->kind == USB_KEYBOARD) + ret = usb_keyboard_write(&s->kbd, data, length); + else + goto fail; + break; + case GET_PROTOCOL: + if (s->kind != USB_KEYBOARD) + goto fail; + ret = 1; + data[0] = s->protocol; + break; + case SET_PROTOCOL: + if (s->kind != USB_KEYBOARD) + goto fail; + ret = 0; + s->protocol = value; + break; + case GET_IDLE: + ret = 1; + data[0] = s->idle; + break; + case SET_IDLE: + s->idle = (uint8_t) (value >> 8); + usb_hid_set_next_idle(s, qemu_get_clock(vm_clock)); + ret = 0; + break; + default: + fail: + ret = USB_RET_STALL; + break; + } + return ret; +} + +static int usb_hid_handle_data(USBDevice *dev, USBPacket *p) +{ + USBHIDState *s = (USBHIDState *)dev; + int ret = 0; + + switch(p->pid) { + case USB_TOKEN_IN: + if (p->devep == 1) { + int64_t curtime = qemu_get_clock(vm_clock); + if (!s->changed && (!s->idle || s->next_idle_clock - curtime > 0)) + return USB_RET_NAK; + usb_hid_set_next_idle(s, curtime); + if (s->kind == USB_MOUSE || s->kind == USB_TABLET) { + ret = usb_pointer_poll(s, p->data, p->len); + } + else if (s->kind == USB_KEYBOARD) { + ret = usb_keyboard_poll(s, p->data, p->len); + } + s->changed = s->n > 0; + } else { + goto fail; + } + break; + case USB_TOKEN_OUT: + default: + fail: + ret = USB_RET_STALL; + break; + } + return ret; +} + +static void usb_hid_handle_destroy(USBDevice *dev) +{ + USBHIDState *s = (USBHIDState *)dev; + + switch(s->kind) { + case USB_KEYBOARD: + qemu_remove_kbd_event_handler(); + break; + default: + qemu_remove_mouse_event_handler(s->ptr.eh_entry); + } +} + +static int usb_hid_initfn(USBDevice *dev, int kind) +{ + USBHIDState *s = DO_UPCAST(USBHIDState, dev, dev); + + usb_desc_init(dev); + s->kind = kind; + + if (s->kind == USB_MOUSE) { + s->ptr.eh_entry = qemu_add_mouse_event_handler(usb_pointer_event, s, + 0, "QEMU USB Mouse"); + } else if (s->kind == USB_TABLET) { + s->ptr.eh_entry = qemu_add_mouse_event_handler(usb_pointer_event, s, + 1, "QEMU USB Tablet"); + } + + /* Force poll routine to be run and grab input the first time. */ + s->changed = 1; + return 0; +} + +static int usb_tablet_initfn(USBDevice *dev) +{ + return usb_hid_initfn(dev, USB_TABLET); +} + +static int usb_mouse_initfn(USBDevice *dev) +{ + return usb_hid_initfn(dev, USB_MOUSE); +} + +static int usb_keyboard_initfn(USBDevice *dev) +{ + return usb_hid_initfn(dev, USB_KEYBOARD); +} + +void usb_hid_datain_cb(USBDevice *dev, void *opaque, void (*datain)(void *)) +{ + USBHIDState *s = (USBHIDState *)dev; + + s->datain_opaque = opaque; + s->datain = datain; +} + +static int usb_hid_post_load(void *opaque, int version_id) +{ + USBHIDState *s = opaque; + + if (s->idle) { + usb_hid_set_next_idle(s, qemu_get_clock(vm_clock)); + } + return 0; +} + +static const VMStateDescription vmstate_usb_ptr_queue = { + .name = "usb-ptr-queue", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField []) { + VMSTATE_INT32(xdx, USBPointerEvent), + VMSTATE_INT32(ydy, USBPointerEvent), + VMSTATE_INT32(dz, USBPointerEvent), + VMSTATE_INT32(buttons_state, USBPointerEvent), + VMSTATE_END_OF_LIST() + } +}; +static const VMStateDescription vmstate_usb_ptr = { + .name = "usb-ptr", + .version_id = 1, + .minimum_version_id = 1, + .post_load = usb_hid_post_load, + .fields = (VMStateField []) { + VMSTATE_USB_DEVICE(dev, USBHIDState), + VMSTATE_STRUCT_ARRAY(ptr.queue, USBHIDState, QUEUE_LENGTH, 0, + vmstate_usb_ptr_queue, USBPointerEvent), + VMSTATE_UINT32(head, USBHIDState), + VMSTATE_UINT32(n, USBHIDState), + VMSTATE_INT32(protocol, USBHIDState), + VMSTATE_UINT8(idle, USBHIDState), + VMSTATE_END_OF_LIST() + } +}; + +static const VMStateDescription vmstate_usb_kbd = { + .name = "usb-kbd", + .version_id = 1, + .minimum_version_id = 1, + .post_load = usb_hid_post_load, + .fields = (VMStateField []) { + VMSTATE_USB_DEVICE(dev, USBHIDState), + VMSTATE_UINT32_ARRAY(kbd.keycodes, USBHIDState, QUEUE_LENGTH), + VMSTATE_UINT32(head, USBHIDState), + VMSTATE_UINT32(n, USBHIDState), + VMSTATE_UINT16(kbd.modifiers, USBHIDState), + VMSTATE_UINT8(kbd.leds, USBHIDState), + VMSTATE_UINT8_ARRAY(kbd.key, USBHIDState, 16), + VMSTATE_INT32(kbd.keys, USBHIDState), + VMSTATE_INT32(protocol, USBHIDState), + VMSTATE_UINT8(idle, USBHIDState), + VMSTATE_END_OF_LIST() + } +}; + +static struct USBDeviceInfo hid_info[] = { + { + .product_desc = "QEMU USB Tablet", + .qdev.name = "usb-tablet", + .usbdevice_name = "tablet", + .qdev.size = sizeof(USBHIDState), + .qdev.vmsd = &vmstate_usb_ptr, + .usb_desc = &desc_tablet, + .init = usb_tablet_initfn, + .handle_packet = usb_generic_handle_packet, + .handle_reset = usb_mouse_handle_reset, + .handle_control = usb_hid_handle_control, + .handle_data = usb_hid_handle_data, + .handle_destroy = usb_hid_handle_destroy, + },{ + .product_desc = "QEMU USB Mouse", + .qdev.name = "usb-mouse", + .usbdevice_name = "mouse", + .qdev.size = sizeof(USBHIDState), + .qdev.vmsd = &vmstate_usb_ptr, + .usb_desc = &desc_mouse, + .init = usb_mouse_initfn, + .handle_packet = usb_generic_handle_packet, + .handle_reset = usb_mouse_handle_reset, + .handle_control = usb_hid_handle_control, + .handle_data = usb_hid_handle_data, + .handle_destroy = usb_hid_handle_destroy, + },{ + .product_desc = "QEMU USB Keyboard", + .qdev.name = "usb-kbd", + .usbdevice_name = "keyboard", + .qdev.size = sizeof(USBHIDState), + .qdev.vmsd = &vmstate_usb_kbd, + .usb_desc = &desc_keyboard, + .init = usb_keyboard_initfn, + .handle_packet = usb_generic_handle_packet, + .handle_reset = usb_keyboard_handle_reset, + .handle_control = usb_hid_handle_control, + .handle_data = usb_hid_handle_data, + .handle_destroy = usb_hid_handle_destroy, + },{ + /* end of list */ + } +}; + +static void usb_hid_register_devices(void) +{ + usb_qdev_register_many(hid_info); +} +device_init(usb_hid_register_devices) diff --git a/hw/usb-hub.c b/hw/usb-hub.c new file mode 100644 index 0000000..3dd31ba --- /dev/null +++ b/hw/usb-hub.c @@ -0,0 +1,590 @@ +/* + * 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 "usb.h" +#include "usb-desc.h" + +//#define DEBUG + +#define NUM_PORTS 8 + +typedef struct USBHubPort { + USBPort port; + uint16_t wPortStatus; + uint16_t wPortChange; +} USBHubPort; + +typedef struct USBHubState { + USBDevice dev; + USBHubPort ports[NUM_PORTS]; +} USBHubState; + +#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 " QEMU_VERSION, + [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 = 0xe0, + .ifs = &desc_iface_hub, + }, + }, +}; + +static const USBDesc desc_hub = { + .id = { + .idVendor = 0, + .idProduct = 0, + .bcdDevice = 0x0101, + .iManufacturer = STR_MANUFACTURER, + .iProduct = STR_PRODUCT, + .iSerialNumber = STR_SERIALNUMBER, + }, + .full = &desc_device_hub, + .str = desc_strings, +}; + +static const uint8_t qemu_hub_dev_descriptor[] = { + 0x12, /* u8 bLength; */ + 0x01, /* u8 bDescriptorType; Device */ + 0x10, 0x01, /* u16 bcdUSB; v1.1 */ + + 0x09, /* u8 bDeviceClass; HUB_CLASSCODE */ + 0x00, /* u8 bDeviceSubClass; */ + 0x00, /* u8 bDeviceProtocol; [ low/full speeds only ] */ + 0x08, /* u8 bMaxPacketSize0; 8 Bytes */ + + 0x00, 0x00, /* u16 idVendor; */ + 0x00, 0x00, /* u16 idProduct; */ + 0x01, 0x01, /* u16 bcdDevice */ + + 0x03, /* u8 iManufacturer; */ + 0x02, /* u8 iProduct; */ + 0x01, /* u8 iSerialNumber; */ + 0x01 /* u8 bNumConfigurations; */ +}; + +/* XXX: patch interrupt size */ +static const uint8_t qemu_hub_config_descriptor[] = { + + /* one configuration */ + 0x09, /* u8 bLength; */ + 0x02, /* u8 bDescriptorType; Configuration */ + 0x19, 0x00, /* u16 wTotalLength; */ + 0x01, /* u8 bNumInterfaces; (1) */ + 0x01, /* u8 bConfigurationValue; */ + 0x00, /* u8 iConfiguration; */ + 0xe0, /* u8 bmAttributes; + Bit 7: must be set, + 6: Self-powered, + 5: Remote wakeup, + 4..0: resvd */ + 0x00, /* u8 MaxPower; */ + + /* USB 1.1: + * USB 2.0, single TT organization (mandatory): + * one interface, protocol 0 + * + * USB 2.0, multiple TT organization (optional): + * two interfaces, protocols 1 (like single TT) + * and 2 (multiple TT mode) ... config is + * sometimes settable + * NOT IMPLEMENTED + */ + + /* one interface */ + 0x09, /* u8 if_bLength; */ + 0x04, /* u8 if_bDescriptorType; Interface */ + 0x00, /* u8 if_bInterfaceNumber; */ + 0x00, /* u8 if_bAlternateSetting; */ + 0x01, /* u8 if_bNumEndpoints; */ + 0x09, /* u8 if_bInterfaceClass; HUB_CLASSCODE */ + 0x00, /* u8 if_bInterfaceSubClass; */ + 0x00, /* u8 if_bInterfaceProtocol; [usb1.1 or single tt] */ + 0x00, /* u8 if_iInterface; */ + + /* one endpoint (status change endpoint) */ + 0x07, /* u8 ep_bLength; */ + 0x05, /* u8 ep_bDescriptorType; Endpoint */ + 0x81, /* u8 ep_bEndpointAddress; IN Endpoint 1 */ + 0x03, /* u8 ep_bmAttributes; Interrupt */ + 0x02, 0x00, /* u16 ep_wMaxPacketSize; 1 + (MAX_ROOT_PORTS / 8) */ + 0xff /* u8 ep_bInterval; (255ms -- usb 2.0 spec) */ +}; + +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]; + + 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; + } +} + +static void usb_hub_detach(USBPort *port1) +{ + USBHubState *s = port1->opaque; + USBHubPort *port = &s->ports[port1->index]; + + 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; + } +} + +static void usb_hub_wakeup(USBDevice *dev) +{ + USBHubState *s = dev->port->opaque; + USBHubPort *port = &s->ports[dev->port->index]; + + if (port->wPortStatus & PORT_STAT_SUSPEND) { + port->wPortChange |= PORT_STAT_C_SUSPEND; + usb_wakeup(&s->dev); + } +} + +static void usb_hub_handle_attach(USBDevice *dev) +{ + USBHubState *s = DO_UPCAST(USBHubState, dev, dev); + int i; + + for (i = 0; i < NUM_PORTS; i++) { + usb_port_location(&s->ports[i].port, dev->port, i+1); + } +} + +static void usb_hub_handle_reset(USBDevice *dev) +{ + /* XXX: do it */ +} + +static int usb_hub_handle_control(USBDevice *dev, int request, int value, + int index, int length, uint8_t *data) +{ + USBHubState *s = (USBHubState *)dev; + int ret; + + ret = usb_desc_handle_control(dev, request, value, index, length, data); + if (ret >= 0) { + return ret; + } + + switch(request) { + case EndpointOutRequest | USB_REQ_CLEAR_FEATURE: + if (value == 0 && index != 0x81) { /* clear ep halt */ + goto fail; + } + ret = 0; + break; + case DeviceRequest | USB_REQ_GET_INTERFACE: + data[0] = 0; + ret = 1; + break; + case DeviceOutRequest | USB_REQ_SET_INTERFACE: + ret = 0; + break; + /* usb specific requests */ + case GetHubStatus: + data[0] = 0; + data[1] = 0; + data[2] = 0; + data[3] = 0; + ret = 4; + break; + case GetPortStatus: + { + unsigned int n = index - 1; + USBHubPort *port; + if (n >= NUM_PORTS) { + goto fail; + } + port = &s->ports[n]; + data[0] = port->wPortStatus; + data[1] = port->wPortStatus >> 8; + data[2] = port->wPortChange; + data[3] = port->wPortChange >> 8; + ret = 4; + } + break; + case SetHubFeature: + case ClearHubFeature: + if (value == 0 || value == 1) { + } else { + goto fail; + } + ret = 0; + break; + case SetPortFeature: + { + unsigned int n = index - 1; + USBHubPort *port; + USBDevice *dev; + 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) { + usb_send_msg(dev, USB_MSG_RESET); + port->wPortChange |= PORT_STAT_C_RESET; + /* set enable bit */ + port->wPortStatus |= PORT_STAT_ENABLE; + } + break; + case PORT_POWER: + break; + default: + goto fail; + } + ret = 0; + } + break; + case ClearPortFeature: + { + unsigned int n = index - 1; + USBHubPort *port; + + 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; + } + ret = 0; + } + 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++; + } + + ret = sizeof(qemu_hub_hub_descriptor) + var_hub_size; + data[0] = ret; + break; + } + default: + fail: + ret = USB_RET_STALL; + break; + } + return ret; +} + +static int usb_hub_handle_data(USBDevice *dev, USBPacket *p) +{ + USBHubState *s = (USBHubState *)dev; + int ret; + + switch(p->pid) { + case USB_TOKEN_IN: + if (p->devep == 1) { + USBHubPort *port; + unsigned int status; + int i, n; + n = (NUM_PORTS + 1 + 7) / 8; + if (p->len == 1) { /* FreeBSD workaround */ + n = 1; + } else if (n > p->len) { + return USB_RET_BABBLE; + } + status = 0; + for(i = 0; i < NUM_PORTS; i++) { + port = &s->ports[i]; + if (port->wPortChange) + status |= (1 << (i + 1)); + } + if (status != 0) { + for(i = 0; i < n; i++) { + p->data[i] = status >> (8 * i); + } + ret = n; + } else { + ret = USB_RET_NAK; /* usb11 11.13.1 */ + } + } else { + goto fail; + } + break; + case USB_TOKEN_OUT: + default: + fail: + ret = USB_RET_STALL; + break; + } + return ret; +} + +static int usb_hub_broadcast_packet(USBHubState *s, USBPacket *p) +{ + USBHubPort *port; + USBDevice *dev; + int i, ret; + + for(i = 0; i < NUM_PORTS; i++) { + port = &s->ports[i]; + dev = port->port.dev; + if (dev && (port->wPortStatus & PORT_STAT_ENABLE)) { + ret = dev->info->handle_packet(dev, p); + if (ret != USB_RET_NODEV) { + return ret; + } + } + } + return USB_RET_NODEV; +} + +static int usb_hub_handle_packet(USBDevice *dev, USBPacket *p) +{ + USBHubState *s = (USBHubState *)dev; + +#if defined(DEBUG) && 0 + printf("usb_hub: pid=0x%x\n", pid); +#endif + if (dev->state == USB_STATE_DEFAULT && + dev->addr != 0 && + p->devaddr != dev->addr && + (p->pid == USB_TOKEN_SETUP || + p->pid == USB_TOKEN_OUT || + p->pid == USB_TOKEN_IN)) { + /* broadcast the packet to the devices */ + return usb_hub_broadcast_packet(s, p); + } + return usb_generic_handle_packet(dev, p); +} + +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, + .wakeup = usb_hub_wakeup, +}; + +static int usb_hub_initfn(USBDevice *dev) +{ + USBHubState *s = DO_UPCAST(USBHubState, dev, dev); + USBHubPort *port; + int i; + + usb_desc_init(dev); + 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); + port->wPortStatus = PORT_STAT_POWER; + port->wPortChange = 0; + } + return 0; +} + +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 struct USBDeviceInfo hub_info = { + .product_desc = "QEMU USB Hub", + .qdev.name = "usb-hub", + .qdev.fw_name = "hub", + .qdev.size = sizeof(USBHubState), + .qdev.vmsd = &vmstate_usb_hub, + .usb_desc = &desc_hub, + .init = usb_hub_initfn, + .handle_packet = usb_hub_handle_packet, + .handle_attach = usb_hub_handle_attach, + .handle_reset = usb_hub_handle_reset, + .handle_control = usb_hub_handle_control, + .handle_data = usb_hub_handle_data, + .handle_destroy = usb_hub_handle_destroy, +}; + +static void usb_hub_register_devices(void) +{ + usb_qdev_register(&hub_info); +} +device_init(usb_hub_register_devices) diff --git a/hw/usb-msd.c b/hw/usb-msd.c new file mode 100644 index 0000000..76f5b02 --- /dev/null +++ b/hw/usb-msd.c @@ -0,0 +1,621 @@ +/* + * USB Mass Storage Device emulation + * + * Copyright (c) 2006 CodeSourcery. + * Written by Paul Brook + * + * This code is licenced under the LGPL. + */ + +#include "qemu-common.h" +#include "qemu-option.h" +#include "qemu-config.h" +#include "usb.h" +#include "usb-desc.h" +#include "scsi.h" +#include "console.h" +#include "monitor.h" +#include "sysemu.h" +#include "blockdev.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, /* Tranfer data to device. */ + USB_MSDM_DATAIN, /* Transfer data from device. */ + USB_MSDM_CSW /* Command Status. */ +}; + +typedef struct { + USBDevice dev; + enum USBMSDMode mode; + uint32_t scsi_len; + uint8_t *scsi_buf; + uint32_t usb_len; + uint8_t *usb_buf; + uint32_t data_len; + uint32_t residue; + uint32_t tag; + SCSIBus bus; + BlockConf conf; + SCSIDevice *scsi_dev; + uint32_t removable; + int result; + /* For async completion. */ + USBPacket *packet; +} MSDState; + +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]; +}; + +struct usb_msd_csw { + uint32_t sig; + uint32_t tag; + uint32_t residue; + uint8_t status; +}; + +enum { + STR_MANUFACTURER = 1, + STR_PRODUCT, + STR_SERIALNUMBER, + STR_CONFIG_FULL, + STR_CONFIG_HIGH, +}; + +static const USBDescStrings desc_strings = { + [STR_MANUFACTURER] = "QEMU " QEMU_VERSION, + [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)", +}; + +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 = 0xc0, + .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 = 0xc0, + .ifs = &desc_iface_high, + }, + }, +}; + +static const USBDesc desc = { + .id = { + .idVendor = 0, + .idProduct = 0, + .bcdDevice = 0, + .iManufacturer = STR_MANUFACTURER, + .iProduct = STR_PRODUCT, + .iSerialNumber = STR_SERIALNUMBER, + }, + .full = &desc_device_full, + .high = &desc_device_high, + .str = desc_strings, +}; + +static void usb_msd_copy_data(MSDState *s) +{ + uint32_t len; + len = s->usb_len; + if (len > s->scsi_len) + len = s->scsi_len; + if (s->mode == USB_MSDM_DATAIN) { + memcpy(s->usb_buf, s->scsi_buf, len); + } else { + memcpy(s->scsi_buf, s->usb_buf, len); + } + s->usb_len -= len; + s->scsi_len -= len; + s->usb_buf += len; + s->scsi_buf += len; + s->data_len -= len; + if (s->scsi_len == 0 || s->data_len == 0) { + if (s->mode == USB_MSDM_DATAIN) { + s->scsi_dev->info->read_data(s->scsi_dev, s->tag); + } else if (s->mode == USB_MSDM_DATAOUT) { + s->scsi_dev->info->write_data(s->scsi_dev, s->tag); + } + } +} + +static void usb_msd_send_status(MSDState *s, USBPacket *p) +{ + struct usb_msd_csw csw; + int len; + + csw.sig = cpu_to_le32(0x53425355); + csw.tag = cpu_to_le32(s->tag); + csw.residue = s->residue; + csw.status = s->result; + + len = MIN(sizeof(csw), p->len); + memcpy(p->data, &csw, len); +} + +static void usb_msd_command_complete(SCSIBus *bus, int reason, uint32_t tag, + uint32_t arg) +{ + MSDState *s = DO_UPCAST(MSDState, dev.qdev, bus->qbus.parent); + USBPacket *p = s->packet; + + if (tag != s->tag) { + fprintf(stderr, "usb-msd: Unexpected SCSI Tag 0x%x\n", tag); + } + if (reason == SCSI_REASON_DONE) { + DPRINTF("Command complete %d\n", arg); + s->residue = s->data_len; + s->result = arg != 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->data_len) { + s->data_len -= s->usb_len; + if (s->mode == USB_MSDM_DATAIN) + memset(s->usb_buf, 0, s->usb_len); + s->usb_len = 0; + } + if (s->data_len == 0) + s->mode = USB_MSDM_CSW; + } + s->packet = NULL; + usb_packet_complete(p); + } else if (s->data_len == 0) { + s->mode = USB_MSDM_CSW; + } + return; + } + s->scsi_len = arg; + s->scsi_buf = s->scsi_dev->info->get_buf(s->scsi_dev, tag); + if (p) { + usb_msd_copy_data(s); + if (s->usb_len == 0) { + /* Set s->packet to NULL before calling usb_packet_complete + because annother request may be issued before + usb_packet_complete returns. */ + DPRINTF("Packet complete %p\n", p); + s->packet = NULL; + usb_packet_complete(p); + } + } +} + +static void usb_msd_handle_reset(USBDevice *dev) +{ + MSDState *s = (MSDState *)dev; + + DPRINTF("Reset\n"); + s->mode = USB_MSDM_CBW; +} + +static int usb_msd_handle_control(USBDevice *dev, int request, int value, + int index, int length, uint8_t *data) +{ + MSDState *s = (MSDState *)dev; + int ret; + + ret = usb_desc_handle_control(dev, request, value, index, length, data); + if (ret >= 0) { + return ret; + } + + ret = 0; + switch (request) { + case DeviceRequest | USB_REQ_GET_INTERFACE: + data[0] = 0; + ret = 1; + break; + case DeviceOutRequest | USB_REQ_SET_INTERFACE: + ret = 0; + break; + case EndpointOutRequest | USB_REQ_CLEAR_FEATURE: + ret = 0; + break; + case InterfaceOutRequest | USB_REQ_SET_INTERFACE: + ret = 0; + break; + /* Class specific requests. */ + case ClassInterfaceOutRequest | MassStorageReset: + /* Reset state ready for the next CBW. */ + s->mode = USB_MSDM_CBW; + ret = 0; + break; + case ClassInterfaceRequest | GetMaxLun: + data[0] = 0; + ret = 1; + break; + default: + ret = USB_RET_STALL; + break; + } + return ret; +} + +static void usb_msd_cancel_io(USBPacket *p, void *opaque) +{ + MSDState *s = opaque; + s->scsi_dev->info->cancel_io(s->scsi_dev, s->tag); + s->packet = NULL; + s->scsi_len = 0; +} + +static int usb_msd_handle_data(USBDevice *dev, USBPacket *p) +{ + MSDState *s = (MSDState *)dev; + int ret = 0; + struct usb_msd_cbw cbw; + uint8_t devep = p->devep; + uint8_t *data = p->data; + int len = p->len; + + switch (p->pid) { + case USB_TOKEN_OUT: + if (devep != 2) + goto fail; + + switch (s->mode) { + case USB_MSDM_CBW: + if (len != 31) { + fprintf(stderr, "usb-msd: Bad CBW size"); + goto fail; + } + memcpy(&cbw, data, 31); + if (le32_to_cpu(cbw.sig) != 0x43425355) { + fprintf(stderr, "usb-msd: Bad signature %08x\n", + le32_to_cpu(cbw.sig)); + goto fail; + } + DPRINTF("Command on LUN %d\n", cbw.lun); + if (cbw.lun != 0) { + fprintf(stderr, "usb-msd: Bad LUN %d\n", cbw.lun); + goto fail; + } + s->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", + s->tag, cbw.flags, cbw.cmd_len, s->data_len); + s->residue = 0; + s->scsi_dev->info->send_command(s->scsi_dev, s->tag, cbw.cmd, 0); + /* ??? Should check that USB and SCSI data transfer + directions match. */ + if (s->residue == 0) { + if (s->mode == USB_MSDM_DATAIN) { + s->scsi_dev->info->read_data(s->scsi_dev, s->tag); + } else if (s->mode == USB_MSDM_DATAOUT) { + s->scsi_dev->info->write_data(s->scsi_dev, s->tag); + } + } + ret = len; + break; + + case USB_MSDM_DATAOUT: + DPRINTF("Data out %d/%d\n", len, s->data_len); + if (len > s->data_len) + goto fail; + + s->usb_buf = data; + s->usb_len = len; + if (s->scsi_len) { + usb_msd_copy_data(s); + } + if (s->residue && s->usb_len) { + s->data_len -= s->usb_len; + if (s->data_len == 0) + s->mode = USB_MSDM_CSW; + s->usb_len = 0; + } + if (s->usb_len) { + DPRINTF("Deferring packet %p\n", p); + usb_defer_packet(p, usb_msd_cancel_io, s); + s->packet = p; + ret = USB_RET_ASYNC; + } else { + ret = len; + } + break; + + default: + DPRINTF("Unexpected write (len %d)\n", len); + goto fail; + } + break; + + case USB_TOKEN_IN: + if (devep != 1) + goto fail; + + switch (s->mode) { + case USB_MSDM_DATAOUT: + if (s->data_len != 0 || len < 13) + goto fail; + /* Waiting for SCSI write to complete. */ + usb_defer_packet(p, usb_msd_cancel_io, s); + s->packet = p; + ret = USB_RET_ASYNC; + break; + + case USB_MSDM_CSW: + DPRINTF("Command status %d tag 0x%x, len %d\n", + s->result, s->tag, len); + if (len < 13) + goto fail; + + usb_msd_send_status(s, p); + s->mode = USB_MSDM_CBW; + ret = 13; + break; + + case USB_MSDM_DATAIN: + DPRINTF("Data in %d/%d, scsi_len %d\n", len, s->data_len, s->scsi_len); + if (len > s->data_len) + len = s->data_len; + s->usb_buf = data; + s->usb_len = len; + if (s->scsi_len) { + usb_msd_copy_data(s); + } + if (s->residue && s->usb_len) { + s->data_len -= s->usb_len; + memset(s->usb_buf, 0, s->usb_len); + if (s->data_len == 0) + s->mode = USB_MSDM_CSW; + s->usb_len = 0; + } + if (s->usb_len) { + DPRINTF("Deferring packet %p\n", p); + usb_defer_packet(p, usb_msd_cancel_io, s); + s->packet = p; + ret = USB_RET_ASYNC; + } else { + ret = len; + } + break; + + default: + DPRINTF("Unexpected read (len %d)\n", len); + goto fail; + } + break; + + default: + DPRINTF("Bad token\n"); + fail: + ret = USB_RET_STALL; + break; + } + + return ret; +} + +static void usb_msd_password_cb(void *opaque, int err) +{ + MSDState *s = opaque; + + if (!err) + usb_device_attach(&s->dev); + else + qdev_unplug(&s->dev.qdev); +} + +static int usb_msd_initfn(USBDevice *dev) +{ + MSDState *s = DO_UPCAST(MSDState, dev, dev); + BlockDriverState *bs = s->conf.bs; + DriveInfo *dinfo; + + if (!bs) { + error_report("usb-msd: drive property not set"); + return -1; + } + + /* + * 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. + */ + bdrv_detach(bs, &s->dev.qdev); + s->conf.bs = NULL; + + dinfo = drive_get_by_blockdev(bs); + if (dinfo && dinfo->serial) { + usb_desc_set_string(dev, STR_SERIALNUMBER, dinfo->serial); + } + + usb_desc_init(dev); + scsi_bus_new(&s->bus, &s->dev.qdev, 0, 1, usb_msd_command_complete); + s->scsi_dev = scsi_bus_legacy_add_drive(&s->bus, bs, 0, !!s->removable); + if (!s->scsi_dev) { + return -1; + } + s->bus.qbus.allow_hotplug = 0; + usb_msd_handle_reset(dev); + + if (bdrv_key_required(bs)) { + if (cur_mon) { + monitor_read_bdrv_key_start(cur_mon, bs, usb_msd_password_cb, s); + s->dev.auto_attach = 0; + } else { + autostart = 0; + } + } + + add_boot_device_path(s->conf.bootindex, &dev->qdev, "/disk@0,0"); + return 0; +} + +static USBDevice *usb_msd_init(const char *filename) +{ + static int nr=0; + char id[8]; + QemuOpts *opts; + DriveInfo *dinfo; + USBDevice *dev; + const char *p1; + char fmt[32]; + + /* parse -usbdevice disk: syntax into drive opts */ + snprintf(id, sizeof(id), "usb%d", nr++); + opts = qemu_opts_create(qemu_find_opts("drive"), id, 0); + + 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); + } else if (*filename != ':') { + printf("unrecognized USB mass-storage option %s\n", filename); + return NULL; + } + filename = p1; + } + if (!*filename) { + printf("block device specification needed\n"); + return NULL; + } + qemu_opt_set(opts, "file", filename); + qemu_opt_set(opts, "if", "none"); + + /* create host drive */ + dinfo = drive_init(opts, 0); + if (!dinfo) { + qemu_opts_del(opts); + return NULL; + } + + /* create guest device */ + dev = usb_create(NULL /* FIXME */, "usb-storage"); + if (!dev) { + return NULL; + } + if (qdev_prop_set_drive(&dev->qdev, "drive", dinfo->bdrv) < 0) { + qdev_free(&dev->qdev); + return NULL; + } + if (qdev_init(&dev->qdev) < 0) + return NULL; + + return dev; +} + +static struct USBDeviceInfo msd_info = { + .product_desc = "QEMU USB MSD", + .qdev.name = "usb-storage", + .qdev.fw_name = "storage", + .qdev.size = sizeof(MSDState), + .usb_desc = &desc, + .init = usb_msd_initfn, + .handle_packet = usb_generic_handle_packet, + .handle_attach = usb_desc_attach, + .handle_reset = usb_msd_handle_reset, + .handle_control = usb_msd_handle_control, + .handle_data = usb_msd_handle_data, + .usbdevice_name = "disk", + .usbdevice_init = usb_msd_init, + .qdev.props = (Property[]) { + DEFINE_BLOCK_PROPERTIES(MSDState, conf), + DEFINE_PROP_BIT("removable", MSDState, removable, 0, false), + DEFINE_PROP_END_OF_LIST(), + }, +}; + +static void usb_msd_register_devices(void) +{ + usb_qdev_register(&msd_info); +} +device_init(usb_msd_register_devices) diff --git a/hw/usb-musb.c b/hw/usb-musb.c new file mode 100644 index 0000000..782cfa2 --- /dev/null +++ b/hw/usb-musb.c @@ -0,0 +1,1495 @@ +/* + * "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 + * + * 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 . + * + * Only host-mode and non-DMA accesses are currently supported. + */ +#include "qemu-common.h" +#include "qemu-timer.h" +#include "usb.h" +#include "irq.h" +#include "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 USBPortOps musb_port_ops = { + .attach = musb_attach, + .detach = musb_detach, +}; + +typedef struct { + 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]; + USBPacket 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]; +} MUSBEndPoint; + +struct MUSBState { + qemu_irq *irqs; + 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]; +} *musb_init(qemu_irq *irqs) +{ + MUSBState *s = qemu_mallocz(sizeof(*s)); + int i; + + s->irqs = irqs; + + s->faddr = 0x00; + 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; + + /* 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_bus_new(&s->bus, NULL /* FIXME */); + usb_register_port(&s->bus, &s->port, s, 0, &musb_port_ops, + USB_SPEED_MASK_LOW | USB_SPEED_MASK_FULL); + usb_port_location(&s->port, NULL, 1); + + 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_intr_set(s, musb_irq_disconnect, 1); + musb_session_update(s, 1, s->session); +} + +static inline void musb_cb_tick0(void *opaque) +{ + MUSBEndPoint *ep = (MUSBEndPoint *) opaque; + + ep->delayed_cb[0](&ep->packey[0], opaque); +} + +static inline void musb_cb_tick1(void *opaque) +{ + MUSBEndPoint *ep = (MUSBEndPoint *) opaque; + + ep->delayed_cb[1](&ep->packey[1], opaque); +} + +#define musb_cb_tick (dir ? musb_cb_tick1 : musb_cb_tick0) + +static inline void musb_schedule_cb(USBPacket *packey, void *opaque, int dir) +{ + MUSBEndPoint *ep = (MUSBEndPoint *) opaque; + int timeout = 0; + + if (ep->status[dir] == USB_RET_NAK) + timeout = ep->timeout[dir]; + else if (ep->interrupt[dir]) + timeout = 8; + else + return musb_cb_tick(opaque); + + if (!ep->intv_timer[dir]) + ep->intv_timer[dir] = qemu_new_timer(vm_clock, musb_cb_tick, opaque); + + qemu_mod_timer(ep->intv_timer[dir], qemu_get_clock(vm_clock) + + muldiv64(timeout, get_ticks_per_sec(), 8000)); +} + +static void musb_schedule0_cb(USBPacket *packey, void *opaque) +{ + return musb_schedule_cb(packey, opaque, 0); +} + +static void musb_schedule1_cb(USBPacket *packey, void *opaque) +{ + return musb_schedule_cb(packey, opaque, 1); +} + +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 inline void musb_packet(MUSBState *s, MUSBEndPoint *ep, + int epnum, int pid, int len, USBCallback cb, int dir) +{ + int ret; + int idx = epnum && dir; + 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; + cb = dir ? musb_schedule1_cb : musb_schedule0_cb; + + ep->packey[dir].pid = pid; + /* A wild guess on the FADDR semantics... */ + ep->packey[dir].devaddr = ep->faddr[idx]; + ep->packey[dir].devep = ep->type[idx] & 0xf; + ep->packey[dir].data = (void *) ep->buf[idx]; + ep->packey[dir].len = len; + ep->packey[dir].complete_cb = cb; + ep->packey[dir].complete_opaque = ep; + + if (s->port.dev) + ret = s->port.dev->info->handle_packet(s->port.dev, &ep->packey[dir]); + else + ret = USB_RET_NODEV; + + if (ret == USB_RET_ASYNC) { + ep->status[dir] = len; + return; + } + + ep->status[dir] = ret; + usb_packet_complete(&ep->packey[dir]); +} + +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; + packey->len = 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]) + return musb_packet(s, ep, epnum, USB_TOKEN_IN, + packey->len, musb_rx_packet_complete, 1); + + 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. */ + + packey->len = ep->status[1]; + + 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 = packey->len; /* 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_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. */ + } + + return 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].pid == USB_TOKEN_IN && ep->status[1] >= 0 && + (ep->fifostart[1]) + ep->rxcount < + ep->packey[1].len) { + TRACE("0x%08x, %d", ep->fifostart[1], ep->rxcount ); + ep->fifostart[1] += ep->rxcount; + ep->fifolen[1] = 0; + + ep->rxcount = MIN(ep->packey[0].len - (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].devaddr == 2) + total = MIN(s->setup_len, 8); + else + total = MIN(s->setup_len, 64); + s->setup_len -= total; + } +#endif + + return 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]) + qemu_del_timer(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, target_phys_addr_t 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, target_phys_addr_t 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_send_msg(s->port.dev, USB_MSG_RESET); + /* 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, target_phys_addr_t 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, target_phys_addr_t 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, target_phys_addr_t 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, target_phys_addr_t 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-net.c b/hw/usb-net.c new file mode 100644 index 0000000..bf51bb3 --- /dev/null +++ b/hw/usb-net.c @@ -0,0 +1,1441 @@ +/* + * 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 "usb.h" +#include "usb-desc.h" +#include "net.h" +#include "qemu-queue.h" +#include "sysemu.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_DT_CS_INTERFACE 0x24 +#define USB_DT_CS_ENDPOINT 0x25 + +#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 = 0xc0, + .bMaxPower = 0x32, + .nif = ARRAY_SIZE(desc_iface_rndis), + .ifs = desc_iface_rndis, + },{ + .bNumInterfaces = 2, + .bConfigurationValue = DEV_CONFIG_VALUE, + .iConfiguration = STRING_CDC, + .bmAttributes = 0xc0, + .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]; + + USBPacket *inpkt; + unsigned int in_ptr, in_len; + uint8_t in_buf[2048]; + + char usbstring_mac[13]; + NICState *nic; + NICConf conf; + QTAILQ_HEAD(rndis_resp_head, rndis_response) rndis_resp; +} USBNetState; + +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); + qemu_free(r); + + return ret; +} + +static void *rndis_queue_response(USBNetState *s, unsigned int length) +{ + struct rndis_response *r = + qemu_mallocz(sizeof(struct rndis_response) + length); + + 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); + qemu_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; +} + +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 = s->in_ptr = s->in_len = 0; + 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 int usb_net_handle_control(USBDevice *dev, int request, int value, + int index, int length, uint8_t *data) +{ + USBNetState *s = (USBNetState *) dev; + int ret; + + ret = usb_desc_handle_control(dev, request, value, index, length, data); + if (ret >= 0) { + return ret; + } + + ret = 0; + 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); + break; + + case ClassInterfaceRequest | USB_CDC_GET_ENCAPSULATED_RESPONSE: + if (!is_rndis(s) || value || index != 0) { + goto fail; + } + ret = rndis_get_response(s, data); + if (!ret) { + data[0] = 0; + ret = 1; + } +#ifdef TRAFFIC_DEBUG + { + unsigned int i; + fprintf(stderr, "GET_ENCAPSULATED_RESPONSE:"); + for (i = 0; i < ret; i++) { + if (!(i & 15)) + fprintf(stderr, "\n%04x:", i); + fprintf(stderr, " %02x", data[i]); + } + fprintf(stderr, "\n\n"); + } +#endif + break; + + case DeviceRequest | USB_REQ_GET_INTERFACE: + case InterfaceRequest | USB_REQ_GET_INTERFACE: + data[0] = 0; + ret = 1; + break; + + case DeviceOutRequest | USB_REQ_SET_INTERFACE: + case InterfaceOutRequest | USB_REQ_SET_INTERFACE: + ret = 0; + 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); + ret = USB_RET_STALL; + break; + } + return ret; +} + +static int usb_net_handle_statusin(USBNetState *s, USBPacket *p) +{ + int ret = 8; + + if (p->len < 8) + return USB_RET_STALL; + + ((le32 *) p->data)[0] = cpu_to_le32(1); + ((le32 *) p->data)[1] = cpu_to_le32(0); + if (!s->rndis_resp.tqh_first) + ret = USB_RET_NAK; + +#ifdef TRAFFIC_DEBUG + fprintf(stderr, "usbnet: interrupt poll len %u return %d", p->len, ret); + { + int i; + fprintf(stderr, ":"); + for (i = 0; i < ret; i++) { + if (!(i & 15)) + fprintf(stderr, "\n%04x:", i); + fprintf(stderr, " %02x", p->data[i]); + } + fprintf(stderr, "\n\n"); + } +#endif + + return ret; +} + +static int usb_net_handle_datain(USBNetState *s, USBPacket *p) +{ + int ret = USB_RET_NAK; + + if (s->in_ptr > s->in_len) { + s->in_ptr = s->in_len = 0; + ret = USB_RET_NAK; + return ret; + } + if (!s->in_len) { + ret = USB_RET_NAK; + return ret; + } + ret = s->in_len - s->in_ptr; + if (ret > p->len) + ret = p->len; + memcpy(p->data, &s->in_buf[s->in_ptr], ret); + s->in_ptr += ret; + if (s->in_ptr >= s->in_len && + (is_rndis(s) || (s->in_len & (64 - 1)) || !ret)) { + /* no short packet necessary */ + s->in_ptr = s->in_len = 0; + } + +#ifdef TRAFFIC_DEBUG + fprintf(stderr, "usbnet: data in len %u return %d", p->len, ret); + { + int i; + fprintf(stderr, ":"); + for (i = 0; i < ret; i++) { + if (!(i & 15)) + fprintf(stderr, "\n%04x:", i); + fprintf(stderr, " %02x", p->data[i]); + } + fprintf(stderr, "\n\n"); + } +#endif + + return ret; +} + +static int usb_net_handle_dataout(USBNetState *s, USBPacket *p) +{ + int ret = p->len; + 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 %u\n", p->len); + { + int i; + fprintf(stderr, ":"); + for (i = 0; i < p->len; i++) { + if (!(i & 15)) + fprintf(stderr, "\n%04x:", i); + fprintf(stderr, " %02x", p->data[i]); + } + fprintf(stderr, "\n\n"); + } +#endif + + if (sz > ret) + sz = ret; + memcpy(&s->out_buf[s->out_ptr], p->data, sz); + s->out_ptr += sz; + + if (!is_rndis(s)) { + if (ret < 64) { + qemu_send_packet(&s->nic->nc, s->out_buf, s->out_ptr); + s->out_ptr = 0; + } + return ret; + } + len = le32_to_cpu(msg->MessageLength); + if (s->out_ptr < 8 || s->out_ptr < len) + return ret; + 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(&s->nic->nc, s->out_buf + offs, size); + } + s->out_ptr -= len; + memmove(s->out_buf, &s->out_buf[len], s->out_ptr); + + return ret; +} + +static int usb_net_handle_data(USBDevice *dev, USBPacket *p) +{ + USBNetState *s = (USBNetState *) dev; + int ret = 0; + + switch(p->pid) { + case USB_TOKEN_IN: + switch (p->devep) { + case 1: + ret = usb_net_handle_statusin(s, p); + break; + + case 2: + ret = usb_net_handle_datain(s, p); + break; + + default: + goto fail; + } + break; + + case USB_TOKEN_OUT: + switch (p->devep) { + case 2: + ret = usb_net_handle_dataout(s, p); + break; + + default: + goto fail; + } + break; + + default: + fail: + ret = USB_RET_STALL; + break; + } + if (ret == USB_RET_STALL) + fprintf(stderr, "usbnet: failed data transaction: " + "pid 0x%x ep 0x%x len 0x%x\n", + p->pid, p->devep, p->len); + return ret; +} + +static ssize_t usbnet_receive(VLANClientState *nc, const uint8_t *buf, size_t size) +{ + USBNetState *s = DO_UPCAST(NICState, nc, nc)->opaque; + struct rndis_packet_msg_type *msg; + + if (is_rndis(s)) { + msg = (struct rndis_packet_msg_type *) s->in_buf; + if (!s->rndis_state == RNDIS_DATA_INITIALIZED) + return -1; + if (size + sizeof(struct rndis_packet_msg_type) > sizeof(s->in_buf)) + return -1; + + memset(msg, 0, sizeof(struct rndis_packet_msg_type)); + msg->MessageType = cpu_to_le32(RNDIS_PACKET_MSG); + msg->MessageLength = cpu_to_le32(size + sizeof(struct rndis_packet_msg_type)); + msg->DataOffset = cpu_to_le32(sizeof(struct rndis_packet_msg_type) - 8); + msg->DataLength = cpu_to_le32(size); + /* msg->OOBDataOffset; + * msg->OOBDataLength; + * msg->NumOOBDataElements; + * msg->PerPacketInfoOffset; + * msg->PerPacketInfoLength; + * msg->VcHandle; + * msg->Reserved; + */ + memcpy(msg + 1, buf, size); + s->in_len = size + sizeof(struct rndis_packet_msg_type); + } else { + if (size > sizeof(s->in_buf)) + return -1; + memcpy(s->in_buf, buf, size); + s->in_len = size; + } + s->in_ptr = 0; + return size; +} + +static int usbnet_can_receive(VLANClientState *nc) +{ + USBNetState *s = DO_UPCAST(NICState, nc, nc)->opaque; + + if (is_rndis(s) && !s->rndis_state == RNDIS_DATA_INITIALIZED) { + return 1; + } + + return !s->in_len; +} + +static void usbnet_cleanup(VLANClientState *nc) +{ + USBNetState *s = DO_UPCAST(NICState, nc, nc)->opaque; + + 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_vlan_client(&s->nic->nc); +} + +static NetClientInfo net_usbnet_info = { + .type = NET_CLIENT_TYPE_NIC, + .size = sizeof(NICState), + .can_receive = usbnet_can_receive, + .receive = usbnet_receive, + .cleanup = usbnet_cleanup, +}; + +static int usb_net_initfn(USBDevice *dev) +{ + USBNetState *s = DO_UPCAST(USBNetState, dev, 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; + + qemu_macaddr_default_if_unset(&s->conf.macaddr); + s->nic = qemu_new_nic(&net_usbnet_info, &s->conf, + s->dev.qdev.info->name, s->dev.qdev.id, s); + qemu_format_nic_info_str(&s->nic->nc, 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); + + add_boot_device_path(s->conf.bootindex, &dev->qdev, "/ethernet@0"); + return 0; +} + +static USBDevice *usb_net_init(const char *cmdline) +{ + USBDevice *dev; + QemuOpts *opts; + int idx; + + opts = qemu_opts_parse(qemu_find_opts("net"), cmdline, 0); + if (!opts) { + return NULL; + } + qemu_opt_set(opts, "type", "nic"); + qemu_opt_set(opts, "model", "usb"); + + idx = net_client_init(NULL, opts, 0); + if (idx == -1) { + return NULL; + } + + dev = usb_create(NULL /* FIXME */, "usb-net"); + if (!dev) { + return NULL; + } + qdev_set_nic_properties(&dev->qdev, &nd_table[idx]); + qdev_init_nofail(&dev->qdev); + return dev; +} + +static struct USBDeviceInfo net_info = { + .product_desc = "QEMU USB Network Interface", + .qdev.name = "usb-net", + .qdev.fw_name = "network", + .qdev.size = sizeof(USBNetState), + .usb_desc = &desc_net, + .init = usb_net_initfn, + .handle_packet = usb_generic_handle_packet, + .handle_reset = usb_net_handle_reset, + .handle_control = usb_net_handle_control, + .handle_data = usb_net_handle_data, + .handle_destroy = usb_net_handle_destroy, + .usbdevice_name = "net", + .usbdevice_init = usb_net_init, + .qdev.props = (Property[]) { + DEFINE_NIC_PROPERTIES(USBNetState, conf), + DEFINE_PROP_END_OF_LIST(), + } +}; + +static void usb_net_register_devices(void) +{ + usb_qdev_register(&net_info); +} +device_init(usb_net_register_devices) diff --git a/hw/usb-ohci.c b/hw/usb-ohci.c new file mode 100644 index 0000000..09ea0b6 --- /dev/null +++ b/hw/usb-ohci.c @@ -0,0 +1,1792 @@ +/* + * 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 . + * + * 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 Handle unrecoverable errors properly + * o BIOS work to boot from USB storage +*/ + +#include "hw.h" +#include "qemu-timer.h" +#include "usb.h" +#include "pci.h" +#include "usb-ohci.h" +#include "sysbus.h" +#include "qdev-addr.h" + +//#define DEBUG_OHCI +/* Dump packet contents. */ +//#define DEBUG_PACKET +//#define DEBUG_ISOCH +/* This causes frames to occur 1000x slower */ +//#define OHCI_TIME_WARP 1 + +#ifdef DEBUG_OHCI +#define DPRINTF printf +#else +#define DPRINTF(...) +#endif + +/* 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; + int mem; + 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; + int done_count; + + /* Frame counter partition */ + uint32_t fsmps:15; + uint32_t fit:1; + uint32_t fi:14; + uint32_t frt:1; + 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 */ + target_phys_addr_t localmem_base; + + /* Active packets. */ + uint32_t old_ctl; + USBPacket usb_packet; + uint8_t usb_buf[8192]; + uint32_t async_td; + int async_complete; + +} OHCIState; + +/* Host Controller Communications Area */ +struct ohci_hcca { + uint32_t intr[32]; + uint16_t frame, pad; + uint32_t done; +}; + +static void ohci_bus_stop(OHCIState *ohci); + +/* Bitfields for the first word of an Endpoint Desciptor. */ +#define OHCI_ED_FA_SHIFT 0 +#define OHCI_ED_FA_MASK (0x7f<> 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 (1<<0) /* Scheduling overrun */ +#define OHCI_INTR_WD (1<<1) /* HcDoneHead writeback */ +#define OHCI_INTR_SF (1<<2) /* Start of frame */ +#define OHCI_INTR_RD (1<<3) /* Resume detect */ +#define OHCI_INTR_UE (1<<4) /* Unrecoverable error */ +#define OHCI_INTR_FNO (1<<5) /* Frame number overflow */ +#define OHCI_INTR_RHSC (1<<6) /* Root hub status change */ +#define OHCI_INTR_OC (1<<30) /* Ownership change */ +#define OHCI_INTR_MIE (1<<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 (1<<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 (1<<0) +#define OHCI_RHS_OCI (1<<1) +#define OHCI_RHS_DRWE (1<<15) +#define OHCI_RHS_LPSC (1<<16) +#define OHCI_RHS_OCIC (1<<17) +#define OHCI_RHS_CRWE (1<<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) + +/* 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]; + + /* 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); + } + + DPRINTF("usb-ohci: Attached port %d\n", port1->index); +} + +static void ohci_detach(USBPort *port1) +{ + OHCIState *s = port1->opaque; + OHCIPort *port = &s->rhport[port1->index]; + uint32_t old_state = port->ctrl; + + /* 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; + } + DPRINTF("usb-ohci: Detached port %d\n", port1->index); + + if (old_state != port->ctrl) + ohci_set_interrupt(s, OHCI_INTR_RHSC); +} + +/* 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) { + usb_attach(&port->port, port->port.dev); + } + } + if (ohci->async_td) { + usb_cancel_packet(&ohci->usb_packet); + ohci->async_td = 0; + } + DPRINTF("usb-ohci: Reset %s\n", ohci->name); +} + +/* Get an array of dwords from main memory */ +static inline int get_dwords(OHCIState *ohci, + uint32_t addr, uint32_t *buf, int num) +{ + int i; + + addr += ohci->localmem_base; + + for (i = 0; i < num; i++, buf++, addr += sizeof(*buf)) { + cpu_physical_memory_rw(addr, (uint8_t *)buf, sizeof(*buf), 0); + *buf = le32_to_cpu(*buf); + } + + return 1; +} + +/* Put an array of dwords in to main memory */ +static inline int put_dwords(OHCIState *ohci, + uint32_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); + cpu_physical_memory_rw(addr, (uint8_t *)&tmp, sizeof(tmp), 1); + } + + return 1; +} + +/* Get an array of words from main memory */ +static inline int get_words(OHCIState *ohci, + uint32_t addr, uint16_t *buf, int num) +{ + int i; + + addr += ohci->localmem_base; + + for (i = 0; i < num; i++, buf++, addr += sizeof(*buf)) { + cpu_physical_memory_rw(addr, (uint8_t *)buf, sizeof(*buf), 0); + *buf = le16_to_cpu(*buf); + } + + return 1; +} + +/* Put an array of words in to main memory */ +static inline int put_words(OHCIState *ohci, + uint32_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); + cpu_physical_memory_rw(addr, (uint8_t *)&tmp, sizeof(tmp), 1); + } + + return 1; +} + +static inline int ohci_read_ed(OHCIState *ohci, + uint32_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, + uint32_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, + uint32_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, + uint32_t addr, struct ohci_hcca *hcca) +{ + cpu_physical_memory_rw(addr + ohci->localmem_base, + (uint8_t *)hcca, sizeof(*hcca), 0); + return 1; +} + +static inline int ohci_put_ed(OHCIState *ohci, + uint32_t addr, struct ohci_ed *ed) +{ + return put_dwords(ohci, addr, (uint32_t *)ed, sizeof(*ed) >> 2); +} + +static inline int ohci_put_td(OHCIState *ohci, + uint32_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, + uint32_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, + uint32_t addr, struct ohci_hcca *hcca) +{ + cpu_physical_memory_rw(addr + ohci->localmem_base, + (uint8_t *)hcca, sizeof(*hcca), 1); + return 1; +} + +/* Read/Write the contents of a TD from/to main memory. */ +static void ohci_copy_td(OHCIState *ohci, struct ohci_td *td, + uint8_t *buf, int len, int write) +{ + uint32_t ptr; + uint32_t n; + + ptr = td->cbp; + n = 0x1000 - (ptr & 0xfff); + if (n > len) + n = len; + cpu_physical_memory_rw(ptr + ohci->localmem_base, buf, n, write); + if (n == len) + return; + ptr = td->be & ~0xfffu; + buf += n; + cpu_physical_memory_rw(ptr + ohci->localmem_base, buf, len - n, write); +} + +/* Read/Write the contents of an ISO TD from/to main memory. */ +static void ohci_copy_iso_td(OHCIState *ohci, + uint32_t start_addr, uint32_t end_addr, + uint8_t *buf, int len, int write) +{ + uint32_t ptr; + uint32_t n; + + ptr = start_addr; + n = 0x1000 - (ptr & 0xfff); + if (n > len) + n = len; + cpu_physical_memory_rw(ptr + ohci->localmem_base, buf, n, write); + if (n == len) + return; + ptr = end_addr & ~0xfffu; + buf += n; + cpu_physical_memory_rw(ptr + ohci->localmem_base, buf, len - n, write); +} + +static void ohci_process_lists(OHCIState *ohci, int completion); + +static void ohci_async_complete_packet(USBPacket *packet, void *opaque) +{ + OHCIState *ohci = opaque; +#ifdef DEBUG_PACKET + DPRINTF("Async packet complete\n"); +#endif + ohci->async_complete = 1; + 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; +#ifdef DEBUG_ISOCH + const char *str = NULL; +#endif + int pid; + int ret; + int i; + USBDevice *dev; + 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)) { + printf("usb-ohci: ISO_TD read error at %x\n", addr); + 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); + +#ifdef DEBUG_ISOCH + printf("--- ISO_TD ED head 0x%.8x tailp 0x%.8x\n" + "0x%.8x 0x%.8x 0x%.8x 0x%.8x\n" + "0x%.8x 0x%.8x 0x%.8x 0x%.8x\n" + "0x%.8x 0x%.8x 0x%.8x 0x%.8x\n" + "frame_number 0x%.8x starting_frame 0x%.8x\n" + "frame_count 0x%.8x relative %d\n" + "di 0x%.8x cc 0x%.8x\n", + ed->head & OHCI_DPTR_MASK, ed->tail & OHCI_DPTR_MASK, + iso_td.flags, iso_td.bp, iso_td.next, iso_td.be, + 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], + ohci->frame_number, starting_frame, + frame_count, relative_frame_number, + OHCI_BM(iso_td.flags, TD_DI), OHCI_BM(iso_td.flags, TD_CC)); +#endif + + if (relative_frame_number < 0) { + DPRINTF("usb-ohci: ISO_TD R=%d < 0\n", 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 */ + DPRINTF("usb-ohci: ISO_TD R=%d > FC=%d\n", 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; + ohci_put_iso_td(ohci, addr, &iso_td); + return 0; + } + + dir = OHCI_BM(ed->flags, ED_D); + switch (dir) { + case OHCI_TD_DIR_IN: +#ifdef DEBUG_ISOCH + str = "in"; +#endif + pid = USB_TOKEN_IN; + break; + case OHCI_TD_DIR_OUT: +#ifdef DEBUG_ISOCH + str = "out"; +#endif + pid = USB_TOKEN_OUT; + break; + case OHCI_TD_DIR_SETUP: +#ifdef DEBUG_ISOCH + str = "setup"; +#endif + pid = USB_TOKEN_SETUP; + break; + default: + printf("usb-ohci: Bad direction %d\n", dir); + return 1; + } + + if (!iso_td.bp || !iso_td.be) { + printf("usb-ohci: ISO_TD bp 0x%.8x be 0x%.8x\n", 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))) { + printf("usb-ohci: ISO_TD cc != not accessed 0x%.8x 0x%.8x\n", + start_offset, next_offset); + return 1; + } + + if ((relative_frame_number < frame_count) && (start_offset > next_offset)) { + printf("usb-ohci: ISO_TD start_offset=0x%.8x > next_offset=0x%.8x\n", + 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) { + ohci_copy_iso_td(ohci, start_addr, end_addr, ohci->usb_buf, len, 0); + } + + if (completion) { + ret = ohci->usb_packet.len; + } else { + ret = USB_RET_NODEV; + for (i = 0; i < ohci->num_ports; i++) { + dev = ohci->rhport[i].port.dev; + if ((ohci->rhport[i].ctrl & OHCI_PORT_PES) == 0) + continue; + ohci->usb_packet.pid = pid; + ohci->usb_packet.devaddr = OHCI_BM(ed->flags, ED_FA); + ohci->usb_packet.devep = OHCI_BM(ed->flags, ED_EN); + ohci->usb_packet.data = ohci->usb_buf; + ohci->usb_packet.len = len; + ohci->usb_packet.complete_cb = ohci_async_complete_packet; + ohci->usb_packet.complete_opaque = ohci; + ret = dev->info->handle_packet(dev, &ohci->usb_packet); + if (ret != USB_RET_NODEV) + break; + } + + if (ret == USB_RET_ASYNC) { + return 1; + } + } + +#ifdef DEBUG_ISOCH + printf("so 0x%.8x eo 0x%.8x\nsa 0x%.8x ea 0x%.8x\ndir %s len %zu ret %d\n", + start_offset, end_offset, start_addr, end_addr, str, len, ret); +#endif + + /* Writeback */ + if (dir == OHCI_TD_DIR_IN && ret >= 0 && ret <= len) { + /* IN transfer succeeded */ + ohci_copy_iso_td(ohci, start_addr, end_addr, ohci->usb_buf, ret, 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) { + printf("usb-ohci: DataOverrun %d > %zu\n", 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) { + printf("usb-ohci: DataUnderrun %d\n", ret); + OHCI_SET_BM(iso_td.offset[relative_frame_number], TD_PSW_CC, + OHCI_CC_DATAUNDERRUN); + } else { + switch (ret) { + 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: + printf("usb-ohci: got NAK/STALL %d\n", 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: + printf("usb-ohci: Bad device response %d\n", 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; + } + ohci_put_iso_td(ohci, addr, &iso_td); + return 1; +} + +/* 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; +#ifdef DEBUG_PACKET + const char *str = NULL; +#endif + int pid; + int ret; + int i; + USBDevice *dev; + 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) { +#ifdef DEBUG_PACKET + DPRINTF("Skipping async TD\n"); +#endif + return 1; + } + if (!ohci_read_td(ohci, addr, &td)) { + fprintf(stderr, "usb-ohci: TD read error at %x\n", addr); + 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: +#ifdef DEBUG_PACKET + str = "in"; +#endif + pid = USB_TOKEN_IN; + break; + case OHCI_TD_DIR_OUT: +#ifdef DEBUG_PACKET + str = "out"; +#endif + pid = USB_TOKEN_OUT; + break; + case OHCI_TD_DIR_SETUP: +#ifdef DEBUG_PACKET + str = "setup"; +#endif + pid = USB_TOKEN_SETUP; + break; + default: + fprintf(stderr, "usb-ohci: Bad direction\n"); + 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; + } + + if (len && dir != OHCI_TD_DIR_IN && !completion) { + ohci_copy_td(ohci, &td, ohci->usb_buf, len, 0); + } + } + + flag_r = (td.flags & OHCI_TD_R) != 0; +#ifdef DEBUG_PACKET + DPRINTF(" TD @ 0x%.8x %" PRId64 " bytes %s r=%d cbp=0x%.8x be=0x%.8x\n", + addr, (int64_t)len, str, flag_r, td.cbp, td.be); + + if (len > 0 && dir != OHCI_TD_DIR_IN) { + DPRINTF(" data:"); + for (i = 0; i < len; i++) + printf(" %.2x", ohci->usb_buf[i]); + DPRINTF("\n"); + } +#endif + if (completion) { + ret = ohci->usb_packet.len; + ohci->async_td = 0; + ohci->async_complete = 0; + } else { + ret = USB_RET_NODEV; + for (i = 0; i < ohci->num_ports; i++) { + dev = ohci->rhport[i].port.dev; + if ((ohci->rhport[i].ctrl & OHCI_PORT_PES) == 0) + continue; + + 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. + */ +#ifdef DEBUG_PACKET + DPRINTF("Too many pending packets\n"); +#endif + return 1; + } + ohci->usb_packet.pid = pid; + ohci->usb_packet.devaddr = OHCI_BM(ed->flags, ED_FA); + ohci->usb_packet.devep = OHCI_BM(ed->flags, ED_EN); + ohci->usb_packet.data = ohci->usb_buf; + ohci->usb_packet.len = len; + ohci->usb_packet.complete_cb = ohci_async_complete_packet; + ohci->usb_packet.complete_opaque = ohci; + ret = dev->info->handle_packet(dev, &ohci->usb_packet); + if (ret != USB_RET_NODEV) + break; + } +#ifdef DEBUG_PACKET + DPRINTF("ret=%d\n", ret); +#endif + if (ret == USB_RET_ASYNC) { + ohci->async_td = addr; + return 1; + } + } + if (ret >= 0) { + if (dir == OHCI_TD_DIR_IN) { + ohci_copy_td(ohci, &td, ohci->usb_buf, ret, 1); +#ifdef DEBUG_PACKET + DPRINTF(" data:"); + for (i = 0; i < ret; i++) + printf(" %.2x", ohci->usb_buf[i]); + DPRINTF("\n"); +#endif + } else { + ret = len; + } + } + + /* Writeback */ + if (ret == len || (dir == OHCI_TD_DIR_IN && ret >= 0 && flag_r)) { + /* Transmission succeeded. */ + if (ret == len) { + td.cbp = 0; + } else { + td.cbp += ret; + if ((td.cbp & 0xfff) + ret > 0xfff) { + td.cbp &= 0xfff; + td.cbp |= td.be & ~0xfff; + } + } + 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); + + ed->head &= ~OHCI_ED_C; + if (td.flags & OHCI_TD_T0) + ed->head |= OHCI_ED_C; + } else { + if (ret >= 0) { + DPRINTF("usb-ohci: Underrun\n"); + OHCI_SET_BM(td.flags, TD_CC, OHCI_CC_DATAUNDERRUN); + } else { + switch (ret) { + case USB_RET_NODEV: + OHCI_SET_BM(td.flags, TD_CC, OHCI_CC_DEVICENOTRESPONDING); + case USB_RET_NAK: + DPRINTF("usb-ohci: got NAK\n"); + return 1; + case USB_RET_STALL: + DPRINTF("usb-ohci: got STALL\n"); + OHCI_SET_BM(td.flags, TD_CC, OHCI_CC_STALL); + break; + case USB_RET_BABBLE: + DPRINTF("usb-ohci: got BABBLE\n"); + OHCI_SET_BM(td.flags, TD_CC, OHCI_CC_DATAOVERRUN); + break; + default: + fprintf(stderr, "usb-ohci: Bad device response %d\n", 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; + ohci_put_td(ohci, addr, &td); + 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)) { + fprintf(stderr, "usb-ohci: ED read error at %x\n", cur); + 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; + } + continue; + } + + while ((ed.head & OHCI_DPTR_MASK) != ed.tail) { +#ifdef DEBUG_PACKET + DPRINTF("ED @ 0x%.8x fa=%u en=%u d=%u s=%u k=%u f=%u mps=%u " + "h=%u c=%u\n head=0x%.8x tailp=0x%.8x next=0x%.8x\n", cur, + 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), (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); +#endif + 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; + } + } + + ohci_put_ed(ohci, cur, &ed); + } + + return active; +} + +/* Generate a SOF event, and set a timer for EOF */ +static void ohci_sof(OHCIState *ohci) +{ + ohci->sof_time = qemu_get_clock(vm_clock); + qemu_mod_timer(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) { + DPRINTF("usb-ohci: head %x, cur %x\n", + 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; + + ohci_read_hcca(ohci, ohci->hcca, &hcca); + + /* 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->async_td && + ohci->old_ctl & (~ohci->ctl) & (OHCI_CTL_BLE | OHCI_CTL_CLE)) { + usb_cancel_packet(&ohci->usb_packet); + ohci->async_td = 0; + } + ohci->old_ctl = ohci->ctl; + ohci_process_lists(ohci, 0); + + /* 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 */ + ohci_put_hcca(ohci, ohci->hcca, &hcca); +} + +/* Start sending SOF tokens across the USB bus, lists are processed in + * next frame + */ +static int ohci_bus_start(OHCIState *ohci) +{ + ohci->eof_timer = qemu_new_timer(vm_clock, + ohci_frame_boundary, + ohci); + + if (ohci->eof_timer == NULL) { + fprintf(stderr, "usb-ohci: %s: qemu_new_timer failed\n", ohci->name); + /* TODO: Signal unrecoverable error */ + return 0; + } + + DPRINTF("usb-ohci: %s: USB Operational\n", ohci->name); + + ohci_sof(ohci); + + return 1; +} + +/* Stop sending SOF tokens on the bus */ +static void ohci_bus_stop(OHCIState *ohci) +{ + if (ohci->eof_timer) + qemu_del_timer(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) { + DPRINTF("usb-ohci: %s: FrameInterval = 0x%x (%u)\n", + 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; + + switch (new_state) { + case OHCI_USB_OPERATIONAL: + ohci_bus_start(ohci); + break; + case OHCI_USB_SUSPEND: + ohci_bus_stop(ohci); + DPRINTF("usb-ohci: %s: USB Suspended\n", ohci->name); + break; + case OHCI_USB_RESUME: + DPRINTF("usb-ohci: %s: USB Resume\n", ohci->name); + break; + case OHCI_USB_RESET: + ohci_reset(ohci); + DPRINTF("usb-ohci: %s: USB Reset\n", ohci->name); + 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_get_clock(vm_clock) - 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); + DPRINTF("usb-ohci: powered down all ports\n"); + } + + if (val & OHCI_RHS_LPSC) { + int i; + + for (i = 0; i < ohci->num_ports; i++) + ohci_port_power(ohci, i, 1); + DPRINTF("usb-ohci: powered up all ports\n"); + } + + 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)) { + DPRINTF("usb-ohci: port %d: SUSPEND\n", portnum); + } + + if (ohci_port_set_if_connected(ohci, portnum, val & OHCI_PORT_PRS)) { + DPRINTF("usb-ohci: port %d: RESET\n", portnum); + usb_send_msg(port->port.dev, USB_MSG_RESET); + 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); + + return; +} + +static uint32_t ohci_mem_read(void *ptr, target_phys_addr_t addr) +{ + OHCIState *ohci = ptr; + uint32_t retval; + + addr &= 0xff; + + /* Only aligned reads are allowed on OHCI */ + if (addr & 3) { + fprintf(stderr, "usb-ohci: Mis-aligned read\n"); + 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: + fprintf(stderr, "ohci_read: Bad offset %x\n", (int)addr); + retval = 0xffffffff; + } + } + + return retval; +} + +static void ohci_mem_write(void *ptr, target_phys_addr_t addr, uint32_t val) +{ + OHCIState *ohci = ptr; + + addr &= 0xff; + + /* Only aligned reads are allowed on OHCI */ + if (addr & 3) { + fprintf(stderr, "usb-ohci: Mis-aligned write\n"); + 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 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); + + 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: + fprintf(stderr, "ohci_write: Bad offset %x\n", (int)addr); + break; + } +} + +/* Only dword reads are defined on OHCI register space */ +static CPUReadMemoryFunc * const ohci_readfn[3]={ + ohci_mem_read, + ohci_mem_read, + ohci_mem_read +}; + +/* Only dword writes are defined on OHCI register space */ +static CPUWriteMemoryFunc * const ohci_writefn[3]={ + ohci_mem_write, + ohci_mem_write, + ohci_mem_write +}; + +static USBPortOps ohci_port_ops = { + .attach = ohci_attach, + .detach = ohci_detach, +}; + +static void usb_ohci_init(OHCIState *ohci, DeviceState *dev, + int num_ports, uint32_t localmem_base) +{ + int i; + + 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 + DPRINTF("usb-ohci: usb_bit_time=%" PRId64 " usb_frame_time=%" PRId64 "\n", + usb_frame_time, usb_bit_time); + } + + ohci->mem = cpu_register_io_memory(ohci_readfn, ohci_writefn, ohci, + DEVICE_LITTLE_ENDIAN); + ohci->localmem_base = localmem_base; + + ohci->name = dev->info->name; + + usb_bus_new(&ohci->bus, dev); + ohci->num_ports = num_ports; + 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); + usb_port_location(&ohci->rhport[i].port, NULL, i+1); + } + + ohci->async_td = 0; + qemu_register_reset(ohci_reset, ohci); +} + +typedef struct { + PCIDevice pci_dev; + OHCIState state; +} OHCIPCIState; + +static void ohci_mapfunc(PCIDevice *pci_dev, int i, + pcibus_t addr, pcibus_t size, int type) +{ + OHCIPCIState *ohci = DO_UPCAST(OHCIPCIState, pci_dev, pci_dev); + cpu_register_physical_memory(addr, size, ohci->state.mem); +} + +static int usb_ohci_initfn_pci(struct PCIDevice *dev) +{ + OHCIPCIState *ohci = DO_UPCAST(OHCIPCIState, pci_dev, dev); + int num_ports = 3; + + pci_config_set_vendor_id(ohci->pci_dev.config, PCI_VENDOR_ID_APPLE); + pci_config_set_device_id(ohci->pci_dev.config, + PCI_DEVICE_ID_APPLE_IPID_USB); + ohci->pci_dev.config[PCI_CLASS_PROG] = 0x10; /* OHCI */ + pci_config_set_class(ohci->pci_dev.config, PCI_CLASS_SERIAL_USB); + /* TODO: RST# value should be 0. */ + ohci->pci_dev.config[PCI_INTERRUPT_PIN] = 0x01; /* interrupt pin 1 */ + + usb_ohci_init(&ohci->state, &dev->qdev, num_ports, 0); + ohci->state.irq = ohci->pci_dev.irq[0]; + + /* TODO: avoid cast below by using dev */ + pci_register_bar(&ohci->pci_dev, 0, 256, + PCI_BASE_ADDRESS_SPACE_MEMORY, ohci_mapfunc); + return 0; +} + +void usb_ohci_init_pci(struct PCIBus *bus, int devfn) +{ + pci_create_simple(bus, devfn, "pci-ohci"); +} + +typedef struct { + SysBusDevice busdev; + OHCIState ohci; + uint32_t num_ports; + target_phys_addr_t dma_offset; +} OHCISysBusState; + +static int ohci_init_pxa(SysBusDevice *dev) +{ + OHCISysBusState *s = FROM_SYSBUS(OHCISysBusState, dev); + + usb_ohci_init(&s->ohci, &dev->qdev, s->num_ports, s->dma_offset); + sysbus_init_irq(dev, &s->ohci.irq); + sysbus_init_mmio(dev, 0x1000, s->ohci.mem); + + return 0; +} + +static PCIDeviceInfo ohci_pci_info = { + .qdev.name = "pci-ohci", + .qdev.desc = "Apple USB Controller", + .qdev.size = sizeof(OHCIPCIState), + .init = usb_ohci_initfn_pci, +}; + +static SysBusDeviceInfo ohci_sysbus_info = { + .init = ohci_init_pxa, + .qdev.name = "sysbus-ohci", + .qdev.desc = "OHCI USB Controller", + .qdev.size = sizeof(OHCISysBusState), + .qdev.props = (Property[]) { + DEFINE_PROP_UINT32("num-ports", OHCISysBusState, num_ports, 3), + DEFINE_PROP_TADDR("dma-offset", OHCISysBusState, dma_offset, 3), + DEFINE_PROP_END_OF_LIST(), + } +}; + +static void ohci_register(void) +{ + pci_qdev_register(&ohci_pci_info); + sysbus_register_withprop(&ohci_sysbus_info); +} +device_init(ohci_register); diff --git a/hw/usb-ohci.h b/hw/usb-ohci.h new file mode 100644 index 0000000..eefcef3 --- /dev/null +++ b/hw/usb-ohci.h @@ -0,0 +1,9 @@ +#ifndef QEMU_USB_OHCI_H +#define QEMU_USB_OHCI_H + +#include "qemu-common.h" + +void usb_ohci_init_pci(struct PCIBus *bus, int devfn); + +#endif + diff --git a/hw/usb-serial.c b/hw/usb-serial.c new file mode 100644 index 0000000..6763d52 --- /dev/null +++ b/hw/usb-serial.c @@ -0,0 +1,611 @@ +/* + * FTDI FT232BM Device emulation + * + * Copyright (c) 2006 CodeSourcery. + * Copyright (c) 2008 Samuel Thibault + * Written by Paul Brook, reused for FTDI by Samuel Thibault + * + * This code is licenced under the LGPL. + */ + +#include "qemu-common.h" +#include "qemu-error.h" +#include "usb.h" +#include "usb-desc.h" +#include "qemu-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; + +enum { + STR_MANUFACTURER = 1, + STR_PRODUCT_SERIAL, + STR_PRODUCT_BRAILLE, + STR_SERIALNUMBER, +}; + +static const USBDescStrings desc_strings = { + [STR_MANUFACTURER] = "QEMU " QEMU_VERSION, + [STR_PRODUCT_SERIAL] = "QEMU USB SERIAL", + [STR_PRODUCT_BRAILLE] = "QEMU USB 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 = 0x80, + .bMaxPower = 50, + .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_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 int usb_serial_handle_control(USBDevice *dev, 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, request, value, index, length, data); + if (ret >= 0) { + return ret; + } + + ret = 0; + switch (request) { + case DeviceRequest | USB_REQ_GET_INTERFACE: + data[0] = 0; + ret = 1; + break; + case InterfaceOutRequest | USB_REQ_SET_INTERFACE: + ret = 0; + break; + case EndpointOutRequest | USB_REQ_CLEAR_FEATURE: + ret = 0; + 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_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_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_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_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; + ret = 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; + ret = 1; + break; + default: + fail: + DPRINTF("got unsupported/bogus control %x, value %x\n", request, value); + ret = USB_RET_STALL; + break; + } + return ret; +} + +static int usb_serial_handle_data(USBDevice *dev, USBPacket *p) +{ + USBSerialState *s = (USBSerialState *)dev; + int ret = 0; + uint8_t devep = p->devep; + uint8_t *data = p->data; + int len = p->len; + int first_len; + + switch (p->pid) { + case USB_TOKEN_OUT: + if (devep != 2) + goto fail; + qemu_chr_write(s->cs, data, len); + break; + + case USB_TOKEN_IN: + if (devep != 1) + goto fail; + first_len = RECV_BUF - s->recv_ptr; + if (len <= 2) { + ret = USB_RET_NAK; + break; + } + *data++ = 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; + *data = FTDI_BI; + ret = 2; + break; + } else { + *data++ = 0; + } + len -= 2; + if (len > s->recv_used) + len = s->recv_used; + if (!len) { + ret = USB_RET_NAK; + break; + } + if (first_len > len) + first_len = len; + memcpy(data, s->recv_buf + s->recv_ptr, first_len); + if (len > first_len) + memcpy(data + first_len, s->recv_buf, len - first_len); + s->recv_used -= len; + s->recv_ptr = (s->recv_ptr + len) % RECV_BUF; + ret = len + 2; + break; + + default: + DPRINTF("Bad token\n"); + fail: + ret = USB_RET_STALL; + break; + } + + return ret; +} + +static void usb_serial_handle_destroy(USBDevice *dev) +{ + USBSerialState *s = (USBSerialState *)dev; + + qemu_chr_close(s->cs); +} + +static int usb_serial_can_read(void *opaque) +{ + USBSerialState *s = opaque; + 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: + usb_serial_reset(s); + /* TODO: Reset USB port */ + break; + } +} + +static int usb_serial_initfn(USBDevice *dev) +{ + USBSerialState *s = DO_UPCAST(USBSerialState, dev, dev); + + usb_desc_init(dev); + + if (!s->cs) { + error_report("Property chardev is required"); + return -1; + } + + qemu_chr_add_handlers(s->cs, usb_serial_can_read, usb_serial_read, + usb_serial_event, s); + usb_serial_handle_reset(dev); + return 0; +} + +static USBDevice *usb_serial_init(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_open(label, filename, NULL); + if (!cdrv) + return NULL; + + dev = usb_create(NULL /* FIXME */, "usb-serial"); + if (!dev) { + return NULL; + } + 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); + qdev_init_nofail(&dev->qdev); + + return dev; +} + +static USBDevice *usb_braille_init(const char *unused) +{ + USBDevice *dev; + CharDriverState *cdrv; + + cdrv = qemu_chr_open("braille", "braille", NULL); + if (!cdrv) + return NULL; + + dev = usb_create(NULL /* FIXME */, "usb-braille"); + qdev_prop_set_chr(&dev->qdev, "chardev", cdrv); + qdev_init_nofail(&dev->qdev); + + return dev; +} + +static struct USBDeviceInfo serial_info = { + .product_desc = "QEMU USB Serial", + .qdev.name = "usb-serial", + .qdev.size = sizeof(USBSerialState), + .usb_desc = &desc_serial, + .init = usb_serial_initfn, + .handle_packet = usb_generic_handle_packet, + .handle_reset = usb_serial_handle_reset, + .handle_control = usb_serial_handle_control, + .handle_data = usb_serial_handle_data, + .handle_destroy = usb_serial_handle_destroy, + .usbdevice_name = "serial", + .usbdevice_init = usb_serial_init, + .qdev.props = (Property[]) { + DEFINE_PROP_CHR("chardev", USBSerialState, cs), + DEFINE_PROP_END_OF_LIST(), + }, +}; + +static struct USBDeviceInfo braille_info = { + .product_desc = "QEMU USB Braille", + .qdev.name = "usb-braille", + .qdev.size = sizeof(USBSerialState), + .usb_desc = &desc_braille, + .init = usb_serial_initfn, + .handle_packet = usb_generic_handle_packet, + .handle_reset = usb_serial_handle_reset, + .handle_control = usb_serial_handle_control, + .handle_data = usb_serial_handle_data, + .handle_destroy = usb_serial_handle_destroy, + .usbdevice_name = "braille", + .usbdevice_init = usb_braille_init, + .qdev.props = (Property[]) { + DEFINE_PROP_CHR("chardev", USBSerialState, cs), + DEFINE_PROP_END_OF_LIST(), + }, +}; + +static void usb_serial_register_devices(void) +{ + usb_qdev_register(&serial_info); + usb_qdev_register(&braille_info); +} +device_init(usb_serial_register_devices) diff --git a/hw/usb-uhci.c b/hw/usb-uhci.c new file mode 100644 index 0000000..b384e1d --- /dev/null +++ b/hw/usb-uhci.c @@ -0,0 +1,1229 @@ +/* + * 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.h" +#include "usb.h" +#include "pci.h" +#include "qemu-timer.h" +#include "usb-uhci.h" + +//#define DEBUG +//#define DEBUG_DUMP_DATA + +#define UHCI_CMD_FGR (1 << 4) +#define UHCI_CMD_EGSM (1 << 3) +#define UHCI_CMD_GRESET (1 << 2) +#define UHCI_CMD_HCRESET (1 << 1) +#define UHCI_CMD_RS (1 << 0) + +#define UHCI_STS_HCHALTED (1 << 5) +#define UHCI_STS_HCPERR (1 << 4) +#define UHCI_STS_HSERR (1 << 3) +#define UHCI_STS_RD (1 << 2) +#define UHCI_STS_USBERR (1 << 1) +#define UHCI_STS_USBINT (1 << 0) + +#define TD_CTRL_SPD (1 << 29) +#define TD_CTRL_ERROR_SHIFT 27 +#define TD_CTRL_IOS (1 << 25) +#define TD_CTRL_IOC (1 << 24) +#define TD_CTRL_ACTIVE (1 << 23) +#define TD_CTRL_STALL (1 << 22) +#define TD_CTRL_BABBLE (1 << 20) +#define TD_CTRL_NAK (1 << 19) +#define TD_CTRL_TIMEOUT (1 << 18) + +#define UHCI_PORT_SUSPEND (1 << 12) +#define UHCI_PORT_RESET (1 << 9) +#define UHCI_PORT_LSDA (1 << 8) +#define UHCI_PORT_RD (1 << 6) +#define UHCI_PORT_ENC (1 << 3) +#define UHCI_PORT_EN (1 << 2) +#define UHCI_PORT_CSC (1 << 1) +#define UHCI_PORT_CCS (1 << 0) + +#define UHCI_PORT_READ_ONLY (0x1bb) +#define UHCI_PORT_WRITE_CLEAR (UHCI_PORT_CSC | UHCI_PORT_ENC) + +#define FRAME_TIMER_FREQ 1000 + +#define FRAME_MAX_LOOPS 100 + +#define NB_PORTS 2 + +#ifdef DEBUG +#define DPRINTF printf + +static const char *pid2str(int pid) +{ + switch (pid) { + case USB_TOKEN_SETUP: return "SETUP"; + case USB_TOKEN_IN: return "IN"; + case USB_TOKEN_OUT: return "OUT"; + } + return "?"; +} + +#else +#define DPRINTF(...) +#endif + +#ifdef DEBUG_DUMP_DATA +static void dump_data(const uint8_t *data, int len) +{ + int i; + + printf("uhci: data: "); + for(i = 0; i < len; i++) + printf(" %02x", data[i]); + printf("\n"); +} +#else +static void dump_data(const uint8_t *data, int len) {} +#endif + +/* + * Pending async transaction. + * 'packet' must be the first field because completion + * handler does "(UHCIAsync *) pkt" cast. + */ +typedef struct UHCIAsync { + USBPacket packet; + struct UHCIAsync *next; + uint32_t td; + uint32_t token; + int8_t valid; + uint8_t isoc; + uint8_t done; + uint8_t buffer[2048]; +} UHCIAsync; + +typedef struct UHCIPort { + USBPort port; + uint16_t ctrl; +} UHCIPort; + +typedef struct UHCIState { + PCIDevice dev; + USBBus bus; + 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; + UHCIPort ports[NB_PORTS]; + + /* Interrupts that should be raised at the end of the current frame. */ + uint32_t pending_int_mask; + + /* Active packets */ + UHCIAsync *async_pending; + UHCIAsync *async_pool; + uint8_t num_ports_vmstate; +} UHCIState; + +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 UHCIAsync *uhci_async_alloc(UHCIState *s) +{ + UHCIAsync *async = qemu_malloc(sizeof(UHCIAsync)); + + memset(&async->packet, 0, sizeof(async->packet)); + async->valid = 0; + async->td = 0; + async->token = 0; + async->done = 0; + async->isoc = 0; + async->next = NULL; + + return async; +} + +static void uhci_async_free(UHCIState *s, UHCIAsync *async) +{ + qemu_free(async); +} + +static void uhci_async_link(UHCIState *s, UHCIAsync *async) +{ + async->next = s->async_pending; + s->async_pending = async; +} + +static void uhci_async_unlink(UHCIState *s, UHCIAsync *async) +{ + UHCIAsync *curr = s->async_pending; + UHCIAsync **prev = &s->async_pending; + + while (curr) { + if (curr == async) { + *prev = curr->next; + return; + } + + prev = &curr->next; + curr = curr->next; + } +} + +static void uhci_async_cancel(UHCIState *s, UHCIAsync *async) +{ + DPRINTF("uhci: cancel td 0x%x token 0x%x done %u\n", + async->td, async->token, async->done); + + if (!async->done) + usb_cancel_packet(&async->packet); + uhci_async_free(s, async); +} + +/* + * Mark all outstanding async packets as invalid. + * This is used for canceling them when TDs are removed by the HCD. + */ +static UHCIAsync *uhci_async_validate_begin(UHCIState *s) +{ + UHCIAsync *async = s->async_pending; + + while (async) { + async->valid--; + async = async->next; + } + return NULL; +} + +/* + * Cancel async packets that are no longer valid + */ +static void uhci_async_validate_end(UHCIState *s) +{ + UHCIAsync *curr = s->async_pending; + UHCIAsync **prev = &s->async_pending; + UHCIAsync *next; + + while (curr) { + if (curr->valid > 0) { + prev = &curr->next; + curr = curr->next; + continue; + } + + next = curr->next; + + /* Unlink */ + *prev = next; + + uhci_async_cancel(s, curr); + + curr = next; + } +} + +static void uhci_async_cancel_all(UHCIState *s) +{ + UHCIAsync *curr = s->async_pending; + UHCIAsync *next; + + while (curr) { + next = curr->next; + + uhci_async_cancel(s, curr); + + curr = next; + } + + s->async_pending = NULL; +} + +static UHCIAsync *uhci_async_find_td(UHCIState *s, uint32_t addr, uint32_t token) +{ + UHCIAsync *async = s->async_pending; + UHCIAsync *match = NULL; + int count = 0; + + /* + * We're looking for the best match here. ie both td addr and token. + * Otherwise we return last good match. ie just token. + * It's ok to match just token because it identifies the transaction + * rather well, token includes: device addr, endpoint, size, etc. + * + * Also since we queue async transactions in reverse order by returning + * last good match we restores the order. + * + * It's expected that we wont have a ton of outstanding transactions. + * If we ever do we'd want to optimize this algorithm. + */ + + while (async) { + if (async->token == token) { + /* Good match */ + match = async; + + if (async->td == addr) { + /* Best match */ + break; + } + } + + async = async->next; + count++; + } + + if (count > 64) + fprintf(stderr, "uhci: warning lots of async transactions\n"); + + return match; +} + +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; + } + qemu_set_irq(s->dev.irq[3], level); +} + +static void uhci_reset(void *opaque) +{ + UHCIState *s = opaque; + uint8_t *pci_conf; + int i; + UHCIPort *port; + + DPRINTF("uhci: full reset\n"); + + pci_conf = s->dev.config; + + pci_conf[0x6a] = 0x01; /* usb clock */ + pci_conf[0x6b] = 0x00; + s->cmd = 0; + s->status = 0; + 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) { + usb_attach(&port->port, port->port.dev); + } + } + + uhci_async_cancel_all(s); +} + +static void uhci_pre_save(void *opaque) +{ + UHCIState *s = opaque; + + uhci_async_cancel_all(s); +} + +static const VMStateDescription vmstate_uhci_port = { + .name = "uhci port", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField []) { + VMSTATE_UINT16(ctrl, UHCIPort), + VMSTATE_END_OF_LIST() + } +}; + +static const VMStateDescription vmstate_uhci = { + .name = "uhci", + .version_id = 2, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .pre_save = uhci_pre_save, + .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(frame_timer, UHCIState), + VMSTATE_INT64_V(expire_time, UHCIState, 2), + VMSTATE_END_OF_LIST() + } +}; + +static void uhci_ioport_writeb(void *opaque, uint32_t addr, uint32_t val) +{ + UHCIState *s = opaque; + + addr &= 0x1f; + switch(addr) { + case 0x0c: + s->sof_timing = val; + break; + } +} + +static uint32_t uhci_ioport_readb(void *opaque, uint32_t addr) +{ + UHCIState *s = opaque; + uint32_t val; + + addr &= 0x1f; + switch(addr) { + case 0x0c: + val = s->sof_timing; + break; + default: + val = 0xff; + break; + } + return val; +} + +static void uhci_ioport_writew(void *opaque, uint32_t addr, uint32_t val) +{ + UHCIState *s = opaque; + + addr &= 0x1f; + DPRINTF("uhci: writew port=0x%04x val=0x%04x\n", addr, val); + + switch(addr) { + case 0x00: + if ((val & UHCI_CMD_RS) && !(s->cmd & UHCI_CMD_RS)) { + /* start frame processing */ + qemu_mod_timer(s->frame_timer, qemu_get_clock(vm_clock)); + s->status &= ~UHCI_STS_HCHALTED; + } else if (!(val & UHCI_CMD_RS)) { + s->status |= UHCI_STS_HCHALTED; + } + if (val & UHCI_CMD_GRESET) { + UHCIPort *port; + USBDevice *dev; + int i; + + /* send reset on the USB bus */ + for(i = 0; i < NB_PORTS; i++) { + port = &s->ports[i]; + dev = port->port.dev; + if (dev) { + usb_send_msg(dev, USB_MSG_RESET); + } + } + uhci_reset(s); + return; + } + if (val & UHCI_CMD_HCRESET) { + uhci_reset(s); + return; + } + s->cmd = val; + 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 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) { + /* port reset */ + if ( (val & UHCI_PORT_RESET) && + !(port->ctrl & UHCI_PORT_RESET) ) { + usb_send_msg(dev, USB_MSG_RESET); + } + } + port->ctrl &= UHCI_PORT_READ_ONLY; + 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 uint32_t uhci_ioport_readw(void *opaque, uint32_t addr) +{ + UHCIState *s = opaque; + uint32_t val; + + addr &= 0x1f; + 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 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; + } + + DPRINTF("uhci: readw port=0x%04x val=0x%04x\n", addr, val); + + return val; +} + +static void uhci_ioport_writel(void *opaque, uint32_t addr, uint32_t val) +{ + UHCIState *s = opaque; + + addr &= 0x1f; + DPRINTF("uhci: writel port=0x%04x val=0x%08x\n", addr, val); + + switch(addr) { + case 0x08: + s->fl_base_addr = val & ~0xfff; + break; + } +} + +static uint32_t uhci_ioport_readl(void *opaque, uint32_t addr) +{ + UHCIState *s = opaque; + uint32_t val; + + addr &= 0x1f; + switch(addr) { + case 0x08: + val = s->fl_base_addr; + break; + default: + val = 0xffffffff; + break; + } + 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]; + + /* 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_wakeup(USBDevice *dev) +{ + USBBus *bus = usb_bus_from_device(dev); + UHCIState *s = container_of(bus, UHCIState, bus); + UHCIPort *port = s->ports + dev->port->index; + + if (port->ctrl & UHCI_PORT_SUSPEND && !(port->ctrl & UHCI_PORT_RD)) { + port->ctrl |= UHCI_PORT_RD; + uhci_resume(s); + } +} + +static int uhci_broadcast_packet(UHCIState *s, USBPacket *p) +{ + int i, ret; + + DPRINTF("uhci: packet enter. pid %s addr 0x%02x ep %d len %d\n", + pid2str(p->pid), p->devaddr, p->devep, p->len); + if (p->pid == USB_TOKEN_OUT || p->pid == USB_TOKEN_SETUP) + dump_data(p->data, p->len); + + ret = USB_RET_NODEV; + for (i = 0; i < NB_PORTS && ret == USB_RET_NODEV; i++) { + UHCIPort *port = &s->ports[i]; + USBDevice *dev = port->port.dev; + + if (dev && (port->ctrl & UHCI_PORT_EN)) + ret = dev->info->handle_packet(dev, p); + } + + DPRINTF("uhci: packet exit. ret %d len %d\n", ret, p->len); + if (p->pid == USB_TOKEN_IN && ret > 0) + dump_data(p->data, ret); + + return ret; +} + +static void uhci_async_complete(USBPacket * packet, void *opaque); +static void uhci_process_frame(UHCIState *s); + +/* return -1 if fatal error (frame must be stopped) + 0 if TD successful + 1 if TD unsuccessful or inactive +*/ +static int uhci_complete_td(UHCIState *s, UHCI_TD *td, UHCIAsync *async, uint32_t *int_mask) +{ + int len = 0, max_len, err, ret; + uint8_t pid; + + max_len = ((td->token >> 21) + 1) & 0x7ff; + pid = td->token & 0xff; + + ret = async->packet.len; + + if (td->ctrl & TD_CTRL_IOS) + td->ctrl &= ~TD_CTRL_ACTIVE; + + if (ret < 0) + goto out; + + len = async->packet.len; + 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) { + if (len > max_len) { + ret = USB_RET_BABBLE; + goto out; + } + + if (len > 0) { + /* write the data back */ + cpu_physical_memory_write(td->buffer, async->buffer, len); + } + + if ((td->ctrl & TD_CTRL_SPD) && len < max_len) { + *int_mask |= 0x02; + /* short packet: do not update QH */ + DPRINTF("uhci: short packet. td 0x%x token 0x%x\n", async->td, async->token); + return 1; + } + } + + /* success */ + return 0; + +out: + switch(ret) { + case USB_RET_STALL: + td->ctrl |= TD_CTRL_STALL; + td->ctrl &= ~TD_CTRL_ACTIVE; + return 1; + + case USB_RET_BABBLE: + td->ctrl |= TD_CTRL_BABBLE | TD_CTRL_STALL; + td->ctrl &= ~TD_CTRL_ACTIVE; + /* frame interrupted */ + return -1; + + case USB_RET_NAK: + td->ctrl |= TD_CTRL_NAK; + if (pid == USB_TOKEN_SETUP) + break; + return 1; + + case USB_RET_NODEV: + default: + break; + } + + /* Retry the TD if error count is not zero */ + + td->ctrl |= TD_CTRL_TIMEOUT; + err = (td->ctrl >> TD_CTRL_ERROR_SHIFT) & 3; + if (err != 0) { + err--; + if (err == 0) { + td->ctrl &= ~TD_CTRL_ACTIVE; + s->status |= UHCI_STS_USBERR; + if (td->ctrl & TD_CTRL_IOC) + *int_mask |= 0x01; + uhci_update_irq(s); + } + } + td->ctrl = (td->ctrl & ~(3 << TD_CTRL_ERROR_SHIFT)) | + (err << TD_CTRL_ERROR_SHIFT); + return 1; +} + +static int uhci_handle_td(UHCIState *s, uint32_t addr, UHCI_TD *td, uint32_t *int_mask) +{ + UHCIAsync *async; + int len = 0, max_len; + uint8_t pid, isoc; + uint32_t token; + + /* Is active ? */ + if (!(td->ctrl & TD_CTRL_ACTIVE)) + return 1; + + /* token field is not unique for isochronous requests, + * so use the destination buffer + */ + if (td->ctrl & TD_CTRL_IOS) { + token = td->buffer; + isoc = 1; + } else { + token = td->token; + isoc = 0; + } + + async = uhci_async_find_td(s, addr, token); + if (async) { + /* Already submitted */ + async->valid = 32; + + if (!async->done) + return 1; + + uhci_async_unlink(s, async); + goto done; + } + + /* Allocate new packet */ + async = uhci_async_alloc(s); + if (!async) + return 1; + + /* valid needs to be large enough to handle 10 frame delay + * for initial isochronous requests + */ + async->valid = 32; + async->td = addr; + async->token = token; + async->isoc = isoc; + + max_len = ((td->token >> 21) + 1) & 0x7ff; + pid = td->token & 0xff; + + async->packet.pid = pid; + async->packet.devaddr = (td->token >> 8) & 0x7f; + async->packet.devep = (td->token >> 15) & 0xf; + async->packet.data = async->buffer; + async->packet.len = max_len; + async->packet.complete_cb = uhci_async_complete; + async->packet.complete_opaque = s; + + switch(pid) { + case USB_TOKEN_OUT: + case USB_TOKEN_SETUP: + cpu_physical_memory_read(td->buffer, async->buffer, max_len); + len = uhci_broadcast_packet(s, &async->packet); + if (len >= 0) + len = max_len; + break; + + case USB_TOKEN_IN: + len = uhci_broadcast_packet(s, &async->packet); + break; + + default: + /* invalid pid : frame interrupted */ + uhci_async_free(s, async); + s->status |= UHCI_STS_HCPERR; + uhci_update_irq(s); + return -1; + } + + if (len == USB_RET_ASYNC) { + uhci_async_link(s, async); + return 2; + } + + async->packet.len = len; + +done: + len = uhci_complete_td(s, td, async, int_mask); + uhci_async_free(s, async); + return len; +} + +static void uhci_async_complete(USBPacket *packet, void *opaque) +{ + UHCIState *s = opaque; + UHCIAsync *async = (UHCIAsync *) packet; + + DPRINTF("uhci: async complete. td 0x%x token 0x%x\n", async->td, async->token); + + if (async->isoc) { + UHCI_TD td; + uint32_t link = async->td; + uint32_t int_mask = 0, val; + + cpu_physical_memory_read(link & ~0xf, (uint8_t *) &td, sizeof(td)); + le32_to_cpus(&td.link); + le32_to_cpus(&td.ctrl); + le32_to_cpus(&td.token); + le32_to_cpus(&td.buffer); + + uhci_async_unlink(s, async); + uhci_complete_td(s, &td, async, &int_mask); + s->pending_int_mask |= int_mask; + + /* update the status bits of the TD */ + val = cpu_to_le32(td.ctrl); + cpu_physical_memory_write((link & ~0xf) + 4, + (const uint8_t *)&val, sizeof(val)); + uhci_async_free(s, async); + } else { + async->done = 1; + uhci_process_frame(s); + } +} + +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_process_frame(UHCIState *s) +{ + uint32_t frame_addr, link, old_td_ctrl, val, int_mask; + uint32_t curr_qh; + int cnt, ret; + UHCI_TD td; + UHCI_QH qh; + QhDb qhdb; + + frame_addr = s->fl_base_addr + ((s->frnum & 0x3ff) << 2); + + DPRINTF("uhci: processing frame %d addr 0x%x\n" , s->frnum, frame_addr); + + cpu_physical_memory_read(frame_addr, (uint8_t *)&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 (is_qh(link)) { + /* QH */ + + 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. + * In our case though it makes no sense to spin here. Sync transations + * are already done, and async completion handler will re-process + * the frame when something is ready. + */ + DPRINTF("uhci: detected loop. qh 0x%x\n", link); + break; + } + + cpu_physical_memory_read(link & ~0xf, (uint8_t *) &qh, sizeof(qh)); + le32_to_cpus(&qh.link); + le32_to_cpus(&qh.el_link); + + DPRINTF("uhci: QH 0x%x load. link 0x%x elink 0x%x\n", + link, qh.link, 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 */ + cpu_physical_memory_read(link & ~0xf, (uint8_t *) &td, sizeof(td)); + le32_to_cpus(&td.link); + le32_to_cpus(&td.ctrl); + le32_to_cpus(&td.token); + le32_to_cpus(&td.buffer); + + DPRINTF("uhci: TD 0x%x load. link 0x%x ctrl 0x%x token 0x%x qh 0x%x\n", + link, td.link, td.ctrl, td.token, curr_qh); + + old_td_ctrl = td.ctrl; + ret = uhci_handle_td(s, link, &td, &int_mask); + if (old_td_ctrl != td.ctrl) { + /* update the status bits of the TD */ + val = cpu_to_le32(td.ctrl); + cpu_physical_memory_write((link & ~0xf) + 4, + (const uint8_t *)&val, sizeof(val)); + } + + if (ret < 0) { + /* interrupted frame */ + break; + } + + if (ret == 2 || ret == 1) { + DPRINTF("uhci: TD 0x%x %s. link 0x%x ctrl 0x%x token 0x%x qh 0x%x\n", + link, ret == 2 ? "pend" : "skip", + td.link, td.ctrl, td.token, curr_qh); + + link = curr_qh ? qh.link : td.link; + continue; + } + + /* completed TD */ + + DPRINTF("uhci: TD 0x%x done. link 0x%x ctrl 0x%x token 0x%x qh 0x%x\n", + link, td.link, td.ctrl, td.token, curr_qh); + + link = td.link; + + if (curr_qh) { + /* update QH element link */ + qh.el_link = link; + val = cpu_to_le32(qh.el_link); + cpu_physical_memory_write((curr_qh & ~0xf) + 4, + (const uint8_t *)&val, sizeof(val)); + + if (!depth_first(link)) { + /* done with this QH */ + + DPRINTF("uhci: QH 0x%x done. link 0x%x elink 0x%x\n", + curr_qh, qh.link, qh.el_link); + + curr_qh = 0; + link = qh.link; + } + } + + /* go to the next entry */ + } + + s->pending_int_mask |= int_mask; +} + +static void uhci_frame_timer(void *opaque) +{ + UHCIState *s = opaque; + + /* prepare the timer for the next frame */ + s->expire_time += (get_ticks_per_sec() / FRAME_TIMER_FREQ); + + if (!(s->cmd & UHCI_CMD_RS)) { + /* Full stop */ + qemu_del_timer(s->frame_timer); + /* set hchalted bit in status - UHCI11D 2.1.2 */ + s->status |= UHCI_STS_HCHALTED; + + DPRINTF("uhci: halted\n"); + return; + } + + /* Complete the previous frame */ + 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; + + /* Start new frame */ + s->frnum = (s->frnum + 1) & 0x7ff; + + DPRINTF("uhci: new frame #%u\n" , s->frnum); + + uhci_async_validate_begin(s); + + uhci_process_frame(s); + + uhci_async_validate_end(s); + + qemu_mod_timer(s->frame_timer, s->expire_time); +} + +static void uhci_map(PCIDevice *pci_dev, int region_num, + pcibus_t addr, pcibus_t size, int type) +{ + UHCIState *s = (UHCIState *)pci_dev; + + register_ioport_write(addr, 32, 2, uhci_ioport_writew, s); + register_ioport_read(addr, 32, 2, uhci_ioport_readw, s); + register_ioport_write(addr, 32, 4, uhci_ioport_writel, s); + register_ioport_read(addr, 32, 4, uhci_ioport_readl, s); + register_ioport_write(addr, 32, 1, uhci_ioport_writeb, s); + register_ioport_read(addr, 32, 1, uhci_ioport_readb, s); +} + +static USBPortOps uhci_port_ops = { + .attach = uhci_attach, + .detach = uhci_detach, + .wakeup = uhci_wakeup, +}; + +static int usb_uhci_common_initfn(UHCIState *s) +{ + uint8_t *pci_conf = s->dev.config; + int i; + + pci_conf[PCI_REVISION_ID] = 0x01; // revision number + pci_conf[PCI_CLASS_PROG] = 0x00; + pci_config_set_class(pci_conf, PCI_CLASS_SERIAL_USB); + /* TODO: reset value should be 0. */ + pci_conf[PCI_INTERRUPT_PIN] = 4; // interrupt pin 3 + pci_conf[0x60] = 0x10; // release number + + usb_bus_new(&s->bus, &s->dev.qdev); + 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); + usb_port_location(&s->ports[i].port, NULL, i+1); + } + s->frame_timer = qemu_new_timer(vm_clock, uhci_frame_timer, s); + s->expire_time = qemu_get_clock(vm_clock) + + (get_ticks_per_sec() / FRAME_TIMER_FREQ); + s->num_ports_vmstate = NB_PORTS; + + qemu_register_reset(uhci_reset, s); + + /* Use region 4 for consistency with real hardware. BSD guests seem + to rely on this. */ + pci_register_bar(&s->dev, 4, 0x20, + PCI_BASE_ADDRESS_SPACE_IO, uhci_map); + + return 0; +} + +static int usb_uhci_piix3_initfn(PCIDevice *dev) +{ + UHCIState *s = DO_UPCAST(UHCIState, dev, dev); + uint8_t *pci_conf = s->dev.config; + + pci_config_set_vendor_id(pci_conf, PCI_VENDOR_ID_INTEL); + pci_config_set_device_id(pci_conf, PCI_DEVICE_ID_INTEL_82371SB_2); + return usb_uhci_common_initfn(s); +} + +static int usb_uhci_piix4_initfn(PCIDevice *dev) +{ + UHCIState *s = DO_UPCAST(UHCIState, dev, dev); + uint8_t *pci_conf = s->dev.config; + + pci_config_set_vendor_id(pci_conf, PCI_VENDOR_ID_INTEL); + pci_config_set_device_id(pci_conf, PCI_DEVICE_ID_INTEL_82371AB_2); + return usb_uhci_common_initfn(s); +} + +static int usb_uhci_vt82c686b_initfn(PCIDevice *dev) +{ + UHCIState *s = DO_UPCAST(UHCIState, dev, dev); + uint8_t *pci_conf = s->dev.config; + + pci_config_set_vendor_id(pci_conf, PCI_VENDOR_ID_VIA); + pci_config_set_device_id(pci_conf, PCI_DEVICE_ID_VIA_UHCI); + + /* 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); + + return usb_uhci_common_initfn(s); +} + +static PCIDeviceInfo uhci_info[] = { + { + .qdev.name = "piix3-usb-uhci", + .qdev.size = sizeof(UHCIState), + .qdev.vmsd = &vmstate_uhci, + .init = usb_uhci_piix3_initfn, + },{ + .qdev.name = "piix4-usb-uhci", + .qdev.size = sizeof(UHCIState), + .qdev.vmsd = &vmstate_uhci, + .init = usb_uhci_piix4_initfn, + },{ + .qdev.name = "vt82c686b-usb-uhci", + .qdev.size = sizeof(UHCIState), + .qdev.vmsd = &vmstate_uhci, + .init = usb_uhci_vt82c686b_initfn, + },{ + /* end of list */ + } +}; + +static void uhci_register(void) +{ + pci_qdev_register_many(uhci_info); +} +device_init(uhci_register); + +void usb_uhci_piix3_init(PCIBus *bus, int devfn) +{ + pci_create_simple(bus, devfn, "piix3-usb-uhci"); +} + +void usb_uhci_piix4_init(PCIBus *bus, int devfn) +{ + pci_create_simple(bus, devfn, "piix4-usb-uhci"); +} + +void usb_uhci_vt82c686b_init(PCIBus *bus, int devfn) +{ + pci_create_simple(bus, devfn, "vt82c686b-usb-uhci"); +} diff --git a/hw/usb-uhci.h b/hw/usb-uhci.h new file mode 100644 index 0000000..3e4d377 --- /dev/null +++ b/hw/usb-uhci.h @@ -0,0 +1,10 @@ +#ifndef QEMU_USB_UHCI_H +#define QEMU_USB_UHCI_H + +#include "qemu-common.h" + +void usb_uhci_piix3_init(PCIBus *bus, int devfn); +void usb_uhci_piix4_init(PCIBus *bus, int devfn); +void usb_uhci_vt82c686b_init(PCIBus *bus, int devfn); + +#endif diff --git a/hw/usb-wacom.c b/hw/usb-wacom.c new file mode 100644 index 0000000..16be7a2 --- /dev/null +++ b/hw/usb-wacom.c @@ -0,0 +1,370 @@ +/* + * Wacom PenPartner USB tablet emulation. + * + * Copyright (c) 2006 Openedhand Ltd. + * Author: Andrzej Zaborowski + * + * 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.h" +#include "console.h" +#include "usb.h" +#include "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; + 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; + +enum { + STR_MANUFACTURER = 1, + STR_PRODUCT, + STR_SERIALNUMBER, +}; + +static const USBDescStrings desc_strings = { + [STR_MANUFACTURER] = "QEMU " QEMU_VERSION, + [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 = 0x80, + .bMaxPower = 40, + .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; +} + +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; +} + +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 int usb_wacom_handle_control(USBDevice *dev, int request, int value, + int index, int length, uint8_t *data) +{ + USBWacomState *s = (USBWacomState *) dev; + int ret; + + ret = usb_desc_handle_control(dev, request, value, index, length, data); + if (ret >= 0) { + return ret; + } + + ret = 0; + switch (request) { + case DeviceRequest | USB_REQ_GET_INTERFACE: + data[0] = 0; + ret = 1; + break; + case DeviceOutRequest | USB_REQ_SET_INTERFACE: + ret = 0; + break; + case WACOM_SET_REPORT: + if (s->mouse_grabbed) { + qemu_remove_mouse_event_handler(s->eh_entry); + s->mouse_grabbed = 0; + } + s->mode = data[0]; + ret = 0; + break; + case WACOM_GET_REPORT: + data[0] = 0; + data[1] = s->mode; + ret = 2; + break; + /* USB HID requests */ + case HID_GET_REPORT: + if (s->mode == WACOM_MODE_HID) + ret = usb_mouse_poll(s, data, length); + else if (s->mode == WACOM_MODE_WACOM) + ret = usb_wacom_poll(s, data, length); + break; + case HID_GET_IDLE: + ret = 1; + data[0] = s->idle; + break; + case HID_SET_IDLE: + s->idle = (uint8_t) (value >> 8); + ret = 0; + break; + default: + ret = USB_RET_STALL; + break; + } + return ret; +} + +static int usb_wacom_handle_data(USBDevice *dev, USBPacket *p) +{ + USBWacomState *s = (USBWacomState *) dev; + int ret = 0; + + switch (p->pid) { + case USB_TOKEN_IN: + if (p->devep == 1) { + if (!(s->changed || s->idle)) + return USB_RET_NAK; + s->changed = 0; + if (s->mode == WACOM_MODE_HID) + ret = usb_mouse_poll(s, p->data, p->len); + else if (s->mode == WACOM_MODE_WACOM) + ret = usb_wacom_poll(s, p->data, p->len); + break; + } + /* Fall through. */ + case USB_TOKEN_OUT: + default: + ret = USB_RET_STALL; + break; + } + return ret; +} + +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 int usb_wacom_initfn(USBDevice *dev) +{ + USBWacomState *s = DO_UPCAST(USBWacomState, dev, dev); + usb_desc_init(dev); + s->changed = 1; + return 0; +} + +static struct USBDeviceInfo wacom_info = { + .product_desc = "QEMU PenPartner Tablet", + .qdev.name = "usb-wacom-tablet", + .qdev.desc = "QEMU PenPartner Tablet", + .usbdevice_name = "wacom-tablet", + .usb_desc = &desc_wacom, + .qdev.size = sizeof(USBWacomState), + .init = usb_wacom_initfn, + .handle_packet = usb_generic_handle_packet, + .handle_reset = usb_wacom_handle_reset, + .handle_control = usb_wacom_handle_control, + .handle_data = usb_wacom_handle_data, + .handle_destroy = usb_wacom_handle_destroy, +}; + +static void usb_wacom_register_devices(void) +{ + usb_qdev_register(&wacom_info); +} +device_init(usb_wacom_register_devices) diff --git a/hw/usb.c b/hw/usb.c new file mode 100644 index 0000000..82a6217 --- /dev/null +++ b/hw/usb.c @@ -0,0 +1,261 @@ +/* + * 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 "usb.h" + +void usb_attach(USBPort *port, USBDevice *dev) +{ + if (dev != NULL) { + /* attach */ + if (port->dev) { + usb_attach(port, NULL); + } + dev->port = port; + port->dev = dev; + port->ops->attach(port); + usb_send_msg(dev, USB_MSG_ATTACH); + } else { + /* detach */ + dev = port->dev; + port->ops->detach(port); + if (dev) { + usb_send_msg(dev, USB_MSG_DETACH); + dev->port = NULL; + port->dev = NULL; + } + } +} + +void usb_wakeup(USBDevice *dev) +{ + if (dev->remote_wakeup && dev->port && dev->port->ops->wakeup) { + dev->port->ops->wakeup(dev); + } +} + +/**********************/ + +/* 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_DATA 1 +#define SETUP_STATE_ACK 2 + +static int do_token_setup(USBDevice *s, USBPacket *p) +{ + int request, value, index; + int ret = 0; + + if (p->len != 8) + return USB_RET_STALL; + + memcpy(s->setup_buf, p->data, 8); + 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) { + ret = s->info->handle_control(s, request, value, index, + s->setup_len, s->data_buf); + if (ret < 0) + return ret; + + if (ret < s->setup_len) + s->setup_len = ret; + s->setup_state = SETUP_STATE_DATA; + } else { + if (s->setup_len == 0) + s->setup_state = SETUP_STATE_ACK; + else + s->setup_state = SETUP_STATE_DATA; + } + + return ret; +} + +static int do_token_in(USBDevice *s, USBPacket *p) +{ + int request, value, index; + int ret = 0; + + if (p->devep != 0) + return s->info->handle_data(s, p); + + 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)) { + s->setup_state = SETUP_STATE_IDLE; + ret = s->info->handle_control(s, request, value, index, + s->setup_len, s->data_buf); + if (ret > 0) + return 0; + return ret; + } + + /* return 0 byte */ + return 0; + + case SETUP_STATE_DATA: + if (s->setup_buf[0] & USB_DIR_IN) { + int len = s->setup_len - s->setup_index; + if (len > p->len) + len = p->len; + memcpy(p->data, 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 len; + } + + s->setup_state = SETUP_STATE_IDLE; + return USB_RET_STALL; + + default: + return USB_RET_STALL; + } +} + +static int do_token_out(USBDevice *s, USBPacket *p) +{ + if (p->devep != 0) + return s->info->handle_data(s, p); + + 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 */ + } + return 0; + + case SETUP_STATE_DATA: + if (!(s->setup_buf[0] & USB_DIR_IN)) { + int len = s->setup_len - s->setup_index; + if (len > p->len) + len = p->len; + memcpy(s->data_buf + s->setup_index, p->data, len); + s->setup_index += len; + if (s->setup_index >= s->setup_len) + s->setup_state = SETUP_STATE_ACK; + return len; + } + + s->setup_state = SETUP_STATE_IDLE; + return USB_RET_STALL; + + default: + return USB_RET_STALL; + } +} + +/* + * Generic packet handler. + * Called by the HC (host controller). + * + * Returns length of the transaction or one of the USB_RET_XXX codes. + */ +int usb_generic_handle_packet(USBDevice *s, USBPacket *p) +{ + switch(p->pid) { + case USB_MSG_ATTACH: + s->state = USB_STATE_ATTACHED; + if (s->info->handle_attach) { + s->info->handle_attach(s); + } + return 0; + + case USB_MSG_DETACH: + s->state = USB_STATE_NOTATTACHED; + return 0; + + case USB_MSG_RESET: + s->remote_wakeup = 0; + s->addr = 0; + s->state = USB_STATE_DEFAULT; + if (s->info->handle_reset) { + s->info->handle_reset(s); + } + return 0; + } + + /* Rest of the PIDs must match our address */ + if (s->state < USB_STATE_DEFAULT || p->devaddr != s->addr) + return USB_RET_NODEV; + + switch (p->pid) { + case USB_TOKEN_SETUP: + return do_token_setup(s, p); + + case USB_TOKEN_IN: + return do_token_in(s, p); + + case USB_TOKEN_OUT: + return do_token_out(s, p); + + default: + return USB_RET_STALL; + } +} + +/* XXX: fix overflow */ +int set_usb_string(uint8_t *buf, const char *str) +{ + int len, i; + uint8_t *q; + + q = buf; + len = strlen(str); + *q++ = 2 * len + 2; + *q++ = 3; + for(i = 0; i < len; i++) { + *q++ = str[i]; + *q++ = 0; + } + return q - buf; +} + +/* Send an internal message to a USB device. */ +void usb_send_msg(USBDevice *dev, int msg) +{ + USBPacket p; + memset(&p, 0, sizeof(p)); + p.pid = msg; + dev->info->handle_packet(dev, &p); + + /* This _must_ be synchronous */ +} diff --git a/hw/usb.h b/hw/usb.h new file mode 100644 index 0000000..d3d755d --- /dev/null +++ b/hw/usb.h @@ -0,0 +1,367 @@ +/* + * QEMU USB API + * + * 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 "block.h" +#include "qdev.h" +#include "qemu-queue.h" + +#define USB_TOKEN_SETUP 0x2d +#define USB_TOKEN_IN 0x69 /* device -> host */ +#define USB_TOKEN_OUT 0xe1 /* host -> device */ + +/* specific usb messages, also sent in the 'pid' parameter */ +#define USB_MSG_ATTACH 0x100 +#define USB_MSG_DETACH 0x101 +#define USB_MSG_RESET 0x102 + +#define USB_RET_NODEV (-1) +#define USB_RET_NAK (-2) +#define USB_RET_STALL (-3) +#define USB_RET_BABBLE (-4) +#define USB_RET_ASYNC (-5) + +#define USB_SPEED_LOW 0 +#define USB_SPEED_FULL 1 +#define USB_SPEED_HIGH 2 +#define USB_SPEED_SUPER 3 + +#define USB_SPEED_MASK_LOW (1 << USB_SPEED_LOW) +#define USB_SPEED_MASK_FULL (1 << USB_SPEED_FULL) +#define USB_SPEED_MASK_HIGH (1 << USB_SPEED_HIGH) +#define USB_SPEED_MASK_SUPER (1 << USB_SPEED_SUPER) + +#define USB_STATE_NOTATTACHED 0 +#define USB_STATE_ATTACHED 1 +//#define USB_STATE_POWERED 2 +#define USB_STATE_DEFAULT 3 +//#define USB_STATE_ADDRESS 4 +//#define USB_STATE_CONFIGURED 5 +#define USB_STATE_SUSPENDED 6 + +#define USB_CLASS_AUDIO 1 +#define USB_CLASS_COMM 2 +#define USB_CLASS_HID 3 +#define USB_CLASS_PHYSICAL 5 +#define USB_CLASS_STILL_IMAGE 6 +#define USB_CLASS_PRINTER 7 +#define USB_CLASS_MASS_STORAGE 8 +#define USB_CLASS_HUB 9 +#define USB_CLASS_CDC_DATA 0x0a +#define USB_CLASS_CSCID 0x0b +#define USB_CLASS_CONTENT_SEC 0x0d +#define USB_CLASS_APP_SPEC 0xfe +#define USB_CLASS_VENDOR_SPEC 0xff + +#define USB_DIR_OUT 0 +#define USB_DIR_IN 0x80 + +#define USB_TYPE_MASK (0x03 << 5) +#define USB_TYPE_STANDARD (0x00 << 5) +#define USB_TYPE_CLASS (0x01 << 5) +#define USB_TYPE_VENDOR (0x02 << 5) +#define USB_TYPE_RESERVED (0x03 << 5) + +#define USB_RECIP_MASK 0x1f +#define USB_RECIP_DEVICE 0x00 +#define USB_RECIP_INTERFACE 0x01 +#define USB_RECIP_ENDPOINT 0x02 +#define USB_RECIP_OTHER 0x03 + +#define DeviceRequest ((USB_DIR_IN|USB_TYPE_STANDARD|USB_RECIP_DEVICE)<<8) +#define DeviceOutRequest ((USB_DIR_OUT|USB_TYPE_STANDARD|USB_RECIP_DEVICE)<<8) +#define InterfaceRequest \ + ((USB_DIR_IN|USB_TYPE_STANDARD|USB_RECIP_INTERFACE)<<8) +#define InterfaceOutRequest \ + ((USB_DIR_OUT|USB_TYPE_STANDARD|USB_RECIP_INTERFACE)<<8) +#define EndpointRequest ((USB_DIR_IN|USB_TYPE_STANDARD|USB_RECIP_ENDPOINT)<<8) +#define EndpointOutRequest \ + ((USB_DIR_OUT|USB_TYPE_STANDARD|USB_RECIP_ENDPOINT)<<8) +#define ClassInterfaceRequest \ + ((USB_DIR_IN|USB_TYPE_CLASS|USB_RECIP_INTERFACE)<<8) +#define ClassInterfaceOutRequest \ + ((USB_DIR_OUT|USB_TYPE_CLASS|USB_RECIP_INTERFACE)<<8) + +#define USB_REQ_GET_STATUS 0x00 +#define USB_REQ_CLEAR_FEATURE 0x01 +#define USB_REQ_SET_FEATURE 0x03 +#define USB_REQ_SET_ADDRESS 0x05 +#define USB_REQ_GET_DESCRIPTOR 0x06 +#define USB_REQ_SET_DESCRIPTOR 0x07 +#define USB_REQ_GET_CONFIGURATION 0x08 +#define USB_REQ_SET_CONFIGURATION 0x09 +#define USB_REQ_GET_INTERFACE 0x0A +#define USB_REQ_SET_INTERFACE 0x0B +#define USB_REQ_SYNCH_FRAME 0x0C + +#define USB_DEVICE_SELF_POWERED 0 +#define USB_DEVICE_REMOTE_WAKEUP 1 + +#define USB_DT_DEVICE 0x01 +#define USB_DT_CONFIG 0x02 +#define USB_DT_STRING 0x03 +#define USB_DT_INTERFACE 0x04 +#define USB_DT_ENDPOINT 0x05 +#define USB_DT_DEVICE_QUALIFIER 0x06 +#define USB_DT_OTHER_SPEED_CONFIG 0x07 + +#define USB_ENDPOINT_XFER_CONTROL 0 +#define USB_ENDPOINT_XFER_ISOC 1 +#define USB_ENDPOINT_XFER_BULK 2 +#define USB_ENDPOINT_XFER_INT 3 + +typedef struct USBBus USBBus; +typedef struct USBPort USBPort; +typedef struct USBDevice USBDevice; +typedef struct USBDeviceInfo USBDeviceInfo; +typedef struct USBPacket USBPacket; + +typedef struct USBDesc USBDesc; +typedef struct USBDescID USBDescID; +typedef struct USBDescDevice USBDescDevice; +typedef struct USBDescConfig USBDescConfig; +typedef struct USBDescIface USBDescIface; +typedef struct USBDescEndpoint USBDescEndpoint; +typedef struct USBDescOther USBDescOther; +typedef struct USBDescString USBDescString; + +struct USBDescString { + uint8_t index; + char *str; + QLIST_ENTRY(USBDescString) next; +}; + +/* definition of a USB device */ +struct USBDevice { + DeviceState qdev; + USBDeviceInfo *info; + USBPort *port; + char *port_path; + void *opaque; + + int speed; + uint8_t addr; + char product_desc[32]; + int auto_attach; + int attached; + + int32_t state; + uint8_t setup_buf[8]; + uint8_t data_buf[1024]; + int32_t remote_wakeup; + int32_t setup_state; + int32_t setup_len; + int32_t setup_index; + + QLIST_HEAD(, USBDescString) strings; + const USBDescDevice *device; + const USBDescConfig *config; +}; + +struct USBDeviceInfo { + DeviceInfo qdev; + int (*init)(USBDevice *dev); + + /* + * Process USB packet. + * Called by the HC (Host Controller). + * + * Returns length of the transaction + * or one of the USB_RET_XXX codes. + */ + int (*handle_packet)(USBDevice *dev, USBPacket *p); + + /* + * Called when device is destroyed. + */ + void (*handle_destroy)(USBDevice *dev); + + /* + * Attach the device + */ + void (*handle_attach)(USBDevice *dev); + + /* + * Reset the device + */ + void (*handle_reset)(USBDevice *dev); + + /* + * Process control request. + * Called from handle_packet(). + * + * Returns length or one of the USB_RET_ codes. + */ + int (*handle_control)(USBDevice *dev, int request, int value, + int index, int length, uint8_t *data); + + /* + * Process data transfers (both BULK and ISOC). + * Called from handle_packet(). + * + * Returns length or one of the USB_RET_ codes. + */ + int (*handle_data)(USBDevice *dev, USBPacket *p); + + const char *product_desc; + const USBDesc *usb_desc; + + /* handle legacy -usbdevice command line options */ + const char *usbdevice_name; + USBDevice *(*usbdevice_init)(const char *params); +}; + +typedef struct USBPortOps { + void (*attach)(USBPort *port); + void (*detach)(USBPort *port); + void (*wakeup)(USBDevice *dev); +} USBPortOps; + +/* USB port on which a device can be connected */ +struct USBPort { + USBDevice *dev; + int speedmask; + char path[16]; + USBPortOps *ops; + void *opaque; + int index; /* internal port index, may be used with the opaque */ + QTAILQ_ENTRY(USBPort) next; +}; + +typedef void USBCallback(USBPacket * packet, void *opaque); + +/* Structure used to hold information about an active USB packet. */ +struct USBPacket { + /* Data fields for use by the driver. */ + int pid; + uint8_t devaddr; + uint8_t devep; + uint8_t *data; + int len; + /* Internal use by the USB layer. */ + USBCallback *complete_cb; + void *complete_opaque; + USBCallback *cancel_cb; + void *cancel_opaque; +}; + +/* Defer completion of a USB packet. The hadle_packet routine should then + return USB_RET_ASYNC. Packets that complete immediately (before + handle_packet returns) should not call this method. */ +static inline void usb_defer_packet(USBPacket *p, USBCallback *cancel, + void * opaque) +{ + p->cancel_cb = cancel; + p->cancel_opaque = opaque; +} + +/* Notify the controller that an async packet is complete. This should only + be called for packets previously deferred with usb_defer_packet, and + should never be called from within handle_packet. */ +static inline void usb_packet_complete(USBPacket *p) +{ + p->complete_cb(p, p->complete_opaque); +} + +/* Cancel an active packet. The packed must have been deferred with + usb_defer_packet, and not yet completed. */ +static inline void usb_cancel_packet(USBPacket * p) +{ + p->cancel_cb(p, p->cancel_opaque); +} + +void usb_attach(USBPort *port, USBDevice *dev); +void usb_wakeup(USBDevice *dev); +int usb_generic_handle_packet(USBDevice *s, USBPacket *p); +int set_usb_string(uint8_t *buf, const char *str); +void usb_send_msg(USBDevice *dev, int msg); + +/* usb-linux.c */ +USBDevice *usb_host_device_open(const char *devname); +int usb_host_device_close(const char *devname); +void usb_host_info(Monitor *mon); + +/* usb-hid.c */ +void usb_hid_datain_cb(USBDevice *dev, void *opaque, void (*datain)(void *)); + +/* usb-bt.c */ +USBDevice *usb_bt_init(HCIInfo *hci); + +/* usb ports of the VM */ + +#define VM_USB_HUB_SIZE 8 + +/* usb-musb.c */ +enum musb_irq_source_e { + musb_irq_suspend = 0, + musb_irq_resume, + musb_irq_rst_babble, + musb_irq_sof, + musb_irq_connect, + musb_irq_disconnect, + musb_irq_vbus_request, + musb_irq_vbus_error, + musb_irq_rx, + musb_irq_tx, + musb_set_vbus, + musb_set_session, + __musb_irq_max, +}; + +typedef struct MUSBState MUSBState; +MUSBState *musb_init(qemu_irq *irqs); +uint32_t musb_core_intr_get(MUSBState *s); +void musb_core_intr_clear(MUSBState *s, uint32_t mask); +void musb_set_size(MUSBState *s, int epnum, int size, int is_tx); + +/* usb-bus.c */ + +struct USBBus { + BusState qbus; + int busnr; + int nfree; + int nused; + QTAILQ_HEAD(, USBPort) free; + QTAILQ_HEAD(, USBPort) used; + QTAILQ_ENTRY(USBBus) next; +}; + +void usb_bus_new(USBBus *bus, DeviceState *host); +USBBus *usb_bus_find(int busnr); +void usb_qdev_register(USBDeviceInfo *info); +void usb_qdev_register_many(USBDeviceInfo *info); +USBDevice *usb_create(USBBus *bus, const char *name); +USBDevice *usb_create_simple(USBBus *bus, const char *name); +USBDevice *usbdevice_create(const char *cmdline); +void usb_register_port(USBBus *bus, USBPort *port, void *opaque, int index, + USBPortOps *ops, int speedmask); +void usb_port_location(USBPort *downstream, USBPort *upstream, int portnr); +void usb_unregister_port(USBBus *bus, USBPort *port); +int usb_device_attach(USBDevice *dev); +int usb_device_detach(USBDevice *dev); +int usb_device_delete_addr(int busnr, int addr); + +static inline USBBus *usb_bus_from_device(USBDevice *d) +{ + return DO_UPCAST(USBBus, qbus, d->qdev.parent_bus); +} diff --git a/hw/versatile_pci.c b/hw/versatile_pci.c new file mode 100644 index 0000000..2fed8a0 --- /dev/null +++ b/hw/versatile_pci.c @@ -0,0 +1,160 @@ +/* + * ARM Versatile/PB PCI host controller + * + * Copyright (c) 2006-2009 CodeSourcery. + * Written by Paul Brook + * + * This code is licenced under the LGPL. + */ + +#include "sysbus.h" +#include "pci.h" +#include "pci_host.h" + +typedef struct { + SysBusDevice busdev; + qemu_irq irq[4]; + int realview; + int mem_config; +} PCIVPBState; + +static inline uint32_t vpb_pci_config_addr(target_phys_addr_t addr) +{ + return addr & 0xffffff; +} + +static void pci_vpb_config_writeb (void *opaque, target_phys_addr_t addr, + uint32_t val) +{ + pci_data_write(opaque, vpb_pci_config_addr (addr), val, 1); +} + +static void pci_vpb_config_writew (void *opaque, target_phys_addr_t addr, + uint32_t val) +{ + pci_data_write(opaque, vpb_pci_config_addr (addr), val, 2); +} + +static void pci_vpb_config_writel (void *opaque, target_phys_addr_t addr, + uint32_t val) +{ + pci_data_write(opaque, vpb_pci_config_addr (addr), val, 4); +} + +static uint32_t pci_vpb_config_readb (void *opaque, target_phys_addr_t addr) +{ + uint32_t val; + val = pci_data_read(opaque, vpb_pci_config_addr (addr), 1); + return val; +} + +static uint32_t pci_vpb_config_readw (void *opaque, target_phys_addr_t addr) +{ + uint32_t val; + val = pci_data_read(opaque, vpb_pci_config_addr (addr), 2); + return val; +} + +static uint32_t pci_vpb_config_readl (void *opaque, target_phys_addr_t addr) +{ + uint32_t val; + val = pci_data_read(opaque, vpb_pci_config_addr (addr), 4); + return val; +} + +static CPUWriteMemoryFunc * const pci_vpb_config_write[] = { + &pci_vpb_config_writeb, + &pci_vpb_config_writew, + &pci_vpb_config_writel, +}; + +static CPUReadMemoryFunc * const pci_vpb_config_read[] = { + &pci_vpb_config_readb, + &pci_vpb_config_readw, + &pci_vpb_config_readl, +}; + +static int pci_vpb_map_irq(PCIDevice *d, int irq_num) +{ + return irq_num; +} + +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_map(SysBusDevice *dev, target_phys_addr_t base) +{ + PCIVPBState *s = (PCIVPBState *)dev; + /* Selfconfig area. */ + cpu_register_physical_memory(base + 0x01000000, 0x1000000, s->mem_config); + /* Normal config area. */ + cpu_register_physical_memory(base + 0x02000000, 0x1000000, s->mem_config); + + if (s->realview) { + /* IO memory area. */ + isa_mmio_init(base + 0x03000000, 0x00100000); + } +} + +static int pci_vpb_init(SysBusDevice *dev) +{ + PCIVPBState *s = FROM_SYSBUS(PCIVPBState, dev); + PCIBus *bus; + int i; + + for (i = 0; i < 4; i++) { + sysbus_init_irq(dev, &s->irq[i]); + } + bus = pci_register_bus(&dev->qdev, "pci", + pci_vpb_set_irq, pci_vpb_map_irq, s->irq, + PCI_DEVFN(11, 0), 4); + + /* ??? Register memory space. */ + + s->mem_config = cpu_register_io_memory(pci_vpb_config_read, + pci_vpb_config_write, bus, + DEVICE_LITTLE_ENDIAN); + sysbus_init_mmio_cb(dev, 0x04000000, pci_vpb_map); + + pci_create_simple(bus, -1, "versatile_pci_host"); + return 0; +} + +static int pci_realview_init(SysBusDevice *dev) +{ + PCIVPBState *s = FROM_SYSBUS(PCIVPBState, dev); + s->realview = 1; + return pci_vpb_init(dev); +} + +static int versatile_pci_host_init(PCIDevice *d) +{ + pci_config_set_vendor_id(d->config, PCI_VENDOR_ID_XILINX); + /* Both boards have the same device ID. Oh well. */ + pci_config_set_device_id(d->config, PCI_DEVICE_ID_XILINX_XC2VP30); + pci_set_word(d->config + PCI_STATUS, + PCI_STATUS_66MHZ | PCI_STATUS_DEVSEL_MEDIUM); + pci_config_set_class(d->config, PCI_CLASS_PROCESSOR_CO); + pci_set_byte(d->config + PCI_LATENCY_TIMER, 0x10); + return 0; +} + +static PCIDeviceInfo versatile_pci_host_info = { + .qdev.name = "versatile_pci_host", + .qdev.size = sizeof(PCIDevice), + .init = versatile_pci_host_init, +}; + +static void versatile_pci_register_devices(void) +{ + sysbus_register_dev("versatile_pci", sizeof(PCIVPBState), pci_vpb_init); + sysbus_register_dev("realview_pci", sizeof(PCIVPBState), + pci_realview_init); + pci_qdev_register(&versatile_pci_host_info); +} + +device_init(versatile_pci_register_devices) diff --git a/hw/versatilepb.c b/hw/versatilepb.c new file mode 100644 index 0000000..9f1bfcf --- /dev/null +++ b/hw/versatilepb.c @@ -0,0 +1,362 @@ +/* + * ARM Versatile Platform/Application Baseboard System emulation. + * + * Copyright (c) 2005-2007 CodeSourcery. + * Written by Paul Brook + * + * This code is licenced under the GPL. + */ + +#include "sysbus.h" +#include "arm-misc.h" +#include "primecell.h" +#include "devices.h" +#include "net.h" +#include "sysemu.h" +#include "pci.h" +#include "usb-ohci.h" +#include "boards.h" +#include "blockdev.h" + +/* Primary interrupt controller. */ + +typedef struct vpb_sic_state +{ + SysBusDevice busdev; + 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 uint32_t vpb_sic_read(void *opaque, target_phys_addr_t offset) +{ + 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, target_phys_addr_t offset, + uint32_t value) +{ + 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 CPUReadMemoryFunc * const vpb_sic_readfn[] = { + vpb_sic_read, + vpb_sic_read, + vpb_sic_read +}; + +static CPUWriteMemoryFunc * const vpb_sic_writefn[] = { + vpb_sic_write, + vpb_sic_write, + vpb_sic_write +}; + +static int vpb_sic_init(SysBusDevice *dev) +{ + vpb_sic_state *s = FROM_SYSBUS(vpb_sic_state, dev); + int iomemtype; + int i; + + qdev_init_gpio_in(&dev->qdev, vpb_sic_set_irq, 32); + for (i = 0; i < 32; i++) { + sysbus_init_irq(dev, &s->parent[i]); + } + s->irq = 31; + iomemtype = cpu_register_io_memory(vpb_sic_readfn, + vpb_sic_writefn, s, + DEVICE_NATIVE_ENDIAN); + sysbus_init_mmio(dev, 0x1000, iomemtype); + return 0; +} + +/* Board init. */ + +/* The AB and PB boards both use the same core, just with different + peripherans 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(ram_addr_t ram_size, + const char *boot_device, + const char *kernel_filename, const char *kernel_cmdline, + const char *initrd_filename, const char *cpu_model, + int board_id) +{ + CPUState *env; + ram_addr_t ram_offset; + qemu_irq *cpu_pic; + qemu_irq pic[32]; + qemu_irq sic[32]; + DeviceState *dev; + PCIBus *pci_bus; + NICInfo *nd; + int n; + int done_smc = 0; + + if (!cpu_model) + cpu_model = "arm926"; + env = cpu_init(cpu_model); + if (!env) { + fprintf(stderr, "Unable to find CPU definition\n"); + exit(1); + } + ram_offset = qemu_ram_alloc(NULL, "versatile.ram", ram_size); + /* ??? RAM should repeat to fill physical memory space. */ + /* SDRAM at address zero. */ + cpu_register_physical_memory(0, ram_size, ram_offset | IO_MEM_RAM); + + arm_sysctl_init(0x10000000, 0x41007004, 0x02000000); + cpu_pic = arm_pic_init_cpu(env); + dev = sysbus_create_varargs("pl190", 0x10140000, + cpu_pic[0], cpu_pic[1], NULL); + for (n = 0; n < 32; n++) { + pic[n] = qdev_get_gpio_in(dev, n); + } + dev = sysbus_create_simple("versatilepb_sic", 0x10003000, NULL); + for (n = 0; n < 32; n++) { + sysbus_connect_irq(sysbus_from_qdev(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 = sysbus_create_varargs("versatile_pci", 0x40000000, + sic[27], sic[28], sic[29], sic[30], NULL); + pci_bus = (PCIBus *)qdev_get_child_bus(dev, "pci"); + + /* The Versatile PCI bridge does not provide access to PCI IO space, + so many of the qemu PCI devices are not useable. */ + for(n = 0; n < nb_nics; n++) { + nd = &nd_table[n]; + + if ((!nd->model && !done_smc) || strcmp(nd->model, "smc91c111") == 0) { + smc91c111_init(nd, 0x10010000, sic[25]); + done_smc = 1; + } else { + pci_nic_init_nofail(nd, "rtl8139", NULL); + } + } + if (usb_enabled) { + usb_ohci_init_pci(pci_bus, -1); + } + 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]); + + /* The versatile/PB actually has a modified Color LCD controller + that includes hardware cursor support from the PL111. */ + sysbus_create_simple("pl110_versatile", 0x10120000, pic[16]); + + 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]); + + /* 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. */ + + versatile_binfo.ram_size = ram_size; + versatile_binfo.kernel_filename = kernel_filename; + versatile_binfo.kernel_cmdline = kernel_cmdline; + versatile_binfo.initrd_filename = initrd_filename; + versatile_binfo.board_id = board_id; + arm_load_kernel(env, &versatile_binfo); +} + +static void vpb_init(ram_addr_t ram_size, + const char *boot_device, + const char *kernel_filename, const char *kernel_cmdline, + const char *initrd_filename, const char *cpu_model) +{ + versatile_init(ram_size, + boot_device, + kernel_filename, kernel_cmdline, + initrd_filename, cpu_model, 0x183); +} + +static void vab_init(ram_addr_t ram_size, + const char *boot_device, + const char *kernel_filename, const char *kernel_cmdline, + const char *initrd_filename, const char *cpu_model) +{ + versatile_init(ram_size, + boot_device, + kernel_filename, kernel_cmdline, + initrd_filename, cpu_model, 0x25e); +} + +static QEMUMachine versatilepb_machine = { + .name = "versatilepb", + .desc = "ARM Versatile/PB (ARM926EJ-S)", + .init = vpb_init, + .use_scsi = 1, +}; + +static QEMUMachine versatileab_machine = { + .name = "versatileab", + .desc = "ARM Versatile/AB (ARM926EJ-S)", + .init = vab_init, + .use_scsi = 1, +}; + +static void versatile_machine_init(void) +{ + qemu_register_machine(&versatilepb_machine); + qemu_register_machine(&versatileab_machine); +} + +machine_init(versatile_machine_init); + +static SysBusDeviceInfo vpb_sic_info = { + .init = vpb_sic_init, + .qdev.name = "versatilepb_sic", + .qdev.size = sizeof(vpb_sic_state), + .qdev.vmsd = &vmstate_vpb_sic, + .qdev.no_user = 1, +}; + +static void versatilepb_register_devices(void) +{ + sysbus_register_withprop(&vpb_sic_info); +} + +device_init(versatilepb_register_devices) diff --git a/hw/vga-isa-mm.c b/hw/vga-isa-mm.c new file mode 100644 index 0000000..4954bb1 --- /dev/null +++ b/hw/vga-isa-mm.c @@ -0,0 +1,128 @@ +/* + * 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.h" +#include "console.h" +#include "pc.h" +#include "vga_int.h" +#include "pixel_ops.h" +#include "qemu-timer.h" + +typedef struct ISAVGAMMState { + VGACommonState vga; + int it_shift; +} ISAVGAMMState; + +/* Memory mapped interface */ +static uint32_t vga_mm_readb (void *opaque, target_phys_addr_t addr) +{ + ISAVGAMMState *s = opaque; + + return vga_ioport_read(&s->vga, addr >> s->it_shift) & 0xff; +} + +static void vga_mm_writeb (void *opaque, + target_phys_addr_t 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, target_phys_addr_t addr) +{ + ISAVGAMMState *s = opaque; + + return vga_ioport_read(&s->vga, addr >> s->it_shift) & 0xffff; +} + +static void vga_mm_writew (void *opaque, + target_phys_addr_t 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, target_phys_addr_t addr) +{ + ISAVGAMMState *s = opaque; + + return vga_ioport_read(&s->vga, addr >> s->it_shift); +} + +static void vga_mm_writel (void *opaque, + target_phys_addr_t addr, uint32_t value) +{ + ISAVGAMMState *s = opaque; + + vga_ioport_write(&s->vga, addr >> s->it_shift, value); +} + +static CPUReadMemoryFunc * const vga_mm_read_ctrl[] = { + &vga_mm_readb, + &vga_mm_readw, + &vga_mm_readl, +}; + +static CPUWriteMemoryFunc * const vga_mm_write_ctrl[] = { + &vga_mm_writeb, + &vga_mm_writew, + &vga_mm_writel, +}; + +static void vga_mm_init(ISAVGAMMState *s, target_phys_addr_t vram_base, + target_phys_addr_t ctrl_base, int it_shift) +{ + int s_ioport_ctrl, vga_io_memory; + + s->it_shift = it_shift; + s_ioport_ctrl = cpu_register_io_memory(vga_mm_read_ctrl, vga_mm_write_ctrl, s, + DEVICE_NATIVE_ENDIAN); + vga_io_memory = cpu_register_io_memory(vga_mem_read, vga_mem_write, s, + DEVICE_NATIVE_ENDIAN); + + vmstate_register(NULL, 0, &vmstate_vga_common, s); + + cpu_register_physical_memory(ctrl_base, 0x100000, s_ioport_ctrl); + s->vga.bank_offset = 0; + cpu_register_physical_memory(vram_base + 0x000a0000, 0x20000, vga_io_memory); + qemu_register_coalesced_mmio(vram_base + 0x000a0000, 0x20000); +} + +int isa_vga_mm_init(target_phys_addr_t vram_base, + target_phys_addr_t ctrl_base, int it_shift) +{ + ISAVGAMMState *s; + + s = qemu_mallocz(sizeof(*s)); + + vga_common_init(&s->vga, VGA_RAM_SIZE); + vga_mm_init(s, vram_base, ctrl_base, it_shift); + + s->vga.ds = graphic_console_init(s->vga.update, s->vga.invalidate, + s->vga.screen_dump, s->vga.text_update, s); + + vga_init_vbe(&s->vga); + return 0; +} diff --git a/hw/vga-isa.c b/hw/vga-isa.c new file mode 100644 index 0000000..3046054 --- /dev/null +++ b/hw/vga-isa.c @@ -0,0 +1,49 @@ +/* + * QEMU ISA 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.h" +#include "console.h" +#include "pc.h" +#include "vga_int.h" +#include "pixel_ops.h" +#include "qemu-timer.h" +#include "loader.h" + +int isa_vga_init(void) +{ + VGACommonState *s; + + s = qemu_mallocz(sizeof(*s)); + + vga_common_init(s, VGA_RAM_SIZE); + vga_init(s); + vmstate_register(NULL, 0, &vmstate_vga_common, s); + + s->ds = graphic_console_init(s->update, s->invalidate, + s->screen_dump, s->text_update, s); + + vga_init_vbe(s); + /* ROM BIOS */ + rom_add_vga(VGABIOS_FILENAME); + return 0; +} diff --git a/hw/vga-pci.c b/hw/vga-pci.c new file mode 100644 index 0000000..ce9ec45 --- /dev/null +++ b/hw/vga-pci.c @@ -0,0 +1,123 @@ +/* + * QEMU PCI 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.h" +#include "console.h" +#include "pc.h" +#include "pci.h" +#include "vga_int.h" +#include "pixel_ops.h" +#include "qemu-timer.h" +#include "loader.h" + +typedef struct PCIVGAState { + PCIDevice dev; + VGACommonState vga; +} PCIVGAState; + +static const VMStateDescription vmstate_vga_pci = { + .name = "vga", + .version_id = 2, + .minimum_version_id = 2, + .minimum_version_id_old = 2, + .fields = (VMStateField []) { + VMSTATE_PCI_DEVICE(dev, PCIVGAState), + VMSTATE_STRUCT(vga, PCIVGAState, 0, vmstate_vga_common, VGACommonState), + VMSTATE_END_OF_LIST() + } +}; + +static void vga_map(PCIDevice *pci_dev, int region_num, + pcibus_t addr, pcibus_t size, int type) +{ + PCIVGAState *d = (PCIVGAState *)pci_dev; + VGACommonState *s = &d->vga; + + cpu_register_physical_memory(addr, s->vram_size, s->vram_offset); + s->map_addr = addr; + s->map_end = addr + s->vram_size; + vga_dirty_log_start(s); +} + +static void pci_vga_write_config(PCIDevice *d, + uint32_t address, uint32_t val, int len) +{ + PCIVGAState *pvs = container_of(d, PCIVGAState, dev); + VGACommonState *s = &pvs->vga; + + pci_default_write_config(d, address, val, len); + if (s->map_addr && pvs->dev.io_regions[0].addr == -1) + s->map_addr = 0; +} + +static int pci_vga_initfn(PCIDevice *dev) +{ + PCIVGAState *d = DO_UPCAST(PCIVGAState, dev, dev); + VGACommonState *s = &d->vga; + uint8_t *pci_conf = d->dev.config; + + // vga + console init + vga_common_init(s, VGA_RAM_SIZE); + vga_init(s); + + s->ds = graphic_console_init(s->update, s->invalidate, + s->screen_dump, s->text_update, s); + + // dummy VGA (same as Bochs ID) + pci_config_set_vendor_id(pci_conf, PCI_VENDOR_ID_QEMU); + pci_config_set_device_id(pci_conf, PCI_DEVICE_ID_QEMU_VGA); + pci_config_set_class(pci_conf, PCI_CLASS_DISPLAY_VGA); + + /* XXX: VGA_RAM_SIZE must be a power of two */ + pci_register_bar(&d->dev, 0, VGA_RAM_SIZE, + PCI_BASE_ADDRESS_MEM_PREFETCH, vga_map); + + if (!dev->rom_bar) { + /* compatibility with pc-0.13 and older */ + vga_init_vbe(s); + } + + return 0; +} + +int pci_vga_init(PCIBus *bus) +{ + pci_create_simple(bus, -1, "VGA"); + return 0; +} + +static PCIDeviceInfo vga_info = { + .qdev.name = "VGA", + .qdev.size = sizeof(PCIVGAState), + .qdev.vmsd = &vmstate_vga_pci, + .no_hotplug = 1, + .init = pci_vga_initfn, + .config_write = pci_vga_write_config, + .romfile = "vgabios-stdvga.bin", +}; + +static void vga_register(void) +{ + pci_qdev_register(&vga_info); +} +device_init(vga_register); diff --git a/hw/vga.c b/hw/vga.c new file mode 100644 index 0000000..e2151a2 --- /dev/null +++ b/hw/vga.c @@ -0,0 +1,2398 @@ +/* + * 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.h" +#include "console.h" +#include "pc.h" +#include "pci.h" +#include "vga_int.h" +#include "pixel_ops.h" +#include "qemu-timer.h" +#include "kvm.h" + +//#define DEBUG_VGA +//#define DEBUG_VGA_MEM +//#define DEBUG_VGA_REG + +//#define DEBUG_BOCHS_VBE + +/* 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 const uint32_t dmask16[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), +}; + +static const uint32_t dmask4[4] = { + PAT(0x00000000), + PAT(0x0000ffff), + PAT(0xffff0000), + PAT(0xffffffff), +}; + +static uint32_t expand4[256]; +static uint16_t expand2[256]; +static uint8_t expand4to8[16]; + +static void vga_screen_dump(void *opaque, const char *filename); +static char *screen_dump_filename; +static DisplayChangeListener *screen_dump_dcl; + +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[0x00] + 5; + hretr_start_char = s->cr[0x04]; + hretr_skew_chars = (s->cr[0x05] >> 5) & 3; + hretr_end_char = s->cr[0x05] & 0x1f; + + vtotal_lines = (s->cr[0x06] + | (((s->cr[0x07] & 1) | ((s->cr[0x07] >> 4) & 2)) << 8)) + 2 + ; + vretr_start_line = s->cr[0x10] + | ((((s->cr[0x07] >> 2) & 1) | ((s->cr[0x07] >> 6) & 2)) << 8) + ; + vretr_end_line = s->cr[0x11] & 0xf; + + + + clocking_mode = (s->sr[0x01] >> 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[0x17] >> 2) & 1; + sldiv2 = (s->cr[0x17] >> 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_get_clock(vm_clock); + + 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 & MSR_COLOR_EMULATION) { + /* 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 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 = s->sr[s->sr_index]; +#ifdef DEBUG_VGA_REG + printf("vga: read SR%x = 0x%02x\n", s->sr_index, val); +#endif + break; + case 0x3c7: + val = s->dac_state; + break; + case 0x3c8: + val = s->dac_write_index; + break; + case 0x3c9: + 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 0x3ca: + val = s->fcr; + break; + case 0x3cc: + val = s->msr; + break; + case 0x3ce: + val = s->gr_index; + break; + case 0x3cf: + 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 0x3b4: + case 0x3d4: + val = s->cr_index; + break; + case 0x3b5: + case 0x3d5: + 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 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; +} + +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 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 & 7; + break; + case 0x3c5: +#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 == 1) s->update_retrace_info(s); + 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: + 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 0x3ce: + s->gr_index = val & 0x0f; + break; + case 0x3cf: +#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]; + break; + case 0x3b4: + case 0x3d4: + s->cr_index = val; + break; + case 0x3b5: + case 0x3d5: +#ifdef DEBUG_VGA_REG + printf("vga: write CR%x = 0x%02x\n", s->cr_index, val); +#endif + /* handle CR0-7 protection */ + if ((s->cr[0x11] & 0x80) && s->cr_index <= 7) { + /* can always write bit 4 of CR7 */ + if (s->cr_index == 7) + s->cr[7] = (s->cr[7] & ~0x10) | (val & 0x10); + return; + } + s->cr[s->cr_index] = val; + + switch(s->cr_index) { + case 0x00: + case 0x04: + case 0x05: + case 0x06: + case 0x07: + case 0x11: + case 0x17: + s->update_retrace_info(s); + break; + } + break; + case 0x3ba: + case 0x3da: + s->fcr = val & 0x10; + break; + } +} + +#ifdef CONFIG_BOCHS_VBE +static uint32_t vbe_ioport_read_index(void *opaque, uint32_t addr) +{ + VGACommonState *s = opaque; + uint32_t val; + val = s->vbe_index; + return val; +} + +static 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->vram_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; +} + +static void vbe_ioport_write_index(void *opaque, uint32_t addr, uint32_t val) +{ + VGACommonState *s = opaque; + s->vbe_index = val; +} + +static 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: + if ((val <= VBE_DISPI_MAX_XRES) && ((val & 7) == 0)) { + s->vbe_regs[s->vbe_index] = val; + } + break; + case VBE_DISPI_INDEX_YRES: + if (val <= VBE_DISPI_MAX_YRES) { + s->vbe_regs[s->vbe_index] = val; + } + break; + case VBE_DISPI_INDEX_BPP: + if (val == 0) + val = 8; + if (val == 4 || val == 8 || val == 15 || + val == 16 || val == 24 || val == 32) { + s->vbe_regs[s->vbe_index] = val; + } + 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); + 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] = + s->vbe_regs[VBE_DISPI_INDEX_XRES]; + s->vbe_regs[VBE_DISPI_INDEX_VIRT_HEIGHT] = + s->vbe_regs[VBE_DISPI_INDEX_YRES]; + s->vbe_regs[VBE_DISPI_INDEX_X_OFFSET] = 0; + s->vbe_regs[VBE_DISPI_INDEX_Y_OFFSET] = 0; + + if (s->vbe_regs[VBE_DISPI_INDEX_BPP] == 4) + s->vbe_line_offset = s->vbe_regs[VBE_DISPI_INDEX_XRES] >> 1; + else + s->vbe_line_offset = s->vbe_regs[VBE_DISPI_INDEX_XRES] * + ((s->vbe_regs[VBE_DISPI_INDEX_BPP] + 7) >> 3); + s->vbe_start_addr = 0; + + /* clear the screen (should be done in BIOS) */ + 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 (should be done + in BIOS) */ + s->gr[0x06] = (s->gr[0x06] & ~0x0c) | 0x05; /* graphic mode + memory map 1 */ + s->cr[0x17] |= 3; /* no CGA modes */ + s->cr[0x13] = s->vbe_line_offset >> 3; + /* width */ + s->cr[0x01] = (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[0x12] = h; + s->cr[0x07] = (s->cr[0x07] & ~0x42) | + ((h >> 7) & 0x02) | ((h >> 3) & 0x40); + /* line compare to 1023 */ + s->cr[0x18] = 0xff; + s->cr[0x07] |= 0x10; + s->cr[0x09] |= 0x40; + + if (s->vbe_regs[VBE_DISPI_INDEX_BPP] == 4) { + shift_control = 0; + s->sr[0x01] &= ~8; /* no double line */ + } else { + shift_control = 2; + s->sr[4] |= 0x08; /* set chain 4 mode */ + s->sr[2] |= 0x0f; /* activate all planes */ + } + s->gr[0x05] = (s->gr[0x05] & ~0x60) | (shift_control << 5); + s->cr[0x09] &= ~0x9f; /* no double scan */ + } else { + /* XXX: the bios should do that */ + s->bank_offset = 0; + } + s->dac_8bit = (val & VBE_DISPI_8BIT_DAC) > 0; + s->vbe_regs[s->vbe_index] = val; + break; + case VBE_DISPI_INDEX_VIRT_WIDTH: + { + int w, h, line_offset; + + if (val < s->vbe_regs[VBE_DISPI_INDEX_XRES]) + return; + w = val; + if (s->vbe_regs[VBE_DISPI_INDEX_BPP] == 4) + line_offset = w >> 1; + else + line_offset = w * ((s->vbe_regs[VBE_DISPI_INDEX_BPP] + 7) >> 3); + h = s->vram_size / line_offset; + /* XXX: support weird bochs semantics ? */ + if (h < s->vbe_regs[VBE_DISPI_INDEX_YRES]) + return; + s->vbe_regs[VBE_DISPI_INDEX_VIRT_WIDTH] = w; + s->vbe_regs[VBE_DISPI_INDEX_VIRT_HEIGHT] = h; + s->vbe_line_offset = line_offset; + } + break; + case VBE_DISPI_INDEX_X_OFFSET: + case VBE_DISPI_INDEX_Y_OFFSET: + { + int x; + s->vbe_regs[s->vbe_index] = val; + s->vbe_start_addr = s->vbe_line_offset * s->vbe_regs[VBE_DISPI_INDEX_Y_OFFSET]; + x = s->vbe_regs[VBE_DISPI_INDEX_X_OFFSET]; + if (s->vbe_regs[VBE_DISPI_INDEX_BPP] == 4) + s->vbe_start_addr += x >> 1; + else + s->vbe_start_addr += x * ((s->vbe_regs[VBE_DISPI_INDEX_BPP] + 7) >> 3); + s->vbe_start_addr >>= 2; + } + break; + default: + break; + } + } +} +#endif + +/* called for accesses between 0xa0000 and 0xc0000 */ +uint32_t vga_mem_readb(void *opaque, target_phys_addr_t addr) +{ + VGACommonState *s = opaque; + int memory_map_mode, plane; + uint32_t ret; + + /* convert to VGA memory offset */ + memory_map_mode = (s->gr[6] >> 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[4] & 0x08) { + /* chain 4 mode : simplest access */ + ret = s->vram_ptr[addr]; + } else if (s->gr[5] & 0x10) { + /* odd/even mode (aka text mode mapping) */ + plane = (s->gr[4] & 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[5] & 0x08)) { + /* read mode 0 */ + plane = s->gr[4]; + ret = GET_PLANE(s->latch, plane); + } else { + /* read mode 1 */ + ret = (s->latch ^ mask16[s->gr[2]]) & mask16[s->gr[7]]; + ret |= ret >> 16; + ret |= ret >> 8; + ret = (~ret) & 0xff; + } + } + return ret; +} + +static uint32_t vga_mem_readw(void *opaque, target_phys_addr_t addr) +{ + uint32_t v; + v = vga_mem_readb(opaque, addr); + v |= vga_mem_readb(opaque, addr + 1) << 8; + return v; +} + +static uint32_t vga_mem_readl(void *opaque, target_phys_addr_t addr) +{ + uint32_t v; + v = vga_mem_readb(opaque, addr); + v |= vga_mem_readb(opaque, addr + 1) << 8; + v |= vga_mem_readb(opaque, addr + 2) << 16; + v |= vga_mem_readb(opaque, addr + 3) << 24; + return v; +} + +/* called for accesses between 0xa0000 and 0xc0000 */ +void vga_mem_writeb(void *opaque, target_phys_addr_t addr, uint32_t val) +{ + VGACommonState *s = opaque; + 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[6] >> 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[4] & 0x08) { + /* chain 4 mode : simplest access */ + plane = addr & 3; + mask = (1 << plane); + if (s->sr[2] & 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 */ + cpu_physical_memory_set_dirty(s->vram_offset + addr); + } + } else if (s->gr[5] & 0x10) { + /* odd/even mode (aka text mode mapping) */ + plane = (s->gr[4] & 2) | (addr & 1); + mask = (1 << plane); + if (s->sr[2] & 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 */ + cpu_physical_memory_set_dirty(s->vram_offset + addr); + } + } else { + /* standard VGA latched access */ + write_mode = s->gr[5] & 3; + switch(write_mode) { + default: + case 0: + /* rotate */ + b = s->gr[3] & 7; + val = ((val >> b) | (val << (8 - b))) & 0xff; + val |= val << 8; + val |= val << 16; + + /* apply set/reset mask */ + set_mask = mask16[s->gr[1]]; + val = (val & ~set_mask) | (mask16[s->gr[0]] & set_mask); + bit_mask = s->gr[8]; + break; + case 1: + val = s->latch; + goto do_write; + case 2: + val = mask16[val & 0x0f]; + bit_mask = s->gr[8]; + break; + case 3: + /* rotate */ + b = s->gr[3] & 7; + val = (val >> b) | (val << (8 - b)); + + bit_mask = s->gr[8] & val; + val = mask16[s->gr[0]]; + break; + } + + /* apply logical operation */ + func_select = s->gr[3] >> 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[2]; + 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 + cpu_physical_memory_set_dirty(s->vram_offset + (addr << 2)); + } +} + +static void vga_mem_writew(void *opaque, target_phys_addr_t addr, uint32_t val) +{ + vga_mem_writeb(opaque, addr, val & 0xff); + vga_mem_writeb(opaque, addr + 1, (val >> 8) & 0xff); +} + +static void vga_mem_writel(void *opaque, target_phys_addr_t addr, uint32_t val) +{ + vga_mem_writeb(opaque, addr, val & 0xff); + vga_mem_writeb(opaque, addr + 1, (val >> 8) & 0xff); + vga_mem_writeb(opaque, addr + 2, (val >> 16) & 0xff); + vga_mem_writeb(opaque, addr + 3, (val >> 24) & 0xff); +} + +typedef void vga_draw_glyph8_func(uint8_t *d, int linesize, + const uint8_t *font_ptr, int h, + uint32_t fgcol, uint32_t bgcol); +typedef void vga_draw_glyph9_func(uint8_t *d, int linesize, + const uint8_t *font_ptr, int h, + uint32_t fgcol, uint32_t bgcol, int dup9); +typedef void vga_draw_line_func(VGACommonState *s1, uint8_t *d, + const uint8_t *s, int width); + +#define DEPTH 8 +#include "vga_template.h" + +#define DEPTH 15 +#include "vga_template.h" + +#define BGR_FORMAT +#define DEPTH 15 +#include "vga_template.h" + +#define DEPTH 16 +#include "vga_template.h" + +#define BGR_FORMAT +#define DEPTH 16 +#include "vga_template.h" + +#define DEPTH 32 +#include "vga_template.h" + +#define BGR_FORMAT +#define DEPTH 32 +#include "vga_template.h" + +static unsigned int rgb_to_pixel8_dup(unsigned int r, unsigned int g, unsigned b) +{ + unsigned int col; + col = rgb_to_pixel8(r, g, b); + col |= col << 8; + col |= col << 16; + return col; +} + +static unsigned int rgb_to_pixel15_dup(unsigned int r, unsigned int g, unsigned b) +{ + unsigned int col; + col = rgb_to_pixel15(r, g, b); + col |= col << 16; + return col; +} + +static unsigned int rgb_to_pixel15bgr_dup(unsigned int r, unsigned int g, + unsigned int b) +{ + unsigned int col; + col = rgb_to_pixel15bgr(r, g, b); + col |= col << 16; + return col; +} + +static unsigned int rgb_to_pixel16_dup(unsigned int r, unsigned int g, unsigned b) +{ + unsigned int col; + col = rgb_to_pixel16(r, g, b); + col |= col << 16; + return col; +} + +static unsigned int rgb_to_pixel16bgr_dup(unsigned int r, unsigned int g, + unsigned int b) +{ + unsigned int col; + col = rgb_to_pixel16bgr(r, g, b); + col |= col << 16; + return col; +} + +static unsigned int rgb_to_pixel32_dup(unsigned int r, unsigned int g, unsigned b) +{ + unsigned int col; + col = rgb_to_pixel32(r, g, b); + return col; +} + +static unsigned int rgb_to_pixel32bgr_dup(unsigned int r, unsigned int g, unsigned b) +{ + unsigned int col; + col = rgb_to_pixel32bgr(r, g, b); + return col; +} + +/* 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[0x10] & 0x80) + v = ((s->ar[0x14] & 0xf) << 4) | (v & 0xf); + else + v = ((s->ar[0x14] & 0xc) << 4) | (v & 0x3f); + v = v * 3; + col = s->rgb_to_pixel(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 = s->rgb_to_pixel(s->palette[v], + s->palette[v + 1], + s->palette[v + 2]); + } else { + col = s->rgb_to_pixel(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; +#ifdef CONFIG_BOCHS_VBE + 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 +#endif + { + /* compute line_offset in bytes */ + line_offset = s->cr[0x13]; + line_offset <<= 3; + + /* starting address */ + start_addr = s->cr[0x0d] | (s->cr[0x0c] << 8); + + /* line compare */ + line_compare = s->cr[0x18] | + ((s->cr[0x07] & 0x10) << 4) | + ((s->cr[0x09] & 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; +} + +#define NB_DEPTHS 7 + +static inline int get_depth_index(DisplayState *s) +{ + switch(ds_get_bits_per_pixel(s)) { + default: + case 8: + return 0; + case 15: + return 1; + case 16: + return 2; + case 32: + if (is_surface_bgr(s->surface)) + return 4; + else + return 3; + } +} + +static vga_draw_glyph8_func * const vga_draw_glyph8_table[NB_DEPTHS] = { + vga_draw_glyph8_8, + vga_draw_glyph8_16, + vga_draw_glyph8_16, + vga_draw_glyph8_32, + vga_draw_glyph8_32, + vga_draw_glyph8_16, + vga_draw_glyph8_16, +}; + +static vga_draw_glyph8_func * const vga_draw_glyph16_table[NB_DEPTHS] = { + vga_draw_glyph16_8, + vga_draw_glyph16_16, + vga_draw_glyph16_16, + vga_draw_glyph16_32, + vga_draw_glyph16_32, + vga_draw_glyph16_16, + vga_draw_glyph16_16, +}; + +static vga_draw_glyph9_func * const vga_draw_glyph9_table[NB_DEPTHS] = { + vga_draw_glyph9_8, + vga_draw_glyph9_16, + vga_draw_glyph9_16, + vga_draw_glyph9_32, + vga_draw_glyph9_32, + vga_draw_glyph9_16, + vga_draw_glyph9_16, +}; + +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[9] & 0x1f) + 1; + cwidth = 8; + if (!(s->sr[1] & 0x01)) + cwidth = 9; + if (s->sr[1] & 0x08) + cwidth = 16; /* NOTE: no 18 pixel wide */ + width = (s->cr[0x01] + 1); + if (s->cr[0x06] == 100) { + /* ugly hack for CGA 160x100x16 - explain me the logic */ + height = 100; + } else { + height = s->cr[0x12] | + ((s->cr[0x07] & 0x02) << 7) | + ((s->cr[0x07] & 0x40) << 3); + height = (height + 1) / cheight; + } + + *pwidth = width; + *pheight = height; + *pcwidth = cwidth; + *pcheight = cheight; +} + +typedef unsigned int rgb_to_pixel_dup_func(unsigned int r, unsigned int g, unsigned b); + +static rgb_to_pixel_dup_func * const rgb_to_pixel_dup_table[NB_DEPTHS] = { + rgb_to_pixel8_dup, + rgb_to_pixel15_dup, + rgb_to_pixel16_dup, + rgb_to_pixel32_dup, + rgb_to_pixel32bgr_dup, + rgb_to_pixel15bgr_dup, + rgb_to_pixel16bgr_dup, +}; + +/* + * Text mode update + * Missing: + * - double scan + * - double width + * - underline + * - flashing + */ +static void vga_draw_text(VGACommonState *s, int full_update) +{ + 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, depth_index; + uint32_t *palette; + uint32_t *ch_attr_ptr; + vga_draw_glyph8_func *vga_draw_glyph8; + vga_draw_glyph9_func *vga_draw_glyph9; + + /* compute font data address (in plane 2) */ + v = s->sr[3]; + 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)) { + /* 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) > 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->ds, s->last_scr_width, s->last_scr_height); + s->last_depth = 0; + s->last_width = width; + s->last_height = height; + s->last_ch = cheight; + s->last_cw = cw; + full_update = 1; + } + s->rgb_to_pixel = + rgb_to_pixel_dup_table[get_depth_index(s->ds)]; + full_update |= update_palette16(s); + palette = s->last_palette; + x_incr = cw * ((ds_get_bits_per_pixel(s->ds) + 7) >> 3); + + cursor_offset = ((s->cr[0x0e] << 8) | s->cr[0x0f]) - s->start_addr; + if (cursor_offset != s->cursor_offset || + s->cr[0xa] != s->cursor_start || + s->cr[0xb] != 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[0xa]; + s->cursor_end = s->cr[0xb]; + } + cursor_ptr = s->vram_ptr + (s->start_addr + cursor_offset) * 4; + + depth_index = get_depth_index(s->ds); + if (cw == 16) + vga_draw_glyph8 = vga_draw_glyph16_table[depth_index]; + else + vga_draw_glyph8 = vga_draw_glyph8_table[depth_index]; + vga_draw_glyph9 = vga_draw_glyph9_table[depth_index]; + + dest = ds_get_data(s->ds); + linesize = ds_get_linesize(s->ds); + 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) { + 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 != 9) { + vga_draw_glyph8(d1, linesize, + font_ptr, cheight, fgcol, bgcol); + } else { + dup9 = 0; + if (ch >= 0xb0 && ch <= 0xdf && (s->ar[0x10] & 0x04)) + dup9 = 1; + vga_draw_glyph9(d1, linesize, + font_ptr, cheight, fgcol, bgcol, dup9); + } + if (src == cursor_ptr && + !(s->cr[0x0a] & 0x20)) { + int line_start, line_last, h; + /* draw the cursor */ + line_start = s->cr[0x0a] & 0x1f; + line_last = s->cr[0x0b] & 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 != 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_update(s->ds, 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, + VGA_DRAW_LINE16, + VGA_DRAW_LINE24, + VGA_DRAW_LINE32, + VGA_DRAW_LINE_NB, +}; + +static vga_draw_line_func * const vga_draw_line_table[NB_DEPTHS * VGA_DRAW_LINE_NB] = { + vga_draw_line2_8, + vga_draw_line2_16, + vga_draw_line2_16, + vga_draw_line2_32, + vga_draw_line2_32, + vga_draw_line2_16, + vga_draw_line2_16, + + vga_draw_line2d2_8, + vga_draw_line2d2_16, + vga_draw_line2d2_16, + vga_draw_line2d2_32, + vga_draw_line2d2_32, + vga_draw_line2d2_16, + vga_draw_line2d2_16, + + vga_draw_line4_8, + vga_draw_line4_16, + vga_draw_line4_16, + vga_draw_line4_32, + vga_draw_line4_32, + vga_draw_line4_16, + vga_draw_line4_16, + + vga_draw_line4d2_8, + vga_draw_line4d2_16, + vga_draw_line4d2_16, + vga_draw_line4d2_32, + vga_draw_line4d2_32, + vga_draw_line4d2_16, + vga_draw_line4d2_16, + + vga_draw_line8d2_8, + vga_draw_line8d2_16, + vga_draw_line8d2_16, + vga_draw_line8d2_32, + vga_draw_line8d2_32, + vga_draw_line8d2_16, + vga_draw_line8d2_16, + + vga_draw_line8_8, + vga_draw_line8_16, + vga_draw_line8_16, + vga_draw_line8_32, + vga_draw_line8_32, + vga_draw_line8_16, + vga_draw_line8_16, + + vga_draw_line15_8, + vga_draw_line15_15, + vga_draw_line15_16, + vga_draw_line15_32, + vga_draw_line15_32bgr, + vga_draw_line15_15bgr, + vga_draw_line15_16bgr, + + vga_draw_line16_8, + vga_draw_line16_15, + vga_draw_line16_16, + vga_draw_line16_32, + vga_draw_line16_32bgr, + vga_draw_line16_15bgr, + vga_draw_line16_16bgr, + + vga_draw_line24_8, + vga_draw_line24_15, + vga_draw_line24_16, + vga_draw_line24_32, + vga_draw_line24_32bgr, + vga_draw_line24_15bgr, + vga_draw_line24_16bgr, + + vga_draw_line32_8, + vga_draw_line32_15, + vga_draw_line32_16, + vga_draw_line32_32, + vga_draw_line32_32bgr, + vga_draw_line32_15bgr, + vga_draw_line32_16bgr, +}; + +static int vga_get_bpp(VGACommonState *s) +{ + int ret; +#ifdef CONFIG_BOCHS_VBE + if (s->vbe_regs[VBE_DISPI_INDEX_ENABLE] & VBE_DISPI_ENABLED) { + ret = s->vbe_regs[VBE_DISPI_INDEX_BPP]; + } else +#endif + { + ret = 0; + } + return ret; +} + +static void vga_get_resolution(VGACommonState *s, int *pwidth, int *pheight) +{ + int width, height; + +#ifdef CONFIG_BOCHS_VBE + 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 +#endif + { + width = (s->cr[0x01] + 1) * 8; + height = s->cr[0x12] | + ((s->cr[0x07] & 0x02) << 7) | + ((s->cr[0x07] & 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); + } +} + +static void vga_sync_dirty_bitmap(VGACommonState *s) +{ + if (s->map_addr) + cpu_physical_sync_dirty_bitmap(s->map_addr, s->map_end); + + if (s->lfb_vram_mapped) { + cpu_physical_sync_dirty_bitmap(isa_mem_base + 0xa0000, 0xa8000); + cpu_physical_sync_dirty_bitmap(isa_mem_base + 0xa8000, 0xb0000); + } + +#ifdef CONFIG_BOCHS_VBE + if (s->vbe_mapped) { + cpu_physical_sync_dirty_bitmap(VBE_DISPI_LFB_PHYSICAL_ADDRESS, + VBE_DISPI_LFB_PHYSICAL_ADDRESS + s->vram_size); + } +#endif + +} + +void vga_dirty_log_start(VGACommonState *s) +{ + if (kvm_enabled() && s->map_addr) + kvm_log_start(s->map_addr, s->map_end - s->map_addr); + + if (kvm_enabled() && s->lfb_vram_mapped) { + kvm_log_start(isa_mem_base + 0xa0000, 0x8000); + kvm_log_start(isa_mem_base + 0xa8000, 0x8000); + } + +#ifdef CONFIG_BOCHS_VBE + if (kvm_enabled() && s->vbe_mapped) { + kvm_log_start(VBE_DISPI_LFB_PHYSICAL_ADDRESS, s->vram_size); + } +#endif +} + +void vga_dirty_log_stop(VGACommonState *s) +{ + if (kvm_enabled() && s->map_addr) + kvm_log_stop(s->map_addr, s->map_end - s->map_addr); + + if (kvm_enabled() && s->lfb_vram_mapped) { + kvm_log_stop(isa_mem_base + 0xa0000, 0x8000); + kvm_log_stop(isa_mem_base + 0xa8000, 0x8000); + } + +#ifdef CONFIG_BOCHS_VBE + if (kvm_enabled() && s->vbe_mapped) { + kvm_log_stop(VBE_DISPI_LFB_PHYSICAL_ADDRESS, s->vram_size); + } +#endif +} + +void vga_dirty_log_restart(VGACommonState *s) +{ + vga_dirty_log_stop(s); + vga_dirty_log_start(s); +} + +/* + * graphic modes + */ +static void vga_draw_graphic(VGACommonState *s, int full_update) +{ + 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; + + 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[0x05] >> 5) & 3; + double_scan = (s->cr[0x09] >> 7); + if (shift_control != 1) { + multi_scan = (((s->cr[0x09] & 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[0x01] & 8) { + disp_width <<= 1; + } + } else if (shift_control == 1) { + if (s->sr[0x01] & 8) { + disp_width <<= 1; + } + } + + depth = s->get_bpp(s); + if (s->line_offset != s->last_line_offset || + disp_width != s->last_width || + height != s->last_height || + s->last_depth != depth) { +#if defined(HOST_WORDS_BIGENDIAN) == defined(TARGET_WORDS_BIGENDIAN) + if (depth == 16 || depth == 32) { +#else + if (depth == 32) { +#endif + qemu_free_displaysurface(s->ds); + s->ds->surface = qemu_create_displaysurface_from(disp_width, height, depth, + s->line_offset, + s->vram_ptr + (s->start_addr * 4)); +#if defined(HOST_WORDS_BIGENDIAN) != defined(TARGET_WORDS_BIGENDIAN) + s->ds->surface->pf = qemu_different_endianness_pixelformat(depth); +#endif + dpy_resize(s->ds); + } else { + qemu_console_resize(s->ds, disp_width, height); + } + 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; + full_update = 1; + } else if (is_buffer_shared(s->ds->surface) && + (full_update || s->ds->surface->data != s->vram_ptr + (s->start_addr * 4))) { + s->ds->surface->data = s->vram_ptr + (s->start_addr * 4); + dpy_setdata(s->ds); + } + + s->rgb_to_pixel = + rgb_to_pixel_dup_table[get_depth_index(s->ds)]; + + if (shift_control == 0) { + full_update |= update_palette16(s); + if (s->sr[0x01] & 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[0x01] & 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 = VGA_DRAW_LINE15; + bits = 16; + break; + case 16: + v = VGA_DRAW_LINE16; + bits = 16; + break; + case 24: + v = VGA_DRAW_LINE24; + bits = 24; + break; + case 32: + v = VGA_DRAW_LINE32; + bits = 32; + break; + } + } + vga_draw_line = vga_draw_line_table[v * NB_DEPTHS + get_depth_index(s->ds)]; + + if (!is_buffer_shared(s->ds->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[0x17], s->line_compare, s->sr[0x01]); +#endif + addr1 = (s->start_addr * 4); + bwidth = (width * bits + 7) / 8; + y_start = -1; + page_min = -1; + page_max = 0; + d = ds_get_data(s->ds); + linesize = ds_get_linesize(s->ds); + y1 = 0; + for(y = 0; y < height; y++) { + addr = addr1; + if (!(s->cr[0x17] & 1)) { + int shift; + /* CGA compatibility handling */ + shift = 14 + ((s->cr[0x17] >> 6) & 1); + addr = (addr & ~(1 << shift)) | ((y1 & 1) << shift); + } + if (!(s->cr[0x17] & 2)) { + addr = (addr & ~0x8000) | ((y1 & 2) << 14); + } + page0 = s->vram_offset + (addr & TARGET_PAGE_MASK); + page1 = s->vram_offset + ((addr + bwidth - 1) & TARGET_PAGE_MASK); + update = full_update | + cpu_physical_memory_get_dirty(page0, VGA_DIRTY_FLAG) | + cpu_physical_memory_get_dirty(page1, VGA_DIRTY_FLAG); + if ((page1 - page0) > TARGET_PAGE_SIZE) { + /* if wide line, can use another page */ + update |= cpu_physical_memory_get_dirty(page0 + TARGET_PAGE_SIZE, + VGA_DIRTY_FLAG); + } + /* 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(s->ds->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_update(s->ds, 0, y_start, + disp_width, y - y_start); + y_start = -1; + } + } + if (!multi_run) { + mask = (s->cr[0x17] & 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_update(s->ds, 0, y_start, + disp_width, y - y_start); + } + /* reset modified pages */ + if (page_max >= page_min) { + cpu_physical_memory_reset_dirty(page_min, page_max + TARGET_PAGE_SIZE, + VGA_DIRTY_FLAG); + } + memset(s->invalidated_y_table, 0, ((height + 31) >> 5) * 4); +} + +static void vga_draw_blank(VGACommonState *s, int full_update) +{ + int i, w, val; + uint8_t *d; + + if (!full_update) + return; + if (s->last_scr_width <= 0 || s->last_scr_height <= 0) + return; + + s->rgb_to_pixel = + rgb_to_pixel_dup_table[get_depth_index(s->ds)]; + if (ds_get_bits_per_pixel(s->ds) == 8) + val = s->rgb_to_pixel(0, 0, 0); + else + val = 0; + w = s->last_scr_width * ((ds_get_bits_per_pixel(s->ds) + 7) >> 3); + d = ds_get_data(s->ds); + for(i = 0; i < s->last_scr_height; i++) { + memset(d, val, w); + d += ds_get_linesize(s->ds); + } + dpy_update(s->ds, 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; + int full_update, graphic_mode; + + if (ds_get_bits_per_pixel(s->ds) == 0) { + /* nothing to do */ + } else { + full_update = 0; + if (!(s->ar_index & 0x20)) { + graphic_mode = GMODE_BLANK; + } else { + graphic_mode = s->gr[6] & 1; + } + if (graphic_mode != s->graphic_mode) { + s->graphic_mode = graphic_mode; + 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->lfb_addr = 0; + s->lfb_end = 0; + s->map_addr = 0; + s->map_end = 0; + s->lfb_vram_mapped = 0; + 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; +#ifdef CONFIG_BOCHS_VBE + 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; +#endif + 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; + 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; + } +} + +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; + + if (!(s->ar_index & 0x20)) { + graphic_mode = GMODE_BLANK; + } else { + graphic_mode = s->gr[6] & 1; + } + 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[9] & 0x1f) + 1; + cw = 8; + if (!(s->sr[1] & 0x01)) + cw = 9; + if (s->sr[1] & 0x08) + cw = 16; /* NOTE: no 18 pixel wide */ + width = (s->cr[0x01] + 1); + if (s->cr[0x06] == 100) { + /* ugly hack for CGA 160x100x16 - explain me the logic */ + height = 100; + } else { + height = s->cr[0x12] | + ((s->cr[0x07] & 0x02) << 7) | + ((s->cr[0x07] & 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; + s->ds->surface->width = width; + s->ds->surface->height = height; + dpy_resize(s->ds); + s->last_width = width; + s->last_height = height; + s->last_ch = cheight; + s->last_cw = cw; + full_update = 1; + } + + /* Update "hardware" cursor */ + cursor_offset = ((s->cr[0x0e] << 8) | s->cr[0x0f]) - s->start_addr; + if (cursor_offset != s->cursor_offset || + s->cr[0xa] != s->cursor_start || + s->cr[0xb] != s->cursor_end || full_update) { + cursor_visible = !(s->cr[0xa] & 0x20); + if (cursor_visible && cursor_offset < size && cursor_offset >= 0) + dpy_cursor(s->ds, + TEXTMODE_X(cursor_offset), + TEXTMODE_Y(cursor_offset)); + else + dpy_cursor(s->ds, -1, -1); + s->cursor_offset = cursor_offset; + s->cursor_start = s->cr[0xa]; + s->cursor_end = s->cr[0xb]; + } + + 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_update(s->ds, 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_update(s->ds, 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_cursor(s->ds, -1, -1); + s->ds->surface->width = s->last_width; + s->ds->surface->height = height; + dpy_resize(s->ds); + + 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_update(s->ds, 0, 0, s->last_width, height); +} + +CPUReadMemoryFunc * const vga_mem_read[3] = { + vga_mem_readb, + vga_mem_readw, + vga_mem_readl, +}; + +CPUWriteMemoryFunc * const vga_mem_write[3] = { + vga_mem_writeb, + vga_mem_writew, + vga_mem_writel, +}; + +static int vga_common_post_load(void *opaque, int version_id) +{ + VGACommonState *s = opaque; + + /* force refresh */ + s->graphic_mode = -1; + return 0; +} + +const VMStateDescription vmstate_vga_common = { + .name = "vga", + .version_id = 2, + .minimum_version_id = 2, + .minimum_version_id_old = 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), +#ifdef CONFIG_BOCHS_VBE + 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), +#endif + VMSTATE_END_OF_LIST() + } +}; + +void vga_common_init(VGACommonState *s, int vga_ram_size) +{ + 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; + } + +#ifdef CONFIG_BOCHS_VBE + s->is_vbe_vmstate = 1; +#else + s->is_vbe_vmstate = 0; +#endif + s->vram_offset = qemu_ram_alloc(NULL, "vga.vram", vga_ram_size); + s->vram_ptr = qemu_get_ram_ptr(s->vram_offset); + s->vram_size = vga_ram_size; + s->get_bpp = vga_get_bpp; + s->get_offsets = vga_get_offsets; + s->get_resolution = vga_get_resolution; + s->update = vga_update_display; + s->invalidate = vga_invalidate_display; + s->screen_dump = vga_screen_dump; + s->text_update = vga_update_text; + 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; + } +} + +/* used by both ISA and PCI */ +void vga_init(VGACommonState *s) +{ + int vga_io_memory; + + qemu_register_reset(vga_reset, s); + + register_ioport_write(0x3c0, 16, 1, vga_ioport_write, s); + + register_ioport_write(0x3b4, 2, 1, vga_ioport_write, s); + register_ioport_write(0x3d4, 2, 1, vga_ioport_write, s); + register_ioport_write(0x3ba, 1, 1, vga_ioport_write, s); + register_ioport_write(0x3da, 1, 1, vga_ioport_write, s); + + register_ioport_read(0x3c0, 16, 1, vga_ioport_read, s); + + register_ioport_read(0x3b4, 2, 1, vga_ioport_read, s); + register_ioport_read(0x3d4, 2, 1, vga_ioport_read, s); + register_ioport_read(0x3ba, 1, 1, vga_ioport_read, s); + register_ioport_read(0x3da, 1, 1, vga_ioport_read, s); + s->bank_offset = 0; + +#ifdef CONFIG_BOCHS_VBE +#if defined (TARGET_I386) + register_ioport_read(0x1ce, 1, 2, vbe_ioport_read_index, s); + register_ioport_read(0x1cf, 1, 2, vbe_ioport_read_data, s); + + register_ioport_write(0x1ce, 1, 2, vbe_ioport_write_index, s); + register_ioport_write(0x1cf, 1, 2, vbe_ioport_write_data, s); +#else + register_ioport_read(0x1ce, 1, 2, vbe_ioport_read_index, s); + register_ioport_read(0x1d0, 1, 2, vbe_ioport_read_data, s); + + register_ioport_write(0x1ce, 1, 2, vbe_ioport_write_index, s); + register_ioport_write(0x1d0, 1, 2, vbe_ioport_write_data, s); +#endif +#endif /* CONFIG_BOCHS_VBE */ + + vga_io_memory = cpu_register_io_memory(vga_mem_read, vga_mem_write, s, + DEVICE_LITTLE_ENDIAN); + cpu_register_physical_memory(isa_mem_base + 0x000a0000, 0x20000, + vga_io_memory); + qemu_register_coalesced_mmio(isa_mem_base + 0x000a0000, 0x20000); +} + +void vga_init_vbe(VGACommonState *s) +{ +#ifdef CONFIG_BOCHS_VBE + /* XXX: use optimized standard vga accesses */ + cpu_register_physical_memory(VBE_DISPI_LFB_PHYSICAL_ADDRESS, + VGA_RAM_SIZE, s->vram_offset); + s->vbe_mapped = 1; +#endif +} +/********************************************************/ +/* vga screen dump */ + +static void vga_save_dpy_update(DisplayState *ds, + int x, int y, int w, int h) +{ + if (screen_dump_filename) { + ppm_save(screen_dump_filename, ds->surface); + screen_dump_filename = NULL; + } +} + +static void vga_save_dpy_resize(DisplayState *s) +{ +} + +static void vga_save_dpy_refresh(DisplayState *s) +{ +} + +int ppm_save(const char *filename, struct DisplaySurface *ds) +{ + FILE *f; + uint8_t *d, *d1; + uint32_t v; + int y, x; + uint8_t r, g, b; + + f = fopen(filename, "wb"); + if (!f) + return -1; + fprintf(f, "P6\n%d %d\n%d\n", + ds->width, ds->height, 255); + d1 = ds->data; + for(y = 0; y < ds->height; y++) { + d = d1; + for(x = 0; x < ds->width; x++) { + if (ds->pf.bits_per_pixel == 32) + v = *(uint32_t *)d; + else + v = (uint32_t) (*(uint16_t *)d); + r = ((v >> ds->pf.rshift) & ds->pf.rmax) * 256 / + (ds->pf.rmax + 1); + g = ((v >> ds->pf.gshift) & ds->pf.gmax) * 256 / + (ds->pf.gmax + 1); + b = ((v >> ds->pf.bshift) & ds->pf.bmax) * 256 / + (ds->pf.bmax + 1); + fputc(r, f); + fputc(g, f); + fputc(b, f); + d += ds->pf.bytes_per_pixel; + } + d1 += ds->linesize; + } + fclose(f); + return 0; +} + +static DisplayChangeListener* vga_screen_dump_init(DisplayState *ds) +{ + DisplayChangeListener *dcl; + + dcl = qemu_mallocz(sizeof(DisplayChangeListener)); + dcl->dpy_update = vga_save_dpy_update; + dcl->dpy_resize = vga_save_dpy_resize; + dcl->dpy_refresh = vga_save_dpy_refresh; + register_displaychangelistener(ds, dcl); + return dcl; +} + +/* save the vga display in a PPM image even if no display is + available */ +static void vga_screen_dump(void *opaque, const char *filename) +{ + VGACommonState *s = opaque; + + if (!screen_dump_dcl) + screen_dump_dcl = vga_screen_dump_init(s->ds); + + screen_dump_filename = (char *)filename; + vga_invalidate_display(s); + vga_hw_update(); +} + diff --git a/hw/vga_int.h b/hw/vga_int.h new file mode 100644 index 0000000..1067f2c --- /dev/null +++ b/hw/vga_int.h @@ -0,0 +1,232 @@ +/* + * 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. + */ + +#include + +#define MSR_COLOR_EMULATION 0x01 +#define MSR_PAGE_SELECT 0x20 + +#define ST01_V_RETRACE 0x08 +#define ST01_DISP_ENABLE 0x01 + +/* bochs VBE support */ +#define CONFIG_BOCHS_VBE + +#define VBE_DISPI_MAX_XRES 1600 +#define VBE_DISPI_MAX_YRES 1200 +#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 + +#ifdef CONFIG_BOCHS_VBE + +#define VGA_STATE_COMMON_BOCHS_VBE \ + 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; +#else + +#define VGA_STATE_COMMON_BOCHS_VBE + +#endif /* !CONFIG_BOCHS_VBE */ + +#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 { + uint8_t *vram_ptr; + ram_addr_t vram_offset; + uint32_t vram_size; + uint32_t lfb_addr; + uint32_t lfb_end; + uint32_t map_addr; + uint32_t map_end; + uint32_t lfb_vram_mapped; /* whether 0xa0000 is mapped as ram */ + uint32_t latch; + 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 vga_io_memory; + 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); + VGA_STATE_COMMON_BOCHS_VBE + /* display refresh support */ + DisplayState *ds; + 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 */ + uint8_t cursor_start, cursor_end; + uint32_t cursor_offset; + unsigned int (*rgb_to_pixel)(unsigned int r, + unsigned int g, unsigned b); + vga_hw_update_ptr update; + vga_hw_invalidate_ptr invalidate; + vga_hw_screen_dump_ptr screen_dump; + vga_hw_text_update_ptr text_update; + /* hardware mouse cursor support */ + uint32_t invalidated_y_table[VGA_MAX_HEIGHT / 32]; + 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, int vga_ram_size); +void vga_init(VGACommonState *s); +void vga_common_reset(VGACommonState *s); + +void vga_dirty_log_start(VGACommonState *s); +void vga_dirty_log_stop(VGACommonState *s); +void vga_dirty_log_restart(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(void *opaque, target_phys_addr_t addr); +void vga_mem_writeb(void *opaque, target_phys_addr_t addr, uint32_t val); +void vga_invalidate_scanlines(VGACommonState *s, int y1, int y2); +int ppm_save(const char *filename, struct DisplaySurface *ds); + +void vga_draw_cursor_line_8(uint8_t *d1, const uint8_t *src1, + int poffset, int w, + unsigned int color0, unsigned int color1, + unsigned int color_xor); +void vga_draw_cursor_line_16(uint8_t *d1, const uint8_t *src1, + int poffset, int w, + unsigned int color0, unsigned int color1, + unsigned int color_xor); +void vga_draw_cursor_line_32(uint8_t *d1, const uint8_t *src1, + int poffset, int w, + unsigned int color0, unsigned int color1, + unsigned int color_xor); + +int vga_ioport_invalid(VGACommonState *s, uint32_t addr); +void vga_init_vbe(VGACommonState *s); + +extern const uint8_t sr_mask[8]; +extern const uint8_t gr_mask[16]; + +#define VGA_RAM_SIZE (8192 * 1024) +#define VGABIOS_FILENAME "vgabios.bin" +#define VGABIOS_CIRRUS_FILENAME "vgabios-cirrus.bin" + +extern CPUReadMemoryFunc * const vga_mem_read[3]; +extern CPUWriteMemoryFunc * const vga_mem_write[3]; diff --git a/hw/vga_template.h b/hw/vga_template.h new file mode 100644 index 0000000..681425f --- /dev/null +++ b/hw/vga_template.h @@ -0,0 +1,525 @@ +/* + * 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. + */ + +#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 */ + +#if DEPTH != 15 && !defined(BGR_FORMAT) + +static inline void glue(vga_draw_glyph_line_, DEPTH)(uint8_t *d, + uint32_t font_data, + uint32_t xorcol, + uint32_t bgcol) +{ +#if BPP == 1 + ((uint32_t *)d)[0] = (dmask16[(font_data >> 4)] & xorcol) ^ bgcol; + ((uint32_t *)d)[1] = (dmask16[(font_data >> 0) & 0xf] & xorcol) ^ bgcol; +#elif BPP == 2 + ((uint32_t *)d)[0] = (dmask4[(font_data >> 6)] & xorcol) ^ bgcol; + ((uint32_t *)d)[1] = (dmask4[(font_data >> 4) & 3] & xorcol) ^ bgcol; + ((uint32_t *)d)[2] = (dmask4[(font_data >> 2) & 3] & xorcol) ^ bgcol; + ((uint32_t *)d)[3] = (dmask4[(font_data >> 0) & 3] & xorcol) ^ bgcol; +#else + ((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; +#endif +} + +static void glue(vga_draw_glyph8_, DEPTH)(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]; + glue(vga_draw_glyph_line_, DEPTH)(d, font_data, xorcol, bgcol); + font_ptr += 4; + d += linesize; + } while (--h); +} + +static void glue(vga_draw_glyph16_, DEPTH)(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]; + glue(vga_draw_glyph_line_, DEPTH)(d, + expand4to8[font_data >> 4], + xorcol, bgcol); + glue(vga_draw_glyph_line_, DEPTH)(d + 8 * BPP, + expand4to8[font_data & 0x0f], + xorcol, bgcol); + font_ptr += 4; + d += linesize; + } while (--h); +} + +static void glue(vga_draw_glyph9_, DEPTH)(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]; +#if BPP == 1 + cpu_to_32wu((uint32_t *)d, (dmask16[(font_data >> 4)] & xorcol) ^ bgcol); + v = (dmask16[(font_data >> 0) & 0xf] & xorcol) ^ bgcol; + cpu_to_32wu(((uint32_t *)d)+1, v); + if (dup9) + ((uint8_t *)d)[8] = v >> (24 * (1 - BIG)); + else + ((uint8_t *)d)[8] = bgcol; + +#elif BPP == 2 + cpu_to_32wu(((uint32_t *)d)+0, (dmask4[(font_data >> 6)] & xorcol) ^ bgcol); + cpu_to_32wu(((uint32_t *)d)+1, (dmask4[(font_data >> 4) & 3] & xorcol) ^ bgcol); + cpu_to_32wu(((uint32_t *)d)+2, (dmask4[(font_data >> 2) & 3] & xorcol) ^ bgcol); + v = (dmask4[(font_data >> 0) & 3] & xorcol) ^ bgcol; + cpu_to_32wu(((uint32_t *)d)+3, v); + if (dup9) + ((uint16_t *)d)[8] = v >> (16 * (1 - BIG)); + else + ((uint16_t *)d)[8] = bgcol; +#else + ((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; +#endif + font_ptr += 4; + d += linesize; + } while (--h); +} + +/* + * 4 color mode + */ +static void glue(vga_draw_line2_, DEPTH)(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[0x12] & 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; + ((PIXEL_TYPE *)d)[0] = palette[v >> 12]; + ((PIXEL_TYPE *)d)[1] = palette[(v >> 8) & 0xf]; + ((PIXEL_TYPE *)d)[2] = palette[(v >> 4) & 0xf]; + ((PIXEL_TYPE *)d)[3] = palette[(v >> 0) & 0xf]; + + v = expand2[GET_PLANE(data, 1)]; + v |= expand2[GET_PLANE(data, 3)] << 2; + ((PIXEL_TYPE *)d)[4] = palette[v >> 12]; + ((PIXEL_TYPE *)d)[5] = palette[(v >> 8) & 0xf]; + ((PIXEL_TYPE *)d)[6] = palette[(v >> 4) & 0xf]; + ((PIXEL_TYPE *)d)[7] = palette[(v >> 0) & 0xf]; + d += BPP * 8; + s += 4; + } +} + +#if BPP == 1 +#define PUT_PIXEL2(d, n, v) ((uint16_t *)d)[(n)] = (v) +#elif BPP == 2 +#define PUT_PIXEL2(d, n, v) ((uint32_t *)d)[(n)] = (v) +#else +#define PUT_PIXEL2(d, n, v) \ +((uint32_t *)d)[2*(n)] = ((uint32_t *)d)[2*(n)+1] = (v) +#endif + +/* + * 4 color mode, dup2 horizontal + */ +static void glue(vga_draw_line2d2_, DEPTH)(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[0x12] & 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 += BPP * 16; + s += 4; + } +} + +/* + * 16 color mode + */ +static void glue(vga_draw_line4_, DEPTH)(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[0x12] & 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; + ((PIXEL_TYPE *)d)[0] = palette[v >> 28]; + ((PIXEL_TYPE *)d)[1] = palette[(v >> 24) & 0xf]; + ((PIXEL_TYPE *)d)[2] = palette[(v >> 20) & 0xf]; + ((PIXEL_TYPE *)d)[3] = palette[(v >> 16) & 0xf]; + ((PIXEL_TYPE *)d)[4] = palette[(v >> 12) & 0xf]; + ((PIXEL_TYPE *)d)[5] = palette[(v >> 8) & 0xf]; + ((PIXEL_TYPE *)d)[6] = palette[(v >> 4) & 0xf]; + ((PIXEL_TYPE *)d)[7] = palette[(v >> 0) & 0xf]; + d += BPP * 8; + s += 4; + } +} + +/* + * 16 color mode, dup2 horizontal + */ +static void glue(vga_draw_line4d2_, DEPTH)(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[0x12] & 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 += BPP * 16; + s += 4; + } +} + +/* + * 256 color mode, double pixels + * + * XXX: add plane_mask support (never used in standard VGA modes) + */ +static void glue(vga_draw_line8d2_, DEPTH)(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 += BPP * 8; + s += 4; + } +} + +/* + * standard 256 color mode + * + * XXX: add plane_mask support (never used in standard VGA modes) + */ +static void glue(vga_draw_line8_, DEPTH)(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++) { + ((PIXEL_TYPE *)d)[0] = palette[s[0]]; + ((PIXEL_TYPE *)d)[1] = palette[s[1]]; + ((PIXEL_TYPE *)d)[2] = palette[s[2]]; + ((PIXEL_TYPE *)d)[3] = palette[s[3]]; + ((PIXEL_TYPE *)d)[4] = palette[s[4]]; + ((PIXEL_TYPE *)d)[5] = palette[s[5]]; + ((PIXEL_TYPE *)d)[6] = palette[s[6]]; + ((PIXEL_TYPE *)d)[7] = palette[s[7]]; + d += BPP * 8; + s += 8; + } +} + +void glue(vga_draw_cursor_line_, DEPTH)(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; +#if DEPTH == 8 + switch(b0 | (b1 << 1)) { + case 0: + break; + case 1: + d[0] ^= color_xor; + break; + case 2: + d[0] = color0; + break; + case 3: + d[0] = color1; + break; + } +#elif DEPTH == 16 + switch(b0 | (b1 << 1)) { + case 0: + break; + case 1: + ((uint16_t *)d)[0] ^= color_xor; + break; + case 2: + ((uint16_t *)d)[0] = color0; + break; + case 3: + ((uint16_t *)d)[0] = color1; + break; + } +#elif DEPTH == 32 + 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; + } +#else +#error unsupported depth +#endif + d += BPP; + } +} + +#endif /* DEPTH != 15 */ + + +/* XXX: optimize */ + +/* + * 15 bit color + */ +static void glue(vga_draw_line15_, PIXEL_NAME)(VGACommonState *s1, uint8_t *d, + const uint8_t *s, int width) +{ +#if DEPTH == 15 && defined(HOST_WORDS_BIGENDIAN) == defined(TARGET_WORDS_BIGENDIAN) + memcpy(d, s, width * 2); +#else + int w; + uint32_t v, r, g, b; + + w = width; + do { + v = lduw_raw((void *)s); + r = (v >> 7) & 0xf8; + g = (v >> 2) & 0xf8; + b = (v << 3) & 0xf8; + ((PIXEL_TYPE *)d)[0] = glue(rgb_to_pixel, PIXEL_NAME)(r, g, b); + s += 2; + d += BPP; + } while (--w != 0); +#endif +} + +/* + * 16 bit color + */ +static void glue(vga_draw_line16_, PIXEL_NAME)(VGACommonState *s1, uint8_t *d, + const uint8_t *s, int width) +{ +#if DEPTH == 16 && defined(HOST_WORDS_BIGENDIAN) == defined(TARGET_WORDS_BIGENDIAN) + memcpy(d, s, width * 2); +#else + int w; + uint32_t v, r, g, b; + + w = width; + do { + v = lduw_raw((void *)s); + r = (v >> 8) & 0xf8; + g = (v >> 3) & 0xfc; + b = (v << 3) & 0xf8; + ((PIXEL_TYPE *)d)[0] = glue(rgb_to_pixel, PIXEL_NAME)(r, g, b); + s += 2; + d += BPP; + } while (--w != 0); +#endif +} + +/* + * 24 bit color + */ +static void glue(vga_draw_line24_, PIXEL_NAME)(VGACommonState *s1, uint8_t *d, + const uint8_t *s, int width) +{ + int w; + uint32_t r, g, b; + + w = width; + do { +#if defined(TARGET_WORDS_BIGENDIAN) + r = s[0]; + g = s[1]; + b = s[2]; +#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 += 3; + d += BPP; + } while (--w != 0); +} + +/* + * 32 bit color + */ +static void glue(vga_draw_line32_, PIXEL_NAME)(VGACommonState *s1, uint8_t *d, + const uint8_t *s, int width) +{ +#if DEPTH == 32 && defined(HOST_WORDS_BIGENDIAN) == defined(TARGET_WORDS_BIGENDIAN) && !defined(BGR_FORMAT) + memcpy(d, s, width * 4); +#else + int w; + uint32_t r, g, b; + + w = width; + do { +#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 (--w != 0); +#endif +} + +#undef PUT_PIXEL2 +#undef DEPTH +#undef BPP +#undef PIXEL_TYPE +#undef PIXEL_NAME +#undef BGR_FORMAT diff --git a/hw/vhost.c b/hw/vhost.c new file mode 100644 index 0000000..6c194f0 --- /dev/null +++ b/hw/vhost.c @@ -0,0 +1,727 @@ +/* + * vhost support + * + * Copyright Red Hat, Inc. 2010 + * + * Authors: + * Michael S. Tsirkin + * + * This work is licensed under the terms of the GNU GPL, version 2. See + * the COPYING file in the top-level directory. + */ + +#include +#include "vhost.h" +#include "hw/hw.h" +#include "range.h" +#include + +static void vhost_dev_sync_region(struct vhost_dev *dev, + uint64_t mfirst, uint64_t mlast, + uint64_t rfirst, uint64_t rlast) +{ + uint64_t start = MAX(mfirst, rfirst); + uint64_t end = MIN(mlast, rlast); + vhost_log_chunk_t *from = dev->log + start / VHOST_LOG_CHUNK; + vhost_log_chunk_t *to = dev->log + end / VHOST_LOG_CHUNK + 1; + uint64_t addr = (start / VHOST_LOG_CHUNK) * VHOST_LOG_CHUNK; + + assert(end / VHOST_LOG_CHUNK < dev->log_size); + assert(start / VHOST_LOG_CHUNK < dev->log_size); + if (end < start) { + return; + } + for (;from < to; ++from) { + vhost_log_chunk_t log; + int bit; + /* 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 the barrier semantics of __sync + * builtins, but it's easier to use them than + * roll our own. */ + log = __sync_fetch_and_and(from, 0); + while ((bit = sizeof(log) > sizeof(int) ? + ffsll(log) : ffs(log))) { + ram_addr_t ram_addr; + bit -= 1; + ram_addr = cpu_get_physical_page_desc(addr + bit * VHOST_LOG_PAGE); + cpu_physical_memory_set_dirty(ram_addr); + log &= ~(0x1ull << bit); + } + addr += VHOST_LOG_CHUNK; + } +} + +static int vhost_client_sync_dirty_bitmap(CPUPhysMemoryClient *client, + target_phys_addr_t start_addr, + target_phys_addr_t end_addr) +{ + struct vhost_dev *dev = container_of(client, struct vhost_dev, client); + int i; + if (!dev->log_enabled || !dev->started) { + return 0; + } + for (i = 0; i < dev->mem->nregions; ++i) { + struct vhost_memory_region *reg = dev->mem->regions + i; + vhost_dev_sync_region(dev, 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, start_addr, end_addr, vq->used_phys, + range_get_last(vq->used_phys, vq->used_size)); + } + return 0; +} + +/* 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; + assert(to >= 0); + ++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 inline void vhost_dev_log_resize(struct vhost_dev* dev, uint64_t size) +{ + vhost_log_chunk_t *log; + uint64_t log_base; + int r; + if (size) { + log = qemu_mallocz(size * sizeof *log); + } else { + log = NULL; + } + log_base = (uint64_t)(unsigned long)log; + r = ioctl(dev->control, VHOST_SET_LOG_BASE, &log_base); + assert(r >= 0); + vhost_client_sync_dirty_bitmap(&dev->client, 0, + (target_phys_addr_t)~0x0ull); + if (dev->log) { + qemu_free(dev->log); + } + 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; + for (i = 0; i < dev->nvqs; ++i) { + struct vhost_virtqueue *vq = dev->vqs + i; + target_phys_addr_t 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); + return -ENOMEM; + } + if (p != vq->ring) { + fprintf(stderr, "Ring buffer relocated for ring %d\n", i); + return -EBUSY; + } + cpu_physical_memory_unmap(p, l, 0, 0); + } + return 0; +} + +static void vhost_client_set_memory(CPUPhysMemoryClient *client, + target_phys_addr_t start_addr, + ram_addr_t size, + ram_addr_t phys_offset) +{ + struct vhost_dev *dev = container_of(client, struct vhost_dev, client); + ram_addr_t flags = phys_offset & ~TARGET_PAGE_MASK; + int s = offsetof(struct vhost_memory, regions) + + (dev->mem->nregions + 1) * sizeof dev->mem->regions[0]; + uint64_t log_size; + int r; + dev->mem = qemu_realloc(dev->mem, s); + + assert(size); + + vhost_dev_unassign_memory(dev, start_addr, size); + if (flags == IO_MEM_RAM) { + /* Add given mapping, merging adjacent regions if any */ + vhost_dev_assign_memory(dev, start_addr, size, + (uintptr_t)qemu_get_ram_ptr(phys_offset)); + } else { + /* Remove old mapping for this memory, if any. */ + vhost_dev_unassign_memory(dev, start_addr, size); + } + + if (!dev->started) { + return; + } + + if (dev->started) { + r = vhost_verify_ring_mappings(dev, start_addr, size); + assert(r >= 0); + } + + if (!dev->log_enabled) { + r = ioctl(dev->control, VHOST_SET_MEM_TABLE, dev->mem); + assert(r >= 0); + 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 = ioctl(dev->control, 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); + } +} + +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 = ioctl(dev->control, 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 |= 0x1 << VHOST_F_LOG_ALL; + } + r = ioctl(dev->control, 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_client_migration_log(CPUPhysMemoryClient *client, + int enable) +{ + struct vhost_dev *dev = container_of(client, struct vhost_dev, client); + 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; + } + if (dev->log) { + qemu_free(dev->log); + } + 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 int vhost_virtqueue_init(struct vhost_dev *dev, + struct VirtIODevice *vdev, + struct vhost_virtqueue *vq, + unsigned idx) +{ + target_phys_addr_t s, l, a; + int r; + struct vhost_vring_file file = { + .index = idx, + }; + struct vhost_vring_state state = { + .index = idx, + }; + struct VirtQueue *vvq = virtio_get_queue(vdev, idx); + + if (!vdev->binding->set_host_notifier) { + fprintf(stderr, "binding does not support host notifiers\n"); + return -ENOSYS; + } + + vq->num = state.num = virtio_queue_get_num(vdev, idx); + r = ioctl(dev->control, VHOST_SET_VRING_NUM, &state); + if (r) { + return -errno; + } + + state.num = virtio_queue_get_last_avail_idx(vdev, idx); + r = ioctl(dev->control, VHOST_SET_VRING_BASE, &state); + 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, idx, dev->log_enabled); + if (r < 0) { + r = -errno; + goto fail_alloc; + } + r = vdev->binding->set_host_notifier(vdev->binding_opaque, idx, true); + if (r < 0) { + fprintf(stderr, "Error binding host notifier: %d\n", -r); + goto fail_host_notifier; + } + + file.fd = event_notifier_get_fd(virtio_queue_get_host_notifier(vvq)); + r = ioctl(dev->control, VHOST_SET_VRING_KICK, &file); + if (r) { + r = -errno; + goto fail_kick; + } + + file.fd = event_notifier_get_fd(virtio_queue_get_guest_notifier(vvq)); + r = ioctl(dev->control, VHOST_SET_VRING_CALL, &file); + if (r) { + r = -errno; + goto fail_call; + } + + return 0; + +fail_call: +fail_kick: + vdev->binding->set_host_notifier(vdev->binding_opaque, idx, false); +fail_host_notifier: +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_cleanup(struct vhost_dev *dev, + struct VirtIODevice *vdev, + struct vhost_virtqueue *vq, + unsigned idx) +{ + struct vhost_vring_state state = { + .index = idx, + }; + int r; + r = vdev->binding->set_host_notifier(vdev->binding_opaque, idx, false); + if (r < 0) { + fprintf(stderr, "vhost VQ %d host cleanup failed: %d\n", idx, r); + fflush(stderr); + } + assert (r >= 0); + r = ioctl(dev->control, 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); + 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)); +} + +int vhost_dev_init(struct vhost_dev *hdev, int devfd, bool force) +{ + uint64_t features; + int r; + if (devfd >= 0) { + hdev->control = devfd; + } else { + hdev->control = open("/dev/vhost-net", O_RDWR); + if (hdev->control < 0) { + return -errno; + } + } + r = ioctl(hdev->control, VHOST_SET_OWNER, NULL); + if (r < 0) { + goto fail; + } + + r = ioctl(hdev->control, VHOST_GET_FEATURES, &features); + if (r < 0) { + goto fail; + } + hdev->features = features; + + hdev->client.set_memory = vhost_client_set_memory; + hdev->client.sync_dirty_bitmap = vhost_client_sync_dirty_bitmap; + hdev->client.migration_log = vhost_client_migration_log; + hdev->mem = qemu_mallocz(offsetof(struct vhost_memory, regions)); + hdev->log = NULL; + hdev->log_size = 0; + hdev->log_enabled = false; + hdev->started = false; + cpu_register_phys_memory_client(&hdev->client); + hdev->force = force; + return 0; +fail: + r = -errno; + close(hdev->control); + return r; +} + +void vhost_dev_cleanup(struct vhost_dev *hdev) +{ + cpu_unregister_phys_memory_client(&hdev->client); + qemu_free(hdev->mem); + close(hdev->control); +} + +bool vhost_dev_query(struct vhost_dev *hdev, VirtIODevice *vdev) +{ + return !vdev->binding->query_guest_notifiers || + vdev->binding->query_guest_notifiers(vdev->binding_opaque) || + hdev->force; +} + +int vhost_dev_start(struct vhost_dev *hdev, VirtIODevice *vdev) +{ + int i, r; + if (!vdev->binding->set_guest_notifiers) { + fprintf(stderr, "binding does not support guest notifiers\n"); + r = -ENOSYS; + goto fail; + } + + r = vdev->binding->set_guest_notifiers(vdev->binding_opaque, true); + if (r < 0) { + fprintf(stderr, "Error binding guest notifier: %d\n", -r); + goto fail_notifiers; + } + + r = vhost_dev_set_features(hdev, hdev->log_enabled); + if (r < 0) { + goto fail_features; + } + r = ioctl(hdev->control, VHOST_SET_MEM_TABLE, hdev->mem); + if (r < 0) { + r = -errno; + goto fail_mem; + } + for (i = 0; i < hdev->nvqs; ++i) { + r = vhost_virtqueue_init(hdev, + vdev, + hdev->vqs + i, + i); + if (r < 0) { + goto fail_vq; + } + } + + if (hdev->log_enabled) { + hdev->log_size = vhost_get_log_size(hdev); + hdev->log = hdev->log_size ? + qemu_mallocz(hdev->log_size * sizeof *hdev->log) : NULL; + r = ioctl(hdev->control, VHOST_SET_LOG_BASE, + (uint64_t)(unsigned long)hdev->log); + if (r < 0) { + r = -errno; + goto fail_log; + } + } + + hdev->started = true; + + return 0; +fail_log: +fail_vq: + while (--i >= 0) { + vhost_virtqueue_cleanup(hdev, + vdev, + hdev->vqs + i, + i); + } +fail_mem: +fail_features: + vdev->binding->set_guest_notifiers(vdev->binding_opaque, false); +fail_notifiers: +fail: + return r; +} + +void vhost_dev_stop(struct vhost_dev *hdev, VirtIODevice *vdev) +{ + int i, r; + + for (i = 0; i < hdev->nvqs; ++i) { + vhost_virtqueue_cleanup(hdev, + vdev, + hdev->vqs + i, + i); + } + vhost_client_sync_dirty_bitmap(&hdev->client, 0, + (target_phys_addr_t)~0x0ull); + r = vdev->binding->set_guest_notifiers(vdev->binding_opaque, false); + if (r < 0) { + fprintf(stderr, "vhost guest notifier cleanup failed: %d\n", r); + fflush(stderr); + } + assert (r >= 0); + + hdev->started = false; + qemu_free(hdev->log); + hdev->log_size = 0; +} diff --git a/hw/vhost.h b/hw/vhost.h new file mode 100644 index 0000000..c8c595a --- /dev/null +++ b/hw/vhost.h @@ -0,0 +1,50 @@ +#ifndef VHOST_H +#define VHOST_H + +#include "hw/hw.h" +#include "hw/virtio.h" + +/* Generic structures common for any vhost based device. */ +struct vhost_virtqueue { + int kick; + int call; + void *desc; + void *avail; + void *used; + int num; + unsigned long long used_phys; + unsigned used_size; + void *ring; + unsigned long long ring_phys; + unsigned ring_size; +}; + +typedef unsigned long vhost_log_chunk_t; +#define VHOST_LOG_PAGE 0x1000 +#define VHOST_LOG_BITS (8 * sizeof(vhost_log_chunk_t)) +#define VHOST_LOG_CHUNK (VHOST_LOG_PAGE * VHOST_LOG_BITS) + +struct vhost_memory; +struct vhost_dev { + CPUPhysMemoryClient client; + int control; + struct vhost_memory *mem; + struct vhost_virtqueue *vqs; + int nvqs; + unsigned long long features; + unsigned long long acked_features; + unsigned long long backend_features; + bool started; + bool log_enabled; + vhost_log_chunk_t *log; + unsigned long long log_size; + bool force; +}; + +int vhost_dev_init(struct vhost_dev *hdev, int devfd, bool force); +void vhost_dev_cleanup(struct vhost_dev *hdev); +bool vhost_dev_query(struct vhost_dev *hdev, VirtIODevice *vdev); +int vhost_dev_start(struct vhost_dev *hdev, VirtIODevice *vdev); +void vhost_dev_stop(struct vhost_dev *hdev, VirtIODevice *vdev); + +#endif diff --git a/hw/vhost_net.c b/hw/vhost_net.c new file mode 100644 index 0000000..420e05f --- /dev/null +++ b/hw/vhost_net.c @@ -0,0 +1,229 @@ +/* + * vhost-net support + * + * Copyright Red Hat, Inc. 2010 + * + * Authors: + * Michael S. Tsirkin + * + * This work is licensed under the terms of the GNU GPL, version 2. See + * the COPYING file in the top-level directory. + */ + +#include "net.h" +#include "net/tap.h" + +#include "virtio-net.h" +#include "vhost_net.h" + +#include "config.h" + +#ifdef CONFIG_VHOST_NET +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "vhost.h" + +struct vhost_net { + struct vhost_dev dev; + struct vhost_virtqueue vqs[2]; + int backend; + VLANClientState *vc; +}; + +unsigned vhost_net_get_features(struct vhost_net *net, unsigned features) +{ + /* Clear features not supported by host kernel. */ + if (!(net->dev.features & (1 << VIRTIO_F_NOTIFY_ON_EMPTY))) { + features &= ~(1 << VIRTIO_F_NOTIFY_ON_EMPTY); + } + if (!(net->dev.features & (1 << VIRTIO_RING_F_INDIRECT_DESC))) { + features &= ~(1 << VIRTIO_RING_F_INDIRECT_DESC); + } + if (!(net->dev.features & (1 << VIRTIO_NET_F_MRG_RXBUF))) { + features &= ~(1 << VIRTIO_NET_F_MRG_RXBUF); + } + return features; +} + +void vhost_net_ack_features(struct vhost_net *net, unsigned features) +{ + net->dev.acked_features = net->dev.backend_features; + if (features & (1 << VIRTIO_F_NOTIFY_ON_EMPTY)) { + net->dev.acked_features |= (1 << VIRTIO_F_NOTIFY_ON_EMPTY); + } + if (features & (1 << VIRTIO_RING_F_INDIRECT_DESC)) { + net->dev.acked_features |= (1 << VIRTIO_RING_F_INDIRECT_DESC); + } + if (features & (1 << VIRTIO_NET_F_MRG_RXBUF)) { + net->dev.acked_features |= (1 << VIRTIO_NET_F_MRG_RXBUF); + } +} + +static int vhost_net_get_fd(VLANClientState *backend) +{ + switch (backend->info->type) { + case NET_CLIENT_TYPE_TAP: + return tap_get_fd(backend); + default: + fprintf(stderr, "vhost-net requires tap backend\n"); + return -EBADFD; + } +} + +struct vhost_net *vhost_net_init(VLANClientState *backend, int devfd, + bool force) +{ + int r; + struct vhost_net *net = qemu_malloc(sizeof *net); + if (!backend) { + fprintf(stderr, "vhost-net requires backend to be setup\n"); + goto fail; + } + r = vhost_net_get_fd(backend); + if (r < 0) { + goto fail; + } + net->vc = backend; + net->dev.backend_features = tap_has_vnet_hdr(backend) ? 0 : + (1 << VHOST_NET_F_VIRTIO_NET_HDR); + net->backend = r; + + r = vhost_dev_init(&net->dev, devfd, force); + if (r < 0) { + goto fail; + } + if (!tap_has_vnet_hdr_len(backend, + sizeof(struct virtio_net_hdr_mrg_rxbuf))) { + net->dev.features &= ~(1 << 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: + qemu_free(net); + return NULL; +} + +bool vhost_net_query(VHostNetState *net, VirtIODevice *dev) +{ + return vhost_dev_query(&net->dev, dev); +} + +int vhost_net_start(struct vhost_net *net, + VirtIODevice *dev) +{ + struct vhost_vring_file file = { }; + int r; + if (net->dev.acked_features & (1 << VIRTIO_NET_F_MRG_RXBUF)) { + tap_set_vnet_hdr_len(net->vc, + sizeof(struct virtio_net_hdr_mrg_rxbuf)); + } + + net->dev.nvqs = 2; + net->dev.vqs = net->vqs; + r = vhost_dev_start(&net->dev, dev); + if (r < 0) { + return r; + } + + net->vc->info->poll(net->vc, false); + qemu_set_fd_handler(net->backend, NULL, NULL, NULL); + file.fd = net->backend; + for (file.index = 0; file.index < net->dev.nvqs; ++file.index) { + r = ioctl(net->dev.control, VHOST_NET_SET_BACKEND, &file); + if (r < 0) { + r = -errno; + goto fail; + } + } + return 0; +fail: + file.fd = -1; + while (file.index-- > 0) { + int r = ioctl(net->dev.control, VHOST_NET_SET_BACKEND, &file); + assert(r >= 0); + } + net->vc->info->poll(net->vc, true); + vhost_dev_stop(&net->dev, dev); + if (net->dev.acked_features & (1 << VIRTIO_NET_F_MRG_RXBUF)) { + tap_set_vnet_hdr_len(net->vc, sizeof(struct virtio_net_hdr)); + } + return r; +} + +void vhost_net_stop(struct vhost_net *net, + VirtIODevice *dev) +{ + struct vhost_vring_file file = { .fd = -1 }; + + for (file.index = 0; file.index < net->dev.nvqs; ++file.index) { + int r = ioctl(net->dev.control, VHOST_NET_SET_BACKEND, &file); + assert(r >= 0); + } + net->vc->info->poll(net->vc, true); + vhost_dev_stop(&net->dev, dev); + if (net->dev.acked_features & (1 << VIRTIO_NET_F_MRG_RXBUF)) { + tap_set_vnet_hdr_len(net->vc, sizeof(struct virtio_net_hdr)); + } +} + +void vhost_net_cleanup(struct vhost_net *net) +{ + vhost_dev_cleanup(&net->dev); + if (net->dev.acked_features & (1 << VIRTIO_NET_F_MRG_RXBUF)) { + tap_set_vnet_hdr_len(net->vc, sizeof(struct virtio_net_hdr)); + } + qemu_free(net); +} +#else +struct vhost_net *vhost_net_init(VLANClientState *backend, int devfd, + bool force) +{ + return NULL; +} + +bool vhost_net_query(VHostNetState *net, VirtIODevice *dev) +{ + return false; +} + +int vhost_net_start(struct vhost_net *net, + VirtIODevice *dev) +{ + return -ENOSYS; +} +void vhost_net_stop(struct vhost_net *net, + VirtIODevice *dev) +{ +} + +void vhost_net_cleanup(struct vhost_net *net) +{ +} + +unsigned vhost_net_get_features(struct vhost_net *net, unsigned features) +{ + return features; +} +void vhost_net_ack_features(struct vhost_net *net, unsigned features) +{ +} +#endif diff --git a/hw/vhost_net.h b/hw/vhost_net.h new file mode 100644 index 0000000..91e40b1 --- /dev/null +++ b/hw/vhost_net.h @@ -0,0 +1,20 @@ +#ifndef VHOST_NET_H +#define VHOST_NET_H + +#include "net.h" + +struct vhost_net; +typedef struct vhost_net VHostNetState; + +VHostNetState *vhost_net_init(VLANClientState *backend, int devfd, bool force); + +bool vhost_net_query(VHostNetState *net, VirtIODevice *dev); +int vhost_net_start(VHostNetState *net, VirtIODevice *dev); +void vhost_net_stop(VHostNetState *net, VirtIODevice *dev); + +void vhost_net_cleanup(VHostNetState *net); + +unsigned vhost_net_get_features(VHostNetState *net, unsigned features); +void vhost_net_ack_features(VHostNetState *net, unsigned features); + +#endif diff --git a/hw/virtex_ml507.c b/hw/virtex_ml507.c new file mode 100644 index 0000000..fa60515 --- /dev/null +++ b/hw/virtex_ml507.c @@ -0,0 +1,276 @@ +/* + * 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 "sysbus.h" +#include "hw.h" +#include "pc.h" +#include "net.h" +#include "flash.h" +#include "sysemu.h" +#include "devices.h" +#include "boards.h" +#include "device_tree.h" +#include "loader.h" +#include "elf.h" +#include "qemu-log.h" + +#include "ppc.h" +#include "ppc4xx.h" +#include "ppc440.h" +#include "ppc405.h" + +#include "blockdev.h" +#include "xilinx.h" + +#define EPAPR_MAGIC (0x45504150) +#define FLASH_SIZE (16 * 1024 * 1024) + +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(CPUState *env, + target_ulong va, + target_phys_addr_t pa) +{ + ppcemb_tlb_t *tlb = &env->tlb[0].tlbe; + + tlb->attr = 0; + tlb->prot = PAGE_VALID | ((PAGE_READ | PAGE_WRITE | PAGE_EXEC) << 4); + tlb->size = 1 << 31; /* up to 0x80000000 */ + tlb->EPN = va & TARGET_PAGE_MASK; + tlb->RPN = pa & TARGET_PAGE_MASK; + tlb->PID = 0; + + tlb = &env->tlb[1].tlbe; + tlb->attr = 0; + tlb->prot = PAGE_VALID | ((PAGE_READ | PAGE_WRITE | PAGE_EXEC) << 4); + tlb->size = 1 << 31; /* up to 0xffffffff */ + tlb->EPN = 0x80000000 & TARGET_PAGE_MASK; + tlb->RPN = 0x80000000 & TARGET_PAGE_MASK; + tlb->PID = 0; +} + +static CPUState *ppc440_init_xilinx(ram_addr_t *ram_size, + int do_init, + const char *cpu_model, + clk_setup_t *cpu_clk, clk_setup_t *tb_clk, + uint32_t sysclk) +{ + CPUState *env; + qemu_irq *irqs; + + env = cpu_init(cpu_model); + if (!env) { + fprintf(stderr, "Unable to initialize CPU!\n"); + exit(1); + } + + 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_emb_timers_init(env, sysclk, PPC_INTERRUPT_DECR); + tb_clk->opaque = env; + + ppc_dcr_init(env, NULL, NULL); + + /* interrupt controller */ + irqs = qemu_mallocz(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 env; +} + +static void main_cpu_reset(void *opaque) +{ + CPUState *env = opaque; + struct boot_info *bi = env->load_info; + + cpu_reset(env); + /* 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(target_phys_addr_t addr, + uint32_t ramsize, + target_phys_addr_t initrd_base, + target_phys_addr_t initrd_size, + const char *kernel_cmdline) +{ + char *path; + int fdt_size; +#ifdef CONFIG_FDT + void *fdt; + int r; + + /* 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); + qemu_free(path); + } + if (!fdt) { + return 0; + } + } + + r = qemu_devtree_setprop_string(fdt, "/chosen", "bootargs", kernel_cmdline); + if (r < 0) + fprintf(stderr, "couldn't set /chosen/bootargs\n"); + cpu_physical_memory_write (addr, (void *)fdt, fdt_size); +#else + /* We lack libfdt so we cannot manipulate the fdt. Just pass on the blob + to the kernel. */ + fdt_size = load_image_targphys("ppc.dtb", addr, 0x10000); + if (fdt_size < 0) { + path = qemu_find_file(QEMU_FILE_TYPE_BIOS, BINARY_DEVICE_TREE_FILE); + if (path) { + fdt_size = load_image_targphys(path, addr, 0x10000); + qemu_free(path); + } + } + + if (kernel_cmdline) { + fprintf(stderr, + "Warning: missing libfdt, cannot pass cmdline to kernel!\n"); + } +#endif + return fdt_size; +} + +static void virtex_init(ram_addr_t ram_size, + const char *boot_device, + const char *kernel_filename, + const char *kernel_cmdline, + const char *initrd_filename, const char *cpu_model) +{ + DeviceState *dev; + CPUState *env; + target_phys_addr_t ram_base = 0; + DriveInfo *dinfo; + ram_addr_t phys_ram; + ram_addr_t phys_flash; + qemu_irq irq[32], *cpu_irq; + clk_setup_t clk_setup[7]; + int kernel_size; + int i; + + /* init CPUs */ + if (cpu_model == NULL) { + cpu_model = "440-Xilinx"; + } + + memset(clk_setup, 0, sizeof(clk_setup)); + env = ppc440_init_xilinx(&ram_size, 1, cpu_model, &clk_setup[0], + &clk_setup[1], 400000000); + qemu_register_reset(main_cpu_reset, env); + + phys_ram = qemu_ram_alloc(NULL, "ram", ram_size); + cpu_register_physical_memory(ram_base, ram_size, phys_ram | IO_MEM_RAM); + + phys_flash = qemu_ram_alloc(NULL, "virtex.flash", FLASH_SIZE); + dinfo = drive_get(IF_PFLASH, 0, 0); + pflash_cfi01_register(0xfc000000, phys_flash, + dinfo ? dinfo->bdrv : NULL, (64 * 1024), + FLASH_SIZE >> 16, + 1, 0x89, 0x18, 0x0000, 0x0, 1); + + cpu_irq = (qemu_irq *) &env->irq_inputs[PPC40x_INPUT_INT]; + dev = xilinx_intc_create(0x81800000, cpu_irq[0], 0); + for (i = 0; i < 32; i++) { + irq[i] = qdev_get_gpio_in(dev, i); + } + + serial_mm_init(0x83e01003ULL, 2, irq[9], 115200, serial_hds[0], 1, 0); + + /* 2 timers at irq 2 @ 62 Mhz. */ + xilinx_timer_create(0x83c00000, irq[3], 2, 62 * 1000000); + + if (kernel_filename) { + uint64_t entry, low, high; + target_phys_addr_t 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; + + /* Provide a device-tree. */ + boot_info.fdt = high + (8192 * 2); + boot_info.fdt &= ~8191; + xilinx_load_device_tree(boot_info.fdt, ram_size, 0, 0, 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/virtio-9p-debug.c b/hw/virtio-9p-debug.c new file mode 100644 index 0000000..6b18842 --- /dev/null +++ b/hw/virtio-9p-debug.c @@ -0,0 +1,645 @@ +/* + * Virtio 9p PDU debug + * + * Copyright IBM, Corp. 2010 + * + * Authors: + * Anthony Liguori + * + * This work is licensed under the terms of the GNU GPL, version 2. See + * the COPYING file in the top-level directory. + * + */ +#include "virtio.h" +#include "pc.h" +#include "virtio-9p.h" +#include "virtio-9p-debug.h" + +#define BUG_ON(cond) assert(!(cond)) + +static FILE *llogfile; + +static struct iovec *get_sg(V9fsPDU *pdu, int rx) +{ + if (rx) { + return pdu->elem.in_sg; + } + return pdu->elem.out_sg; +} + +static int get_sg_count(V9fsPDU *pdu, int rx) +{ + if (rx) { + return pdu->elem.in_num; + } + return pdu->elem.out_num; + +} + +static void pprint_int8(V9fsPDU *pdu, int rx, size_t *offsetp, + const char *name) +{ + size_t copied; + int count = get_sg_count(pdu, rx); + size_t offset = *offsetp; + struct iovec *sg = get_sg(pdu, rx); + int8_t value; + + copied = do_pdu_unpack(&value, sg, count, offset, sizeof(value)); + + BUG_ON(copied != sizeof(value)); + offset += sizeof(value); + fprintf(llogfile, "%s=0x%x", name, value); + *offsetp = offset; +} + +static void pprint_int16(V9fsPDU *pdu, int rx, size_t *offsetp, + const char *name) +{ + size_t copied; + int count = get_sg_count(pdu, rx); + struct iovec *sg = get_sg(pdu, rx); + size_t offset = *offsetp; + int16_t value; + + + copied = do_pdu_unpack(&value, sg, count, offset, sizeof(value)); + + BUG_ON(copied != sizeof(value)); + offset += sizeof(value); + fprintf(llogfile, "%s=0x%x", name, value); + *offsetp = offset; +} + +static void pprint_int32(V9fsPDU *pdu, int rx, size_t *offsetp, + const char *name) +{ + size_t copied; + int count = get_sg_count(pdu, rx); + struct iovec *sg = get_sg(pdu, rx); + size_t offset = *offsetp; + int32_t value; + + + copied = do_pdu_unpack(&value, sg, count, offset, sizeof(value)); + + BUG_ON(copied != sizeof(value)); + offset += sizeof(value); + fprintf(llogfile, "%s=0x%x", name, value); + *offsetp = offset; +} + +static void pprint_int64(V9fsPDU *pdu, int rx, size_t *offsetp, + const char *name) +{ + size_t copied; + int count = get_sg_count(pdu, rx); + struct iovec *sg = get_sg(pdu, rx); + size_t offset = *offsetp; + int64_t value; + + + copied = do_pdu_unpack(&value, sg, count, offset, sizeof(value)); + + BUG_ON(copied != sizeof(value)); + offset += sizeof(value); + fprintf(llogfile, "%s=0x%" PRIx64, name, value); + *offsetp = offset; +} + +static void pprint_str(V9fsPDU *pdu, int rx, size_t *offsetp, const char *name) +{ + int sg_count = get_sg_count(pdu, rx); + struct iovec *sg = get_sg(pdu, rx); + size_t offset = *offsetp; + uint16_t tmp_size, size; + size_t result; + size_t copied = 0; + int i = 0; + + /* get the size */ + copied = do_pdu_unpack(&tmp_size, sg, sg_count, offset, sizeof(tmp_size)); + BUG_ON(copied != sizeof(tmp_size)); + size = le16_to_cpupu(&tmp_size); + offset += copied; + + fprintf(llogfile, "%s=", name); + for (i = 0; size && i < sg_count; i++) { + size_t len; + if (offset >= sg[i].iov_len) { + /* skip this sg */ + offset -= sg[i].iov_len; + continue; + } else { + len = MIN(sg[i].iov_len - offset, size); + result = fwrite(sg[i].iov_base + offset, 1, len, llogfile); + BUG_ON(result != len); + size -= len; + copied += len; + if (size) { + offset = 0; + continue; + } + } + } + *offsetp += copied; +} + +static void pprint_qid(V9fsPDU *pdu, int rx, size_t *offsetp, const char *name) +{ + fprintf(llogfile, "%s={", name); + pprint_int8(pdu, rx, offsetp, "type"); + pprint_int32(pdu, rx, offsetp, ", version"); + pprint_int64(pdu, rx, offsetp, ", path"); + fprintf(llogfile, "}"); +} + +static void pprint_stat(V9fsPDU *pdu, int rx, size_t *offsetp, const char *name) +{ + fprintf(llogfile, "%s={", name); + pprint_int16(pdu, rx, offsetp, "size"); + pprint_int16(pdu, rx, offsetp, ", type"); + pprint_int32(pdu, rx, offsetp, ", dev"); + pprint_qid(pdu, rx, offsetp, ", qid"); + pprint_int32(pdu, rx, offsetp, ", mode"); + pprint_int32(pdu, rx, offsetp, ", atime"); + pprint_int32(pdu, rx, offsetp, ", mtime"); + pprint_int64(pdu, rx, offsetp, ", length"); + pprint_str(pdu, rx, offsetp, ", name"); + pprint_str(pdu, rx, offsetp, ", uid"); + pprint_str(pdu, rx, offsetp, ", gid"); + pprint_str(pdu, rx, offsetp, ", muid"); + pprint_str(pdu, rx, offsetp, ", extension"); + pprint_int32(pdu, rx, offsetp, ", uid"); + pprint_int32(pdu, rx, offsetp, ", gid"); + pprint_int32(pdu, rx, offsetp, ", muid"); + fprintf(llogfile, "}"); +} + +static void pprint_stat_dotl(V9fsPDU *pdu, int rx, size_t *offsetp, + const char *name) +{ + fprintf(llogfile, "%s={", name); + pprint_qid(pdu, rx, offsetp, "qid"); + pprint_int32(pdu, rx, offsetp, ", st_mode"); + pprint_int64(pdu, rx, offsetp, ", st_nlink"); + pprint_int32(pdu, rx, offsetp, ", st_uid"); + pprint_int32(pdu, rx, offsetp, ", st_gid"); + pprint_int64(pdu, rx, offsetp, ", st_rdev"); + pprint_int64(pdu, rx, offsetp, ", st_size"); + pprint_int64(pdu, rx, offsetp, ", st_blksize"); + pprint_int64(pdu, rx, offsetp, ", st_blocks"); + pprint_int64(pdu, rx, offsetp, ", atime"); + pprint_int64(pdu, rx, offsetp, ", atime_nsec"); + pprint_int64(pdu, rx, offsetp, ", mtime"); + pprint_int64(pdu, rx, offsetp, ", mtime_nsec"); + pprint_int64(pdu, rx, offsetp, ", ctime"); + pprint_int64(pdu, rx, offsetp, ", ctime_nsec"); + fprintf(llogfile, "}"); +} + + + +static void pprint_strs(V9fsPDU *pdu, int rx, size_t *offsetp, const char *name) +{ + int sg_count = get_sg_count(pdu, rx); + struct iovec *sg = get_sg(pdu, rx); + size_t offset = *offsetp; + uint16_t tmp_count, count, i; + size_t copied = 0; + + fprintf(llogfile, "%s={", name); + + /* Get the count */ + copied = do_pdu_unpack(&tmp_count, sg, sg_count, offset, sizeof(tmp_count)); + BUG_ON(copied != sizeof(tmp_count)); + count = le16_to_cpupu(&tmp_count); + offset += copied; + + for (i = 0; i < count; i++) { + char str[512]; + if (i) { + fprintf(llogfile, ", "); + } + snprintf(str, sizeof(str), "[%d]", i); + pprint_str(pdu, rx, &offset, str); + } + + fprintf(llogfile, "}"); + + *offsetp = offset; +} + +static void pprint_qids(V9fsPDU *pdu, int rx, size_t *offsetp, const char *name) +{ + int sg_count = get_sg_count(pdu, rx); + struct iovec *sg = get_sg(pdu, rx); + size_t offset = *offsetp; + uint16_t tmp_count, count, i; + size_t copied = 0; + + fprintf(llogfile, "%s={", name); + + copied = do_pdu_unpack(&tmp_count, sg, sg_count, offset, sizeof(tmp_count)); + BUG_ON(copied != sizeof(tmp_count)); + count = le16_to_cpupu(&tmp_count); + offset += copied; + + for (i = 0; i < count; i++) { + char str[512]; + if (i) { + fprintf(llogfile, ", "); + } + snprintf(str, sizeof(str), "[%d]", i); + pprint_qid(pdu, rx, &offset, str); + } + + fprintf(llogfile, "}"); + + *offsetp = offset; +} + +static void pprint_sg(V9fsPDU *pdu, int rx, size_t *offsetp, const char *name) +{ + struct iovec *sg = get_sg(pdu, rx); + unsigned int count; + int i; + + if (rx) { + count = pdu->elem.in_num; + } else { + count = pdu->elem.out_num; + } + + fprintf(llogfile, "%s={", name); + for (i = 0; i < count; i++) { + if (i) { + fprintf(llogfile, ", "); + } + fprintf(llogfile, "(%p, 0x%zx)", sg[i].iov_base, sg[i].iov_len); + } + fprintf(llogfile, "}"); +} + +/* FIXME: read from a directory fid returns serialized stat_t's */ +#ifdef DEBUG_DATA +static void pprint_data(V9fsPDU *pdu, int rx, size_t *offsetp, const char *name) +{ + struct iovec *sg = get_sg(pdu, rx); + size_t offset = *offsetp; + unsigned int count; + int32_t size; + int total, i, j; + ssize_t len; + + if (rx) { + count = pdu->elem.in_num; + } else + count = pdu->elem.out_num; + } + + BUG_ON((offset + sizeof(size)) > sg[0].iov_len); + + memcpy(&size, sg[0].iov_base + offset, sizeof(size)); + offset += sizeof(size); + + fprintf(llogfile, "size: %x\n", size); + + sg[0].iov_base += 11; /* skip header */ + sg[0].iov_len -= 11; + + total = 0; + for (i = 0; i < count; i++) { + total += sg[i].iov_len; + if (total >= size) { + /* trim sg list so writev does the right thing */ + sg[i].iov_len -= (total - size); + i++; + break; + } + } + + fprintf(llogfile, "%s={\"", name); + fflush(llogfile); + for (j = 0; j < i; j++) { + if (j) { + fprintf(llogfile, "\", \""); + fflush(llogfile); + } + + do { + len = writev(fileno(llogfile), &sg[j], 1); + } while (len == -1 && errno == EINTR); + fprintf(llogfile, "len == %ld: %m\n", len); + BUG_ON(len != sg[j].iov_len); + } + fprintf(llogfile, "\"}"); + + sg[0].iov_base -= 11; + sg[0].iov_len += 11; + +} +#endif + +void pprint_pdu(V9fsPDU *pdu) +{ + size_t offset = 7; + + if (llogfile == NULL) { + llogfile = fopen("/tmp/pdu.log", "w"); + } + + BUG_ON(!llogfile); + + switch (pdu->id) { + case P9_TREADDIR: + fprintf(llogfile, "TREADDIR: ("); + pprint_int32(pdu, 0, &offset, "fid"); + pprint_int64(pdu, 0, &offset, ", initial offset"); + pprint_int32(pdu, 0, &offset, ", max count"); + break; + case P9_RREADDIR: + fprintf(llogfile, "RREADDIR: ("); + pprint_int32(pdu, 1, &offset, "count"); +#ifdef DEBUG_DATA + pprint_data(pdu, 1, &offset, ", data"); +#endif + break; + case P9_TMKDIR: + fprintf(llogfile, "TMKDIR: ("); + pprint_int32(pdu, 0, &offset, "fid"); + pprint_str(pdu, 0, &offset, "name"); + pprint_int32(pdu, 0, &offset, "mode"); + pprint_int32(pdu, 0, &offset, "gid"); + break; + case P9_RMKDIR: + fprintf(llogfile, "RMKDIR: ("); + pprint_qid(pdu, 0, &offset, "qid"); + break; + case P9_TVERSION: + fprintf(llogfile, "TVERSION: ("); + pprint_int32(pdu, 0, &offset, "msize"); + pprint_str(pdu, 0, &offset, ", version"); + break; + case P9_RVERSION: + fprintf(llogfile, "RVERSION: ("); + pprint_int32(pdu, 1, &offset, "msize"); + pprint_str(pdu, 1, &offset, ", version"); + break; + case P9_TGETATTR: + fprintf(llogfile, "TGETATTR: ("); + pprint_int32(pdu, 0, &offset, "fid"); + break; + case P9_RGETATTR: + fprintf(llogfile, "RGETATTR: ("); + pprint_stat_dotl(pdu, 1, &offset, "getattr"); + break; + case P9_TAUTH: + fprintf(llogfile, "TAUTH: ("); + pprint_int32(pdu, 0, &offset, "afid"); + pprint_str(pdu, 0, &offset, ", uname"); + pprint_str(pdu, 0, &offset, ", aname"); + pprint_int32(pdu, 0, &offset, ", n_uname"); + break; + case P9_RAUTH: + fprintf(llogfile, "RAUTH: ("); + pprint_qid(pdu, 1, &offset, "qid"); + break; + case P9_TATTACH: + fprintf(llogfile, "TATTACH: ("); + pprint_int32(pdu, 0, &offset, "fid"); + pprint_int32(pdu, 0, &offset, ", afid"); + pprint_str(pdu, 0, &offset, ", uname"); + pprint_str(pdu, 0, &offset, ", aname"); + pprint_int32(pdu, 0, &offset, ", n_uname"); + break; + case P9_RATTACH: + fprintf(llogfile, "RATTACH: ("); + pprint_qid(pdu, 1, &offset, "qid"); + break; + case P9_TERROR: + fprintf(llogfile, "TERROR: ("); + break; + case P9_RERROR: + fprintf(llogfile, "RERROR: ("); + pprint_str(pdu, 1, &offset, "ename"); + pprint_int32(pdu, 1, &offset, ", ecode"); + break; + case P9_TFLUSH: + fprintf(llogfile, "TFLUSH: ("); + pprint_int16(pdu, 0, &offset, "oldtag"); + break; + case P9_RFLUSH: + fprintf(llogfile, "RFLUSH: ("); + break; + case P9_TWALK: + fprintf(llogfile, "TWALK: ("); + pprint_int32(pdu, 0, &offset, "fid"); + pprint_int32(pdu, 0, &offset, ", newfid"); + pprint_strs(pdu, 0, &offset, ", wnames"); + break; + case P9_RWALK: + fprintf(llogfile, "RWALK: ("); + pprint_qids(pdu, 1, &offset, "wqids"); + break; + case P9_TOPEN: + fprintf(llogfile, "TOPEN: ("); + pprint_int32(pdu, 0, &offset, "fid"); + pprint_int8(pdu, 0, &offset, ", mode"); + break; + case P9_ROPEN: + fprintf(llogfile, "ROPEN: ("); + pprint_qid(pdu, 1, &offset, "qid"); + pprint_int32(pdu, 1, &offset, ", iounit"); + break; + case P9_TCREATE: + fprintf(llogfile, "TCREATE: ("); + pprint_int32(pdu, 0, &offset, "fid"); + pprint_str(pdu, 0, &offset, ", name"); + pprint_int32(pdu, 0, &offset, ", perm"); + pprint_int8(pdu, 0, &offset, ", mode"); + pprint_str(pdu, 0, &offset, ", extension"); + break; + case P9_RCREATE: + fprintf(llogfile, "RCREATE: ("); + pprint_qid(pdu, 1, &offset, "qid"); + pprint_int32(pdu, 1, &offset, ", iounit"); + break; + case P9_TSYMLINK: + fprintf(llogfile, "TSYMLINK: ("); + pprint_int32(pdu, 0, &offset, "fid"); + pprint_str(pdu, 0, &offset, ", name"); + pprint_str(pdu, 0, &offset, ", symname"); + pprint_int32(pdu, 0, &offset, ", gid"); + break; + case P9_RSYMLINK: + fprintf(llogfile, "RSYMLINK: ("); + pprint_qid(pdu, 1, &offset, "qid"); + break; + case P9_TLCREATE: + fprintf(llogfile, "TLCREATE: ("); + pprint_int32(pdu, 0, &offset, "dfid"); + pprint_str(pdu, 0, &offset, ", name"); + pprint_int32(pdu, 0, &offset, ", flags"); + pprint_int32(pdu, 0, &offset, ", mode"); + pprint_int32(pdu, 0, &offset, ", gid"); + break; + case P9_RLCREATE: + fprintf(llogfile, "RLCREATE: ("); + pprint_qid(pdu, 1, &offset, "qid"); + pprint_int32(pdu, 1, &offset, ", iounit"); + break; + case P9_TMKNOD: + fprintf(llogfile, "TMKNOD: ("); + pprint_int32(pdu, 0, &offset, "fid"); + pprint_str(pdu, 0, &offset, "name"); + pprint_int32(pdu, 0, &offset, "mode"); + pprint_int32(pdu, 0, &offset, "major"); + pprint_int32(pdu, 0, &offset, "minor"); + pprint_int32(pdu, 0, &offset, "gid"); + break; + case P9_RMKNOD: + fprintf(llogfile, "RMKNOD: )"); + pprint_qid(pdu, 0, &offset, "qid"); + break; + case P9_TREADLINK: + fprintf(llogfile, "TREADLINK: ("); + pprint_int32(pdu, 0, &offset, "fid"); + break; + case P9_RREADLINK: + fprintf(llogfile, "RREADLINK: ("); + pprint_str(pdu, 0, &offset, "target"); + break; + case P9_TREAD: + fprintf(llogfile, "TREAD: ("); + pprint_int32(pdu, 0, &offset, "fid"); + pprint_int64(pdu, 0, &offset, ", offset"); + pprint_int32(pdu, 0, &offset, ", count"); + pprint_sg(pdu, 0, &offset, ", sg"); + break; + case P9_RREAD: + fprintf(llogfile, "RREAD: ("); + pprint_int32(pdu, 1, &offset, "count"); + pprint_sg(pdu, 1, &offset, ", sg"); + offset = 7; +#ifdef DEBUG_DATA + pprint_data(pdu, 1, &offset, ", data"); +#endif + break; + case P9_TWRITE: + fprintf(llogfile, "TWRITE: ("); + pprint_int32(pdu, 0, &offset, "fid"); + pprint_int64(pdu, 0, &offset, ", offset"); + pprint_int32(pdu, 0, &offset, ", count"); + break; + case P9_RWRITE: + fprintf(llogfile, "RWRITE: ("); + pprint_int32(pdu, 1, &offset, "count"); + break; + case P9_TCLUNK: + fprintf(llogfile, "TCLUNK: ("); + pprint_int32(pdu, 0, &offset, "fid"); + break; + case P9_RCLUNK: + fprintf(llogfile, "RCLUNK: ("); + break; + case P9_TFSYNC: + fprintf(llogfile, "TFSYNC: ("); + pprint_int32(pdu, 0, &offset, "fid"); + break; + case P9_RFSYNC: + fprintf(llogfile, "RFSYNC: ("); + break; + case P9_TLINK: + fprintf(llogfile, "TLINK: ("); + pprint_int32(pdu, 0, &offset, "dfid"); + pprint_int32(pdu, 0, &offset, ", fid"); + pprint_str(pdu, 0, &offset, ", newpath"); + break; + case P9_RLINK: + fprintf(llogfile, "RLINK: ("); + break; + case P9_TREMOVE: + fprintf(llogfile, "TREMOVE: ("); + pprint_int32(pdu, 0, &offset, "fid"); + break; + case P9_RREMOVE: + fprintf(llogfile, "RREMOVE: ("); + break; + case P9_TSTAT: + fprintf(llogfile, "TSTAT: ("); + pprint_int32(pdu, 0, &offset, "fid"); + break; + case P9_RSTAT: + fprintf(llogfile, "RSTAT: ("); + offset += 2; /* ignored */ + pprint_stat(pdu, 1, &offset, "stat"); + break; + case P9_TWSTAT: + fprintf(llogfile, "TWSTAT: ("); + pprint_int32(pdu, 0, &offset, "fid"); + offset += 2; /* ignored */ + pprint_stat(pdu, 0, &offset, ", stat"); + break; + case P9_RWSTAT: + fprintf(llogfile, "RWSTAT: ("); + break; + case P9_TXATTRWALK: + fprintf(llogfile, "TXATTRWALK: ("); + pprint_int32(pdu, 0, &offset, "fid"); + pprint_int32(pdu, 0, &offset, ", newfid"); + pprint_str(pdu, 0, &offset, ", xattr name"); + break; + case P9_RXATTRWALK: + fprintf(llogfile, "RXATTRWALK: ("); + pprint_int64(pdu, 1, &offset, "xattrsize"); + case P9_TXATTRCREATE: + fprintf(llogfile, "TXATTRCREATE: ("); + pprint_int32(pdu, 0, &offset, "fid"); + pprint_str(pdu, 0, &offset, ", name"); + pprint_int64(pdu, 0, &offset, ", xattrsize"); + pprint_int32(pdu, 0, &offset, ", flags"); + break; + case P9_RXATTRCREATE: + fprintf(llogfile, "RXATTRCREATE: ("); + break; + case P9_TLOCK: + fprintf(llogfile, "TLOCK: ("); + pprint_int32(pdu, 0, &offset, "fid"); + pprint_int8(pdu, 0, &offset, ", type"); + pprint_int32(pdu, 0, &offset, ", flags"); + pprint_int64(pdu, 0, &offset, ", start"); + pprint_int64(pdu, 0, &offset, ", length"); + pprint_int32(pdu, 0, &offset, ", proc_id"); + pprint_str(pdu, 0, &offset, ", client_id"); + break; + case P9_RLOCK: + fprintf(llogfile, "RLOCK: ("); + pprint_int8(pdu, 0, &offset, "status"); + break; + case P9_TGETLOCK: + fprintf(llogfile, "TGETLOCK: ("); + pprint_int32(pdu, 0, &offset, "fid"); + pprint_int8(pdu, 0, &offset, ", type"); + pprint_int64(pdu, 0, &offset, ", start"); + pprint_int64(pdu, 0, &offset, ", length"); + pprint_int32(pdu, 0, &offset, ", proc_id"); + pprint_str(pdu, 0, &offset, ", client_id"); + break; + case P9_RGETLOCK: + fprintf(llogfile, "RGETLOCK: ("); + pprint_int8(pdu, 0, &offset, "type"); + pprint_int64(pdu, 0, &offset, ", start"); + pprint_int64(pdu, 0, &offset, ", length"); + pprint_int32(pdu, 0, &offset, ", proc_id"); + pprint_str(pdu, 0, &offset, ", client_id"); + break; + default: + fprintf(llogfile, "unknown(%d): (", pdu->id); + break; + } + + fprintf(llogfile, ")\n"); + /* Flush the log message out */ + fflush(llogfile); +} diff --git a/hw/virtio-9p-debug.h b/hw/virtio-9p-debug.h new file mode 100644 index 0000000..d9a2491 --- /dev/null +++ b/hw/virtio-9p-debug.h @@ -0,0 +1,6 @@ +#ifndef _QEMU_VIRTIO_9P_DEBUG_H +#define _QEMU_VIRTIO_9P_DEBUG_H + +void pprint_pdu(V9fsPDU *pdu); + +#endif diff --git a/hw/virtio-9p-local.c b/hw/virtio-9p-local.c new file mode 100644 index 0000000..a8e7525 --- /dev/null +++ b/hw/virtio-9p-local.c @@ -0,0 +1,563 @@ +/* + * Virtio 9p Posix callback + * + * Copyright IBM, Corp. 2010 + * + * Authors: + * Anthony Liguori + * + * This work is licensed under the terms of the GNU GPL, version 2. See + * the COPYING file in the top-level directory. + * + */ +#include "virtio.h" +#include "virtio-9p.h" +#include "virtio-9p-xattr.h" +#include +#include +#include +#include +#include +#include + + +static int local_lstat(FsContext *fs_ctx, const char *path, struct stat *stbuf) +{ + int err; + err = lstat(rpath(fs_ctx, path), stbuf); + if (err) { + return err; + } + if (fs_ctx->fs_sm == 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(rpath(fs_ctx, path), "user.virtfs.uid", &tmp_uid, + sizeof(uid_t)) > 0) { + stbuf->st_uid = tmp_uid; + } + if (getxattr(rpath(fs_ctx, path), "user.virtfs.gid", &tmp_gid, + sizeof(gid_t)) > 0) { + stbuf->st_gid = tmp_gid; + } + if (getxattr(rpath(fs_ctx, path), "user.virtfs.mode", &tmp_mode, + sizeof(mode_t)) > 0) { + stbuf->st_mode = tmp_mode; + } + if (getxattr(rpath(fs_ctx, path), "user.virtfs.rdev", &tmp_dev, + sizeof(dev_t)) > 0) { + stbuf->st_rdev = tmp_dev; + } + } + return err; +} + +static int local_set_xattr(const char *path, FsCred *credp) +{ + int err; + if (credp->fc_uid != -1) { + err = setxattr(path, "user.virtfs.uid", &credp->fc_uid, sizeof(uid_t), + 0); + if (err) { + return err; + } + } + if (credp->fc_gid != -1) { + err = setxattr(path, "user.virtfs.gid", &credp->fc_gid, sizeof(gid_t), + 0); + if (err) { + return err; + } + } + if (credp->fc_mode != -1) { + err = setxattr(path, "user.virtfs.mode", &credp->fc_mode, + sizeof(mode_t), 0); + if (err) { + return err; + } + } + if (credp->fc_rdev != -1) { + err = setxattr(path, "user.virtfs.rdev", &credp->fc_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) +{ + if (chmod(rpath(fs_ctx, path), credp->fc_mode & 07777) < 0) { + return -1; + } + if (lchown(rpath(fs_ctx, path), 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->fs_sm != SM_NONE) { + return -1; + } + } + return 0; +} + +static ssize_t local_readlink(FsContext *fs_ctx, const char *path, + char *buf, size_t bufsz) +{ + ssize_t tsize = -1; + if (fs_ctx->fs_sm == SM_MAPPED) { + int fd; + fd = open(rpath(fs_ctx, path), O_RDONLY); + if (fd == -1) { + return -1; + } + do { + tsize = read(fd, (void *)buf, bufsz); + } while (tsize == -1 && errno == EINTR); + close(fd); + return tsize; + } else if ((fs_ctx->fs_sm == SM_PASSTHROUGH) || + (fs_ctx->fs_sm == SM_NONE)) { + tsize = readlink(rpath(fs_ctx, path), buf, bufsz); + } + return tsize; +} + +static int local_close(FsContext *ctx, int fd) +{ + return close(fd); +} + +static int local_closedir(FsContext *ctx, DIR *dir) +{ + return closedir(dir); +} + +static int local_open(FsContext *ctx, const char *path, int flags) +{ + return open(rpath(ctx, path), flags); +} + +static DIR *local_opendir(FsContext *ctx, const char *path) +{ + return opendir(rpath(ctx, path)); +} + +static void local_rewinddir(FsContext *ctx, DIR *dir) +{ + return rewinddir(dir); +} + +static off_t local_telldir(FsContext *ctx, DIR *dir) +{ + return telldir(dir); +} + +static struct dirent *local_readdir(FsContext *ctx, DIR *dir) +{ + return readdir(dir); +} + +static void local_seekdir(FsContext *ctx, DIR *dir, off_t off) +{ + return seekdir(dir, off); +} + +static ssize_t local_preadv(FsContext *ctx, int fd, const struct iovec *iov, + int iovcnt, off_t offset) +{ +#ifdef CONFIG_PREADV + return preadv(fd, iov, iovcnt, offset); +#else + int err = lseek(fd, offset, SEEK_SET); + if (err == -1) { + return err; + } else { + return readv(fd, iov, iovcnt); + } +#endif +} + +static ssize_t local_pwritev(FsContext *ctx, int fd, const struct iovec *iov, + int iovcnt, off_t offset) +{ +#ifdef CONFIG_PREADV + return pwritev(fd, iov, iovcnt, offset); +#else + int err = lseek(fd, offset, SEEK_SET); + if (err == -1) { + return err; + } else { + return writev(fd, iov, iovcnt); + } +#endif +} + +static int local_chmod(FsContext *fs_ctx, const char *path, FsCred *credp) +{ + if (fs_ctx->fs_sm == SM_MAPPED) { + return local_set_xattr(rpath(fs_ctx, path), credp); + } else if ((fs_ctx->fs_sm == SM_PASSTHROUGH) || + (fs_ctx->fs_sm == SM_NONE)) { + return chmod(rpath(fs_ctx, path), credp->fc_mode); + } + return -1; +} + +static int local_mknod(FsContext *fs_ctx, const char *path, FsCred *credp) +{ + int err = -1; + int serrno = 0; + + /* Determine the security model */ + if (fs_ctx->fs_sm == SM_MAPPED) { + err = mknod(rpath(fs_ctx, path), SM_LOCAL_MODE_BITS|S_IFREG, 0); + if (err == -1) { + return err; + } + local_set_xattr(rpath(fs_ctx, path), credp); + if (err == -1) { + serrno = errno; + goto err_end; + } + } else if ((fs_ctx->fs_sm == SM_PASSTHROUGH) || + (fs_ctx->fs_sm == SM_NONE)) { + err = mknod(rpath(fs_ctx, path), credp->fc_mode, credp->fc_rdev); + if (err == -1) { + return err; + } + err = local_post_create_passthrough(fs_ctx, path, credp); + if (err == -1) { + serrno = errno; + goto err_end; + } + } + return err; + +err_end: + remove(rpath(fs_ctx, path)); + errno = serrno; + return err; +} + +static int local_mkdir(FsContext *fs_ctx, const char *path, FsCred *credp) +{ + int err = -1; + int serrno = 0; + + /* Determine the security model */ + if (fs_ctx->fs_sm == SM_MAPPED) { + err = mkdir(rpath(fs_ctx, path), SM_LOCAL_DIR_MODE_BITS); + if (err == -1) { + return err; + } + credp->fc_mode = credp->fc_mode|S_IFDIR; + err = local_set_xattr(rpath(fs_ctx, path), credp); + if (err == -1) { + serrno = errno; + goto err_end; + } + } else if ((fs_ctx->fs_sm == SM_PASSTHROUGH) || + (fs_ctx->fs_sm == SM_NONE)) { + err = mkdir(rpath(fs_ctx, path), credp->fc_mode); + if (err == -1) { + return err; + } + err = local_post_create_passthrough(fs_ctx, path, credp); + if (err == -1) { + serrno = errno; + goto err_end; + } + } + return err; + +err_end: + remove(rpath(fs_ctx, path)); + errno = serrno; + return err; +} + +static int local_fstat(FsContext *fs_ctx, int fd, struct stat *stbuf) +{ + int err; + err = fstat(fd, stbuf); + if (err) { + return err; + } + if (fs_ctx->fs_sm == 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 = tmp_uid; + } + if (fgetxattr(fd, "user.virtfs.gid", &tmp_gid, sizeof(gid_t)) > 0) { + stbuf->st_gid = tmp_gid; + } + if (fgetxattr(fd, "user.virtfs.mode", &tmp_mode, sizeof(mode_t)) > 0) { + stbuf->st_mode = tmp_mode; + } + if (fgetxattr(fd, "user.virtfs.rdev", &tmp_dev, sizeof(dev_t)) > 0) { + stbuf->st_rdev = tmp_dev; + } + } + return err; +} + +static int local_open2(FsContext *fs_ctx, const char *path, int flags, + FsCred *credp) +{ + int fd = -1; + int err = -1; + int serrno = 0; + + /* Determine the security model */ + if (fs_ctx->fs_sm == SM_MAPPED) { + fd = open(rpath(fs_ctx, path), flags, SM_LOCAL_MODE_BITS); + if (fd == -1) { + return fd; + } + credp->fc_mode = credp->fc_mode|S_IFREG; + /* Set cleint credentials in xattr */ + err = local_set_xattr(rpath(fs_ctx, path), credp); + if (err == -1) { + serrno = errno; + goto err_end; + } + } else if ((fs_ctx->fs_sm == SM_PASSTHROUGH) || + (fs_ctx->fs_sm == SM_NONE)) { + fd = open(rpath(fs_ctx, path), flags, credp->fc_mode); + if (fd == -1) { + return fd; + } + err = local_post_create_passthrough(fs_ctx, path, credp); + if (err == -1) { + serrno = errno; + goto err_end; + } + } + return fd; + +err_end: + close(fd); + remove(rpath(fs_ctx, path)); + errno = serrno; + return err; +} + + +static int local_symlink(FsContext *fs_ctx, const char *oldpath, + const char *newpath, FsCred *credp) +{ + int err = -1; + int serrno = 0; + + /* Determine the security model */ + if (fs_ctx->fs_sm == SM_MAPPED) { + int fd; + ssize_t oldpath_size, write_size; + fd = open(rpath(fs_ctx, newpath), O_CREAT|O_EXCL|O_RDWR, + SM_LOCAL_MODE_BITS); + if (fd == -1) { + return fd; + } + /* Write the oldpath (target) to the file. */ + oldpath_size = strlen(oldpath) + 1; + 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(rpath(fs_ctx, newpath), credp); + if (err == -1) { + serrno = errno; + goto err_end; + } + } else if ((fs_ctx->fs_sm == SM_PASSTHROUGH) || + (fs_ctx->fs_sm == SM_NONE)) { + err = symlink(oldpath, rpath(fs_ctx, newpath)); + if (err) { + return err; + } + err = lchown(rpath(fs_ctx, newpath), 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->fs_sm != SM_NONE) { + serrno = errno; + goto err_end; + } else + err = 0; + } + } + return err; + +err_end: + remove(rpath(fs_ctx, newpath)); + errno = serrno; + return err; +} + +static int local_link(FsContext *ctx, const char *oldpath, const char *newpath) +{ + char *tmp = qemu_strdup(rpath(ctx, oldpath)); + int err, serrno = 0; + + if (tmp == NULL) { + return -ENOMEM; + } + + err = link(tmp, rpath(ctx, newpath)); + if (err == -1) { + serrno = errno; + } + + qemu_free(tmp); + + if (err == -1) { + errno = serrno; + } + + return err; +} + +static int local_truncate(FsContext *ctx, const char *path, off_t size) +{ + return truncate(rpath(ctx, path), size); +} + +static int local_rename(FsContext *ctx, const char *oldpath, + const char *newpath) +{ + char *tmp; + int err; + + tmp = qemu_strdup(rpath(ctx, oldpath)); + + err = rename(tmp, rpath(ctx, newpath)); + if (err == -1) { + int serrno = errno; + qemu_free(tmp); + errno = serrno; + } else { + qemu_free(tmp); + } + + return err; + +} + +static int local_chown(FsContext *fs_ctx, const char *path, FsCred *credp) +{ + if ((credp->fc_uid == -1 && credp->fc_gid == -1) || + (fs_ctx->fs_sm == SM_PASSTHROUGH)) { + return lchown(rpath(fs_ctx, path), credp->fc_uid, credp->fc_gid); + } else if (fs_ctx->fs_sm == SM_MAPPED) { + return local_set_xattr(rpath(fs_ctx, path), credp); + } else if ((fs_ctx->fs_sm == SM_PASSTHROUGH) || + (fs_ctx->fs_sm == SM_NONE)) { + return lchown(rpath(fs_ctx, path), credp->fc_uid, credp->fc_gid); + } + return -1; +} + +static int local_utimensat(FsContext *s, const char *path, + const struct timespec *buf) +{ + return qemu_utimensat(AT_FDCWD, rpath(s, path), buf, AT_SYMLINK_NOFOLLOW); +} + +static int local_remove(FsContext *ctx, const char *path) +{ + return remove(rpath(ctx, path)); +} + +static int local_fsync(FsContext *ctx, int fd, int datasync) +{ + if (datasync) { + return qemu_fdatasync(fd); + } else { + return fsync(fd); + } +} + +static int local_statfs(FsContext *s, const char *path, struct statfs *stbuf) +{ + return statfs(rpath(s, path), stbuf); +} + +static ssize_t local_lgetxattr(FsContext *ctx, const char *path, + const char *name, void *value, size_t size) +{ + return v9fs_get_xattr(ctx, path, name, value, size); +} + +static ssize_t local_llistxattr(FsContext *ctx, const char *path, + void *value, size_t size) +{ + return v9fs_list_xattr(ctx, path, value, size); +} + +static int local_lsetxattr(FsContext *ctx, const char *path, const char *name, + void *value, size_t size, int flags) +{ + return v9fs_set_xattr(ctx, path, name, value, size, flags); +} + +static int local_lremovexattr(FsContext *ctx, + const char *path, const char *name) +{ + return v9fs_remove_xattr(ctx, path, name); +} + + +FileOperations local_ops = { + .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 = local_readdir, + .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, +}; diff --git a/hw/virtio-9p-posix-acl.c b/hw/virtio-9p-posix-acl.c new file mode 100644 index 0000000..3978d0c --- /dev/null +++ b/hw/virtio-9p-posix-acl.c @@ -0,0 +1,140 @@ +/* + * Virtio 9p system.posix* xattr callback + * + * Copyright IBM, Corp. 2010 + * + * Authors: + * Aneesh Kumar K.V + * + * This work is licensed under the terms of the GNU GPL, version 2. See + * the COPYING file in the top-level directory. + * + */ + +#include +#include +#include "virtio.h" +#include "virtio-9p.h" +#include "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) +{ + return lgetxattr(rpath(ctx, path), MAP_ACL_ACCESS, value, size); +} + +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; + } + + strncpy(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) +{ + return lsetxattr(rpath(ctx, path), MAP_ACL_ACCESS, value, size, flags); +} + +static int mp_pacl_removexattr(FsContext *ctx, + const char *path, const char *name) +{ + int ret; + ret = lremovexattr(rpath(ctx, path), MAP_ACL_ACCESS); + if (ret == -1 && errno == ENODATA) { + /* + * We don't get ENODATA error when trying to remote a + * posix acl that is not present. So don't throw the error + * even in case of mapped security model + */ + errno = 0; + ret = 0; + } + return ret; +} + +static ssize_t mp_dacl_getxattr(FsContext *ctx, const char *path, + const char *name, void *value, size_t size) +{ + return lgetxattr(rpath(ctx, path), MAP_ACL_DEFAULT, value, size); +} + +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; + } + + strncpy(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) +{ + return lsetxattr(rpath(ctx, path), MAP_ACL_DEFAULT, value, size, flags); +} + +static int mp_dacl_removexattr(FsContext *ctx, + const char *path, const char *name) +{ + return lremovexattr(rpath(ctx, path), MAP_ACL_DEFAULT); +} + + +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/virtio-9p-xattr-user.c b/hw/virtio-9p-xattr-user.c new file mode 100644 index 0000000..faa02a1 --- /dev/null +++ b/hw/virtio-9p-xattr-user.c @@ -0,0 +1,109 @@ +/* + * Virtio 9p user. xattr callback + * + * Copyright IBM, Corp. 2010 + * + * Authors: + * Aneesh Kumar K.V + * + * This work is licensed under the terms of the GNU GPL, version 2. See + * the COPYING file in the top-level directory. + * + */ + +#include +#include "virtio.h" +#include "virtio-9p.h" +#include "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) +{ + if (strncmp(name, "user.virtfs.", 12) == 0) { + /* + * Don't allow fetch of user.virtfs namesapce + * in case of mapped security + */ + errno = ENOATTR; + return -1; + } + return lgetxattr(rpath(ctx, path), name, value, size); +} + +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; + } + + strncpy(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) +{ + if (strncmp(name, "user.virtfs.", 12) == 0) { + /* + * Don't allow fetch of user.virtfs namesapce + * in case of mapped security + */ + errno = EACCES; + return -1; + } + return lsetxattr(rpath(ctx, path), name, value, size, flags); +} + +static int mp_user_removexattr(FsContext *ctx, + const char *path, const char *name) +{ + if (strncmp(name, "user.virtfs.", 12) == 0) { + /* + * Don't allow fetch of user.virtfs namesapce + * in case of mapped security + */ + errno = EACCES; + return -1; + } + return lremovexattr(rpath(ctx, path), name); +} + +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/virtio-9p-xattr.c b/hw/virtio-9p-xattr.c new file mode 100644 index 0000000..1aab081 --- /dev/null +++ b/hw/virtio-9p-xattr.c @@ -0,0 +1,159 @@ +/* + * Virtio 9p xattr callback + * + * Copyright IBM, Corp. 2010 + * + * Authors: + * Aneesh Kumar K.V + * + * This work is licensed under the terms of the GNU GPL, version 2. See + * the COPYING file in the top-level directory. + * + */ + +#include "virtio.h" +#include "virtio-9p.h" +#include "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; + } + + strncpy(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; + void *ovalue = value; + XattrOperations *xops; + char *orig_value, *orig_value_start; + ssize_t xattr_len, parsed_len = 0, attr_len; + + /* Get the actual len */ + xattr_len = llistxattr(rpath(ctx, path), value, 0); + if (xattr_len <= 0) { + return xattr_len; + } + + /* Now fetch the xattr and find the actual size */ + orig_value = qemu_malloc(xattr_len); + xattr_len = llistxattr(rpath(ctx, path), orig_value, xattr_len); + + /* 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: + qemu_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/virtio-9p-xattr.h b/hw/virtio-9p-xattr.h new file mode 100644 index 0000000..2bbae2d --- /dev/null +++ b/hw/virtio-9p-xattr.h @@ -0,0 +1,102 @@ +/* + * Virtio 9p + * + * Copyright IBM, Corp. 2010 + * + * Authors: + * Aneesh Kumar K.V + * + * 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 + +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) +{ + return lgetxattr(rpath(ctx, path), name, value, size); +} + +static inline int pt_setxattr(FsContext *ctx, const char *path, + const char *name, void *value, + size_t size, int flags) +{ + return lsetxattr(rpath(ctx, path), name, value, size, flags); +} + +static inline int pt_removexattr(FsContext *ctx, + const char *path, const char *name) +{ + return lremovexattr(rpath(ctx, path), name); +} + +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/virtio-9p.c b/hw/virtio-9p.c new file mode 100644 index 0000000..7c59988 --- /dev/null +++ b/hw/virtio-9p.c @@ -0,0 +1,3744 @@ +/* + * Virtio 9p backend + * + * Copyright IBM, Corp. 2010 + * + * Authors: + * Anthony Liguori + * + * This work is licensed under the terms of the GNU GPL, version 2. See + * the COPYING file in the top-level directory. + * + */ + +#include "virtio.h" +#include "pc.h" +#include "qemu_socket.h" +#include "virtio-9p.h" +#include "fsdev/qemu-fsdev.h" +#include "virtio-9p-debug.h" +#include "virtio-9p-xattr.h" + +int debug_9p_pdu; + +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; +} + +void cred_init(FsCred *credp) +{ + credp->fc_uid = -1; + credp->fc_gid = -1; + credp->fc_mode = -1; + credp->fc_rdev = -1; +} + +static int v9fs_do_lstat(V9fsState *s, V9fsString *path, struct stat *stbuf) +{ + return s->ops->lstat(&s->ctx, path->data, stbuf); +} + +static ssize_t v9fs_do_readlink(V9fsState *s, V9fsString *path, V9fsString *buf) +{ + ssize_t len; + + buf->data = qemu_malloc(1024); + + len = s->ops->readlink(&s->ctx, path->data, buf->data, 1024 - 1); + if (len > -1) { + buf->size = len; + buf->data[len] = 0; + } + + return len; +} + +static int v9fs_do_close(V9fsState *s, int fd) +{ + return s->ops->close(&s->ctx, fd); +} + +static int v9fs_do_closedir(V9fsState *s, DIR *dir) +{ + return s->ops->closedir(&s->ctx, dir); +} + +static int v9fs_do_open(V9fsState *s, V9fsString *path, int flags) +{ + return s->ops->open(&s->ctx, path->data, flags); +} + +static DIR *v9fs_do_opendir(V9fsState *s, V9fsString *path) +{ + return s->ops->opendir(&s->ctx, path->data); +} + +static void v9fs_do_rewinddir(V9fsState *s, DIR *dir) +{ + return s->ops->rewinddir(&s->ctx, dir); +} + +static off_t v9fs_do_telldir(V9fsState *s, DIR *dir) +{ + return s->ops->telldir(&s->ctx, dir); +} + +static struct dirent *v9fs_do_readdir(V9fsState *s, DIR *dir) +{ + return s->ops->readdir(&s->ctx, dir); +} + +static void v9fs_do_seekdir(V9fsState *s, DIR *dir, off_t off) +{ + return s->ops->seekdir(&s->ctx, dir, off); +} + +static int v9fs_do_preadv(V9fsState *s, int fd, const struct iovec *iov, + int iovcnt, int64_t offset) +{ + return s->ops->preadv(&s->ctx, fd, iov, iovcnt, offset); +} + +static int v9fs_do_pwritev(V9fsState *s, int fd, const struct iovec *iov, + int iovcnt, int64_t offset) +{ + return s->ops->pwritev(&s->ctx, fd, iov, iovcnt, offset); +} + +static int v9fs_do_chmod(V9fsState *s, V9fsString *path, mode_t mode) +{ + FsCred cred; + cred_init(&cred); + cred.fc_mode = mode; + return s->ops->chmod(&s->ctx, path->data, &cred); +} + +static int v9fs_do_mknod(V9fsState *s, char *name, + mode_t mode, dev_t dev, uid_t uid, gid_t gid) +{ + FsCred cred; + cred_init(&cred); + cred.fc_uid = uid; + cred.fc_gid = gid; + cred.fc_mode = mode; + cred.fc_rdev = dev; + return s->ops->mknod(&s->ctx, name, &cred); +} + +static int v9fs_do_mkdir(V9fsState *s, char *name, mode_t mode, + uid_t uid, gid_t gid) +{ + FsCred cred; + + cred_init(&cred); + cred.fc_uid = uid; + cred.fc_gid = gid; + cred.fc_mode = mode; + + return s->ops->mkdir(&s->ctx, name, &cred); +} + +static int v9fs_do_fstat(V9fsState *s, int fd, struct stat *stbuf) +{ + return s->ops->fstat(&s->ctx, fd, stbuf); +} + +static int v9fs_do_open2(V9fsState *s, char *fullname, uid_t uid, gid_t gid, + int flags, int mode) +{ + FsCred cred; + + cred_init(&cred); + cred.fc_uid = uid; + cred.fc_gid = gid; + cred.fc_mode = mode & 07777; + flags = flags; + + return s->ops->open2(&s->ctx, fullname, flags, &cred); +} + +static int v9fs_do_symlink(V9fsState *s, V9fsFidState *fidp, + const char *oldpath, const char *newpath, gid_t gid) +{ + FsCred cred; + cred_init(&cred); + cred.fc_uid = fidp->uid; + cred.fc_gid = gid; + cred.fc_mode = 0777; + + return s->ops->symlink(&s->ctx, oldpath, newpath, &cred); +} + +static int v9fs_do_link(V9fsState *s, V9fsString *oldpath, V9fsString *newpath) +{ + return s->ops->link(&s->ctx, oldpath->data, newpath->data); +} + +static int v9fs_do_truncate(V9fsState *s, V9fsString *path, off_t size) +{ + return s->ops->truncate(&s->ctx, path->data, size); +} + +static int v9fs_do_rename(V9fsState *s, V9fsString *oldpath, + V9fsString *newpath) +{ + return s->ops->rename(&s->ctx, oldpath->data, newpath->data); +} + +static int v9fs_do_chown(V9fsState *s, V9fsString *path, uid_t uid, gid_t gid) +{ + FsCred cred; + cred_init(&cred); + cred.fc_uid = uid; + cred.fc_gid = gid; + + return s->ops->chown(&s->ctx, path->data, &cred); +} + +static int v9fs_do_utimensat(V9fsState *s, V9fsString *path, + const struct timespec times[2]) +{ + return s->ops->utimensat(&s->ctx, path->data, times); +} + +static int v9fs_do_remove(V9fsState *s, V9fsString *path) +{ + return s->ops->remove(&s->ctx, path->data); +} + +static int v9fs_do_fsync(V9fsState *s, int fd, int datasync) +{ + return s->ops->fsync(&s->ctx, fd, datasync); +} + +static int v9fs_do_statfs(V9fsState *s, V9fsString *path, struct statfs *stbuf) +{ + return s->ops->statfs(&s->ctx, path->data, stbuf); +} + +static ssize_t v9fs_do_lgetxattr(V9fsState *s, V9fsString *path, + V9fsString *xattr_name, + void *value, size_t size) +{ + return s->ops->lgetxattr(&s->ctx, path->data, + xattr_name->data, value, size); +} + +static ssize_t v9fs_do_llistxattr(V9fsState *s, V9fsString *path, + void *value, size_t size) +{ + return s->ops->llistxattr(&s->ctx, path->data, + value, size); +} + +static int v9fs_do_lsetxattr(V9fsState *s, V9fsString *path, + V9fsString *xattr_name, + void *value, size_t size, int flags) +{ + return s->ops->lsetxattr(&s->ctx, path->data, + xattr_name->data, value, size, flags); +} + +static int v9fs_do_lremovexattr(V9fsState *s, V9fsString *path, + V9fsString *xattr_name) +{ + return s->ops->lremovexattr(&s->ctx, path->data, + xattr_name->data); +} + + +static void v9fs_string_init(V9fsString *str) +{ + str->data = NULL; + str->size = 0; +} + +static void v9fs_string_free(V9fsString *str) +{ + qemu_free(str->data); + str->data = NULL; + str->size = 0; +} + +static void v9fs_string_null(V9fsString *str) +{ + v9fs_string_free(str); +} + +static int number_to_string(void *arg, char type) +{ + unsigned int ret = 0; + + switch (type) { + case 'u': { + unsigned int num = *(unsigned int *)arg; + + do { + ret++; + num = num/10; + } while (num); + break; + } + case 'U': { + unsigned long num = *(unsigned long *)arg; + do { + ret++; + num = num/10; + } while (num); + break; + } + default: + printf("Number_to_string: Unknown number format\n"); + return -1; + } + + return ret; +} + +static int GCC_FMT_ATTR(2, 0) +v9fs_string_alloc_printf(char **strp, const char *fmt, va_list ap) +{ + va_list ap2; + char *iter = (char *)fmt; + int len = 0; + int nr_args = 0; + char *arg_char_ptr; + unsigned int arg_uint; + unsigned long arg_ulong; + + /* Find the number of %'s that denotes an argument */ + for (iter = strstr(iter, "%"); iter; iter = strstr(iter, "%")) { + nr_args++; + iter++; + } + + len = strlen(fmt) - 2*nr_args; + + if (!nr_args) { + goto alloc_print; + } + + va_copy(ap2, ap); + + iter = (char *)fmt; + + /* Now parse the format string */ + for (iter = strstr(iter, "%"); iter; iter = strstr(iter, "%")) { + iter++; + switch (*iter) { + case 'u': + arg_uint = va_arg(ap2, unsigned int); + len += number_to_string((void *)&arg_uint, 'u'); + break; + case 'l': + if (*++iter == 'u') { + arg_ulong = va_arg(ap2, unsigned long); + len += number_to_string((void *)&arg_ulong, 'U'); + } else { + return -1; + } + break; + case 's': + arg_char_ptr = va_arg(ap2, char *); + len += strlen(arg_char_ptr); + break; + case 'c': + len += 1; + break; + default: + fprintf(stderr, + "v9fs_string_alloc_printf:Incorrect format %c", *iter); + return -1; + } + iter++; + } + +alloc_print: + *strp = qemu_malloc((len + 1) * sizeof(**strp)); + + return vsprintf(*strp, fmt, ap); +} + +static void GCC_FMT_ATTR(2, 3) +v9fs_string_sprintf(V9fsString *str, const char *fmt, ...) +{ + va_list ap; + int err; + + v9fs_string_free(str); + + va_start(ap, fmt); + err = v9fs_string_alloc_printf(&str->data, fmt, ap); + BUG_ON(err == -1); + va_end(ap); + + str->size = err; +} + +static void v9fs_string_copy(V9fsString *lhs, V9fsString *rhs) +{ + v9fs_string_free(lhs); + v9fs_string_sprintf(lhs, "%s", rhs->data); +} + +static size_t v9fs_string_size(V9fsString *str) +{ + return str->size; +} + +static V9fsFidState *lookup_fid(V9fsState *s, int32_t fid) +{ + V9fsFidState *f; + + for (f = s->fid_list; f; f = f->next) { + if (f->fid == fid) { + return f; + } + } + + return NULL; +} + +static V9fsFidState *alloc_fid(V9fsState *s, int32_t fid) +{ + V9fsFidState *f; + + f = lookup_fid(s, fid); + if (f) { + return NULL; + } + + f = qemu_mallocz(sizeof(V9fsFidState)); + + f->fid = fid; + f->fid_type = P9_FID_NONE; + + f->next = s->fid_list; + s->fid_list = f; + + return f; +} + +static int v9fs_xattr_fid_clunk(V9fsState *s, 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_do_lsetxattr(s, &fidp->path, &fidp->fs.xattr.name, + fidp->fs.xattr.value, + fidp->fs.xattr.len, + fidp->fs.xattr.flags); + } else { + retval = v9fs_do_lremovexattr(s, &fidp->path, &fidp->fs.xattr.name); + } +free_out: + v9fs_string_free(&fidp->fs.xattr.name); +free_value: + if (fidp->fs.xattr.value) { + qemu_free(fidp->fs.xattr.value); + } + return retval; +} + +static int free_fid(V9fsState *s, int32_t fid) +{ + int retval = 0; + V9fsFidState **fidpp, *fidp; + + for (fidpp = &s->fid_list; *fidpp; fidpp = &(*fidpp)->next) { + if ((*fidpp)->fid == fid) { + break; + } + } + + if (*fidpp == NULL) { + return -ENOENT; + } + + fidp = *fidpp; + *fidpp = fidp->next; + + if (fidp->fid_type == P9_FID_FILE) { + v9fs_do_close(s, fidp->fs.fd); + } else if (fidp->fid_type == P9_FID_DIR) { + v9fs_do_closedir(s, fidp->fs.dir); + } else if (fidp->fid_type == P9_FID_XATTR) { + retval = v9fs_xattr_fid_clunk(s, fidp); + } + v9fs_string_free(&fidp->path); + qemu_free(fidp); + + return retval; +} + +#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; + + 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(V9fsState *s, V9fsFidState *fidp, V9fsQID *qidp) +{ + struct stat stbuf; + int err; + + err = v9fs_do_lstat(s, &fidp->path, &stbuf); + if (err) { + 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); + } + return pdu; +} + +static void free_pdu(V9fsState *s, V9fsPDU *pdu) +{ + if (pdu) { + QLIST_INSERT_HEAD(&s->free_list, pdu, next); + } +} + +size_t pdu_packunpack(void *addr, struct iovec *sg, int sg_count, + size_t offset, size_t size, int pack) +{ + int i = 0; + size_t copied = 0; + + for (i = 0; size && i < sg_count; i++) { + size_t len; + if (offset >= sg[i].iov_len) { + /* skip this sg */ + offset -= sg[i].iov_len; + continue; + } else { + len = MIN(sg[i].iov_len - offset, size); + if (pack) { + memcpy(sg[i].iov_base + offset, addr, len); + } else { + memcpy(addr, sg[i].iov_base + offset, len); + } + size -= len; + copied += len; + addr += len; + if (size) { + offset = 0; + continue; + } + } + } + + return copied; +} + +static size_t pdu_unpack(void *dst, V9fsPDU *pdu, size_t offset, size_t size) +{ + return pdu_packunpack(dst, pdu->elem.out_sg, pdu->elem.out_num, + offset, size, 0); +} + +static size_t pdu_pack(V9fsPDU *pdu, size_t offset, const void *src, + size_t size) +{ + return pdu_packunpack((void *)src, pdu->elem.in_sg, pdu->elem.in_num, + offset, size, 1); +} + +static int pdu_copy_sg(V9fsPDU *pdu, size_t offset, int rx, struct iovec *sg) +{ + size_t pos = 0; + int i, j; + struct iovec *src_sg; + unsigned int num; + + if (rx) { + src_sg = pdu->elem.in_sg; + num = pdu->elem.in_num; + } else { + src_sg = pdu->elem.out_sg; + num = pdu->elem.out_num; + } + + j = 0; + for (i = 0; i < num; i++) { + if (offset <= pos) { + sg[j].iov_base = src_sg[i].iov_base; + sg[j].iov_len = src_sg[i].iov_len; + j++; + } else if (offset < (src_sg[i].iov_len + pos)) { + sg[j].iov_base = src_sg[i].iov_base; + sg[j].iov_len = src_sg[i].iov_len; + sg[j].iov_base += (offset - pos); + sg[j].iov_len -= (offset - pos); + j++; + } + pos += src_sg[i].iov_len; + } + + return j; +} + +static size_t pdu_unmarshal(V9fsPDU *pdu, size_t offset, const char *fmt, ...) +{ + size_t old_offset = offset; + va_list ap; + int i; + + va_start(ap, fmt); + for (i = 0; fmt[i]; i++) { + switch (fmt[i]) { + case 'b': { + uint8_t *valp = va_arg(ap, uint8_t *); + offset += pdu_unpack(valp, pdu, offset, sizeof(*valp)); + break; + } + case 'w': { + uint16_t val, *valp; + valp = va_arg(ap, uint16_t *); + val = le16_to_cpupu(valp); + offset += pdu_unpack(&val, pdu, offset, sizeof(val)); + *valp = val; + break; + } + case 'd': { + uint32_t val, *valp; + valp = va_arg(ap, uint32_t *); + val = le32_to_cpupu(valp); + offset += pdu_unpack(&val, pdu, offset, sizeof(val)); + *valp = val; + break; + } + case 'q': { + uint64_t val, *valp; + valp = va_arg(ap, uint64_t *); + val = le64_to_cpup(valp); + offset += pdu_unpack(&val, pdu, offset, sizeof(val)); + *valp = val; + break; + } + case 'v': { + struct iovec *iov = va_arg(ap, struct iovec *); + int *iovcnt = va_arg(ap, int *); + *iovcnt = pdu_copy_sg(pdu, offset, 0, iov); + break; + } + case 's': { + V9fsString *str = va_arg(ap, V9fsString *); + offset += pdu_unmarshal(pdu, offset, "w", &str->size); + /* FIXME: sanity check str->size */ + str->data = qemu_malloc(str->size + 1); + offset += pdu_unpack(str->data, pdu, offset, str->size); + str->data[str->size] = 0; + break; + } + case 'Q': { + V9fsQID *qidp = va_arg(ap, V9fsQID *); + offset += pdu_unmarshal(pdu, offset, "bdq", + &qidp->type, &qidp->version, &qidp->path); + break; + } + case 'S': { + V9fsStat *statp = va_arg(ap, V9fsStat *); + offset += pdu_unmarshal(pdu, offset, "wwdQdddqsssssddd", + &statp->size, &statp->type, &statp->dev, + &statp->qid, &statp->mode, &statp->atime, + &statp->mtime, &statp->length, + &statp->name, &statp->uid, &statp->gid, + &statp->muid, &statp->extension, + &statp->n_uid, &statp->n_gid, + &statp->n_muid); + break; + } + case 'I': { + V9fsIattr *iattr = va_arg(ap, V9fsIattr *); + offset += pdu_unmarshal(pdu, offset, "ddddqqqqq", + &iattr->valid, &iattr->mode, + &iattr->uid, &iattr->gid, &iattr->size, + &iattr->atime_sec, &iattr->atime_nsec, + &iattr->mtime_sec, &iattr->mtime_nsec); + break; + } + default: + break; + } + } + + va_end(ap); + + return offset - old_offset; +} + +static size_t pdu_marshal(V9fsPDU *pdu, size_t offset, const char *fmt, ...) +{ + size_t old_offset = offset; + va_list ap; + int i; + + va_start(ap, fmt); + for (i = 0; fmt[i]; i++) { + switch (fmt[i]) { + case 'b': { + uint8_t val = va_arg(ap, int); + offset += pdu_pack(pdu, offset, &val, sizeof(val)); + break; + } + case 'w': { + uint16_t val; + cpu_to_le16w(&val, va_arg(ap, int)); + offset += pdu_pack(pdu, offset, &val, sizeof(val)); + break; + } + case 'd': { + uint32_t val; + cpu_to_le32w(&val, va_arg(ap, uint32_t)); + offset += pdu_pack(pdu, offset, &val, sizeof(val)); + break; + } + case 'q': { + uint64_t val; + cpu_to_le64w(&val, va_arg(ap, uint64_t)); + offset += pdu_pack(pdu, offset, &val, sizeof(val)); + break; + } + case 'v': { + struct iovec *iov = va_arg(ap, struct iovec *); + int *iovcnt = va_arg(ap, int *); + *iovcnt = pdu_copy_sg(pdu, offset, 1, iov); + break; + } + case 's': { + V9fsString *str = va_arg(ap, V9fsString *); + offset += pdu_marshal(pdu, offset, "w", str->size); + offset += pdu_pack(pdu, offset, str->data, str->size); + break; + } + case 'Q': { + V9fsQID *qidp = va_arg(ap, V9fsQID *); + offset += pdu_marshal(pdu, offset, "bdq", + qidp->type, qidp->version, qidp->path); + break; + } + case 'S': { + V9fsStat *statp = va_arg(ap, V9fsStat *); + offset += pdu_marshal(pdu, offset, "wwdQdddqsssssddd", + statp->size, statp->type, statp->dev, + &statp->qid, statp->mode, statp->atime, + statp->mtime, statp->length, &statp->name, + &statp->uid, &statp->gid, &statp->muid, + &statp->extension, statp->n_uid, + statp->n_gid, statp->n_muid); + break; + } + case 'A': { + V9fsStatDotl *statp = va_arg(ap, V9fsStatDotl *); + offset += pdu_marshal(pdu, offset, "qQdddqqqqqqqqqqqqqqq", + statp->st_result_mask, + &statp->qid, statp->st_mode, + statp->st_uid, statp->st_gid, + statp->st_nlink, statp->st_rdev, + statp->st_size, statp->st_blksize, statp->st_blocks, + statp->st_atime_sec, statp->st_atime_nsec, + statp->st_mtime_sec, statp->st_mtime_nsec, + statp->st_ctime_sec, statp->st_ctime_nsec, + statp->st_btime_sec, statp->st_btime_nsec, + statp->st_gen, statp->st_data_version); + break; + } + default: + break; + } + } + va_end(ap); + + return offset - old_offset; +} + +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; + } + } + + /* 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(&s->vdev, s->vq); + + 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 && 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_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(V9fsState *s, V9fsString *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_do_readlink(s, name, &v9stat->extension); + if (err == -1) { + err = -errno; + return err; + } + v9stat->extension.data[err] = 0; + v9stat->extension.size = 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 struct iovec *adjust_sg(struct iovec *sg, int len, int *iovcnt) +{ + while (len && *iovcnt) { + if (len < sg->iov_len) { + sg->iov_len -= len; + sg->iov_base += len; + len = 0; + } else { + len -= sg->iov_len; + sg++; + *iovcnt -= 1; + } + } + + return sg; +} + +static struct iovec *cap_sg(struct iovec *sg, int cap, int *cnt) +{ + int i; + int total = 0; + + for (i = 0; i < *cnt; i++) { + if ((total + sg[i].iov_len) > cap) { + sg[i].iov_len -= ((total + sg[i].iov_len) - cap); + i++; + break; + } + total += sg[i].iov_len; + } + + *cnt = i; + + return sg; +} + +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"); +} + +static void v9fs_fix_path(V9fsString *dst, V9fsString *src, int len) +{ + V9fsString str; + v9fs_string_init(&str); + v9fs_string_copy(&str, dst); + v9fs_string_sprintf(dst, "%s%s", src->data, str.data+len); + v9fs_string_free(&str); +} + +static void v9fs_version(V9fsState *s, V9fsPDU *pdu) +{ + V9fsString version; + size_t offset = 7; + + pdu_unmarshal(pdu, offset, "ds", &s->msize, &version); + + 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"); + } + + offset += pdu_marshal(pdu, offset, "ds", s->msize, &version); + complete_pdu(s, pdu, offset); + + v9fs_string_free(&version); +} + +static void v9fs_attach(V9fsState *s, V9fsPDU *pdu) +{ + int32_t fid, afid, n_uname; + V9fsString uname, aname; + V9fsFidState *fidp; + V9fsQID qid; + size_t offset = 7; + ssize_t err; + + pdu_unmarshal(pdu, offset, "ddssd", &fid, &afid, &uname, &aname, &n_uname); + + fidp = alloc_fid(s, fid); + if (fidp == NULL) { + err = -EINVAL; + goto out; + } + + fidp->uid = n_uname; + + v9fs_string_sprintf(&fidp->path, "%s", "/"); + err = fid_to_qid(s, fidp, &qid); + if (err) { + err = -EINVAL; + free_fid(s, fid); + goto out; + } + + offset += pdu_marshal(pdu, offset, "Q", &qid); + + err = offset; +out: + complete_pdu(s, pdu, err); + v9fs_string_free(&uname); + v9fs_string_free(&aname); +} + +static void v9fs_stat_post_lstat(V9fsState *s, V9fsStatState *vs, int err) +{ + if (err == -1) { + err = -errno; + goto out; + } + + err = stat_to_v9stat(s, &vs->fidp->path, &vs->stbuf, &vs->v9stat); + if (err) { + goto out; + } + vs->offset += pdu_marshal(vs->pdu, vs->offset, "wS", 0, &vs->v9stat); + err = vs->offset; + +out: + complete_pdu(s, vs->pdu, err); + v9fs_stat_free(&vs->v9stat); + qemu_free(vs); +} + +static void v9fs_stat(V9fsState *s, V9fsPDU *pdu) +{ + int32_t fid; + V9fsStatState *vs; + ssize_t err = 0; + + vs = qemu_malloc(sizeof(*vs)); + vs->pdu = pdu; + vs->offset = 7; + + memset(&vs->v9stat, 0, sizeof(vs->v9stat)); + + pdu_unmarshal(vs->pdu, vs->offset, "d", &fid); + + vs->fidp = lookup_fid(s, fid); + if (vs->fidp == NULL) { + err = -ENOENT; + goto out; + } + + err = v9fs_do_lstat(s, &vs->fidp->path, &vs->stbuf); + v9fs_stat_post_lstat(s, vs, err); + return; + +out: + complete_pdu(s, vs->pdu, err); + v9fs_stat_free(&vs->v9stat); + qemu_free(vs); +} + +static void v9fs_getattr_post_lstat(V9fsState *s, V9fsStatStateDotl *vs, + int err) +{ + if (err == -1) { + err = -errno; + goto out; + } + + stat_to_v9stat_dotl(s, &vs->stbuf, &vs->v9stat_dotl); + vs->offset += pdu_marshal(vs->pdu, vs->offset, "A", &vs->v9stat_dotl); + err = vs->offset; + +out: + complete_pdu(s, vs->pdu, err); + qemu_free(vs); +} + +static void v9fs_getattr(V9fsState *s, V9fsPDU *pdu) +{ + int32_t fid; + V9fsStatStateDotl *vs; + ssize_t err = 0; + V9fsFidState *fidp; + uint64_t request_mask; + + vs = qemu_malloc(sizeof(*vs)); + vs->pdu = pdu; + vs->offset = 7; + + memset(&vs->v9stat_dotl, 0, sizeof(vs->v9stat_dotl)); + + pdu_unmarshal(vs->pdu, vs->offset, "dq", &fid, &request_mask); + + fidp = lookup_fid(s, fid); + if (fidp == NULL) { + err = -ENOENT; + goto out; + } + + /* Currently we only support BASIC fields in stat, so there is no + * need to look at request_mask. + */ + err = v9fs_do_lstat(s, &fidp->path, &vs->stbuf); + v9fs_getattr_post_lstat(s, vs, err); + return; + +out: + complete_pdu(s, vs->pdu, err); + qemu_free(vs); +} + +/* From Linux kernel code */ +#define ATTR_MODE (1 << 0) +#define ATTR_UID (1 << 1) +#define ATTR_GID (1 << 2) +#define ATTR_SIZE (1 << 3) +#define ATTR_ATIME (1 << 4) +#define ATTR_MTIME (1 << 5) +#define ATTR_CTIME (1 << 6) +#define ATTR_MASK 127 +#define ATTR_ATIME_SET (1 << 7) +#define ATTR_MTIME_SET (1 << 8) + +static void v9fs_setattr_post_truncate(V9fsState *s, V9fsSetattrState *vs, + int err) +{ + if (err == -1) { + err = -errno; + goto out; + } + err = vs->offset; + +out: + complete_pdu(s, vs->pdu, err); + qemu_free(vs); +} + +static void v9fs_setattr_post_chown(V9fsState *s, V9fsSetattrState *vs, int err) +{ + if (err == -1) { + err = -errno; + goto out; + } + + if (vs->v9iattr.valid & (ATTR_SIZE)) { + err = v9fs_do_truncate(s, &vs->fidp->path, vs->v9iattr.size); + } + v9fs_setattr_post_truncate(s, vs, err); + return; + +out: + complete_pdu(s, vs->pdu, err); + qemu_free(vs); +} + +static void v9fs_setattr_post_utimensat(V9fsState *s, V9fsSetattrState *vs, + int err) +{ + if (err == -1) { + err = -errno; + 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 ((vs->v9iattr.valid & (ATTR_UID | ATTR_GID)) || + ((vs->v9iattr.valid & ATTR_CTIME) + && !((vs->v9iattr.valid & ATTR_MASK) & ~ATTR_CTIME))) { + if (!(vs->v9iattr.valid & ATTR_UID)) { + vs->v9iattr.uid = -1; + } + if (!(vs->v9iattr.valid & ATTR_GID)) { + vs->v9iattr.gid = -1; + } + err = v9fs_do_chown(s, &vs->fidp->path, vs->v9iattr.uid, + vs->v9iattr.gid); + } + v9fs_setattr_post_chown(s, vs, err); + return; + +out: + complete_pdu(s, vs->pdu, err); + qemu_free(vs); +} + +static void v9fs_setattr_post_chmod(V9fsState *s, V9fsSetattrState *vs, int err) +{ + if (err == -1) { + err = -errno; + goto out; + } + + if (vs->v9iattr.valid & (ATTR_ATIME | ATTR_MTIME)) { + struct timespec times[2]; + if (vs->v9iattr.valid & ATTR_ATIME) { + if (vs->v9iattr.valid & ATTR_ATIME_SET) { + times[0].tv_sec = vs->v9iattr.atime_sec; + times[0].tv_nsec = vs->v9iattr.atime_nsec; + } else { + times[0].tv_nsec = UTIME_NOW; + } + } else { + times[0].tv_nsec = UTIME_OMIT; + } + + if (vs->v9iattr.valid & ATTR_MTIME) { + if (vs->v9iattr.valid & ATTR_MTIME_SET) { + times[1].tv_sec = vs->v9iattr.mtime_sec; + times[1].tv_nsec = vs->v9iattr.mtime_nsec; + } else { + times[1].tv_nsec = UTIME_NOW; + } + } else { + times[1].tv_nsec = UTIME_OMIT; + } + err = v9fs_do_utimensat(s, &vs->fidp->path, times); + } + v9fs_setattr_post_utimensat(s, vs, err); + return; + +out: + complete_pdu(s, vs->pdu, err); + qemu_free(vs); +} + +static void v9fs_setattr(V9fsState *s, V9fsPDU *pdu) +{ + int32_t fid; + V9fsSetattrState *vs; + int err = 0; + + vs = qemu_malloc(sizeof(*vs)); + vs->pdu = pdu; + vs->offset = 7; + + pdu_unmarshal(pdu, vs->offset, "dI", &fid, &vs->v9iattr); + + vs->fidp = lookup_fid(s, fid); + if (vs->fidp == NULL) { + err = -EINVAL; + goto out; + } + + if (vs->v9iattr.valid & ATTR_MODE) { + err = v9fs_do_chmod(s, &vs->fidp->path, vs->v9iattr.mode); + } + + v9fs_setattr_post_chmod(s, vs, err); + return; + +out: + complete_pdu(s, vs->pdu, err); + qemu_free(vs); +} + +static void v9fs_walk_complete(V9fsState *s, V9fsWalkState *vs, int err) +{ + complete_pdu(s, vs->pdu, err); + + if (vs->nwnames) { + for (vs->name_idx = 0; vs->name_idx < vs->nwnames; vs->name_idx++) { + v9fs_string_free(&vs->wnames[vs->name_idx]); + } + + qemu_free(vs->wnames); + qemu_free(vs->qids); + } +} + +static void v9fs_walk_marshal(V9fsWalkState *vs) +{ + int i; + vs->offset = 7; + vs->offset += pdu_marshal(vs->pdu, vs->offset, "w", vs->nwnames); + + for (i = 0; i < vs->nwnames; i++) { + vs->offset += pdu_marshal(vs->pdu, vs->offset, "Q", &vs->qids[i]); + } +} + +static void v9fs_walk_post_newfid_lstat(V9fsState *s, V9fsWalkState *vs, + int err) +{ + if (err == -1) { + free_fid(s, vs->newfidp->fid); + v9fs_string_free(&vs->path); + err = -ENOENT; + goto out; + } + + stat_to_qid(&vs->stbuf, &vs->qids[vs->name_idx]); + + vs->name_idx++; + if (vs->name_idx < vs->nwnames) { + v9fs_string_sprintf(&vs->path, "%s/%s", vs->newfidp->path.data, + vs->wnames[vs->name_idx].data); + v9fs_string_copy(&vs->newfidp->path, &vs->path); + + err = v9fs_do_lstat(s, &vs->newfidp->path, &vs->stbuf); + v9fs_walk_post_newfid_lstat(s, vs, err); + return; + } + + v9fs_string_free(&vs->path); + v9fs_walk_marshal(vs); + err = vs->offset; +out: + v9fs_walk_complete(s, vs, err); +} + +static void v9fs_walk_post_oldfid_lstat(V9fsState *s, V9fsWalkState *vs, + int err) +{ + if (err == -1) { + v9fs_string_free(&vs->path); + err = -ENOENT; + goto out; + } + + stat_to_qid(&vs->stbuf, &vs->qids[vs->name_idx]); + vs->name_idx++; + if (vs->name_idx < vs->nwnames) { + + v9fs_string_sprintf(&vs->path, "%s/%s", + vs->fidp->path.data, vs->wnames[vs->name_idx].data); + v9fs_string_copy(&vs->fidp->path, &vs->path); + + err = v9fs_do_lstat(s, &vs->fidp->path, &vs->stbuf); + v9fs_walk_post_oldfid_lstat(s, vs, err); + return; + } + + v9fs_string_free(&vs->path); + v9fs_walk_marshal(vs); + err = vs->offset; +out: + v9fs_walk_complete(s, vs, err); +} + +static void v9fs_walk(V9fsState *s, V9fsPDU *pdu) +{ + int32_t fid, newfid; + V9fsWalkState *vs; + int err = 0; + int i; + + vs = qemu_malloc(sizeof(*vs)); + vs->pdu = pdu; + vs->wnames = NULL; + vs->qids = NULL; + vs->offset = 7; + + vs->offset += pdu_unmarshal(vs->pdu, vs->offset, "ddw", &fid, + &newfid, &vs->nwnames); + + if (vs->nwnames) { + vs->wnames = qemu_mallocz(sizeof(vs->wnames[0]) * vs->nwnames); + + vs->qids = qemu_mallocz(sizeof(vs->qids[0]) * vs->nwnames); + + for (i = 0; i < vs->nwnames; i++) { + vs->offset += pdu_unmarshal(vs->pdu, vs->offset, "s", + &vs->wnames[i]); + } + } + + vs->fidp = lookup_fid(s, fid); + if (vs->fidp == NULL) { + err = -ENOENT; + goto out; + } + + /* FIXME: is this really valid? */ + if (fid == newfid) { + + BUG_ON(vs->fidp->fid_type != P9_FID_NONE); + v9fs_string_init(&vs->path); + vs->name_idx = 0; + + if (vs->name_idx < vs->nwnames) { + v9fs_string_sprintf(&vs->path, "%s/%s", + vs->fidp->path.data, vs->wnames[vs->name_idx].data); + v9fs_string_copy(&vs->fidp->path, &vs->path); + + err = v9fs_do_lstat(s, &vs->fidp->path, &vs->stbuf); + v9fs_walk_post_oldfid_lstat(s, vs, err); + return; + } + } else { + vs->newfidp = alloc_fid(s, newfid); + if (vs->newfidp == NULL) { + err = -EINVAL; + goto out; + } + + vs->newfidp->uid = vs->fidp->uid; + v9fs_string_init(&vs->path); + vs->name_idx = 0; + v9fs_string_copy(&vs->newfidp->path, &vs->fidp->path); + + if (vs->name_idx < vs->nwnames) { + v9fs_string_sprintf(&vs->path, "%s/%s", vs->newfidp->path.data, + vs->wnames[vs->name_idx].data); + v9fs_string_copy(&vs->newfidp->path, &vs->path); + + err = v9fs_do_lstat(s, &vs->newfidp->path, &vs->stbuf); + v9fs_walk_post_newfid_lstat(s, vs, err); + return; + } + } + + v9fs_walk_marshal(vs); + err = vs->offset; +out: + v9fs_walk_complete(s, vs, err); +} + +static int32_t get_iounit(V9fsState *s, V9fsString *name) +{ + struct statfs stbuf; + int32_t iounit = 0; + + /* + * iounit should be multiples of f_bsize (host filesystem block size + * and as well as less than (client msize - P9_IOHDRSZ)) + */ + if (!v9fs_do_statfs(s, name, &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_post_opendir(V9fsState *s, V9fsOpenState *vs, int err) +{ + if (vs->fidp->fs.dir == NULL) { + err = -errno; + goto out; + } + vs->fidp->fid_type = P9_FID_DIR; + vs->offset += pdu_marshal(vs->pdu, vs->offset, "Qd", &vs->qid, 0); + err = vs->offset; +out: + complete_pdu(s, vs->pdu, err); + qemu_free(vs); + +} + +static void v9fs_open_post_getiounit(V9fsState *s, V9fsOpenState *vs) +{ + int err; + vs->offset += pdu_marshal(vs->pdu, vs->offset, "Qd", &vs->qid, vs->iounit); + err = vs->offset; + complete_pdu(s, vs->pdu, err); + qemu_free(vs); +} + +static void v9fs_open_post_open(V9fsState *s, V9fsOpenState *vs, int err) +{ + if (vs->fidp->fs.fd == -1) { + err = -errno; + goto out; + } + vs->fidp->fid_type = P9_FID_FILE; + vs->iounit = get_iounit(s, &vs->fidp->path); + v9fs_open_post_getiounit(s, vs); + return; +out: + complete_pdu(s, vs->pdu, err); + qemu_free(vs); +} + +static void v9fs_open_post_lstat(V9fsState *s, V9fsOpenState *vs, int err) +{ + int flags; + + if (err) { + err = -errno; + goto out; + } + + stat_to_qid(&vs->stbuf, &vs->qid); + + if (S_ISDIR(vs->stbuf.st_mode)) { + vs->fidp->fs.dir = v9fs_do_opendir(s, &vs->fidp->path); + v9fs_open_post_opendir(s, vs, err); + } else { + if (s->proto_version == V9FS_PROTO_2000L) { + flags = vs->mode; + flags &= ~(O_NOCTTY | O_ASYNC | O_CREAT); + /* Ignore direct disk access hint until the server supports it. */ + flags &= ~O_DIRECT; + } else { + flags = omode_to_uflags(vs->mode); + } + vs->fidp->fs.fd = v9fs_do_open(s, &vs->fidp->path, flags); + v9fs_open_post_open(s, vs, err); + } + return; +out: + complete_pdu(s, vs->pdu, err); + qemu_free(vs); +} + +static void v9fs_open(V9fsState *s, V9fsPDU *pdu) +{ + int32_t fid; + V9fsOpenState *vs; + ssize_t err = 0; + + vs = qemu_malloc(sizeof(*vs)); + vs->pdu = pdu; + vs->offset = 7; + vs->mode = 0; + + if (s->proto_version == V9FS_PROTO_2000L) { + pdu_unmarshal(vs->pdu, vs->offset, "dd", &fid, &vs->mode); + } else { + pdu_unmarshal(vs->pdu, vs->offset, "db", &fid, &vs->mode); + } + + vs->fidp = lookup_fid(s, fid); + if (vs->fidp == NULL) { + err = -ENOENT; + goto out; + } + + BUG_ON(vs->fidp->fid_type != P9_FID_NONE); + + err = v9fs_do_lstat(s, &vs->fidp->path, &vs->stbuf); + + v9fs_open_post_lstat(s, vs, err); + return; +out: + complete_pdu(s, pdu, err); + qemu_free(vs); +} + +static void v9fs_post_lcreate(V9fsState *s, V9fsLcreateState *vs, int err) +{ + if (err == 0) { + v9fs_string_copy(&vs->fidp->path, &vs->fullname); + stat_to_qid(&vs->stbuf, &vs->qid); + vs->offset += pdu_marshal(vs->pdu, vs->offset, "Qd", &vs->qid, + &vs->iounit); + err = vs->offset; + } else { + vs->fidp->fid_type = P9_FID_NONE; + err = -errno; + if (vs->fidp->fs.fd > 0) { + close(vs->fidp->fs.fd); + } + } + + complete_pdu(s, vs->pdu, err); + v9fs_string_free(&vs->name); + v9fs_string_free(&vs->fullname); + qemu_free(vs); +} + +static void v9fs_lcreate_post_get_iounit(V9fsState *s, V9fsLcreateState *vs, + int err) +{ + if (err) { + err = -errno; + goto out; + } + err = v9fs_do_lstat(s, &vs->fullname, &vs->stbuf); + +out: + v9fs_post_lcreate(s, vs, err); +} + +static void v9fs_lcreate_post_do_open2(V9fsState *s, V9fsLcreateState *vs, + int err) +{ + if (vs->fidp->fs.fd == -1) { + err = -errno; + goto out; + } + vs->fidp->fid_type = P9_FID_FILE; + vs->iounit = get_iounit(s, &vs->fullname); + v9fs_lcreate_post_get_iounit(s, vs, err); + return; + +out: + v9fs_post_lcreate(s, vs, err); +} + +static void v9fs_lcreate(V9fsState *s, V9fsPDU *pdu) +{ + int32_t dfid, flags, mode; + gid_t gid; + V9fsLcreateState *vs; + ssize_t err = 0; + + vs = qemu_malloc(sizeof(*vs)); + vs->pdu = pdu; + vs->offset = 7; + + v9fs_string_init(&vs->fullname); + + pdu_unmarshal(vs->pdu, vs->offset, "dsddd", &dfid, &vs->name, &flags, + &mode, &gid); + + vs->fidp = lookup_fid(s, dfid); + if (vs->fidp == NULL) { + err = -ENOENT; + goto out; + } + + v9fs_string_sprintf(&vs->fullname, "%s/%s", vs->fidp->path.data, + vs->name.data); + + /* Ignore direct disk access hint until the server supports it. */ + flags &= ~O_DIRECT; + + vs->fidp->fs.fd = v9fs_do_open2(s, vs->fullname.data, vs->fidp->uid, + gid, flags, mode); + v9fs_lcreate_post_do_open2(s, vs, err); + return; + +out: + complete_pdu(s, vs->pdu, err); + v9fs_string_free(&vs->name); + qemu_free(vs); +} + +static void v9fs_post_do_fsync(V9fsState *s, V9fsPDU *pdu, int err) +{ + if (err == -1) { + err = -errno; + } + complete_pdu(s, pdu, err); +} + +static void v9fs_fsync(V9fsState *s, V9fsPDU *pdu) +{ + int32_t fid; + size_t offset = 7; + V9fsFidState *fidp; + int datasync; + int err; + + pdu_unmarshal(pdu, offset, "dd", &fid, &datasync); + fidp = lookup_fid(s, fid); + if (fidp == NULL) { + err = -ENOENT; + v9fs_post_do_fsync(s, pdu, err); + return; + } + err = v9fs_do_fsync(s, fidp->fs.fd, datasync); + v9fs_post_do_fsync(s, pdu, err); +} + +static void v9fs_clunk(V9fsState *s, V9fsPDU *pdu) +{ + int32_t fid; + size_t offset = 7; + int err; + + pdu_unmarshal(pdu, offset, "d", &fid); + + err = free_fid(s, fid); + if (err < 0) { + goto out; + } + + offset = 7; + err = offset; +out: + complete_pdu(s, pdu, err); +} + +static void v9fs_read_post_readdir(V9fsState *, V9fsReadState *, ssize_t); + +static void v9fs_read_post_seekdir(V9fsState *s, V9fsReadState *vs, ssize_t err) +{ + if (err) { + goto out; + } + v9fs_stat_free(&vs->v9stat); + v9fs_string_free(&vs->name); + vs->offset += pdu_marshal(vs->pdu, vs->offset, "d", vs->count); + vs->offset += vs->count; + err = vs->offset; +out: + complete_pdu(s, vs->pdu, err); + qemu_free(vs); + return; +} + +static void v9fs_read_post_dir_lstat(V9fsState *s, V9fsReadState *vs, + ssize_t err) +{ + if (err) { + err = -errno; + goto out; + } + err = stat_to_v9stat(s, &vs->name, &vs->stbuf, &vs->v9stat); + if (err) { + goto out; + } + + vs->len = pdu_marshal(vs->pdu, vs->offset + 4 + vs->count, "S", + &vs->v9stat); + if ((vs->len != (vs->v9stat.size + 2)) || + ((vs->count + vs->len) > vs->max_count)) { + v9fs_do_seekdir(s, vs->fidp->fs.dir, vs->dir_pos); + v9fs_read_post_seekdir(s, vs, err); + return; + } + vs->count += vs->len; + v9fs_stat_free(&vs->v9stat); + v9fs_string_free(&vs->name); + vs->dir_pos = vs->dent->d_off; + vs->dent = v9fs_do_readdir(s, vs->fidp->fs.dir); + v9fs_read_post_readdir(s, vs, err); + return; +out: + v9fs_do_seekdir(s, vs->fidp->fs.dir, vs->dir_pos); + v9fs_read_post_seekdir(s, vs, err); + return; + +} + +static void v9fs_read_post_readdir(V9fsState *s, V9fsReadState *vs, ssize_t err) +{ + if (vs->dent) { + memset(&vs->v9stat, 0, sizeof(vs->v9stat)); + v9fs_string_init(&vs->name); + v9fs_string_sprintf(&vs->name, "%s/%s", vs->fidp->path.data, + vs->dent->d_name); + err = v9fs_do_lstat(s, &vs->name, &vs->stbuf); + v9fs_read_post_dir_lstat(s, vs, err); + return; + } + + vs->offset += pdu_marshal(vs->pdu, vs->offset, "d", vs->count); + vs->offset += vs->count; + err = vs->offset; + complete_pdu(s, vs->pdu, err); + qemu_free(vs); + return; +} + +static void v9fs_read_post_telldir(V9fsState *s, V9fsReadState *vs, ssize_t err) +{ + vs->dent = v9fs_do_readdir(s, vs->fidp->fs.dir); + v9fs_read_post_readdir(s, vs, err); + return; +} + +static void v9fs_read_post_rewinddir(V9fsState *s, V9fsReadState *vs, + ssize_t err) +{ + vs->dir_pos = v9fs_do_telldir(s, vs->fidp->fs.dir); + v9fs_read_post_telldir(s, vs, err); + return; +} + +static void v9fs_read_post_preadv(V9fsState *s, V9fsReadState *vs, ssize_t err) +{ + if (err < 0) { + /* IO error return the error */ + err = -errno; + goto out; + } + vs->total += vs->len; + vs->sg = adjust_sg(vs->sg, vs->len, &vs->cnt); + if (vs->total < vs->count && vs->len > 0) { + do { + if (0) { + print_sg(vs->sg, vs->cnt); + } + vs->len = v9fs_do_preadv(s, vs->fidp->fs.fd, vs->sg, vs->cnt, + vs->off); + if (vs->len > 0) { + vs->off += vs->len; + } + } while (vs->len == -1 && errno == EINTR); + if (vs->len == -1) { + err = -errno; + } + v9fs_read_post_preadv(s, vs, err); + return; + } + vs->offset += pdu_marshal(vs->pdu, vs->offset, "d", vs->total); + vs->offset += vs->count; + err = vs->offset; + +out: + complete_pdu(s, vs->pdu, err); + qemu_free(vs); +} + +static void v9fs_xattr_read(V9fsState *s, V9fsReadState *vs) +{ + ssize_t err = 0; + int read_count; + int64_t xattr_len; + + xattr_len = vs->fidp->fs.xattr.len; + read_count = xattr_len - vs->off; + if (read_count > vs->count) { + read_count = vs->count; + } else if (read_count < 0) { + /* + * read beyond XATTR value + */ + read_count = 0; + } + vs->offset += pdu_marshal(vs->pdu, vs->offset, "d", read_count); + vs->offset += pdu_pack(vs->pdu, vs->offset, + ((char *)vs->fidp->fs.xattr.value) + vs->off, + read_count); + err = vs->offset; + complete_pdu(s, vs->pdu, err); + qemu_free(vs); +} + +static void v9fs_read(V9fsState *s, V9fsPDU *pdu) +{ + int32_t fid; + V9fsReadState *vs; + ssize_t err = 0; + + vs = qemu_malloc(sizeof(*vs)); + vs->pdu = pdu; + vs->offset = 7; + vs->total = 0; + vs->len = 0; + vs->count = 0; + + pdu_unmarshal(vs->pdu, vs->offset, "dqd", &fid, &vs->off, &vs->count); + + vs->fidp = lookup_fid(s, fid); + if (vs->fidp == NULL) { + err = -EINVAL; + goto out; + } + + if (vs->fidp->fid_type == P9_FID_DIR) { + vs->max_count = vs->count; + vs->count = 0; + if (vs->off == 0) { + v9fs_do_rewinddir(s, vs->fidp->fs.dir); + } + v9fs_read_post_rewinddir(s, vs, err); + return; + } else if (vs->fidp->fid_type == P9_FID_FILE) { + vs->sg = vs->iov; + pdu_marshal(vs->pdu, vs->offset + 4, "v", vs->sg, &vs->cnt); + vs->sg = cap_sg(vs->sg, vs->count, &vs->cnt); + if (vs->total <= vs->count) { + vs->len = v9fs_do_preadv(s, vs->fidp->fs.fd, vs->sg, vs->cnt, + vs->off); + if (vs->len > 0) { + vs->off += vs->len; + } + err = vs->len; + v9fs_read_post_preadv(s, vs, err); + } + return; + } else if (vs->fidp->fid_type == P9_FID_XATTR) { + v9fs_xattr_read(s, vs); + return; + } else { + err = -EINVAL; + } +out: + complete_pdu(s, pdu, err); + qemu_free(vs); +} + +typedef struct V9fsReadDirState { + V9fsPDU *pdu; + V9fsFidState *fidp; + V9fsQID qid; + off_t saved_dir_pos; + struct dirent *dent; + int32_t count; + int32_t max_count; + size_t offset; + int64_t initial_offset; + V9fsString name; +} V9fsReadDirState; + +static void v9fs_readdir_post_seekdir(V9fsState *s, V9fsReadDirState *vs) +{ + vs->offset += pdu_marshal(vs->pdu, vs->offset, "d", vs->count); + vs->offset += vs->count; + complete_pdu(s, vs->pdu, vs->offset); + qemu_free(vs); + return; +} + +/* 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) + */ +#define V9_READDIR_DATA_SZ (24 + strlen(vs->name.data)) + +static void v9fs_readdir_post_readdir(V9fsState *s, V9fsReadDirState *vs) +{ + int len; + size_t size; + + if (vs->dent) { + v9fs_string_init(&vs->name); + v9fs_string_sprintf(&vs->name, "%s", vs->dent->d_name); + + if ((vs->count + V9_READDIR_DATA_SZ) > vs->max_count) { + /* Ran out of buffer. Set dir back to old position and return */ + v9fs_do_seekdir(s, vs->fidp->fs.dir, vs->saved_dir_pos); + v9fs_readdir_post_seekdir(s, vs); + return; + } + + /* 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(vs->dent->d_ino), sizeof(vs->qid.path)); + memcpy(&vs->qid.path, &vs->dent->d_ino, size); + /* Fill the other fields with dummy values */ + vs->qid.type = 0; + vs->qid.version = 0; + + len = pdu_marshal(vs->pdu, vs->offset+4+vs->count, "Qqbs", + &vs->qid, vs->dent->d_off, + vs->dent->d_type, &vs->name); + vs->count += len; + v9fs_string_free(&vs->name); + vs->saved_dir_pos = vs->dent->d_off; + vs->dent = v9fs_do_readdir(s, vs->fidp->fs.dir); + v9fs_readdir_post_readdir(s, vs); + return; + } + + vs->offset += pdu_marshal(vs->pdu, vs->offset, "d", vs->count); + vs->offset += vs->count; + complete_pdu(s, vs->pdu, vs->offset); + qemu_free(vs); + return; +} + +static void v9fs_readdir_post_telldir(V9fsState *s, V9fsReadDirState *vs) +{ + vs->dent = v9fs_do_readdir(s, vs->fidp->fs.dir); + v9fs_readdir_post_readdir(s, vs); + return; +} + +static void v9fs_readdir_post_setdir(V9fsState *s, V9fsReadDirState *vs) +{ + vs->saved_dir_pos = v9fs_do_telldir(s, vs->fidp->fs.dir); + v9fs_readdir_post_telldir(s, vs); + return; +} + +static void v9fs_readdir(V9fsState *s, V9fsPDU *pdu) +{ + int32_t fid; + V9fsReadDirState *vs; + ssize_t err = 0; + size_t offset = 7; + + vs = qemu_malloc(sizeof(*vs)); + vs->pdu = pdu; + vs->offset = 7; + vs->count = 0; + + pdu_unmarshal(vs->pdu, offset, "dqd", &fid, &vs->initial_offset, + &vs->max_count); + + vs->fidp = lookup_fid(s, fid); + if (vs->fidp == NULL || !(vs->fidp->fs.dir)) { + err = -EINVAL; + goto out; + } + + if (vs->initial_offset == 0) { + v9fs_do_rewinddir(s, vs->fidp->fs.dir); + } else { + v9fs_do_seekdir(s, vs->fidp->fs.dir, vs->initial_offset); + } + + v9fs_readdir_post_setdir(s, vs); + return; + +out: + complete_pdu(s, pdu, err); + qemu_free(vs); + return; +} + +static void v9fs_write_post_pwritev(V9fsState *s, V9fsWriteState *vs, + ssize_t err) +{ + if (err < 0) { + /* IO error return the error */ + err = -errno; + goto out; + } + vs->total += vs->len; + vs->sg = adjust_sg(vs->sg, vs->len, &vs->cnt); + if (vs->total < vs->count && vs->len > 0) { + do { + if (0) { + print_sg(vs->sg, vs->cnt); + } + vs->len = v9fs_do_pwritev(s, vs->fidp->fs.fd, vs->sg, vs->cnt, + vs->off); + if (vs->len > 0) { + vs->off += vs->len; + } + } while (vs->len == -1 && errno == EINTR); + if (vs->len == -1) { + err = -errno; + } + v9fs_write_post_pwritev(s, vs, err); + return; + } + vs->offset += pdu_marshal(vs->pdu, vs->offset, "d", vs->total); + err = vs->offset; +out: + complete_pdu(s, vs->pdu, err); + qemu_free(vs); +} + +static void v9fs_xattr_write(V9fsState *s, V9fsWriteState *vs) +{ + int i, to_copy; + ssize_t err = 0; + int write_count; + int64_t xattr_len; + + xattr_len = vs->fidp->fs.xattr.len; + write_count = xattr_len - vs->off; + if (write_count > vs->count) { + write_count = vs->count; + } else if (write_count < 0) { + /* + * write beyond XATTR value len specified in + * xattrcreate + */ + err = -ENOSPC; + goto out; + } + vs->offset += pdu_marshal(vs->pdu, vs->offset, "d", write_count); + err = vs->offset; + vs->fidp->fs.xattr.copied_len += write_count; + /* + * Now copy the content from sg list + */ + for (i = 0; i < vs->cnt; i++) { + if (write_count > vs->sg[i].iov_len) { + to_copy = vs->sg[i].iov_len; + } else { + to_copy = write_count; + } + memcpy((char *)vs->fidp->fs.xattr.value + vs->off, + vs->sg[i].iov_base, to_copy); + /* updating vs->off since we are not using below */ + vs->off += to_copy; + write_count -= to_copy; + } +out: + complete_pdu(s, vs->pdu, err); + qemu_free(vs); +} + +static void v9fs_write(V9fsState *s, V9fsPDU *pdu) +{ + int32_t fid; + V9fsWriteState *vs; + ssize_t err; + + vs = qemu_malloc(sizeof(*vs)); + + vs->pdu = pdu; + vs->offset = 7; + vs->sg = vs->iov; + vs->total = 0; + vs->len = 0; + + pdu_unmarshal(vs->pdu, vs->offset, "dqdv", &fid, &vs->off, &vs->count, + vs->sg, &vs->cnt); + + vs->fidp = lookup_fid(s, fid); + if (vs->fidp == NULL) { + err = -EINVAL; + goto out; + } + + if (vs->fidp->fid_type == P9_FID_FILE) { + if (vs->fidp->fs.fd == -1) { + err = -EINVAL; + goto out; + } + } else if (vs->fidp->fid_type == P9_FID_XATTR) { + /* + * setxattr operation + */ + v9fs_xattr_write(s, vs); + return; + } else { + err = -EINVAL; + goto out; + } + vs->sg = cap_sg(vs->sg, vs->count, &vs->cnt); + if (vs->total <= vs->count) { + vs->len = v9fs_do_pwritev(s, vs->fidp->fs.fd, vs->sg, vs->cnt, vs->off); + if (vs->len > 0) { + vs->off += vs->len; + } + err = vs->len; + v9fs_write_post_pwritev(s, vs, err); + } + return; +out: + complete_pdu(s, vs->pdu, err); + qemu_free(vs); +} + +static void v9fs_create_post_getiounit(V9fsState *s, V9fsCreateState *vs) +{ + int err; + v9fs_string_copy(&vs->fidp->path, &vs->fullname); + stat_to_qid(&vs->stbuf, &vs->qid); + + vs->offset += pdu_marshal(vs->pdu, vs->offset, "Qd", &vs->qid, vs->iounit); + err = vs->offset; + + complete_pdu(s, vs->pdu, err); + v9fs_string_free(&vs->name); + v9fs_string_free(&vs->extension); + v9fs_string_free(&vs->fullname); + qemu_free(vs); +} + +static void v9fs_post_create(V9fsState *s, V9fsCreateState *vs, int err) +{ + if (err == 0) { + vs->iounit = get_iounit(s, &vs->fidp->path); + v9fs_create_post_getiounit(s, vs); + return; + } + + complete_pdu(s, vs->pdu, err); + v9fs_string_free(&vs->name); + v9fs_string_free(&vs->extension); + v9fs_string_free(&vs->fullname); + qemu_free(vs); +} + +static void v9fs_create_post_perms(V9fsState *s, V9fsCreateState *vs, int err) +{ + if (err) { + err = -errno; + } + v9fs_post_create(s, vs, err); +} + +static void v9fs_create_post_opendir(V9fsState *s, V9fsCreateState *vs, + int err) +{ + if (!vs->fidp->fs.dir) { + err = -errno; + } + vs->fidp->fid_type = P9_FID_DIR; + v9fs_post_create(s, vs, err); +} + +static void v9fs_create_post_dir_lstat(V9fsState *s, V9fsCreateState *vs, + int err) +{ + if (err) { + err = -errno; + goto out; + } + + vs->fidp->fs.dir = v9fs_do_opendir(s, &vs->fullname); + v9fs_create_post_opendir(s, vs, err); + return; + +out: + v9fs_post_create(s, vs, err); +} + +static void v9fs_create_post_mkdir(V9fsState *s, V9fsCreateState *vs, int err) +{ + if (err) { + err = -errno; + goto out; + } + + err = v9fs_do_lstat(s, &vs->fullname, &vs->stbuf); + v9fs_create_post_dir_lstat(s, vs, err); + return; + +out: + v9fs_post_create(s, vs, err); +} + +static void v9fs_create_post_fstat(V9fsState *s, V9fsCreateState *vs, int err) +{ + if (err) { + vs->fidp->fid_type = P9_FID_NONE; + close(vs->fidp->fs.fd); + err = -errno; + } + v9fs_post_create(s, vs, err); + return; +} + +static void v9fs_create_post_open2(V9fsState *s, V9fsCreateState *vs, int err) +{ + if (vs->fidp->fs.fd == -1) { + err = -errno; + goto out; + } + vs->fidp->fid_type = P9_FID_FILE; + err = v9fs_do_fstat(s, vs->fidp->fs.fd, &vs->stbuf); + v9fs_create_post_fstat(s, vs, err); + + return; + +out: + v9fs_post_create(s, vs, err); + +} + +static void v9fs_create_post_lstat(V9fsState *s, V9fsCreateState *vs, int err) +{ + + if (err == 0 || errno != ENOENT) { + err = -errno; + goto out; + } + + if (vs->perm & P9_STAT_MODE_DIR) { + err = v9fs_do_mkdir(s, vs->fullname.data, vs->perm & 0777, + vs->fidp->uid, -1); + v9fs_create_post_mkdir(s, vs, err); + } else if (vs->perm & P9_STAT_MODE_SYMLINK) { + err = v9fs_do_symlink(s, vs->fidp, vs->extension.data, + vs->fullname.data, -1); + v9fs_create_post_perms(s, vs, err); + } else if (vs->perm & P9_STAT_MODE_LINK) { + int32_t nfid = atoi(vs->extension.data); + V9fsFidState *nfidp = lookup_fid(s, nfid); + if (nfidp == NULL) { + err = -errno; + v9fs_post_create(s, vs, err); + } + err = v9fs_do_link(s, &nfidp->path, &vs->fullname); + v9fs_create_post_perms(s, vs, err); + } else if (vs->perm & P9_STAT_MODE_DEVICE) { + char ctype; + uint32_t major, minor; + mode_t nmode = 0; + + if (sscanf(vs->extension.data, "%c %u %u", &ctype, &major, + &minor) != 3) { + err = -errno; + v9fs_post_create(s, vs, err); + } + + switch (ctype) { + case 'c': + nmode = S_IFCHR; + break; + case 'b': + nmode = S_IFBLK; + break; + default: + err = -EIO; + v9fs_post_create(s, vs, err); + } + + nmode |= vs->perm & 0777; + err = v9fs_do_mknod(s, vs->fullname.data, nmode, + makedev(major, minor), vs->fidp->uid, -1); + v9fs_create_post_perms(s, vs, err); + } else if (vs->perm & P9_STAT_MODE_NAMED_PIPE) { + err = v9fs_do_mknod(s, vs->fullname.data, S_IFIFO | (vs->perm & 0777), + 0, vs->fidp->uid, -1); + v9fs_post_create(s, vs, err); + } else if (vs->perm & P9_STAT_MODE_SOCKET) { + err = v9fs_do_mknod(s, vs->fullname.data, S_IFSOCK | (vs->perm & 0777), + 0, vs->fidp->uid, -1); + v9fs_post_create(s, vs, err); + } else { + vs->fidp->fs.fd = v9fs_do_open2(s, vs->fullname.data, vs->fidp->uid, + -1, omode_to_uflags(vs->mode)|O_CREAT, vs->perm); + + v9fs_create_post_open2(s, vs, err); + } + + return; + +out: + v9fs_post_create(s, vs, err); +} + +static void v9fs_create(V9fsState *s, V9fsPDU *pdu) +{ + int32_t fid; + V9fsCreateState *vs; + int err = 0; + + vs = qemu_malloc(sizeof(*vs)); + vs->pdu = pdu; + vs->offset = 7; + + v9fs_string_init(&vs->fullname); + + pdu_unmarshal(vs->pdu, vs->offset, "dsdbs", &fid, &vs->name, + &vs->perm, &vs->mode, &vs->extension); + + vs->fidp = lookup_fid(s, fid); + if (vs->fidp == NULL) { + err = -EINVAL; + goto out; + } + + v9fs_string_sprintf(&vs->fullname, "%s/%s", vs->fidp->path.data, + vs->name.data); + + err = v9fs_do_lstat(s, &vs->fullname, &vs->stbuf); + v9fs_create_post_lstat(s, vs, err); + return; + +out: + complete_pdu(s, vs->pdu, err); + v9fs_string_free(&vs->name); + v9fs_string_free(&vs->extension); + qemu_free(vs); +} + +static void v9fs_post_symlink(V9fsState *s, V9fsSymlinkState *vs, int err) +{ + if (err == 0) { + stat_to_qid(&vs->stbuf, &vs->qid); + vs->offset += pdu_marshal(vs->pdu, vs->offset, "Q", &vs->qid); + err = vs->offset; + } else { + err = -errno; + } + complete_pdu(s, vs->pdu, err); + v9fs_string_free(&vs->name); + v9fs_string_free(&vs->symname); + v9fs_string_free(&vs->fullname); + qemu_free(vs); +} + +static void v9fs_symlink_post_do_symlink(V9fsState *s, V9fsSymlinkState *vs, + int err) +{ + if (err) { + goto out; + } + err = v9fs_do_lstat(s, &vs->fullname, &vs->stbuf); +out: + v9fs_post_symlink(s, vs, err); +} + +static void v9fs_symlink(V9fsState *s, V9fsPDU *pdu) +{ + int32_t dfid; + V9fsSymlinkState *vs; + int err = 0; + gid_t gid; + + vs = qemu_malloc(sizeof(*vs)); + vs->pdu = pdu; + vs->offset = 7; + + v9fs_string_init(&vs->fullname); + + pdu_unmarshal(vs->pdu, vs->offset, "dssd", &dfid, &vs->name, + &vs->symname, &gid); + + vs->dfidp = lookup_fid(s, dfid); + if (vs->dfidp == NULL) { + err = -EINVAL; + goto out; + } + + v9fs_string_sprintf(&vs->fullname, "%s/%s", vs->dfidp->path.data, + vs->name.data); + err = v9fs_do_symlink(s, vs->dfidp, vs->symname.data, + vs->fullname.data, gid); + v9fs_symlink_post_do_symlink(s, vs, err); + return; + +out: + complete_pdu(s, vs->pdu, err); + v9fs_string_free(&vs->name); + v9fs_string_free(&vs->symname); + qemu_free(vs); +} + +static void v9fs_flush(V9fsState *s, V9fsPDU *pdu) +{ + /* A nop call with no return */ + complete_pdu(s, pdu, 7); +} + +static void v9fs_link(V9fsState *s, V9fsPDU *pdu) +{ + int32_t dfid, oldfid; + V9fsFidState *dfidp, *oldfidp; + V9fsString name, fullname; + size_t offset = 7; + int err = 0; + + v9fs_string_init(&fullname); + + pdu_unmarshal(pdu, offset, "dds", &dfid, &oldfid, &name); + + dfidp = lookup_fid(s, dfid); + if (dfidp == NULL) { + err = -errno; + goto out; + } + + oldfidp = lookup_fid(s, oldfid); + if (oldfidp == NULL) { + err = -errno; + goto out; + } + + v9fs_string_sprintf(&fullname, "%s/%s", dfidp->path.data, name.data); + err = offset; + err = v9fs_do_link(s, &oldfidp->path, &fullname); + if (err) { + err = -errno; + } + v9fs_string_free(&fullname); + +out: + v9fs_string_free(&name); + complete_pdu(s, pdu, err); +} + +static void v9fs_remove_post_remove(V9fsState *s, V9fsRemoveState *vs, + int err) +{ + if (err < 0) { + err = -errno; + } else { + err = vs->offset; + } + + /* For TREMOVE we need to clunk the fid even on failed remove */ + free_fid(s, vs->fidp->fid); + + complete_pdu(s, vs->pdu, err); + qemu_free(vs); +} + +static void v9fs_remove(V9fsState *s, V9fsPDU *pdu) +{ + int32_t fid; + V9fsRemoveState *vs; + int err = 0; + + vs = qemu_malloc(sizeof(*vs)); + vs->pdu = pdu; + vs->offset = 7; + + pdu_unmarshal(vs->pdu, vs->offset, "d", &fid); + + vs->fidp = lookup_fid(s, fid); + if (vs->fidp == NULL) { + err = -EINVAL; + goto out; + } + + err = v9fs_do_remove(s, &vs->fidp->path); + v9fs_remove_post_remove(s, vs, err); + return; + +out: + complete_pdu(s, pdu, err); + qemu_free(vs); +} + +static void v9fs_wstat_post_truncate(V9fsState *s, V9fsWstatState *vs, int err) +{ + if (err < 0) { + goto out; + } + + err = vs->offset; + +out: + v9fs_stat_free(&vs->v9stat); + complete_pdu(s, vs->pdu, err); + qemu_free(vs); +} + +static void v9fs_wstat_post_rename(V9fsState *s, V9fsWstatState *vs, int err) +{ + if (err < 0) { + goto out; + } + if (vs->v9stat.length != -1) { + if (v9fs_do_truncate(s, &vs->fidp->path, vs->v9stat.length) < 0) { + err = -errno; + } + } + v9fs_wstat_post_truncate(s, vs, err); + return; + +out: + v9fs_stat_free(&vs->v9stat); + complete_pdu(s, vs->pdu, err); + qemu_free(vs); +} + +static int v9fs_complete_rename(V9fsState *s, V9fsRenameState *vs) +{ + int err = 0; + char *old_name, *new_name; + char *end; + + if (vs->newdirfid != -1) { + V9fsFidState *dirfidp; + dirfidp = lookup_fid(s, vs->newdirfid); + + if (dirfidp == NULL) { + err = -ENOENT; + goto out; + } + + BUG_ON(dirfidp->fid_type != P9_FID_NONE); + + new_name = qemu_mallocz(dirfidp->path.size + vs->name.size + 2); + + strcpy(new_name, dirfidp->path.data); + strcat(new_name, "/"); + strcat(new_name + dirfidp->path.size, vs->name.data); + } else { + old_name = vs->fidp->path.data; + end = strrchr(old_name, '/'); + if (end) { + end++; + } else { + end = old_name; + } + new_name = qemu_mallocz(end - old_name + vs->name.size + 1); + + strncat(new_name, old_name, end - old_name); + strncat(new_name + (end - old_name), vs->name.data, vs->name.size); + } + + v9fs_string_free(&vs->name); + vs->name.data = qemu_strdup(new_name); + vs->name.size = strlen(new_name); + + if (strcmp(new_name, vs->fidp->path.data) != 0) { + if (v9fs_do_rename(s, &vs->fidp->path, &vs->name)) { + err = -errno; + } else { + V9fsFidState *fidp; + /* + * Fixup fid's pointing to the old name to + * start pointing to the new name + */ + for (fidp = s->fid_list; fidp; fidp = fidp->next) { + if (vs->fidp == fidp) { + /* + * we replace name of this fid towards the end + * so that our below strcmp will work + */ + continue; + } + if (!strncmp(vs->fidp->path.data, fidp->path.data, + strlen(vs->fidp->path.data))) { + /* replace the name */ + v9fs_fix_path(&fidp->path, &vs->name, + strlen(vs->fidp->path.data)); + } + } + v9fs_string_copy(&vs->fidp->path, &vs->name); + } + } +out: + v9fs_string_free(&vs->name); + return err; +} + +static void v9fs_rename_post_rename(V9fsState *s, V9fsRenameState *vs, int err) +{ + complete_pdu(s, vs->pdu, err); + qemu_free(vs); +} + +static void v9fs_wstat_post_chown(V9fsState *s, V9fsWstatState *vs, int err) +{ + if (err < 0) { + goto out; + } + + if (vs->v9stat.name.size != 0) { + V9fsRenameState *vr; + + vr = qemu_mallocz(sizeof(V9fsRenameState)); + vr->newdirfid = -1; + vr->pdu = vs->pdu; + vr->fidp = vs->fidp; + vr->offset = vs->offset; + vr->name.size = vs->v9stat.name.size; + vr->name.data = qemu_strdup(vs->v9stat.name.data); + + err = v9fs_complete_rename(s, vr); + qemu_free(vr); + } + v9fs_wstat_post_rename(s, vs, err); + return; + +out: + v9fs_stat_free(&vs->v9stat); + complete_pdu(s, vs->pdu, err); + qemu_free(vs); +} + +static void v9fs_rename(V9fsState *s, V9fsPDU *pdu) +{ + int32_t fid; + V9fsRenameState *vs; + ssize_t err = 0; + + vs = qemu_malloc(sizeof(*vs)); + vs->pdu = pdu; + vs->offset = 7; + + pdu_unmarshal(vs->pdu, vs->offset, "dds", &fid, &vs->newdirfid, &vs->name); + + vs->fidp = lookup_fid(s, fid); + if (vs->fidp == NULL) { + err = -ENOENT; + goto out; + } + + BUG_ON(vs->fidp->fid_type != P9_FID_NONE); + + err = v9fs_complete_rename(s, vs); + v9fs_rename_post_rename(s, vs, err); + return; +out: + complete_pdu(s, vs->pdu, err); + qemu_free(vs); +} + +static void v9fs_wstat_post_utime(V9fsState *s, V9fsWstatState *vs, int err) +{ + if (err < 0) { + goto out; + } + + if (vs->v9stat.n_gid != -1 || vs->v9stat.n_uid != -1) { + if (v9fs_do_chown(s, &vs->fidp->path, vs->v9stat.n_uid, + vs->v9stat.n_gid)) { + err = -errno; + } + } + v9fs_wstat_post_chown(s, vs, err); + return; + +out: + v9fs_stat_free(&vs->v9stat); + complete_pdu(s, vs->pdu, err); + qemu_free(vs); +} + +static void v9fs_wstat_post_chmod(V9fsState *s, V9fsWstatState *vs, int err) +{ + if (err < 0) { + goto out; + } + + if (vs->v9stat.mtime != -1 || vs->v9stat.atime != -1) { + struct timespec times[2]; + if (vs->v9stat.atime != -1) { + times[0].tv_sec = vs->v9stat.atime; + times[0].tv_nsec = 0; + } else { + times[0].tv_nsec = UTIME_OMIT; + } + if (vs->v9stat.mtime != -1) { + times[1].tv_sec = vs->v9stat.mtime; + times[1].tv_nsec = 0; + } else { + times[1].tv_nsec = UTIME_OMIT; + } + + if (v9fs_do_utimensat(s, &vs->fidp->path, times)) { + err = -errno; + } + } + + v9fs_wstat_post_utime(s, vs, err); + return; + +out: + v9fs_stat_free(&vs->v9stat); + complete_pdu(s, vs->pdu, err); + qemu_free(vs); +} + +static void v9fs_wstat_post_fsync(V9fsState *s, V9fsWstatState *vs, int err) +{ + if (err == -1) { + err = -errno; + } + v9fs_stat_free(&vs->v9stat); + complete_pdu(s, vs->pdu, err); + qemu_free(vs); +} + +static void v9fs_wstat_post_lstat(V9fsState *s, V9fsWstatState *vs, int err) +{ + uint32_t v9_mode; + + if (err == -1) { + err = -errno; + goto out; + } + + v9_mode = stat_to_v9mode(&vs->stbuf); + + if ((vs->v9stat.mode & P9_STAT_MODE_TYPE_BITS) != + (v9_mode & P9_STAT_MODE_TYPE_BITS)) { + /* Attempting to change the type */ + err = -EIO; + goto out; + } + + if (v9fs_do_chmod(s, &vs->fidp->path, v9mode_to_mode(vs->v9stat.mode, + &vs->v9stat.extension))) { + err = -errno; + } + v9fs_wstat_post_chmod(s, vs, err); + return; + +out: + v9fs_stat_free(&vs->v9stat); + complete_pdu(s, vs->pdu, err); + qemu_free(vs); +} + +static void v9fs_wstat(V9fsState *s, V9fsPDU *pdu) +{ + int32_t fid; + V9fsWstatState *vs; + int err = 0; + + vs = qemu_malloc(sizeof(*vs)); + vs->pdu = pdu; + vs->offset = 7; + + pdu_unmarshal(pdu, vs->offset, "dwS", &fid, &vs->unused, &vs->v9stat); + + vs->fidp = lookup_fid(s, fid); + if (vs->fidp == NULL) { + err = -EINVAL; + goto out; + } + + /* do we need to sync the file? */ + if (donttouch_stat(&vs->v9stat)) { + err = v9fs_do_fsync(s, vs->fidp->fs.fd, 0); + v9fs_wstat_post_fsync(s, vs, err); + return; + } + + if (vs->v9stat.mode != -1) { + err = v9fs_do_lstat(s, &vs->fidp->path, &vs->stbuf); + v9fs_wstat_post_lstat(s, vs, err); + return; + } + + v9fs_wstat_post_chmod(s, vs, err); + return; + +out: + v9fs_stat_free(&vs->v9stat); + complete_pdu(s, vs->pdu, err); + qemu_free(vs); +} + +static void v9fs_statfs_post_statfs(V9fsState *s, V9fsStatfsState *vs, int err) +{ + int32_t bsize_factor; + + if (err) { + err = -errno; + goto out; + } + + /* + * compute bsize factor based on host file system block size + * and client msize + */ + bsize_factor = (s->msize - P9_IOHDRSZ)/vs->stbuf.f_bsize; + if (!bsize_factor) { + bsize_factor = 1; + } + vs->v9statfs.f_type = vs->stbuf.f_type; + vs->v9statfs.f_bsize = vs->stbuf.f_bsize; + vs->v9statfs.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 + */ + vs->v9statfs.f_blocks = vs->stbuf.f_blocks/bsize_factor; + vs->v9statfs.f_bfree = vs->stbuf.f_bfree/bsize_factor; + vs->v9statfs.f_bavail = vs->stbuf.f_bavail/bsize_factor; + vs->v9statfs.f_files = vs->stbuf.f_files; + vs->v9statfs.f_ffree = vs->stbuf.f_ffree; + vs->v9statfs.fsid_val = (unsigned int) vs->stbuf.f_fsid.__val[0] | + (unsigned long long)vs->stbuf.f_fsid.__val[1] << 32; + vs->v9statfs.f_namelen = vs->stbuf.f_namelen; + + vs->offset += pdu_marshal(vs->pdu, vs->offset, "ddqqqqqqd", + vs->v9statfs.f_type, vs->v9statfs.f_bsize, vs->v9statfs.f_blocks, + vs->v9statfs.f_bfree, vs->v9statfs.f_bavail, vs->v9statfs.f_files, + vs->v9statfs.f_ffree, vs->v9statfs.fsid_val, + vs->v9statfs.f_namelen); + +out: + complete_pdu(s, vs->pdu, vs->offset); + qemu_free(vs); +} + +static void v9fs_statfs(V9fsState *s, V9fsPDU *pdu) +{ + V9fsStatfsState *vs; + ssize_t err = 0; + + vs = qemu_malloc(sizeof(*vs)); + vs->pdu = pdu; + vs->offset = 7; + + memset(&vs->v9statfs, 0, sizeof(vs->v9statfs)); + + pdu_unmarshal(vs->pdu, vs->offset, "d", &vs->fid); + + vs->fidp = lookup_fid(s, vs->fid); + if (vs->fidp == NULL) { + err = -ENOENT; + goto out; + } + + err = v9fs_do_statfs(s, &vs->fidp->path, &vs->stbuf); + v9fs_statfs_post_statfs(s, vs, err); + return; + +out: + complete_pdu(s, vs->pdu, err); + qemu_free(vs); +} + +static void v9fs_mknod_post_lstat(V9fsState *s, V9fsMkState *vs, int err) +{ + if (err == -1) { + err = -errno; + goto out; + } + + stat_to_qid(&vs->stbuf, &vs->qid); + vs->offset += pdu_marshal(vs->pdu, vs->offset, "Q", &vs->qid); + err = vs->offset; +out: + complete_pdu(s, vs->pdu, err); + v9fs_string_free(&vs->fullname); + v9fs_string_free(&vs->name); + qemu_free(vs); +} + +static void v9fs_mknod_post_mknod(V9fsState *s, V9fsMkState *vs, int err) +{ + if (err == -1) { + err = -errno; + goto out; + } + + err = v9fs_do_lstat(s, &vs->fullname, &vs->stbuf); + v9fs_mknod_post_lstat(s, vs, err); + return; +out: + complete_pdu(s, vs->pdu, err); + v9fs_string_free(&vs->fullname); + v9fs_string_free(&vs->name); + qemu_free(vs); +} + +static void v9fs_mknod(V9fsState *s, V9fsPDU *pdu) +{ + int32_t fid; + V9fsMkState *vs; + int err = 0; + V9fsFidState *fidp; + gid_t gid; + int mode; + int major, minor; + + vs = qemu_malloc(sizeof(*vs)); + vs->pdu = pdu; + vs->offset = 7; + + v9fs_string_init(&vs->fullname); + pdu_unmarshal(vs->pdu, vs->offset, "dsdddd", &fid, &vs->name, &mode, + &major, &minor, &gid); + + fidp = lookup_fid(s, fid); + if (fidp == NULL) { + err = -ENOENT; + goto out; + } + + v9fs_string_sprintf(&vs->fullname, "%s/%s", fidp->path.data, vs->name.data); + err = v9fs_do_mknod(s, vs->fullname.data, mode, makedev(major, minor), + fidp->uid, gid); + v9fs_mknod_post_mknod(s, vs, err); + return; + +out: + complete_pdu(s, vs->pdu, err); + v9fs_string_free(&vs->fullname); + v9fs_string_free(&vs->name); + qemu_free(vs); +} + +/* + * 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(V9fsState *s, V9fsPDU *pdu) +{ + int32_t fid, err = 0; + V9fsLockState *vs; + + vs = qemu_mallocz(sizeof(*vs)); + vs->pdu = pdu; + vs->offset = 7; + + vs->flock = qemu_malloc(sizeof(*vs->flock)); + pdu_unmarshal(vs->pdu, vs->offset, "dbdqqds", &fid, &vs->flock->type, + &vs->flock->flags, &vs->flock->start, &vs->flock->length, + &vs->flock->proc_id, &vs->flock->client_id); + + vs->status = P9_LOCK_ERROR; + + /* We support only block flag now (that too ignored currently) */ + if (vs->flock->flags & ~P9_LOCK_FLAGS_BLOCK) { + err = -EINVAL; + goto out; + } + vs->fidp = lookup_fid(s, fid); + if (vs->fidp == NULL) { + err = -ENOENT; + goto out; + } + + err = v9fs_do_fstat(s, vs->fidp->fs.fd, &vs->stbuf); + if (err < 0) { + err = -errno; + goto out; + } + vs->status = P9_LOCK_SUCCESS; +out: + vs->offset += pdu_marshal(vs->pdu, vs->offset, "b", vs->status); + complete_pdu(s, vs->pdu, err); + qemu_free(vs->flock); + qemu_free(vs); +} + +/* + * When a TGETLOCK request comes, always return success because all lock + * handling is done by client's VFS layer. + */ + +static void v9fs_getlock(V9fsState *s, V9fsPDU *pdu) +{ + int32_t fid, err = 0; + V9fsGetlockState *vs; + + vs = qemu_mallocz(sizeof(*vs)); + vs->pdu = pdu; + vs->offset = 7; + + vs->glock = qemu_malloc(sizeof(*vs->glock)); + pdu_unmarshal(vs->pdu, vs->offset, "dbqqds", &fid, &vs->glock->type, + &vs->glock->start, &vs->glock->length, &vs->glock->proc_id, + &vs->glock->client_id); + + vs->fidp = lookup_fid(s, fid); + if (vs->fidp == NULL) { + err = -ENOENT; + goto out; + } + + err = v9fs_do_fstat(s, vs->fidp->fs.fd, &vs->stbuf); + if (err < 0) { + err = -errno; + goto out; + } + vs->glock->type = F_UNLCK; + vs->offset += pdu_marshal(vs->pdu, vs->offset, "bqqds", vs->glock->type, + vs->glock->start, vs->glock->length, vs->glock->proc_id, + &vs->glock->client_id); +out: + complete_pdu(s, vs->pdu, err); + qemu_free(vs->glock); + qemu_free(vs); +} + +static void v9fs_mkdir_post_lstat(V9fsState *s, V9fsMkState *vs, int err) +{ + if (err == -1) { + err = -errno; + goto out; + } + + stat_to_qid(&vs->stbuf, &vs->qid); + vs->offset += pdu_marshal(vs->pdu, vs->offset, "Q", &vs->qid); + err = vs->offset; +out: + complete_pdu(s, vs->pdu, err); + v9fs_string_free(&vs->fullname); + v9fs_string_free(&vs->name); + qemu_free(vs); +} + +static void v9fs_mkdir_post_mkdir(V9fsState *s, V9fsMkState *vs, int err) +{ + if (err == -1) { + err = -errno; + goto out; + } + + err = v9fs_do_lstat(s, &vs->fullname, &vs->stbuf); + v9fs_mkdir_post_lstat(s, vs, err); + return; +out: + complete_pdu(s, vs->pdu, err); + v9fs_string_free(&vs->fullname); + v9fs_string_free(&vs->name); + qemu_free(vs); +} + +static void v9fs_mkdir(V9fsState *s, V9fsPDU *pdu) +{ + int32_t fid; + V9fsMkState *vs; + int err = 0; + V9fsFidState *fidp; + gid_t gid; + int mode; + + vs = qemu_malloc(sizeof(*vs)); + vs->pdu = pdu; + vs->offset = 7; + + v9fs_string_init(&vs->fullname); + pdu_unmarshal(vs->pdu, vs->offset, "dsdd", &fid, &vs->name, &mode, + &gid); + + fidp = lookup_fid(s, fid); + if (fidp == NULL) { + err = -ENOENT; + goto out; + } + + v9fs_string_sprintf(&vs->fullname, "%s/%s", fidp->path.data, vs->name.data); + err = v9fs_do_mkdir(s, vs->fullname.data, mode, fidp->uid, gid); + v9fs_mkdir_post_mkdir(s, vs, err); + return; + +out: + complete_pdu(s, vs->pdu, err); + v9fs_string_free(&vs->fullname); + v9fs_string_free(&vs->name); + qemu_free(vs); +} + +static void v9fs_post_xattr_getvalue(V9fsState *s, V9fsXattrState *vs, int err) +{ + + if (err < 0) { + err = -errno; + free_fid(s, vs->xattr_fidp->fid); + goto out; + } + vs->offset += pdu_marshal(vs->pdu, vs->offset, "q", vs->size); + err = vs->offset; +out: + complete_pdu(s, vs->pdu, err); + v9fs_string_free(&vs->name); + qemu_free(vs); + return; +} + +static void v9fs_post_xattr_check(V9fsState *s, V9fsXattrState *vs, ssize_t err) +{ + if (err < 0) { + err = -errno; + free_fid(s, vs->xattr_fidp->fid); + goto out; + } + /* + * Read the xattr value + */ + vs->xattr_fidp->fs.xattr.len = vs->size; + vs->xattr_fidp->fid_type = P9_FID_XATTR; + vs->xattr_fidp->fs.xattr.copied_len = -1; + if (vs->size) { + vs->xattr_fidp->fs.xattr.value = qemu_malloc(vs->size); + err = v9fs_do_lgetxattr(s, &vs->xattr_fidp->path, + &vs->name, vs->xattr_fidp->fs.xattr.value, + vs->xattr_fidp->fs.xattr.len); + } + v9fs_post_xattr_getvalue(s, vs, err); + return; +out: + complete_pdu(s, vs->pdu, err); + v9fs_string_free(&vs->name); + qemu_free(vs); +} + +static void v9fs_post_lxattr_getvalue(V9fsState *s, + V9fsXattrState *vs, int err) +{ + if (err < 0) { + err = -errno; + free_fid(s, vs->xattr_fidp->fid); + goto out; + } + vs->offset += pdu_marshal(vs->pdu, vs->offset, "q", vs->size); + err = vs->offset; +out: + complete_pdu(s, vs->pdu, err); + v9fs_string_free(&vs->name); + qemu_free(vs); + return; +} + +static void v9fs_post_lxattr_check(V9fsState *s, + V9fsXattrState *vs, ssize_t err) +{ + if (err < 0) { + err = -errno; + free_fid(s, vs->xattr_fidp->fid); + goto out; + } + /* + * Read the xattr value + */ + vs->xattr_fidp->fs.xattr.len = vs->size; + vs->xattr_fidp->fid_type = P9_FID_XATTR; + vs->xattr_fidp->fs.xattr.copied_len = -1; + if (vs->size) { + vs->xattr_fidp->fs.xattr.value = qemu_malloc(vs->size); + err = v9fs_do_llistxattr(s, &vs->xattr_fidp->path, + vs->xattr_fidp->fs.xattr.value, + vs->xattr_fidp->fs.xattr.len); + } + v9fs_post_lxattr_getvalue(s, vs, err); + return; +out: + complete_pdu(s, vs->pdu, err); + v9fs_string_free(&vs->name); + qemu_free(vs); +} + +static void v9fs_xattrwalk(V9fsState *s, V9fsPDU *pdu) +{ + ssize_t err = 0; + V9fsXattrState *vs; + int32_t fid, newfid; + + vs = qemu_malloc(sizeof(*vs)); + vs->pdu = pdu; + vs->offset = 7; + + pdu_unmarshal(vs->pdu, vs->offset, "dds", &fid, &newfid, &vs->name); + vs->file_fidp = lookup_fid(s, fid); + if (vs->file_fidp == NULL) { + err = -ENOENT; + goto out; + } + + vs->xattr_fidp = alloc_fid(s, newfid); + if (vs->xattr_fidp == NULL) { + err = -EINVAL; + goto out; + } + + v9fs_string_copy(&vs->xattr_fidp->path, &vs->file_fidp->path); + if (vs->name.data[0] == 0) { + /* + * listxattr request. Get the size first + */ + vs->size = v9fs_do_llistxattr(s, &vs->xattr_fidp->path, + NULL, 0); + if (vs->size < 0) { + err = vs->size; + } + v9fs_post_lxattr_check(s, vs, err); + return; + } else { + /* + * specific xattr fid. We check for xattr + * presence also collect the xattr size + */ + vs->size = v9fs_do_lgetxattr(s, &vs->xattr_fidp->path, + &vs->name, NULL, 0); + if (vs->size < 0) { + err = vs->size; + } + v9fs_post_xattr_check(s, vs, err); + return; + } +out: + complete_pdu(s, vs->pdu, err); + v9fs_string_free(&vs->name); + qemu_free(vs); +} + +static void v9fs_xattrcreate(V9fsState *s, V9fsPDU *pdu) +{ + int flags; + int32_t fid; + ssize_t err = 0; + V9fsXattrState *vs; + + vs = qemu_malloc(sizeof(*vs)); + vs->pdu = pdu; + vs->offset = 7; + + pdu_unmarshal(vs->pdu, vs->offset, "dsqd", + &fid, &vs->name, &vs->size, &flags); + + vs->file_fidp = lookup_fid(s, fid); + if (vs->file_fidp == NULL) { + err = -EINVAL; + goto out; + } + + /* Make the file fid point to xattr */ + vs->xattr_fidp = vs->file_fidp; + vs->xattr_fidp->fid_type = P9_FID_XATTR; + vs->xattr_fidp->fs.xattr.copied_len = 0; + vs->xattr_fidp->fs.xattr.len = vs->size; + vs->xattr_fidp->fs.xattr.flags = flags; + v9fs_string_init(&vs->xattr_fidp->fs.xattr.name); + v9fs_string_copy(&vs->xattr_fidp->fs.xattr.name, &vs->name); + if (vs->size) + vs->xattr_fidp->fs.xattr.value = qemu_malloc(vs->size); + else + vs->xattr_fidp->fs.xattr.value = NULL; + +out: + complete_pdu(s, vs->pdu, err); + v9fs_string_free(&vs->name); + qemu_free(vs); +} + +static void v9fs_readlink_post_readlink(V9fsState *s, V9fsReadLinkState *vs, + int err) +{ + if (err < 0) { + err = -errno; + goto out; + } + vs->offset += pdu_marshal(vs->pdu, vs->offset, "s", &vs->target); + err = vs->offset; +out: + complete_pdu(s, vs->pdu, err); + v9fs_string_free(&vs->target); + qemu_free(vs); +} + +static void v9fs_readlink(V9fsState *s, V9fsPDU *pdu) +{ + int32_t fid; + V9fsReadLinkState *vs; + int err = 0; + V9fsFidState *fidp; + + vs = qemu_malloc(sizeof(*vs)); + vs->pdu = pdu; + vs->offset = 7; + + pdu_unmarshal(vs->pdu, vs->offset, "d", &fid); + + fidp = lookup_fid(s, fid); + if (fidp == NULL) { + err = -ENOENT; + goto out; + } + + v9fs_string_init(&vs->target); + err = v9fs_do_readlink(s, &fidp->path, &vs->target); + v9fs_readlink_post_readlink(s, vs, err); + return; +out: + complete_pdu(s, vs->pdu, err); + qemu_free(vs); +} + +typedef void (pdu_handler_t)(V9fsState *s, V9fsPDU *pdu); + +static pdu_handler_t *pdu_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_TREADLINK] = v9fs_readlink, + [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 submit_pdu(V9fsState *s, V9fsPDU *pdu) +{ + pdu_handler_t *handler; + + if (debug_9p_pdu) { + pprint_pdu(pdu); + } + + BUG_ON(pdu->id >= ARRAY_SIZE(pdu_handlers)); + + handler = pdu_handlers[pdu->id]; + BUG_ON(handler == NULL); + + handler(s, pdu); +} + +static 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) { + uint8_t *ptr; + + BUG_ON(pdu->elem.out_num == 0 || pdu->elem.in_num == 0); + BUG_ON(pdu->elem.out_sg[0].iov_len < 7); + + ptr = pdu->elem.out_sg[0].iov_base; + + memcpy(&pdu->size, ptr, 4); + pdu->id = ptr[4]; + memcpy(&pdu->tag, ptr + 5, 2); + + submit_pdu(s, pdu); + } + + free_pdu(s, pdu); +} + +static uint32_t virtio_9p_get_features(VirtIODevice *vdev, uint32_t features) +{ + features |= 1 << VIRTIO_9P_MOUNT_TAG; + return features; +} + +static V9fsState *to_virtio_9p(VirtIODevice *vdev) +{ + return (V9fsState *)vdev; +} + +static void virtio_9p_get_config(VirtIODevice *vdev, uint8_t *config) +{ + struct virtio_9p_config *cfg; + V9fsState *s = to_virtio_9p(vdev); + + cfg = qemu_mallocz(sizeof(struct virtio_9p_config) + + s->tag_len); + stw_raw(&cfg->tag_len, s->tag_len); + memcpy(cfg->tag, s->tag, s->tag_len); + memcpy(config, cfg, s->config_size); + qemu_free(cfg); +} + +VirtIODevice *virtio_9p_init(DeviceState *dev, V9fsConf *conf) + { + V9fsState *s; + int i, len; + struct stat stat; + FsTypeEntry *fse; + + + s = (V9fsState *)virtio_common_init("virtio-9p", + VIRTIO_ID_9P, + sizeof(struct virtio_9p_config)+ + MAX_TAG_LEN, + sizeof(V9fsState)); + + /* initialize pdu allocator */ + QLIST_INIT(&s->free_list); + for (i = 0; i < (MAX_REQ - 1); i++) { + QLIST_INSERT_HEAD(&s->free_list, &s->pdus[i], next); + } + + s->vq = virtio_add_queue(&s->vdev, MAX_REQ, handle_9p_output); + + fse = get_fsdev_fsentry(conf->fsdev_id); + + if (!fse) { + /* We don't have a fsdev identified by fsdev_id */ + fprintf(stderr, "Virtio-9p device couldn't find fsdev with the " + "id = %s\n", conf->fsdev_id ? conf->fsdev_id : "NULL"); + exit(1); + } + + if (!fse->path || !conf->tag) { + /* we haven't specified a mount_tag or the path */ + fprintf(stderr, "fsdev with id %s needs path " + "and Virtio-9p device needs mount_tag arguments\n", + conf->fsdev_id); + exit(1); + } + + if (!strcmp(fse->security_model, "passthrough")) { + /* Files on the Fileserver set to client user credentials */ + s->ctx.fs_sm = SM_PASSTHROUGH; + s->ctx.xops = passthrough_xattr_ops; + } else if (!strcmp(fse->security_model, "mapped")) { + /* Files on the fileserver are set to QEMU credentials. + * Client user credentials are saved in extended attributes. + */ + s->ctx.fs_sm = SM_MAPPED; + s->ctx.xops = mapped_xattr_ops; + } else if (!strcmp(fse->security_model, "none")) { + /* + * Files on the fileserver are set to QEMU credentials. + */ + s->ctx.fs_sm = SM_NONE; + s->ctx.xops = none_xattr_ops; + } else { + fprintf(stderr, "Default to security_model=none. You may want" + " enable advanced security model using " + "security option:\n\t security_model=passthrough \n\t " + "security_model=mapped\n"); + s->ctx.fs_sm = SM_NONE; + s->ctx.xops = none_xattr_ops; + } + + if (lstat(fse->path, &stat)) { + fprintf(stderr, "share path %s does not exist\n", fse->path); + exit(1); + } else if (!S_ISDIR(stat.st_mode)) { + fprintf(stderr, "share path %s is not a directory \n", fse->path); + exit(1); + } + + s->ctx.fs_root = qemu_strdup(fse->path); + len = strlen(conf->tag); + if (len > MAX_TAG_LEN) { + len = MAX_TAG_LEN; + } + /* s->tag is non-NULL terminated string */ + s->tag = qemu_malloc(len); + memcpy(s->tag, conf->tag, len); + s->tag_len = len; + s->ctx.uid = -1; + + s->ops = fse->ops; + s->vdev.get_features = virtio_9p_get_features; + s->config_size = sizeof(struct virtio_9p_config) + + s->tag_len; + s->vdev.get_config = virtio_9p_get_config; + + return &s->vdev; +} diff --git a/hw/virtio-9p.h b/hw/virtio-9p.h new file mode 100644 index 0000000..2ae4ce7 --- /dev/null +++ b/hw/virtio-9p.h @@ -0,0 +1,507 @@ +#ifndef _QEMU_VIRTIO_9P_H +#define _QEMU_VIRTIO_9P_H + +#include +#include +#include +#include + +#include "file-op-9p.h" + +/* The feature bitmap for virtio 9P */ +/* The mount point is specified in a config variable */ +#define VIRTIO_9P_MOUNT_TAG 0 + +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_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 + +/* + * 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 V9fsPDU +{ + uint32_t size; + uint16_t tag; + uint8_t id; + VirtQueueElement elem; + QLIST_ENTRY(V9fsPDU) next; +}; + + +/* FIXME + * 1) change user needs to set groups and stuff + */ + +/* from Linux's linux/virtio_9p.h */ + +/* The ID for virtio console */ +#define VIRTIO_ID_9P 9 +#define MAX_REQ 128 +#define MAX_TAG_LEN 32 + +#define BUG_ON(cond) assert(!(cond)) + +typedef struct V9fsFidState V9fsFidState; + +typedef struct V9fsString +{ + int16_t size; + char *data; +} V9fsString; + +typedef struct V9fsQID +{ + int8_t type; + int32_t version; + int64_t path; +} V9fsQID; + +typedef struct V9fsStat +{ + int16_t size; + int16_t type; + int32_t dev; + V9fsQID qid; + int32_t mode; + int32_t atime; + int32_t mtime; + int64_t length; + V9fsString name; + V9fsString uid; + V9fsString gid; + V9fsString muid; + /* 9p2000.u */ + V9fsString extension; + int32_t n_uid; + int32_t n_gid; + int32_t n_muid; +} V9fsStat; + +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; + +struct V9fsFidState +{ + int fid_type; + int32_t fid; + V9fsString path; + union { + int fd; + DIR *dir; + V9fsXattr xattr; + } fs; + uid_t uid; + V9fsFidState *next; +}; + +typedef struct V9fsState +{ + VirtIODevice vdev; + VirtQueue *vq; + V9fsPDU pdus[MAX_REQ]; + QLIST_HEAD(, V9fsPDU) free_list; + V9fsFidState *fid_list; + FileOperations *ops; + FsContext ctx; + uint16_t tag_len; + uint8_t *tag; + size_t config_size; + enum p9_proto_version proto_version; + int32_t msize; +} V9fsState; + +typedef struct V9fsCreateState { + V9fsPDU *pdu; + size_t offset; + V9fsFidState *fidp; + V9fsQID qid; + int32_t perm; + int8_t mode; + struct stat stbuf; + V9fsString name; + V9fsString extension; + V9fsString fullname; + int iounit; +} V9fsCreateState; + +typedef struct V9fsLcreateState { + V9fsPDU *pdu; + size_t offset; + V9fsFidState *fidp; + V9fsQID qid; + int32_t iounit; + struct stat stbuf; + V9fsString name; + V9fsString fullname; +} V9fsLcreateState; + +typedef struct V9fsStatState { + V9fsPDU *pdu; + size_t offset; + V9fsStat v9stat; + V9fsFidState *fidp; + struct stat stbuf; +} V9fsStatState; + +typedef struct V9fsStatDotl { + uint64_t st_result_mask; + V9fsQID qid; + uint32_t st_mode; + uint32_t st_uid; + uint32_t st_gid; + uint64_t st_nlink; + uint64_t st_rdev; + uint64_t st_size; + uint64_t st_blksize; + uint64_t st_blocks; + uint64_t st_atime_sec; + uint64_t st_atime_nsec; + uint64_t st_mtime_sec; + uint64_t st_mtime_nsec; + uint64_t st_ctime_sec; + uint64_t st_ctime_nsec; + uint64_t st_btime_sec; + uint64_t st_btime_nsec; + uint64_t st_gen; + uint64_t st_data_version; +} V9fsStatDotl; + +typedef struct V9fsStatStateDotl { + V9fsPDU *pdu; + size_t offset; + V9fsStatDotl v9stat_dotl; + struct stat stbuf; +} V9fsStatStateDotl; + + +typedef struct V9fsWalkState { + V9fsPDU *pdu; + size_t offset; + int16_t nwnames; + int name_idx; + V9fsQID *qids; + V9fsFidState *fidp; + V9fsFidState *newfidp; + V9fsString path; + V9fsString *wnames; + struct stat stbuf; +} V9fsWalkState; + +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 V9fsRemoveState { + V9fsPDU *pdu; + size_t offset; + V9fsFidState *fidp; +} V9fsRemoveState; + +typedef struct V9fsWstatState +{ + V9fsPDU *pdu; + size_t offset; + int16_t unused; + V9fsStat v9stat; + V9fsFidState *fidp; + struct stat stbuf; +} V9fsWstatState; + +typedef struct V9fsSymlinkState +{ + V9fsPDU *pdu; + size_t offset; + V9fsString name; + V9fsString symname; + V9fsString fullname; + V9fsFidState *dfidp; + V9fsQID qid; + struct stat stbuf; +} V9fsSymlinkState; + +typedef struct V9fsIattr +{ + int32_t valid; + int32_t mode; + int32_t uid; + int32_t gid; + int64_t size; + int64_t atime_sec; + int64_t atime_nsec; + int64_t mtime_sec; + int64_t mtime_nsec; +} V9fsIattr; + +typedef struct V9fsSetattrState +{ + V9fsPDU *pdu; + size_t offset; + V9fsIattr v9iattr; + V9fsFidState *fidp; +} V9fsSetattrState; + +struct virtio_9p_config +{ + /* number of characters in tag */ + uint16_t tag_len; + /* Variable size tag name */ + uint8_t tag[0]; +} __attribute__((packed)); + +typedef struct V9fsStatfs +{ + 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; +} V9fsStatfs; + +typedef struct V9fsStatfsState { + V9fsPDU *pdu; + size_t offset; + int32_t fid; + V9fsStatfs v9statfs; + V9fsFidState *fidp; + struct statfs stbuf; +} V9fsStatfsState; + +typedef struct V9fsMkState { + V9fsPDU *pdu; + size_t offset; + V9fsQID qid; + struct stat stbuf; + V9fsString name; + V9fsString fullname; +} V9fsMkState; + +typedef struct V9fsRenameState { + V9fsPDU *pdu; + V9fsFidState *fidp; + size_t offset; + int32_t newdirfid; + V9fsString name; +} V9fsRenameState; + +typedef struct V9fsXattrState +{ + V9fsPDU *pdu; + size_t offset; + V9fsFidState *file_fidp; + V9fsFidState *xattr_fidp; + V9fsString name; + int64_t size; + int flags; + void *value; +} V9fsXattrState; + +#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 V9fsLockState +{ + V9fsPDU *pdu; + size_t offset; + int8_t status; + struct stat stbuf; + V9fsFidState *fidp; + V9fsFlock *flock; +} V9fsLockState; + +typedef struct V9fsGetlock +{ + uint8_t type; + uint64_t start; /* absolute offset */ + uint64_t length; + uint32_t proc_id; + V9fsString client_id; +} V9fsGetlock; + +typedef struct V9fsGetlockState +{ + V9fsPDU *pdu; + size_t offset; + struct stat stbuf; + V9fsFidState *fidp; + V9fsGetlock *glock; +} V9fsGetlockState; + +typedef struct V9fsReadLinkState +{ + V9fsPDU *pdu; + size_t offset; + V9fsString target; +} V9fsReadLinkState; + +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); +} + +#endif diff --git a/hw/virtio-balloon.c b/hw/virtio-balloon.c new file mode 100644 index 0000000..8adddea --- /dev/null +++ b/hw/virtio-balloon.c @@ -0,0 +1,283 @@ +/* + * Virtio Block Device + * + * Copyright IBM, Corp. 2008 + * + * Authors: + * Anthony Liguori + * + * This work is licensed under the terms of the GNU GPL, version 2. See + * the COPYING file in the top-level directory. + * + */ + +#include "iov.h" +#include "qemu-common.h" +#include "virtio.h" +#include "pc.h" +#include "sysemu.h" +#include "cpu.h" +#include "monitor.h" +#include "balloon.h" +#include "virtio-balloon.h" +#include "kvm.h" +#include "qlist.h" +#include "qint.h" +#include "qstring.h" + +#if defined(__linux__) +#include +#endif + +/* Disable guest-provided stats by now (https://bugzilla.redhat.com/show_bug.cgi?id=623903) */ +#define ENABLE_GUEST_STATS 0 + + +typedef struct VirtIOBalloon +{ + VirtIODevice vdev; + VirtQueue *ivq, *dvq, *svq; + uint32_t num_pages; + uint32_t actual; + uint64_t stats[VIRTIO_BALLOON_S_NR]; + VirtQueueElement stats_vq_elem; + size_t stats_vq_offset; + MonitorCompletion *stats_callback; + void *stats_opaque_callback_data; +} VirtIOBalloon; + +static VirtIOBalloon *to_virtio_balloon(VirtIODevice *vdev) +{ + return (VirtIOBalloon *)vdev; +} + +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 +} + +/* + * reset_stats - Mark all items in the stats array as unset + * + * This function needs to be called at device intialization and before + * 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 void stat_put(QDict *dict, const char *label, uint64_t val) +{ + if (val != -1) + qdict_put(dict, label, qint_from_int(val)); +} + +static QObject *get_stats_qobject(VirtIOBalloon *dev) +{ + QDict *dict = qdict_new(); + uint64_t actual = ram_size - ((uint64_t) dev->actual << + VIRTIO_BALLOON_PFN_SHIFT); + + stat_put(dict, "actual", actual); +#if ENABLE_GUEST_STATS + stat_put(dict, "mem_swapped_in", dev->stats[VIRTIO_BALLOON_S_SWAP_IN]); + stat_put(dict, "mem_swapped_out", dev->stats[VIRTIO_BALLOON_S_SWAP_OUT]); + stat_put(dict, "major_page_faults", dev->stats[VIRTIO_BALLOON_S_MAJFLT]); + stat_put(dict, "minor_page_faults", dev->stats[VIRTIO_BALLOON_S_MINFLT]); + stat_put(dict, "free_mem", dev->stats[VIRTIO_BALLOON_S_MEMFREE]); + stat_put(dict, "total_mem", dev->stats[VIRTIO_BALLOON_S_MEMTOT]); +#endif + + return QOBJECT(dict); +} + +static void virtio_balloon_handle_output(VirtIODevice *vdev, VirtQueue *vq) +{ + VirtIOBalloon *s = to_virtio_balloon(vdev); + VirtQueueElement elem; + + while (virtqueue_pop(vq, &elem)) { + size_t offset = 0; + uint32_t pfn; + + while (iov_to_buf(elem.out_sg, elem.out_num, &pfn, offset, 4) == 4) { + ram_addr_t pa; + ram_addr_t addr; + + pa = (ram_addr_t)ldl_p(&pfn) << VIRTIO_BALLOON_PFN_SHIFT; + offset += 4; + + addr = cpu_get_physical_page_desc(pa); + if ((addr & ~TARGET_PAGE_MASK) != IO_MEM_RAM) + continue; + + /* Using qemu_get_ram_ptr is bending the rules a bit, but + should be OK because we only want a single page. */ + balloon_page(qemu_get_ram_ptr(addr), !!(vq == s->dvq)); + } + + virtqueue_push(vq, &elem, offset); + virtio_notify(vdev, vq); + } +} + +static void complete_stats_request(VirtIOBalloon *vb) +{ + QObject *stats; + + if (!vb->stats_opaque_callback_data) + return; + + stats = get_stats_qobject(vb); + vb->stats_callback(vb->stats_opaque_callback_data, stats); + qobject_decref(stats); + vb->stats_opaque_callback_data = NULL; + vb->stats_callback = NULL; +} + +static void virtio_balloon_receive_stats(VirtIODevice *vdev, VirtQueue *vq) +{ + VirtIOBalloon *s = DO_UPCAST(VirtIOBalloon, vdev, vdev); + VirtQueueElement *elem = &s->stats_vq_elem; + VirtIOBalloonStat stat; + size_t offset = 0; + + if (!virtqueue_pop(vq, elem)) { + return; + } + + /* 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, &stat, offset, sizeof(stat)) + == sizeof(stat)) { + uint16_t tag = tswap16(stat.tag); + uint64_t val = tswap64(stat.val); + + offset += sizeof(stat); + if (tag < VIRTIO_BALLOON_S_NR) + s->stats[tag] = val; + } + s->stats_vq_offset = offset; + + complete_stats_request(s); +} + +static void virtio_balloon_get_config(VirtIODevice *vdev, uint8_t *config_data) +{ + VirtIOBalloon *dev = to_virtio_balloon(vdev); + struct virtio_balloon_config config; + + config.num_pages = cpu_to_le32(dev->num_pages); + config.actual = cpu_to_le32(dev->actual); + + memcpy(config_data, &config, 8); +} + +static void virtio_balloon_set_config(VirtIODevice *vdev, + const uint8_t *config_data) +{ + VirtIOBalloon *dev = to_virtio_balloon(vdev); + struct virtio_balloon_config config; + memcpy(&config, config_data, 8); + dev->actual = config.actual; +} + +static uint32_t virtio_balloon_get_features(VirtIODevice *vdev, uint32_t f) +{ + f |= (1 << VIRTIO_BALLOON_F_STATS_VQ); + return f; +} + +static void virtio_balloon_to_target(void *opaque, ram_addr_t target, + MonitorCompletion cb, void *cb_data) +{ + VirtIOBalloon *dev = opaque; + + if (target > ram_size) + target = ram_size; + + if (target) { + dev->num_pages = (ram_size - target) >> VIRTIO_BALLOON_PFN_SHIFT; + virtio_notify_config(&dev->vdev); + } else { + /* For now, only allow one request at a time. This restriction can be + * removed later by queueing callback and data pairs. + */ + if (dev->stats_callback != NULL) { + return; + } + dev->stats_callback = cb; + dev->stats_opaque_callback_data = cb_data; + if (ENABLE_GUEST_STATS && (dev->vdev.guest_features & (1 << VIRTIO_BALLOON_F_STATS_VQ))) { + virtqueue_push(dev->svq, &dev->stats_vq_elem, dev->stats_vq_offset); + virtio_notify(&dev->vdev, dev->svq); + } else { + /* Stats are not supported. Clear out any stale values that might + * have been set by a more featureful guest kernel. + */ + reset_stats(dev); + complete_stats_request(dev); + } + } +} + +static void virtio_balloon_save(QEMUFile *f, void *opaque) +{ + VirtIOBalloon *s = opaque; + + virtio_save(&s->vdev, f); + + 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) +{ + VirtIOBalloon *s = opaque; + + if (version_id != 1) + return -EINVAL; + + virtio_load(&s->vdev, f); + + s->num_pages = qemu_get_be32(f); + s->actual = qemu_get_be32(f); + return 0; +} + +VirtIODevice *virtio_balloon_init(DeviceState *dev) +{ + VirtIOBalloon *s; + + s = (VirtIOBalloon *)virtio_common_init("virtio-balloon", + VIRTIO_ID_BALLOON, + 8, sizeof(VirtIOBalloon)); + + s->vdev.get_config = virtio_balloon_get_config; + s->vdev.set_config = virtio_balloon_set_config; + s->vdev.get_features = virtio_balloon_get_features; + + s->ivq = virtio_add_queue(&s->vdev, 128, virtio_balloon_handle_output); + s->dvq = virtio_add_queue(&s->vdev, 128, virtio_balloon_handle_output); + s->svq = virtio_add_queue(&s->vdev, 128, virtio_balloon_receive_stats); + + reset_stats(s); + qemu_add_balloon_handler(virtio_balloon_to_target, s); + + register_savevm(dev, "virtio-balloon", -1, 1, + virtio_balloon_save, virtio_balloon_load, s); + + return &s->vdev; +} diff --git a/hw/virtio-balloon.h b/hw/virtio-balloon.h new file mode 100644 index 0000000..e20cf6b --- /dev/null +++ b/hw/virtio-balloon.h @@ -0,0 +1,55 @@ +/* + * Virtio Support + * + * Copyright IBM, Corp. 2007-2008 + * + * Authors: + * Anthony Liguori + * Rusty Russell + * + * 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_BALLOON_H +#define _QEMU_VIRTIO_BALLOON_H + +#include "virtio.h" +#include "pci.h" + +/* from Linux's linux/virtio_balloon.h */ + +/* The ID for virtio_balloon */ +#define VIRTIO_ID_BALLOON 5 + +/* The feature bitmap for virtio balloon */ +#define VIRTIO_BALLOON_F_MUST_TELL_HOST 0 /* Tell before reclaiming pages */ +#define VIRTIO_BALLOON_F_STATS_VQ 1 /* Memory stats virtqueue */ + +/* Size of a PFN in the balloon interface. */ +#define VIRTIO_BALLOON_PFN_SHIFT 12 + +struct virtio_balloon_config +{ + /* Number of pages host wants Guest to give up. */ + uint32_t num_pages; + /* Number of pages we've actually got in balloon. */ + uint32_t actual; +}; + +/* Memory Statistics */ +#define VIRTIO_BALLOON_S_SWAP_IN 0 /* Amount of memory swapped in */ +#define VIRTIO_BALLOON_S_SWAP_OUT 1 /* Amount of memory swapped out */ +#define VIRTIO_BALLOON_S_MAJFLT 2 /* Number of major faults */ +#define VIRTIO_BALLOON_S_MINFLT 3 /* Number of minor faults */ +#define VIRTIO_BALLOON_S_MEMFREE 4 /* Total amount of free memory */ +#define VIRTIO_BALLOON_S_MEMTOT 5 /* Total amount of memory */ +#define VIRTIO_BALLOON_S_NR 6 + +typedef struct VirtIOBalloonStat { + uint16_t tag; + uint64_t val; +} __attribute__((packed)) VirtIOBalloonStat; + +#endif diff --git a/hw/virtio-blk.c b/hw/virtio-blk.c new file mode 100644 index 0000000..114c638 --- /dev/null +++ b/hw/virtio-blk.c @@ -0,0 +1,588 @@ +/* + * Virtio Block Device + * + * Copyright IBM, Corp. 2007 + * + * Authors: + * Anthony Liguori + * + * This work is licensed under the terms of the GNU GPL, version 2. See + * the COPYING file in the top-level directory. + * + */ + +#include +#include "qemu-error.h" +#include "trace.h" +#include "blockdev.h" +#include "virtio-blk.h" +#ifdef __linux__ +# include +#endif + +typedef struct VirtIOBlock +{ + VirtIODevice vdev; + BlockDriverState *bs; + VirtQueue *vq; + void *rq; + QEMUBH *bh; + BlockConf *conf; + unsigned short sector_mask; + char sn[BLOCK_SERIAL_STRLEN]; + DeviceState *qdev; +} VirtIOBlock; + +static VirtIOBlock *to_virtio_blk(VirtIODevice *vdev) +{ + return (VirtIOBlock *)vdev; +} + +typedef struct VirtIOBlockReq +{ + VirtIOBlock *dev; + VirtQueueElement elem; + struct virtio_blk_inhdr *in; + struct virtio_blk_outhdr *out; + struct virtio_scsi_inhdr *scsi; + QEMUIOVector qiov; + struct VirtIOBlockReq *next; +} VirtIOBlockReq; + +static void virtio_blk_req_complete(VirtIOBlockReq *req, int status) +{ + VirtIOBlock *s = req->dev; + + trace_virtio_blk_req_complete(req, status); + + stb_p(&req->in->status, status); + virtqueue_push(s->vq, &req->elem, req->qiov.size + sizeof(*req->in)); + virtio_notify(&s->vdev, s->vq); + + qemu_free(req); +} + +static int virtio_blk_handle_rw_error(VirtIOBlockReq *req, int error, + int is_read) +{ + BlockErrorAction action = bdrv_get_on_error(req->dev->bs, is_read); + VirtIOBlock *s = req->dev; + + if (action == BLOCK_ERR_IGNORE) { + bdrv_mon_event(s->bs, BDRV_ACTION_IGNORE, is_read); + return 0; + } + + if ((error == ENOSPC && action == BLOCK_ERR_STOP_ENOSPC) + || action == BLOCK_ERR_STOP_ANY) { + req->next = s->rq; + s->rq = req; + bdrv_mon_event(s->bs, BDRV_ACTION_STOP, is_read); + vm_stop(0); + } else { + virtio_blk_req_complete(req, VIRTIO_BLK_S_IOERR); + bdrv_mon_event(s->bs, BDRV_ACTION_REPORT, is_read); + } + + return 1; +} + +static void virtio_blk_rw_complete(void *opaque, int ret) +{ + VirtIOBlockReq *req = opaque; + + trace_virtio_blk_rw_complete(req, ret); + + if (ret) { + int is_read = !(ldl_p(&req->out->type) & VIRTIO_BLK_T_OUT); + if (virtio_blk_handle_rw_error(req, -ret, is_read)) + return; + } + + virtio_blk_req_complete(req, VIRTIO_BLK_S_OK); +} + +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); +} + +static VirtIOBlockReq *virtio_blk_alloc_request(VirtIOBlock *s) +{ + VirtIOBlockReq *req = qemu_malloc(sizeof(*req)); + req->dev = s; + req->qiov.size = 0; + req->next = NULL; + return req; +} + +static VirtIOBlockReq *virtio_blk_get_request(VirtIOBlock *s) +{ + VirtIOBlockReq *req = virtio_blk_alloc_request(s); + + if (req != NULL) { + if (!virtqueue_pop(s->vq, &req->elem)) { + qemu_free(req); + return NULL; + } + } + + return req; +} + +#ifdef __linux__ +static void virtio_blk_handle_scsi(VirtIOBlockReq *req) +{ + struct sg_io_hdr hdr; + int ret; + int status; + int i; + + /* + * 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 (req->elem.out_num < 2 || req->elem.in_num < 3) { + virtio_blk_req_complete(req, VIRTIO_BLK_S_IOERR); + return; + } + + /* + * No support for bidirection commands yet. + */ + if (req->elem.out_num > 2 && req->elem.in_num > 3) { + virtio_blk_req_complete(req, VIRTIO_BLK_S_UNSUPP); + return; + } + + /* + * The scsi inhdr is placed in the second-to-last input segment, just + * before the regular inhdr. + */ + req->scsi = (void *)req->elem.in_sg[req->elem.in_num - 2].iov_base; + + memset(&hdr, 0, sizeof(struct sg_io_hdr)); + hdr.interface_id = 'S'; + hdr.cmd_len = req->elem.out_sg[1].iov_len; + hdr.cmdp = req->elem.out_sg[1].iov_base; + hdr.dxfer_len = 0; + + if (req->elem.out_num > 2) { + /* + * If there are more than the minimally required 2 output segments + * there is write payload starting from the third iovec. + */ + hdr.dxfer_direction = SG_DXFER_TO_DEV; + hdr.iovec_count = req->elem.out_num - 2; + + for (i = 0; i < hdr.iovec_count; i++) + hdr.dxfer_len += req->elem.out_sg[i + 2].iov_len; + + hdr.dxferp = req->elem.out_sg + 2; + + } else if (req->elem.in_num > 3) { + /* + * If we have more than 3 input segments the guest wants to actually + * read data. + */ + hdr.dxfer_direction = SG_DXFER_FROM_DEV; + hdr.iovec_count = req->elem.in_num - 3; + for (i = 0; i < hdr.iovec_count; i++) + hdr.dxfer_len += req->elem.in_sg[i].iov_len; + + hdr.dxferp = req->elem.in_sg; + } else { + /* + * Some SCSI commands don't actually transfer any data. + */ + hdr.dxfer_direction = SG_DXFER_NONE; + } + + hdr.sbp = req->elem.in_sg[req->elem.in_num - 3].iov_base; + hdr.mx_sb_len = req->elem.in_sg[req->elem.in_num - 3].iov_len; + + ret = bdrv_ioctl(req->dev->bs, SG_IO, &hdr); + if (ret) { + status = VIRTIO_BLK_S_UNSUPP; + hdr.status = ret; + hdr.resid = hdr.dxfer_len; + } else if (hdr.status) { + status = VIRTIO_BLK_S_IOERR; + } else { + status = VIRTIO_BLK_S_OK; + } + + stl_p(&req->scsi->errors, hdr.status); + stl_p(&req->scsi->residual, hdr.resid); + stl_p(&req->scsi->sense_len, hdr.sb_len_wr); + stl_p(&req->scsi->data_len, hdr.dxfer_len); + + virtio_blk_req_complete(req, status); +} +#else +static void virtio_blk_handle_scsi(VirtIOBlockReq *req) +{ + virtio_blk_req_complete(req, VIRTIO_BLK_S_UNSUPP); +} +#endif /* __linux__ */ + +typedef struct MultiReqBuffer { + BlockRequest blkreq[32]; + unsigned int num_writes; +} MultiReqBuffer; + +static void virtio_submit_multiwrite(BlockDriverState *bs, MultiReqBuffer *mrb) +{ + int i, ret; + + if (!mrb->num_writes) { + return; + } + + ret = bdrv_aio_multiwrite(bs, mrb->blkreq, mrb->num_writes); + if (ret != 0) { + for (i = 0; i < mrb->num_writes; i++) { + if (mrb->blkreq[i].error) { + virtio_blk_rw_complete(mrb->blkreq[i].opaque, -EIO); + } + } + } + + mrb->num_writes = 0; +} + +static void virtio_blk_handle_flush(VirtIOBlockReq *req, MultiReqBuffer *mrb) +{ + BlockDriverAIOCB *acb; + + /* + * Make sure all outstanding writes are posted to the backing device. + */ + virtio_submit_multiwrite(req->dev->bs, mrb); + + acb = bdrv_aio_flush(req->dev->bs, virtio_blk_flush_complete, req); + if (!acb) { + virtio_blk_flush_complete(req, -EIO); + } +} + +static void virtio_blk_handle_write(VirtIOBlockReq *req, MultiReqBuffer *mrb) +{ + BlockRequest *blkreq; + uint64_t sector; + + sector = ldq_p(&req->out->sector); + + trace_virtio_blk_handle_write(req, sector, req->qiov.size / 512); + + if (sector & req->dev->sector_mask) { + virtio_blk_rw_complete(req, -EIO); + return; + } + if (req->qiov.size % req->dev->conf->logical_block_size) { + virtio_blk_rw_complete(req, -EIO); + return; + } + + if (mrb->num_writes == 32) { + virtio_submit_multiwrite(req->dev->bs, mrb); + } + + blkreq = &mrb->blkreq[mrb->num_writes]; + blkreq->sector = sector; + blkreq->nb_sectors = req->qiov.size / BDRV_SECTOR_SIZE; + blkreq->qiov = &req->qiov; + blkreq->cb = virtio_blk_rw_complete; + blkreq->opaque = req; + blkreq->error = 0; + + mrb->num_writes++; +} + +static void virtio_blk_handle_read(VirtIOBlockReq *req) +{ + BlockDriverAIOCB *acb; + uint64_t sector; + + sector = ldq_p(&req->out->sector); + + if (sector & req->dev->sector_mask) { + virtio_blk_rw_complete(req, -EIO); + return; + } + if (req->qiov.size % req->dev->conf->logical_block_size) { + virtio_blk_rw_complete(req, -EIO); + return; + } + + acb = bdrv_aio_readv(req->dev->bs, sector, &req->qiov, + req->qiov.size / BDRV_SECTOR_SIZE, + virtio_blk_rw_complete, req); + if (!acb) { + virtio_blk_rw_complete(req, -EIO); + } +} + +static void virtio_blk_handle_request(VirtIOBlockReq *req, + MultiReqBuffer *mrb) +{ + uint32_t type; + + if (req->elem.out_num < 1 || req->elem.in_num < 1) { + error_report("virtio-blk missing headers"); + exit(1); + } + + if (req->elem.out_sg[0].iov_len < sizeof(*req->out) || + req->elem.in_sg[req->elem.in_num - 1].iov_len < sizeof(*req->in)) { + error_report("virtio-blk header not in correct element"); + exit(1); + } + + req->out = (void *)req->elem.out_sg[0].iov_base; + req->in = (void *)req->elem.in_sg[req->elem.in_num - 1].iov_base; + + type = ldl_p(&req->out->type); + + if (type & VIRTIO_BLK_T_FLUSH) { + virtio_blk_handle_flush(req, mrb); + } else if (type & VIRTIO_BLK_T_SCSI_CMD) { + virtio_blk_handle_scsi(req); + } else if (type & VIRTIO_BLK_T_GET_ID) { + VirtIOBlock *s = req->dev; + + memcpy(req->elem.in_sg[0].iov_base, s->sn, + MIN(req->elem.in_sg[0].iov_len, sizeof(s->sn))); + virtio_blk_req_complete(req, VIRTIO_BLK_S_OK); + } else if (type & VIRTIO_BLK_T_OUT) { + qemu_iovec_init_external(&req->qiov, &req->elem.out_sg[1], + req->elem.out_num - 1); + virtio_blk_handle_write(req, mrb); + } else { + qemu_iovec_init_external(&req->qiov, &req->elem.in_sg[0], + req->elem.in_num - 1); + virtio_blk_handle_read(req); + } +} + +static void virtio_blk_handle_output(VirtIODevice *vdev, VirtQueue *vq) +{ + VirtIOBlock *s = to_virtio_blk(vdev); + VirtIOBlockReq *req; + MultiReqBuffer mrb = { + .num_writes = 0, + }; + + while ((req = virtio_blk_get_request(s))) { + virtio_blk_handle_request(req, &mrb); + } + + virtio_submit_multiwrite(s->bs, &mrb); + + /* + * FIXME: Want to check for completions before returning to guest mode, + * so cached reads and writes are reported as quickly as possible. But + * that should be done in the generic block layer. + */ +} + +static void virtio_blk_dma_restart_bh(void *opaque) +{ + VirtIOBlock *s = opaque; + VirtIOBlockReq *req = s->rq; + MultiReqBuffer mrb = { + .num_writes = 0, + }; + + qemu_bh_delete(s->bh); + s->bh = NULL; + + s->rq = NULL; + + while (req) { + virtio_blk_handle_request(req, &mrb); + req = req->next; + } + + virtio_submit_multiwrite(s->bs, &mrb); +} + +static void virtio_blk_dma_restart_cb(void *opaque, int running, int reason) +{ + VirtIOBlock *s = opaque; + + if (!running) + return; + + if (!s->bh) { + s->bh = qemu_bh_new(virtio_blk_dma_restart_bh, s); + qemu_bh_schedule(s->bh); + } +} + +static void virtio_blk_reset(VirtIODevice *vdev) +{ + /* + * This should cancel pending requests, but can't do nicely until there + * are per-device request lists. + */ + qemu_aio_flush(); +} + +/* coalesce internal state, copy to pci i/o region 0 + */ +static void virtio_blk_update_config(VirtIODevice *vdev, uint8_t *config) +{ + VirtIOBlock *s = to_virtio_blk(vdev); + struct virtio_blk_config blkcfg; + uint64_t capacity; + int cylinders, heads, secs; + + bdrv_get_geometry(s->bs, &capacity); + bdrv_get_geometry_hint(s->bs, &cylinders, &heads, &secs); + memset(&blkcfg, 0, sizeof(blkcfg)); + stq_raw(&blkcfg.capacity, capacity); + stl_raw(&blkcfg.seg_max, 128 - 2); + stw_raw(&blkcfg.cylinders, cylinders); + blkcfg.heads = heads; + blkcfg.sectors = secs & ~s->sector_mask; + blkcfg.blk_size = s->conf->logical_block_size; + blkcfg.size_max = 0; + blkcfg.physical_block_exp = get_physical_block_exp(s->conf); + blkcfg.alignment_offset = 0; + blkcfg.min_io_size = s->conf->min_io_size / blkcfg.blk_size; + blkcfg.opt_io_size = s->conf->opt_io_size / blkcfg.blk_size; + memcpy(config, &blkcfg, sizeof(struct virtio_blk_config)); +} + +static uint32_t virtio_blk_get_features(VirtIODevice *vdev, uint32_t features) +{ + VirtIOBlock *s = to_virtio_blk(vdev); + + features |= (1 << VIRTIO_BLK_F_SEG_MAX); + features |= (1 << VIRTIO_BLK_F_GEOMETRY); + features |= (1 << VIRTIO_BLK_F_TOPOLOGY); + features |= (1 << VIRTIO_BLK_F_BLK_SIZE); + + if (bdrv_enable_write_cache(s->bs)) + features |= (1 << VIRTIO_BLK_F_WCACHE); + + if (bdrv_is_read_only(s->bs)) + features |= 1 << VIRTIO_BLK_F_RO; + + return features; +} + +static void virtio_blk_save(QEMUFile *f, void *opaque) +{ + VirtIOBlock *s = opaque; + VirtIOBlockReq *req = s->rq; + + virtio_save(&s->vdev, f); + + while (req) { + qemu_put_sbyte(f, 1); + qemu_put_buffer(f, (unsigned char*)&req->elem, sizeof(req->elem)); + req = req->next; + } + qemu_put_sbyte(f, 0); +} + +static int virtio_blk_load(QEMUFile *f, void *opaque, int version_id) +{ + VirtIOBlock *s = opaque; + + if (version_id != 2) + return -EINVAL; + + virtio_load(&s->vdev, f); + while (qemu_get_sbyte(f)) { + VirtIOBlockReq *req = virtio_blk_alloc_request(s); + qemu_get_buffer(f, (unsigned char*)&req->elem, sizeof(req->elem)); + 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_change_cb(void *opaque, int reason) +{ + VirtIOBlock *s = opaque; + + if (reason & CHANGE_SIZE) { + virtio_notify_config(&s->vdev); + } +} + +VirtIODevice *virtio_blk_init(DeviceState *dev, BlockConf *conf) +{ + VirtIOBlock *s; + int cylinders, heads, secs; + static int virtio_blk_id; + DriveInfo *dinfo; + + if (!conf->bs) { + error_report("virtio-blk-pci: drive property not set"); + return NULL; + } + if (!bdrv_is_inserted(conf->bs)) { + error_report("Device needs media, but drive is empty"); + return NULL; + } + + s = (VirtIOBlock *)virtio_common_init("virtio-blk", VIRTIO_ID_BLOCK, + sizeof(struct virtio_blk_config), + sizeof(VirtIOBlock)); + + s->vdev.get_config = virtio_blk_update_config; + s->vdev.get_features = virtio_blk_get_features; + s->vdev.reset = virtio_blk_reset; + s->bs = conf->bs; + s->conf = conf; + s->rq = NULL; + s->sector_mask = (s->conf->logical_block_size / BDRV_SECTOR_SIZE) - 1; + bdrv_guess_geometry(s->bs, &cylinders, &heads, &secs); + + /* NB: per existing s/n string convention the string is terminated + * by '\0' only when less than sizeof (s->sn) + */ + dinfo = drive_get_by_blockdev(s->bs); + strncpy(s->sn, dinfo->serial, sizeof (s->sn)); + + s->vq = virtio_add_queue(&s->vdev, 128, virtio_blk_handle_output); + + qemu_add_vm_change_state_handler(virtio_blk_dma_restart_cb, s); + s->qdev = dev; + register_savevm(dev, "virtio-blk", virtio_blk_id++, 2, + virtio_blk_save, virtio_blk_load, s); + bdrv_set_removable(s->bs, 0); + bdrv_set_change_cb(s->bs, virtio_blk_change_cb, s); + s->bs->buffer_alignment = conf->logical_block_size; + + add_boot_device_path(conf->bootindex, dev, "/disk@0,0"); + + return &s->vdev; +} + +void virtio_blk_exit(VirtIODevice *vdev) +{ + VirtIOBlock *s = to_virtio_blk(vdev); + unregister_savevm(s->qdev, "virtio-blk", s); +} diff --git a/hw/virtio-blk.h b/hw/virtio-blk.h new file mode 100644 index 0000000..fff46da --- /dev/null +++ b/hw/virtio-blk.h @@ -0,0 +1,106 @@ +/* + * Virtio Block Device + * + * Copyright IBM, Corp. 2007 + * + * Authors: + * Anthony Liguori + * + * 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_BLK_H +#define _QEMU_VIRTIO_BLK_H + +#include "virtio.h" +#include "block.h" + +/* from Linux's linux/virtio_blk.h */ + +/* The ID for virtio_block */ +#define VIRTIO_ID_BLOCK 2 + +/* Feature bits */ +#define VIRTIO_BLK_F_BARRIER 0 /* Does host support barriers? */ +#define VIRTIO_BLK_F_SIZE_MAX 1 /* Indicates maximum segment size */ +#define VIRTIO_BLK_F_SEG_MAX 2 /* Indicates maximum # of segments */ +#define VIRTIO_BLK_F_GEOMETRY 4 /* Indicates support of legacy geometry */ +#define VIRTIO_BLK_F_RO 5 /* Disk is read-only */ +#define VIRTIO_BLK_F_BLK_SIZE 6 /* Block size of disk is available*/ +#define VIRTIO_BLK_F_SCSI 7 /* Supports scsi command passthru */ +/* #define VIRTIO_BLK_F_IDENTIFY 8 ATA IDENTIFY supported, DEPRECATED */ +#define VIRTIO_BLK_F_WCACHE 9 /* write cache enabled */ +#define VIRTIO_BLK_F_TOPOLOGY 10 /* Topology information is available */ + +struct virtio_blk_config +{ + uint64_t capacity; + uint32_t size_max; + uint32_t seg_max; + uint16_t cylinders; + uint8_t heads; + uint8_t sectors; + uint32_t blk_size; + uint8_t physical_block_exp; + uint8_t alignment_offset; + uint16_t min_io_size; + uint32_t opt_io_size; +} __attribute__((packed)); + +/* These two define direction. */ +#define VIRTIO_BLK_T_IN 0 +#define VIRTIO_BLK_T_OUT 1 + +/* This bit says it's a scsi command, not an actual read or write. */ +#define VIRTIO_BLK_T_SCSI_CMD 2 + +/* Flush the volatile write cache */ +#define VIRTIO_BLK_T_FLUSH 4 + +/* return the device ID string */ +#define VIRTIO_BLK_T_GET_ID 8 + +/* Barrier before this op. */ +#define VIRTIO_BLK_T_BARRIER 0x80000000 + +/* This is the first element of the read scatter-gather list. */ +struct virtio_blk_outhdr +{ + /* VIRTIO_BLK_T* */ + uint32_t type; + /* io priority. */ + uint32_t ioprio; + /* Sector (ie. 512 byte offset) */ + uint64_t sector; +}; + +#define VIRTIO_BLK_S_OK 0 +#define VIRTIO_BLK_S_IOERR 1 +#define VIRTIO_BLK_S_UNSUPP 2 + +/* This is the last element of the write scatter-gather list */ +struct virtio_blk_inhdr +{ + unsigned char status; +}; + +/* SCSI pass-through header */ +struct virtio_scsi_inhdr +{ + uint32_t errors; + uint32_t data_len; + uint32_t sense_len; + uint32_t residual; +}; + +#ifdef __linux__ +#define DEFINE_VIRTIO_BLK_FEATURES(_state, _field) \ + DEFINE_VIRTIO_COMMON_FEATURES(_state, _field), \ + DEFINE_PROP_BIT("scsi", _state, _field, VIRTIO_BLK_F_SCSI, true) +#else +#define DEFINE_VIRTIO_BLK_FEATURES(_state, _field) \ + DEFINE_VIRTIO_COMMON_FEATURES(_state, _field) +#endif +#endif diff --git a/hw/virtio-console.c b/hw/virtio-console.c new file mode 100644 index 0000000..62624ec --- /dev/null +++ b/hw/virtio-console.c @@ -0,0 +1,141 @@ +/* + * Virtio Console and Generic Serial Port Devices + * + * Copyright Red Hat, Inc. 2009, 2010 + * + * Authors: + * Amit Shah + * + * This work is licensed under the terms of the GNU GPL, version 2. See + * the COPYING file in the top-level directory. + */ + +#include "qemu-char.h" +#include "virtio-serial.h" + +typedef struct VirtConsole { + VirtIOSerialPort port; + CharDriverState *chr; +} VirtConsole; + + +/* Callback function that's called when the guest sends us data */ +static ssize_t flush_buf(VirtIOSerialPort *port, const uint8_t *buf, size_t len) +{ + VirtConsole *vcon = DO_UPCAST(VirtConsole, port, port); + + return qemu_chr_write(vcon->chr, buf, len); +} + +/* 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(&vcon->port); +} + +/* 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; + + virtio_serial_write(&vcon->port, buf, size); +} + +static void chr_event(void *opaque, int event) +{ + VirtConsole *vcon = opaque; + + switch (event) { + case CHR_EVENT_OPENED: + virtio_serial_open(&vcon->port); + break; + case CHR_EVENT_CLOSED: + virtio_serial_close(&vcon->port); + break; + } +} + +static int generic_port_init(VirtConsole *vcon, VirtIOSerialDevice *dev) +{ + vcon->port.info = dev->info; + + if (vcon->chr) { + qemu_chr_add_handlers(vcon->chr, chr_can_read, chr_read, chr_event, + vcon); + vcon->port.info->have_data = flush_buf; + } + return 0; +} + +/* Virtio Console Ports */ +static int virtconsole_initfn(VirtIOSerialDevice *dev) +{ + VirtIOSerialPort *port = DO_UPCAST(VirtIOSerialPort, dev, &dev->qdev); + VirtConsole *vcon = DO_UPCAST(VirtConsole, port, port); + + port->is_console = true; + return generic_port_init(vcon, dev); +} + +static int virtconsole_exitfn(VirtIOSerialDevice *dev) +{ + VirtIOSerialPort *port = DO_UPCAST(VirtIOSerialPort, dev, &dev->qdev); + VirtConsole *vcon = DO_UPCAST(VirtConsole, port, port); + + if (vcon->chr) { + port->info->have_data = NULL; + qemu_chr_close(vcon->chr); + } + + return 0; +} + +static VirtIOSerialPortInfo virtconsole_info = { + .qdev.name = "virtconsole", + .qdev.size = sizeof(VirtConsole), + .init = virtconsole_initfn, + .exit = virtconsole_exitfn, + .qdev.props = (Property[]) { + DEFINE_PROP_UINT8("is_console", VirtConsole, port.is_console, 1), + DEFINE_PROP_UINT32("nr", VirtConsole, port.id, VIRTIO_CONSOLE_BAD_ID), + DEFINE_PROP_CHR("chardev", VirtConsole, chr), + DEFINE_PROP_STRING("name", VirtConsole, port.name), + DEFINE_PROP_END_OF_LIST(), + }, +}; + +static void virtconsole_register(void) +{ + virtio_serial_port_qdev_register(&virtconsole_info); +} +device_init(virtconsole_register) + +/* Generic Virtio Serial Ports */ +static int virtserialport_initfn(VirtIOSerialDevice *dev) +{ + VirtIOSerialPort *port = DO_UPCAST(VirtIOSerialPort, dev, &dev->qdev); + VirtConsole *vcon = DO_UPCAST(VirtConsole, port, port); + + return generic_port_init(vcon, dev); +} + +static VirtIOSerialPortInfo virtserialport_info = { + .qdev.name = "virtserialport", + .qdev.size = sizeof(VirtConsole), + .init = virtserialport_initfn, + .exit = virtconsole_exitfn, + .qdev.props = (Property[]) { + DEFINE_PROP_UINT32("nr", VirtConsole, port.id, VIRTIO_CONSOLE_BAD_ID), + DEFINE_PROP_CHR("chardev", VirtConsole, chr), + DEFINE_PROP_STRING("name", VirtConsole, port.name), + DEFINE_PROP_END_OF_LIST(), + }, +}; + +static void virtserialport_register(void) +{ + virtio_serial_port_qdev_register(&virtserialport_info); +} +device_init(virtserialport_register) diff --git a/hw/virtio-net.c b/hw/virtio-net.c new file mode 100644 index 0000000..e9775a6 --- /dev/null +++ b/hw/virtio-net.c @@ -0,0 +1,1077 @@ +/* + * Virtio Network Device + * + * Copyright IBM, Corp. 2007 + * + * Authors: + * Anthony Liguori + * + * This work is licensed under the terms of the GNU GPL, version 2. See + * the COPYING file in the top-level directory. + * + */ + +#include "iov.h" +#include "virtio.h" +#include "net.h" +#include "net/checksum.h" +#include "net/tap.h" +#include "qemu-error.h" +#include "qemu-timer.h" +#include "virtio-net.h" +#include "vhost_net.h" + +#define VIRTIO_NET_VM_VERSION 11 + +#define MAC_TABLE_ENTRIES 64 +#define MAX_VLAN (1 << 12) /* Per 802.1Q definition */ + +typedef struct VirtIONet +{ + VirtIODevice vdev; + uint8_t mac[ETH_ALEN]; + uint16_t status; + VirtQueue *rx_vq; + VirtQueue *tx_vq; + VirtQueue *ctrl_vq; + NICState *nic; + QEMUTimer *tx_timer; + QEMUBH *tx_bh; + uint32_t tx_timeout; + int32_t tx_burst; + int tx_waiting; + uint32_t has_vnet_hdr; + uint8_t has_ufo; + struct { + VirtQueueElement elem; + ssize_t len; + } async_tx; + int mergeable_rx_bufs; + uint8_t promisc; + uint8_t allmulti; + uint8_t alluni; + uint8_t nomulti; + uint8_t nouni; + uint8_t nobcast; + uint8_t vhost_started; + struct { + int in_use; + int first_multi; + uint8_t multi_overflow; + uint8_t uni_overflow; + uint8_t *macs; + } mac_table; + uint32_t *vlans; + DeviceState *qdev; +} VirtIONet; + +/* TODO + * - we could suppress RX interrupt if we were so inclined. + */ + +static VirtIONet *to_virtio_net(VirtIODevice *vdev) +{ + return (VirtIONet *)vdev; +} + +static void virtio_net_get_config(VirtIODevice *vdev, uint8_t *config) +{ + VirtIONet *n = to_virtio_net(vdev); + struct virtio_net_config netcfg; + + stw_p(&netcfg.status, n->status); + memcpy(netcfg.mac, n->mac, ETH_ALEN); + memcpy(config, &netcfg, sizeof(netcfg)); +} + +static void virtio_net_set_config(VirtIODevice *vdev, const uint8_t *config) +{ + VirtIONet *n = to_virtio_net(vdev); + struct virtio_net_config netcfg; + + memcpy(&netcfg, config, sizeof(netcfg)); + + if (memcmp(netcfg.mac, n->mac, ETH_ALEN)) { + memcpy(n->mac, netcfg.mac, ETH_ALEN); + qemu_format_nic_info_str(&n->nic->nc, n->mac); + } +} + +static bool virtio_net_started(VirtIONet *n, uint8_t status) +{ + return (status & VIRTIO_CONFIG_S_DRIVER_OK) && + (n->status & VIRTIO_NET_S_LINK_UP) && n->vdev.vm_running; +} + +static void virtio_net_vhost_status(VirtIONet *n, uint8_t status) +{ + if (!n->nic->nc.peer) { + return; + } + if (n->nic->nc.peer->info->type != NET_CLIENT_TYPE_TAP) { + return; + } + + if (!tap_get_vhost_net(n->nic->nc.peer)) { + return; + } + if (!!n->vhost_started == virtio_net_started(n, status)) { + return; + } + if (!n->vhost_started) { + int r; + if (!vhost_net_query(tap_get_vhost_net(n->nic->nc.peer), &n->vdev)) { + return; + } + r = vhost_net_start(tap_get_vhost_net(n->nic->nc.peer), &n->vdev); + if (r < 0) { + error_report("unable to start vhost net: %d: " + "falling back on userspace virtio", -r); + } else { + n->vhost_started = 1; + } + } else { + vhost_net_stop(tap_get_vhost_net(n->nic->nc.peer), &n->vdev); + n->vhost_started = 0; + } +} + +static void virtio_net_set_status(struct VirtIODevice *vdev, uint8_t status) +{ + VirtIONet *n = to_virtio_net(vdev); + + virtio_net_vhost_status(n, status); + + if (!n->tx_waiting) { + return; + } + + if (virtio_net_started(n, status) && !n->vhost_started) { + if (n->tx_timer) { + qemu_mod_timer(n->tx_timer, + qemu_get_clock(vm_clock) + n->tx_timeout); + } else { + qemu_bh_schedule(n->tx_bh); + } + } else { + if (n->tx_timer) { + qemu_del_timer(n->tx_timer); + } else { + qemu_bh_cancel(n->tx_bh); + } + } +} + +static void virtio_net_set_link_status(VLANClientState *nc) +{ + VirtIONet *n = DO_UPCAST(NICState, nc, nc)->opaque; + 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(&n->vdev); + + virtio_net_set_status(&n->vdev, n->vdev.status); +} + +static void virtio_net_reset(VirtIODevice *vdev) +{ + VirtIONet *n = to_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; + + /* 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); + memset(n->vlans, 0, MAX_VLAN >> 3); +} + +static int peer_has_vnet_hdr(VirtIONet *n) +{ + if (!n->nic->nc.peer) + return 0; + + if (n->nic->nc.peer->info->type != NET_CLIENT_TYPE_TAP) + return 0; + + n->has_vnet_hdr = tap_has_vnet_hdr(n->nic->nc.peer); + + return n->has_vnet_hdr; +} + +static int peer_has_ufo(VirtIONet *n) +{ + if (!peer_has_vnet_hdr(n)) + return 0; + + n->has_ufo = tap_has_ufo(n->nic->nc.peer); + + return n->has_ufo; +} + +static uint32_t virtio_net_get_features(VirtIODevice *vdev, uint32_t features) +{ + VirtIONet *n = to_virtio_net(vdev); + + features |= (1 << VIRTIO_NET_F_MAC); + + if (peer_has_vnet_hdr(n)) { + tap_using_vnet_hdr(n->nic->nc.peer, 1); + } else { + features &= ~(0x1 << VIRTIO_NET_F_CSUM); + features &= ~(0x1 << VIRTIO_NET_F_HOST_TSO4); + features &= ~(0x1 << VIRTIO_NET_F_HOST_TSO6); + features &= ~(0x1 << VIRTIO_NET_F_HOST_ECN); + + features &= ~(0x1 << VIRTIO_NET_F_GUEST_CSUM); + features &= ~(0x1 << VIRTIO_NET_F_GUEST_TSO4); + features &= ~(0x1 << VIRTIO_NET_F_GUEST_TSO6); + features &= ~(0x1 << VIRTIO_NET_F_GUEST_ECN); + } + + if (!peer_has_vnet_hdr(n) || !peer_has_ufo(n)) { + features &= ~(0x1 << VIRTIO_NET_F_GUEST_UFO); + features &= ~(0x1 << VIRTIO_NET_F_HOST_UFO); + } + + if (!n->nic->nc.peer || + n->nic->nc.peer->info->type != NET_CLIENT_TYPE_TAP) { + return features; + } + if (!tap_get_vhost_net(n->nic->nc.peer)) { + return features; + } + return vhost_net_get_features(tap_get_vhost_net(n->nic->nc.peer), features); +} + +static uint32_t virtio_net_bad_features(VirtIODevice *vdev) +{ + uint32_t features = 0; + + /* Linux kernel 2.6.25. It understood MAC (as everyone must), + * but also these: */ + features |= (1 << VIRTIO_NET_F_MAC); + features |= (1 << VIRTIO_NET_F_CSUM); + features |= (1 << VIRTIO_NET_F_HOST_TSO4); + features |= (1 << VIRTIO_NET_F_HOST_TSO6); + features |= (1 << VIRTIO_NET_F_HOST_ECN); + + return features; +} + +static void virtio_net_set_features(VirtIODevice *vdev, uint32_t features) +{ + VirtIONet *n = to_virtio_net(vdev); + + n->mergeable_rx_bufs = !!(features & (1 << VIRTIO_NET_F_MRG_RXBUF)); + + if (n->has_vnet_hdr) { + tap_set_offload(n->nic->nc.peer, + (features >> VIRTIO_NET_F_GUEST_CSUM) & 1, + (features >> VIRTIO_NET_F_GUEST_TSO4) & 1, + (features >> VIRTIO_NET_F_GUEST_TSO6) & 1, + (features >> VIRTIO_NET_F_GUEST_ECN) & 1, + (features >> VIRTIO_NET_F_GUEST_UFO) & 1); + } + if (!n->nic->nc.peer || + n->nic->nc.peer->info->type != NET_CLIENT_TYPE_TAP) { + return; + } + if (!tap_get_vhost_net(n->nic->nc.peer)) { + return; + } + vhost_net_ack_features(tap_get_vhost_net(n->nic->nc.peer), features); +} + +static int virtio_net_handle_rx_mode(VirtIONet *n, uint8_t cmd, + VirtQueueElement *elem) +{ + uint8_t on; + + if (elem->out_num != 2 || elem->out_sg[1].iov_len != sizeof(on)) { + error_report("virtio-net ctrl invalid rx mode command"); + exit(1); + } + + on = ldub_p(elem->out_sg[1].iov_base); + + if (cmd == VIRTIO_NET_CTRL_RX_MODE_PROMISC) + n->promisc = on; + else if (cmd == VIRTIO_NET_CTRL_RX_MODE_ALLMULTI) + n->allmulti = on; + else if (cmd == VIRTIO_NET_CTRL_RX_MODE_ALLUNI) + n->alluni = on; + else if (cmd == VIRTIO_NET_CTRL_RX_MODE_NOMULTI) + n->nomulti = on; + else if (cmd == VIRTIO_NET_CTRL_RX_MODE_NOUNI) + n->nouni = on; + else if (cmd == VIRTIO_NET_CTRL_RX_MODE_NOBCAST) + n->nobcast = on; + else + return VIRTIO_NET_ERR; + + return VIRTIO_NET_OK; +} + +static int virtio_net_handle_mac(VirtIONet *n, uint8_t cmd, + VirtQueueElement *elem) +{ + struct virtio_net_ctrl_mac mac_data; + + if (cmd != VIRTIO_NET_CTRL_MAC_TABLE_SET || elem->out_num != 3 || + elem->out_sg[1].iov_len < sizeof(mac_data) || + elem->out_sg[2].iov_len < sizeof(mac_data)) + return VIRTIO_NET_ERR; + + n->mac_table.in_use = 0; + n->mac_table.first_multi = 0; + n->mac_table.uni_overflow = 0; + n->mac_table.multi_overflow = 0; + memset(n->mac_table.macs, 0, MAC_TABLE_ENTRIES * ETH_ALEN); + + mac_data.entries = ldl_p(elem->out_sg[1].iov_base); + + if (sizeof(mac_data.entries) + + (mac_data.entries * ETH_ALEN) > elem->out_sg[1].iov_len) + return VIRTIO_NET_ERR; + + if (mac_data.entries <= MAC_TABLE_ENTRIES) { + memcpy(n->mac_table.macs, elem->out_sg[1].iov_base + sizeof(mac_data), + mac_data.entries * ETH_ALEN); + n->mac_table.in_use += mac_data.entries; + } else { + n->mac_table.uni_overflow = 1; + } + + n->mac_table.first_multi = n->mac_table.in_use; + + mac_data.entries = ldl_p(elem->out_sg[2].iov_base); + + if (sizeof(mac_data.entries) + + (mac_data.entries * ETH_ALEN) > elem->out_sg[2].iov_len) + return VIRTIO_NET_ERR; + + if (mac_data.entries) { + if (n->mac_table.in_use + mac_data.entries <= MAC_TABLE_ENTRIES) { + memcpy(n->mac_table.macs + (n->mac_table.in_use * ETH_ALEN), + elem->out_sg[2].iov_base + sizeof(mac_data), + mac_data.entries * ETH_ALEN); + n->mac_table.in_use += mac_data.entries; + } else { + n->mac_table.multi_overflow = 1; + } + } + + return VIRTIO_NET_OK; +} + +static int virtio_net_handle_vlan_table(VirtIONet *n, uint8_t cmd, + VirtQueueElement *elem) +{ + uint16_t vid; + + if (elem->out_num != 2 || elem->out_sg[1].iov_len != sizeof(vid)) { + error_report("virtio-net ctrl invalid vlan command"); + return VIRTIO_NET_ERR; + } + + vid = lduw_p(elem->out_sg[1].iov_base); + + 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; + + return VIRTIO_NET_OK; +} + +static void virtio_net_handle_ctrl(VirtIODevice *vdev, VirtQueue *vq) +{ + VirtIONet *n = to_virtio_net(vdev); + struct virtio_net_ctrl_hdr ctrl; + virtio_net_ctrl_ack status = VIRTIO_NET_ERR; + VirtQueueElement elem; + + while (virtqueue_pop(vq, &elem)) { + if ((elem.in_num < 1) || (elem.out_num < 1)) { + error_report("virtio-net ctrl missing headers"); + exit(1); + } + + if (elem.out_sg[0].iov_len < sizeof(ctrl) || + elem.in_sg[elem.in_num - 1].iov_len < sizeof(status)) { + error_report("virtio-net ctrl header not in correct element"); + exit(1); + } + + ctrl.class = ldub_p(elem.out_sg[0].iov_base); + ctrl.cmd = ldub_p(elem.out_sg[0].iov_base + sizeof(ctrl.class)); + + if (ctrl.class == VIRTIO_NET_CTRL_RX_MODE) + status = virtio_net_handle_rx_mode(n, ctrl.cmd, &elem); + else if (ctrl.class == VIRTIO_NET_CTRL_MAC) + status = virtio_net_handle_mac(n, ctrl.cmd, &elem); + else if (ctrl.class == VIRTIO_NET_CTRL_VLAN) + status = virtio_net_handle_vlan_table(n, ctrl.cmd, &elem); + + stb_p(elem.in_sg[elem.in_num - 1].iov_base, status); + + virtqueue_push(vq, &elem, sizeof(status)); + virtio_notify(vdev, vq); + } +} + +/* RX */ + +static void virtio_net_handle_rx(VirtIODevice *vdev, VirtQueue *vq) +{ + VirtIONet *n = to_virtio_net(vdev); + + qemu_flush_queued_packets(&n->nic->nc); + + /* We now have RX buffers, signal to the IO thread to break out of the + * select to re-poll the tap file descriptor */ + qemu_notify_event(); +} + +static int virtio_net_can_receive(VLANClientState *nc) +{ + VirtIONet *n = DO_UPCAST(NICState, nc, nc)->opaque; + if (!n->vdev.vm_running) { + return 0; + } + + if (!virtio_queue_ready(n->rx_vq) || + !(n->vdev.status & VIRTIO_CONFIG_S_DRIVER_OK)) + return 0; + + return 1; +} + +static int virtio_net_has_buffers(VirtIONet *n, int bufsize) +{ + if (virtio_queue_empty(n->rx_vq) || + (n->mergeable_rx_bufs && + !virtqueue_avail_bytes(n->rx_vq, bufsize, 0))) { + virtio_queue_set_notification(n->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(n->rx_vq) || + (n->mergeable_rx_bufs && + !virtqueue_avail_bytes(n->rx_vq, bufsize, 0))) + return 0; + } + + virtio_queue_set_notification(n->rx_vq, 0); + return 1; +} + +/* 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, + const 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 */ + /* FIXME this cast is evil */ + net_checksum_calculate((uint8_t *)buf, size); + hdr->flags &= ~VIRTIO_NET_HDR_F_NEEDS_CSUM; + } +} + +static int receive_header(VirtIONet *n, struct iovec *iov, int iovcnt, + const void *buf, size_t size, size_t hdr_len) +{ + struct virtio_net_hdr *hdr = (struct virtio_net_hdr *)iov[0].iov_base; + int offset = 0; + + hdr->flags = 0; + hdr->gso_type = VIRTIO_NET_HDR_GSO_NONE; + + if (n->has_vnet_hdr) { + memcpy(hdr, buf, sizeof(*hdr)); + offset = sizeof(*hdr); + work_around_broken_dhclient(hdr, buf + offset, size - offset); + } + + /* We only ever receive a struct virtio_net_hdr from the tapfd, + * but we may be passing along a larger header to the guest. + */ + iov[0].iov_base += hdr_len; + iov[0].iov_len -= hdr_len; + + return offset; +} + +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; + + if (n->has_vnet_hdr) { + ptr += sizeof(struct virtio_net_hdr); + } + + 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(VLANClientState *nc, const uint8_t *buf, size_t size) +{ + VirtIONet *n = DO_UPCAST(NICState, nc, nc)->opaque; + struct virtio_net_hdr_mrg_rxbuf *mhdr = NULL; + size_t guest_hdr_len, offset, i, host_hdr_len; + + if (!virtio_net_can_receive(&n->nic->nc)) + return -1; + + /* hdr_len refers to the header we supply to the guest */ + guest_hdr_len = n->mergeable_rx_bufs ? + sizeof(struct virtio_net_hdr_mrg_rxbuf) : sizeof(struct virtio_net_hdr); + + + host_hdr_len = n->has_vnet_hdr ? sizeof(struct virtio_net_hdr) : 0; + if (!virtio_net_has_buffers(n, size + guest_hdr_len - host_hdr_len)) + return 0; + + if (!receive_filter(n, buf, size)) + return size; + + offset = i = 0; + + while (offset < size) { + VirtQueueElement elem; + int len, total; + struct iovec sg[VIRTQUEUE_MAX_SIZE]; + + total = 0; + + if (virtqueue_pop(n->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%x", + i, n->mergeable_rx_bufs, offset, size, + guest_hdr_len, host_hdr_len, n->vdev.guest_features); + exit(1); + } + + if (elem.in_num < 1) { + error_report("virtio-net receive queue contains no in buffers"); + exit(1); + } + + if (!n->mergeable_rx_bufs && elem.in_sg[0].iov_len != guest_hdr_len) { + error_report("virtio-net header not in first element"); + exit(1); + } + + memcpy(&sg, &elem.in_sg[0], sizeof(sg[0]) * elem.in_num); + + if (i == 0) { + if (n->mergeable_rx_bufs) + mhdr = (struct virtio_net_hdr_mrg_rxbuf *)sg[0].iov_base; + + offset += receive_header(n, sg, elem.in_num, + buf + offset, size - offset, guest_hdr_len); + total += guest_hdr_len; + } + + /* copy in packet. ugh */ + len = iov_from_buf(sg, elem.in_num, + 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) { +#if 0 + error_report("virtio-net truncated non-mergeable packet: " + "i %zd mergeable %d offset %zd, size %zd, " + "guest hdr len %zd, host hdr len %zd", + i, n->mergeable_rx_bufs, + offset, size, guest_hdr_len, host_hdr_len); +#endif + return size; + } + + /* signal other side */ + virtqueue_fill(n->rx_vq, &elem, total, i++); + } + + if (mhdr) { + stw_p(&mhdr->num_buffers, i); + } + + virtqueue_flush(n->rx_vq, i); + virtio_notify(&n->vdev, n->rx_vq); + + return size; +} + +static int32_t virtio_net_flush_tx(VirtIONet *n, VirtQueue *vq); + +static void virtio_net_tx_complete(VLANClientState *nc, ssize_t len) +{ + VirtIONet *n = DO_UPCAST(NICState, nc, nc)->opaque; + + virtqueue_push(n->tx_vq, &n->async_tx.elem, n->async_tx.len); + virtio_notify(&n->vdev, n->tx_vq); + + n->async_tx.elem.out_num = n->async_tx.len = 0; + + virtio_queue_set_notification(n->tx_vq, 1); + virtio_net_flush_tx(n, n->tx_vq); +} + +/* TX */ +static int32_t virtio_net_flush_tx(VirtIONet *n, VirtQueue *vq) +{ + VirtQueueElement elem; + int32_t num_packets = 0; + if (!(n->vdev.status & VIRTIO_CONFIG_S_DRIVER_OK)) { + return num_packets; + } + + assert(n->vdev.vm_running); + + if (n->async_tx.elem.out_num) { + virtio_queue_set_notification(n->tx_vq, 0); + return num_packets; + } + + while (virtqueue_pop(vq, &elem)) { + ssize_t ret, len = 0; + unsigned int out_num = elem.out_num; + struct iovec *out_sg = &elem.out_sg[0]; + unsigned hdr_len; + + /* hdr_len refers to the header received from the guest */ + hdr_len = n->mergeable_rx_bufs ? + sizeof(struct virtio_net_hdr_mrg_rxbuf) : + sizeof(struct virtio_net_hdr); + + if (out_num < 1 || out_sg->iov_len != hdr_len) { + error_report("virtio-net header not in first element"); + exit(1); + } + + /* ignore the header if GSO is not supported */ + if (!n->has_vnet_hdr) { + out_num--; + out_sg++; + len += hdr_len; + } else if (n->mergeable_rx_bufs) { + /* tapfd expects a struct virtio_net_hdr */ + hdr_len -= sizeof(struct virtio_net_hdr); + out_sg->iov_len -= hdr_len; + len += hdr_len; + } + + ret = qemu_sendv_packet_async(&n->nic->nc, out_sg, out_num, + virtio_net_tx_complete); + if (ret == 0) { + virtio_queue_set_notification(n->tx_vq, 0); + n->async_tx.elem = elem; + n->async_tx.len = len; + return -EBUSY; + } + + len += ret; + + virtqueue_push(vq, &elem, len); + virtio_notify(&n->vdev, vq); + + if (++num_packets >= n->tx_burst) { + break; + } + } + return num_packets; +} + +static void virtio_net_handle_tx_timer(VirtIODevice *vdev, VirtQueue *vq) +{ + VirtIONet *n = to_virtio_net(vdev); + + /* This happens when device was stopped but VCPU wasn't. */ + if (!n->vdev.vm_running) { + n->tx_waiting = 1; + return; + } + + if (n->tx_waiting) { + virtio_queue_set_notification(vq, 1); + qemu_del_timer(n->tx_timer); + n->tx_waiting = 0; + virtio_net_flush_tx(n, vq); + } else { + qemu_mod_timer(n->tx_timer, + qemu_get_clock(vm_clock) + n->tx_timeout); + n->tx_waiting = 1; + virtio_queue_set_notification(vq, 0); + } +} + +static void virtio_net_handle_tx_bh(VirtIODevice *vdev, VirtQueue *vq) +{ + VirtIONet *n = to_virtio_net(vdev); + + if (unlikely(n->tx_waiting)) { + return; + } + n->tx_waiting = 1; + /* This happens when device was stopped but VCPU wasn't. */ + if (!n->vdev.vm_running) { + return; + } + virtio_queue_set_notification(vq, 0); + qemu_bh_schedule(n->tx_bh); +} + +static void virtio_net_tx_timer(void *opaque) +{ + VirtIONet *n = opaque; + assert(n->vdev.vm_running); + + n->tx_waiting = 0; + + /* Just in case the driver is not ready on more */ + if (!(n->vdev.status & VIRTIO_CONFIG_S_DRIVER_OK)) + return; + + virtio_queue_set_notification(n->tx_vq, 1); + virtio_net_flush_tx(n, n->tx_vq); +} + +static void virtio_net_tx_bh(void *opaque) +{ + VirtIONet *n = opaque; + int32_t ret; + + assert(n->vdev.vm_running); + + n->tx_waiting = 0; + + /* Just in case the driver is not ready on more */ + if (unlikely(!(n->vdev.status & VIRTIO_CONFIG_S_DRIVER_OK))) + return; + + ret = virtio_net_flush_tx(n, n->tx_vq); + 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(n->tx_bh); + n->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(n->tx_vq, 1); + if (virtio_net_flush_tx(n, n->tx_vq) > 0) { + virtio_queue_set_notification(n->tx_vq, 0); + qemu_bh_schedule(n->tx_bh); + n->tx_waiting = 1; + } +} + +static void virtio_net_save(QEMUFile *f, void *opaque) +{ + VirtIONet *n = opaque; + + /* At this point, backend must be stopped, otherwise + * it might keep writing to memory. */ + assert(!n->vhost_started); + virtio_save(&n->vdev, f); + + qemu_put_buffer(f, n->mac, ETH_ALEN); + qemu_put_be32(f, n->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); +} + +static int virtio_net_load(QEMUFile *f, void *opaque, int version_id) +{ + VirtIONet *n = opaque; + int i; + + if (version_id < 2 || version_id > VIRTIO_NET_VM_VERSION) + return -EINVAL; + + virtio_load(&n->vdev, f); + + qemu_get_buffer(f, n->mac, ETH_ALEN); + n->tx_waiting = qemu_get_be32(f); + n->mergeable_rx_bufs = qemu_get_be32(f); + + 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 if (n->mac_table.in_use) { + qemu_fseek(f, n->mac_table.in_use * ETH_ALEN, SEEK_CUR); + 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 (n->has_vnet_hdr) { + tap_using_vnet_hdr(n->nic->nc.peer, 1); + tap_set_offload(n->nic->nc.peer, + (n->vdev.guest_features >> VIRTIO_NET_F_GUEST_CSUM) & 1, + (n->vdev.guest_features >> VIRTIO_NET_F_GUEST_TSO4) & 1, + (n->vdev.guest_features >> VIRTIO_NET_F_GUEST_TSO6) & 1, + (n->vdev.guest_features >> VIRTIO_NET_F_GUEST_ECN) & 1, + (n->vdev.guest_features >> VIRTIO_NET_F_GUEST_UFO) & 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; + } + } + + /* 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; + return 0; +} + +static void virtio_net_cleanup(VLANClientState *nc) +{ + VirtIONet *n = DO_UPCAST(NICState, nc, nc)->opaque; + + n->nic = NULL; +} + +static NetClientInfo net_virtio_info = { + .type = NET_CLIENT_TYPE_NIC, + .size = sizeof(NICState), + .can_receive = virtio_net_can_receive, + .receive = virtio_net_receive, + .cleanup = virtio_net_cleanup, + .link_status_changed = virtio_net_set_link_status, +}; + +VirtIODevice *virtio_net_init(DeviceState *dev, NICConf *conf, + virtio_net_conf *net) +{ + VirtIONet *n; + + n = (VirtIONet *)virtio_common_init("virtio-net", VIRTIO_ID_NET, + sizeof(struct virtio_net_config), + sizeof(VirtIONet)); + + n->vdev.get_config = virtio_net_get_config; + n->vdev.set_config = virtio_net_set_config; + n->vdev.get_features = virtio_net_get_features; + n->vdev.set_features = virtio_net_set_features; + n->vdev.bad_features = virtio_net_bad_features; + n->vdev.reset = virtio_net_reset; + n->vdev.set_status = virtio_net_set_status; + n->rx_vq = virtio_add_queue(&n->vdev, 256, virtio_net_handle_rx); + + if (net->tx && strcmp(net->tx, "timer") && strcmp(net->tx, "bh")) { + error_report("virtio-net: " + "Unknown option tx=%s, valid options: \"timer\" \"bh\"", + net->tx); + error_report("Defaulting to \"bh\""); + } + + if (net->tx && !strcmp(net->tx, "timer")) { + n->tx_vq = virtio_add_queue(&n->vdev, 256, virtio_net_handle_tx_timer); + n->tx_timer = qemu_new_timer(vm_clock, virtio_net_tx_timer, n); + n->tx_timeout = net->txtimer; + } else { + n->tx_vq = virtio_add_queue(&n->vdev, 256, virtio_net_handle_tx_bh); + n->tx_bh = qemu_bh_new(virtio_net_tx_bh, n); + } + n->ctrl_vq = virtio_add_queue(&n->vdev, 64, virtio_net_handle_ctrl); + qemu_macaddr_default_if_unset(&conf->macaddr); + memcpy(&n->mac[0], &conf->macaddr, sizeof(n->mac)); + n->status = VIRTIO_NET_S_LINK_UP; + + n->nic = qemu_new_nic(&net_virtio_info, conf, dev->info->name, dev->id, n); + + qemu_format_nic_info_str(&n->nic->nc, conf->macaddr.a); + + n->tx_waiting = 0; + n->tx_burst = net->txburst; + n->mergeable_rx_bufs = 0; + n->promisc = 1; /* for compatibility */ + + n->mac_table.macs = qemu_mallocz(MAC_TABLE_ENTRIES * ETH_ALEN); + + n->vlans = qemu_mallocz(MAX_VLAN >> 3); + + n->qdev = dev; + register_savevm(dev, "virtio-net", -1, VIRTIO_NET_VM_VERSION, + virtio_net_save, virtio_net_load, n); + + add_boot_device_path(conf->bootindex, dev, "/ethernet-phy@0"); + + return &n->vdev; +} + +void virtio_net_exit(VirtIODevice *vdev) +{ + VirtIONet *n = DO_UPCAST(VirtIONet, vdev, vdev); + + /* This will stop vhost backend if appropriate. */ + virtio_net_set_status(vdev, 0); + + qemu_purge_queued_packets(&n->nic->nc); + + unregister_savevm(n->qdev, "virtio-net", n); + + qemu_free(n->mac_table.macs); + qemu_free(n->vlans); + + if (n->tx_timer) { + qemu_del_timer(n->tx_timer); + qemu_free_timer(n->tx_timer); + } else { + qemu_bh_delete(n->tx_bh); + } + + virtio_cleanup(&n->vdev); + qemu_del_vlan_client(&n->nic->nc); +} diff --git a/hw/virtio-net.h b/hw/virtio-net.h new file mode 100644 index 0000000..8af9a1c --- /dev/null +++ b/hw/virtio-net.h @@ -0,0 +1,189 @@ +/* + * Virtio Network Device + * + * Copyright IBM, Corp. 2007 + * + * Authors: + * Anthony Liguori + * + * 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_NET_H +#define _QEMU_VIRTIO_NET_H + +#include "virtio.h" +#include "net.h" +#include "pci.h" + +#define ETH_ALEN 6 + +/* from Linux's virtio_net.h */ + +/* The ID for virtio_net */ +#define VIRTIO_ID_NET 1 + +/* The feature bitmap for virtio net */ +#define VIRTIO_NET_F_CSUM 0 /* Host handles pkts w/ partial csum */ +#define VIRTIO_NET_F_GUEST_CSUM 1 /* Guest handles pkts w/ partial csum */ +#define VIRTIO_NET_F_MAC 5 /* Host has given MAC address. */ +#define VIRTIO_NET_F_GSO 6 /* Host handles pkts w/ any GSO type */ +#define VIRTIO_NET_F_GUEST_TSO4 7 /* Guest can handle TSOv4 in. */ +#define VIRTIO_NET_F_GUEST_TSO6 8 /* Guest can handle TSOv6 in. */ +#define VIRTIO_NET_F_GUEST_ECN 9 /* Guest can handle TSO[6] w/ ECN in. */ +#define VIRTIO_NET_F_GUEST_UFO 10 /* Guest can handle UFO in. */ +#define VIRTIO_NET_F_HOST_TSO4 11 /* Host can handle TSOv4 in. */ +#define VIRTIO_NET_F_HOST_TSO6 12 /* Host can handle TSOv6 in. */ +#define VIRTIO_NET_F_HOST_ECN 13 /* Host can handle TSO[6] w/ ECN in. */ +#define VIRTIO_NET_F_HOST_UFO 14 /* Host can handle UFO in. */ +#define VIRTIO_NET_F_MRG_RXBUF 15 /* Host can merge receive buffers. */ +#define VIRTIO_NET_F_STATUS 16 /* virtio_net_config.status available */ +#define VIRTIO_NET_F_CTRL_VQ 17 /* Control channel available */ +#define VIRTIO_NET_F_CTRL_RX 18 /* Control channel RX mode support */ +#define VIRTIO_NET_F_CTRL_VLAN 19 /* Control channel VLAN filtering */ +#define VIRTIO_NET_F_CTRL_RX_EXTRA 20 /* Extra RX mode control support */ + +#define VIRTIO_NET_S_LINK_UP 1 /* Link is up */ + +#define TX_TIMER_INTERVAL 150000 /* 150 us */ + +/* Limit the number of packets that can be sent via a single flush + * of the TX queue. This gives us a guaranteed exit condition and + * ensures fairness in the io path. 256 conveniently matches the + * length of the TX queue and shows a good balance of performance + * and latency. */ +#define TX_BURST 256 + +typedef struct virtio_net_conf +{ + uint32_t txtimer; + int32_t txburst; + char *tx; +} virtio_net_conf; + +/* Maximum packet size we can receive from tap device: header + 64k */ +#define VIRTIO_NET_MAX_BUFSIZE (sizeof(struct virtio_net_hdr) + (64 << 10)) + +struct virtio_net_config +{ + /* The config defining mac address ($ETH_ALEN bytes) */ + uint8_t mac[ETH_ALEN]; + /* See VIRTIO_NET_F_STATUS and VIRTIO_NET_S_* above */ + uint16_t status; +} __attribute__((packed)); + +/* This is the first element of the scatter-gather list. If you don't + * specify GSO or CSUM features, you can simply ignore the header. */ +struct virtio_net_hdr +{ +#define VIRTIO_NET_HDR_F_NEEDS_CSUM 1 // Use csum_start, csum_offset + uint8_t flags; +#define VIRTIO_NET_HDR_GSO_NONE 0 // Not a GSO frame +#define VIRTIO_NET_HDR_GSO_TCPV4 1 // GSO frame, IPv4 TCP (TSO) +#define VIRTIO_NET_HDR_GSO_UDP 3 // GSO frame, IPv4 UDP (UFO) +#define VIRTIO_NET_HDR_GSO_TCPV6 4 // GSO frame, IPv6 TCP +#define VIRTIO_NET_HDR_GSO_ECN 0x80 // TCP has ECN set + uint8_t gso_type; + uint16_t hdr_len; + uint16_t gso_size; + uint16_t csum_start; + uint16_t csum_offset; +}; + +/* This is the version of the header to use when the MRG_RXBUF + * feature has been negotiated. */ +struct virtio_net_hdr_mrg_rxbuf +{ + struct virtio_net_hdr hdr; + uint16_t num_buffers; /* Number of merged rx buffers */ +}; + +/* + * Control virtqueue data structures + * + * The control virtqueue expects a header in the first sg entry + * and an ack/status response in the last entry. Data for the + * command goes in between. + */ +struct virtio_net_ctrl_hdr { + uint8_t class; + uint8_t cmd; +}; + +typedef uint8_t virtio_net_ctrl_ack; + +#define VIRTIO_NET_OK 0 +#define VIRTIO_NET_ERR 1 + +/* + * Control the RX mode, ie. promisucous, allmulti, etc... + * All commands require an "out" sg entry containing a 1 byte + * state value, zero = disable, non-zero = enable. Commands + * 0 and 1 are supported with the VIRTIO_NET_F_CTRL_RX feature. + * Commands 2-5 are added with VIRTIO_NET_F_CTRL_RX_EXTRA. + */ +#define VIRTIO_NET_CTRL_RX_MODE 0 + #define VIRTIO_NET_CTRL_RX_MODE_PROMISC 0 + #define VIRTIO_NET_CTRL_RX_MODE_ALLMULTI 1 + #define VIRTIO_NET_CTRL_RX_MODE_ALLUNI 2 + #define VIRTIO_NET_CTRL_RX_MODE_NOMULTI 3 + #define VIRTIO_NET_CTRL_RX_MODE_NOUNI 4 + #define VIRTIO_NET_CTRL_RX_MODE_NOBCAST 5 + +/* + * Control the MAC filter table. + * + * The MAC filter table is managed by the hypervisor, the guest should + * assume the size is infinite. Filtering should be considered + * non-perfect, ie. based on hypervisor resources, the guest may + * received packets from sources not specified in the filter list. + * + * In addition to the class/cmd header, the TABLE_SET command requires + * two out scatterlists. Each contains a 4 byte count of entries followed + * by a concatenated byte stream of the ETH_ALEN MAC addresses. The + * first sg list contains unicast addresses, the second is for multicast. + * This functionality is present if the VIRTIO_NET_F_CTRL_RX feature + * is available. + */ +struct virtio_net_ctrl_mac { + uint32_t entries; + uint8_t macs[][ETH_ALEN]; +}; +#define VIRTIO_NET_CTRL_MAC 1 + #define VIRTIO_NET_CTRL_MAC_TABLE_SET 0 + +/* + * Control VLAN filtering + * + * The VLAN filter table is controlled via a simple ADD/DEL interface. + * VLAN IDs not added may be filterd by the hypervisor. Del is the + * opposite of add. Both commands expect an out entry containing a 2 + * byte VLAN ID. VLAN filterting is available with the + * VIRTIO_NET_F_CTRL_VLAN feature bit. + */ +#define VIRTIO_NET_CTRL_VLAN 2 + #define VIRTIO_NET_CTRL_VLAN_ADD 0 + #define VIRTIO_NET_CTRL_VLAN_DEL 1 + +#define DEFINE_VIRTIO_NET_FEATURES(_state, _field) \ + DEFINE_VIRTIO_COMMON_FEATURES(_state, _field), \ + DEFINE_PROP_BIT("csum", _state, _field, VIRTIO_NET_F_CSUM, true), \ + DEFINE_PROP_BIT("guest_csum", _state, _field, VIRTIO_NET_F_GUEST_CSUM, true), \ + DEFINE_PROP_BIT("gso", _state, _field, VIRTIO_NET_F_GSO, true), \ + DEFINE_PROP_BIT("guest_tso4", _state, _field, VIRTIO_NET_F_GUEST_TSO4, true), \ + DEFINE_PROP_BIT("guest_tso6", _state, _field, VIRTIO_NET_F_GUEST_TSO6, true), \ + DEFINE_PROP_BIT("guest_ecn", _state, _field, VIRTIO_NET_F_GUEST_ECN, true), \ + DEFINE_PROP_BIT("guest_ufo", _state, _field, VIRTIO_NET_F_GUEST_UFO, true), \ + DEFINE_PROP_BIT("host_tso4", _state, _field, VIRTIO_NET_F_HOST_TSO4, true), \ + DEFINE_PROP_BIT("host_tso6", _state, _field, VIRTIO_NET_F_HOST_TSO6, true), \ + DEFINE_PROP_BIT("host_ecn", _state, _field, VIRTIO_NET_F_HOST_ECN, true), \ + DEFINE_PROP_BIT("host_ufo", _state, _field, VIRTIO_NET_F_HOST_UFO, true), \ + DEFINE_PROP_BIT("mrg_rxbuf", _state, _field, VIRTIO_NET_F_MRG_RXBUF, true), \ + DEFINE_PROP_BIT("status", _state, _field, VIRTIO_NET_F_STATUS, true), \ + DEFINE_PROP_BIT("ctrl_vq", _state, _field, VIRTIO_NET_F_CTRL_VQ, true), \ + DEFINE_PROP_BIT("ctrl_rx", _state, _field, VIRTIO_NET_F_CTRL_RX, true), \ + DEFINE_PROP_BIT("ctrl_vlan", _state, _field, VIRTIO_NET_F_CTRL_VLAN, true), \ + DEFINE_PROP_BIT("ctrl_rx_extra", _state, _field, VIRTIO_NET_F_CTRL_RX_EXTRA, true) +#endif diff --git a/hw/virtio-pci.c b/hw/virtio-pci.c new file mode 100644 index 0000000..d40ac23 --- /dev/null +++ b/hw/virtio-pci.c @@ -0,0 +1,942 @@ +/* + * Virtio PCI Bindings + * + * Copyright IBM, Corp. 2007 + * Copyright (c) 2009 CodeSourcery + * + * Authors: + * Anthony Liguori + * Paul Brook + * + * This work is licensed under the terms of the GNU GPL, version 2. See + * the COPYING file in the top-level directory. + * + */ + +#include + +#include "virtio.h" +#include "virtio-blk.h" +#include "virtio-net.h" +#include "pci.h" +#include "qemu-error.h" +#include "msix.h" +#include "net.h" +#include "loader.h" +#include "kvm.h" +#include "blockdev.h" + +/* from Linux's linux/virtio_pci.h */ + +/* A 32-bit r/o bitmask of the features supported by the host */ +#define VIRTIO_PCI_HOST_FEATURES 0 + +/* A 32-bit r/w bitmask of features activated by the guest */ +#define VIRTIO_PCI_GUEST_FEATURES 4 + +/* A 32-bit r/w PFN for the currently selected queue */ +#define VIRTIO_PCI_QUEUE_PFN 8 + +/* A 16-bit r/o queue size for the currently selected queue */ +#define VIRTIO_PCI_QUEUE_NUM 12 + +/* A 16-bit r/w queue selector */ +#define VIRTIO_PCI_QUEUE_SEL 14 + +/* A 16-bit r/w queue notifier */ +#define VIRTIO_PCI_QUEUE_NOTIFY 16 + +/* An 8-bit device status register. */ +#define VIRTIO_PCI_STATUS 18 + +/* An 8-bit r/o interrupt status register. Reading the value will return the + * current contents of the ISR and will also clear it. This is effectively + * a read-and-acknowledge. */ +#define VIRTIO_PCI_ISR 19 + +/* MSI-X registers: only enabled if MSI-X is enabled. */ +/* A 16-bit vector for configuration changes. */ +#define VIRTIO_MSI_CONFIG_VECTOR 20 +/* A 16-bit vector for selected queue notifications. */ +#define VIRTIO_MSI_QUEUE_VECTOR 22 + +/* Config space size */ +#define VIRTIO_PCI_CONFIG_NOMSI 20 +#define VIRTIO_PCI_CONFIG_MSI 24 +#define VIRTIO_PCI_REGION_SIZE(dev) (msix_present(dev) ? \ + VIRTIO_PCI_CONFIG_MSI : \ + VIRTIO_PCI_CONFIG_NOMSI) + +/* The remaining space is defined by each driver as the per-driver + * configuration space */ +#define VIRTIO_PCI_CONFIG(dev) (msix_enabled(dev) ? \ + VIRTIO_PCI_CONFIG_MSI : \ + VIRTIO_PCI_CONFIG_NOMSI) + +/* Virtio ABI version, if we increment this, we break the guest driver. */ +#define VIRTIO_PCI_ABI_VERSION 0 + +/* How many bits to shift physical queue address written to QUEUE_PFN. + * 12 is historical, and due to x86 page size. */ +#define VIRTIO_PCI_QUEUE_ADDR_SHIFT 12 + +/* Flags track per-device state like workarounds for quirks in older guests. */ +#define VIRTIO_PCI_FLAG_BUS_MASTER_BUG (1 << 0) + +/* 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) + +/* QEMU doesn't strictly need write barriers since everything runs in + * lock-step. We'll leave the calls to wmb() in though to make it obvious for + * KVM or if kqemu gets SMP support. + */ +#define wmb() do { } while (0) + +/* PCI bindings. */ + +typedef struct { + PCIDevice pci_dev; + VirtIODevice *vdev; + uint32_t flags; + uint32_t addr; + uint32_t class_code; + uint32_t nvectors; + BlockConf block; + NICConf nic; + uint32_t host_features; +#ifdef CONFIG_LINUX + V9fsConf fsconf; +#endif + /* Max. number of ports we can have for a the virtio-serial device */ + uint32_t max_virtserial_ports; + virtio_net_conf net; + bool ioeventfd_disabled; + bool ioeventfd_started; +} VirtIOPCIProxy; + +/* virtio device */ + +static void virtio_pci_notify(void *opaque, uint16_t vector) +{ + VirtIOPCIProxy *proxy = opaque; + if (msix_enabled(&proxy->pci_dev)) + msix_notify(&proxy->pci_dev, vector); + else + qemu_set_irq(proxy->pci_dev.irq[0], proxy->vdev->isr & 1); +} + +static void virtio_pci_save_config(void * opaque, QEMUFile *f) +{ + VirtIOPCIProxy *proxy = opaque; + pci_device_save(&proxy->pci_dev, f); + msix_save(&proxy->pci_dev, f); + if (msix_present(&proxy->pci_dev)) + qemu_put_be16(f, proxy->vdev->config_vector); +} + +static void virtio_pci_save_queue(void * opaque, int n, QEMUFile *f) +{ + VirtIOPCIProxy *proxy = opaque; + if (msix_present(&proxy->pci_dev)) + qemu_put_be16(f, virtio_queue_vector(proxy->vdev, n)); +} + +static int virtio_pci_load_config(void * opaque, QEMUFile *f) +{ + VirtIOPCIProxy *proxy = opaque; + int ret; + ret = pci_device_load(&proxy->pci_dev, f); + if (ret) { + return ret; + } + msix_load(&proxy->pci_dev, f); + if (msix_present(&proxy->pci_dev)) { + qemu_get_be16s(f, &proxy->vdev->config_vector); + } else { + proxy->vdev->config_vector = VIRTIO_NO_VECTOR; + } + if (proxy->vdev->config_vector != VIRTIO_NO_VECTOR) { + return msix_vector_use(&proxy->pci_dev, proxy->vdev->config_vector); + } + return 0; +} + +static int virtio_pci_load_queue(void * opaque, int n, QEMUFile *f) +{ + VirtIOPCIProxy *proxy = opaque; + uint16_t vector; + if (msix_present(&proxy->pci_dev)) { + qemu_get_be16s(f, &vector); + } else { + vector = VIRTIO_NO_VECTOR; + } + virtio_queue_set_vector(proxy->vdev, n, vector); + if (vector != VIRTIO_NO_VECTOR) { + return msix_vector_use(&proxy->pci_dev, vector); + } + return 0; +} + +static int virtio_pci_set_host_notifier_internal(VirtIOPCIProxy *proxy, + int n, bool assign) +{ + VirtQueue *vq = virtio_get_queue(proxy->vdev, n); + EventNotifier *notifier = virtio_queue_get_host_notifier(vq); + int r; + if (assign) { + r = event_notifier_init(notifier, 1); + if (r < 0) { + error_report("%s: unable to init event notifier: %d", + __func__, r); + return r; + } + r = kvm_set_ioeventfd_pio_word(event_notifier_get_fd(notifier), + proxy->addr + VIRTIO_PCI_QUEUE_NOTIFY, + n, assign); + if (r < 0) { + error_report("%s: unable to map ioeventfd: %d", + __func__, r); + event_notifier_cleanup(notifier); + } + } else { + r = kvm_set_ioeventfd_pio_word(event_notifier_get_fd(notifier), + proxy->addr + VIRTIO_PCI_QUEUE_NOTIFY, + n, assign); + if (r < 0) { + error_report("%s: unable to unmap ioeventfd: %d", + __func__, r); + return r; + } + + /* Handle the race condition where the guest kicked and we deassigned + * before we got around to handling the kick. + */ + if (event_notifier_test_and_clear(notifier)) { + virtio_queue_notify_vq(vq); + } + + event_notifier_cleanup(notifier); + } + return r; +} + +static void virtio_pci_host_notifier_read(void *opaque) +{ + VirtQueue *vq = opaque; + EventNotifier *n = virtio_queue_get_host_notifier(vq); + if (event_notifier_test_and_clear(n)) { + virtio_queue_notify_vq(vq); + } +} + +static void virtio_pci_set_host_notifier_fd_handler(VirtIOPCIProxy *proxy, + int n, bool assign) +{ + VirtQueue *vq = virtio_get_queue(proxy->vdev, n); + EventNotifier *notifier = virtio_queue_get_host_notifier(vq); + if (assign) { + qemu_set_fd_handler(event_notifier_get_fd(notifier), + virtio_pci_host_notifier_read, NULL, vq); + } else { + qemu_set_fd_handler(event_notifier_get_fd(notifier), + NULL, NULL, NULL); + } +} + +static void virtio_pci_start_ioeventfd(VirtIOPCIProxy *proxy) +{ + int n, r; + + if (!(proxy->flags & VIRTIO_PCI_FLAG_USE_IOEVENTFD) || + proxy->ioeventfd_disabled || + proxy->ioeventfd_started) { + return; + } + + for (n = 0; n < VIRTIO_PCI_QUEUE_MAX; n++) { + if (!virtio_queue_get_num(proxy->vdev, n)) { + continue; + } + + r = virtio_pci_set_host_notifier_internal(proxy, n, true); + if (r < 0) { + goto assign_error; + } + + virtio_pci_set_host_notifier_fd_handler(proxy, n, true); + } + proxy->ioeventfd_started = true; + return; + +assign_error: + while (--n >= 0) { + if (!virtio_queue_get_num(proxy->vdev, n)) { + continue; + } + + virtio_pci_set_host_notifier_fd_handler(proxy, n, false); + r = virtio_pci_set_host_notifier_internal(proxy, n, 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) +{ + int r; + int n; + + if (!proxy->ioeventfd_started) { + return; + } + + for (n = 0; n < VIRTIO_PCI_QUEUE_MAX; n++) { + if (!virtio_queue_get_num(proxy->vdev, n)) { + continue; + } + + virtio_pci_set_host_notifier_fd_handler(proxy, n, false); + r = virtio_pci_set_host_notifier_internal(proxy, n, false); + assert(r >= 0); + } + proxy->ioeventfd_started = false; +} + +static void virtio_pci_reset(DeviceState *d) +{ + VirtIOPCIProxy *proxy = container_of(d, VirtIOPCIProxy, pci_dev.qdev); + virtio_pci_stop_ioeventfd(proxy); + virtio_reset(proxy->vdev); + msix_reset(&proxy->pci_dev); + proxy->flags &= ~VIRTIO_PCI_FLAG_BUS_MASTER_BUG; +} + +static void virtio_ioport_write(void *opaque, uint32_t addr, uint32_t val) +{ + VirtIOPCIProxy *proxy = opaque; + VirtIODevice *vdev = proxy->vdev; + target_phys_addr_t 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)) { + if (vdev->bad_features) + val = proxy->host_features & vdev->bad_features(vdev); + else + val = 0; + } + if (vdev->set_features) + vdev->set_features(vdev, val); + vdev->guest_features = val; + break; + case VIRTIO_PCI_QUEUE_PFN: + pa = (target_phys_addr_t)val << VIRTIO_PCI_QUEUE_ADDR_SHIFT; + if (pa == 0) { + virtio_pci_stop_ioeventfd(proxy); + virtio_reset(proxy->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_PCI_QUEUE_MAX) + vdev->queue_sel = val; + break; + case VIRTIO_PCI_QUEUE_NOTIFY: + 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(proxy->vdev); + msix_unuse_all_vectors(&proxy->pci_dev); + } + + /* Linux before 2.6.34 sets the device as OK without enabling + the PCI device bus master bit. In this case we need to disable + some safety checks. */ + if ((val & VIRTIO_CONFIG_S_DRIVER_OK) && + !(proxy->pci_dev.config[PCI_COMMAND] & PCI_COMMAND_MASTER)) { + proxy->flags |= VIRTIO_PCI_FLAG_BUS_MASTER_BUG; + } + 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 = proxy->vdev; + uint32_t ret = 0xFFFFFFFF; + + switch (addr) { + case VIRTIO_PCI_HOST_FEATURES: + ret = proxy->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; + qemu_set_irq(proxy->pci_dev.irq[0], 0); + 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 uint32_t virtio_pci_config_readb(void *opaque, uint32_t addr) +{ + VirtIOPCIProxy *proxy = opaque; + uint32_t config = VIRTIO_PCI_CONFIG(&proxy->pci_dev); + addr -= proxy->addr; + if (addr < config) + return virtio_ioport_read(proxy, addr); + addr -= config; + return virtio_config_readb(proxy->vdev, addr); +} + +static uint32_t virtio_pci_config_readw(void *opaque, uint32_t addr) +{ + VirtIOPCIProxy *proxy = opaque; + uint32_t config = VIRTIO_PCI_CONFIG(&proxy->pci_dev); + addr -= proxy->addr; + if (addr < config) + return virtio_ioport_read(proxy, addr); + addr -= config; + return virtio_config_readw(proxy->vdev, addr); +} + +static uint32_t virtio_pci_config_readl(void *opaque, uint32_t addr) +{ + VirtIOPCIProxy *proxy = opaque; + uint32_t config = VIRTIO_PCI_CONFIG(&proxy->pci_dev); + addr -= proxy->addr; + if (addr < config) + return virtio_ioport_read(proxy, addr); + addr -= config; + return virtio_config_readl(proxy->vdev, addr); +} + +static void virtio_pci_config_writeb(void *opaque, uint32_t addr, uint32_t val) +{ + VirtIOPCIProxy *proxy = opaque; + uint32_t config = VIRTIO_PCI_CONFIG(&proxy->pci_dev); + addr -= proxy->addr; + if (addr < config) { + virtio_ioport_write(proxy, addr, val); + return; + } + addr -= config; + virtio_config_writeb(proxy->vdev, addr, val); +} + +static void virtio_pci_config_writew(void *opaque, uint32_t addr, uint32_t val) +{ + VirtIOPCIProxy *proxy = opaque; + uint32_t config = VIRTIO_PCI_CONFIG(&proxy->pci_dev); + addr -= proxy->addr; + if (addr < config) { + virtio_ioport_write(proxy, addr, val); + return; + } + addr -= config; + virtio_config_writew(proxy->vdev, addr, val); +} + +static void virtio_pci_config_writel(void *opaque, uint32_t addr, uint32_t val) +{ + VirtIOPCIProxy *proxy = opaque; + uint32_t config = VIRTIO_PCI_CONFIG(&proxy->pci_dev); + addr -= proxy->addr; + if (addr < config) { + virtio_ioport_write(proxy, addr, val); + return; + } + addr -= config; + virtio_config_writel(proxy->vdev, addr, val); +} + +static void virtio_map(PCIDevice *pci_dev, int region_num, + pcibus_t addr, pcibus_t size, int type) +{ + VirtIOPCIProxy *proxy = container_of(pci_dev, VirtIOPCIProxy, pci_dev); + VirtIODevice *vdev = proxy->vdev; + unsigned config_len = VIRTIO_PCI_REGION_SIZE(pci_dev) + vdev->config_len; + + proxy->addr = addr; + + register_ioport_write(addr, config_len, 1, virtio_pci_config_writeb, proxy); + register_ioport_write(addr, config_len, 2, virtio_pci_config_writew, proxy); + register_ioport_write(addr, config_len, 4, virtio_pci_config_writel, proxy); + register_ioport_read(addr, config_len, 1, virtio_pci_config_readb, proxy); + register_ioport_read(addr, config_len, 2, virtio_pci_config_readw, proxy); + register_ioport_read(addr, config_len, 4, virtio_pci_config_readl, proxy); + + if (vdev->config_len) + vdev->get_config(vdev, vdev->config); +} + +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); + + if (PCI_COMMAND == address) { + if (!(val & PCI_COMMAND_MASTER)) { + if (!(proxy->flags & VIRTIO_PCI_FLAG_BUS_MASTER_BUG)) { + virtio_pci_stop_ioeventfd(proxy); + virtio_set_status(proxy->vdev, + proxy->vdev->status & ~VIRTIO_CONFIG_S_DRIVER_OK); + } + } + } + + pci_default_write_config(pci_dev, address, val, len); + msix_write_config(pci_dev, address, val, len); +} + +static unsigned virtio_pci_get_features(void *opaque) +{ + VirtIOPCIProxy *proxy = opaque; + return proxy->host_features; +} + +static void virtio_pci_guest_notifier_read(void *opaque) +{ + VirtQueue *vq = opaque; + EventNotifier *n = virtio_queue_get_guest_notifier(vq); + if (event_notifier_test_and_clear(n)) { + virtio_irq(vq); + } +} + +static int virtio_pci_set_guest_notifier(void *opaque, int n, bool assign) +{ + VirtIOPCIProxy *proxy = opaque; + VirtQueue *vq = virtio_get_queue(proxy->vdev, n); + EventNotifier *notifier = virtio_queue_get_guest_notifier(vq); + + if (assign) { + int r = event_notifier_init(notifier, 0); + if (r < 0) { + return r; + } + qemu_set_fd_handler(event_notifier_get_fd(notifier), + virtio_pci_guest_notifier_read, NULL, vq); + } else { + qemu_set_fd_handler(event_notifier_get_fd(notifier), + NULL, NULL, NULL); + event_notifier_cleanup(notifier); + } + + return 0; +} + +static bool virtio_pci_query_guest_notifiers(void *opaque) +{ + VirtIOPCIProxy *proxy = opaque; + return msix_enabled(&proxy->pci_dev); +} + +static int virtio_pci_set_guest_notifiers(void *opaque, bool assign) +{ + VirtIOPCIProxy *proxy = opaque; + VirtIODevice *vdev = proxy->vdev; + int r, n; + + for (n = 0; n < VIRTIO_PCI_QUEUE_MAX; n++) { + if (!virtio_queue_get_num(vdev, n)) { + break; + } + + r = virtio_pci_set_guest_notifier(opaque, n, assign); + if (r < 0) { + goto assign_error; + } + } + + return 0; + +assign_error: + /* We get here on assignment failure. Recover by undoing for VQs 0 .. n. */ + while (--n >= 0) { + virtio_pci_set_guest_notifier(opaque, n, !assign); + } + return r; +} + +static int virtio_pci_set_host_notifier(void *opaque, int n, bool assign) +{ + VirtIOPCIProxy *proxy = 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_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); +} + +static void virtio_pci_vmstate_change(void *opaque, bool running) +{ + VirtIOPCIProxy *proxy = opaque; + + if (running) { + /* Try to find out if the guest has bus master disabled, but is + in ready state. Then we have a buggy guest OS. */ + if ((proxy->vdev->status & VIRTIO_CONFIG_S_DRIVER_OK) && + !(proxy->pci_dev.config[PCI_COMMAND] & PCI_COMMAND_MASTER)) { + proxy->flags |= VIRTIO_PCI_FLAG_BUS_MASTER_BUG; + } + virtio_pci_start_ioeventfd(proxy); + } else { + virtio_pci_stop_ioeventfd(proxy); + } +} + +static const VirtIOBindings virtio_pci_bindings = { + .notify = virtio_pci_notify, + .save_config = virtio_pci_save_config, + .load_config = virtio_pci_load_config, + .save_queue = virtio_pci_save_queue, + .load_queue = virtio_pci_load_queue, + .get_features = virtio_pci_get_features, + .query_guest_notifiers = virtio_pci_query_guest_notifiers, + .set_host_notifier = virtio_pci_set_host_notifier, + .set_guest_notifiers = virtio_pci_set_guest_notifiers, + .vmstate_change = virtio_pci_vmstate_change, +}; + +static void virtio_init_pci(VirtIOPCIProxy *proxy, VirtIODevice *vdev, + uint16_t vendor, uint16_t device, + uint16_t class_code, uint8_t pif) +{ + uint8_t *config; + uint32_t size; + + proxy->vdev = vdev; + + config = proxy->pci_dev.config; + pci_config_set_vendor_id(config, vendor); + pci_config_set_device_id(config, device); + + config[0x08] = VIRTIO_PCI_ABI_VERSION; + + config[0x09] = pif; + pci_config_set_class(config, class_code); + + config[0x2c] = vendor & 0xFF; + config[0x2d] = (vendor >> 8) & 0xFF; + config[0x2e] = vdev->device_id & 0xFF; + config[0x2f] = (vdev->device_id >> 8) & 0xFF; + + config[0x3d] = 1; + + if (vdev->nvectors && !msix_init(&proxy->pci_dev, vdev->nvectors, 1, 0)) { + pci_register_bar(&proxy->pci_dev, 1, + msix_bar_size(&proxy->pci_dev), + PCI_BASE_ADDRESS_SPACE_MEMORY, + msix_mmio_map); + } else + vdev->nvectors = 0; + + proxy->pci_dev.config_write = virtio_write_config; + + size = VIRTIO_PCI_REGION_SIZE(&proxy->pci_dev) + vdev->config_len; + if (size & (size-1)) + size = 1 << qemu_fls(size); + + pci_register_bar(&proxy->pci_dev, 0, size, PCI_BASE_ADDRESS_SPACE_IO, + virtio_map); + + if (!kvm_has_many_ioeventfds()) { + proxy->flags &= ~VIRTIO_PCI_FLAG_USE_IOEVENTFD; + } + + virtio_bind_device(vdev, &virtio_pci_bindings, proxy); + proxy->host_features |= 0x1 << VIRTIO_F_NOTIFY_ON_EMPTY; + proxy->host_features |= 0x1 << VIRTIO_F_BAD_FEATURE; + proxy->host_features = vdev->get_features(vdev, proxy->host_features); +} + +static int virtio_blk_init_pci(PCIDevice *pci_dev) +{ + VirtIOPCIProxy *proxy = DO_UPCAST(VirtIOPCIProxy, pci_dev, pci_dev); + VirtIODevice *vdev; + + if (proxy->class_code != PCI_CLASS_STORAGE_SCSI && + proxy->class_code != PCI_CLASS_STORAGE_OTHER) + proxy->class_code = PCI_CLASS_STORAGE_SCSI; + + vdev = virtio_blk_init(&pci_dev->qdev, &proxy->block); + if (!vdev) { + return -1; + } + vdev->nvectors = proxy->nvectors; + virtio_init_pci(proxy, vdev, + PCI_VENDOR_ID_REDHAT_QUMRANET, + PCI_DEVICE_ID_VIRTIO_BLOCK, + proxy->class_code, 0x00); + /* make the actual value visible */ + proxy->nvectors = vdev->nvectors; + return 0; +} + +static int virtio_exit_pci(PCIDevice *pci_dev) +{ + return msix_uninit(pci_dev); +} + +static int virtio_blk_exit_pci(PCIDevice *pci_dev) +{ + VirtIOPCIProxy *proxy = DO_UPCAST(VirtIOPCIProxy, pci_dev, pci_dev); + + virtio_pci_stop_ioeventfd(proxy); + virtio_blk_exit(proxy->vdev); + blockdev_mark_auto_del(proxy->block.bs); + return virtio_exit_pci(pci_dev); +} + +static int virtio_serial_init_pci(PCIDevice *pci_dev) +{ + VirtIOPCIProxy *proxy = DO_UPCAST(VirtIOPCIProxy, pci_dev, pci_dev); + VirtIODevice *vdev; + + if (proxy->class_code != PCI_CLASS_COMMUNICATION_OTHER && + proxy->class_code != PCI_CLASS_DISPLAY_OTHER && /* qemu 0.10 */ + proxy->class_code != PCI_CLASS_OTHERS) /* qemu-kvm */ + proxy->class_code = PCI_CLASS_COMMUNICATION_OTHER; + + vdev = virtio_serial_init(&pci_dev->qdev, proxy->max_virtserial_ports); + if (!vdev) { + return -1; + } + vdev->nvectors = proxy->nvectors == DEV_NVECTORS_UNSPECIFIED + ? proxy->max_virtserial_ports + 1 + : proxy->nvectors; + virtio_init_pci(proxy, vdev, + PCI_VENDOR_ID_REDHAT_QUMRANET, + PCI_DEVICE_ID_VIRTIO_CONSOLE, + proxy->class_code, 0x00); + proxy->nvectors = vdev->nvectors; + return 0; +} + +static int virtio_serial_exit_pci(PCIDevice *pci_dev) +{ + VirtIOPCIProxy *proxy = DO_UPCAST(VirtIOPCIProxy, pci_dev, pci_dev); + + virtio_serial_exit(proxy->vdev); + return virtio_exit_pci(pci_dev); +} + +static int virtio_net_init_pci(PCIDevice *pci_dev) +{ + VirtIOPCIProxy *proxy = DO_UPCAST(VirtIOPCIProxy, pci_dev, pci_dev); + VirtIODevice *vdev; + + vdev = virtio_net_init(&pci_dev->qdev, &proxy->nic, &proxy->net); + + vdev->nvectors = proxy->nvectors; + virtio_init_pci(proxy, vdev, + PCI_VENDOR_ID_REDHAT_QUMRANET, + PCI_DEVICE_ID_VIRTIO_NET, + PCI_CLASS_NETWORK_ETHERNET, + 0x00); + + /* make the actual value visible */ + proxy->nvectors = vdev->nvectors; + return 0; +} + +static int virtio_net_exit_pci(PCIDevice *pci_dev) +{ + VirtIOPCIProxy *proxy = DO_UPCAST(VirtIOPCIProxy, pci_dev, pci_dev); + + virtio_pci_stop_ioeventfd(proxy); + virtio_net_exit(proxy->vdev); + return virtio_exit_pci(pci_dev); +} + +static int virtio_balloon_init_pci(PCIDevice *pci_dev) +{ + VirtIOPCIProxy *proxy = DO_UPCAST(VirtIOPCIProxy, pci_dev, pci_dev); + VirtIODevice *vdev; + + vdev = virtio_balloon_init(&pci_dev->qdev); + virtio_init_pci(proxy, vdev, + PCI_VENDOR_ID_REDHAT_QUMRANET, + PCI_DEVICE_ID_VIRTIO_BALLOON, + PCI_CLASS_MEMORY_RAM, + 0x00); + return 0; +} + +#ifdef CONFIG_VIRTFS +static int virtio_9p_init_pci(PCIDevice *pci_dev) +{ + VirtIOPCIProxy *proxy = DO_UPCAST(VirtIOPCIProxy, pci_dev, pci_dev); + VirtIODevice *vdev; + + vdev = virtio_9p_init(&pci_dev->qdev, &proxy->fsconf); + vdev->nvectors = proxy->nvectors; + virtio_init_pci(proxy, vdev, + PCI_VENDOR_ID_REDHAT_QUMRANET, + 0x1009, + 0x2, + 0x00); + /* make the actual value visible */ + proxy->nvectors = vdev->nvectors; + return 0; +} +#endif + +static PCIDeviceInfo virtio_info[] = { + { + .qdev.name = "virtio-blk-pci", + .qdev.alias = "virtio-blk", + .qdev.size = sizeof(VirtIOPCIProxy), + .init = virtio_blk_init_pci, + .exit = virtio_blk_exit_pci, + .qdev.props = (Property[]) { + DEFINE_PROP_HEX32("class", VirtIOPCIProxy, class_code, 0), + DEFINE_BLOCK_PROPERTIES(VirtIOPCIProxy, block), + DEFINE_PROP_BIT("ioeventfd", VirtIOPCIProxy, flags, + VIRTIO_PCI_FLAG_USE_IOEVENTFD_BIT, true), + DEFINE_PROP_UINT32("vectors", VirtIOPCIProxy, nvectors, 2), + DEFINE_VIRTIO_BLK_FEATURES(VirtIOPCIProxy, host_features), + DEFINE_PROP_END_OF_LIST(), + }, + .qdev.reset = virtio_pci_reset, + },{ + .qdev.name = "virtio-net-pci", + .qdev.size = sizeof(VirtIOPCIProxy), + .init = virtio_net_init_pci, + .exit = virtio_net_exit_pci, + .romfile = "pxe-virtio.bin", + .qdev.props = (Property[]) { + DEFINE_PROP_BIT("ioeventfd", VirtIOPCIProxy, flags, + VIRTIO_PCI_FLAG_USE_IOEVENTFD_BIT, false), + DEFINE_PROP_UINT32("vectors", VirtIOPCIProxy, nvectors, 3), + DEFINE_VIRTIO_NET_FEATURES(VirtIOPCIProxy, host_features), + DEFINE_NIC_PROPERTIES(VirtIOPCIProxy, nic), + DEFINE_PROP_UINT32("x-txtimer", VirtIOPCIProxy, + net.txtimer, TX_TIMER_INTERVAL), + DEFINE_PROP_INT32("x-txburst", VirtIOPCIProxy, + net.txburst, TX_BURST), + DEFINE_PROP_STRING("tx", VirtIOPCIProxy, net.tx), + DEFINE_PROP_END_OF_LIST(), + }, + .qdev.reset = virtio_pci_reset, + },{ + .qdev.name = "virtio-serial-pci", + .qdev.alias = "virtio-serial", + .qdev.size = sizeof(VirtIOPCIProxy), + .init = virtio_serial_init_pci, + .exit = virtio_serial_exit_pci, + .qdev.props = (Property[]) { + DEFINE_PROP_UINT32("vectors", VirtIOPCIProxy, nvectors, + DEV_NVECTORS_UNSPECIFIED), + DEFINE_PROP_HEX32("class", VirtIOPCIProxy, class_code, 0), + DEFINE_VIRTIO_COMMON_FEATURES(VirtIOPCIProxy, host_features), + DEFINE_PROP_UINT32("max_ports", VirtIOPCIProxy, max_virtserial_ports, + 31), + DEFINE_PROP_END_OF_LIST(), + }, + .qdev.reset = virtio_pci_reset, + },{ + .qdev.name = "virtio-balloon-pci", + .qdev.size = sizeof(VirtIOPCIProxy), + .init = virtio_balloon_init_pci, + .exit = virtio_exit_pci, + .qdev.props = (Property[]) { + DEFINE_VIRTIO_COMMON_FEATURES(VirtIOPCIProxy, host_features), + DEFINE_PROP_END_OF_LIST(), + }, + .qdev.reset = virtio_pci_reset, + },{ +#ifdef CONFIG_VIRTFS + .qdev.name = "virtio-9p-pci", + .qdev.size = sizeof(VirtIOPCIProxy), + .init = virtio_9p_init_pci, + .qdev.props = (Property[]) { + DEFINE_PROP_UINT32("vectors", VirtIOPCIProxy, nvectors, 2), + DEFINE_VIRTIO_COMMON_FEATURES(VirtIOPCIProxy, host_features), + DEFINE_PROP_STRING("mount_tag", VirtIOPCIProxy, fsconf.tag), + DEFINE_PROP_STRING("fsdev", VirtIOPCIProxy, fsconf.fsdev_id), + DEFINE_PROP_END_OF_LIST(), + }, + }, { +#endif + /* end of list */ + } +}; + +static void virtio_pci_register_devices(void) +{ + pci_qdev_register_many(virtio_info); +} + +device_init(virtio_pci_register_devices) diff --git a/hw/virtio-serial-bus.c b/hw/virtio-serial-bus.c new file mode 100644 index 0000000..e05ab5e --- /dev/null +++ b/hw/virtio-serial-bus.c @@ -0,0 +1,908 @@ +/* + * A bus for connecting virtio serial and console ports + * + * Copyright (C) 2009, 2010 Red Hat, Inc. + * + * Author(s): + * Amit Shah + * + * Some earlier parts are: + * Copyright IBM, Corp. 2008 + * authored by + * Christian Ehrhardt + * + * This work is licensed under the terms of the GNU GPL, version 2. See + * the COPYING file in the top-level directory. + */ + +#include "iov.h" +#include "monitor.h" +#include "qemu-queue.h" +#include "sysbus.h" +#include "virtio-serial.h" + +/* The virtio-serial bus on top of which the ports will ride as devices */ +struct VirtIOSerialBus { + BusState qbus; + + /* This is the parent device that provides the bus for ports. */ + VirtIOSerial *vser; + + /* The maximum number of ports that can ride on top of this bus */ + uint32_t max_nr_ports; +}; + +struct VirtIOSerial { + VirtIODevice vdev; + + VirtQueue *c_ivq, *c_ovq; + /* Arrays of ivqs and ovqs: one per port */ + VirtQueue **ivqs, **ovqs; + + VirtIOSerialBus *bus; + + DeviceState *qdev; + + QTAILQ_HEAD(, VirtIOSerialPort) ports; + + /* bitmap for identifying active ports */ + uint32_t *ports_map; + + struct virtio_console_config config; +}; + +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 bool use_multiport(VirtIOSerial *vser) +{ + return vser->vdev.guest_features & (1 << 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, + buf + offset, size - offset); + offset += len; + + virtqueue_push(vq, &elem, len); + } + + virtio_notify(&port->vser->vdev, 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) +{ + assert(port); + assert(virtio_queue_ready(vq)); + + 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 = port->info->have_data(port, + port->elem.out_sg[i].iov_base + + port->iov_offset, + buf_size); + if (ret < 0 && ret != -EAGAIN) { + /* We don't handle any other type of errors here */ + abort(); + } + if (ret == -EAGAIN || (ret >= 0 && ret < buf_size)) { + virtio_serial_throttle_port(port, true); + 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, &port->vser->vdev); +} + +static size_t send_control_msg(VirtIOSerialPort *port, void *buf, size_t len) +{ + VirtQueueElement elem; + VirtQueue *vq; + struct virtio_console_control *cpkt; + + vq = port->vser->c_ivq; + if (!virtio_queue_ready(vq)) { + return 0; + } + if (!virtqueue_pop(vq, &elem)) { + return 0; + } + + cpkt = (struct virtio_console_control *)buf; + stl_p(&cpkt->id, port->id); + memcpy(elem.in_sg[0].iov_base, buf, len); + + virtqueue_push(vq, &elem, len); + virtio_notify(&port->vser->vdev, vq); + return len; +} + +static size_t send_control_event(VirtIOSerialPort *port, uint16_t event, + uint16_t value) +{ + struct virtio_console_control cpkt; + + stw_p(&cpkt.event, event); + stw_p(&cpkt.value, value); + + return send_control_msg(port, &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, 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, &port->vser->vdev); + + send_control_event(port, 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) +{ + VirtQueue *vq = port->ivq; + + if (!virtio_queue_ready(vq) || + !(port->vser->vdev.status & VIRTIO_CONFIG_S_DRIVER_OK) || + virtio_queue_empty(vq)) { + return 0; + } + if (use_multiport(port->vser) && !port->guest_connected) { + return 0; + } + + if (virtqueue_avail_bytes(vq, 4096, 0)) { + return 4096; + } + if (virtqueue_avail_bytes(vq, 1, 0)) { + return 1; + } + return 0; +} + +void virtio_serial_throttle_port(VirtIOSerialPort *port, bool throttle) +{ + if (!port) { + return; + } + + port->throttled = throttle; + if (throttle) { + return; + } + + flush_queued_data(port); +} + +/* Guest wants to notify us of some event */ +static void handle_control_message(VirtIOSerial *vser, void *buf, size_t len) +{ + struct VirtIOSerialPort *port; + 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 = lduw_p(&gcpkt->event); + cpkt.value = lduw_p(&gcpkt->value); + + port = find_port_by_id(vser, ldl_p(&gcpkt->id)); + if (!port && cpkt.event != VIRTIO_CONSOLE_DEVICE_READY) + return; + + switch(cpkt.event) { + case VIRTIO_CONSOLE_DEVICE_READY: + if (!cpkt.value) { + error_report("virtio-serial-bus: Guest failure in adding device %s\n", + vser->bus->qbus.name); + break; + } + /* + * 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(port, VIRTIO_CONSOLE_PORT_ADD, 1); + } + break; + + case VIRTIO_CONSOLE_PORT_READY: + if (!cpkt.value) { + error_report("virtio-serial-bus: Guest failure in adding port %u for device %s\n", + 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 (port->is_console) { + send_control_event(port, VIRTIO_CONSOLE_CONSOLE_PORT, 1); + } + + if (port->name) { + stw_p(&cpkt.event, VIRTIO_CONSOLE_PORT_NAME); + stw_p(&cpkt.value, 1); + + buffer_len = sizeof(cpkt) + strlen(port->name) + 1; + buffer = qemu_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(port, buffer, buffer_len); + qemu_free(buffer); + } + + if (port->host_connected) { + send_control_event(port, 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 (port->info->guest_ready) { + port->info->guest_ready(port); + } + break; + + case VIRTIO_CONSOLE_PORT_OPEN: + port->guest_connected = cpkt.value; + if (cpkt.value && port->info->guest_open) { + /* Send the guest opened notification if an app is interested */ + port->info->guest_open(port); + } + + if (!cpkt.value && port->info->guest_close) { + /* Send the guest closed notification if an app is interested */ + port->info->guest_close(port); + } + 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 = DO_UPCAST(VirtIOSerial, vdev, vdev); + + len = 0; + buf = NULL; + while (virtqueue_pop(vq, &elem)) { + size_t cur_len, copied; + + 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) { + qemu_free(buf); + + buf = qemu_malloc(cur_len); + len = cur_len; + } + copied = iov_to_buf(elem.out_sg, elem.out_num, buf, 0, len); + + handle_control_message(vser, buf, copied); + virtqueue_push(vq, &elem, 0); + } + qemu_free(buf); + virtio_notify(vdev, vq); +} + +/* Guest wrote something to some port. */ +static void handle_output(VirtIODevice *vdev, VirtQueue *vq) +{ + VirtIOSerial *vser; + VirtIOSerialPort *port; + bool discard; + + vser = DO_UPCAST(VirtIOSerial, vdev, vdev); + port = find_port_by_vq(vser, vq); + + discard = false; + if (!port || !port->host_connected || !port->info->have_data) { + discard = true; + } + + if (discard) { + discard_vq_data(vq, vdev); + return; + } + if (port->throttled) { + return; + } + + do_flush_queued_data(port, vq, vdev); +} + +static void handle_input(VirtIODevice *vdev, VirtQueue *vq) +{ +} + +static uint32_t get_features(VirtIODevice *vdev, uint32_t features) +{ + VirtIOSerial *vser; + + vser = DO_UPCAST(VirtIOSerial, vdev, vdev); + + if (vser->bus->max_nr_ports > 1) { + features |= (1 << VIRTIO_CONSOLE_F_MULTIPORT); + } + return features; +} + +/* Guest requested config info */ +static void get_config(VirtIODevice *vdev, uint8_t *config_data) +{ + VirtIOSerial *vser; + + vser = DO_UPCAST(VirtIOSerial, vdev, vdev); + memcpy(config_data, &vser->config, sizeof(struct virtio_console_config)); +} + +static void set_config(VirtIODevice *vdev, const uint8_t *config_data) +{ + struct virtio_console_config config; + + memcpy(&config, config_data, sizeof(config)); +} + +static void virtio_serial_save(QEMUFile *f, void *opaque) +{ + VirtIOSerial *s = opaque; + VirtIOSerialPort *port; + uint32_t nr_active_ports; + unsigned int i; + + /* The virtio device */ + virtio_save(&s->vdev, f); + + /* The config space */ + qemu_put_be16s(f, &s->config.cols); + qemu_put_be16s(f, &s->config.rows); + + qemu_put_be32s(f, &s->config.max_nr_ports); + + /* The ports map */ + + for (i = 0; i < (s->config.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 int virtio_serial_load(QEMUFile *f, void *opaque, int version_id) +{ + VirtIOSerial *s = opaque; + VirtIOSerialPort *port; + uint32_t max_nr_ports, nr_active_ports, ports_map; + unsigned int i; + + if (version_id > 3) { + return -EINVAL; + } + + /* The virtio device */ + virtio_load(&s->vdev, f); + + if (version_id < 2) { + return 0; + } + + /* The config space */ + qemu_get_be16s(f, &s->config.cols); + qemu_get_be16s(f, &s->config.rows); + + qemu_get_be32s(f, &max_nr_ports); + if (max_nr_ports > s->config.max_nr_ports) { + /* Source could have had more ports than us. Fail migration. */ + return -EINVAL; + } + + 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); + + /* Items in struct VirtIOSerialPort */ + for (i = 0; i < nr_active_ports; i++) { + uint32_t id; + bool host_connected; + + id = qemu_get_be32(f); + port = find_port_by_id(s, id); + + port->guest_connected = qemu_get_byte(f); + host_connected = qemu_get_byte(f); + if (host_connected != port->host_connected) { + /* + * We have to let the guest know of the host connection + * status change + */ + send_control_event(port, VIRTIO_CONSOLE_PORT_OPEN, + port->host_connected); + } + + 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); + } + } + } + return 0; +} + +static void virtser_bus_dev_print(Monitor *mon, DeviceState *qdev, int indent); + +static struct BusInfo virtser_bus_info = { + .name = "virtio-serial-bus", + .size = sizeof(VirtIOSerialBus), + .print_dev = virtser_bus_dev_print, +}; + +static VirtIOSerialBus *virtser_bus_new(DeviceState *dev) +{ + VirtIOSerialBus *bus; + + bus = FROM_QBUS(VirtIOSerialBus, qbus_create(&virtser_bus_info, dev, NULL)); + bus->qbus.allow_hotplug = 1; + + return bus; +} + +static void virtser_bus_dev_print(Monitor *mon, DeviceState *qdev, int indent) +{ + VirtIOSerialDevice *dev = DO_UPCAST(VirtIOSerialDevice, qdev, qdev); + VirtIOSerialPort *port = DO_UPCAST(VirtIOSerialPort, dev, &dev->qdev); + + monitor_printf(mon, "%*s dev-prop-int: id: %u\n", + indent, "", port->id); + monitor_printf(mon, "%*s dev-prop-int: guest_connected: %d\n", + indent, "", port->guest_connected); + monitor_printf(mon, "%*s dev-prop-int: host_connected: %d\n", + indent, "", port->host_connected); + monitor_printf(mon, "%*s dev-prop-int: throttled: %d\n", + indent, "", port->throttled); +} + +/* 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; + + for (i = 0; i < (vser->config.max_nr_ports + 31) / 32; i++) { + uint32_t map, bit; + + map = vser->ports_map[i]; + bit = ffs(~map); + if (bit) { + return (bit - 1) + 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(find_port_by_id(vser, port_id), + VIRTIO_CONSOLE_PORT_ADD, 1); +} + +static void remove_port(VirtIOSerial *vser, uint32_t port_id) +{ + VirtIOSerialPort *port; + unsigned int i; + + i = port_id / 32; + vser->ports_map[i] &= ~(1U << (port_id % 32)); + + port = find_port_by_id(vser, port_id); + /* Flush out any unconsumed buffers first */ + discard_vq_data(port->ovq, &port->vser->vdev); + + send_control_event(port, VIRTIO_CONSOLE_PORT_REMOVE, 1); +} + +static int virtser_port_qdev_init(DeviceState *qdev, DeviceInfo *base) +{ + VirtIOSerialDevice *dev = DO_UPCAST(VirtIOSerialDevice, qdev, qdev); + VirtIOSerialPortInfo *info = DO_UPCAST(VirtIOSerialPortInfo, qdev, base); + VirtIOSerialPort *port = DO_UPCAST(VirtIOSerialPort, dev, &dev->qdev); + VirtIOSerialBus *bus = DO_UPCAST(VirtIOSerialBus, qbus, qdev->parent_bus); + int ret; + bool plugging_port0; + + port->vser = bus->vser; + + /* + * 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 = port->is_console && !find_port_by_id(port->vser, 0); + + if (find_port_by_id(port->vser, port->id)) { + error_report("virtio-serial-bus: A port already exists at id %u\n", + port->id); + return -1; + } + + 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_report("virtio-serial-bus: Maximum port limit for this device reached\n"); + return -1; + } + } + } + + if (port->id >= port->vser->config.max_nr_ports) { + error_report("virtio-serial-bus: Out-of-range port id specified, max. allowed: %u\n", + port->vser->config.max_nr_ports - 1); + return -1; + } + + dev->info = info; + ret = info->init(dev); + if (ret) { + return ret; + } + + if (!use_multiport(port->vser)) { + /* + * Allow writes to guest in this case; we have no way of + * knowing if a guest port is connected. + */ + port->guest_connected = true; + } + + port->elem.out_num = 0; + + 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(&port->vser->vdev); + + return ret; +} + +static int virtser_port_qdev_exit(DeviceState *qdev) +{ + VirtIOSerialDevice *dev = DO_UPCAST(VirtIOSerialDevice, qdev, qdev); + VirtIOSerialPort *port = DO_UPCAST(VirtIOSerialPort, dev, &dev->qdev); + VirtIOSerial *vser = port->vser; + + remove_port(port->vser, port->id); + + QTAILQ_REMOVE(&vser->ports, port, next); + + if (port->info->exit) + port->info->exit(dev); + + return 0; +} + +void virtio_serial_port_qdev_register(VirtIOSerialPortInfo *info) +{ + info->qdev.init = virtser_port_qdev_init; + info->qdev.bus_info = &virtser_bus_info; + info->qdev.exit = virtser_port_qdev_exit; + info->qdev.unplug = qdev_simple_unplug_cb; + qdev_register(&info->qdev); +} + +VirtIODevice *virtio_serial_init(DeviceState *dev, uint32_t max_nr_ports) +{ + VirtIOSerial *vser; + VirtIODevice *vdev; + uint32_t i, max_supported_ports; + + if (!max_nr_ports) + return NULL; + + /* Each port takes 2 queues, and one pair is for the control queue */ + max_supported_ports = VIRTIO_PCI_QUEUE_MAX / 2 - 1; + + if (max_nr_ports > max_supported_ports) { + error_report("maximum ports supported: %u", max_supported_ports); + return NULL; + } + + vdev = virtio_common_init("virtio-serial", VIRTIO_ID_CONSOLE, + sizeof(struct virtio_console_config), + sizeof(VirtIOSerial)); + + vser = DO_UPCAST(VirtIOSerial, vdev, vdev); + + /* Spawn a new virtio-serial bus on which the ports will ride as devices */ + vser->bus = virtser_bus_new(dev); + vser->bus->vser = vser; + QTAILQ_INIT(&vser->ports); + + vser->bus->max_nr_ports = max_nr_ports; + vser->ivqs = qemu_malloc(max_nr_ports * sizeof(VirtQueue *)); + vser->ovqs = qemu_malloc(max_nr_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->config.max_nr_ports = max_nr_ports; + vser->ports_map = qemu_mallocz(((max_nr_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->vdev.get_features = get_features; + vser->vdev.get_config = get_config; + vser->vdev.set_config = set_config; + + vser->qdev = dev; + + /* + * 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); + + return vdev; +} + +void virtio_serial_exit(VirtIODevice *vdev) +{ + VirtIOSerial *vser = DO_UPCAST(VirtIOSerial, vdev, vdev); + + unregister_savevm(vser->qdev, "virtio-console", vser); + + qemu_free(vser->ivqs); + qemu_free(vser->ovqs); + qemu_free(vser->ports_map); + + virtio_cleanup(vdev); +} diff --git a/hw/virtio-serial.h b/hw/virtio-serial.h new file mode 100644 index 0000000..a308196 --- /dev/null +++ b/hw/virtio-serial.h @@ -0,0 +1,202 @@ +/* + * Virtio Serial / Console Support + * + * Copyright IBM, Corp. 2008 + * Copyright Red Hat, Inc. 2009, 2010 + * + * Authors: + * Christian Ehrhardt + * Amit Shah + * + * 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_SERIAL_H +#define _QEMU_VIRTIO_SERIAL_H + +#include "qdev.h" +#include "virtio.h" + +/* == Interface shared between the guest kernel and qemu == */ + +/* The Virtio ID for virtio console / serial ports */ +#define VIRTIO_ID_CONSOLE 3 + +/* Features supported */ +#define VIRTIO_CONSOLE_F_MULTIPORT 1 + +#define VIRTIO_CONSOLE_BAD_ID (~(uint32_t)0) + +struct virtio_console_config { + /* + * These two fields are used by VIRTIO_CONSOLE_F_SIZE which + * isn't implemented here yet + */ + uint16_t cols; + uint16_t rows; + + uint32_t max_nr_ports; +} __attribute__((packed)); + +struct virtio_console_control { + uint32_t id; /* Port number */ + uint16_t event; /* The kind of control event (see below) */ + uint16_t value; /* Extra information for the key */ +}; + +/* Some events for the internal messages (control packets) */ +#define VIRTIO_CONSOLE_DEVICE_READY 0 +#define VIRTIO_CONSOLE_PORT_ADD 1 +#define VIRTIO_CONSOLE_PORT_REMOVE 2 +#define VIRTIO_CONSOLE_PORT_READY 3 +#define VIRTIO_CONSOLE_CONSOLE_PORT 4 +#define VIRTIO_CONSOLE_RESIZE 5 +#define VIRTIO_CONSOLE_PORT_OPEN 6 +#define VIRTIO_CONSOLE_PORT_NAME 7 + +/* == In-qemu interface == */ + +typedef struct VirtIOSerial VirtIOSerial; +typedef struct VirtIOSerialBus VirtIOSerialBus; +typedef struct VirtIOSerialPort VirtIOSerialPort; +typedef struct VirtIOSerialPortInfo VirtIOSerialPortInfo; + +typedef struct VirtIOSerialDevice { + DeviceState qdev; + VirtIOSerialPortInfo *info; +} VirtIOSerialDevice; + +/* + * This is the state that's shared between all the ports. Some of the + * state is configurable via command-line options. Some of it can be + * set by individual devices in their initfn routines. Some of the + * state is set by the generic qdev device init routine. + */ +struct VirtIOSerialPort { + DeviceState dev; + VirtIOSerialPortInfo *info; + + QTAILQ_ENTRY(VirtIOSerialPort) next; + + /* + * This field gives us the virtio device as well as the qdev bus + * that we are associated with + */ + VirtIOSerial *vser; + + VirtQueue *ivq, *ovq; + + /* + * This name is sent to the guest and exported via sysfs. + * The guest could create symlinks based on this information. + * The name is in the reverse fqdn format, like org.qemu.console.0 + */ + char *name; + + /* + * This id helps identify ports between the guest and the host. + * The guest sends a "header" with this id with each data packet + * that it sends and the host can then find out which associated + * device to send out this data to + */ + uint32_t id; + + /* + * This is the elem that we pop from the virtqueue. A slow + * backend that consumes guest data (e.g. the file backend for + * qemu chardevs) can cause the guest to block till all the output + * is flushed. This isn't desired, so we keep a note of the last + * element popped and continue consuming it once the backend + * becomes writable again. + */ + VirtQueueElement elem; + + /* + * The index and the offset into the iov buffer that was popped in + * elem above. + */ + uint32_t iov_idx; + uint64_t iov_offset; + + /* Identify if this is a port that binds with hvc in the guest */ + uint8_t is_console; + + /* Is the corresponding guest device open? */ + bool guest_connected; + /* Is this device open for IO on the host? */ + bool host_connected; + /* Do apps not want to receive data? */ + bool throttled; +}; + +struct VirtIOSerialPortInfo { + DeviceInfo qdev; + /* + * The per-port (or per-app) init function that's called when a + * new device is found on the bus. + */ + int (*init)(VirtIOSerialDevice *dev); + /* + * Per-port exit function that's called when a port gets + * hot-unplugged or removed. + */ + int (*exit)(VirtIOSerialDevice *dev); + + /* Callbacks for guest events */ + /* Guest opened device. */ + void (*guest_open)(VirtIOSerialPort *port); + /* Guest closed device. */ + void (*guest_close)(VirtIOSerialPort *port); + + /* Guest is now ready to accept data (virtqueues set up). */ + void (*guest_ready)(VirtIOSerialPort *port); + + /* + * Guest wrote some data to the port. This data is handed over to + * the app via this callback. The app can return a size less than + * 'len'. In this case, throttling will be enabled for this port. + */ + ssize_t (*have_data)(VirtIOSerialPort *port, const uint8_t *buf, + size_t len); +}; + +/* Interface to the virtio-serial bus */ + +/* + * Individual ports/apps should call this function to register the port + * with the virtio-serial bus + */ +void virtio_serial_port_qdev_register(VirtIOSerialPortInfo *info); + +/* + * Open a connection to the port + * Returns 0 on success (always). + */ +int virtio_serial_open(VirtIOSerialPort *port); + +/* + * Close the connection to the port + * Returns 0 on success (always). + */ +int virtio_serial_close(VirtIOSerialPort *port); + +/* + * Send data to Guest + */ +ssize_t virtio_serial_write(VirtIOSerialPort *port, const uint8_t *buf, + size_t size); + +/* + * Query whether a guest is ready to receive data. + */ +size_t virtio_serial_guest_ready(VirtIOSerialPort *port); + +/* + * Flow control: Ports can signal to the virtio-serial core to stop + * sending data or re-start sending data, depending on the 'throttle' + * value here. + */ +void virtio_serial_throttle_port(VirtIOSerialPort *port, bool throttle); + +#endif diff --git a/hw/virtio.c b/hw/virtio.c new file mode 100644 index 0000000..31bd9e3 --- /dev/null +++ b/hw/virtio.c @@ -0,0 +1,882 @@ +/* + * Virtio Support + * + * Copyright IBM, Corp. 2007 + * + * Authors: + * Anthony Liguori + * + * This work is licensed under the terms of the GNU GPL, version 2. See + * the COPYING file in the top-level directory. + * + */ + +#include + +#include "trace.h" +#include "qemu-error.h" +#include "virtio.h" +#include "sysemu.h" + +/* The alignment to use between consumer and producer parts of vring. + * x86 pagesize again. */ +#define VIRTIO_PCI_VRING_ALIGN 4096 + +/* QEMU doesn't strictly need write barriers since everything runs in + * lock-step. We'll leave the calls to wmb() in though to make it obvious for + * KVM or if kqemu gets SMP support. + * In any case, we must prevent the compiler from reordering the code. + * TODO: we likely need some rmb()/mb() as well. + */ + +#define wmb() __asm__ __volatile__("": : :"memory") + +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; + target_phys_addr_t desc; + target_phys_addr_t avail; + target_phys_addr_t used; +} VRing; + +struct VirtQueue +{ + VRing vring; + target_phys_addr_t pa; + uint16_t last_avail_idx; + int inuse; + uint16_t vector; + void (*handle_output)(VirtIODevice *vdev, VirtQueue *vq); + VirtIODevice *vdev; + EventNotifier guest_notifier; + EventNotifier host_notifier; +}; + +/* virt queue functions */ +static void virtqueue_init(VirtQueue *vq) +{ + target_phys_addr_t pa = vq->pa; + + vq->vring.desc = pa; + vq->vring.avail = pa + vq->vring.num * sizeof(VRingDesc); + vq->vring.used = vring_align(vq->vring.avail + + offsetof(VRingAvail, ring[vq->vring.num]), + VIRTIO_PCI_VRING_ALIGN); +} + +static inline uint64_t vring_desc_addr(target_phys_addr_t desc_pa, int i) +{ + target_phys_addr_t pa; + pa = desc_pa + sizeof(VRingDesc) * i + offsetof(VRingDesc, addr); + return ldq_phys(pa); +} + +static inline uint32_t vring_desc_len(target_phys_addr_t desc_pa, int i) +{ + target_phys_addr_t pa; + pa = desc_pa + sizeof(VRingDesc) * i + offsetof(VRingDesc, len); + return ldl_phys(pa); +} + +static inline uint16_t vring_desc_flags(target_phys_addr_t desc_pa, int i) +{ + target_phys_addr_t pa; + pa = desc_pa + sizeof(VRingDesc) * i + offsetof(VRingDesc, flags); + return lduw_phys(pa); +} + +static inline uint16_t vring_desc_next(target_phys_addr_t desc_pa, int i) +{ + target_phys_addr_t pa; + pa = desc_pa + sizeof(VRingDesc) * i + offsetof(VRingDesc, next); + return lduw_phys(pa); +} + +static inline uint16_t vring_avail_flags(VirtQueue *vq) +{ + target_phys_addr_t pa; + pa = vq->vring.avail + offsetof(VRingAvail, flags); + return lduw_phys(pa); +} + +static inline uint16_t vring_avail_idx(VirtQueue *vq) +{ + target_phys_addr_t pa; + pa = vq->vring.avail + offsetof(VRingAvail, idx); + return lduw_phys(pa); +} + +static inline uint16_t vring_avail_ring(VirtQueue *vq, int i) +{ + target_phys_addr_t pa; + pa = vq->vring.avail + offsetof(VRingAvail, ring[i]); + return lduw_phys(pa); +} + +static inline void vring_used_ring_id(VirtQueue *vq, int i, uint32_t val) +{ + target_phys_addr_t pa; + pa = vq->vring.used + offsetof(VRingUsed, ring[i].id); + stl_phys(pa, val); +} + +static inline void vring_used_ring_len(VirtQueue *vq, int i, uint32_t val) +{ + target_phys_addr_t pa; + pa = vq->vring.used + offsetof(VRingUsed, ring[i].len); + stl_phys(pa, val); +} + +static uint16_t vring_used_idx(VirtQueue *vq) +{ + target_phys_addr_t pa; + pa = vq->vring.used + offsetof(VRingUsed, idx); + return lduw_phys(pa); +} + +static inline void vring_used_idx_increment(VirtQueue *vq, uint16_t val) +{ + target_phys_addr_t pa; + pa = vq->vring.used + offsetof(VRingUsed, idx); + stw_phys(pa, vring_used_idx(vq) + val); +} + +static inline void vring_used_flags_set_bit(VirtQueue *vq, int mask) +{ + target_phys_addr_t pa; + pa = vq->vring.used + offsetof(VRingUsed, flags); + stw_phys(pa, lduw_phys(pa) | mask); +} + +static inline void vring_used_flags_unset_bit(VirtQueue *vq, int mask) +{ + target_phys_addr_t pa; + pa = vq->vring.used + offsetof(VRingUsed, flags); + stw_phys(pa, lduw_phys(pa) & ~mask); +} + +void virtio_queue_set_notification(VirtQueue *vq, int enable) +{ + 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); +} + +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; +} + +void virtqueue_fill(VirtQueue *vq, const VirtQueueElement *elem, + unsigned int len, unsigned int idx) +{ + unsigned int offset; + int i; + + trace_virtqueue_fill(vq, elem, len, idx); + + 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 += elem->in_sg[i].iov_len; + } + + 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); + + 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) +{ + /* Make sure buffer is written before we update index. */ + wmb(); + trace_virtqueue_flush(vq, count); + vring_used_idx_increment(vq, count); + vq->inuse -= count; +} + +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); + } + + 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(target_phys_addr_t 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(desc_pa, i) & VRING_DESC_F_NEXT)) + return max; + + /* Check they're not leading us off end of descriptors. */ + next = vring_desc_next(desc_pa, i); + /* Make sure compiler knows to grab that: we don't want it changing! */ + wmb(); + + if (next >= max) { + error_report("Desc next is %u", next); + exit(1); + } + + return next; +} + +int virtqueue_avail_bytes(VirtQueue *vq, int in_bytes, int out_bytes) +{ + unsigned int idx; + 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)) { + unsigned int max, num_bufs, indirect = 0; + target_phys_addr_t 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(desc_pa, i) & VRING_DESC_F_INDIRECT) { + if (vring_desc_len(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(desc_pa, i) / sizeof(VRingDesc); + num_bufs = i = 0; + desc_pa = vring_desc_addr(desc_pa, i); + } + + 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(desc_pa, i) & VRING_DESC_F_WRITE) { + if (in_bytes > 0 && + (in_total += vring_desc_len(desc_pa, i)) >= in_bytes) + return 1; + } else { + if (out_bytes > 0 && + (out_total += vring_desc_len(desc_pa, i)) >= out_bytes) + return 1; + } + } while ((i = virtqueue_next_desc(desc_pa, i, max)) != max); + + if (!indirect) + total_bufs = num_bufs; + else + total_bufs++; + } + + return 0; +} + +void virtqueue_map_sg(struct iovec *sg, target_phys_addr_t *addr, + size_t num_sg, int is_write) +{ + unsigned int i; + target_phys_addr_t len; + + 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: trying to map MMIO memory"); + exit(1); + } + } +} + +int virtqueue_pop(VirtQueue *vq, VirtQueueElement *elem) +{ + unsigned int i, head, max; + target_phys_addr_t desc_pa = vq->vring.desc; + + 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 (vring_desc_flags(desc_pa, i) & VRING_DESC_F_INDIRECT) { + if (vring_desc_len(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(desc_pa, i) / sizeof(VRingDesc); + desc_pa = vring_desc_addr(desc_pa, i); + i = 0; + } + + /* Collect all the descriptors */ + do { + struct iovec *sg; + + if (vring_desc_flags(desc_pa, i) & VRING_DESC_F_WRITE) { + elem->in_addr[elem->in_num] = vring_desc_addr(desc_pa, i); + sg = &elem->in_sg[elem->in_num++]; + } else { + elem->out_addr[elem->out_num] = vring_desc_addr(desc_pa, i); + sg = &elem->out_sg[elem->out_num++]; + } + + sg->iov_len = vring_desc_len(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(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) +{ + if (vdev->binding->notify) { + vdev->binding->notify(vdev->binding_opaque, vector); + } +} + +void virtio_update_irq(VirtIODevice *vdev) +{ + virtio_notify_vector(vdev, VIRTIO_NO_VECTOR); +} + +void virtio_reset(void *opaque) +{ + VirtIODevice *vdev = opaque; + int i; + + virtio_set_status(vdev, 0); + + if (vdev->reset) + vdev->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_PCI_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; + vdev->vq[i].pa = 0; + vdev->vq[i].vector = VIRTIO_NO_VECTOR; + } +} + +uint32_t virtio_config_readb(VirtIODevice *vdev, uint32_t addr) +{ + uint8_t val; + + vdev->get_config(vdev, vdev->config); + + if (addr > (vdev->config_len - sizeof(val))) + return (uint32_t)-1; + + memcpy(&val, vdev->config + addr, sizeof(val)); + return val; +} + +uint32_t virtio_config_readw(VirtIODevice *vdev, uint32_t addr) +{ + uint16_t val; + + vdev->get_config(vdev, vdev->config); + + if (addr > (vdev->config_len - sizeof(val))) + return (uint32_t)-1; + + memcpy(&val, vdev->config + addr, sizeof(val)); + return val; +} + +uint32_t virtio_config_readl(VirtIODevice *vdev, uint32_t addr) +{ + uint32_t val; + + vdev->get_config(vdev, vdev->config); + + if (addr > (vdev->config_len - sizeof(val))) + return (uint32_t)-1; + + memcpy(&val, vdev->config + addr, sizeof(val)); + return val; +} + +void virtio_config_writeb(VirtIODevice *vdev, uint32_t addr, uint32_t data) +{ + uint8_t val = data; + + if (addr > (vdev->config_len - sizeof(val))) + return; + + memcpy(vdev->config + addr, &val, sizeof(val)); + + if (vdev->set_config) + vdev->set_config(vdev, vdev->config); +} + +void virtio_config_writew(VirtIODevice *vdev, uint32_t addr, uint32_t data) +{ + uint16_t val = data; + + if (addr > (vdev->config_len - sizeof(val))) + return; + + memcpy(vdev->config + addr, &val, sizeof(val)); + + if (vdev->set_config) + vdev->set_config(vdev, vdev->config); +} + +void virtio_config_writel(VirtIODevice *vdev, uint32_t addr, uint32_t data) +{ + uint32_t val = data; + + if (addr > (vdev->config_len - sizeof(val))) + return; + + memcpy(vdev->config + addr, &val, sizeof(val)); + + if (vdev->set_config) + vdev->set_config(vdev, vdev->config); +} + +void virtio_queue_set_addr(VirtIODevice *vdev, int n, target_phys_addr_t addr) +{ + vdev->vq[n].pa = addr; + virtqueue_init(&vdev->vq[n]); +} + +target_phys_addr_t virtio_queue_get_addr(VirtIODevice *vdev, int n) +{ + return vdev->vq[n].pa; +} + +int virtio_queue_get_num(VirtIODevice *vdev, int n) +{ + return vdev->vq[n].vring.num; +} + +void virtio_queue_notify_vq(VirtQueue *vq) +{ + if (vq->vring.desc) { + 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) +{ + if (n < VIRTIO_PCI_QUEUE_MAX) { + virtio_queue_notify_vq(&vdev->vq[n]); + } +} + +uint16_t virtio_queue_vector(VirtIODevice *vdev, int n) +{ + return n < VIRTIO_PCI_QUEUE_MAX ? vdev->vq[n].vector : + VIRTIO_NO_VECTOR; +} + +void virtio_queue_set_vector(VirtIODevice *vdev, int n, uint16_t vector) +{ + if (n < VIRTIO_PCI_QUEUE_MAX) + vdev->vq[n].vector = vector; +} + +VirtQueue *virtio_add_queue(VirtIODevice *vdev, int queue_size, + void (*handle_output)(VirtIODevice *, VirtQueue *)) +{ + int i; + + for (i = 0; i < VIRTIO_PCI_QUEUE_MAX; i++) { + if (vdev->vq[i].vring.num == 0) + break; + } + + if (i == VIRTIO_PCI_QUEUE_MAX || queue_size > VIRTQUEUE_MAX_SIZE) + abort(); + + vdev->vq[i].vring.num = queue_size; + vdev->vq[i].handle_output = handle_output; + + return &vdev->vq[i]; +} + +void virtio_irq(VirtQueue *vq) +{ + trace_virtio_irq(vq); + vq->vdev->isr |= 0x01; + virtio_notify_vector(vq->vdev, vq->vector); +} + +void virtio_notify(VirtIODevice *vdev, VirtQueue *vq) +{ + /* Always notify when queue is empty (when feature acknowledge) */ + if ((vring_avail_flags(vq) & VRING_AVAIL_F_NO_INTERRUPT) && + (!(vdev->guest_features & (1 << VIRTIO_F_NOTIFY_ON_EMPTY)) || + (vq->inuse || vring_avail_idx(vq) != vq->last_avail_idx))) + 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; + virtio_notify_vector(vdev, vdev->config_vector); +} + +void virtio_save(VirtIODevice *vdev, QEMUFile *f) +{ + int i; + + if (vdev->binding->save_config) + vdev->binding->save_config(vdev->binding_opaque, f); + + qemu_put_8s(f, &vdev->status); + qemu_put_8s(f, &vdev->isr); + qemu_put_be16s(f, &vdev->queue_sel); + qemu_put_be32s(f, &vdev->guest_features); + qemu_put_be32(f, vdev->config_len); + qemu_put_buffer(f, vdev->config, vdev->config_len); + + for (i = 0; i < VIRTIO_PCI_QUEUE_MAX; i++) { + if (vdev->vq[i].vring.num == 0) + break; + } + + qemu_put_be32(f, i); + + for (i = 0; i < VIRTIO_PCI_QUEUE_MAX; i++) { + if (vdev->vq[i].vring.num == 0) + break; + + qemu_put_be32(f, vdev->vq[i].vring.num); + qemu_put_be64(f, vdev->vq[i].pa); + qemu_put_be16s(f, &vdev->vq[i].last_avail_idx); + if (vdev->binding->save_queue) + vdev->binding->save_queue(vdev->binding_opaque, i, f); + } +} + +int virtio_load(VirtIODevice *vdev, QEMUFile *f) +{ + int num, i, ret; + uint32_t features; + uint32_t supported_features = + vdev->binding->get_features(vdev->binding_opaque); + + if (vdev->binding->load_config) { + ret = vdev->binding->load_config(vdev->binding_opaque, f); + if (ret) + return ret; + } + + qemu_get_8s(f, &vdev->status); + qemu_get_8s(f, &vdev->isr); + qemu_get_be16s(f, &vdev->queue_sel); + qemu_get_be32s(f, &features); + if (features & ~supported_features) { + error_report("Features 0x%x unsupported. Allowed features: 0x%x", + features, supported_features); + return -1; + } + if (vdev->set_features) + vdev->set_features(vdev, features); + vdev->guest_features = features; + vdev->config_len = qemu_get_be32(f); + qemu_get_buffer(f, vdev->config, vdev->config_len); + + num = qemu_get_be32(f); + + for (i = 0; i < num; i++) { + vdev->vq[i].vring.num = qemu_get_be32(f); + vdev->vq[i].pa = qemu_get_be64(f); + qemu_get_be16s(f, &vdev->vq[i].last_avail_idx); + + if (vdev->vq[i].pa) { + uint16_t nheads; + virtqueue_init(&vdev->vq[i]); + nheads = vring_avail_idx(&vdev->vq[i]) - vdev->vq[i].last_avail_idx; + /* Check it isn't doing very 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\n", + i, vdev->vq[i].vring.num, + vring_avail_idx(&vdev->vq[i]), + vdev->vq[i].last_avail_idx, nheads); + return -1; + } + } else if (vdev->vq[i].last_avail_idx) { + error_report("VQ %d address 0x0 " + "inconsistent with Host index 0x%x\n", + i, vdev->vq[i].last_avail_idx); + return -1; + } + if (vdev->binding->load_queue) { + ret = vdev->binding->load_queue(vdev->binding_opaque, i, f); + if (ret) + return ret; + } + } + + virtio_notify_vector(vdev, VIRTIO_NO_VECTOR); + return 0; +} + +void virtio_cleanup(VirtIODevice *vdev) +{ + qemu_del_vm_change_state_handler(vdev->vmstate); + if (vdev->config) + qemu_free(vdev->config); + qemu_free(vdev->vq); +} + +static void virtio_vmstate_change(void *opaque, int running, int reason) +{ + VirtIODevice *vdev = opaque; + 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 (vdev->binding->vmstate_change) { + vdev->binding->vmstate_change(vdev->binding_opaque, backend_run); + } + + if (!backend_run) { + virtio_set_status(vdev, vdev->status); + } +} + +VirtIODevice *virtio_common_init(const char *name, uint16_t device_id, + size_t config_size, size_t struct_size) +{ + VirtIODevice *vdev; + int i; + + vdev = qemu_mallocz(struct_size); + + vdev->device_id = device_id; + vdev->status = 0; + vdev->isr = 0; + vdev->queue_sel = 0; + vdev->config_vector = VIRTIO_NO_VECTOR; + vdev->vq = qemu_mallocz(sizeof(VirtQueue) * VIRTIO_PCI_QUEUE_MAX); + for(i = 0; i < VIRTIO_PCI_QUEUE_MAX; i++) { + vdev->vq[i].vector = VIRTIO_NO_VECTOR; + vdev->vq[i].vdev = vdev; + } + + vdev->name = name; + vdev->config_len = config_size; + if (vdev->config_len) + vdev->config = qemu_mallocz(config_size); + else + vdev->config = NULL; + + vdev->vmstate = qemu_add_vm_change_state_handler(virtio_vmstate_change, vdev); + + return vdev; +} + +void virtio_bind_device(VirtIODevice *vdev, const VirtIOBindings *binding, + void *opaque) +{ + vdev->binding = binding; + vdev->binding_opaque = opaque; +} + +target_phys_addr_t virtio_queue_get_desc_addr(VirtIODevice *vdev, int n) +{ + return vdev->vq[n].vring.desc; +} + +target_phys_addr_t virtio_queue_get_avail_addr(VirtIODevice *vdev, int n) +{ + return vdev->vq[n].vring.avail; +} + +target_phys_addr_t virtio_queue_get_used_addr(VirtIODevice *vdev, int n) +{ + return vdev->vq[n].vring.used; +} + +target_phys_addr_t virtio_queue_get_ring_addr(VirtIODevice *vdev, int n) +{ + return vdev->vq[n].vring.desc; +} + +target_phys_addr_t virtio_queue_get_desc_size(VirtIODevice *vdev, int n) +{ + return sizeof(VRingDesc) * vdev->vq[n].vring.num; +} + +target_phys_addr_t virtio_queue_get_avail_size(VirtIODevice *vdev, int n) +{ + return offsetof(VRingAvail, ring) + + sizeof(uint64_t) * vdev->vq[n].vring.num; +} + +target_phys_addr_t virtio_queue_get_used_size(VirtIODevice *vdev, int n) +{ + return offsetof(VRingUsed, ring) + + sizeof(VRingUsedElem) * vdev->vq[n].vring.num; +} + +target_phys_addr_t 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; +} + +VirtQueue *virtio_get_queue(VirtIODevice *vdev, int n) +{ + return vdev->vq + n; +} + +EventNotifier *virtio_queue_get_guest_notifier(VirtQueue *vq) +{ + return &vq->guest_notifier; +} +EventNotifier *virtio_queue_get_host_notifier(VirtQueue *vq) +{ + return &vq->host_notifier; +} diff --git a/hw/virtio.h b/hw/virtio.h new file mode 100644 index 0000000..d1fb8a0 --- /dev/null +++ b/hw/virtio.h @@ -0,0 +1,229 @@ +/* + * Virtio Support + * + * Copyright IBM, Corp. 2007 + * + * Authors: + * Anthony Liguori + * + * 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_H +#define _QEMU_VIRTIO_H + +#include "hw.h" +#include "net.h" +#include "qdev.h" +#include "sysemu.h" +#include "block_int.h" +#include "event_notifier.h" +#ifdef CONFIG_LINUX +#include "9p.h" +#endif + +/* from Linux's linux/virtio_config.h */ + +/* Status byte for guest to report progress, and synchronize features. */ +/* We have seen device and processed generic fields (VIRTIO_CONFIG_F_VIRTIO) */ +#define VIRTIO_CONFIG_S_ACKNOWLEDGE 1 +/* We have found a driver for the device. */ +#define VIRTIO_CONFIG_S_DRIVER 2 +/* Driver has used its parts of the config, and is happy */ +#define VIRTIO_CONFIG_S_DRIVER_OK 4 +/* We've given up on this device. */ +#define VIRTIO_CONFIG_S_FAILED 0x80 + +/* Some virtio feature bits (currently bits 28 through 31) are reserved for the + * transport being used (eg. virtio_ring), the rest are per-device feature bits. */ +#define VIRTIO_TRANSPORT_F_START 28 +#define VIRTIO_TRANSPORT_F_END 32 + +/* We notify when the ring is completely used, even if the guest is suppressing + * callbacks */ +#define VIRTIO_F_NOTIFY_ON_EMPTY 24 +/* We support indirect buffer descriptors */ +#define VIRTIO_RING_F_INDIRECT_DESC 28 +/* A guest should never accept this. It implies negotiation is broken. */ +#define VIRTIO_F_BAD_FEATURE 30 + +/* from Linux's linux/virtio_ring.h */ + +/* This marks a buffer as continuing via the next field. */ +#define VRING_DESC_F_NEXT 1 +/* This marks a buffer as write-only (otherwise read-only). */ +#define VRING_DESC_F_WRITE 2 +/* This means the buffer contains a list of buffer descriptors. */ +#define VRING_DESC_F_INDIRECT 4 + +/* This means don't notify other side when buffer added. */ +#define VRING_USED_F_NO_NOTIFY 1 +/* This means don't interrupt guest when buffer consumed. */ +#define VRING_AVAIL_F_NO_INTERRUPT 1 + +struct VirtQueue; + +static inline target_phys_addr_t vring_align(target_phys_addr_t addr, + unsigned long align) +{ + return (addr + align - 1) & ~(align - 1); +} + +typedef struct VirtQueue VirtQueue; + +#define VIRTQUEUE_MAX_SIZE 1024 + +typedef struct VirtQueueElement +{ + unsigned int index; + unsigned int out_num; + unsigned int in_num; + target_phys_addr_t in_addr[VIRTQUEUE_MAX_SIZE]; + target_phys_addr_t out_addr[VIRTQUEUE_MAX_SIZE]; + struct iovec in_sg[VIRTQUEUE_MAX_SIZE]; + struct iovec out_sg[VIRTQUEUE_MAX_SIZE]; +} VirtQueueElement; + +typedef struct { + void (*notify)(void * opaque, uint16_t vector); + void (*save_config)(void * opaque, QEMUFile *f); + void (*save_queue)(void * opaque, int n, QEMUFile *f); + int (*load_config)(void * opaque, QEMUFile *f); + int (*load_queue)(void * opaque, int n, QEMUFile *f); + int (*load_done)(void * opaque, QEMUFile *f); + unsigned (*get_features)(void * opaque); + bool (*query_guest_notifiers)(void * opaque); + int (*set_guest_notifiers)(void * opaque, bool assigned); + int (*set_host_notifier)(void * opaque, int n, bool assigned); + void (*vmstate_change)(void * opaque, bool running); +} VirtIOBindings; + +#define VIRTIO_PCI_QUEUE_MAX 64 + +#define VIRTIO_NO_VECTOR 0xffff + +struct VirtIODevice +{ + const char *name; + uint8_t status; + uint8_t isr; + uint16_t queue_sel; + uint32_t guest_features; + size_t config_len; + void *config; + uint16_t config_vector; + int nvectors; + uint32_t (*get_features)(VirtIODevice *vdev, uint32_t requested_features); + uint32_t (*bad_features)(VirtIODevice *vdev); + void (*set_features)(VirtIODevice *vdev, uint32_t val); + void (*get_config)(VirtIODevice *vdev, uint8_t *config); + void (*set_config)(VirtIODevice *vdev, const uint8_t *config); + void (*reset)(VirtIODevice *vdev); + void (*set_status)(VirtIODevice *vdev, uint8_t val); + VirtQueue *vq; + const VirtIOBindings *binding; + void *binding_opaque; + uint16_t device_id; + bool vm_running; + VMChangeStateEntry *vmstate; +}; + +static inline void virtio_set_status(VirtIODevice *vdev, uint8_t val) +{ + if (vdev->set_status) { + vdev->set_status(vdev, val); + } + vdev->status = val; +} + +VirtQueue *virtio_add_queue(VirtIODevice *vdev, int queue_size, + void (*handle_output)(VirtIODevice *, + VirtQueue *)); + +void virtqueue_push(VirtQueue *vq, const VirtQueueElement *elem, + unsigned int len); +void virtqueue_flush(VirtQueue *vq, unsigned int count); +void virtqueue_fill(VirtQueue *vq, const VirtQueueElement *elem, + unsigned int len, unsigned int idx); + +void virtqueue_map_sg(struct iovec *sg, target_phys_addr_t *addr, + size_t num_sg, int is_write); +int virtqueue_pop(VirtQueue *vq, VirtQueueElement *elem); +int virtqueue_avail_bytes(VirtQueue *vq, int in_bytes, int out_bytes); + +void virtio_notify(VirtIODevice *vdev, VirtQueue *vq); + +void virtio_save(VirtIODevice *vdev, QEMUFile *f); + +int virtio_load(VirtIODevice *vdev, QEMUFile *f); + +void virtio_cleanup(VirtIODevice *vdev); + +void virtio_notify_config(VirtIODevice *vdev); + +void virtio_queue_set_notification(VirtQueue *vq, int enable); + +int virtio_queue_ready(VirtQueue *vq); + +int virtio_queue_empty(VirtQueue *vq); + +/* Host binding interface. */ + +VirtIODevice *virtio_common_init(const char *name, uint16_t device_id, + size_t config_size, size_t struct_size); +uint32_t virtio_config_readb(VirtIODevice *vdev, uint32_t addr); +uint32_t virtio_config_readw(VirtIODevice *vdev, uint32_t addr); +uint32_t virtio_config_readl(VirtIODevice *vdev, uint32_t addr); +void virtio_config_writeb(VirtIODevice *vdev, uint32_t addr, uint32_t data); +void virtio_config_writew(VirtIODevice *vdev, uint32_t addr, uint32_t data); +void virtio_config_writel(VirtIODevice *vdev, uint32_t addr, uint32_t data); +void virtio_queue_set_addr(VirtIODevice *vdev, int n, target_phys_addr_t addr); +target_phys_addr_t virtio_queue_get_addr(VirtIODevice *vdev, int n); +int virtio_queue_get_num(VirtIODevice *vdev, int n); +void virtio_queue_notify(VirtIODevice *vdev, int n); +uint16_t virtio_queue_vector(VirtIODevice *vdev, int n); +void virtio_queue_set_vector(VirtIODevice *vdev, int n, uint16_t vector); +void virtio_reset(void *opaque); +void virtio_update_irq(VirtIODevice *vdev); + +void virtio_bind_device(VirtIODevice *vdev, const VirtIOBindings *binding, + void *opaque); + +/* Base devices. */ +VirtIODevice *virtio_blk_init(DeviceState *dev, BlockConf *conf); +struct virtio_net_conf; +VirtIODevice *virtio_net_init(DeviceState *dev, NICConf *conf, + struct virtio_net_conf *net); +VirtIODevice *virtio_serial_init(DeviceState *dev, uint32_t max_nr_ports); +VirtIODevice *virtio_balloon_init(DeviceState *dev); +#ifdef CONFIG_LINUX +VirtIODevice *virtio_9p_init(DeviceState *dev, V9fsConf *conf); +#endif + + +void virtio_net_exit(VirtIODevice *vdev); +void virtio_blk_exit(VirtIODevice *vdev); +void virtio_serial_exit(VirtIODevice *vdev); + +#define DEFINE_VIRTIO_COMMON_FEATURES(_state, _field) \ + DEFINE_PROP_BIT("indirect_desc", _state, _field, \ + VIRTIO_RING_F_INDIRECT_DESC, true) + +target_phys_addr_t virtio_queue_get_desc_addr(VirtIODevice *vdev, int n); +target_phys_addr_t virtio_queue_get_avail_addr(VirtIODevice *vdev, int n); +target_phys_addr_t virtio_queue_get_used_addr(VirtIODevice *vdev, int n); +target_phys_addr_t virtio_queue_get_ring_addr(VirtIODevice *vdev, int n); +target_phys_addr_t virtio_queue_get_desc_size(VirtIODevice *vdev, int n); +target_phys_addr_t virtio_queue_get_avail_size(VirtIODevice *vdev, int n); +target_phys_addr_t virtio_queue_get_used_size(VirtIODevice *vdev, int n); +target_phys_addr_t virtio_queue_get_ring_size(VirtIODevice *vdev, int n); +uint16_t virtio_queue_get_last_avail_idx(VirtIODevice *vdev, int n); +void virtio_queue_set_last_avail_idx(VirtIODevice *vdev, int n, uint16_t idx); +VirtQueue *virtio_get_queue(VirtIODevice *vdev, int n); +EventNotifier *virtio_queue_get_guest_notifier(VirtQueue *vq); +EventNotifier *virtio_queue_get_host_notifier(VirtQueue *vq); +void virtio_queue_notify_vq(VirtQueue *vq); +void virtio_irq(VirtQueue *vq); +#endif diff --git a/hw/vmmouse.c b/hw/vmmouse.c new file mode 100644 index 0000000..2097119 --- /dev/null +++ b/hw/vmmouse.c @@ -0,0 +1,291 @@ +/* + * QEMU VMMouse emulation + * + * Copyright (C) 2007 Anthony Liguori + * + * 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.h" +#include "console.h" +#include "ps2.h" +#include "pc.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 + +typedef struct _VMMouseState +{ + 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 void vmmouse_get_data(uint32_t *data) +{ + CPUState *env = cpu_single_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]; + + DPRINTF("get_data = {%x, %x, %x, %x, %x, %x}\n", + data[0], data[1], data[2], data[3], data[4], data[5]); +} + +static void vmmouse_set_data(const uint32_t *data) +{ + CPUState *env = cpu_single_env; + + DPRINTF("set_data = {%x, %x, %x, %x, %x, %x}\n", + data[0], data[1], data[2], data[3], data[4], data[5]); + + 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 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, + .minimum_version_id_old = 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() + } +}; + +void *vmmouse_init(void *m) +{ + VMMouseState *s = NULL; + + DPRINTF("vmmouse_init\n"); + + s = qemu_mallocz(sizeof(VMMouseState)); + + s->status = 0xffff; + s->ps2_mouse = m; + s->queue_size = VMMOUSE_QUEUE_SIZE; + + vmport_register(VMMOUSE_STATUS, vmmouse_ioport_read, s); + vmport_register(VMMOUSE_COMMAND, vmmouse_ioport_read, s); + vmport_register(VMMOUSE_DATA, vmmouse_ioport_read, s); + vmstate_register(NULL, 0, &vmstate_vmmouse, s); + + return s; +} diff --git a/hw/vmport.c b/hw/vmport.c new file mode 100644 index 0000000..6c9d7c9 --- /dev/null +++ b/hw/vmport.c @@ -0,0 +1,111 @@ +/* + * 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.h" +#include "isa.h" +#include "pc.h" +#include "sysemu.h" +#include "kvm.h" + +//#define VMPORT_DEBUG + +#define VMPORT_CMD_GETVERSION 0x0a +#define VMPORT_CMD_GETRAMSIZE 0x14 + +#define VMPORT_ENTRIES 0x2c +#define VMPORT_MAGIC 0x564D5868 + +typedef struct _VMPortState +{ + IOPortReadFunc *func[VMPORT_ENTRIES]; + void *opaque[VMPORT_ENTRIES]; +} VMPortState; + +static VMPortState port_state; + +void vmport_register(unsigned char command, IOPortReadFunc *func, void *opaque) +{ + if (command >= VMPORT_ENTRIES) + return; + + port_state.func[command] = func; + port_state.opaque[command] = opaque; +} + +static uint32_t vmport_ioport_read(void *opaque, uint32_t addr) +{ + VMPortState *s = opaque; + CPUState *env = cpu_single_env; + unsigned char command; + uint32_t eax; + + cpu_synchronize_state(env); + + 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, uint32_t addr, uint32_t val) +{ + CPUState *env = cpu_single_env; + + env->regs[R_EAX] = vmport_ioport_read(opaque, addr); +} + +static uint32_t vmport_cmd_get_version(void *opaque, uint32_t addr) +{ + CPUState *env = cpu_single_env; + env->regs[R_EBX] = VMPORT_MAGIC; + return 6; +} + +static uint32_t vmport_cmd_ram_size(void *opaque, uint32_t addr) +{ + CPUState *env = cpu_single_env; + env->regs[R_EBX] = 0x1177; + return ram_size; +} + +void vmport_init(void) +{ + register_ioport_read(0x5658, 1, 4, vmport_ioport_read, &port_state); + register_ioport_write(0x5658, 1, 4, vmport_ioport_write, &port_state); + + /* 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); +} diff --git a/hw/vmware_vga.c b/hw/vmware_vga.c new file mode 100644 index 0000000..6c59053 --- /dev/null +++ b/hw/vmware_vga.c @@ -0,0 +1,1330 @@ +/* + * QEMU VMware-SVGA "chipset". + * + * Copyright (c) 2007 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 "hw.h" +#include "loader.h" +#include "console.h" +#include "pci.h" +#include "vmware_vga.h" + +#define VERBOSE +#undef DIRECT_VRAM +#define HW_RECT_ACCEL +#define HW_FILL_ACCEL +#define HW_MOUSE_ACCEL + +# include "vga_int.h" + +struct vmsvga_state_s { + VGACommonState vga; + + int width; + int height; + int invalidated; + int depth; + int bypp; + int enable; + int config; + struct { + int id; + int x; + int y; + int on; + } cursor; + + target_phys_addr_t vram_base; + + int index; + int scratch_size; + uint32_t *scratch; + int new_width; + int new_height; + uint32_t guest; + uint32_t svgaid; + uint32_t wred; + uint32_t wgreen; + uint32_t wblue; + int syncing; + int fb_size; + + ram_addr_t fifo_offset; + uint8_t *fifo_ptr; + unsigned int fifo_size; + target_phys_addr_t fifo_base; + + union { + uint32_t *fifo; + struct __attribute__((__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; +}; + +struct pci_vmsvga_state_s { + PCIDevice card; + struct vmsvga_state_s chip; +}; + +#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 2360 +#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 void vmsvga_update_rect(struct vmsvga_state_s *s, + int x, int y, int w, int h) +{ +#ifndef DIRECT_VRAM + int line; + int bypl; + int width; + int start; + uint8_t *src; + uint8_t *dst; + + if (x + w > s->width) { + fprintf(stderr, "%s: update width too large x: %d, w: %d\n", + __FUNCTION__, x, w); + x = MIN(x, s->width); + w = s->width - x; + } + + if (y + h > s->height) { + fprintf(stderr, "%s: update height too large y: %d, h: %d\n", + __FUNCTION__, y, h); + y = MIN(y, s->height); + h = s->height - y; + } + + line = h; + bypl = s->bypp * s->width; + width = s->bypp * w; + start = s->bypp * x + bypl * y; + src = s->vga.vram_ptr + start; + dst = ds_get_data(s->vga.ds) + start; + + for (; line > 0; line --, src += bypl, dst += bypl) + memcpy(dst, src, width); +#endif + + dpy_update(s->vga.ds, x, y, w, h); +} + +static inline void vmsvga_update_screen(struct vmsvga_state_s *s) +{ +#ifndef DIRECT_VRAM + memcpy(ds_get_data(s->vga.ds), s->vga.vram_ptr, s->bypp * s->width * s->height); +#endif + + dpy_update(s->vga.ds, 0, 0, s->width, s->height); +} + +#ifdef DIRECT_VRAM +# define vmsvga_update_rect_delayed vmsvga_update_rect +#else +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; +} +#endif + +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 void vmsvga_copy_rect(struct vmsvga_state_s *s, + int x0, int y0, int x1, int y1, int w, int h) +{ +# ifdef DIRECT_VRAM + uint8_t *vram = ds_get_data(s->ds); +# else + uint8_t *vram = s->vga.vram_ptr; +# endif + int bypl = s->bypp * s->width; + int width = s->bypp * w; + int line = h; + uint8_t *ptr[2]; + +# ifdef DIRECT_VRAM + if (s->ds->dpy_copy) + qemu_console_copy(s->ds, x0, y0, x1, y1, w, h); + else +# endif + { + if (y1 > y0) { + ptr[0] = vram + s->bypp * x0 + bypl * (y0 + h - 1); + ptr[1] = vram + s->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 + s->bypp * x0 + bypl * y0; + ptr[1] = vram + s->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); +} +#endif + +#ifdef HW_FILL_ACCEL +static inline void vmsvga_fill_rect(struct vmsvga_state_s *s, + uint32_t c, int x, int y, int w, int h) +{ +# ifdef DIRECT_VRAM + uint8_t *vram = ds_get_data(s->ds); +# else + uint8_t *vram = s->vga.vram_ptr; +# endif + int bypp = s->bypp; + int bypl = bypp * s->width; + int width = bypp * w; + int line = h; + int column; + uint8_t *fst = vram + bypp * x + bypl * y; + uint8_t *dst; + uint8_t *src; + uint8_t col[4]; + +# ifdef DIRECT_VRAM + if (s->ds->dpy_fill) + s->ds->dpy_fill(s->ds, x, y, w, h, c); + else +# endif + { + col[0] = c; + col[1] = c >> 8; + col[2] = c >> 16; + col[3] = c >> 24; + + if (line --) { + dst = fst; + src = col; + for (column = width; column > 0; column --) { + *(dst ++) = *(src ++); + if (src - col == bypp) + src = col; + } + dst = fst; + for (; line > 0; line --) { + dst += bypl; + memcpy(dst, fst, width); + } + } + } + + vmsvga_update_rect_delayed(s, x, y, w, h); +} +#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", + __FUNCTION__, c->bpp); + cursor_put(qc); + qc = cursor_builtin_left_ptr(); + } + + if (s->vga.ds->cursor_define) + s->vga.ds->cursor_define(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 + vmsvga_fill_rect(s, colour, x, y, width, height); + break; +#else + args = 0; + goto badcmd; +#endif + + 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 + vmsvga_copy_rect(s, x, y, dx, dy, width, height); + break; +#else + args = 0; + goto badcmd; +#endif + + 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", + __FUNCTION__, 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; + switch (s->index) { + case SVGA_REG_ID: + return s->svgaid; + + case SVGA_REG_ENABLE: + return s->enable; + + case SVGA_REG_WIDTH: + return s->width; + + case SVGA_REG_HEIGHT: + return s->height; + + case SVGA_REG_MAX_WIDTH: + return SVGA_MAX_WIDTH; + + case SVGA_REG_MAX_HEIGHT: + return SVGA_MAX_HEIGHT; + + case SVGA_REG_DEPTH: + return s->depth; + + case SVGA_REG_BITS_PER_PIXEL: + return (s->depth + 7) & ~7; + + case SVGA_REG_PSEUDOCOLOR: + return 0x0; + + case SVGA_REG_RED_MASK: + return s->wred; + case SVGA_REG_GREEN_MASK: + return s->wgreen; + case SVGA_REG_BLUE_MASK: + return s->wblue; + + case SVGA_REG_BYTES_PER_LINE: + return ((s->depth + 7) >> 3) * s->new_width; + + case SVGA_REG_FB_START: + return s->vram_base; + + case SVGA_REG_FB_OFFSET: + return 0x0; + + case SVGA_REG_VRAM_SIZE: + return s->vga.vram_size; + + case SVGA_REG_FB_SIZE: + return s->fb_size; + + 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 (s->vga.ds->mouse_set) + caps |= SVGA_CAP_CURSOR | SVGA_CAP_CURSOR_BYPASS_2 | + SVGA_CAP_CURSOR_BYPASS; +#endif + return caps; + + case SVGA_REG_MEM_START: + return s->fifo_base; + + case SVGA_REG_MEM_SIZE: + return s->fifo_size; + + case SVGA_REG_CONFIG_DONE: + return s->config; + + case SVGA_REG_SYNC: + case SVGA_REG_BUSY: + return s->syncing; + + case SVGA_REG_GUEST_ID: + return s->guest; + + case SVGA_REG_CURSOR_ID: + return s->cursor.id; + + case SVGA_REG_CURSOR_X: + return s->cursor.x; + + case SVGA_REG_CURSOR_Y: + return s->cursor.x; + + case SVGA_REG_CURSOR_ON: + return s->cursor.on; + + case SVGA_REG_HOST_BITS_PER_PIXEL: + return (s->depth + 7) & ~7; + + case SVGA_REG_SCRATCH_SIZE: + return s->scratch_size; + + case SVGA_REG_MEM_REGS: + case SVGA_REG_NUM_DISPLAYS: + case SVGA_REG_PITCHLOCK: + case SVGA_PALETTE_BASE ... SVGA_PALETTE_END: + return 0; + + default: + if (s->index >= SVGA_SCRATCH_BASE && + s->index < SVGA_SCRATCH_BASE + s->scratch_size) + return s->scratch[s->index - SVGA_SCRATCH_BASE]; + printf("%s: Bad register %02x\n", __FUNCTION__, s->index); + } + + return 0; +} + +static void vmsvga_value_write(void *opaque, uint32_t address, uint32_t value) +{ + struct vmsvga_state_s *s = opaque; + 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->config &= !!value; + s->width = -1; + s->height = -1; + s->invalidated = 1; + s->vga.invalidate(&s->vga); + if (s->enable) { + s->fb_size = ((s->depth + 7) >> 3) * s->new_width * s->new_height; + vga_dirty_log_stop(&s->vga); + } else { + vga_dirty_log_start(&s->vga); + } + break; + + case SVGA_REG_WIDTH: + s->new_width = value; + s->invalidated = 1; + break; + + case SVGA_REG_HEIGHT: + s->new_height = value; + s->invalidated = 1; + break; + + case SVGA_REG_DEPTH: + case SVGA_REG_BITS_PER_PIXEL: + if (value != s->depth) { + printf("%s: Bad colour depth: %i bits\n", __FUNCTION__, value); + s->config = 0; + } + 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; + } + 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", __FUNCTION__, + 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 (s->vga.ds->mouse_set && value <= SVGA_CURSOR_ON_SHOW) + s->vga.ds->mouse_set(s->cursor.x, s->cursor.y, s->cursor.on); +#endif + break; + + 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", __FUNCTION__, s->index); + } +} + +static uint32_t vmsvga_bios_read(void *opaque, uint32_t address) +{ + printf("%s: what are we supposed to return?\n", __FUNCTION__); + 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", + __FUNCTION__, data); +} + +static inline void vmsvga_size(struct vmsvga_state_s *s) +{ + if (s->new_width != s->width || s->new_height != s->height) { + s->width = s->new_width; + s->height = s->new_height; + qemu_console_resize(s->vga.ds, s->width, s->height); + s->invalidated = 1; + } +} + +static void vmsvga_update_display(void *opaque) +{ + struct vmsvga_state_s *s = opaque; + if (!s->enable) { + s->vga.update(&s->vga); + return; + } + + vmsvga_size(s); + + 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 (s->invalidated) { + s->invalidated = 0; + vmsvga_update_screen(s); + } +} + +static void vmsvga_reset(struct vmsvga_state_s *s) +{ + s->index = 0; + s->enable = 0; + s->config = 0; + s->width = -1; + s->height = -1; + s->svgaid = SVGA_ID; + s->depth = ds_get_bits_per_pixel(s->vga.ds); + s->bypp = ds_get_bytes_per_pixel(s->vga.ds); + s->cursor.on = 0; + s->redraw_fifo_first = 0; + s->redraw_fifo_last = 0; + switch (s->depth) { + case 8: + s->wred = 0x00000007; + s->wgreen = 0x00000038; + s->wblue = 0x000000c0; + break; + case 15: + s->wred = 0x0000001f; + s->wgreen = 0x000003e0; + s->wblue = 0x00007c00; + break; + case 16: + s->wred = 0x0000001f; + s->wgreen = 0x000007e0; + s->wblue = 0x0000f800; + break; + case 24: + s->wred = 0x00ff0000; + s->wgreen = 0x0000ff00; + s->wblue = 0x000000ff; + break; + case 32: + s->wred = 0x00ff0000; + s->wgreen = 0x0000ff00; + s->wblue = 0x000000ff; + break; + } + 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.invalidate(&s->vga); + return; + } + + s->invalidated = 1; +} + +/* save the vga display in a PPM image even if no display is + available */ +static void vmsvga_screen_dump(void *opaque, const char *filename) +{ + struct vmsvga_state_s *s = opaque; + if (!s->enable) { + s->vga.screen_dump(&s->vga, filename); + return; + } + + if (s->depth == 32) { + DisplaySurface *ds = qemu_create_displaysurface_from(s->width, + s->height, 32, ds_get_linesize(s->vga.ds), s->vga.vram_ptr); + ppm_save(filename, ds); + qemu_free(ds); + } +} + +static void vmsvga_text_update(void *opaque, console_ch_t *chardata) +{ + struct vmsvga_state_s *s = opaque; + + if (s->vga.text_update) + s->vga.text_update(&s->vga, chardata); +} + +#ifdef DIRECT_VRAM +static uint32_t vmsvga_vram_readb(void *opaque, target_phys_addr_t addr) +{ + struct vmsvga_state_s *s = opaque; + if (addr < s->fb_size) + return *(uint8_t *) (ds_get_data(s->ds) + addr); + else + return *(uint8_t *) (s->vram_ptr + addr); +} + +static uint32_t vmsvga_vram_readw(void *opaque, target_phys_addr_t addr) +{ + struct vmsvga_state_s *s = opaque; + if (addr < s->fb_size) + return *(uint16_t *) (ds_get_data(s->ds) + addr); + else + return *(uint16_t *) (s->vram_ptr + addr); +} + +static uint32_t vmsvga_vram_readl(void *opaque, target_phys_addr_t addr) +{ + struct vmsvga_state_s *s = opaque; + if (addr < s->fb_size) + return *(uint32_t *) (ds_get_data(s->ds) + addr); + else + return *(uint32_t *) (s->vram_ptr + addr); +} + +static void vmsvga_vram_writeb(void *opaque, target_phys_addr_t addr, + uint32_t value) +{ + struct vmsvga_state_s *s = opaque; + if (addr < s->fb_size) + *(uint8_t *) (ds_get_data(s->ds) + addr) = value; + else + *(uint8_t *) (s->vram_ptr + addr) = value; +} + +static void vmsvga_vram_writew(void *opaque, target_phys_addr_t addr, + uint32_t value) +{ + struct vmsvga_state_s *s = opaque; + if (addr < s->fb_size) + *(uint16_t *) (ds_get_data(s->ds) + addr) = value; + else + *(uint16_t *) (s->vram_ptr + addr) = value; +} + +static void vmsvga_vram_writel(void *opaque, target_phys_addr_t addr, + uint32_t value) +{ + struct vmsvga_state_s *s = opaque; + if (addr < s->fb_size) + *(uint32_t *) (ds_get_data(s->ds) + addr) = value; + else + *(uint32_t *) (s->vram_ptr + addr) = value; +} + +static CPUReadMemoryFunc * const vmsvga_vram_read[] = { + vmsvga_vram_readb, + vmsvga_vram_readw, + vmsvga_vram_readl, +}; + +static CPUWriteMemoryFunc * const vmsvga_vram_write[] = { + vmsvga_vram_writeb, + vmsvga_vram_writew, + vmsvga_vram_writel, +}; +#endif + +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, + .minimum_version_id_old = 0, + .post_load = vmsvga_post_load, + .fields = (VMStateField []) { + VMSTATE_INT32_EQUAL(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_INT32(fb_size, struct vmsvga_state_s), + VMSTATE_END_OF_LIST() + } +}; + +static const VMStateDescription vmstate_vmware_vga = { + .name = "vmware_vga", + .version_id = 0, + .minimum_version_id = 0, + .minimum_version_id_old = 0, + .fields = (VMStateField []) { + VMSTATE_PCI_DEVICE(card, 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 void vmsvga_init(struct vmsvga_state_s *s, int vga_ram_size) +{ + s->scratch_size = SVGA_SCRATCH_SIZE; + s->scratch = qemu_malloc(s->scratch_size * 4); + + s->vga.ds = graphic_console_init(vmsvga_update_display, + vmsvga_invalidate_display, + vmsvga_screen_dump, + vmsvga_text_update, s); + + + s->fifo_size = SVGA_FIFO_SIZE; + s->fifo_offset = qemu_ram_alloc(NULL, "vmsvga.fifo", s->fifo_size); + s->fifo_ptr = qemu_get_ram_ptr(s->fifo_offset); + + vga_common_init(&s->vga, vga_ram_size); + vga_init(&s->vga); + vmstate_register(NULL, 0, &vmstate_vga_common, &s->vga); + + vmsvga_reset(s); +} + +static void pci_vmsvga_map_ioport(PCIDevice *pci_dev, int region_num, + pcibus_t addr, pcibus_t size, int type) +{ + struct pci_vmsvga_state_s *d = (struct pci_vmsvga_state_s *) pci_dev; + struct vmsvga_state_s *s = &d->chip; + + register_ioport_read(addr + SVGA_IO_MUL * SVGA_INDEX_PORT, + 1, 4, vmsvga_index_read, s); + register_ioport_write(addr + SVGA_IO_MUL * SVGA_INDEX_PORT, + 1, 4, vmsvga_index_write, s); + register_ioport_read(addr + SVGA_IO_MUL * SVGA_VALUE_PORT, + 1, 4, vmsvga_value_read, s); + register_ioport_write(addr + SVGA_IO_MUL * SVGA_VALUE_PORT, + 1, 4, vmsvga_value_write, s); + register_ioport_read(addr + SVGA_IO_MUL * SVGA_BIOS_PORT, + 1, 4, vmsvga_bios_read, s); + register_ioport_write(addr + SVGA_IO_MUL * SVGA_BIOS_PORT, + 1, 4, vmsvga_bios_write, s); +} + +static void pci_vmsvga_map_mem(PCIDevice *pci_dev, int region_num, + pcibus_t addr, pcibus_t size, int type) +{ + struct pci_vmsvga_state_s *d = (struct pci_vmsvga_state_s *) pci_dev; + struct vmsvga_state_s *s = &d->chip; + ram_addr_t iomemtype; + + s->vram_base = addr; +#ifdef DIRECT_VRAM + iomemtype = cpu_register_io_memory(vmsvga_vram_read, + vmsvga_vram_write, s, DEVICE_NATIVE_ENDIAN); +#else + iomemtype = s->vga.vram_offset | IO_MEM_RAM; +#endif + cpu_register_physical_memory(s->vram_base, s->vga.vram_size, + iomemtype); + + s->vga.map_addr = addr; + s->vga.map_end = addr + s->vga.vram_size; + vga_dirty_log_restart(&s->vga); +} + +static void pci_vmsvga_map_fifo(PCIDevice *pci_dev, int region_num, + pcibus_t addr, pcibus_t size, int type) +{ + struct pci_vmsvga_state_s *d = (struct pci_vmsvga_state_s *) pci_dev; + struct vmsvga_state_s *s = &d->chip; + ram_addr_t iomemtype; + + s->fifo_base = addr; + iomemtype = s->fifo_offset | IO_MEM_RAM; + cpu_register_physical_memory(s->fifo_base, s->fifo_size, + iomemtype); +} + +static int pci_vmsvga_initfn(PCIDevice *dev) +{ + struct pci_vmsvga_state_s *s = + DO_UPCAST(struct pci_vmsvga_state_s, card, dev); + + pci_config_set_vendor_id(s->card.config, PCI_VENDOR_ID_VMWARE); + pci_config_set_device_id(s->card.config, SVGA_PCI_DEVICE_ID); + pci_config_set_class(s->card.config, PCI_CLASS_DISPLAY_VGA); + s->card.config[PCI_CACHE_LINE_SIZE] = 0x08; /* Cache line size */ + s->card.config[PCI_LATENCY_TIMER] = 0x40; /* Latency timer */ + s->card.config[PCI_SUBSYSTEM_VENDOR_ID] = PCI_VENDOR_ID_VMWARE & 0xff; + s->card.config[PCI_SUBSYSTEM_VENDOR_ID + 1] = PCI_VENDOR_ID_VMWARE >> 8; + s->card.config[PCI_SUBSYSTEM_ID] = SVGA_PCI_DEVICE_ID & 0xff; + s->card.config[PCI_SUBSYSTEM_ID + 1] = SVGA_PCI_DEVICE_ID >> 8; + s->card.config[PCI_INTERRUPT_LINE] = 0xff; /* End */ + + pci_register_bar(&s->card, 0, 0x10, + PCI_BASE_ADDRESS_SPACE_IO, pci_vmsvga_map_ioport); + pci_register_bar(&s->card, 1, VGA_RAM_SIZE, + PCI_BASE_ADDRESS_MEM_PREFETCH, pci_vmsvga_map_mem); + + pci_register_bar(&s->card, 2, SVGA_FIFO_SIZE, + PCI_BASE_ADDRESS_MEM_PREFETCH, pci_vmsvga_map_fifo); + + vmsvga_init(&s->chip, VGA_RAM_SIZE); + + if (!dev->rom_bar) { + /* compatibility with pc-0.13 and older */ + vga_init_vbe(&s->chip.vga); + } + + return 0; +} + +void pci_vmsvga_init(PCIBus *bus) +{ + pci_create_simple(bus, -1, "vmware-svga"); +} + +static PCIDeviceInfo vmsvga_info = { + .qdev.name = "vmware-svga", + .qdev.size = sizeof(struct pci_vmsvga_state_s), + .qdev.vmsd = &vmstate_vmware_vga, + .no_hotplug = 1, + .init = pci_vmsvga_initfn, + .romfile = "vgabios-vmware.bin", +}; + +static void vmsvga_register(void) +{ + pci_qdev_register(&vmsvga_info); +} +device_init(vmsvga_register); diff --git a/hw/vmware_vga.h b/hw/vmware_vga.h new file mode 100644 index 0000000..2e0813c --- /dev/null +++ b/hw/vmware_vga.h @@ -0,0 +1,9 @@ +#ifndef QEMU_VMWARE_VGA_H +#define QEMU_VMWARE_VGA_H + +#include "qemu-common.h" + +/* vmware_vga.c */ +void pci_vmsvga_init(PCIBus *bus); + +#endif diff --git a/hw/vt82c686.c b/hw/vt82c686.c new file mode 100644 index 0000000..cacc217 --- /dev/null +++ b/hw/vt82c686.c @@ -0,0 +1,594 @@ +/* + * 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. + */ + +#include "hw.h" +#include "pc.h" +#include "vt82c686.h" +#include "i2c.h" +#include "smbus.h" +#include "pci.h" +#include "isa.h" +#include "sysbus.h" +#include "mips.h" +#include "apm.h" +#include "acpi.h" +#include "pm_smbus.h" +#include "sysemu.h" +#include "qemu-timer.h" + +typedef uint32_t pci_addr_t; +#include "pci_host.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[0xff]; + uint8_t index; + uint8_t data; +} SuperIOConfig; + +typedef struct VT82C686BState { + PCIDevice dev; + SuperIOConfig superio_conf; +} VT82C686BState; + +static void superio_ioport_writeb(void *opaque, uint32_t addr, uint32_t data) +{ + int can_write; + 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 { + /* 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 = 0; + break; + default: + can_write = 1; + + if (can_write) { + switch (superio_conf->index) { + case 0xe7: + if ((data & 0xff) != 0xfe) { + DPRINTF("chage uart 1 base. unsupported yet \n"); + } + break; + case 0xe8: + if ((data & 0xff) != 0xbe) { + DPRINTF("chage uart 2 base. unsupported yet \n"); + } + break; + + default: + superio_conf->config[superio_conf->index] = data & 0xff; + } + } + } + superio_conf->config[superio_conf->index] = data & 0xff; + } +} + +static uint32_t superio_ioport_readb(void *opaque, uint32_t addr) +{ + SuperIOConfig *superio_conf = opaque; + + DPRINTF("superio_ioport_readb address 0x%x \n", addr); + return (superio_conf->config[superio_conf->index]); +} + +static void vt82c686b_reset(void * opaque) +{ + PCIDevice *d = opaque; + uint8_t *pci_conf = d->config; + VT82C686BState *vt82c = DO_UPCAST(VT82C686BState, dev, 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 = DO_UPCAST(VT82C686BState, dev, 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 */ + if (val & 0x2) { + /* floppy also uses 0x3f0 and 0x3f1. + * But we do not emulate flopy,so just set it here. */ + isa_unassign_ioport(0x3f0, 2); + register_ioport_read(0x3f0, 2, 1, superio_ioport_readb, + &vt686->superio_conf); + register_ioport_write(0x3f0, 2, 1, superio_ioport_writeb, + &vt686->superio_conf); + } else { + isa_unassign_ioport(0x3f0, 2); + } + } +} + +#define ACPI_DBG_IO_ADDR 0xb044 + +typedef struct VT686PMState { + PCIDevice dev; + uint16_t pmsts; + uint16_t pmen; + uint16_t pmcntrl; + APMState apm; + QEMUTimer *tmr_timer; + int64_t tmr_overflow_time; + PMSMBus smb; + uint32_t smb_io_base; +} VT686PMState; + +typedef struct VT686AC97State { + PCIDevice dev; +} VT686AC97State; + +typedef struct VT686MC97State { + PCIDevice dev; +} VT686MC97State; + +#define RTC_EN (1 << 10) +#define PWRBTN_EN (1 << 8) +#define GBL_EN (1 << 5) +#define TMROF_EN (1 << 0) +#define SUS_EN (1 << 13) + +#define ACPI_ENABLE 0xf1 +#define ACPI_DISABLE 0xf0 + +static uint32_t get_pmtmr(VT686PMState *s) +{ + uint32_t d; + d = muldiv64(qemu_get_clock(vm_clock), PM_TIMER_FREQUENCY, get_ticks_per_sec()); + return d & 0xffffff; +} + +static int get_pmsts(VT686PMState *s) +{ + int64_t d; + int pmsts; + pmsts = s->pmsts; + d = muldiv64(qemu_get_clock(vm_clock), PM_TIMER_FREQUENCY, get_ticks_per_sec()); + if (d >= s->tmr_overflow_time) + s->pmsts |= TMROF_EN; + return pmsts; +} + +static void pm_update_sci(VT686PMState *s) +{ + int sci_level, pmsts; + int64_t expire_time; + + pmsts = get_pmsts(s); + sci_level = (((pmsts & s->pmen) & + (RTC_EN | PWRBTN_EN | GBL_EN | TMROF_EN)) != 0); + qemu_set_irq(s->dev.irq[0], sci_level); + /* schedule a timer interruption if needed */ + if ((s->pmen & TMROF_EN) && !(pmsts & TMROF_EN)) { + expire_time = muldiv64(s->tmr_overflow_time, get_ticks_per_sec(), PM_TIMER_FREQUENCY); + qemu_mod_timer(s->tmr_timer, expire_time); + } else { + qemu_del_timer(s->tmr_timer); + } +} + +static void pm_tmr_timer(void *opaque) +{ + VT686PMState *s = opaque; + pm_update_sci(s); +} + +static void pm_ioport_writew(void *opaque, uint32_t addr, uint32_t val) +{ + VT686PMState *s = opaque; + + addr &= 0x0f; + switch (addr) { + case 0x00: + { + int64_t d; + int pmsts; + pmsts = get_pmsts(s); + if (pmsts & val & TMROF_EN) { + /* if TMRSTS is reset, then compute the new overflow time */ + d = muldiv64(qemu_get_clock(vm_clock), PM_TIMER_FREQUENCY, get_ticks_per_sec()); + s->tmr_overflow_time = (d + 0x800000LL) & ~0x7fffffLL; + } + s->pmsts &= ~val; + pm_update_sci(s); + } + break; + case 0x02: + s->pmen = val; + pm_update_sci(s); + break; + case 0x04: + { + int sus_typ; + s->pmcntrl = val & ~(SUS_EN); + if (val & SUS_EN) { + /* change suspend type */ + sus_typ = (val >> 10) & 3; + switch (sus_typ) { + case 0: /* soft power off */ + qemu_system_shutdown_request(); + break; + default: + break; + } + } + } + break; + default: + break; + } + DPRINTF("PM writew port=0x%04x val=0x%02x\n", addr, val); +} + +static uint32_t pm_ioport_readw(void *opaque, uint32_t addr) +{ + VT686PMState *s = opaque; + uint32_t val; + + addr &= 0x0f; + switch (addr) { + case 0x00: + val = get_pmsts(s); + break; + case 0x02: + val = s->pmen; + break; + case 0x04: + val = s->pmcntrl; + break; + default: + val = 0; + break; + } + DPRINTF("PM readw port=0x%04x val=0x%02x\n", addr, val); + return val; +} + +static void pm_ioport_writel(void *opaque, uint32_t addr, uint32_t val) +{ + addr &= 0x0f; + DPRINTF("PM writel port=0x%04x val=0x%08x\n", addr, val); +} + +static uint32_t pm_ioport_readl(void *opaque, uint32_t addr) +{ + VT686PMState *s = opaque; + uint32_t val; + + addr &= 0x0f; + switch (addr) { + case 0x08: + val = get_pmtmr(s); + break; + default: + val = 0; + break; + } + DPRINTF("PM readl port=0x%04x val=0x%08x\n", addr, val); + return val; +} + +static void pm_io_space_update(VT686PMState *s) +{ + uint32_t pm_io_base; + + if (s->dev.config[0x80] & 1) { + pm_io_base = pci_get_long(s->dev.config + 0x40); + pm_io_base &= 0xffc0; + + /* XXX: need to improve memory and ioport allocation */ + DPRINTF("PM: mapping to 0x%x\n", pm_io_base); + register_ioport_write(pm_io_base, 64, 2, pm_ioport_writew, s); + register_ioport_read(pm_io_base, 64, 2, pm_ioport_readw, s); + register_ioport_write(pm_io_base, 64, 4, pm_ioport_writel, s); + register_ioport_read(pm_io_base, 64, 4, pm_ioport_readl, s); + } +} + +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, + .minimum_version_id_old = 1, + .post_load = vmstate_acpi_post_load, + .fields = (VMStateField []) { + VMSTATE_PCI_DEVICE(dev, VT686PMState), + VMSTATE_UINT16(pmsts, VT686PMState), + VMSTATE_UINT16(pmen, VT686PMState), + VMSTATE_UINT16(pmcntrl, VT686PMState), + VMSTATE_STRUCT(apm, VT686PMState, 0, vmstate_apm, APMState), + VMSTATE_TIMER(tmr_timer, VT686PMState), + VMSTATE_INT64(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 int vt82c686b_ac97_initfn(PCIDevice *dev) +{ + VT686AC97State *s = DO_UPCAST(VT686AC97State, dev, dev); + uint8_t *pci_conf = s->dev.config; + + pci_config_set_vendor_id(pci_conf, PCI_VENDOR_ID_VIA); + pci_config_set_device_id(pci_conf, PCI_DEVICE_ID_VIA_AC97); + pci_config_set_class(pci_conf, PCI_CLASS_MULTIMEDIA_AUDIO); + pci_config_set_revision(pci_conf, 0x50); + + 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); + + return 0; +} + +void vt82c686b_ac97_init(PCIBus *bus, int devfn) +{ + PCIDevice *dev; + + dev = pci_create(bus, devfn, "VT82C686B_AC97"); + qdev_init_nofail(&dev->qdev); +} + +static PCIDeviceInfo via_ac97_info = { + .qdev.name = "VT82C686B_AC97", + .qdev.desc = "AC97", + .qdev.size = sizeof(VT686AC97State), + .init = vt82c686b_ac97_initfn, +}; + +static void vt82c686b_ac97_register(void) +{ + pci_qdev_register(&via_ac97_info); +} + +device_init(vt82c686b_ac97_register); + +static int vt82c686b_mc97_initfn(PCIDevice *dev) +{ + VT686MC97State *s = DO_UPCAST(VT686MC97State, dev, dev); + uint8_t *pci_conf = s->dev.config; + + pci_config_set_vendor_id(pci_conf, PCI_VENDOR_ID_VIA); + pci_config_set_device_id(pci_conf, PCI_DEVICE_ID_VIA_MC97); + pci_config_set_class(pci_conf, PCI_CLASS_COMMUNICATION_OTHER); + pci_config_set_revision(pci_conf, 0x30); + + 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); + + return 0; +} + +void vt82c686b_mc97_init(PCIBus *bus, int devfn) +{ + PCIDevice *dev; + + dev = pci_create(bus, devfn, "VT82C686B_MC97"); + qdev_init_nofail(&dev->qdev); +} + +static PCIDeviceInfo via_mc97_info = { + .qdev.name = "VT82C686B_MC97", + .qdev.desc = "MC97", + .qdev.size = sizeof(VT686MC97State), + .init = vt82c686b_mc97_initfn, +}; + +static void vt82c686b_mc97_register(void) +{ + pci_qdev_register(&via_mc97_info); +} + +device_init(vt82c686b_mc97_register); + +/* vt82c686 pm init */ +static int vt82c686b_pm_initfn(PCIDevice *dev) +{ + VT686PMState *s = DO_UPCAST(VT686PMState, dev, dev); + uint8_t *pci_conf; + + pci_conf = s->dev.config; + pci_config_set_vendor_id(pci_conf, PCI_VENDOR_ID_VIA); + pci_config_set_device_id(pci_conf, PCI_DEVICE_ID_VIA_ACPI); + pci_config_set_class(pci_conf, PCI_CLASS_BRIDGE_OTHER); + pci_config_set_revision(pci_conf, 0x40); + + 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; + register_ioport_write(s->smb_io_base, 0xf, 1, smb_ioport_writeb, &s->smb); + register_ioport_read(s->smb_io_base, 0xf, 1, smb_ioport_readb, &s->smb); + + apm_init(&s->apm, NULL, s); + + s->tmr_timer = qemu_new_timer(vm_clock, pm_tmr_timer, s); + + pm_smbus_init(&s->dev.qdev, &s->smb); + + return 0; +} + +i2c_bus *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, "VT82C686B_PM"); + qdev_prop_set_uint32(&dev->qdev, "smb_io_base", smb_io_base); + + s = DO_UPCAST(VT686PMState, dev, dev); + + qdev_init_nofail(&dev->qdev); + + return s->smb.smbus; +} + +static PCIDeviceInfo via_pm_info = { + .qdev.name = "VT82C686B_PM", + .qdev.desc = "PM", + .qdev.size = sizeof(VT686PMState), + .qdev.vmsd = &vmstate_acpi, + .init = vt82c686b_pm_initfn, + .config_write = pm_write_config, + .qdev.props = (Property[]) { + DEFINE_PROP_UINT32("smb_io_base", VT686PMState, smb_io_base, 0), + DEFINE_PROP_END_OF_LIST(), + } +}; + +static void vt82c686b_pm_register(void) +{ + pci_qdev_register(&via_pm_info); +} + +device_init(vt82c686b_pm_register); + +static const VMStateDescription vmstate_via = { + .name = "vt82c686b", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField []) { + VMSTATE_PCI_DEVICE(dev, VT82C686BState), + VMSTATE_END_OF_LIST() + } +}; + +/* init the PCI-to-ISA bridge */ +static int vt82c686b_initfn(PCIDevice *d) +{ + uint8_t *pci_conf; + uint8_t *wmask; + int i; + + isa_bus_new(&d->qdev); + + pci_conf = d->config; + pci_config_set_vendor_id(pci_conf, PCI_VENDOR_ID_VIA); + pci_config_set_device_id(pci_conf, PCI_DEVICE_ID_VIA_ISA_BRIDGE); + pci_config_set_class(pci_conf, PCI_CLASS_BRIDGE_ISA); + pci_config_set_prog_interface(pci_conf, 0x0); + pci_config_set_revision(pci_conf,0x40); /* Revision 4.0 */ + + wmask = d->wmask; + for (i = 0x00; i < 0xff; i++) { + if (i<=0x03 || (i>=0x08 && i<=0x3f)) { + wmask[i] = 0x00; + } + } + + qemu_register_reset(vt82c686b_reset, d); + + return 0; +} + +int vt82c686b_init(PCIBus *bus, int devfn) +{ + PCIDevice *d; + + d = pci_create_simple_multifunction(bus, devfn, true, "VT82C686B"); + + return d->devfn; +} + +static PCIDeviceInfo via_info = { + .qdev.name = "VT82C686B", + .qdev.desc = "ISA bridge", + .qdev.size = sizeof(VT82C686BState), + .qdev.vmsd = &vmstate_via, + .qdev.no_user = 1, + .init = vt82c686b_initfn, + .config_write = vt82c686b_write_config, +}; + +static void vt82c686b_register(void) +{ + pci_qdev_register(&via_info); +} +device_init(vt82c686b_register); diff --git a/hw/vt82c686.h b/hw/vt82c686.h new file mode 100644 index 0000000..e3270ca --- /dev/null +++ b/hw/vt82c686.h @@ -0,0 +1,11 @@ +#ifndef HW_VT82C686_H +#define HW_VT82C686_H + +/* vt82c686.c */ +int vt82c686b_init(PCIBus * bus, int devfn); +void vt82c686b_ac97_init(PCIBus *bus, int devfn); +void vt82c686b_mc97_init(PCIBus *bus, int devfn); +i2c_bus *vt82c686b_pm_init(PCIBus *bus, int devfn, uint32_t smb_io_base, + qemu_irq sci_irq); + +#endif diff --git a/hw/watchdog.c b/hw/watchdog.c new file mode 100644 index 0000000..e9dd56e --- /dev/null +++ b/hw/watchdog.c @@ -0,0 +1,147 @@ +/* + * 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 . + * + * By Richard W.M. Jones (rjones@redhat.com). + */ + +#include "qemu-common.h" +#include "qemu-option.h" +#include "qemu-config.h" +#include "qemu-queue.h" +#include "qemu-objects.h" +#include "monitor.h" +#include "sysemu.h" +#include "hw/watchdog.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. */ + +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 (strcmp(p, "?") == 0) { + 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); + qemu_opt_set(opts, "driver", p); + 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 + return -1; + + return 0; +} + +static void watchdog_mon_event(const char *action) +{ + QObject *data; + + data = qobject_from_jsonf("{ 'action': %s }", action); + monitor_protocol_event(QEVENT_WATCHDOG, data); + qobject_decref(data); +} + +/* 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 */ + watchdog_mon_event("reset"); + qemu_system_reset_request(); + break; + + case WDT_SHUTDOWN: /* same as 'system_powerdown' in monitor */ + watchdog_mon_event("shutdown"); + qemu_system_powerdown_request(); + break; + + case WDT_POWEROFF: /* same as 'quit' command in monitor */ + watchdog_mon_event("poweroff"); + exit(0); + break; + + case WDT_PAUSE: /* same as 'stop' command in monitor */ + watchdog_mon_event("pause"); + vm_stop(0); + break; + + case WDT_DEBUG: + watchdog_mon_event("debug"); + fprintf(stderr, "watchdog: timer fired\n"); + break; + + case WDT_NONE: + watchdog_mon_event("none"); + break; + } +} diff --git a/hw/watchdog.h b/hw/watchdog.h new file mode 100644 index 0000000..c12a293 --- /dev/null +++ b/hw/watchdog.h @@ -0,0 +1,43 @@ +/* + * 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 . + * + * By Richard W.M. Jones (rjones@redhat.com). + */ + +#ifndef QEMU_WATCHDOG_H +#define QEMU_WATCHDOG_H + +#include "qemu-queue.h" + +struct WatchdogTimerModel { + QLIST_ENTRY(WatchdogTimerModel) entry; + + /* Short name of the device - used to select it on the command line. */ + const char *wdt_name; + /* Longer description (eg. manufacturer and full model number). */ + const char *wdt_description; +}; +typedef struct WatchdogTimerModel WatchdogTimerModel; + +/* in hw/watchdog.c */ +int select_watchdog(const char *p); +int select_watchdog_action(const char *action); +void watchdog_add_model(WatchdogTimerModel *model); +void watchdog_perform_action(void); + +#endif /* QEMU_WATCHDOG_H */ diff --git a/hw/wdt_i6300esb.c b/hw/wdt_i6300esb.c new file mode 100644 index 0000000..90bf5f6 --- /dev/null +++ b/hw/wdt_i6300esb.c @@ -0,0 +1,448 @@ +/* + * 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 . + * + * By Richard W.M. Jones (rjones@redhat.com). + */ + +#include + +#include "qemu-common.h" +#include "qemu-timer.h" +#include "watchdog.h" +#include "hw.h" +#include "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; + + 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; + +/* 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. */ + timeout = get_ticks_per_sec() * timeout / 33000000; + + i6300esb_debug("stage %d, timeout %" PRIi64 "\n", d->stage, timeout); + + qemu_mod_timer(d->timer, qemu_get_clock(vm_clock) + timeout); +} + +/* This is called when the guest disables the watchdog. */ +static void i6300esb_disable_timer(I6300State *d) +{ + i6300esb_debug("timer disabled\n"); + + qemu_del_timer(d->timer); +} + +static void i6300esb_reset(DeviceState *dev) +{ + PCIDevice *pdev = DO_UPCAST(PCIDevice, qdev, dev); + I6300State *d = DO_UPCAST(I6300State, dev, 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 = DO_UPCAST(I6300State, dev, 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 = DO_UPCAST(I6300State, dev, 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, target_phys_addr_t addr) +{ + i6300esb_debug ("addr = %x\n", (int) addr); + + return 0; +} + +static uint32_t i6300esb_mem_readw(void *vp, target_phys_addr_t 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, target_phys_addr_t addr) +{ + i6300esb_debug("addr = %x\n", (int) addr); + + return 0; +} + +static void i6300esb_mem_writeb(void *vp, target_phys_addr_t 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, target_phys_addr_t 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, target_phys_addr_t 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 void i6300esb_map(PCIDevice *dev, int region_num, + pcibus_t addr, pcibus_t size, int type) +{ + static CPUReadMemoryFunc * const mem_read[3] = { + i6300esb_mem_readb, + i6300esb_mem_readw, + i6300esb_mem_readl, + }; + static CPUWriteMemoryFunc * const mem_write[3] = { + i6300esb_mem_writeb, + i6300esb_mem_writew, + i6300esb_mem_writel, + }; + I6300State *d = DO_UPCAST(I6300State, dev, dev); + int io_mem; + + i6300esb_debug("addr = %"FMT_PCIBUS", size = %"FMT_PCIBUS", type = %d\n", + addr, size, type); + + io_mem = cpu_register_io_memory(mem_read, mem_write, d, + DEVICE_NATIVE_ENDIAN); + cpu_register_physical_memory (addr, 0x10, io_mem); + /* qemu_register_coalesced_mmio (addr, 0x10); ? */ +} + +static const VMStateDescription vmstate_i6300esb = { + .name = "i6300esb_wdt", + .version_id = sizeof(I6300State), + .minimum_version_id = sizeof(I6300State), + .minimum_version_id_old = sizeof(I6300State), + .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(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 int i6300esb_init(PCIDevice *dev) +{ + I6300State *d = DO_UPCAST(I6300State, dev, dev); + uint8_t *pci_conf; + + i6300esb_debug("I6300State = %p\n", d); + + d->timer = qemu_new_timer(vm_clock, i6300esb_timer_expired, d); + d->previous_reboot_flag = 0; + + pci_conf = d->dev.config; + pci_config_set_vendor_id(pci_conf, PCI_VENDOR_ID_INTEL); + pci_config_set_device_id(pci_conf, PCI_DEVICE_ID_INTEL_ESB_9); + pci_config_set_class(pci_conf, PCI_CLASS_SYSTEM_OTHER); + + pci_register_bar(&d->dev, 0, 0x10, + PCI_BASE_ADDRESS_SPACE_MEMORY, i6300esb_map); + + return 0; +} + +static WatchdogTimerModel model = { + .wdt_name = "i6300esb", + .wdt_description = "Intel 6300ESB", +}; + +static PCIDeviceInfo i6300esb_info = { + .qdev.name = "i6300esb", + .qdev.size = sizeof(I6300State), + .qdev.vmsd = &vmstate_i6300esb, + .qdev.reset = i6300esb_reset, + .config_read = i6300esb_config_read, + .config_write = i6300esb_config_write, + .init = i6300esb_init, +}; + +static void i6300esb_register_devices(void) +{ + watchdog_add_model(&model); + pci_qdev_register(&i6300esb_info); +} + +device_init(i6300esb_register_devices); diff --git a/hw/wdt_ib700.c b/hw/wdt_ib700.c new file mode 100644 index 0000000..1248464 --- /dev/null +++ b/hw/wdt_ib700.c @@ -0,0 +1,137 @@ +/* + * 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 . + * + * By Richard W.M. Jones (rjones@redhat.com). + */ + +#include "qemu-common.h" +#include "qemu-timer.h" +#include "watchdog.h" +#include "hw.h" +#include "isa.h" +#include "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 + +typedef struct IB700state { + ISADevice dev; + QEMUTimer *timer; +} 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, + * unchangable 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(); + qemu_mod_timer(s->timer, qemu_get_clock (vm_clock) + 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); + + qemu_del_timer(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(); + qemu_del_timer(s->timer); +} + +static const VMStateDescription vmstate_ib700 = { + .name = "ib700_wdt", + .version_id = 0, + .minimum_version_id = 0, + .minimum_version_id_old = 0, + .fields = (VMStateField []) { + VMSTATE_TIMER(timer, IB700State), + VMSTATE_END_OF_LIST() + } +}; + +static int wdt_ib700_init(ISADevice *dev) +{ + IB700State *s = DO_UPCAST(IB700State, dev, dev); + + ib700_debug("watchdog init\n"); + + s->timer = qemu_new_timer(vm_clock, ib700_timer_expired, s); + register_ioport_write(0x441, 2, 1, ib700_write_disable_reg, s); + register_ioport_write(0x443, 2, 1, ib700_write_enable_reg, s); + + return 0; +} + +static void wdt_ib700_reset(DeviceState *dev) +{ + IB700State *s = DO_UPCAST(IB700State, dev.qdev, dev); + + ib700_debug("watchdog reset\n"); + + qemu_del_timer(s->timer); +} + +static WatchdogTimerModel model = { + .wdt_name = "ib700", + .wdt_description = "iBASE 700", +}; + +static ISADeviceInfo wdt_ib700_info = { + .qdev.name = "ib700", + .qdev.size = sizeof(IB700State), + .qdev.vmsd = &vmstate_ib700, + .qdev.reset = wdt_ib700_reset, + .init = wdt_ib700_init, +}; + +static void wdt_ib700_register_devices(void) +{ + watchdog_add_model(&model); + isa_qdev_register(&wdt_ib700_info); +} + +device_init(wdt_ib700_register_devices); diff --git a/hw/wm8750.c b/hw/wm8750.c new file mode 100644 index 0000000..c9c6744 --- /dev/null +++ b/hw/wm8750.c @@ -0,0 +1,707 @@ +/* + * WM8750 audio CODEC. + * + * Copyright (c) 2006 Openedhand Ltd. + * Written by Andrzej Zaborowski + * + * This file is licensed under GNU GPL. + */ + +#include "hw.h" +#include "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; + +typedef struct { + i2c_slave i2c; + 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(i2c_slave *i2c) +{ + WM8750State *s = (WM8750State *) 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(i2c_slave *i2c, enum i2c_event event) +{ + WM8750State *s = (WM8750State *) 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(i2c_slave *i2c, uint8_t data) +{ + WM8750State *s = (WM8750State *) i2c; + uint8_t cmd; + uint16_t value; + + if (s->i2c_len >= 2) { + printf("%s: long message (%i bytes)\n", __FUNCTION__, s->i2c_len); +#ifdef VERBOSE + return 1; +#endif + } + 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(&s->i2c); + break; + +#ifdef VERBOSE + default: + printf("%s: unknown register %02x\n", __FUNCTION__, cmd); +#endif + } + + return 0; +} + +static int wm8750_rx(i2c_slave *i2c) +{ + return 0x00; +} + +static void wm8750_pre_save(void *opaque) +{ + WM8750State *s = opaque; + + s->rate_vmstate = (s->rate - wm_rate_table) / sizeof(*s->rate); +} + +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, + .minimum_version_id_old = 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(i2c, WM8750State), + VMSTATE_END_OF_LIST() + } +}; + +static int wm8750_init(i2c_slave *i2c) +{ + WM8750State *s = FROM_I2C_SLAVE(WM8750State, i2c); + + AUD_register_card(CODEC, &s->card); + wm8750_reset(&s->i2c); + + return 0; +} + +#if 0 +static void wm8750_fini(i2c_slave *i2c) +{ + WM8750State *s = (WM8750State *) i2c; + wm8750_reset(&s->i2c); + AUD_remove_card(&s->card); + qemu_free(s); +} +#endif + +void wm8750_data_req_set(DeviceState *dev, + void (*data_req)(void *, int, int), void *opaque) +{ + WM8750State *s = FROM_I2C_SLAVE(WM8750State, I2C_SLAVE_FROM_QDEV(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 samples 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 I2CSlaveInfo wm8750_info = { + .qdev.name = "wm8750", + .qdev.size = sizeof(WM8750State), + .qdev.vmsd = &vmstate_wm8750, + .init = wm8750_init, + .event = wm8750_event, + .recv = wm8750_rx, + .send = wm8750_tx +}; + +static void wm8750_register_devices(void) +{ + i2c_register_slave(&wm8750_info); +} + +device_init(wm8750_register_devices) diff --git a/hw/xen.h b/hw/xen.h new file mode 100644 index 0000000..780dcf7 --- /dev/null +++ b/hw/xen.h @@ -0,0 +1,21 @@ +#ifndef QEMU_HW_XEN_H +#define QEMU_HW_XEN_H 1 +/* + * public xen header + * stuff needed outside xen-*.c, i.e. interfaces to qemu. + * must not depend on any xen headers being present in + * /usr/include/xen, so it can be included unconditionally. + */ +#include + +/* xen-machine.c */ +enum xen_mode { + XEN_EMULATE = 0, // xen emulation, using xenner (default) + XEN_CREATE, // create xen domain + XEN_ATTACH // attach to xen domain created by xend +}; + +extern uint32_t xen_domid; +extern enum xen_mode xen_mode; + +#endif /* QEMU_HW_XEN_H */ diff --git a/hw/xen_backend.c b/hw/xen_backend.c new file mode 100644 index 0000000..a2e408f --- /dev/null +++ b/hw/xen_backend.c @@ -0,0 +1,714 @@ +/* + * xen backend driver infrastructure + * (c) 2008 Gerd Hoffmann + * + * 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 . + */ + +/* + * TODO: add some xenbus / xenstore concepts overview here. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "hw.h" +#include "qemu-char.h" +#include "qemu-log.h" +#include "xen_backend.h" + +/* ------------------------------------------------------------- */ + +/* public */ +int xen_xc; +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 qemu_free() stuff. */ + ret = qemu_strdup(str); + free(str); + } + return ret; +} + +int xenstore_write_int(const char *base, const char *node, int ival) +{ + char val[32]; + + snprintf(val, sizeof(val), "%d", 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; + qemu_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); +} + +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); +} + +/* ------------------------------------------------------------- */ + +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; + char *dom0; + + xendev = xen_be_find_xendev(type, dom, dev); + if (xendev) + return xendev; + + /* init new xendev */ + xendev = qemu_mallocz(ops->size); + xendev->type = type; + xendev->dom = dom; + xendev->dev = dev; + xendev->ops = ops; + + dom0 = xs_get_domain_path(xenstore, 0); + snprintf(xendev->be, sizeof(xendev->be), "%s/backend/%s/%d/%d", + dom0, xendev->type, xendev->dom, xendev->dev); + snprintf(xendev->name, sizeof(xendev->name), "%s-%d", + xendev->type, xendev->dev); + free(dom0); + + xendev->debug = debug; + xendev->local_port = -1; + + xendev->evtchndev = xc_evtchn_open(); + if (xendev->evtchndev < 0) { + xen_be_printf(NULL, 0, "can't open evtchn device\n"); + qemu_free(xendev); + return NULL; + } + fcntl(xc_evtchn_fd(xendev->evtchndev), F_SETFD, FD_CLOEXEC); + + if (ops->flags & DEVOPS_FLAG_NEED_GNTDEV) { + xendev->gnttabdev = xc_gnttab_open(); + if (xendev->gnttabdev < 0) { + xen_be_printf(NULL, 0, "can't open gnttab device\n"); + xc_evtchn_close(xendev->evtchndev); + qemu_free(xendev); + return NULL; + } + } else { + xendev->gnttabdev = -1; + } + + 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); + qemu_free(xendev->fe); + } + + if (xendev->evtchndev >= 0) + xc_evtchn_close(xendev->evtchndev); + if (xendev->gnttabdev >= 0) + xc_gnttab_close(xendev->gnttabdev); + + QTAILQ_REMOVE(&xendevs, xendev, next); + qemu_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) { + qemu_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 connect 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_connect(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->connect) + rc = xendev->ops->connect(xendev); + if (rc != 0) { + xen_be_printf(xendev, 0, "connect() failed\n"); + return rc; + } + + xen_be_set_state(xendev, XenbusStateConnected); + return 0; +} + +/* + * 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_connect(xendev); + 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, *dom0; + unsigned int cdev, j; + + /* setup watch */ + dom0 = xs_get_domain_path(xenstore, 0); + snprintf(token, sizeof(token), "be:%p:%d:%p", type, dom, ops); + snprintf(path, sizeof(path), "%s/backend/%s/%d", dom0, type, dom); + free(dom0); + 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], *dom0; + unsigned int len, dev; + + dom0 = xs_get_domain_path(xenstore, 0); + len = snprintf(path, sizeof(path), "%s/backend/%s/%d", dom0, type, dom); + free(dom0); + 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; + + if (0) { + /* FIXME: detect devices being deleted from xenstore ... */ + xen_be_del_xendev(dom, dev); + } + + xendev = xen_be_get_xendev(type, dom, dev, ops); + if (xendev != NULL) { + 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; + } + + if (qemu_set_fd_handler(xs_fileno(xenstore), xenstore_update, NULL, NULL) < 0) + goto err; + + xen_xc = xc_interface_open(); + if (xen_xc == -1) { + xen_be_printf(NULL, 0, "can't open xen interface\n"); + 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_backend.h b/hw/xen_backend.h new file mode 100644 index 0000000..1b428e3 --- /dev/null +++ b/hw/xen_backend.h @@ -0,0 +1,106 @@ +#ifndef QEMU_HW_XEN_BACKEND_H +#define QEMU_HW_XEN_BACKEND_H 1 + +#include "xen_common.h" +#include "sysemu.h" +#include "net.h" + +/* ------------------------------------------------------------- */ + +#define XEN_BUFSIZE 1024 + +struct XenDevice; + +/* driver uses grant tables -> open gntdev device (xendev->gnttabdev) */ +#define DEVOPS_FLAG_NEED_GNTDEV 1 +/* don't expect frontend doing correct state transitions (aka console quirk) */ +#define DEVOPS_FLAG_IGNORE_STATE 2 + +struct XenDevOps { + size_t size; + uint32_t flags; + void (*alloc)(struct XenDevice *xendev); + int (*init)(struct XenDevice *xendev); + int (*connect)(struct XenDevice *xendev); + void (*event)(struct XenDevice *xendev); + void (*disconnect)(struct XenDevice *xendev); + int (*free)(struct XenDevice *xendev); + void (*backend_changed)(struct XenDevice *xendev, const char *node); + void (*frontend_changed)(struct XenDevice *xendev, const char *node); +}; + +struct XenDevice { + const char *type; + int dom; + int dev; + char name[64]; + int debug; + + enum xenbus_state be_state; + enum xenbus_state fe_state; + int online; + char be[XEN_BUFSIZE]; + char *fe; + char *protocol; + int remote_port; + int local_port; + + int evtchndev; + int gnttabdev; + + struct XenDevOps *ops; + QTAILQ_ENTRY(XenDevice) next; +}; + +/* ------------------------------------------------------------- */ + +/* variables */ +extern int xen_xc; +extern struct xs_handle *xenstore; +extern const char *xen_protocol; + +/* xenstore helper functions */ +int xenstore_write_str(const char *base, const char *node, const char *val); +int xenstore_write_int(const char *base, const char *node, int ival); +char *xenstore_read_str(const char *base, const char *node); +int xenstore_read_int(const char *base, const char *node, int *ival); + +int xenstore_write_be_str(struct XenDevice *xendev, const char *node, const char *val); +int xenstore_write_be_int(struct XenDevice *xendev, const char *node, int ival); +char *xenstore_read_be_str(struct XenDevice *xendev, const char *node); +int xenstore_read_be_int(struct XenDevice *xendev, const char *node, int *ival); +char *xenstore_read_fe_str(struct XenDevice *xendev, const char *node); +int xenstore_read_fe_int(struct XenDevice *xendev, const char *node, int *ival); + +const char *xenbus_strstate(enum xenbus_state state); +struct XenDevice *xen_be_find_xendev(const char *type, int dom, int dev); +void xen_be_check_state(struct XenDevice *xendev); + +/* xen backend driver bits */ +int xen_be_init(void); +int xen_be_register(const char *type, struct XenDevOps *ops); +int xen_be_set_state(struct XenDevice *xendev, enum xenbus_state state); +int xen_be_bind_evtchn(struct XenDevice *xendev); +void xen_be_unbind_evtchn(struct XenDevice *xendev); +int xen_be_send_notify(struct XenDevice *xendev); +void xen_be_printf(struct XenDevice *xendev, int msg_level, const char *fmt, ...) + GCC_FMT_ATTR(3, 4); + +/* actual backend drivers */ +extern struct XenDevOps xen_console_ops; /* xen_console.c */ +extern struct XenDevOps xen_kbdmouse_ops; /* xen_framebuffer.c */ +extern struct XenDevOps xen_framebuffer_ops; /* xen_framebuffer.c */ +extern struct XenDevOps xen_blkdev_ops; /* xen_disk.c */ +extern struct XenDevOps xen_netdev_ops; /* xen_nic.c */ + +void xen_init_display(int domid); + +/* configuration (aka xenbus setup) */ +void xen_config_cleanup(void); +int xen_config_dev_blk(DriveInfo *disk); +int xen_config_dev_nic(NICInfo *nic); +int xen_config_dev_vfb(int vdev, const char *type); +int xen_config_dev_vkbd(int vdev); +int xen_config_dev_console(int vdev); + +#endif /* QEMU_HW_XEN_BACKEND_H */ diff --git a/hw/xen_blkif.h b/hw/xen_blkif.h new file mode 100644 index 0000000..c0f4136 --- /dev/null +++ b/hw/xen_blkif.h @@ -0,0 +1,103 @@ +#ifndef __XEN_BLKIF_H__ +#define __XEN_BLKIF_H__ + +#include +#include +#include + +/* 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 (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 (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/xen_common.h b/hw/xen_common.h new file mode 100644 index 0000000..8a55b44 --- /dev/null +++ b/hw/xen_common.h @@ -0,0 +1,34 @@ +#ifndef QEMU_HW_XEN_COMMON_H +#define QEMU_HW_XEN_COMMON_H 1 + +#include +#include + +#include +#include +#include + +#include "hw.h" +#include "xen.h" +#include "qemu-queue.h" + +/* + * tweaks needed to build with different xen versions + * 0x00030205 -> 3.1.0 + * 0x00030207 -> 3.2.0 + * 0x00030208 -> unstable + */ +#include +#if __XEN_LATEST_INTERFACE_VERSION__ < 0x00030205 +# define evtchn_port_or_error_t int +#endif +#if __XEN_LATEST_INTERFACE_VERSION__ < 0x00030207 +# define xc_map_foreign_pages xc_map_foreign_batch +#endif +#if __XEN_LATEST_INTERFACE_VERSION__ < 0x00030208 +# define xen_mb() mb() +# define xen_rmb() rmb() +# define xen_wmb() wmb() +#endif + +#endif /* QEMU_HW_XEN_COMMON_H */ diff --git a/hw/xen_console.c b/hw/xen_console.c new file mode 100644 index 0000000..d2261f4 --- /dev/null +++ b/hw/xen_console.c @@ -0,0 +1,269 @@ +/* + * Copyright (C) International Business Machines Corp., 2005 + * Author(s): Anthony Liguori + * + * 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 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "hw.h" +#include "sysemu.h" +#include "qemu-char.h" +#include "xen_backend.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 = qemu_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 = qemu_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_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; + + /* setup */ + dom = xs_get_domain_path(xenstore, con->xendev.dom); + snprintf(con->console, sizeof(con->console), "%s/console", dom); + 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); + return -1; + } + + if (!serial_hds[con->xendev.dev]) + xen_be_printf(xendev, 1, "WARNING: serial line %d not configured\n", + con->xendev.dev); + else + con->chr = serial_hds[con->xendev.dev]; + + return 0; +} + +static int con_connect(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; + + con->sring = xc_map_foreign_range(xen_xc, con->xendev.dom, + XC_PAGE_SIZE, + PROT_READ|PROT_WRITE, + con->ring_ref); + if (!con->sring) + return -1; + + xen_be_bind_evtchn(&con->xendev); + if (con->chr) + qemu_chr_add_handlers(con->chr, xencons_can_receive, xencons_receive, + NULL, con); + + 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 (con->chr) + qemu_chr_add_handlers(con->chr, NULL, NULL, NULL, NULL); + xen_be_unbind_evtchn(&con->xendev); + + if (con->sring) { + munmap(con->sring, XC_PAGE_SIZE); + 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, + .init = con_init, + .connect = con_connect, + .event = con_event, + .disconnect = con_disconnect, +}; diff --git a/hw/xen_devconfig.c b/hw/xen_devconfig.c new file mode 100644 index 0000000..8d50216 --- /dev/null +++ b/hw/xen_devconfig.c @@ -0,0 +1,173 @@ +#include "xen_backend.h" +#include "blockdev.h" +#include "block_int.h" /* XXX */ + +/* ------------------------------------------------------------- */ + +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 = qemu_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(qemu_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]; + int vdev = 202 * 256 + 16 * disk->unit; + int cdrom = disk->bdrv->type == BDRV_TYPE_CDROM; + const char *devtype = cdrom ? "cdrom" : "disk"; + const char *mode = cdrom ? "r" : "w"; + + snprintf(disk->bdrv->device_name, sizeof(disk->bdrv->device_name), + "xvd%c", 'a' + disk->unit); + xen_be_printf(NULL, 1, "config disk %d [%s]: %s\n", + disk->unit, disk->bdrv->device_name, disk->bdrv->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", disk->bdrv->device_name); + xenstore_write_str(be, "type", "file"); + xenstore_write_str(be, "params", disk->bdrv->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]; + + snprintf(mac, sizeof(mac), "%02x:%02x:%02x:%02x:%02x:%02x", + nic->macaddr[0], nic->macaddr[1], nic->macaddr[2], + nic->macaddr[3], nic->macaddr[4], nic->macaddr[5]); + xen_be_printf(NULL, 1, "config nic %d: mac=\"%s\"\n", nic->vlan->id, mac); + xen_config_dev_dirs("vif", "qnic", nic->vlan->id, fe, be, sizeof(fe)); + + /* frontend */ + xenstore_write_int(fe, "handle", nic->vlan->id); + xenstore_write_str(fe, "mac", mac); + + /* backend */ + xenstore_write_int(be, "handle", nic->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_disk.c b/hw/xen_disk.c new file mode 100644 index 0000000..ed9e5eb --- /dev/null +++ b/hw/xen_disk.c @@ -0,0 +1,788 @@ +/* + * xen paravirt block device backend + * + * (c) Gerd Hoffmann + * + * 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 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "hw.h" +#include "block_int.h" +#include "qemu-char.h" +#include "xen_blkif.h" +#include "xen_backend.h" +#include "blockdev.h" + +/* ------------------------------------------------------------- */ + +static int syncwrite = 0; +static int batch_maps = 0; + +static int max_requests = 32; +static int use_aio = 1; + +/* ------------------------------------------------------------- */ + +#define BLOCK_SIZE 512 +#define IOCB_COUNT (BLKIF_MAX_SEGMENTS_PER_REQUEST + 2) + +struct ioreq { + blkif_request_t req; + int16_t status; + + /* parsed request */ + off_t start; + QEMUIOVector v; + int presync; + int postsync; + + /* 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; + + /* aio status */ + int aio_inflight; + int aio_errors; + + struct XenBlkDev *blkdev; + QLIST_ENTRY(ioreq) list; +}; + +struct XenBlkDev { + struct XenDevice xendev; /* must be first */ + char *params; + char *mode; + char *type; + char *dev; + char *devtype; + 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; + + /* qemu block driver */ + DriveInfo *dinfo; + BlockDriverState *bs; + QEMUBH *bh; +}; + +/* ------------------------------------------------------------- */ + +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 = qemu_mallocz(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); + qemu_iovec_reset(&ioreq->v); + } + 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) +{ + struct XenBlkDev *blkdev = ioreq->blkdev; + + QLIST_REMOVE(ioreq, list); + memset(ioreq, 0, sizeof(*ioreq)); + ioreq->blkdev = blkdev; + QLIST_INSERT_HEAD(&blkdev->freelist, ioreq, list); + blkdev->requests_finished--; +} + +/* + * 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_WRITE_BARRIER: + if (!ioreq->req.nr_segments) { + ioreq->presync = 1; + return 0; + } + if (!syncwrite) + ioreq->presync = ioreq->postsync = 1; + /* fall through */ + case BLKIF_OP_WRITE: + ioreq->prot = PROT_READ; /* from memory */ + if (syncwrite) + ioreq->postsync = 1; + break; + 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) +{ + int gnt = ioreq->blkdev->xendev.gnttabdev; + int i; + + if (ioreq->v.niov == 0) + return; + if (batch_maps) { + if (!ioreq->pages) + return; + if (xc_gnttab_munmap(gnt, ioreq->pages, ioreq->v.niov) != 0) + xen_be_printf(&ioreq->blkdev->xendev, 0, "xc_gnttab_munmap failed: %s\n", + strerror(errno)); + ioreq->blkdev->cnt_map -= ioreq->v.niov; + ioreq->pages = NULL; + } else { + for (i = 0; i < ioreq->v.niov; 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; + } + } +} + +static int ioreq_map(struct ioreq *ioreq) +{ + int gnt = ioreq->blkdev->xendev.gnttabdev; + int i; + + if (ioreq->v.niov == 0) + return 0; + if (batch_maps) { + ioreq->pages = xc_gnttab_map_grant_refs + (gnt, ioreq->v.niov, ioreq->domids, ioreq->refs, ioreq->prot); + if (ioreq->pages == NULL) { + xen_be_printf(&ioreq->blkdev->xendev, 0, + "can't map %d grant refs (%s, %d maps)\n", + ioreq->v.niov, strerror(errno), ioreq->blkdev->cnt_map); + return -1; + } + for (i = 0; i < ioreq->v.niov; i++) + ioreq->v.iov[i].iov_base = ioreq->pages + i * XC_PAGE_SIZE + + (uintptr_t)ioreq->v.iov[i].iov_base; + ioreq->blkdev->cnt_map += ioreq->v.niov; + } else { + for (i = 0; i < ioreq->v.niov; i++) { + ioreq->page[i] = xc_gnttab_map_grant_ref + (gnt, ioreq->domids[i], ioreq->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", + ioreq->refs[i], strerror(errno), ioreq->blkdev->cnt_map); + ioreq_unmap(ioreq); + return -1; + } + ioreq->v.iov[i].iov_base = ioreq->page[i] + (uintptr_t)ioreq->v.iov[i].iov_base; + ioreq->blkdev->cnt_map++; + } + } + return 0; +} + +static int ioreq_runio_qemu_sync(struct ioreq *ioreq) +{ + struct XenBlkDev *blkdev = ioreq->blkdev; + int i, rc, len = 0; + off_t pos; + + if (ioreq->req.nr_segments && ioreq_map(ioreq) == -1) + goto err; + if (ioreq->presync) + bdrv_flush(blkdev->bs); + + switch (ioreq->req.operation) { + case BLKIF_OP_READ: + pos = ioreq->start; + for (i = 0; i < ioreq->v.niov; i++) { + rc = bdrv_read(blkdev->bs, pos / BLOCK_SIZE, + ioreq->v.iov[i].iov_base, + ioreq->v.iov[i].iov_len / BLOCK_SIZE); + if (rc != 0) { + xen_be_printf(&blkdev->xendev, 0, "rd I/O error (%p, len %zd)\n", + ioreq->v.iov[i].iov_base, + ioreq->v.iov[i].iov_len); + goto err; + } + len += ioreq->v.iov[i].iov_len; + pos += ioreq->v.iov[i].iov_len; + } + break; + case BLKIF_OP_WRITE: + case BLKIF_OP_WRITE_BARRIER: + if (!ioreq->req.nr_segments) + break; + pos = ioreq->start; + for (i = 0; i < ioreq->v.niov; i++) { + rc = bdrv_write(blkdev->bs, pos / BLOCK_SIZE, + ioreq->v.iov[i].iov_base, + ioreq->v.iov[i].iov_len / BLOCK_SIZE); + if (rc != 0) { + xen_be_printf(&blkdev->xendev, 0, "wr I/O error (%p, len %zd)\n", + ioreq->v.iov[i].iov_base, + ioreq->v.iov[i].iov_len); + goto err; + } + len += ioreq->v.iov[i].iov_len; + pos += ioreq->v.iov[i].iov_len; + } + break; + default: + /* unknown operation (shouldn't happen -- parse catches this) */ + goto err; + } + + if (ioreq->postsync) + bdrv_flush(blkdev->bs); + ioreq->status = BLKIF_RSP_OKAY; + + ioreq_unmap(ioreq); + ioreq_finish(ioreq); + return 0; + +err: + ioreq->status = BLKIF_RSP_ERROR; + return -1; +} + +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->aio_inflight > 0) + return; + + ioreq->status = ioreq->aio_errors ? BLKIF_RSP_ERROR : BLKIF_RSP_OKAY; + ioreq_unmap(ioreq); + ioreq_finish(ioreq); + 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; + + ioreq->aio_inflight++; + if (ioreq->presync) + bdrv_flush(blkdev->bs); /* FIXME: aio_flush() ??? */ + + switch (ioreq->req.operation) { + case BLKIF_OP_READ: + ioreq->aio_inflight++; + bdrv_aio_readv(blkdev->bs, ioreq->start / BLOCK_SIZE, + &ioreq->v, ioreq->v.size / BLOCK_SIZE, + qemu_aio_complete, ioreq); + break; + case BLKIF_OP_WRITE: + case BLKIF_OP_WRITE_BARRIER: + ioreq->aio_inflight++; + if (!ioreq->req.nr_segments) + break; + bdrv_aio_writev(blkdev->bs, ioreq->start / BLOCK_SIZE, + &ioreq->v, ioreq->v.size / BLOCK_SIZE, + qemu_aio_complete, ioreq); + break; + default: + /* unknown operation (shouldn't happen -- parse catches this) */ + goto err; + } + + if (ioreq->postsync) + bdrv_flush(blkdev->bs); /* FIXME: aio_flush() ??? */ + qemu_aio_complete(ioreq, 0); + + return 0; + +err: + 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; + } + 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); + } + 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'. */ + + if (use_aio) + 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); + continue; + } + + if (use_aio) { + /* run i/o in aio mode */ + ioreq_runio_qemu_aio(ioreq); + } else { + /* run i/o in sync mode */ + ioreq_runio_qemu_sync(ioreq); + } + } + if (!use_aio) + blk_send_response_all(blkdev); + + 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); +} + +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; +} + +static int blk_init(struct XenDevice *xendev) +{ + struct XenBlkDev *blkdev = container_of(xendev, struct XenBlkDev, xendev); + int index, qflags, have_barriers, info = 0; + char *h; + + /* read xenstore entries */ + if (blkdev->params == NULL) { + blkdev->params = xenstore_read_be_str(&blkdev->xendev, "params"); + h = strchr(blkdev->params, ':'); + if (h != NULL) { + blkdev->fileproto = blkdev->params; + blkdev->filename = h+1; + *h = 0; + } else { + blkdev->fileproto = ""; + blkdev->filename = blkdev->params; + } + } + 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"); + + /* do we have all we need? */ + if (blkdev->params == NULL || + blkdev->mode == NULL || + blkdev->type == NULL || + blkdev->dev == NULL) + return -1; + + /* read-only ? */ + if (strcmp(blkdev->mode, "w") == 0) { + qflags = BDRV_O_RDWR; + } else { + qflags = 0; + info |= VDISK_READONLY; + } + + /* cdrom ? */ + if (blkdev->devtype && !strcmp(blkdev->devtype, "cdrom")) + info |= VDISK_CDROM; + + /* init qemu block driver */ + index = (blkdev->xendev.dev - 202 * 256) / 16; + blkdev->dinfo = drive_get(IF_XEN, 0, index); + if (!blkdev->dinfo) { + /* setup via xenbus -> create new block driver instance */ + xen_be_printf(&blkdev->xendev, 2, "create new bdrv (xenbus setup)\n"); + blkdev->bs = bdrv_new(blkdev->dev); + if (bdrv_open(blkdev->bs, blkdev->filename, qflags, + bdrv_find_whitelisted_format(blkdev->fileproto)) != 0) { + bdrv_delete(blkdev->bs); + return -1; + } + } else { + /* setup via qemu cmdline -> already setup for us */ + xen_be_printf(&blkdev->xendev, 2, "get configured bdrv (cmdline setup)\n"); + blkdev->bs = blkdev->dinfo->bdrv; + } + blkdev->file_blk = BLOCK_SIZE; + blkdev->file_size = bdrv_getlength(blkdev->bs); + if (blkdev->file_size < 0) { + xen_be_printf(&blkdev->xendev, 1, "bdrv_getlength: %d (%s) | drv %s\n", + (int)blkdev->file_size, strerror(-blkdev->file_size), + blkdev->bs->drv ? blkdev->bs->drv->format_name : "-"); + blkdev->file_size = 0; + } + have_barriers = blkdev->bs->drv && blkdev->bs->drv->bdrv_flush ? 1 : 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 info */ + xenstore_write_be_int(&blkdev->xendev, "feature-barrier", have_barriers); + xenstore_write_be_int(&blkdev->xendev, "info", info); + xenstore_write_be_int(&blkdev->xendev, "sector-size", blkdev->file_blk); + xenstore_write_be_int(&blkdev->xendev, "sectors", + blkdev->file_size / blkdev->file_blk); + return 0; +} + +static int blk_connect(struct XenDevice *xendev) +{ + struct XenBlkDev *blkdev = container_of(xendev, struct XenBlkDev, xendev); + + 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; + + 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; + } + } + + 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->bs) { + if (!blkdev->dinfo) { + /* close/delete only if we created it ourself */ + bdrv_close(blkdev->bs); + bdrv_delete(blkdev->bs); + } + blkdev->bs = 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; + } +} + +static int blk_free(struct XenDevice *xendev) +{ + struct XenBlkDev *blkdev = container_of(xendev, struct XenBlkDev, xendev); + struct ioreq *ioreq; + + while (!QLIST_EMPTY(&blkdev->freelist)) { + ioreq = QLIST_FIRST(&blkdev->freelist); + QLIST_REMOVE(ioreq, list); + qemu_iovec_destroy(&ioreq->v); + qemu_free(ioreq); + } + + qemu_free(blkdev->params); + qemu_free(blkdev->mode); + qemu_free(blkdev->type); + qemu_free(blkdev->dev); + qemu_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, + .connect = blk_connect, + .disconnect = blk_disconnect, + .event = blk_event, + .free = blk_free, +}; diff --git a/hw/xen_domainbuild.c b/hw/xen_domainbuild.c new file mode 100644 index 0000000..7f1fd66 --- /dev/null +++ b/hw/xen_domainbuild.c @@ -0,0 +1,300 @@ +#include +#include "xen_backend.h" +#include "xen_domainbuild.h" +#include "sysemu.h" +#include "qemu-timer.h" +#include "qemu-log.h" + +#include + +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; + } + + qemu_mod_timer(xen_poll, qemu_get_clock(rt_clock) + 1000); + return; + +quit: + qemu_system_shutdown_request(); + return; +} + +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 == 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 = qemu_new_timer(rt_clock, xen_domain_poll, NULL); + qemu_mod_timer(xen_poll, qemu_get_clock(rt_clock) + 1000); + return 0; + +err: + return -1; +} diff --git a/hw/xen_domainbuild.h b/hw/xen_domainbuild.h new file mode 100644 index 0000000..dea0121 --- /dev/null +++ b/hw/xen_domainbuild.h @@ -0,0 +1,13 @@ +#ifndef QEMU_HW_XEN_DOMAINBUILD_H +#define QEMU_HW_XEN_DOMAINBUILD_H 1 + +#include "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/xen_machine_pv.c b/hw/xen_machine_pv.c new file mode 100644 index 0000000..77a34bf --- /dev/null +++ b/hw/xen_machine_pv.c @@ -0,0 +1,124 @@ +/* + * 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.h" +#include "pc.h" +#include "sysemu.h" +#include "boards.h" +#include "xen_backend.h" +#include "xen_domainbuild.h" +#include "blockdev.h" + +static void xen_init_pv(ram_addr_t ram_size, + const char *boot_device, + const char *kernel_filename, + const char *kernel_cmdline, + const char *initrd_filename, + const char *cpu_model) +{ + CPUState *env; + DriveInfo *dinfo; + int i; + + /* Initialize a dummy CPU */ + if (cpu_model == NULL) { +#ifdef TARGET_X86_64 + cpu_model = "qemu64"; +#else + cpu_model = "qemu32"; +#endif + } + env = cpu_init(cpu_model); + env->halted = 1; + + /* 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, +}; + +static void xenpv_machine_init(void) +{ + qemu_register_machine(&xenpv_machine); +} + +machine_init(xenpv_machine_init); diff --git a/hw/xen_nic.c b/hw/xen_nic.c new file mode 100644 index 0000000..08055b8 --- /dev/null +++ b/hw/xen_nic.c @@ -0,0 +1,421 @@ +/* + * xen paravirt network card backend + * + * (c) Gerd Hoffmann + * + * 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 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "hw.h" +#include "net.h" +#include "net/checksum.h" +#include "net/util.h" +#include "qemu-char.h" +#include "xen_backend.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 = qemu_malloc(XC_PAGE_SIZE); + memcpy(tmpbuf, page + txreq.offset, txreq.size); + net_checksum_calculate(tmpbuf, txreq.size); + qemu_send_packet(&netdev->nic->nc, tmpbuf, txreq.size); + } else { + qemu_send_packet(&netdev->nic->nc, 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; + } + qemu_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 int net_rx_ok(VLANClientState *nc) +{ + struct XenNetDev *netdev = DO_UPCAST(NICState, nc, nc)->opaque; + RING_IDX rc, rp; + + if (netdev->xendev.be_state != XenbusStateConnected) + return 0; + + rc = netdev->rx_ring.req_cons; + rp = netdev->rx_ring.sring->req_prod; + xen_rmb(); + + if (rc == rp || RING_REQUEST_CONS_OVERFLOW(&netdev->rx_ring, rc)) { + xen_be_printf(&netdev->xendev, 2, "%s: no rx buffers (%d/%d)\n", + __FUNCTION__, rc, rp); + return 0; + } + return 1; +} + +static ssize_t net_rx_packet(VLANClientState *nc, const uint8_t *buf, size_t size) +{ + struct XenNetDev *netdev = DO_UPCAST(NICState, nc, nc)->opaque; + 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)) { + xen_be_printf(&netdev->xendev, 2, "no buffer, drop packet\n"); + return -1; + } + 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_TYPE_NIC, + .size = sizeof(NICState), + .can_receive = net_rx_ok, + .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->conf.vlan = qemu_find_vlan(netdev->xendev.dev, 1); + netdev->conf.peer = NULL; + + netdev->nic = qemu_new_nic(&net_xen_info, &netdev->conf, + "xen", NULL, netdev); + + snprintf(netdev->nic->nc.info_str, sizeof(netdev->nic->nc.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); + netdev->rxs = xc_gnttab_map_grant_ref(netdev->xendev.gnttabdev, + netdev->xendev.dom, + netdev->rx_ring_ref, + PROT_READ | PROT_WRITE); + if (!netdev->txs || !netdev->rxs) + 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; + } + if (netdev->nic) { + qemu_del_vlan_client(&netdev->nic->nc); + netdev->nic = NULL; + } +} + +static void net_event(struct XenDevice *xendev) +{ + struct XenNetDev *netdev = container_of(xendev, struct XenNetDev, xendev); + net_tx_packets(netdev); +} + +static int net_free(struct XenDevice *xendev) +{ + struct XenNetDev *netdev = container_of(xendev, struct XenNetDev, xendev); + + qemu_free(netdev->mac); + return 0; +} + +/* ------------------------------------------------------------- */ + +struct XenDevOps xen_netdev_ops = { + .size = sizeof(struct XenNetDev), + .flags = DEVOPS_FLAG_NEED_GNTDEV, + .init = net_init, + .connect = net_connect, + .event = net_event, + .disconnect = net_disconnect, + .free = net_free, +}; diff --git a/hw/xenfb.c b/hw/xenfb.c new file mode 100644 index 0000000..da5297b --- /dev/null +++ b/hw/xenfb.c @@ -0,0 +1,1013 @@ +/* + * xen paravirt framebuffer backend + * + * Copyright IBM, Corp. 2005-2006 + * Copyright Red Hat, Inc. 2006-2008 + * + * Authors: + * Anthony Liguori , + * Markus Armbruster , + * Daniel P. Berrange , + * Pat Campbell , + * Gerd Hoffmann + * + * 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 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "hw.h" +#include "sysemu.h" +#include "console.h" +#include "qemu-char.h" +#include "xen_backend.h" + +#ifndef BTN_LEFT +#define BTN_LEFT 0x110 /* from */ +#endif + +/* -------------------------------------------------------------------- */ + +struct common { + struct XenDevice xendev; /* must be first */ + void *page; + DisplayState *ds; +}; + +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 refresh_period; + 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) +{ + int mfn; + + if (xenstore_read_fe_int(&c->xendev, "page-ref", &mfn) == -1) + return -1; + 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 %d, 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; + int dw = ds_get_width(xenfb->c.ds); + int dh = ds_get_height(xenfb->c.ds); + int i; + + 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) +{ + struct XenInput *in = container_of(xendev, struct XenInput, c.xendev); + + if (!in->c.ds) { + xen_be_printf(xendev, 1, "ds not set (yet)\n"); + return -1; + } + + xenstore_write_be_int(xendev, "feature-abs-pointer", 1); + return 0; +} + +static int input_connect(struct XenDevice *xendev) +{ + struct XenInput *in = container_of(xendev, struct XenInput, c.xendev); + int rc; + + if (xenstore_read_fe_int(xendev, "request-abs-pointer", + &in->abs_pointer_wanted) == -1) + in->abs_pointer_wanted = 0; + + rc = common_bind(&in->c); + if (rc != 0) + return rc; + + qemu_add_kbd_event_handler(xenfb_key_event, in); + in->qmouse = qemu_add_mouse_event_handler(xenfb_mouse_event, in, + in->abs_pointer_wanted, + "Xen PVFB Mouse"); + return 0; +} + +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, unsigned long *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; + unsigned long *pgmfns = NULL; + unsigned long *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 = qemu_mallocz(sizeof(unsigned long) * n_fbdirs); + fbmfns = qemu_mallocz(sizeof(unsigned long) * 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 | PROT_WRITE, fbmfns, xenfb->fbpages); + if (xenfb->pixels == NULL) + goto out; + + ret = 0; /* all is fine */ + +out: + qemu_free(pgmfns); + qemu_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) +{ + int line, oops = 0; + int bpp = ds_get_bits_per_pixel(xenfb->c.ds); + int linesize = ds_get_linesize(xenfb->c.ds); + uint8_t *data = ds_get_data(xenfb->c.ds); + + if (!is_buffer_shared(xenfb->c.ds->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_update(xenfb->c.ds, 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; + int i; + + if (xenfb->c.xendev.be_state != XenbusStateConnected) + return; + + if (xenfb->feature_update) { +#ifdef XENFB_TYPE_REFRESH_PERIOD + struct DisplayChangeListener *l; + int period = 99999999; + int idle = 1; + + if (xenfb_queue_full(xenfb)) + return; + + for (l = xenfb->c.ds->listeners; l != NULL; l = l->next) { + if (l->idle) + continue; + idle = 0; + if (!l->gui_timer_interval) { + if (period > GUI_REFRESH_INTERVAL) + period = GUI_REFRESH_INTERVAL; + } else { + if (period > l->gui_timer_interval) + period = l->gui_timer_interval; + } + } + if (idle) + period = XENFB_NO_REFRESH; + + if (xenfb->refresh_period != period) { + xenfb_send_refresh_period(xenfb, period); + xenfb->refresh_period = period; + xen_be_printf(&xenfb->c.xendev, 1, "refresh period: %d\n", period); + } +#else + ; /* nothing */ +#endif + } else { + /* we don't get update notifications, thus use the + * sledge hammer approach ... */ + xenfb->up_fullscreen = 1; + } + + /* resize if needed */ + if (xenfb->do_resize) { + xenfb->do_resize = 0; + switch (xenfb->depth) { + case 16: + case 32: + /* console.c supported depth -> buffer can be used directly */ + qemu_free_displaysurface(xenfb->c.ds); + xenfb->c.ds->surface = qemu_create_displaysurface_from + (xenfb->width, xenfb->height, xenfb->depth, + xenfb->row_stride, xenfb->pixels + xenfb->offset); + break; + default: + /* we must convert stuff */ + qemu_resize_displaysurface(xenfb->c.ds, xenfb->width, xenfb->height); + break; + } + xen_be_printf(&xenfb->c.xendev, 1, "update: resizing: %dx%d @ %d bpp%s\n", + xenfb->width, xenfb->height, xenfb->depth, + is_buffer_shared(xenfb->c.ds->surface) ? " (shared)" : ""); + dpy_resize(xenfb->c.ds); + 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; +} + +/* 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) +{ + struct XenFB *fb = container_of(xendev, struct XenFB, c.xendev); + + fb->refresh_period = -1; + +#ifdef XENFB_TYPE_RESIZE + xenstore_write_be_int(xendev, "feature-resize", 1); +#endif + return 0; +} + +static int fb_connect(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); + 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, + .connect = input_connect, + .disconnect = input_disconnect, + .event = input_event, +}; + +struct XenDevOps xen_framebuffer_ops = { + .size = sizeof(struct XenFB), + .init = fb_init, + .connect = fb_connect, + .disconnect = fb_disconnect, + .event = fb_event, + .frontend_changed = fb_frontend_changed, +}; + +/* + * 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.ds = graphic_console_init(xenfb_update, + xenfb_invalidate, + NULL, + NULL, + fb); + fb->have_console = 1; + + /* vkbd */ + in = container_of(xin, struct XenInput, c.xendev); + in->c.ds = fb->c.ds; + + /* retry ->init() */ + xen_be_check_state(xin); + xen_be_check_state(xfb); +} diff --git a/hw/xilinx.h b/hw/xilinx.h new file mode 100644 index 0000000..705ff5b --- /dev/null +++ b/hw/xilinx.h @@ -0,0 +1,50 @@ + +/* OPB Interrupt Controller. */ +qemu_irq *microblaze_pic_init_cpu(CPUState *env); + +static inline DeviceState * +xilinx_intc_create(target_phys_addr_t base, qemu_irq irq, int kind_of_intr) +{ + DeviceState *dev; + + dev = qdev_create(NULL, "xilinx,intc"); + qdev_prop_set_uint32(dev, "kind-of-intr", kind_of_intr); + qdev_init_nofail(dev); + sysbus_mmio_map(sysbus_from_qdev(dev), 0, base); + sysbus_connect_irq(sysbus_from_qdev(dev), 0, irq); + return dev; +} + +/* OPB Timer/Counter. */ +static inline DeviceState * +xilinx_timer_create(target_phys_addr_t base, qemu_irq irq, int nr, int freq) +{ + DeviceState *dev; + + dev = qdev_create(NULL, "xilinx,timer"); + qdev_prop_set_uint32(dev, "nr-timers", nr); + qdev_prop_set_uint32(dev, "frequency", freq); + qdev_init_nofail(dev); + sysbus_mmio_map(sysbus_from_qdev(dev), 0, base); + sysbus_connect_irq(sysbus_from_qdev(dev), 0, irq); + return dev; +} + +/* XPS Ethernet Lite MAC. */ +static inline DeviceState * +xilinx_ethlite_create(NICInfo *nd, target_phys_addr_t base, qemu_irq irq, + int txpingpong, int rxpingpong) +{ + DeviceState *dev; + + qemu_check_nic_model(nd, "xilinx-ethlite"); + + dev = qdev_create(NULL, "xilinx,ethlite"); + qdev_set_nic_properties(dev, nd); + qdev_prop_set_uint32(dev, "txpingpong", txpingpong); + qdev_prop_set_uint32(dev, "rxpingpong", rxpingpong); + qdev_init_nofail(dev); + sysbus_mmio_map(sysbus_from_qdev(dev), 0, base); + sysbus_connect_irq(sysbus_from_qdev(dev), 0, irq); + return dev; +} diff --git a/hw/xilinx_ethlite.c b/hw/xilinx_ethlite.c new file mode 100644 index 0000000..54b57d7 --- /dev/null +++ b/hw/xilinx_ethlite.c @@ -0,0 +1,254 @@ +/* + * 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 "sysbus.h" +#include "hw.h" +#include "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 + +struct xlx_ethlite +{ + SysBusDevice busdev; + 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 uint32_t eth_readl (void *opaque, target_phys_addr_t addr) +{ + 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 %x=%x\n", __func__, addr * 4, r)); + break; + + /* Rx packet data is endian fixed at the way into the rx rams. This + * speeds things up because the ethlite MAC does not have a len + * register. That means the CPU will issue MMIO reads for the entire + * 2k rx buffer even for small packets. + */ + default: + r = s->regs[addr]; + break; + } + return r; +} + +static void +eth_writel (void *opaque, target_phys_addr_t addr, uint32_t value) +{ + struct xlx_ethlite *s = opaque; + unsigned int base = 0; + + 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=%x val=%x\n", __func__, addr * 4, value)); + if ((value & (CTRL_P | CTRL_S)) == CTRL_S) { + qemu_send_packet(&s->nic->nc, + (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_TX_LEN0: + case R_TX_LEN1: + case R_TX_GIE0: + case R_RX_CTRL0: + case R_RX_CTRL1: + D(qemu_log("%s addr=%x val=%x\n", __func__, addr * 4, value)); + s->regs[addr] = value; + break; + + /* Packet data, make sure it stays BE. */ + default: + s->regs[addr] = cpu_to_be32(value); + break; + } +} + +static CPUReadMemoryFunc * const eth_read[] = { + NULL, NULL, ð_readl, +}; + +static CPUWriteMemoryFunc * const eth_write[] = { + NULL, NULL, ð_writel, +}; + +static int eth_can_rx(VLANClientState *nc) +{ + struct xlx_ethlite *s = DO_UPCAST(NICState, nc, nc)->opaque; + int r; + r = !(s->regs[R_RX_CTRL0] & CTRL_S); + return r; +} + +static ssize_t eth_rx(VLANClientState *nc, const uint8_t *buf, size_t size) +{ + struct xlx_ethlite *s = DO_UPCAST(NICState, nc, nc)->opaque; + unsigned int rxbase = s->rxbuf * (0x800 / 4); + int i; + + /* 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 %d rxbase=%x\n", __func__, size, rxbase)); + memcpy(&s->regs[rxbase + R_RX_BUF0], buf, size); + + /* Bring it into host endianess. */ + for (i = 0; i < ((size + 3) / 4); i++) { + uint32_t d = s->regs[rxbase + R_RX_BUF0 + i]; + s->regs[rxbase + R_RX_BUF0 + i] = be32_to_cpu(d); + } + + s->regs[rxbase + R_RX_CTRL0] |= CTRL_S; + if (s->regs[rxbase + 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 eth_cleanup(VLANClientState *nc) +{ + struct xlx_ethlite *s = DO_UPCAST(NICState, nc, nc)->opaque; + + s->nic = NULL; +} + +static NetClientInfo net_xilinx_ethlite_info = { + .type = NET_CLIENT_TYPE_NIC, + .size = sizeof(NICState), + .can_receive = eth_can_rx, + .receive = eth_rx, + .cleanup = eth_cleanup, +}; + +static int xilinx_ethlite_init(SysBusDevice *dev) +{ + struct xlx_ethlite *s = FROM_SYSBUS(typeof (*s), dev); + int regs; + + sysbus_init_irq(dev, &s->irq); + s->rxbuf = 0; + + regs = cpu_register_io_memory(eth_read, eth_write, s, DEVICE_NATIVE_ENDIAN); + sysbus_init_mmio(dev, R_MAX * 4, regs); + + qemu_macaddr_default_if_unset(&s->conf.macaddr); + s->nic = qemu_new_nic(&net_xilinx_ethlite_info, &s->conf, + dev->qdev.info->name, dev->qdev.id, s); + qemu_format_nic_info_str(&s->nic->nc, s->conf.macaddr.a); + return 0; +} + +static SysBusDeviceInfo xilinx_ethlite_info = { + .init = xilinx_ethlite_init, + .qdev.name = "xilinx,ethlite", + .qdev.size = sizeof(struct xlx_ethlite), + .qdev.props = (Property[]) { + DEFINE_PROP_UINT32("txpingpong", struct xlx_ethlite, c_tx_pingpong, 1), + DEFINE_PROP_UINT32("rxpingpong", struct xlx_ethlite, c_rx_pingpong, 1), + DEFINE_NIC_PROPERTIES(struct xlx_ethlite, conf), + DEFINE_PROP_END_OF_LIST(), + } +}; + +static void xilinx_ethlite_register(void) +{ + sysbus_register_withprop(&xilinx_ethlite_info); +} + +device_init(xilinx_ethlite_register) diff --git a/hw/xilinx_intc.c b/hw/xilinx_intc.c new file mode 100644 index 0000000..cb72d5a --- /dev/null +++ b/hw/xilinx_intc.c @@ -0,0 +1,176 @@ +/* + * 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 "sysbus.h" +#include "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 + +struct xlx_pic +{ + SysBusDevice busdev; + 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]; +}; + +static void update_irq(struct xlx_pic *p) +{ + uint32_t i; + /* 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] & (1 << i)) + break; + } + if (i == 32) + i = ~0; + + p->regs[R_IVR] = i; + if ((p->regs[R_MER] & 1) && p->regs[R_IPR]) { + qemu_irq_raise(p->parent_irq); + } else { + qemu_irq_lower(p->parent_irq); + } +} + +static uint32_t pic_readl (void *opaque, target_phys_addr_t addr) +{ + 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_writel (void *opaque, target_phys_addr_t addr, uint32_t value) +{ + struct xlx_pic *p = opaque; + + 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; + default: + if (addr < ARRAY_SIZE(p->regs)) + p->regs[addr] = value; + break; + } + update_irq(p); +} + +static CPUReadMemoryFunc * const pic_read[] = { + NULL, NULL, + &pic_readl, +}; + +static CPUWriteMemoryFunc * const pic_write[] = { + NULL, NULL, + &pic_writel, +}; + +static void irq_handler(void *opaque, int irq, int level) +{ + struct xlx_pic *p = opaque; + + if (!(p->regs[R_MER] & 2)) { + qemu_irq_lower(p->parent_irq); + return; + } + + /* Update source flops. Don't clear unless level triggered. + Edge triggered interrupts only go away when explicitely acked to + the interrupt controller. */ + if (!(p->c_kind_of_intr & (1 << irq)) || level) { + p->regs[R_ISR] &= ~(1 << irq); + p->regs[R_ISR] |= (level << irq); + } + update_irq(p); +} + +static int xilinx_intc_init(SysBusDevice *dev) +{ + struct xlx_pic *p = FROM_SYSBUS(typeof (*p), dev); + int pic_regs; + + qdev_init_gpio_in(&dev->qdev, irq_handler, 32); + sysbus_init_irq(dev, &p->parent_irq); + + pic_regs = cpu_register_io_memory(pic_read, pic_write, p, DEVICE_NATIVE_ENDIAN); + sysbus_init_mmio(dev, R_MAX * 4, pic_regs); + return 0; +} + +static SysBusDeviceInfo xilinx_intc_info = { + .init = xilinx_intc_init, + .qdev.name = "xilinx,intc", + .qdev.size = sizeof(struct xlx_pic), + .qdev.props = (Property[]) { + DEFINE_PROP_UINT32("kind-of-intr", struct xlx_pic, c_kind_of_intr, 0), + DEFINE_PROP_END_OF_LIST(), + } +}; + +static void xilinx_intc_register(void) +{ + sysbus_register_withprop(&xilinx_intc_info); +} + +device_init(xilinx_intc_register) diff --git a/hw/xilinx_timer.c b/hw/xilinx_timer.c new file mode 100644 index 0000000..30827b0 --- /dev/null +++ b/hw/xilinx_timer.c @@ -0,0 +1,235 @@ +/* + * 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 "sysbus.h" +#include "sysemu.h" +#include "qemu-timer.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]; +}; + +struct timerblock +{ + SysBusDevice busdev; + qemu_irq irq; + uint32_t nr_timers; + uint32_t freq_hz; + struct xlx_timer *timers; +}; + +static inline unsigned int timer_from_addr(target_phys_addr_t 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 < t->nr_timers; 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 uint32_t timer_readl (void *opaque, target_phys_addr_t addr) +{ + 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(printf("%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(printf("%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_count(xt->ptimer, count); + ptimer_run(xt->ptimer, 1); +} + +static void +timer_writel (void *opaque, target_phys_addr_t addr, uint32_t value) +{ + struct timerblock *t = opaque; + struct xlx_timer *xt; + unsigned int timer; + + addr >>= 2; + timer = timer_from_addr(addr); + xt = &t->timers[timer]; + D(printf("%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; + 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 CPUReadMemoryFunc * const timer_read[] = { + NULL, NULL, + &timer_readl, +}; + +static CPUWriteMemoryFunc * const timer_write[] = { + NULL, NULL, + &timer_writel, +}; + +static void timer_hit(void *opaque) +{ + struct xlx_timer *xt = opaque; + struct timerblock *t = xt->parent; + D(printf("%s %d\n", __func__, timer)); + xt->regs[R_TCSR] |= TCSR_TINT; + + if (xt->regs[R_TCSR] & TCSR_ARHT) + timer_enable(xt); + timer_update_irq(t); +} + +static int xilinx_timer_init(SysBusDevice *dev) +{ + struct timerblock *t = FROM_SYSBUS(typeof (*t), dev); + unsigned int i; + int timer_regs; + + /* All timers share a single irq line. */ + sysbus_init_irq(dev, &t->irq); + + /* Init all the ptimers. */ + t->timers = qemu_mallocz(sizeof t->timers[0] * t->nr_timers); + for (i = 0; i < t->nr_timers; 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); + } + + timer_regs = cpu_register_io_memory(timer_read, timer_write, t, + DEVICE_NATIVE_ENDIAN); + sysbus_init_mmio(dev, R_MAX * 4 * t->nr_timers, timer_regs); + return 0; +} + +static SysBusDeviceInfo xilinx_timer_info = { + .init = xilinx_timer_init, + .qdev.name = "xilinx,timer", + .qdev.size = sizeof(struct timerblock), + .qdev.props = (Property[]) { + DEFINE_PROP_UINT32("frequency", struct timerblock, freq_hz, 0), + DEFINE_PROP_UINT32("nr-timers", struct timerblock, nr_timers, 0), + DEFINE_PROP_END_OF_LIST(), + } +}; + +static void xilinx_timer_register(void) +{ + sysbus_register_withprop(&xilinx_timer_info); +} + +device_init(xilinx_timer_register) diff --git a/hw/xilinx_uartlite.c b/hw/xilinx_uartlite.c new file mode 100644 index 0000000..9b94e98 --- /dev/null +++ b/hw/xilinx_uartlite.c @@ -0,0 +1,220 @@ +/* + * 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 "sysbus.h" +#include "qemu-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 + +struct xlx_uartlite +{ + SysBusDevice busdev; + 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]; +}; + +static void uart_update_irq(struct xlx_uartlite *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(struct xlx_uartlite *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 uint32_t uart_readl (void *opaque, target_phys_addr_t addr) +{ + struct xlx_uartlite *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); + 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_writel (void *opaque, target_phys_addr_t addr, uint32_t value) +{ + struct xlx_uartlite *s = opaque; + 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_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 CPUReadMemoryFunc * const uart_read[] = { + &uart_readl, + &uart_readl, + &uart_readl, +}; + +static CPUWriteMemoryFunc * const uart_write[] = { + &uart_writel, + &uart_writel, + &uart_writel, +}; + +static void uart_rx(void *opaque, const uint8_t *buf, int size) +{ + struct xlx_uartlite *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) +{ + struct xlx_uartlite *s = opaque; + int r; + + r = s->rx_fifo_len < sizeof(s->rx_fifo); + if (!r) + printf("cannot receive!\n"); + return r; +} + +static void uart_event(void *opaque, int event) +{ + +} + +static int xilinx_uartlite_init(SysBusDevice *dev) +{ + struct xlx_uartlite *s = FROM_SYSBUS(typeof (*s), dev); + int uart_regs; + + sysbus_init_irq(dev, &s->irq); + + uart_update_status(s); + uart_regs = cpu_register_io_memory(uart_read, uart_write, s, + DEVICE_NATIVE_ENDIAN); + sysbus_init_mmio(dev, R_MAX * 4, uart_regs); + + s->chr = qdev_init_chardev(&dev->qdev); + if (s->chr) + qemu_chr_add_handlers(s->chr, uart_can_rx, uart_rx, uart_event, s); + return 0; +} + +static void xilinx_uart_register(void) +{ + sysbus_register_dev("xilinx,uartlite", sizeof (struct xlx_uartlite), + xilinx_uartlite_init); +} + +device_init(xilinx_uart_register) diff --git a/hw/xio3130_downstream.c b/hw/xio3130_downstream.c new file mode 100644 index 0000000..5aa6a6b --- /dev/null +++ b/hw/xio3130_downstream.c @@ -0,0 +1,211 @@ +/* + * x3130_downstream.c + * TI X3130 pci express downstream port switch + * + * Copyright (c) 2010 Isaku Yamahata + * 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 . + */ + +#include "pci_ids.h" +#include "msi.h" +#include "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); + msi_write_config(d, address, val, len); + pcie_aer_write_config(d, address, val, len); +} + +static void xio3130_downstream_reset(DeviceState *qdev) +{ + PCIDevice *d = DO_UPCAST(PCIDevice, qdev, qdev); + msi_reset(d); + pcie_cap_deverr_reset(d); + pcie_cap_slot_reset(d); + pcie_cap_ari_reset(d); + pci_bridge_reset(qdev); +} + +static int xio3130_downstream_initfn(PCIDevice *d) +{ + PCIBridge* br = DO_UPCAST(PCIBridge, dev, d); + PCIEPort *p = DO_UPCAST(PCIEPort, br, br); + PCIESlot *s = DO_UPCAST(PCIESlot, port, p); + int rc; + int tmp; + + rc = pci_bridge_initfn(d); + if (rc < 0) { + return rc; + } + + pcie_port_init_reg(d); + pci_config_set_vendor_id(d->config, PCI_VENDOR_ID_TI); + pci_config_set_device_id(d->config, PCI_DEVICE_ID_TI_XIO3130D); + d->config[PCI_REVISION_ID] = XIO3130_REVISION; + + 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_ari_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: + tmp = pci_bridge_exitfn(d); + assert(!tmp); + return rc; +} + +static int xio3130_downstream_exitfn(PCIDevice *d) +{ + PCIBridge* br = DO_UPCAST(PCIBridge, dev, d); + PCIEPort *p = DO_UPCAST(PCIEPort, br, br); + PCIESlot *s = DO_UPCAST(PCIESlot, port, p); + + pcie_aer_exit(d); + pcie_chassis_del_slot(s); + pcie_cap_exit(d); + msi_uninit(d); + return 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 = DO_UPCAST(PCIBridge, dev, d); + + qdev = &br->dev.qdev; + 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 DO_UPCAST(PCIESlot, port, DO_UPCAST(PCIEPort, br, br)); +} + +static const VMStateDescription vmstate_xio3130_downstream = { + .name = "xio3130-express-downstream-port", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .post_load = pcie_cap_slot_post_load, + .fields = (VMStateField[]) { + VMSTATE_PCIE_DEVICE(port.br.dev, PCIESlot), + VMSTATE_STRUCT(port.br.dev.exp.aer_log, PCIESlot, 0, + vmstate_pcie_aer_log, PCIEAERLog), + VMSTATE_END_OF_LIST() + } +}; + +static PCIDeviceInfo xio3130_downstream_info = { + .qdev.name = "xio3130-downstream", + .qdev.desc = "TI X3130 Downstream Port of PCI Express Switch", + .qdev.size = sizeof(PCIESlot), + .qdev.reset = xio3130_downstream_reset, + .qdev.vmsd = &vmstate_xio3130_downstream, + + .is_express = 1, + .is_bridge = 1, + .config_write = xio3130_downstream_write_config, + .init = xio3130_downstream_initfn, + .exit = xio3130_downstream_exitfn, + + .qdev.props = (Property[]) { + DEFINE_PROP_UINT8("port", PCIESlot, port.port, 0), + DEFINE_PROP_UINT8("chassis", PCIESlot, chassis, 0), + DEFINE_PROP_UINT16("slot", PCIESlot, slot, 0), + DEFINE_PROP_UINT16("aer_log_max", PCIESlot, + port.br.dev.exp.aer_log.log_max, + PCIE_AER_LOG_MAX_DEFAULT), + DEFINE_PROP_END_OF_LIST(), + } +}; + +static void xio3130_downstream_register(void) +{ + pci_qdev_register(&xio3130_downstream_info); +} + +device_init(xio3130_downstream_register); + +/* + * Local variables: + * c-indent-level: 4 + * c-basic-offset: 4 + * tab-width: 8 + * indent-tab-mode: nil + * End: + */ diff --git a/hw/xio3130_downstream.h b/hw/xio3130_downstream.h new file mode 100644 index 0000000..010487f --- /dev/null +++ b/hw/xio3130_downstream.h @@ -0,0 +1,11 @@ +#ifndef QEMU_XIO3130_DOWNSTREAM_H +#define QEMU_XIO3130_DOWNSTREAM_H + +#include "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/xio3130_upstream.c b/hw/xio3130_upstream.c new file mode 100644 index 0000000..a7640f5 --- /dev/null +++ b/hw/xio3130_upstream.c @@ -0,0 +1,186 @@ +/* + * xio3130_upstream.c + * TI X3130 pci express upstream port switch + * + * Copyright (c) 2010 Isaku Yamahata + * 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 . + */ + +#include "pci_ids.h" +#include "msi.h" +#include "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); + msi_write_config(d, address, val, len); + pcie_aer_write_config(d, address, val, len); +} + +static void xio3130_upstream_reset(DeviceState *qdev) +{ + PCIDevice *d = DO_UPCAST(PCIDevice, qdev, qdev); + msi_reset(d); + pci_bridge_reset(qdev); + pcie_cap_deverr_reset(d); +} + +static int xio3130_upstream_initfn(PCIDevice *d) +{ + PCIBridge* br = DO_UPCAST(PCIBridge, dev, d); + PCIEPort *p = DO_UPCAST(PCIEPort, br, br); + int rc; + int tmp; + + rc = pci_bridge_initfn(d); + if (rc < 0) { + return rc; + } + + pcie_port_init_reg(d); + pci_config_set_vendor_id(d->config, PCI_VENDOR_ID_TI); + pci_config_set_device_id(d->config, PCI_DEVICE_ID_TI_XIO3130U); + d->config[PCI_REVISION_ID] = XIO3130_REVISION; + + 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: + tmp = pci_bridge_exitfn(d); + assert(!tmp); + return rc; +} + +static int xio3130_upstream_exitfn(PCIDevice *d) +{ + pcie_aer_exit(d); + pcie_cap_exit(d); + msi_uninit(d); + return 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 = DO_UPCAST(PCIBridge, dev, d); + + qdev = &br->dev.qdev; + pci_bridge_map_irq(br, bus_name, map_irq); + qdev_prop_set_uint8(qdev, "port", port); + qdev_init_nofail(qdev); + + return DO_UPCAST(PCIEPort, br, br); +} + +static const VMStateDescription vmstate_xio3130_upstream = { + .name = "xio3130-express-upstream-port", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + VMSTATE_PCIE_DEVICE(br.dev, PCIEPort), + VMSTATE_STRUCT(br.dev.exp.aer_log, PCIEPort, 0, vmstate_pcie_aer_log, + PCIEAERLog), + VMSTATE_END_OF_LIST() + } +}; + +static PCIDeviceInfo xio3130_upstream_info = { + .qdev.name = "x3130-upstream", + .qdev.desc = "TI X3130 Upstream Port of PCI Express Switch", + .qdev.size = sizeof(PCIEPort), + .qdev.reset = xio3130_upstream_reset, + .qdev.vmsd = &vmstate_xio3130_upstream, + + .is_express = 1, + .is_bridge = 1, + .config_write = xio3130_upstream_write_config, + .init = xio3130_upstream_initfn, + .exit = xio3130_upstream_exitfn, + + .qdev.props = (Property[]) { + DEFINE_PROP_UINT8("port", PCIEPort, port, 0), + DEFINE_PROP_UINT16("aer_log_max", PCIEPort, br.dev.exp.aer_log.log_max, + PCIE_AER_LOG_MAX_DEFAULT), + DEFINE_PROP_END_OF_LIST(), + } +}; + +static void xio3130_upstream_register(void) +{ + pci_qdev_register(&xio3130_upstream_info); +} + +device_init(xio3130_upstream_register); + + +/* + * Local variables: + * c-indent-level: 4 + * c-basic-offset: 4 + * tab-width: 8 + * indent-tab-mode: nil + * End: + */ diff --git a/hw/xio3130_upstream.h b/hw/xio3130_upstream.h new file mode 100644 index 0000000..e996997 --- /dev/null +++ b/hw/xio3130_upstream.h @@ -0,0 +1,10 @@ +#ifndef QEMU_XIO3130_UPSTREAM_H +#define QEMU_XIO3130_UPSTREAM_H + +#include "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/zaurus.c b/hw/zaurus.c new file mode 100644 index 0000000..fca11a5 --- /dev/null +++ b/hw/zaurus.c @@ -0,0 +1,268 @@ +/* + * Copyright (c) 2006-2008 Openedhand Ltd. + * Written by Andrzej Zaborowski + * + * 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 . + */ +#include "hw.h" +#include "pxa.h" +#include "sharpsl.h" +#include "sysbus.h" + +#undef REG_FMT +#define REG_FMT "0x%02lx" + +/* SCOOP devices */ + +typedef struct ScoopInfo ScoopInfo; +struct ScoopInfo { + SysBusDevice busdev; + qemu_irq handler[16]; + 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 = ffs(diff) - 1; + qemu_set_irq(s->handler[bit], (level >> bit) & 1); + } + + s->prev_level = level; +} + +static uint32_t scoop_readb(void *opaque, target_phys_addr_t addr) +{ + 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_writeb(void *opaque, target_phys_addr_t addr, uint32_t value) +{ + 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 CPUReadMemoryFunc * const scoop_readfn[] = { + scoop_readb, + scoop_readb, + scoop_readb, +}; +static CPUWriteMemoryFunc * const scoop_writefn[] = { + scoop_writeb, + scoop_writeb, + scoop_writeb, +}; + +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 *dev) +{ + ScoopInfo *s = FROM_SYSBUS(ScoopInfo, dev); + int iomemtype; + + s->status = 0x02; + qdev_init_gpio_out(&s->busdev.qdev, s->handler, 16); + qdev_init_gpio_in(&s->busdev.qdev, scoop_gpio_set, 16); + iomemtype = cpu_register_io_memory(scoop_readfn, + scoop_writefn, s, DEVICE_NATIVE_ENDIAN); + + sysbus_init_mmio(dev, 0x1000, iomemtype); + + return 0; +} + +static bool is_version_0 (void *opaque, int version_id) +{ + return version_id == 0; +} + + +static const VMStateDescription vmstate_scoop_regs = { + .name = "scoop", + .version_id = 1, + .minimum_version_id = 0, + .minimum_version_id_old = 0, + .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_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 SysBusDeviceInfo scoop_sysbus_info = { + .init = scoop_init, + .qdev.name = "scoop", + .qdev.desc = "Scoop2 Sharp custom ASIC", + .qdev.size = sizeof(ScoopInfo), + .qdev.vmsd = &vmstate_scoop_regs, + .qdev.props = (Property[]) { + DEFINE_PROP_END_OF_LIST(), + } +}; + +static void scoop_register(void) +{ + sysbus_register_withprop(&scoop_sysbus_info); +} +device_init(scoop_register); + +/* Write the bootloader parameters memory area. */ + +#define MAGIC_CHG(a, b, c, d) ((d << 24) | (c << 16) | (b << 8) | a) + +static struct __attribute__ ((__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(target_phys_addr_t ptr) +{ + cpu_physical_memory_write(ptr, (void *)&zaurus_bootparam, + sizeof(struct sl_param_info)); +} diff --git a/i386-dis.c b/i386-dis.c new file mode 100644 index 0000000..c4a81c9 --- /dev/null +++ b/i386-dis.c @@ -0,0 +1,6562 @@ +/* opcodes/i386-dis.c r1.126 */ +/* Print i386 instructions for GDB, the GNU debugger. + Copyright 1988, 1989, 1991, 1993, 1994, 1995, 1996, 1997, 1998, 1999, + 2001, 2002, 2003, 2004, 2005, 2006 Free Software Foundation, Inc. + + This file is part of GDB. + + 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 . */ + +/* 80386 instruction printer by Pace Willisson (pace@prep.ai.mit.edu) + July 1988 + modified by John Hassey (hassey@dg-rtp.dg.com) + x86-64 support added by Jan Hubicka (jh@suse.cz) + VIA PadLock support by Michal Ludvig (mludvig@suse.cz). */ + +/* The main tables describing the instructions is essentially a copy + of the "Opcode Map" chapter (Appendix A) of the Intel 80386 + Programmers Manual. Usually, there is a capital letter, followed + by a small letter. The capital letter tell the addressing mode, + and the small letter tells about the operand size. Refer to + the Intel manual for details. */ + +#include +#include "dis-asm.h" +/* include/opcode/i386.h r1.78 */ + +/* opcode/i386.h -- Intel 80386 opcode macros + Copyright 1989, 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, + 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007 + Free Software Foundation, Inc. + + This file is part of GAS, the GNU Assembler, and GDB, the GNU Debugger. + + 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 . */ + +/* The SystemV/386 SVR3.2 assembler, and probably all AT&T derived + ix86 Unix assemblers, generate floating point instructions with + reversed source and destination registers in certain cases. + Unfortunately, gcc and possibly many other programs use this + reversed syntax, so we're stuck with it. + + eg. `fsub %st(3),%st' results in st = st - st(3) as expected, but + `fsub %st,%st(3)' results in st(3) = st - st(3), rather than + the expected st(3) = st(3) - st + + This happens with all the non-commutative arithmetic floating point + operations with two register operands, where the source register is + %st, and destination register is %st(i). + + The affected opcode map is dceX, dcfX, deeX, defX. */ + +#ifndef SYSV386_COMPAT +/* Set non-zero for broken, compatible instructions. Set to zero for + non-broken opcodes at your peril. gcc generates SystemV/386 + compatible instructions. */ +#define SYSV386_COMPAT 1 +#endif +#ifndef OLDGCC_COMPAT +/* Set non-zero to cater for old (<= 2.8.1) versions of gcc that could + generate nonsense fsubp, fsubrp, fdivp and fdivrp with operands + reversed. */ +#define OLDGCC_COMPAT SYSV386_COMPAT +#endif + +#define MOV_AX_DISP32 0xa0 +#define POP_SEG_SHORT 0x07 +#define JUMP_PC_RELATIVE 0xeb +#define INT_OPCODE 0xcd +#define INT3_OPCODE 0xcc +/* The opcode for the fwait instruction, which disassembler treats as a + prefix when it can. */ +#define FWAIT_OPCODE 0x9b +#define ADDR_PREFIX_OPCODE 0x67 +#define DATA_PREFIX_OPCODE 0x66 +#define LOCK_PREFIX_OPCODE 0xf0 +#define CS_PREFIX_OPCODE 0x2e +#define DS_PREFIX_OPCODE 0x3e +#define ES_PREFIX_OPCODE 0x26 +#define FS_PREFIX_OPCODE 0x64 +#define GS_PREFIX_OPCODE 0x65 +#define SS_PREFIX_OPCODE 0x36 +#define REPNE_PREFIX_OPCODE 0xf2 +#define REPE_PREFIX_OPCODE 0xf3 + +#define TWO_BYTE_OPCODE_ESCAPE 0x0f +#define NOP_OPCODE (char) 0x90 + +/* register numbers */ +#define EBP_REG_NUM 5 +#define ESP_REG_NUM 4 + +/* modrm_byte.regmem for twobyte escape */ +#define ESCAPE_TO_TWO_BYTE_ADDRESSING ESP_REG_NUM +/* index_base_byte.index for no index register addressing */ +#define NO_INDEX_REGISTER ESP_REG_NUM +/* index_base_byte.base for no base register addressing */ +#define NO_BASE_REGISTER EBP_REG_NUM +#define NO_BASE_REGISTER_16 6 + +/* modrm.mode = REGMEM_FIELD_HAS_REG when a register is in there */ +#define REGMEM_FIELD_HAS_REG 0x3/* always = 0x3 */ +#define REGMEM_FIELD_HAS_MEM (~REGMEM_FIELD_HAS_REG) + +/* x86-64 extension prefix. */ +#define REX_OPCODE 0x40 + +/* Indicates 64 bit operand size. */ +#define REX_W 8 +/* High extension to reg field of modrm byte. */ +#define REX_R 4 +/* High extension to SIB index field. */ +#define REX_X 2 +/* High extension to base field of modrm or SIB, or reg field of opcode. */ +#define REX_B 1 + +/* max operands per insn */ +#define MAX_OPERANDS 4 + +/* max immediates per insn (lcall, ljmp, insertq, extrq) */ +#define MAX_IMMEDIATE_OPERANDS 2 + +/* max memory refs per insn (string ops) */ +#define MAX_MEMORY_OPERANDS 2 + +/* max size of insn mnemonics. */ +#define MAX_MNEM_SIZE 16 + +/* max size of register name in insn mnemonics. */ +#define MAX_REG_NAME_SIZE 8 + +/* opcodes/i386-dis.c r1.126 */ +#include "qemu-common.h" + +#include + +static int fetch_data2(struct disassemble_info *, bfd_byte *); +static int fetch_data(struct disassemble_info *, bfd_byte *); +static void ckprefix (void); +static const char *prefix_name (int, int); +static int print_insn (bfd_vma, disassemble_info *); +static void dofloat (int); +static void OP_ST (int, int); +static void OP_STi (int, int); +static int putop (const char *, int); +static void oappend (const char *); +static void append_seg (void); +static void OP_indirE (int, int); +static void print_operand_value (char *buf, size_t bufsize, int hex, bfd_vma disp); +static void print_displacement (char *, bfd_vma); +static void OP_E (int, int); +static void OP_G (int, int); +static bfd_vma get64 (void); +static bfd_signed_vma get32 (void); +static bfd_signed_vma get32s (void); +static int get16 (void); +static void set_op (bfd_vma, int); +static void OP_REG (int, int); +static void OP_IMREG (int, int); +static void OP_I (int, int); +static void OP_I64 (int, int); +static void OP_sI (int, int); +static void OP_J (int, int); +static void OP_SEG (int, int); +static void OP_DIR (int, int); +static void OP_OFF (int, int); +static void OP_OFF64 (int, int); +static void ptr_reg (int, int); +static void OP_ESreg (int, int); +static void OP_DSreg (int, int); +static void OP_C (int, int); +static void OP_D (int, int); +static void OP_T (int, int); +static void OP_R (int, int); +static void OP_MMX (int, int); +static void OP_XMM (int, int); +static void OP_EM (int, int); +static void OP_EX (int, int); +static void OP_EMC (int,int); +static void OP_MXC (int,int); +static void OP_MS (int, int); +static void OP_XS (int, int); +static void OP_M (int, int); +static void OP_VMX (int, int); +static void OP_0fae (int, int); +static void OP_0f07 (int, int); +static void NOP_Fixup1 (int, int); +static void NOP_Fixup2 (int, int); +static void OP_3DNowSuffix (int, int); +static void OP_SIMD_Suffix (int, int); +static void SIMD_Fixup (int, int); +static void PNI_Fixup (int, int); +static void SVME_Fixup (int, int); +static void INVLPG_Fixup (int, int); +static void BadOp (void); +static void VMX_Fixup (int, int); +static void REP_Fixup (int, int); +static void CMPXCHG8B_Fixup (int, int); +static void XMM_Fixup (int, int); +static void CRC32_Fixup (int, int); + +struct dis_private { + /* Points to first byte not fetched. */ + bfd_byte *max_fetched; + bfd_byte the_buffer[MAX_MNEM_SIZE]; + bfd_vma insn_start; + int orig_sizeflag; + jmp_buf bailout; +}; + +enum address_mode +{ + mode_16bit, + mode_32bit, + mode_64bit +}; + +static enum address_mode address_mode; + +/* Flags for the prefixes for the current instruction. See below. */ +static int prefixes; + +/* REX prefix the current instruction. See below. */ +static int rex; +/* Bits of REX we've already used. */ +static int rex_used; +/* Mark parts used in the REX prefix. When we are testing for + empty prefix (for 8bit register REX extension), just mask it + out. Otherwise test for REX bit is excuse for existence of REX + only in case value is nonzero. */ +#define USED_REX(value) \ + { \ + if (value) \ + { \ + if ((rex & value)) \ + rex_used |= (value) | REX_OPCODE; \ + } \ + else \ + rex_used |= REX_OPCODE; \ + } + +/* Flags for prefixes which we somehow handled when printing the + current instruction. */ +static int used_prefixes; + +/* Flags stored in PREFIXES. */ +#define PREFIX_REPZ 1 +#define PREFIX_REPNZ 2 +#define PREFIX_LOCK 4 +#define PREFIX_CS 8 +#define PREFIX_SS 0x10 +#define PREFIX_DS 0x20 +#define PREFIX_ES 0x40 +#define PREFIX_FS 0x80 +#define PREFIX_GS 0x100 +#define PREFIX_DATA 0x200 +#define PREFIX_ADDR 0x400 +#define PREFIX_FWAIT 0x800 + +/* Make sure that bytes from INFO->PRIVATE_DATA->BUFFER (inclusive) + to ADDR (exclusive) are valid. Returns 1 for success, longjmps + on error. */ +static int +fetch_data2(struct disassemble_info *info, bfd_byte *addr) +{ + int status; + struct dis_private *priv = (struct dis_private *) info->private_data; + bfd_vma start = priv->insn_start + (priv->max_fetched - priv->the_buffer); + + if (addr <= priv->the_buffer + MAX_MNEM_SIZE) + status = (*info->read_memory_func) (start, + priv->max_fetched, + addr - priv->max_fetched, + info); + else + status = -1; + if (status != 0) + { + /* If we did manage to read at least one byte, then + print_insn_i386 will do something sensible. Otherwise, print + an error. We do that here because this is where we know + STATUS. */ + if (priv->max_fetched == priv->the_buffer) + (*info->memory_error_func) (status, start, info); + longjmp (priv->bailout, 1); + } + else + priv->max_fetched = addr; + return 1; +} + +static int +fetch_data(struct disassemble_info *info, bfd_byte *addr) +{ + if (addr <= ((struct dis_private *) (info->private_data))->max_fetched) { + return 1; + } else { + return fetch_data2(info, addr); + } +} + + +#define XX { NULL, 0 } + +#define Eb { OP_E, b_mode } +#define Ev { OP_E, v_mode } +#define Ed { OP_E, d_mode } +#define Edq { OP_E, dq_mode } +#define Edqw { OP_E, dqw_mode } +#define Edqb { OP_E, dqb_mode } +#define Edqd { OP_E, dqd_mode } +#define indirEv { OP_indirE, stack_v_mode } +#define indirEp { OP_indirE, f_mode } +#define stackEv { OP_E, stack_v_mode } +#define Em { OP_E, m_mode } +#define Ew { OP_E, w_mode } +#define M { OP_M, 0 } /* lea, lgdt, etc. */ +#define Ma { OP_M, v_mode } +#define Mp { OP_M, f_mode } /* 32 or 48 bit memory operand for LDS, LES etc */ +#define Mq { OP_M, q_mode } +#define Gb { OP_G, b_mode } +#define Gv { OP_G, v_mode } +#define Gd { OP_G, d_mode } +#define Gdq { OP_G, dq_mode } +#define Gm { OP_G, m_mode } +#define Gw { OP_G, w_mode } +#define Rd { OP_R, d_mode } +#define Rm { OP_R, m_mode } +#define Ib { OP_I, b_mode } +#define sIb { OP_sI, b_mode } /* sign extened byte */ +#define Iv { OP_I, v_mode } +#define Iq { OP_I, q_mode } +#define Iv64 { OP_I64, v_mode } +#define Iw { OP_I, w_mode } +#define I1 { OP_I, const_1_mode } +#define Jb { OP_J, b_mode } +#define Jv { OP_J, v_mode } +#define Cm { OP_C, m_mode } +#define Dm { OP_D, m_mode } +#define Td { OP_T, d_mode } + +#define RMeAX { OP_REG, eAX_reg } +#define RMeBX { OP_REG, eBX_reg } +#define RMeCX { OP_REG, eCX_reg } +#define RMeDX { OP_REG, eDX_reg } +#define RMeSP { OP_REG, eSP_reg } +#define RMeBP { OP_REG, eBP_reg } +#define RMeSI { OP_REG, eSI_reg } +#define RMeDI { OP_REG, eDI_reg } +#define RMrAX { OP_REG, rAX_reg } +#define RMrBX { OP_REG, rBX_reg } +#define RMrCX { OP_REG, rCX_reg } +#define RMrDX { OP_REG, rDX_reg } +#define RMrSP { OP_REG, rSP_reg } +#define RMrBP { OP_REG, rBP_reg } +#define RMrSI { OP_REG, rSI_reg } +#define RMrDI { OP_REG, rDI_reg } +#define RMAL { OP_REG, al_reg } +#define RMAL { OP_REG, al_reg } +#define RMCL { OP_REG, cl_reg } +#define RMDL { OP_REG, dl_reg } +#define RMBL { OP_REG, bl_reg } +#define RMAH { OP_REG, ah_reg } +#define RMCH { OP_REG, ch_reg } +#define RMDH { OP_REG, dh_reg } +#define RMBH { OP_REG, bh_reg } +#define RMAX { OP_REG, ax_reg } +#define RMDX { OP_REG, dx_reg } + +#define eAX { OP_IMREG, eAX_reg } +#define eBX { OP_IMREG, eBX_reg } +#define eCX { OP_IMREG, eCX_reg } +#define eDX { OP_IMREG, eDX_reg } +#define eSP { OP_IMREG, eSP_reg } +#define eBP { OP_IMREG, eBP_reg } +#define eSI { OP_IMREG, eSI_reg } +#define eDI { OP_IMREG, eDI_reg } +#define AL { OP_IMREG, al_reg } +#define CL { OP_IMREG, cl_reg } +#define DL { OP_IMREG, dl_reg } +#define BL { OP_IMREG, bl_reg } +#define AH { OP_IMREG, ah_reg } +#define CH { OP_IMREG, ch_reg } +#define DH { OP_IMREG, dh_reg } +#define BH { OP_IMREG, bh_reg } +#define AX { OP_IMREG, ax_reg } +#define DX { OP_IMREG, dx_reg } +#define zAX { OP_IMREG, z_mode_ax_reg } +#define indirDX { OP_IMREG, indir_dx_reg } + +#define Sw { OP_SEG, w_mode } +#define Sv { OP_SEG, v_mode } +#define Ap { OP_DIR, 0 } +#define Ob { OP_OFF64, b_mode } +#define Ov { OP_OFF64, v_mode } +#define Xb { OP_DSreg, eSI_reg } +#define Xv { OP_DSreg, eSI_reg } +#define Xz { OP_DSreg, eSI_reg } +#define Yb { OP_ESreg, eDI_reg } +#define Yv { OP_ESreg, eDI_reg } +#define DSBX { OP_DSreg, eBX_reg } + +#define es { OP_REG, es_reg } +#define ss { OP_REG, ss_reg } +#define cs { OP_REG, cs_reg } +#define ds { OP_REG, ds_reg } +#define fs { OP_REG, fs_reg } +#define gs { OP_REG, gs_reg } + +#define MX { OP_MMX, 0 } +#define XM { OP_XMM, 0 } +#define EM { OP_EM, v_mode } +#define EMd { OP_EM, d_mode } +#define EMq { OP_EM, q_mode } +#define EXd { OP_EX, d_mode } +#define EXq { OP_EX, q_mode } +#define EXx { OP_EX, x_mode } +#define MS { OP_MS, v_mode } +#define XS { OP_XS, v_mode } +#define EMC { OP_EMC, v_mode } +#define MXC { OP_MXC, 0 } +#define VM { OP_VMX, q_mode } +#define OPSUF { OP_3DNowSuffix, 0 } +#define OPSIMD { OP_SIMD_Suffix, 0 } +#define XMM0 { XMM_Fixup, 0 } + +/* Used handle "rep" prefix for string instructions. */ +#define Xbr { REP_Fixup, eSI_reg } +#define Xvr { REP_Fixup, eSI_reg } +#define Ybr { REP_Fixup, eDI_reg } +#define Yvr { REP_Fixup, eDI_reg } +#define Yzr { REP_Fixup, eDI_reg } +#define indirDXr { REP_Fixup, indir_dx_reg } +#define ALr { REP_Fixup, al_reg } +#define eAXr { REP_Fixup, eAX_reg } + +#define cond_jump_flag { NULL, cond_jump_mode } +#define loop_jcxz_flag { NULL, loop_jcxz_mode } + +/* bits in sizeflag */ +#define SUFFIX_ALWAYS 4 +#define AFLAG 2 +#define DFLAG 1 + +#define b_mode 1 /* byte operand */ +#define v_mode 2 /* operand size depends on prefixes */ +#define w_mode 3 /* word operand */ +#define d_mode 4 /* double word operand */ +#define q_mode 5 /* quad word operand */ +#define t_mode 6 /* ten-byte operand */ +#define x_mode 7 /* 16-byte XMM operand */ +#define m_mode 8 /* d_mode in 32bit, q_mode in 64bit mode. */ +#define cond_jump_mode 9 +#define loop_jcxz_mode 10 +#define dq_mode 11 /* operand size depends on REX prefixes. */ +#define dqw_mode 12 /* registers like dq_mode, memory like w_mode. */ +#define f_mode 13 /* 4- or 6-byte pointer operand */ +#define const_1_mode 14 +#define stack_v_mode 15 /* v_mode for stack-related opcodes. */ +#define z_mode 16 /* non-quad operand size depends on prefixes */ +#define o_mode 17 /* 16-byte operand */ +#define dqb_mode 18 /* registers like dq_mode, memory like b_mode. */ +#define dqd_mode 19 /* registers like dq_mode, memory like d_mode. */ + +#define es_reg 100 +#define cs_reg 101 +#define ss_reg 102 +#define ds_reg 103 +#define fs_reg 104 +#define gs_reg 105 + +#define eAX_reg 108 +#define eCX_reg 109 +#define eDX_reg 110 +#define eBX_reg 111 +#define eSP_reg 112 +#define eBP_reg 113 +#define eSI_reg 114 +#define eDI_reg 115 + +#define al_reg 116 +#define cl_reg 117 +#define dl_reg 118 +#define bl_reg 119 +#define ah_reg 120 +#define ch_reg 121 +#define dh_reg 122 +#define bh_reg 123 + +#define ax_reg 124 +#define cx_reg 125 +#define dx_reg 126 +#define bx_reg 127 +#define sp_reg 128 +#define bp_reg 129 +#define si_reg 130 +#define di_reg 131 + +#define rAX_reg 132 +#define rCX_reg 133 +#define rDX_reg 134 +#define rBX_reg 135 +#define rSP_reg 136 +#define rBP_reg 137 +#define rSI_reg 138 +#define rDI_reg 139 + +#define z_mode_ax_reg 149 +#define indir_dx_reg 150 + +#define FLOATCODE 1 +#define USE_GROUPS 2 +#define USE_PREFIX_USER_TABLE 3 +#define X86_64_SPECIAL 4 +#define IS_3BYTE_OPCODE 5 + +#define FLOAT NULL, { { NULL, FLOATCODE } } + +#define GRP1a NULL, { { NULL, USE_GROUPS }, { NULL, 0 } } +#define GRP1b NULL, { { NULL, USE_GROUPS }, { NULL, 1 } } +#define GRP1S NULL, { { NULL, USE_GROUPS }, { NULL, 2 } } +#define GRP1Ss NULL, { { NULL, USE_GROUPS }, { NULL, 3 } } +#define GRP2b NULL, { { NULL, USE_GROUPS }, { NULL, 4 } } +#define GRP2S NULL, { { NULL, USE_GROUPS }, { NULL, 5 } } +#define GRP2b_one NULL, { { NULL, USE_GROUPS }, { NULL, 6 } } +#define GRP2S_one NULL, { { NULL, USE_GROUPS }, { NULL, 7 } } +#define GRP2b_cl NULL, { { NULL, USE_GROUPS }, { NULL, 8 } } +#define GRP2S_cl NULL, { { NULL, USE_GROUPS }, { NULL, 9 } } +#define GRP3b NULL, { { NULL, USE_GROUPS }, { NULL, 10 } } +#define GRP3S NULL, { { NULL, USE_GROUPS }, { NULL, 11 } } +#define GRP4 NULL, { { NULL, USE_GROUPS }, { NULL, 12 } } +#define GRP5 NULL, { { NULL, USE_GROUPS }, { NULL, 13 } } +#define GRP6 NULL, { { NULL, USE_GROUPS }, { NULL, 14 } } +#define GRP7 NULL, { { NULL, USE_GROUPS }, { NULL, 15 } } +#define GRP8 NULL, { { NULL, USE_GROUPS }, { NULL, 16 } } +#define GRP9 NULL, { { NULL, USE_GROUPS }, { NULL, 17 } } +#define GRP11_C6 NULL, { { NULL, USE_GROUPS }, { NULL, 18 } } +#define GRP11_C7 NULL, { { NULL, USE_GROUPS }, { NULL, 19 } } +#define GRP12 NULL, { { NULL, USE_GROUPS }, { NULL, 20 } } +#define GRP13 NULL, { { NULL, USE_GROUPS }, { NULL, 21 } } +#define GRP14 NULL, { { NULL, USE_GROUPS }, { NULL, 22 } } +#define GRP15 NULL, { { NULL, USE_GROUPS }, { NULL, 23 } } +#define GRP16 NULL, { { NULL, USE_GROUPS }, { NULL, 24 } } +#define GRPAMD NULL, { { NULL, USE_GROUPS }, { NULL, 25 } } +#define GRPPADLCK1 NULL, { { NULL, USE_GROUPS }, { NULL, 26 } } +#define GRPPADLCK2 NULL, { { NULL, USE_GROUPS }, { NULL, 27 } } + +#define PREGRP0 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 0 } } +#define PREGRP1 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 1 } } +#define PREGRP2 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 2 } } +#define PREGRP3 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 3 } } +#define PREGRP4 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 4 } } +#define PREGRP5 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 5 } } +#define PREGRP6 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 6 } } +#define PREGRP7 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 7 } } +#define PREGRP8 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 8 } } +#define PREGRP9 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 9 } } +#define PREGRP10 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 10 } } +#define PREGRP11 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 11 } } +#define PREGRP12 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 12 } } +#define PREGRP13 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 13 } } +#define PREGRP14 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 14 } } +#define PREGRP15 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 15 } } +#define PREGRP16 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 16 } } +#define PREGRP17 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 17 } } +#define PREGRP18 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 18 } } +#define PREGRP19 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 19 } } +#define PREGRP20 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 20 } } +#define PREGRP21 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 21 } } +#define PREGRP22 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 22 } } +#define PREGRP23 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 23 } } +#define PREGRP24 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 24 } } +#define PREGRP25 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 25 } } +#define PREGRP26 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 26 } } +#define PREGRP27 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 27 } } +#define PREGRP28 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 28 } } +#define PREGRP29 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 29 } } +#define PREGRP30 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 30 } } +#define PREGRP31 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 31 } } +#define PREGRP32 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 32 } } +#define PREGRP33 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 33 } } +#define PREGRP34 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 34 } } +#define PREGRP35 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 35 } } +#define PREGRP36 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 36 } } +#define PREGRP37 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 37 } } +#define PREGRP38 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 38 } } +#define PREGRP39 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 39 } } +#define PREGRP40 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 40 } } +#define PREGRP41 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 41 } } +#define PREGRP42 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 42 } } +#define PREGRP43 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 43 } } +#define PREGRP44 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 44 } } +#define PREGRP45 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 45 } } +#define PREGRP46 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 46 } } +#define PREGRP47 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 47 } } +#define PREGRP48 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 48 } } +#define PREGRP49 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 49 } } +#define PREGRP50 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 50 } } +#define PREGRP51 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 51 } } +#define PREGRP52 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 52 } } +#define PREGRP53 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 53 } } +#define PREGRP54 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 54 } } +#define PREGRP55 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 55 } } +#define PREGRP56 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 56 } } +#define PREGRP57 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 57 } } +#define PREGRP58 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 58 } } +#define PREGRP59 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 59 } } +#define PREGRP60 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 60 } } +#define PREGRP61 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 61 } } +#define PREGRP62 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 62 } } +#define PREGRP63 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 63 } } +#define PREGRP64 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 64 } } +#define PREGRP65 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 65 } } +#define PREGRP66 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 66 } } +#define PREGRP67 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 67 } } +#define PREGRP68 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 68 } } +#define PREGRP69 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 69 } } +#define PREGRP70 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 70 } } +#define PREGRP71 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 71 } } +#define PREGRP72 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 72 } } +#define PREGRP73 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 73 } } +#define PREGRP74 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 74 } } +#define PREGRP75 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 75 } } +#define PREGRP76 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 76 } } +#define PREGRP77 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 77 } } +#define PREGRP78 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 78 } } +#define PREGRP79 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 79 } } +#define PREGRP80 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 80 } } +#define PREGRP81 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 81 } } +#define PREGRP82 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 82 } } +#define PREGRP83 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 83 } } +#define PREGRP84 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 84 } } +#define PREGRP85 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 85 } } +#define PREGRP86 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 86 } } +#define PREGRP87 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 87 } } +#define PREGRP88 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 88 } } +#define PREGRP89 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 89 } } +#define PREGRP90 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 90 } } +#define PREGRP91 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 91 } } +#define PREGRP92 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 92 } } +#define PREGRP93 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 93 } } +#define PREGRP94 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 94 } } +#define PREGRP95 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 95 } } +#define PREGRP96 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 96 } } +#define PREGRP97 NULL, { { NULL, USE_PREFIX_USER_TABLE }, { NULL, 97 } } + + +#define X86_64_0 NULL, { { NULL, X86_64_SPECIAL }, { NULL, 0 } } +#define X86_64_1 NULL, { { NULL, X86_64_SPECIAL }, { NULL, 1 } } +#define X86_64_2 NULL, { { NULL, X86_64_SPECIAL }, { NULL, 2 } } +#define X86_64_3 NULL, { { NULL, X86_64_SPECIAL }, { NULL, 3 } } + +#define THREE_BYTE_0 NULL, { { NULL, IS_3BYTE_OPCODE }, { NULL, 0 } } +#define THREE_BYTE_1 NULL, { { NULL, IS_3BYTE_OPCODE }, { NULL, 1 } } + +typedef void (*op_rtn) (int bytemode, int sizeflag); + +struct dis386 { + const char *name; + struct + { + op_rtn rtn; + int bytemode; + } op[MAX_OPERANDS]; +}; + +/* Upper case letters in the instruction names here are macros. + 'A' => print 'b' if no register operands or suffix_always is true + 'B' => print 'b' if suffix_always is true + 'C' => print 's' or 'l' ('w' or 'd' in Intel mode) depending on operand + . size prefix + 'D' => print 'w' if no register operands or 'w', 'l' or 'q', if + . suffix_always is true + 'E' => print 'e' if 32-bit form of jcxz + 'F' => print 'w' or 'l' depending on address size prefix (loop insns) + 'G' => print 'w' or 'l' depending on operand size prefix (i/o insns) + 'H' => print ",pt" or ",pn" branch hint + 'I' => honor following macro letter even in Intel mode (implemented only + . for some of the macro letters) + 'J' => print 'l' + 'K' => print 'd' or 'q' if rex prefix is present. + 'L' => print 'l' if suffix_always is true + 'N' => print 'n' if instruction has no wait "prefix" + 'O' => print 'd' or 'o' (or 'q' in Intel mode) + 'P' => print 'w', 'l' or 'q' if instruction has an operand size prefix, + . or suffix_always is true. print 'q' if rex prefix is present. + 'Q' => print 'w', 'l' or 'q' if no register operands or suffix_always + . is true + 'R' => print 'w', 'l' or 'q' ('d' for 'l' and 'e' in Intel mode) + 'S' => print 'w', 'l' or 'q' if suffix_always is true + 'T' => print 'q' in 64bit mode and behave as 'P' otherwise + 'U' => print 'q' in 64bit mode and behave as 'Q' otherwise + 'V' => print 'q' in 64bit mode and behave as 'S' otherwise + 'W' => print 'b', 'w' or 'l' ('d' in Intel mode) + 'X' => print 's', 'd' depending on data16 prefix (for XMM) + 'Y' => 'q' if instruction has an REX 64bit overwrite prefix + 'Z' => print 'q' in 64bit mode and behave as 'L' otherwise + + Many of the above letters print nothing in Intel mode. See "putop" + for the details. + + Braces '{' and '}', and vertical bars '|', indicate alternative + mnemonic strings for AT&T, Intel, X86_64 AT&T, and X86_64 Intel + modes. In cases where there are only two alternatives, the X86_64 + instruction is reserved, and "(bad)" is printed. +*/ + +static const struct dis386 dis386[] = { + /* 00 */ + { "addB", { Eb, Gb } }, + { "addS", { Ev, Gv } }, + { "addB", { Gb, Eb } }, + { "addS", { Gv, Ev } }, + { "addB", { AL, Ib } }, + { "addS", { eAX, Iv } }, + { "push{T|}", { es } }, + { "pop{T|}", { es } }, + /* 08 */ + { "orB", { Eb, Gb } }, + { "orS", { Ev, Gv } }, + { "orB", { Gb, Eb } }, + { "orS", { Gv, Ev } }, + { "orB", { AL, Ib } }, + { "orS", { eAX, Iv } }, + { "push{T|}", { cs } }, + { "(bad)", { XX } }, /* 0x0f extended opcode escape */ + /* 10 */ + { "adcB", { Eb, Gb } }, + { "adcS", { Ev, Gv } }, + { "adcB", { Gb, Eb } }, + { "adcS", { Gv, Ev } }, + { "adcB", { AL, Ib } }, + { "adcS", { eAX, Iv } }, + { "push{T|}", { ss } }, + { "pop{T|}", { ss } }, + /* 18 */ + { "sbbB", { Eb, Gb } }, + { "sbbS", { Ev, Gv } }, + { "sbbB", { Gb, Eb } }, + { "sbbS", { Gv, Ev } }, + { "sbbB", { AL, Ib } }, + { "sbbS", { eAX, Iv } }, + { "push{T|}", { ds } }, + { "pop{T|}", { ds } }, + /* 20 */ + { "andB", { Eb, Gb } }, + { "andS", { Ev, Gv } }, + { "andB", { Gb, Eb } }, + { "andS", { Gv, Ev } }, + { "andB", { AL, Ib } }, + { "andS", { eAX, Iv } }, + { "(bad)", { XX } }, /* SEG ES prefix */ + { "daa{|}", { XX } }, + /* 28 */ + { "subB", { Eb, Gb } }, + { "subS", { Ev, Gv } }, + { "subB", { Gb, Eb } }, + { "subS", { Gv, Ev } }, + { "subB", { AL, Ib } }, + { "subS", { eAX, Iv } }, + { "(bad)", { XX } }, /* SEG CS prefix */ + { "das{|}", { XX } }, + /* 30 */ + { "xorB", { Eb, Gb } }, + { "xorS", { Ev, Gv } }, + { "xorB", { Gb, Eb } }, + { "xorS", { Gv, Ev } }, + { "xorB", { AL, Ib } }, + { "xorS", { eAX, Iv } }, + { "(bad)", { XX } }, /* SEG SS prefix */ + { "aaa{|}", { XX } }, + /* 38 */ + { "cmpB", { Eb, Gb } }, + { "cmpS", { Ev, Gv } }, + { "cmpB", { Gb, Eb } }, + { "cmpS", { Gv, Ev } }, + { "cmpB", { AL, Ib } }, + { "cmpS", { eAX, Iv } }, + { "(bad)", { XX } }, /* SEG DS prefix */ + { "aas{|}", { XX } }, + /* 40 */ + { "inc{S|}", { RMeAX } }, + { "inc{S|}", { RMeCX } }, + { "inc{S|}", { RMeDX } }, + { "inc{S|}", { RMeBX } }, + { "inc{S|}", { RMeSP } }, + { "inc{S|}", { RMeBP } }, + { "inc{S|}", { RMeSI } }, + { "inc{S|}", { RMeDI } }, + /* 48 */ + { "dec{S|}", { RMeAX } }, + { "dec{S|}", { RMeCX } }, + { "dec{S|}", { RMeDX } }, + { "dec{S|}", { RMeBX } }, + { "dec{S|}", { RMeSP } }, + { "dec{S|}", { RMeBP } }, + { "dec{S|}", { RMeSI } }, + { "dec{S|}", { RMeDI } }, + /* 50 */ + { "pushV", { RMrAX } }, + { "pushV", { RMrCX } }, + { "pushV", { RMrDX } }, + { "pushV", { RMrBX } }, + { "pushV", { RMrSP } }, + { "pushV", { RMrBP } }, + { "pushV", { RMrSI } }, + { "pushV", { RMrDI } }, + /* 58 */ + { "popV", { RMrAX } }, + { "popV", { RMrCX } }, + { "popV", { RMrDX } }, + { "popV", { RMrBX } }, + { "popV", { RMrSP } }, + { "popV", { RMrBP } }, + { "popV", { RMrSI } }, + { "popV", { RMrDI } }, + /* 60 */ + { X86_64_0 }, + { X86_64_1 }, + { X86_64_2 }, + { X86_64_3 }, + { "(bad)", { XX } }, /* seg fs */ + { "(bad)", { XX } }, /* seg gs */ + { "(bad)", { XX } }, /* op size prefix */ + { "(bad)", { XX } }, /* adr size prefix */ + /* 68 */ + { "pushT", { Iq } }, + { "imulS", { Gv, Ev, Iv } }, + { "pushT", { sIb } }, + { "imulS", { Gv, Ev, sIb } }, + { "ins{b||b|}", { Ybr, indirDX } }, + { "ins{R||G|}", { Yzr, indirDX } }, + { "outs{b||b|}", { indirDXr, Xb } }, + { "outs{R||G|}", { indirDXr, Xz } }, + /* 70 */ + { "joH", { Jb, XX, cond_jump_flag } }, + { "jnoH", { Jb, XX, cond_jump_flag } }, + { "jbH", { Jb, XX, cond_jump_flag } }, + { "jaeH", { Jb, XX, cond_jump_flag } }, + { "jeH", { Jb, XX, cond_jump_flag } }, + { "jneH", { Jb, XX, cond_jump_flag } }, + { "jbeH", { Jb, XX, cond_jump_flag } }, + { "jaH", { Jb, XX, cond_jump_flag } }, + /* 78 */ + { "jsH", { Jb, XX, cond_jump_flag } }, + { "jnsH", { Jb, XX, cond_jump_flag } }, + { "jpH", { Jb, XX, cond_jump_flag } }, + { "jnpH", { Jb, XX, cond_jump_flag } }, + { "jlH", { Jb, XX, cond_jump_flag } }, + { "jgeH", { Jb, XX, cond_jump_flag } }, + { "jleH", { Jb, XX, cond_jump_flag } }, + { "jgH", { Jb, XX, cond_jump_flag } }, + /* 80 */ + { GRP1b }, + { GRP1S }, + { "(bad)", { XX } }, + { GRP1Ss }, + { "testB", { Eb, Gb } }, + { "testS", { Ev, Gv } }, + { "xchgB", { Eb, Gb } }, + { "xchgS", { Ev, Gv } }, + /* 88 */ + { "movB", { Eb, Gb } }, + { "movS", { Ev, Gv } }, + { "movB", { Gb, Eb } }, + { "movS", { Gv, Ev } }, + { "movD", { Sv, Sw } }, + { "leaS", { Gv, M } }, + { "movD", { Sw, Sv } }, + { GRP1a }, + /* 90 */ + { PREGRP38 }, + { "xchgS", { RMeCX, eAX } }, + { "xchgS", { RMeDX, eAX } }, + { "xchgS", { RMeBX, eAX } }, + { "xchgS", { RMeSP, eAX } }, + { "xchgS", { RMeBP, eAX } }, + { "xchgS", { RMeSI, eAX } }, + { "xchgS", { RMeDI, eAX } }, + /* 98 */ + { "cW{t||t|}R", { XX } }, + { "cR{t||t|}O", { XX } }, + { "Jcall{T|}", { Ap } }, + { "(bad)", { XX } }, /* fwait */ + { "pushfT", { XX } }, + { "popfT", { XX } }, + { "sahf{|}", { XX } }, + { "lahf{|}", { XX } }, + /* a0 */ + { "movB", { AL, Ob } }, + { "movS", { eAX, Ov } }, + { "movB", { Ob, AL } }, + { "movS", { Ov, eAX } }, + { "movs{b||b|}", { Ybr, Xb } }, + { "movs{R||R|}", { Yvr, Xv } }, + { "cmps{b||b|}", { Xb, Yb } }, + { "cmps{R||R|}", { Xv, Yv } }, + /* a8 */ + { "testB", { AL, Ib } }, + { "testS", { eAX, Iv } }, + { "stosB", { Ybr, AL } }, + { "stosS", { Yvr, eAX } }, + { "lodsB", { ALr, Xb } }, + { "lodsS", { eAXr, Xv } }, + { "scasB", { AL, Yb } }, + { "scasS", { eAX, Yv } }, + /* b0 */ + { "movB", { RMAL, Ib } }, + { "movB", { RMCL, Ib } }, + { "movB", { RMDL, Ib } }, + { "movB", { RMBL, Ib } }, + { "movB", { RMAH, Ib } }, + { "movB", { RMCH, Ib } }, + { "movB", { RMDH, Ib } }, + { "movB", { RMBH, Ib } }, + /* b8 */ + { "movS", { RMeAX, Iv64 } }, + { "movS", { RMeCX, Iv64 } }, + { "movS", { RMeDX, Iv64 } }, + { "movS", { RMeBX, Iv64 } }, + { "movS", { RMeSP, Iv64 } }, + { "movS", { RMeBP, Iv64 } }, + { "movS", { RMeSI, Iv64 } }, + { "movS", { RMeDI, Iv64 } }, + /* c0 */ + { GRP2b }, + { GRP2S }, + { "retT", { Iw } }, + { "retT", { XX } }, + { "les{S|}", { Gv, Mp } }, + { "ldsS", { Gv, Mp } }, + { GRP11_C6 }, + { GRP11_C7 }, + /* c8 */ + { "enterT", { Iw, Ib } }, + { "leaveT", { XX } }, + { "lretP", { Iw } }, + { "lretP", { XX } }, + { "int3", { XX } }, + { "int", { Ib } }, + { "into{|}", { XX } }, + { "iretP", { XX } }, + /* d0 */ + { GRP2b_one }, + { GRP2S_one }, + { GRP2b_cl }, + { GRP2S_cl }, + { "aam{|}", { sIb } }, + { "aad{|}", { sIb } }, + { "(bad)", { XX } }, + { "xlat", { DSBX } }, + /* d8 */ + { FLOAT }, + { FLOAT }, + { FLOAT }, + { FLOAT }, + { FLOAT }, + { FLOAT }, + { FLOAT }, + { FLOAT }, + /* e0 */ + { "loopneFH", { Jb, XX, loop_jcxz_flag } }, + { "loopeFH", { Jb, XX, loop_jcxz_flag } }, + { "loopFH", { Jb, XX, loop_jcxz_flag } }, + { "jEcxzH", { Jb, XX, loop_jcxz_flag } }, + { "inB", { AL, Ib } }, + { "inG", { zAX, Ib } }, + { "outB", { Ib, AL } }, + { "outG", { Ib, zAX } }, + /* e8 */ + { "callT", { Jv } }, + { "jmpT", { Jv } }, + { "Jjmp{T|}", { Ap } }, + { "jmp", { Jb } }, + { "inB", { AL, indirDX } }, + { "inG", { zAX, indirDX } }, + { "outB", { indirDX, AL } }, + { "outG", { indirDX, zAX } }, + /* f0 */ + { "(bad)", { XX } }, /* lock prefix */ + { "icebp", { XX } }, + { "(bad)", { XX } }, /* repne */ + { "(bad)", { XX } }, /* repz */ + { "hlt", { XX } }, + { "cmc", { XX } }, + { GRP3b }, + { GRP3S }, + /* f8 */ + { "clc", { XX } }, + { "stc", { XX } }, + { "cli", { XX } }, + { "sti", { XX } }, + { "cld", { XX } }, + { "std", { XX } }, + { GRP4 }, + { GRP5 }, +}; + +static const struct dis386 dis386_twobyte[] = { + /* 00 */ + { GRP6 }, + { GRP7 }, + { "larS", { Gv, Ew } }, + { "lslS", { Gv, Ew } }, + { "(bad)", { XX } }, + { "syscall", { XX } }, + { "clts", { XX } }, + { "sysretP", { XX } }, + /* 08 */ + { "invd", { XX } }, + { "wbinvd", { XX } }, + { "(bad)", { XX } }, + { "ud2a", { XX } }, + { "(bad)", { XX } }, + { GRPAMD }, + { "femms", { XX } }, + { "", { MX, EM, OPSUF } }, /* See OP_3DNowSuffix. */ + /* 10 */ + { PREGRP8 }, + { PREGRP9 }, + { PREGRP30 }, + { "movlpX", { EXq, XM, { SIMD_Fixup, 'h' } } }, + { "unpcklpX", { XM, EXq } }, + { "unpckhpX", { XM, EXq } }, + { PREGRP31 }, + { "movhpX", { EXq, XM, { SIMD_Fixup, 'l' } } }, + /* 18 */ + { GRP16 }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "nopQ", { Ev } }, + /* 20 */ + { "movZ", { Rm, Cm } }, + { "movZ", { Rm, Dm } }, + { "movZ", { Cm, Rm } }, + { "movZ", { Dm, Rm } }, + { "movL", { Rd, Td } }, + { "(bad)", { XX } }, + { "movL", { Td, Rd } }, + { "(bad)", { XX } }, + /* 28 */ + { "movapX", { XM, EXx } }, + { "movapX", { EXx, XM } }, + { PREGRP2 }, + { PREGRP33 }, + { PREGRP4 }, + { PREGRP3 }, + { PREGRP93 }, + { PREGRP94 }, + /* 30 */ + { "wrmsr", { XX } }, + { "rdtsc", { XX } }, + { "rdmsr", { XX } }, + { "rdpmc", { XX } }, + { "sysenter", { XX } }, + { "sysexit", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + /* 38 */ + { THREE_BYTE_0 }, + { "(bad)", { XX } }, + { THREE_BYTE_1 }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + /* 40 */ + { "cmovo", { Gv, Ev } }, + { "cmovno", { Gv, Ev } }, + { "cmovb", { Gv, Ev } }, + { "cmovae", { Gv, Ev } }, + { "cmove", { Gv, Ev } }, + { "cmovne", { Gv, Ev } }, + { "cmovbe", { Gv, Ev } }, + { "cmova", { Gv, Ev } }, + /* 48 */ + { "cmovs", { Gv, Ev } }, + { "cmovns", { Gv, Ev } }, + { "cmovp", { Gv, Ev } }, + { "cmovnp", { Gv, Ev } }, + { "cmovl", { Gv, Ev } }, + { "cmovge", { Gv, Ev } }, + { "cmovle", { Gv, Ev } }, + { "cmovg", { Gv, Ev } }, + /* 50 */ + { "movmskpX", { Gdq, XS } }, + { PREGRP13 }, + { PREGRP12 }, + { PREGRP11 }, + { "andpX", { XM, EXx } }, + { "andnpX", { XM, EXx } }, + { "orpX", { XM, EXx } }, + { "xorpX", { XM, EXx } }, + /* 58 */ + { PREGRP0 }, + { PREGRP10 }, + { PREGRP17 }, + { PREGRP16 }, + { PREGRP14 }, + { PREGRP7 }, + { PREGRP5 }, + { PREGRP6 }, + /* 60 */ + { PREGRP95 }, + { PREGRP96 }, + { PREGRP97 }, + { "packsswb", { MX, EM } }, + { "pcmpgtb", { MX, EM } }, + { "pcmpgtw", { MX, EM } }, + { "pcmpgtd", { MX, EM } }, + { "packuswb", { MX, EM } }, + /* 68 */ + { "punpckhbw", { MX, EM } }, + { "punpckhwd", { MX, EM } }, + { "punpckhdq", { MX, EM } }, + { "packssdw", { MX, EM } }, + { PREGRP26 }, + { PREGRP24 }, + { "movd", { MX, Edq } }, + { PREGRP19 }, + /* 70 */ + { PREGRP22 }, + { GRP12 }, + { GRP13 }, + { GRP14 }, + { "pcmpeqb", { MX, EM } }, + { "pcmpeqw", { MX, EM } }, + { "pcmpeqd", { MX, EM } }, + { "emms", { XX } }, + /* 78 */ + { PREGRP34 }, + { PREGRP35 }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { PREGRP28 }, + { PREGRP29 }, + { PREGRP23 }, + { PREGRP20 }, + /* 80 */ + { "joH", { Jv, XX, cond_jump_flag } }, + { "jnoH", { Jv, XX, cond_jump_flag } }, + { "jbH", { Jv, XX, cond_jump_flag } }, + { "jaeH", { Jv, XX, cond_jump_flag } }, + { "jeH", { Jv, XX, cond_jump_flag } }, + { "jneH", { Jv, XX, cond_jump_flag } }, + { "jbeH", { Jv, XX, cond_jump_flag } }, + { "jaH", { Jv, XX, cond_jump_flag } }, + /* 88 */ + { "jsH", { Jv, XX, cond_jump_flag } }, + { "jnsH", { Jv, XX, cond_jump_flag } }, + { "jpH", { Jv, XX, cond_jump_flag } }, + { "jnpH", { Jv, XX, cond_jump_flag } }, + { "jlH", { Jv, XX, cond_jump_flag } }, + { "jgeH", { Jv, XX, cond_jump_flag } }, + { "jleH", { Jv, XX, cond_jump_flag } }, + { "jgH", { Jv, XX, cond_jump_flag } }, + /* 90 */ + { "seto", { Eb } }, + { "setno", { Eb } }, + { "setb", { Eb } }, + { "setae", { Eb } }, + { "sete", { Eb } }, + { "setne", { Eb } }, + { "setbe", { Eb } }, + { "seta", { Eb } }, + /* 98 */ + { "sets", { Eb } }, + { "setns", { Eb } }, + { "setp", { Eb } }, + { "setnp", { Eb } }, + { "setl", { Eb } }, + { "setge", { Eb } }, + { "setle", { Eb } }, + { "setg", { Eb } }, + /* a0 */ + { "pushT", { fs } }, + { "popT", { fs } }, + { "cpuid", { XX } }, + { "btS", { Ev, Gv } }, + { "shldS", { Ev, Gv, Ib } }, + { "shldS", { Ev, Gv, CL } }, + { GRPPADLCK2 }, + { GRPPADLCK1 }, + /* a8 */ + { "pushT", { gs } }, + { "popT", { gs } }, + { "rsm", { XX } }, + { "btsS", { Ev, Gv } }, + { "shrdS", { Ev, Gv, Ib } }, + { "shrdS", { Ev, Gv, CL } }, + { GRP15 }, + { "imulS", { Gv, Ev } }, + /* b0 */ + { "cmpxchgB", { Eb, Gb } }, + { "cmpxchgS", { Ev, Gv } }, + { "lssS", { Gv, Mp } }, + { "btrS", { Ev, Gv } }, + { "lfsS", { Gv, Mp } }, + { "lgsS", { Gv, Mp } }, + { "movz{bR|x|bR|x}", { Gv, Eb } }, + { "movz{wR|x|wR|x}", { Gv, Ew } }, /* yes, there really is movzww ! */ + /* b8 */ + { PREGRP37 }, + { "ud2b", { XX } }, + { GRP8 }, + { "btcS", { Ev, Gv } }, + { "bsfS", { Gv, Ev } }, + { PREGRP36 }, + { "movs{bR|x|bR|x}", { Gv, Eb } }, + { "movs{wR|x|wR|x}", { Gv, Ew } }, /* yes, there really is movsww ! */ + /* c0 */ + { "xaddB", { Eb, Gb } }, + { "xaddS", { Ev, Gv } }, + { PREGRP1 }, + { "movntiS", { Ev, Gv } }, + { "pinsrw", { MX, Edqw, Ib } }, + { "pextrw", { Gdq, MS, Ib } }, + { "shufpX", { XM, EXx, Ib } }, + { GRP9 }, + /* c8 */ + { "bswap", { RMeAX } }, + { "bswap", { RMeCX } }, + { "bswap", { RMeDX } }, + { "bswap", { RMeBX } }, + { "bswap", { RMeSP } }, + { "bswap", { RMeBP } }, + { "bswap", { RMeSI } }, + { "bswap", { RMeDI } }, + /* d0 */ + { PREGRP27 }, + { "psrlw", { MX, EM } }, + { "psrld", { MX, EM } }, + { "psrlq", { MX, EM } }, + { "paddq", { MX, EM } }, + { "pmullw", { MX, EM } }, + { PREGRP21 }, + { "pmovmskb", { Gdq, MS } }, + /* d8 */ + { "psubusb", { MX, EM } }, + { "psubusw", { MX, EM } }, + { "pminub", { MX, EM } }, + { "pand", { MX, EM } }, + { "paddusb", { MX, EM } }, + { "paddusw", { MX, EM } }, + { "pmaxub", { MX, EM } }, + { "pandn", { MX, EM } }, + /* e0 */ + { "pavgb", { MX, EM } }, + { "psraw", { MX, EM } }, + { "psrad", { MX, EM } }, + { "pavgw", { MX, EM } }, + { "pmulhuw", { MX, EM } }, + { "pmulhw", { MX, EM } }, + { PREGRP15 }, + { PREGRP25 }, + /* e8 */ + { "psubsb", { MX, EM } }, + { "psubsw", { MX, EM } }, + { "pminsw", { MX, EM } }, + { "por", { MX, EM } }, + { "paddsb", { MX, EM } }, + { "paddsw", { MX, EM } }, + { "pmaxsw", { MX, EM } }, + { "pxor", { MX, EM } }, + /* f0 */ + { PREGRP32 }, + { "psllw", { MX, EM } }, + { "pslld", { MX, EM } }, + { "psllq", { MX, EM } }, + { "pmuludq", { MX, EM } }, + { "pmaddwd", { MX, EM } }, + { "psadbw", { MX, EM } }, + { PREGRP18 }, + /* f8 */ + { "psubb", { MX, EM } }, + { "psubw", { MX, EM } }, + { "psubd", { MX, EM } }, + { "psubq", { MX, EM } }, + { "paddb", { MX, EM } }, + { "paddw", { MX, EM } }, + { "paddd", { MX, EM } }, + { "(bad)", { XX } }, +}; + +static const unsigned char onebyte_has_modrm[256] = { + /* 0 1 2 3 4 5 6 7 8 9 a b c d e f */ + /* ------------------------------- */ + /* 00 */ 1,1,1,1,0,0,0,0,1,1,1,1,0,0,0,0, /* 00 */ + /* 10 */ 1,1,1,1,0,0,0,0,1,1,1,1,0,0,0,0, /* 10 */ + /* 20 */ 1,1,1,1,0,0,0,0,1,1,1,1,0,0,0,0, /* 20 */ + /* 30 */ 1,1,1,1,0,0,0,0,1,1,1,1,0,0,0,0, /* 30 */ + /* 40 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 40 */ + /* 50 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 50 */ + /* 60 */ 0,0,1,1,0,0,0,0,0,1,0,1,0,0,0,0, /* 60 */ + /* 70 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 70 */ + /* 80 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, /* 80 */ + /* 90 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 90 */ + /* a0 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* a0 */ + /* b0 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* b0 */ + /* c0 */ 1,1,0,0,1,1,1,1,0,0,0,0,0,0,0,0, /* c0 */ + /* d0 */ 1,1,1,1,0,0,0,0,1,1,1,1,1,1,1,1, /* d0 */ + /* e0 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* e0 */ + /* f0 */ 0,0,0,0,0,0,1,1,0,0,0,0,0,0,1,1 /* f0 */ + /* ------------------------------- */ + /* 0 1 2 3 4 5 6 7 8 9 a b c d e f */ +}; + +static const unsigned char twobyte_has_modrm[256] = { + /* 0 1 2 3 4 5 6 7 8 9 a b c d e f */ + /* ------------------------------- */ + /* 00 */ 1,1,1,1,0,0,0,0,0,0,0,0,0,1,0,1, /* 0f */ + /* 10 */ 1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,1, /* 1f */ + /* 20 */ 1,1,1,1,1,0,1,0,1,1,1,1,1,1,1,1, /* 2f */ + /* 30 */ 0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0, /* 3f */ + /* 40 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, /* 4f */ + /* 50 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, /* 5f */ + /* 60 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, /* 6f */ + /* 70 */ 1,1,1,1,1,1,1,0,1,1,0,0,1,1,1,1, /* 7f */ + /* 80 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 8f */ + /* 90 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, /* 9f */ + /* a0 */ 0,0,0,1,1,1,1,1,0,0,0,1,1,1,1,1, /* af */ + /* b0 */ 1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1, /* bf */ + /* c0 */ 1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0, /* cf */ + /* d0 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, /* df */ + /* e0 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, /* ef */ + /* f0 */ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0 /* ff */ + /* ------------------------------- */ + /* 0 1 2 3 4 5 6 7 8 9 a b c d e f */ +}; + +static const unsigned char twobyte_uses_DATA_prefix[256] = { + /* 0 1 2 3 4 5 6 7 8 9 a b c d e f */ + /* ------------------------------- */ + /* 00 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 0f */ + /* 10 */ 1,1,1,0,0,0,1,0,0,0,0,0,0,0,0,0, /* 1f */ + /* 20 */ 0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0, /* 2f */ + /* 30 */ 0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0, /* 3f */ + /* 40 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 4f */ + /* 50 */ 0,1,1,1,0,0,0,0,1,1,1,1,1,1,1,1, /* 5f */ + /* 60 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1, /* 6f */ + /* 70 */ 1,0,0,0,0,0,0,0,1,1,0,0,1,1,1,1, /* 7f */ + /* 80 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 8f */ + /* 90 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 9f */ + /* a0 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* af */ + /* b0 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* bf */ + /* c0 */ 0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0, /* cf */ + /* d0 */ 1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0, /* df */ + /* e0 */ 0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0, /* ef */ + /* f0 */ 1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0 /* ff */ + /* ------------------------------- */ + /* 0 1 2 3 4 5 6 7 8 9 a b c d e f */ +}; + +static const unsigned char twobyte_uses_REPNZ_prefix[256] = { + /* 0 1 2 3 4 5 6 7 8 9 a b c d e f */ + /* ------------------------------- */ + /* 00 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 0f */ + /* 10 */ 1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 1f */ + /* 20 */ 0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0, /* 2f */ + /* 30 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 3f */ + /* 40 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 4f */ + /* 50 */ 0,1,0,0,0,0,0,0,1,1,1,0,1,1,1,1, /* 5f */ + /* 60 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 6f */ + /* 70 */ 1,0,0,0,0,0,0,0,1,1,0,0,1,1,0,0, /* 7f */ + /* 80 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 8f */ + /* 90 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 9f */ + /* a0 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* af */ + /* b0 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* bf */ + /* c0 */ 0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0, /* cf */ + /* d0 */ 1,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0, /* df */ + /* e0 */ 0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0, /* ef */ + /* f0 */ 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* ff */ + /* ------------------------------- */ + /* 0 1 2 3 4 5 6 7 8 9 a b c d e f */ +}; + +static const unsigned char twobyte_uses_REPZ_prefix[256] = { + /* 0 1 2 3 4 5 6 7 8 9 a b c d e f */ + /* ------------------------------- */ + /* 00 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 0f */ + /* 10 */ 1,1,1,0,0,0,1,0,0,0,0,0,0,0,0,0, /* 1f */ + /* 20 */ 0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0, /* 2f */ + /* 30 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 3f */ + /* 40 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 4f */ + /* 50 */ 0,1,1,1,0,0,0,0,1,1,1,1,1,1,1,1, /* 5f */ + /* 60 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1, /* 6f */ + /* 70 */ 1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1, /* 7f */ + /* 80 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 8f */ + /* 90 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 9f */ + /* a0 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* af */ + /* b0 */ 0,0,0,0,0,0,0,0,1,0,0,0,0,1,0,0, /* bf */ + /* c0 */ 0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0, /* cf */ + /* d0 */ 0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0, /* df */ + /* e0 */ 0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0, /* ef */ + /* f0 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* ff */ + /* ------------------------------- */ + /* 0 1 2 3 4 5 6 7 8 9 a b c d e f */ +}; + +/* This is used to determine if opcode 0f 38 XX uses DATA prefix. */ +static const unsigned char threebyte_0x38_uses_DATA_prefix[256] = { + /* 0 1 2 3 4 5 6 7 8 9 a b c d e f */ + /* ------------------------------- */ + /* 00 */ 1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0, /* 0f */ + /* 10 */ 1,0,0,0,1,1,0,1,0,0,0,0,1,1,1,0, /* 1f */ + /* 20 */ 1,1,1,1,1,1,0,0,1,1,1,1,0,0,0,0, /* 2f */ + /* 30 */ 1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1, /* 3f */ + /* 40 */ 1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 4f */ + /* 50 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 5f */ + /* 60 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 6f */ + /* 70 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 7f */ + /* 80 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 8f */ + /* 90 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 9f */ + /* a0 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* af */ + /* b0 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* bf */ + /* c0 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* cf */ + /* d0 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* df */ + /* e0 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* ef */ + /* f0 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* ff */ + /* ------------------------------- */ + /* 0 1 2 3 4 5 6 7 8 9 a b c d e f */ +}; + +/* This is used to determine if opcode 0f 38 XX uses REPNZ prefix. */ +static const unsigned char threebyte_0x38_uses_REPNZ_prefix[256] = { + /* 0 1 2 3 4 5 6 7 8 9 a b c d e f */ + /* ------------------------------- */ + /* 00 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 0f */ + /* 10 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 1f */ + /* 20 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 2f */ + /* 30 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 3f */ + /* 40 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 4f */ + /* 50 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 5f */ + /* 60 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 6f */ + /* 70 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 7f */ + /* 80 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 8f */ + /* 90 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 9f */ + /* a0 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* af */ + /* b0 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* bf */ + /* c0 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* cf */ + /* d0 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* df */ + /* e0 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* ef */ + /* f0 */ 1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* ff */ + /* ------------------------------- */ + /* 0 1 2 3 4 5 6 7 8 9 a b c d e f */ +}; + +/* This is used to determine if opcode 0f 38 XX uses REPZ prefix. */ +static const unsigned char threebyte_0x38_uses_REPZ_prefix[256] = { + /* 0 1 2 3 4 5 6 7 8 9 a b c d e f */ + /* ------------------------------- */ + /* 00 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 0f */ + /* 10 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 1f */ + /* 20 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 2f */ + /* 30 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 3f */ + /* 40 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 4f */ + /* 50 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 5f */ + /* 60 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 6f */ + /* 70 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 7f */ + /* 80 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 8f */ + /* 90 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 9f */ + /* a0 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* af */ + /* b0 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* bf */ + /* c0 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* cf */ + /* d0 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* df */ + /* e0 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* ef */ + /* f0 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* ff */ + /* ------------------------------- */ + /* 0 1 2 3 4 5 6 7 8 9 a b c d e f */ +}; + +/* This is used to determine if opcode 0f 3a XX uses DATA prefix. */ +static const unsigned char threebyte_0x3a_uses_DATA_prefix[256] = { + /* 0 1 2 3 4 5 6 7 8 9 a b c d e f */ + /* ------------------------------- */ + /* 00 */ 0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1, /* 0f */ + /* 10 */ 0,0,0,0,1,1,1,1,0,0,0,0,0,0,0,0, /* 1f */ + /* 20 */ 1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 2f */ + /* 30 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 3f */ + /* 40 */ 1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 4f */ + /* 50 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 5f */ + /* 60 */ 1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0, /* 6f */ + /* 70 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 7f */ + /* 80 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 8f */ + /* 90 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 9f */ + /* a0 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* af */ + /* b0 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* bf */ + /* c0 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* cf */ + /* d0 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* df */ + /* e0 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* ef */ + /* f0 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* ff */ + /* ------------------------------- */ + /* 0 1 2 3 4 5 6 7 8 9 a b c d e f */ +}; + +/* This is used to determine if opcode 0f 3a XX uses REPNZ prefix. */ +static const unsigned char threebyte_0x3a_uses_REPNZ_prefix[256] = { + /* 0 1 2 3 4 5 6 7 8 9 a b c d e f */ + /* ------------------------------- */ + /* 00 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 0f */ + /* 10 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 1f */ + /* 20 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 2f */ + /* 30 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 3f */ + /* 40 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 4f */ + /* 50 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 5f */ + /* 60 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 6f */ + /* 70 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 7f */ + /* 80 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 8f */ + /* 90 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 9f */ + /* a0 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* af */ + /* b0 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* bf */ + /* c0 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* cf */ + /* d0 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* df */ + /* e0 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* ef */ + /* f0 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* ff */ + /* ------------------------------- */ + /* 0 1 2 3 4 5 6 7 8 9 a b c d e f */ +}; + +/* This is used to determine if opcode 0f 3a XX uses REPZ prefix. */ +static const unsigned char threebyte_0x3a_uses_REPZ_prefix[256] = { + /* 0 1 2 3 4 5 6 7 8 9 a b c d e f */ + /* ------------------------------- */ + /* 00 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 0f */ + /* 10 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 1f */ + /* 20 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 2f */ + /* 30 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 3f */ + /* 40 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 4f */ + /* 50 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 5f */ + /* 60 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 6f */ + /* 70 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 7f */ + /* 80 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 8f */ + /* 90 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 9f */ + /* a0 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* af */ + /* b0 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* bf */ + /* c0 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* cf */ + /* d0 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* df */ + /* e0 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* ef */ + /* f0 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* ff */ + /* ------------------------------- */ + /* 0 1 2 3 4 5 6 7 8 9 a b c d e f */ +}; + +static char obuf[100]; +static char *obufp; +static char scratchbuf[100]; +static unsigned char *start_codep; +static unsigned char *insn_codep; +static unsigned char *codep; +static disassemble_info *the_info; +static struct + { + int mod; + int reg; + int rm; + } +modrm; +static unsigned char need_modrm; + +/* If we are accessing mod/rm/reg without need_modrm set, then the + values are stale. Hitting this abort likely indicates that you + need to update onebyte_has_modrm or twobyte_has_modrm. */ +#define MODRM_CHECK if (!need_modrm) abort () + +static const char * const *names64; +static const char * const *names32; +static const char * const *names16; +static const char * const *names8; +static const char * const *names8rex; +static const char * const *names_seg; +static const char * const *index16; + +static const char * const intel_names64[] = { + "rax", "rcx", "rdx", "rbx", "rsp", "rbp", "rsi", "rdi", + "r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15" +}; +static const char * const intel_names32[] = { + "eax", "ecx", "edx", "ebx", "esp", "ebp", "esi", "edi", + "r8d", "r9d", "r10d", "r11d", "r12d", "r13d", "r14d", "r15d" +}; +static const char * const intel_names16[] = { + "ax", "cx", "dx", "bx", "sp", "bp", "si", "di", + "r8w", "r9w", "r10w", "r11w", "r12w", "r13w", "r14w", "r15w" +}; +static const char * const intel_names8[] = { + "al", "cl", "dl", "bl", "ah", "ch", "dh", "bh", +}; +static const char * const intel_names8rex[] = { + "al", "cl", "dl", "bl", "spl", "bpl", "sil", "dil", + "r8b", "r9b", "r10b", "r11b", "r12b", "r13b", "r14b", "r15b" +}; +static const char * const intel_names_seg[] = { + "es", "cs", "ss", "ds", "fs", "gs", "?", "?", +}; +static const char * const intel_index16[] = { + "bx+si", "bx+di", "bp+si", "bp+di", "si", "di", "bp", "bx" +}; + +static const char * const att_names64[] = { + "%rax", "%rcx", "%rdx", "%rbx", "%rsp", "%rbp", "%rsi", "%rdi", + "%r8", "%r9", "%r10", "%r11", "%r12", "%r13", "%r14", "%r15" +}; +static const char * const att_names32[] = { + "%eax", "%ecx", "%edx", "%ebx", "%esp", "%ebp", "%esi", "%edi", + "%r8d", "%r9d", "%r10d", "%r11d", "%r12d", "%r13d", "%r14d", "%r15d" +}; +static const char * const att_names16[] = { + "%ax", "%cx", "%dx", "%bx", "%sp", "%bp", "%si", "%di", + "%r8w", "%r9w", "%r10w", "%r11w", "%r12w", "%r13w", "%r14w", "%r15w" +}; +static const char * const att_names8[] = { + "%al", "%cl", "%dl", "%bl", "%ah", "%ch", "%dh", "%bh", +}; +static const char * const att_names8rex[] = { + "%al", "%cl", "%dl", "%bl", "%spl", "%bpl", "%sil", "%dil", + "%r8b", "%r9b", "%r10b", "%r11b", "%r12b", "%r13b", "%r14b", "%r15b" +}; +static const char * const att_names_seg[] = { + "%es", "%cs", "%ss", "%ds", "%fs", "%gs", "%?", "%?", +}; +static const char * const att_index16[] = { + "%bx,%si", "%bx,%di", "%bp,%si", "%bp,%di", "%si", "%di", "%bp", "%bx" +}; + +static const struct dis386 grps[][8] = { + /* GRP1a */ + { + { "popU", { stackEv } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + }, + /* GRP1b */ + { + { "addA", { Eb, Ib } }, + { "orA", { Eb, Ib } }, + { "adcA", { Eb, Ib } }, + { "sbbA", { Eb, Ib } }, + { "andA", { Eb, Ib } }, + { "subA", { Eb, Ib } }, + { "xorA", { Eb, Ib } }, + { "cmpA", { Eb, Ib } }, + }, + /* GRP1S */ + { + { "addQ", { Ev, Iv } }, + { "orQ", { Ev, Iv } }, + { "adcQ", { Ev, Iv } }, + { "sbbQ", { Ev, Iv } }, + { "andQ", { Ev, Iv } }, + { "subQ", { Ev, Iv } }, + { "xorQ", { Ev, Iv } }, + { "cmpQ", { Ev, Iv } }, + }, + /* GRP1Ss */ + { + { "addQ", { Ev, sIb } }, + { "orQ", { Ev, sIb } }, + { "adcQ", { Ev, sIb } }, + { "sbbQ", { Ev, sIb } }, + { "andQ", { Ev, sIb } }, + { "subQ", { Ev, sIb } }, + { "xorQ", { Ev, sIb } }, + { "cmpQ", { Ev, sIb } }, + }, + /* GRP2b */ + { + { "rolA", { Eb, Ib } }, + { "rorA", { Eb, Ib } }, + { "rclA", { Eb, Ib } }, + { "rcrA", { Eb, Ib } }, + { "shlA", { Eb, Ib } }, + { "shrA", { Eb, Ib } }, + { "(bad)", { XX } }, + { "sarA", { Eb, Ib } }, + }, + /* GRP2S */ + { + { "rolQ", { Ev, Ib } }, + { "rorQ", { Ev, Ib } }, + { "rclQ", { Ev, Ib } }, + { "rcrQ", { Ev, Ib } }, + { "shlQ", { Ev, Ib } }, + { "shrQ", { Ev, Ib } }, + { "(bad)", { XX } }, + { "sarQ", { Ev, Ib } }, + }, + /* GRP2b_one */ + { + { "rolA", { Eb, I1 } }, + { "rorA", { Eb, I1 } }, + { "rclA", { Eb, I1 } }, + { "rcrA", { Eb, I1 } }, + { "shlA", { Eb, I1 } }, + { "shrA", { Eb, I1 } }, + { "(bad)", { XX } }, + { "sarA", { Eb, I1 } }, + }, + /* GRP2S_one */ + { + { "rolQ", { Ev, I1 } }, + { "rorQ", { Ev, I1 } }, + { "rclQ", { Ev, I1 } }, + { "rcrQ", { Ev, I1 } }, + { "shlQ", { Ev, I1 } }, + { "shrQ", { Ev, I1 } }, + { "(bad)", { XX } }, + { "sarQ", { Ev, I1 } }, + }, + /* GRP2b_cl */ + { + { "rolA", { Eb, CL } }, + { "rorA", { Eb, CL } }, + { "rclA", { Eb, CL } }, + { "rcrA", { Eb, CL } }, + { "shlA", { Eb, CL } }, + { "shrA", { Eb, CL } }, + { "(bad)", { XX } }, + { "sarA", { Eb, CL } }, + }, + /* GRP2S_cl */ + { + { "rolQ", { Ev, CL } }, + { "rorQ", { Ev, CL } }, + { "rclQ", { Ev, CL } }, + { "rcrQ", { Ev, CL } }, + { "shlQ", { Ev, CL } }, + { "shrQ", { Ev, CL } }, + { "(bad)", { XX } }, + { "sarQ", { Ev, CL } }, + }, + /* GRP3b */ + { + { "testA", { Eb, Ib } }, + { "(bad)", { Eb } }, + { "notA", { Eb } }, + { "negA", { Eb } }, + { "mulA", { Eb } }, /* Don't print the implicit %al register, */ + { "imulA", { Eb } }, /* to distinguish these opcodes from other */ + { "divA", { Eb } }, /* mul/imul opcodes. Do the same for div */ + { "idivA", { Eb } }, /* and idiv for consistency. */ + }, + /* GRP3S */ + { + { "testQ", { Ev, Iv } }, + { "(bad)", { XX } }, + { "notQ", { Ev } }, + { "negQ", { Ev } }, + { "mulQ", { Ev } }, /* Don't print the implicit register. */ + { "imulQ", { Ev } }, + { "divQ", { Ev } }, + { "idivQ", { Ev } }, + }, + /* GRP4 */ + { + { "incA", { Eb } }, + { "decA", { Eb } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + }, + /* GRP5 */ + { + { "incQ", { Ev } }, + { "decQ", { Ev } }, + { "callT", { indirEv } }, + { "JcallT", { indirEp } }, + { "jmpT", { indirEv } }, + { "JjmpT", { indirEp } }, + { "pushU", { stackEv } }, + { "(bad)", { XX } }, + }, + /* GRP6 */ + { + { "sldtD", { Sv } }, + { "strD", { Sv } }, + { "lldt", { Ew } }, + { "ltr", { Ew } }, + { "verr", { Ew } }, + { "verw", { Ew } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + }, + /* GRP7 */ + { + { "sgdt{Q|IQ||}", { { VMX_Fixup, 0 } } }, + { "sidt{Q|IQ||}", { { PNI_Fixup, 0 } } }, + { "lgdt{Q|Q||}", { M } }, + { "lidt{Q|Q||}", { { SVME_Fixup, 0 } } }, + { "smswD", { Sv } }, + { "(bad)", { XX } }, + { "lmsw", { Ew } }, + { "invlpg", { { INVLPG_Fixup, w_mode } } }, + }, + /* GRP8 */ + { + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "btQ", { Ev, Ib } }, + { "btsQ", { Ev, Ib } }, + { "btrQ", { Ev, Ib } }, + { "btcQ", { Ev, Ib } }, + }, + /* GRP9 */ + { + { "(bad)", { XX } }, + { "cmpxchg8b", { { CMPXCHG8B_Fixup, q_mode } } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "", { VM } }, /* See OP_VMX. */ + { "vmptrst", { Mq } }, + }, + /* GRP11_C6 */ + { + { "movA", { Eb, Ib } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + }, + /* GRP11_C7 */ + { + { "movQ", { Ev, Iv } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + }, + /* GRP12 */ + { + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "psrlw", { MS, Ib } }, + { "(bad)", { XX } }, + { "psraw", { MS, Ib } }, + { "(bad)", { XX } }, + { "psllw", { MS, Ib } }, + { "(bad)", { XX } }, + }, + /* GRP13 */ + { + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "psrld", { MS, Ib } }, + { "(bad)", { XX } }, + { "psrad", { MS, Ib } }, + { "(bad)", { XX } }, + { "pslld", { MS, Ib } }, + { "(bad)", { XX } }, + }, + /* GRP14 */ + { + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "psrlq", { MS, Ib } }, + { "psrldq", { MS, Ib } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "psllq", { MS, Ib } }, + { "pslldq", { MS, Ib } }, + }, + /* GRP15 */ + { + { "fxsave", { Ev } }, + { "fxrstor", { Ev } }, + { "ldmxcsr", { Ev } }, + { "stmxcsr", { Ev } }, + { "(bad)", { XX } }, + { "lfence", { { OP_0fae, 0 } } }, + { "mfence", { { OP_0fae, 0 } } }, + { "clflush", { { OP_0fae, 0 } } }, + }, + /* GRP16 */ + { + { "prefetchnta", { Ev } }, + { "prefetcht0", { Ev } }, + { "prefetcht1", { Ev } }, + { "prefetcht2", { Ev } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + }, + /* GRPAMD */ + { + { "prefetch", { Eb } }, + { "prefetchw", { Eb } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + }, + /* GRPPADLCK1 */ + { + { "xstore-rng", { { OP_0f07, 0 } } }, + { "xcrypt-ecb", { { OP_0f07, 0 } } }, + { "xcrypt-cbc", { { OP_0f07, 0 } } }, + { "xcrypt-ctr", { { OP_0f07, 0 } } }, + { "xcrypt-cfb", { { OP_0f07, 0 } } }, + { "xcrypt-ofb", { { OP_0f07, 0 } } }, + { "(bad)", { { OP_0f07, 0 } } }, + { "(bad)", { { OP_0f07, 0 } } }, + }, + /* GRPPADLCK2 */ + { + { "montmul", { { OP_0f07, 0 } } }, + { "xsha1", { { OP_0f07, 0 } } }, + { "xsha256", { { OP_0f07, 0 } } }, + { "(bad)", { { OP_0f07, 0 } } }, + { "(bad)", { { OP_0f07, 0 } } }, + { "(bad)", { { OP_0f07, 0 } } }, + { "(bad)", { { OP_0f07, 0 } } }, + { "(bad)", { { OP_0f07, 0 } } }, + } +}; + +static const struct dis386 prefix_user_table[][4] = { + /* PREGRP0 */ + { + { "addps", { XM, EXx } }, + { "addss", { XM, EXd } }, + { "addpd", { XM, EXx } }, + { "addsd", { XM, EXq } }, + }, + /* PREGRP1 */ + { + { "", { XM, EXx, OPSIMD } }, /* See OP_SIMD_SUFFIX. */ + { "", { XM, EXx, OPSIMD } }, + { "", { XM, EXx, OPSIMD } }, + { "", { XM, EXx, OPSIMD } }, + }, + /* PREGRP2 */ + { + { "cvtpi2ps", { XM, EMC } }, + { "cvtsi2ssY", { XM, Ev } }, + { "cvtpi2pd", { XM, EMC } }, + { "cvtsi2sdY", { XM, Ev } }, + }, + /* PREGRP3 */ + { + { "cvtps2pi", { MXC, EXx } }, + { "cvtss2siY", { Gv, EXx } }, + { "cvtpd2pi", { MXC, EXx } }, + { "cvtsd2siY", { Gv, EXx } }, + }, + /* PREGRP4 */ + { + { "cvttps2pi", { MXC, EXx } }, + { "cvttss2siY", { Gv, EXx } }, + { "cvttpd2pi", { MXC, EXx } }, + { "cvttsd2siY", { Gv, EXx } }, + }, + /* PREGRP5 */ + { + { "divps", { XM, EXx } }, + { "divss", { XM, EXx } }, + { "divpd", { XM, EXx } }, + { "divsd", { XM, EXx } }, + }, + /* PREGRP6 */ + { + { "maxps", { XM, EXx } }, + { "maxss", { XM, EXx } }, + { "maxpd", { XM, EXx } }, + { "maxsd", { XM, EXx } }, + }, + /* PREGRP7 */ + { + { "minps", { XM, EXx } }, + { "minss", { XM, EXx } }, + { "minpd", { XM, EXx } }, + { "minsd", { XM, EXx } }, + }, + /* PREGRP8 */ + { + { "movups", { XM, EXx } }, + { "movss", { XM, EXx } }, + { "movupd", { XM, EXx } }, + { "movsd", { XM, EXx } }, + }, + /* PREGRP9 */ + { + { "movups", { EXx, XM } }, + { "movss", { EXx, XM } }, + { "movupd", { EXx, XM } }, + { "movsd", { EXx, XM } }, + }, + /* PREGRP10 */ + { + { "mulps", { XM, EXx } }, + { "mulss", { XM, EXx } }, + { "mulpd", { XM, EXx } }, + { "mulsd", { XM, EXx } }, + }, + /* PREGRP11 */ + { + { "rcpps", { XM, EXx } }, + { "rcpss", { XM, EXx } }, + { "(bad)", { XM, EXx } }, + { "(bad)", { XM, EXx } }, + }, + /* PREGRP12 */ + { + { "rsqrtps",{ XM, EXx } }, + { "rsqrtss",{ XM, EXx } }, + { "(bad)", { XM, EXx } }, + { "(bad)", { XM, EXx } }, + }, + /* PREGRP13 */ + { + { "sqrtps", { XM, EXx } }, + { "sqrtss", { XM, EXx } }, + { "sqrtpd", { XM, EXx } }, + { "sqrtsd", { XM, EXx } }, + }, + /* PREGRP14 */ + { + { "subps", { XM, EXx } }, + { "subss", { XM, EXx } }, + { "subpd", { XM, EXx } }, + { "subsd", { XM, EXx } }, + }, + /* PREGRP15 */ + { + { "(bad)", { XM, EXx } }, + { "cvtdq2pd", { XM, EXq } }, + { "cvttpd2dq", { XM, EXx } }, + { "cvtpd2dq", { XM, EXx } }, + }, + /* PREGRP16 */ + { + { "cvtdq2ps", { XM, EXx } }, + { "cvttps2dq", { XM, EXx } }, + { "cvtps2dq", { XM, EXx } }, + { "(bad)", { XM, EXx } }, + }, + /* PREGRP17 */ + { + { "cvtps2pd", { XM, EXq } }, + { "cvtss2sd", { XM, EXx } }, + { "cvtpd2ps", { XM, EXx } }, + { "cvtsd2ss", { XM, EXx } }, + }, + /* PREGRP18 */ + { + { "maskmovq", { MX, MS } }, + { "(bad)", { XM, EXx } }, + { "maskmovdqu", { XM, XS } }, + { "(bad)", { XM, EXx } }, + }, + /* PREGRP19 */ + { + { "movq", { MX, EM } }, + { "movdqu", { XM, EXx } }, + { "movdqa", { XM, EXx } }, + { "(bad)", { XM, EXx } }, + }, + /* PREGRP20 */ + { + { "movq", { EM, MX } }, + { "movdqu", { EXx, XM } }, + { "movdqa", { EXx, XM } }, + { "(bad)", { EXx, XM } }, + }, + /* PREGRP21 */ + { + { "(bad)", { EXx, XM } }, + { "movq2dq",{ XM, MS } }, + { "movq", { EXx, XM } }, + { "movdq2q",{ MX, XS } }, + }, + /* PREGRP22 */ + { + { "pshufw", { MX, EM, Ib } }, + { "pshufhw",{ XM, EXx, Ib } }, + { "pshufd", { XM, EXx, Ib } }, + { "pshuflw",{ XM, EXx, Ib } }, + }, + /* PREGRP23 */ + { + { "movd", { Edq, MX } }, + { "movq", { XM, EXx } }, + { "movd", { Edq, XM } }, + { "(bad)", { Ed, XM } }, + }, + /* PREGRP24 */ + { + { "(bad)", { MX, EXx } }, + { "(bad)", { XM, EXx } }, + { "punpckhqdq", { XM, EXx } }, + { "(bad)", { XM, EXx } }, + }, + /* PREGRP25 */ + { + { "movntq", { EM, MX } }, + { "(bad)", { EM, XM } }, + { "movntdq",{ EM, XM } }, + { "(bad)", { EM, XM } }, + }, + /* PREGRP26 */ + { + { "(bad)", { MX, EXx } }, + { "(bad)", { XM, EXx } }, + { "punpcklqdq", { XM, EXx } }, + { "(bad)", { XM, EXx } }, + }, + /* PREGRP27 */ + { + { "(bad)", { MX, EXx } }, + { "(bad)", { XM, EXx } }, + { "addsubpd", { XM, EXx } }, + { "addsubps", { XM, EXx } }, + }, + /* PREGRP28 */ + { + { "(bad)", { MX, EXx } }, + { "(bad)", { XM, EXx } }, + { "haddpd", { XM, EXx } }, + { "haddps", { XM, EXx } }, + }, + /* PREGRP29 */ + { + { "(bad)", { MX, EXx } }, + { "(bad)", { XM, EXx } }, + { "hsubpd", { XM, EXx } }, + { "hsubps", { XM, EXx } }, + }, + /* PREGRP30 */ + { + { "movlpX", { XM, EXq, { SIMD_Fixup, 'h' } } }, /* really only 2 operands */ + { "movsldup", { XM, EXx } }, + { "movlpd", { XM, EXq } }, + { "movddup", { XM, EXq } }, + }, + /* PREGRP31 */ + { + { "movhpX", { XM, EXq, { SIMD_Fixup, 'l' } } }, + { "movshdup", { XM, EXx } }, + { "movhpd", { XM, EXq } }, + { "(bad)", { XM, EXq } }, + }, + /* PREGRP32 */ + { + { "(bad)", { XM, EXx } }, + { "(bad)", { XM, EXx } }, + { "(bad)", { XM, EXx } }, + { "lddqu", { XM, M } }, + }, + /* PREGRP33 */ + { + {"movntps", { Ev, XM } }, + {"movntss", { Ev, XM } }, + {"movntpd", { Ev, XM } }, + {"movntsd", { Ev, XM } }, + }, + + /* PREGRP34 */ + { + {"vmread", { Em, Gm } }, + {"(bad)", { XX } }, + {"extrq", { XS, Ib, Ib } }, + {"insertq", { XM, XS, Ib, Ib } }, + }, + + /* PREGRP35 */ + { + {"vmwrite", { Gm, Em } }, + {"(bad)", { XX } }, + {"extrq", { XM, XS } }, + {"insertq", { XM, XS } }, + }, + + /* PREGRP36 */ + { + { "bsrS", { Gv, Ev } }, + { "lzcntS", { Gv, Ev } }, + { "bsrS", { Gv, Ev } }, + { "(bad)", { XX } }, + }, + + /* PREGRP37 */ + { + { "(bad)", { XX } }, + { "popcntS", { Gv, Ev } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + }, + + /* PREGRP38 */ + { + { "xchgS", { { NOP_Fixup1, eAX_reg }, { NOP_Fixup2, eAX_reg } } }, + { "pause", { XX } }, + { "xchgS", { { NOP_Fixup1, eAX_reg }, { NOP_Fixup2, eAX_reg } } }, + { "(bad)", { XX } }, + }, + + /* PREGRP39 */ + { + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "pblendvb", {XM, EXx, XMM0 } }, + { "(bad)", { XX } }, + }, + + /* PREGRP40 */ + { + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "blendvps", {XM, EXx, XMM0 } }, + { "(bad)", { XX } }, + }, + + /* PREGRP41 */ + { + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "blendvpd", { XM, EXx, XMM0 } }, + { "(bad)", { XX } }, + }, + + /* PREGRP42 */ + { + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "ptest", { XM, EXx } }, + { "(bad)", { XX } }, + }, + + /* PREGRP43 */ + { + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "pmovsxbw", { XM, EXx } }, + { "(bad)", { XX } }, + }, + + /* PREGRP44 */ + { + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "pmovsxbd", { XM, EXx } }, + { "(bad)", { XX } }, + }, + + /* PREGRP45 */ + { + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "pmovsxbq", { XM, EXx } }, + { "(bad)", { XX } }, + }, + + /* PREGRP46 */ + { + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "pmovsxwd", { XM, EXx } }, + { "(bad)", { XX } }, + }, + + /* PREGRP47 */ + { + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "pmovsxwq", { XM, EXx } }, + { "(bad)", { XX } }, + }, + + /* PREGRP48 */ + { + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "pmovsxdq", { XM, EXx } }, + { "(bad)", { XX } }, + }, + + /* PREGRP49 */ + { + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "pmuldq", { XM, EXx } }, + { "(bad)", { XX } }, + }, + + /* PREGRP50 */ + { + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "pcmpeqq", { XM, EXx } }, + { "(bad)", { XX } }, + }, + + /* PREGRP51 */ + { + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "movntdqa", { XM, EM } }, + { "(bad)", { XX } }, + }, + + /* PREGRP52 */ + { + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "packusdw", { XM, EXx } }, + { "(bad)", { XX } }, + }, + + /* PREGRP53 */ + { + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "pmovzxbw", { XM, EXx } }, + { "(bad)", { XX } }, + }, + + /* PREGRP54 */ + { + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "pmovzxbd", { XM, EXx } }, + { "(bad)", { XX } }, + }, + + /* PREGRP55 */ + { + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "pmovzxbq", { XM, EXx } }, + { "(bad)", { XX } }, + }, + + /* PREGRP56 */ + { + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "pmovzxwd", { XM, EXx } }, + { "(bad)", { XX } }, + }, + + /* PREGRP57 */ + { + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "pmovzxwq", { XM, EXx } }, + { "(bad)", { XX } }, + }, + + /* PREGRP58 */ + { + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "pmovzxdq", { XM, EXx } }, + { "(bad)", { XX } }, + }, + + /* PREGRP59 */ + { + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "pminsb", { XM, EXx } }, + { "(bad)", { XX } }, + }, + + /* PREGRP60 */ + { + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "pminsd", { XM, EXx } }, + { "(bad)", { XX } }, + }, + + /* PREGRP61 */ + { + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "pminuw", { XM, EXx } }, + { "(bad)", { XX } }, + }, + + /* PREGRP62 */ + { + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "pminud", { XM, EXx } }, + { "(bad)", { XX } }, + }, + + /* PREGRP63 */ + { + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "pmaxsb", { XM, EXx } }, + { "(bad)", { XX } }, + }, + + /* PREGRP64 */ + { + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "pmaxsd", { XM, EXx } }, + { "(bad)", { XX } }, + }, + + /* PREGRP65 */ + { + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "pmaxuw", { XM, EXx } }, + { "(bad)", { XX } }, + }, + + /* PREGRP66 */ + { + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "pmaxud", { XM, EXx } }, + { "(bad)", { XX } }, + }, + + /* PREGRP67 */ + { + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "pmulld", { XM, EXx } }, + { "(bad)", { XX } }, + }, + + /* PREGRP68 */ + { + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "phminposuw", { XM, EXx } }, + { "(bad)", { XX } }, + }, + + /* PREGRP69 */ + { + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "roundps", { XM, EXx, Ib } }, + { "(bad)", { XX } }, + }, + + /* PREGRP70 */ + { + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "roundpd", { XM, EXx, Ib } }, + { "(bad)", { XX } }, + }, + + /* PREGRP71 */ + { + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "roundss", { XM, EXx, Ib } }, + { "(bad)", { XX } }, + }, + + /* PREGRP72 */ + { + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "roundsd", { XM, EXx, Ib } }, + { "(bad)", { XX } }, + }, + + /* PREGRP73 */ + { + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "blendps", { XM, EXx, Ib } }, + { "(bad)", { XX } }, + }, + + /* PREGRP74 */ + { + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "blendpd", { XM, EXx, Ib } }, + { "(bad)", { XX } }, + }, + + /* PREGRP75 */ + { + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "pblendw", { XM, EXx, Ib } }, + { "(bad)", { XX } }, + }, + + /* PREGRP76 */ + { + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "pextrb", { Edqb, XM, Ib } }, + { "(bad)", { XX } }, + }, + + /* PREGRP77 */ + { + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "pextrw", { Edqw, XM, Ib } }, + { "(bad)", { XX } }, + }, + + /* PREGRP78 */ + { + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "pextrK", { Edq, XM, Ib } }, + { "(bad)", { XX } }, + }, + + /* PREGRP79 */ + { + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "extractps", { Edqd, XM, Ib } }, + { "(bad)", { XX } }, + }, + + /* PREGRP80 */ + { + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "pinsrb", { XM, Edqb, Ib } }, + { "(bad)", { XX } }, + }, + + /* PREGRP81 */ + { + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "insertps", { XM, EXx, Ib } }, + { "(bad)", { XX } }, + }, + + /* PREGRP82 */ + { + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "pinsrK", { XM, Edq, Ib } }, + { "(bad)", { XX } }, + }, + + /* PREGRP83 */ + { + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "dpps", { XM, EXx, Ib } }, + { "(bad)", { XX } }, + }, + + /* PREGRP84 */ + { + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "dppd", { XM, EXx, Ib } }, + { "(bad)", { XX } }, + }, + + /* PREGRP85 */ + { + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "mpsadbw", { XM, EXx, Ib } }, + { "(bad)", { XX } }, + }, + + /* PREGRP86 */ + { + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "pcmpgtq", { XM, EXx } }, + { "(bad)", { XX } }, + }, + + /* PREGRP87 */ + { + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "crc32", { Gdq, { CRC32_Fixup, b_mode } } }, + }, + + /* PREGRP88 */ + { + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "crc32", { Gdq, { CRC32_Fixup, v_mode } } }, + }, + + /* PREGRP89 */ + { + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "pcmpestrm", { XM, EXx, Ib } }, + { "(bad)", { XX } }, + }, + + /* PREGRP90 */ + { + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "pcmpestri", { XM, EXx, Ib } }, + { "(bad)", { XX } }, + }, + + /* PREGRP91 */ + { + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "pcmpistrm", { XM, EXx, Ib } }, + { "(bad)", { XX } }, + }, + + /* PREGRP92 */ + { + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "pcmpistri", { XM, EXx, Ib } }, + { "(bad)", { XX } }, + }, + + /* PREGRP93 */ + { + { "ucomiss",{ XM, EXd } }, + { "(bad)", { XX } }, + { "ucomisd",{ XM, EXq } }, + { "(bad)", { XX } }, + }, + + /* PREGRP94 */ + { + { "comiss", { XM, EXd } }, + { "(bad)", { XX } }, + { "comisd", { XM, EXq } }, + { "(bad)", { XX } }, + }, + + /* PREGRP95 */ + { + { "punpcklbw",{ MX, EMd } }, + { "(bad)", { XX } }, + { "punpcklbw",{ MX, EMq } }, + { "(bad)", { XX } }, + }, + + /* PREGRP96 */ + { + { "punpcklwd",{ MX, EMd } }, + { "(bad)", { XX } }, + { "punpcklwd",{ MX, EMq } }, + { "(bad)", { XX } }, + }, + + /* PREGRP97 */ + { + { "punpckldq",{ MX, EMd } }, + { "(bad)", { XX } }, + { "punpckldq",{ MX, EMq } }, + { "(bad)", { XX } }, + }, +}; + +static const struct dis386 x86_64_table[][2] = { + { + { "pusha{P|}", { XX } }, + { "(bad)", { XX } }, + }, + { + { "popa{P|}", { XX } }, + { "(bad)", { XX } }, + }, + { + { "bound{S|}", { Gv, Ma } }, + { "(bad)", { XX } }, + }, + { + { "arpl", { Ew, Gw } }, + { "movs{||lq|xd}", { Gv, Ed } }, + }, +}; + +static const struct dis386 three_byte_table[][256] = { + /* THREE_BYTE_0 */ + { + /* 00 */ + { "pshufb", { MX, EM } }, + { "phaddw", { MX, EM } }, + { "phaddd", { MX, EM } }, + { "phaddsw", { MX, EM } }, + { "pmaddubsw", { MX, EM } }, + { "phsubw", { MX, EM } }, + { "phsubd", { MX, EM } }, + { "phsubsw", { MX, EM } }, + /* 08 */ + { "psignb", { MX, EM } }, + { "psignw", { MX, EM } }, + { "psignd", { MX, EM } }, + { "pmulhrsw", { MX, EM } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + /* 10 */ + { PREGRP39 }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { PREGRP40 }, + { PREGRP41 }, + { "(bad)", { XX } }, + { PREGRP42 }, + /* 18 */ + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "pabsb", { MX, EM } }, + { "pabsw", { MX, EM } }, + { "pabsd", { MX, EM } }, + { "(bad)", { XX } }, + /* 20 */ + { PREGRP43 }, + { PREGRP44 }, + { PREGRP45 }, + { PREGRP46 }, + { PREGRP47 }, + { PREGRP48 }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + /* 28 */ + { PREGRP49 }, + { PREGRP50 }, + { PREGRP51 }, + { PREGRP52 }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + /* 30 */ + { PREGRP53 }, + { PREGRP54 }, + { PREGRP55 }, + { PREGRP56 }, + { PREGRP57 }, + { PREGRP58 }, + { "(bad)", { XX } }, + { PREGRP86 }, + /* 38 */ + { PREGRP59 }, + { PREGRP60 }, + { PREGRP61 }, + { PREGRP62 }, + { PREGRP63 }, + { PREGRP64 }, + { PREGRP65 }, + { PREGRP66 }, + /* 40 */ + { PREGRP67 }, + { PREGRP68 }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + /* 48 */ + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + /* 50 */ + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + /* 58 */ + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + /* 60 */ + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + /* 68 */ + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + /* 70 */ + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + /* 78 */ + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + /* 80 */ + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + /* 88 */ + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + /* 90 */ + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + /* 98 */ + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + /* a0 */ + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + /* a8 */ + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + /* b0 */ + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + /* b8 */ + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + /* c0 */ + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + /* c8 */ + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + /* d0 */ + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + /* d8 */ + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + /* e0 */ + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + /* e8 */ + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + /* f0 */ + { PREGRP87 }, + { PREGRP88 }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + /* f8 */ + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + }, + /* THREE_BYTE_1 */ + { + /* 00 */ + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + /* 08 */ + { PREGRP69 }, + { PREGRP70 }, + { PREGRP71 }, + { PREGRP72 }, + { PREGRP73 }, + { PREGRP74 }, + { PREGRP75 }, + { "palignr", { MX, EM, Ib } }, + /* 10 */ + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { PREGRP76 }, + { PREGRP77 }, + { PREGRP78 }, + { PREGRP79 }, + /* 18 */ + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + /* 20 */ + { PREGRP80 }, + { PREGRP81 }, + { PREGRP82 }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + /* 28 */ + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + /* 30 */ + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + /* 38 */ + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + /* 40 */ + { PREGRP83 }, + { PREGRP84 }, + { PREGRP85 }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + /* 48 */ + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + /* 50 */ + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + /* 58 */ + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + /* 60 */ + { PREGRP89 }, + { PREGRP90 }, + { PREGRP91 }, + { PREGRP92 }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + /* 68 */ + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + /* 70 */ + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + /* 78 */ + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + /* 80 */ + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + /* 88 */ + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + /* 90 */ + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + /* 98 */ + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + /* a0 */ + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + /* a8 */ + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + /* b0 */ + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + /* b8 */ + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + /* c0 */ + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + /* c8 */ + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + /* d0 */ + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + /* d8 */ + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + /* e0 */ + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + /* e8 */ + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + /* f0 */ + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + /* f8 */ + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + } +}; + +#define INTERNAL_DISASSEMBLER_ERROR _("") + +static void +ckprefix (void) +{ + int newrex; + rex = 0; + prefixes = 0; + used_prefixes = 0; + rex_used = 0; + while (1) + { + fetch_data(the_info, codep + 1); + newrex = 0; + switch (*codep) + { + /* REX prefixes family. */ + case 0x40: + case 0x41: + case 0x42: + case 0x43: + case 0x44: + case 0x45: + case 0x46: + case 0x47: + case 0x48: + case 0x49: + case 0x4a: + case 0x4b: + case 0x4c: + case 0x4d: + case 0x4e: + case 0x4f: + if (address_mode == mode_64bit) + newrex = *codep; + else + return; + break; + case 0xf3: + prefixes |= PREFIX_REPZ; + break; + case 0xf2: + prefixes |= PREFIX_REPNZ; + break; + case 0xf0: + prefixes |= PREFIX_LOCK; + break; + case 0x2e: + prefixes |= PREFIX_CS; + break; + case 0x36: + prefixes |= PREFIX_SS; + break; + case 0x3e: + prefixes |= PREFIX_DS; + break; + case 0x26: + prefixes |= PREFIX_ES; + break; + case 0x64: + prefixes |= PREFIX_FS; + break; + case 0x65: + prefixes |= PREFIX_GS; + break; + case 0x66: + prefixes |= PREFIX_DATA; + break; + case 0x67: + prefixes |= PREFIX_ADDR; + break; + case FWAIT_OPCODE: + /* fwait is really an instruction. If there are prefixes + before the fwait, they belong to the fwait, *not* to the + following instruction. */ + if (prefixes || rex) + { + prefixes |= PREFIX_FWAIT; + codep++; + return; + } + prefixes = PREFIX_FWAIT; + break; + default: + return; + } + /* Rex is ignored when followed by another prefix. */ + if (rex) + { + rex_used = rex; + return; + } + rex = newrex; + codep++; + } +} + +/* Return the name of the prefix byte PREF, or NULL if PREF is not a + prefix byte. */ + +static const char * +prefix_name (int pref, int sizeflag) +{ + static const char * const rexes [16] = + { + "rex", /* 0x40 */ + "rex.B", /* 0x41 */ + "rex.X", /* 0x42 */ + "rex.XB", /* 0x43 */ + "rex.R", /* 0x44 */ + "rex.RB", /* 0x45 */ + "rex.RX", /* 0x46 */ + "rex.RXB", /* 0x47 */ + "rex.W", /* 0x48 */ + "rex.WB", /* 0x49 */ + "rex.WX", /* 0x4a */ + "rex.WXB", /* 0x4b */ + "rex.WR", /* 0x4c */ + "rex.WRB", /* 0x4d */ + "rex.WRX", /* 0x4e */ + "rex.WRXB", /* 0x4f */ + }; + + switch (pref) + { + /* REX prefixes family. */ + case 0x40: + case 0x41: + case 0x42: + case 0x43: + case 0x44: + case 0x45: + case 0x46: + case 0x47: + case 0x48: + case 0x49: + case 0x4a: + case 0x4b: + case 0x4c: + case 0x4d: + case 0x4e: + case 0x4f: + return rexes [pref - 0x40]; + case 0xf3: + return "repz"; + case 0xf2: + return "repnz"; + case 0xf0: + return "lock"; + case 0x2e: + return "cs"; + case 0x36: + return "ss"; + case 0x3e: + return "ds"; + case 0x26: + return "es"; + case 0x64: + return "fs"; + case 0x65: + return "gs"; + case 0x66: + return (sizeflag & DFLAG) ? "data16" : "data32"; + case 0x67: + if (address_mode == mode_64bit) + return (sizeflag & AFLAG) ? "addr32" : "addr64"; + else + return (sizeflag & AFLAG) ? "addr16" : "addr32"; + case FWAIT_OPCODE: + return "fwait"; + default: + return NULL; + } +} + +static char op_out[MAX_OPERANDS][100]; +static int op_ad, op_index[MAX_OPERANDS]; +static int two_source_ops; +static bfd_vma op_address[MAX_OPERANDS]; +static bfd_vma op_riprel[MAX_OPERANDS]; +static bfd_vma start_pc; + +/* + * On the 386's of 1988, the maximum length of an instruction is 15 bytes. + * (see topic "Redundant prefixes" in the "Differences from 8086" + * section of the "Virtual 8086 Mode" chapter.) + * 'pc' should be the address of this instruction, it will + * be used to print the target address if this is a relative jump or call + * The function returns the length of this instruction in bytes. + */ + +static char intel_syntax; +static char open_char; +static char close_char; +static char separator_char; +static char scale_char; + +int +print_insn_i386 (bfd_vma pc, disassemble_info *info) +{ + intel_syntax = -1; + + return print_insn (pc, info); +} + +static int +print_insn (bfd_vma pc, disassemble_info *info) +{ + const struct dis386 *dp; + int i; + char *op_txt[MAX_OPERANDS]; + int needcomma; + unsigned char uses_DATA_prefix, uses_LOCK_prefix; + unsigned char uses_REPNZ_prefix, uses_REPZ_prefix; + int sizeflag; + const char *p; + struct dis_private priv; + unsigned char op; + + if (info->mach == bfd_mach_x86_64_intel_syntax + || info->mach == bfd_mach_x86_64) + address_mode = mode_64bit; + else + address_mode = mode_32bit; + + if (intel_syntax == (char) -1) + intel_syntax = (info->mach == bfd_mach_i386_i386_intel_syntax + || info->mach == bfd_mach_x86_64_intel_syntax); + + if (info->mach == bfd_mach_i386_i386 + || info->mach == bfd_mach_x86_64 + || info->mach == bfd_mach_i386_i386_intel_syntax + || info->mach == bfd_mach_x86_64_intel_syntax) + priv.orig_sizeflag = AFLAG | DFLAG; + else if (info->mach == bfd_mach_i386_i8086) + priv.orig_sizeflag = 0; + else + abort (); + + for (p = info->disassembler_options; p != NULL; ) + { + if (strncmp (p, "x86-64", 6) == 0) + { + address_mode = mode_64bit; + priv.orig_sizeflag = AFLAG | DFLAG; + } + else if (strncmp (p, "i386", 4) == 0) + { + address_mode = mode_32bit; + priv.orig_sizeflag = AFLAG | DFLAG; + } + else if (strncmp (p, "i8086", 5) == 0) + { + address_mode = mode_16bit; + priv.orig_sizeflag = 0; + } + else if (strncmp (p, "intel", 5) == 0) + { + intel_syntax = 1; + } + else if (strncmp (p, "att", 3) == 0) + { + intel_syntax = 0; + } + else if (strncmp (p, "addr", 4) == 0) + { + if (address_mode == mode_64bit) + { + if (p[4] == '3' && p[5] == '2') + priv.orig_sizeflag &= ~AFLAG; + else if (p[4] == '6' && p[5] == '4') + priv.orig_sizeflag |= AFLAG; + } + else + { + if (p[4] == '1' && p[5] == '6') + priv.orig_sizeflag &= ~AFLAG; + else if (p[4] == '3' && p[5] == '2') + priv.orig_sizeflag |= AFLAG; + } + } + else if (strncmp (p, "data", 4) == 0) + { + if (p[4] == '1' && p[5] == '6') + priv.orig_sizeflag &= ~DFLAG; + else if (p[4] == '3' && p[5] == '2') + priv.orig_sizeflag |= DFLAG; + } + else if (strncmp (p, "suffix", 6) == 0) + priv.orig_sizeflag |= SUFFIX_ALWAYS; + + p = strchr (p, ','); + if (p != NULL) + p++; + } + + if (intel_syntax) + { + names64 = intel_names64; + names32 = intel_names32; + names16 = intel_names16; + names8 = intel_names8; + names8rex = intel_names8rex; + names_seg = intel_names_seg; + index16 = intel_index16; + open_char = '['; + close_char = ']'; + separator_char = '+'; + scale_char = '*'; + } + else + { + names64 = att_names64; + names32 = att_names32; + names16 = att_names16; + names8 = att_names8; + names8rex = att_names8rex; + names_seg = att_names_seg; + index16 = att_index16; + open_char = '('; + close_char = ')'; + separator_char = ','; + scale_char = ','; + } + + /* The output looks better if we put 7 bytes on a line, since that + puts most long word instructions on a single line. */ + info->bytes_per_line = 7; + + info->private_data = &priv; + priv.max_fetched = priv.the_buffer; + priv.insn_start = pc; + + obuf[0] = 0; + for (i = 0; i < MAX_OPERANDS; ++i) + { + op_out[i][0] = 0; + op_index[i] = -1; + } + + the_info = info; + start_pc = pc; + start_codep = priv.the_buffer; + codep = priv.the_buffer; + + if (setjmp (priv.bailout) != 0) + { + const char *name; + + /* Getting here means we tried for data but didn't get it. That + means we have an incomplete instruction of some sort. Just + print the first byte as a prefix or a .byte pseudo-op. */ + if (codep > priv.the_buffer) + { + name = prefix_name (priv.the_buffer[0], priv.orig_sizeflag); + if (name != NULL) + (*info->fprintf_func) (info->stream, "%s", name); + else + { + /* Just print the first byte as a .byte instruction. */ + (*info->fprintf_func) (info->stream, ".byte 0x%x", + (unsigned int) priv.the_buffer[0]); + } + + return 1; + } + + return -1; + } + + obufp = obuf; + ckprefix (); + + insn_codep = codep; + sizeflag = priv.orig_sizeflag; + + fetch_data(info, codep + 1); + two_source_ops = (*codep == 0x62) || (*codep == 0xc8); + + if (((prefixes & PREFIX_FWAIT) + && ((*codep < 0xd8) || (*codep > 0xdf))) + || (rex && rex_used)) + { + const char *name; + + /* fwait not followed by floating point instruction, or rex followed + by other prefixes. Print the first prefix. */ + name = prefix_name (priv.the_buffer[0], priv.orig_sizeflag); + if (name == NULL) + name = INTERNAL_DISASSEMBLER_ERROR; + (*info->fprintf_func) (info->stream, "%s", name); + return 1; + } + + op = 0; + if (*codep == 0x0f) + { + unsigned char threebyte; + fetch_data(info, codep + 2); + threebyte = *++codep; + dp = &dis386_twobyte[threebyte]; + need_modrm = twobyte_has_modrm[*codep]; + uses_DATA_prefix = twobyte_uses_DATA_prefix[*codep]; + uses_REPNZ_prefix = twobyte_uses_REPNZ_prefix[*codep]; + uses_REPZ_prefix = twobyte_uses_REPZ_prefix[*codep]; + uses_LOCK_prefix = (*codep & ~0x02) == 0x20; + codep++; + if (dp->name == NULL && dp->op[0].bytemode == IS_3BYTE_OPCODE) + { + fetch_data(info, codep + 2); + op = *codep++; + switch (threebyte) + { + case 0x38: + uses_DATA_prefix = threebyte_0x38_uses_DATA_prefix[op]; + uses_REPNZ_prefix = threebyte_0x38_uses_REPNZ_prefix[op]; + uses_REPZ_prefix = threebyte_0x38_uses_REPZ_prefix[op]; + break; + case 0x3a: + uses_DATA_prefix = threebyte_0x3a_uses_DATA_prefix[op]; + uses_REPNZ_prefix = threebyte_0x3a_uses_REPNZ_prefix[op]; + uses_REPZ_prefix = threebyte_0x3a_uses_REPZ_prefix[op]; + break; + default: + break; + } + } + } + else + { + dp = &dis386[*codep]; + need_modrm = onebyte_has_modrm[*codep]; + uses_DATA_prefix = 0; + uses_REPNZ_prefix = 0; + /* pause is 0xf3 0x90. */ + uses_REPZ_prefix = *codep == 0x90; + uses_LOCK_prefix = 0; + codep++; + } + + if (!uses_REPZ_prefix && (prefixes & PREFIX_REPZ)) + { + oappend ("repz "); + used_prefixes |= PREFIX_REPZ; + } + if (!uses_REPNZ_prefix && (prefixes & PREFIX_REPNZ)) + { + oappend ("repnz "); + used_prefixes |= PREFIX_REPNZ; + } + + if (!uses_LOCK_prefix && (prefixes & PREFIX_LOCK)) + { + oappend ("lock "); + used_prefixes |= PREFIX_LOCK; + } + + if (prefixes & PREFIX_ADDR) + { + sizeflag ^= AFLAG; + if (dp->op[2].bytemode != loop_jcxz_mode || intel_syntax) + { + if ((sizeflag & AFLAG) || address_mode == mode_64bit) + oappend ("addr32 "); + else + oappend ("addr16 "); + used_prefixes |= PREFIX_ADDR; + } + } + + if (!uses_DATA_prefix && (prefixes & PREFIX_DATA)) + { + sizeflag ^= DFLAG; + if (dp->op[2].bytemode == cond_jump_mode + && dp->op[0].bytemode == v_mode + && !intel_syntax) + { + if (sizeflag & DFLAG) + oappend ("data32 "); + else + oappend ("data16 "); + used_prefixes |= PREFIX_DATA; + } + } + + if (dp->name == NULL && dp->op[0].bytemode == IS_3BYTE_OPCODE) + { + dp = &three_byte_table[dp->op[1].bytemode][op]; + modrm.mod = (*codep >> 6) & 3; + modrm.reg = (*codep >> 3) & 7; + modrm.rm = *codep & 7; + } + else if (need_modrm) + { + fetch_data(info, codep + 1); + modrm.mod = (*codep >> 6) & 3; + modrm.reg = (*codep >> 3) & 7; + modrm.rm = *codep & 7; + } + + if (dp->name == NULL && dp->op[0].bytemode == FLOATCODE) + { + dofloat (sizeflag); + } + else + { + int index; + if (dp->name == NULL) + { + switch (dp->op[0].bytemode) + { + case USE_GROUPS: + dp = &grps[dp->op[1].bytemode][modrm.reg]; + break; + + case USE_PREFIX_USER_TABLE: + index = 0; + used_prefixes |= (prefixes & PREFIX_REPZ); + if (prefixes & PREFIX_REPZ) + index = 1; + else + { + /* We should check PREFIX_REPNZ and PREFIX_REPZ + before PREFIX_DATA. */ + used_prefixes |= (prefixes & PREFIX_REPNZ); + if (prefixes & PREFIX_REPNZ) + index = 3; + else + { + used_prefixes |= (prefixes & PREFIX_DATA); + if (prefixes & PREFIX_DATA) + index = 2; + } + } + dp = &prefix_user_table[dp->op[1].bytemode][index]; + break; + + case X86_64_SPECIAL: + index = address_mode == mode_64bit ? 1 : 0; + dp = &x86_64_table[dp->op[1].bytemode][index]; + break; + + default: + oappend (INTERNAL_DISASSEMBLER_ERROR); + break; + } + } + + if (putop (dp->name, sizeflag) == 0) + { + for (i = 0; i < MAX_OPERANDS; ++i) + { + obufp = op_out[i]; + op_ad = MAX_OPERANDS - 1 - i; + if (dp->op[i].rtn) + (*dp->op[i].rtn) (dp->op[i].bytemode, sizeflag); + } + } + } + + /* See if any prefixes were not used. If so, print the first one + separately. If we don't do this, we'll wind up printing an + instruction stream which does not precisely correspond to the + bytes we are disassembling. */ + if ((prefixes & ~used_prefixes) != 0) + { + const char *name; + + name = prefix_name (priv.the_buffer[0], priv.orig_sizeflag); + if (name == NULL) + name = INTERNAL_DISASSEMBLER_ERROR; + (*info->fprintf_func) (info->stream, "%s", name); + return 1; + } + if (rex & ~rex_used) + { + const char *name; + name = prefix_name (rex | 0x40, priv.orig_sizeflag); + if (name == NULL) + name = INTERNAL_DISASSEMBLER_ERROR; + (*info->fprintf_func) (info->stream, "%s ", name); + } + + obufp = obuf + strlen (obuf); + for (i = strlen (obuf); i < 6; i++) + oappend (" "); + oappend (" "); + (*info->fprintf_func) (info->stream, "%s", obuf); + + /* The enter and bound instructions are printed with operands in the same + order as the intel book; everything else is printed in reverse order. */ + if (intel_syntax || two_source_ops) + { + bfd_vma riprel; + + for (i = 0; i < MAX_OPERANDS; ++i) + op_txt[i] = op_out[i]; + + for (i = 0; i < (MAX_OPERANDS >> 1); ++i) + { + op_ad = op_index[i]; + op_index[i] = op_index[MAX_OPERANDS - 1 - i]; + op_index[MAX_OPERANDS - 1 - i] = op_ad; + riprel = op_riprel[i]; + op_riprel[i] = op_riprel [MAX_OPERANDS - 1 - i]; + op_riprel[MAX_OPERANDS - 1 - i] = riprel; + } + } + else + { + for (i = 0; i < MAX_OPERANDS; ++i) + op_txt[MAX_OPERANDS - 1 - i] = op_out[i]; + } + + needcomma = 0; + for (i = 0; i < MAX_OPERANDS; ++i) + if (*op_txt[i]) + { + if (needcomma) + (*info->fprintf_func) (info->stream, ","); + if (op_index[i] != -1 && !op_riprel[i]) + (*info->print_address_func) ((bfd_vma) op_address[op_index[i]], info); + else + (*info->fprintf_func) (info->stream, "%s", op_txt[i]); + needcomma = 1; + } + + for (i = 0; i < MAX_OPERANDS; i++) + if (op_index[i] != -1 && op_riprel[i]) + { + (*info->fprintf_func) (info->stream, " # "); + (*info->print_address_func) ((bfd_vma) (start_pc + codep - start_codep + + op_address[op_index[i]]), info); + break; + } + return codep - priv.the_buffer; +} + +static const char *float_mem[] = { + /* d8 */ + "fadd{s||s|}", + "fmul{s||s|}", + "fcom{s||s|}", + "fcomp{s||s|}", + "fsub{s||s|}", + "fsubr{s||s|}", + "fdiv{s||s|}", + "fdivr{s||s|}", + /* d9 */ + "fld{s||s|}", + "(bad)", + "fst{s||s|}", + "fstp{s||s|}", + "fldenvIC", + "fldcw", + "fNstenvIC", + "fNstcw", + /* da */ + "fiadd{l||l|}", + "fimul{l||l|}", + "ficom{l||l|}", + "ficomp{l||l|}", + "fisub{l||l|}", + "fisubr{l||l|}", + "fidiv{l||l|}", + "fidivr{l||l|}", + /* db */ + "fild{l||l|}", + "fisttp{l||l|}", + "fist{l||l|}", + "fistp{l||l|}", + "(bad)", + "fld{t||t|}", + "(bad)", + "fstp{t||t|}", + /* dc */ + "fadd{l||l|}", + "fmul{l||l|}", + "fcom{l||l|}", + "fcomp{l||l|}", + "fsub{l||l|}", + "fsubr{l||l|}", + "fdiv{l||l|}", + "fdivr{l||l|}", + /* dd */ + "fld{l||l|}", + "fisttp{ll||ll|}", + "fst{l||l|}", + "fstp{l||l|}", + "frstorIC", + "(bad)", + "fNsaveIC", + "fNstsw", + /* de */ + "fiadd", + "fimul", + "ficom", + "ficomp", + "fisub", + "fisubr", + "fidiv", + "fidivr", + /* df */ + "fild", + "fisttp", + "fist", + "fistp", + "fbld", + "fild{ll||ll|}", + "fbstp", + "fistp{ll||ll|}", +}; + +static const unsigned char float_mem_mode[] = { + /* d8 */ + d_mode, + d_mode, + d_mode, + d_mode, + d_mode, + d_mode, + d_mode, + d_mode, + /* d9 */ + d_mode, + 0, + d_mode, + d_mode, + 0, + w_mode, + 0, + w_mode, + /* da */ + d_mode, + d_mode, + d_mode, + d_mode, + d_mode, + d_mode, + d_mode, + d_mode, + /* db */ + d_mode, + d_mode, + d_mode, + d_mode, + 0, + t_mode, + 0, + t_mode, + /* dc */ + q_mode, + q_mode, + q_mode, + q_mode, + q_mode, + q_mode, + q_mode, + q_mode, + /* dd */ + q_mode, + q_mode, + q_mode, + q_mode, + 0, + 0, + 0, + w_mode, + /* de */ + w_mode, + w_mode, + w_mode, + w_mode, + w_mode, + w_mode, + w_mode, + w_mode, + /* df */ + w_mode, + w_mode, + w_mode, + w_mode, + t_mode, + q_mode, + t_mode, + q_mode +}; + +#define ST { OP_ST, 0 } +#define STi { OP_STi, 0 } + +#define FGRPd9_2 NULL, { { NULL, 0 } } +#define FGRPd9_4 NULL, { { NULL, 1 } } +#define FGRPd9_5 NULL, { { NULL, 2 } } +#define FGRPd9_6 NULL, { { NULL, 3 } } +#define FGRPd9_7 NULL, { { NULL, 4 } } +#define FGRPda_5 NULL, { { NULL, 5 } } +#define FGRPdb_4 NULL, { { NULL, 6 } } +#define FGRPde_3 NULL, { { NULL, 7 } } +#define FGRPdf_4 NULL, { { NULL, 8 } } + +static const struct dis386 float_reg[][8] = { + /* d8 */ + { + { "fadd", { ST, STi } }, + { "fmul", { ST, STi } }, + { "fcom", { STi } }, + { "fcomp", { STi } }, + { "fsub", { ST, STi } }, + { "fsubr", { ST, STi } }, + { "fdiv", { ST, STi } }, + { "fdivr", { ST, STi } }, + }, + /* d9 */ + { + { "fld", { STi } }, + { "fxch", { STi } }, + { FGRPd9_2 }, + { "(bad)", { XX } }, + { FGRPd9_4 }, + { FGRPd9_5 }, + { FGRPd9_6 }, + { FGRPd9_7 }, + }, + /* da */ + { + { "fcmovb", { ST, STi } }, + { "fcmove", { ST, STi } }, + { "fcmovbe",{ ST, STi } }, + { "fcmovu", { ST, STi } }, + { "(bad)", { XX } }, + { FGRPda_5 }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + }, + /* db */ + { + { "fcmovnb",{ ST, STi } }, + { "fcmovne",{ ST, STi } }, + { "fcmovnbe",{ ST, STi } }, + { "fcmovnu",{ ST, STi } }, + { FGRPdb_4 }, + { "fucomi", { ST, STi } }, + { "fcomi", { ST, STi } }, + { "(bad)", { XX } }, + }, + /* dc */ + { + { "fadd", { STi, ST } }, + { "fmul", { STi, ST } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, +#if SYSV386_COMPAT + { "fsub", { STi, ST } }, + { "fsubr", { STi, ST } }, + { "fdiv", { STi, ST } }, + { "fdivr", { STi, ST } }, +#else + { "fsubr", { STi, ST } }, + { "fsub", { STi, ST } }, + { "fdivr", { STi, ST } }, + { "fdiv", { STi, ST } }, +#endif + }, + /* dd */ + { + { "ffree", { STi } }, + { "(bad)", { XX } }, + { "fst", { STi } }, + { "fstp", { STi } }, + { "fucom", { STi } }, + { "fucomp", { STi } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + }, + /* de */ + { + { "faddp", { STi, ST } }, + { "fmulp", { STi, ST } }, + { "(bad)", { XX } }, + { FGRPde_3 }, +#if SYSV386_COMPAT + { "fsubp", { STi, ST } }, + { "fsubrp", { STi, ST } }, + { "fdivp", { STi, ST } }, + { "fdivrp", { STi, ST } }, +#else + { "fsubrp", { STi, ST } }, + { "fsubp", { STi, ST } }, + { "fdivrp", { STi, ST } }, + { "fdivp", { STi, ST } }, +#endif + }, + /* df */ + { + { "ffreep", { STi } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { "(bad)", { XX } }, + { FGRPdf_4 }, + { "fucomip", { ST, STi } }, + { "fcomip", { ST, STi } }, + { "(bad)", { XX } }, + }, +}; + +static const char *fgrps[][8] = { + /* d9_2 0 */ + { + "fnop","(bad)","(bad)","(bad)","(bad)","(bad)","(bad)","(bad)", + }, + + /* d9_4 1 */ + { + "fchs","fabs","(bad)","(bad)","ftst","fxam","(bad)","(bad)", + }, + + /* d9_5 2 */ + { + "fld1","fldl2t","fldl2e","fldpi","fldlg2","fldln2","fldz","(bad)", + }, + + /* d9_6 3 */ + { + "f2xm1","fyl2x","fptan","fpatan","fxtract","fprem1","fdecstp","fincstp", + }, + + /* d9_7 4 */ + { + "fprem","fyl2xp1","fsqrt","fsincos","frndint","fscale","fsin","fcos", + }, + + /* da_5 5 */ + { + "(bad)","fucompp","(bad)","(bad)","(bad)","(bad)","(bad)","(bad)", + }, + + /* db_4 6 */ + { + "feni(287 only)","fdisi(287 only)","fNclex","fNinit", + "fNsetpm(287 only)","(bad)","(bad)","(bad)", + }, + + /* de_3 7 */ + { + "(bad)","fcompp","(bad)","(bad)","(bad)","(bad)","(bad)","(bad)", + }, + + /* df_4 8 */ + { + "fNstsw","(bad)","(bad)","(bad)","(bad)","(bad)","(bad)","(bad)", + }, +}; + +static void +dofloat (int sizeflag) +{ + const struct dis386 *dp; + unsigned char floatop; + + floatop = codep[-1]; + + if (modrm.mod != 3) + { + int fp_indx = (floatop - 0xd8) * 8 + modrm.reg; + + putop (float_mem[fp_indx], sizeflag); + obufp = op_out[0]; + op_ad = 2; + OP_E (float_mem_mode[fp_indx], sizeflag); + return; + } + /* Skip mod/rm byte. */ + MODRM_CHECK; + codep++; + + dp = &float_reg[floatop - 0xd8][modrm.reg]; + if (dp->name == NULL) + { + putop (fgrps[dp->op[0].bytemode][modrm.rm], sizeflag); + + /* Instruction fnstsw is only one with strange arg. */ + if (floatop == 0xdf && codep[-1] == 0xe0) + pstrcpy (op_out[0], sizeof(op_out[0]), names16[0]); + } + else + { + putop (dp->name, sizeflag); + + obufp = op_out[0]; + op_ad = 2; + if (dp->op[0].rtn) + (*dp->op[0].rtn) (dp->op[0].bytemode, sizeflag); + + obufp = op_out[1]; + op_ad = 1; + if (dp->op[1].rtn) + (*dp->op[1].rtn) (dp->op[1].bytemode, sizeflag); + } +} + +static void +OP_ST (int bytemode ATTRIBUTE_UNUSED, int sizeflag ATTRIBUTE_UNUSED) +{ + oappend ("%st" + intel_syntax); +} + +static void +OP_STi (int bytemode ATTRIBUTE_UNUSED, int sizeflag ATTRIBUTE_UNUSED) +{ + snprintf (scratchbuf, sizeof(scratchbuf), "%%st(%d)", modrm.rm); + oappend (scratchbuf + intel_syntax); +} + +/* Capital letters in template are macros. */ +static int +putop (const char *template, int sizeflag) +{ + const char *p; + int alt = 0; + + for (p = template; *p; p++) + { + switch (*p) + { + default: + *obufp++ = *p; + break; + case '{': + alt = 0; + if (intel_syntax) + alt += 1; + if (address_mode == mode_64bit) + alt += 2; + while (alt != 0) + { + while (*++p != '|') + { + if (*p == '}') + { + /* Alternative not valid. */ + pstrcpy (obuf, sizeof(obuf), "(bad)"); + obufp = obuf + 5; + return 1; + } + else if (*p == '\0') + abort (); + } + alt--; + } + /* Fall through. */ + case 'I': + alt = 1; + continue; + case '|': + while (*++p != '}') + { + if (*p == '\0') + abort (); + } + break; + case '}': + break; + case 'A': + if (intel_syntax) + break; + if (modrm.mod != 3 || (sizeflag & SUFFIX_ALWAYS)) + *obufp++ = 'b'; + break; + case 'B': + if (intel_syntax) + break; + if (sizeflag & SUFFIX_ALWAYS) + *obufp++ = 'b'; + break; + case 'C': + if (intel_syntax && !alt) + break; + if ((prefixes & PREFIX_DATA) || (sizeflag & SUFFIX_ALWAYS)) + { + if (sizeflag & DFLAG) + *obufp++ = intel_syntax ? 'd' : 'l'; + else + *obufp++ = intel_syntax ? 'w' : 's'; + used_prefixes |= (prefixes & PREFIX_DATA); + } + break; + case 'D': + if (intel_syntax || !(sizeflag & SUFFIX_ALWAYS)) + break; + USED_REX (REX_W); + if (modrm.mod == 3) + { + if (rex & REX_W) + *obufp++ = 'q'; + else if (sizeflag & DFLAG) + *obufp++ = intel_syntax ? 'd' : 'l'; + else + *obufp++ = 'w'; + used_prefixes |= (prefixes & PREFIX_DATA); + } + else + *obufp++ = 'w'; + break; + case 'E': /* For jcxz/jecxz */ + if (address_mode == mode_64bit) + { + if (sizeflag & AFLAG) + *obufp++ = 'r'; + else + *obufp++ = 'e'; + } + else + if (sizeflag & AFLAG) + *obufp++ = 'e'; + used_prefixes |= (prefixes & PREFIX_ADDR); + break; + case 'F': + if (intel_syntax) + break; + if ((prefixes & PREFIX_ADDR) || (sizeflag & SUFFIX_ALWAYS)) + { + if (sizeflag & AFLAG) + *obufp++ = address_mode == mode_64bit ? 'q' : 'l'; + else + *obufp++ = address_mode == mode_64bit ? 'l' : 'w'; + used_prefixes |= (prefixes & PREFIX_ADDR); + } + break; + case 'G': + if (intel_syntax || (obufp[-1] != 's' && !(sizeflag & SUFFIX_ALWAYS))) + break; + if ((rex & REX_W) || (sizeflag & DFLAG)) + *obufp++ = 'l'; + else + *obufp++ = 'w'; + if (!(rex & REX_W)) + used_prefixes |= (prefixes & PREFIX_DATA); + break; + case 'H': + if (intel_syntax) + break; + if ((prefixes & (PREFIX_CS | PREFIX_DS)) == PREFIX_CS + || (prefixes & (PREFIX_CS | PREFIX_DS)) == PREFIX_DS) + { + used_prefixes |= prefixes & (PREFIX_CS | PREFIX_DS); + *obufp++ = ','; + *obufp++ = 'p'; + if (prefixes & PREFIX_DS) + *obufp++ = 't'; + else + *obufp++ = 'n'; + } + break; + case 'J': + if (intel_syntax) + break; + *obufp++ = 'l'; + break; + case 'K': + USED_REX (REX_W); + if (rex & REX_W) + *obufp++ = 'q'; + else + *obufp++ = 'd'; + break; + case 'Z': + if (intel_syntax) + break; + if (address_mode == mode_64bit && (sizeflag & SUFFIX_ALWAYS)) + { + *obufp++ = 'q'; + break; + } + /* Fall through. */ + case 'L': + if (intel_syntax) + break; + if (sizeflag & SUFFIX_ALWAYS) + *obufp++ = 'l'; + break; + case 'N': + if ((prefixes & PREFIX_FWAIT) == 0) + *obufp++ = 'n'; + else + used_prefixes |= PREFIX_FWAIT; + break; + case 'O': + USED_REX (REX_W); + if (rex & REX_W) + *obufp++ = 'o'; + else if (intel_syntax && (sizeflag & DFLAG)) + *obufp++ = 'q'; + else + *obufp++ = 'd'; + if (!(rex & REX_W)) + used_prefixes |= (prefixes & PREFIX_DATA); + break; + case 'T': + if (intel_syntax) + break; + if (address_mode == mode_64bit && (sizeflag & DFLAG)) + { + *obufp++ = 'q'; + break; + } + /* Fall through. */ + case 'P': + if (intel_syntax) + break; + if ((prefixes & PREFIX_DATA) + || (rex & REX_W) + || (sizeflag & SUFFIX_ALWAYS)) + { + USED_REX (REX_W); + if (rex & REX_W) + *obufp++ = 'q'; + else + { + if (sizeflag & DFLAG) + *obufp++ = 'l'; + else + *obufp++ = 'w'; + } + used_prefixes |= (prefixes & PREFIX_DATA); + } + break; + case 'U': + if (intel_syntax) + break; + if (address_mode == mode_64bit && (sizeflag & DFLAG)) + { + if (modrm.mod != 3 || (sizeflag & SUFFIX_ALWAYS)) + *obufp++ = 'q'; + break; + } + /* Fall through. */ + case 'Q': + if (intel_syntax && !alt) + break; + USED_REX (REX_W); + if (modrm.mod != 3 || (sizeflag & SUFFIX_ALWAYS)) + { + if (rex & REX_W) + *obufp++ = 'q'; + else + { + if (sizeflag & DFLAG) + *obufp++ = intel_syntax ? 'd' : 'l'; + else + *obufp++ = 'w'; + } + used_prefixes |= (prefixes & PREFIX_DATA); + } + break; + case 'R': + USED_REX (REX_W); + if (rex & REX_W) + *obufp++ = 'q'; + else if (sizeflag & DFLAG) + { + if (intel_syntax) + *obufp++ = 'd'; + else + *obufp++ = 'l'; + } + else + *obufp++ = 'w'; + if (intel_syntax && !p[1] + && ((rex & REX_W) || (sizeflag & DFLAG))) + *obufp++ = 'e'; + if (!(rex & REX_W)) + used_prefixes |= (prefixes & PREFIX_DATA); + break; + case 'V': + if (intel_syntax) + break; + if (address_mode == mode_64bit && (sizeflag & DFLAG)) + { + if (sizeflag & SUFFIX_ALWAYS) + *obufp++ = 'q'; + break; + } + /* Fall through. */ + case 'S': + if (intel_syntax) + break; + if (sizeflag & SUFFIX_ALWAYS) + { + if (rex & REX_W) + *obufp++ = 'q'; + else + { + if (sizeflag & DFLAG) + *obufp++ = 'l'; + else + *obufp++ = 'w'; + used_prefixes |= (prefixes & PREFIX_DATA); + } + } + break; + case 'X': + if (prefixes & PREFIX_DATA) + *obufp++ = 'd'; + else + *obufp++ = 's'; + used_prefixes |= (prefixes & PREFIX_DATA); + break; + case 'Y': + if (intel_syntax) + break; + if (rex & REX_W) + { + USED_REX (REX_W); + *obufp++ = 'q'; + } + break; + /* implicit operand size 'l' for i386 or 'q' for x86-64 */ + case 'W': + /* operand size flag for cwtl, cbtw */ + USED_REX (REX_W); + if (rex & REX_W) + { + if (intel_syntax) + *obufp++ = 'd'; + else + *obufp++ = 'l'; + } + else if (sizeflag & DFLAG) + *obufp++ = 'w'; + else + *obufp++ = 'b'; + if (!(rex & REX_W)) + used_prefixes |= (prefixes & PREFIX_DATA); + break; + } + alt = 0; + } + *obufp = 0; + return 0; +} + +static void +oappend (const char *s) +{ + strcpy (obufp, s); + obufp += strlen (s); +} + +static void +append_seg (void) +{ + if (prefixes & PREFIX_CS) + { + used_prefixes |= PREFIX_CS; + oappend ("%cs:" + intel_syntax); + } + if (prefixes & PREFIX_DS) + { + used_prefixes |= PREFIX_DS; + oappend ("%ds:" + intel_syntax); + } + if (prefixes & PREFIX_SS) + { + used_prefixes |= PREFIX_SS; + oappend ("%ss:" + intel_syntax); + } + if (prefixes & PREFIX_ES) + { + used_prefixes |= PREFIX_ES; + oappend ("%es:" + intel_syntax); + } + if (prefixes & PREFIX_FS) + { + used_prefixes |= PREFIX_FS; + oappend ("%fs:" + intel_syntax); + } + if (prefixes & PREFIX_GS) + { + used_prefixes |= PREFIX_GS; + oappend ("%gs:" + intel_syntax); + } +} + +static void +OP_indirE (int bytemode, int sizeflag) +{ + if (!intel_syntax) + oappend ("*"); + OP_E (bytemode, sizeflag); +} + +static void +print_operand_value (char *buf, size_t bufsize, int hex, bfd_vma disp) +{ + if (address_mode == mode_64bit) + { + if (hex) + { + char tmp[30]; + int i; + buf[0] = '0'; + buf[1] = 'x'; + snprintf_vma (tmp, sizeof(tmp), disp); + for (i = 0; tmp[i] == '0' && tmp[i + 1]; i++); + pstrcpy (buf + 2, bufsize - 2, tmp + i); + } + else + { + bfd_signed_vma v = disp; + char tmp[30]; + int i; + if (v < 0) + { + *(buf++) = '-'; + v = -disp; + /* Check for possible overflow on 0x8000000000000000. */ + if (v < 0) + { + pstrcpy (buf, bufsize, "9223372036854775808"); + return; + } + } + if (!v) + { + pstrcpy (buf, bufsize, "0"); + return; + } + + i = 0; + tmp[29] = 0; + while (v) + { + tmp[28 - i] = (v % 10) + '0'; + v /= 10; + i++; + } + pstrcpy (buf, bufsize, tmp + 29 - i); + } + } + else + { + if (hex) + snprintf (buf, bufsize, "0x%x", (unsigned int) disp); + else + snprintf (buf, bufsize, "%d", (int) disp); + } +} + +/* Put DISP in BUF as signed hex number. */ + +static void +print_displacement (char *buf, bfd_vma disp) +{ + bfd_signed_vma val = disp; + char tmp[30]; + int i, j = 0; + + if (val < 0) + { + buf[j++] = '-'; + val = -disp; + + /* Check for possible overflow. */ + if (val < 0) + { + switch (address_mode) + { + case mode_64bit: + strcpy (buf + j, "0x8000000000000000"); + break; + case mode_32bit: + strcpy (buf + j, "0x80000000"); + break; + case mode_16bit: + strcpy (buf + j, "0x8000"); + break; + } + return; + } + } + + buf[j++] = '0'; + buf[j++] = 'x'; + + snprintf_vma (tmp, sizeof(tmp), val); + for (i = 0; tmp[i] == '0'; i++) + continue; + if (tmp[i] == '\0') + i--; + strcpy (buf + j, tmp + i); +} + +static void +intel_operand_size (int bytemode, int sizeflag) +{ + switch (bytemode) + { + case b_mode: + case dqb_mode: + oappend ("BYTE PTR "); + break; + case w_mode: + case dqw_mode: + oappend ("WORD PTR "); + break; + case stack_v_mode: + if (address_mode == mode_64bit && (sizeflag & DFLAG)) + { + oappend ("QWORD PTR "); + used_prefixes |= (prefixes & PREFIX_DATA); + break; + } + /* FALLTHRU */ + case v_mode: + case dq_mode: + USED_REX (REX_W); + if (rex & REX_W) + oappend ("QWORD PTR "); + else if ((sizeflag & DFLAG) || bytemode == dq_mode) + oappend ("DWORD PTR "); + else + oappend ("WORD PTR "); + used_prefixes |= (prefixes & PREFIX_DATA); + break; + case z_mode: + if ((rex & REX_W) || (sizeflag & DFLAG)) + *obufp++ = 'D'; + oappend ("WORD PTR "); + if (!(rex & REX_W)) + used_prefixes |= (prefixes & PREFIX_DATA); + break; + case d_mode: + case dqd_mode: + oappend ("DWORD PTR "); + break; + case q_mode: + oappend ("QWORD PTR "); + break; + case m_mode: + if (address_mode == mode_64bit) + oappend ("QWORD PTR "); + else + oappend ("DWORD PTR "); + break; + case f_mode: + if (sizeflag & DFLAG) + oappend ("FWORD PTR "); + else + oappend ("DWORD PTR "); + used_prefixes |= (prefixes & PREFIX_DATA); + break; + case t_mode: + oappend ("TBYTE PTR "); + break; + case x_mode: + oappend ("XMMWORD PTR "); + break; + case o_mode: + oappend ("OWORD PTR "); + break; + default: + break; + } +} + +static void +OP_E (int bytemode, int sizeflag) +{ + bfd_vma disp; + int add = 0; + int riprel = 0; + USED_REX (REX_B); + if (rex & REX_B) + add += 8; + + /* Skip mod/rm byte. */ + MODRM_CHECK; + codep++; + + if (modrm.mod == 3) + { + switch (bytemode) + { + case b_mode: + USED_REX (0); + if (rex) + oappend (names8rex[modrm.rm + add]); + else + oappend (names8[modrm.rm + add]); + break; + case w_mode: + oappend (names16[modrm.rm + add]); + break; + case d_mode: + oappend (names32[modrm.rm + add]); + break; + case q_mode: + oappend (names64[modrm.rm + add]); + break; + case m_mode: + if (address_mode == mode_64bit) + oappend (names64[modrm.rm + add]); + else + oappend (names32[modrm.rm + add]); + break; + case stack_v_mode: + if (address_mode == mode_64bit && (sizeflag & DFLAG)) + { + oappend (names64[modrm.rm + add]); + used_prefixes |= (prefixes & PREFIX_DATA); + break; + } + bytemode = v_mode; + /* FALLTHRU */ + case v_mode: + case dq_mode: + case dqb_mode: + case dqd_mode: + case dqw_mode: + USED_REX (REX_W); + if (rex & REX_W) + oappend (names64[modrm.rm + add]); + else if ((sizeflag & DFLAG) || bytemode != v_mode) + oappend (names32[modrm.rm + add]); + else + oappend (names16[modrm.rm + add]); + used_prefixes |= (prefixes & PREFIX_DATA); + break; + case 0: + break; + default: + oappend (INTERNAL_DISASSEMBLER_ERROR); + break; + } + return; + } + + disp = 0; + if (intel_syntax) + intel_operand_size (bytemode, sizeflag); + append_seg (); + + if ((sizeflag & AFLAG) || address_mode == mode_64bit) + { + /* 32/64 bit address mode */ + int havedisp; + int havesib; + int havebase; + int base; + int index = 0; + int scale = 0; + + havesib = 0; + havebase = 1; + base = modrm.rm; + + if (base == 4) + { + havesib = 1; + fetch_data(the_info, codep + 1); + index = (*codep >> 3) & 7; + if (address_mode == mode_64bit || index != 0x4) + /* When INDEX == 0x4 in 32 bit mode, SCALE is ignored. */ + scale = (*codep >> 6) & 3; + base = *codep & 7; + USED_REX (REX_X); + if (rex & REX_X) + index += 8; + codep++; + } + base += add; + + switch (modrm.mod) + { + case 0: + if ((base & 7) == 5) + { + havebase = 0; + if (address_mode == mode_64bit && !havesib) + riprel = 1; + disp = get32s (); + } + break; + case 1: + fetch_data (the_info, codep + 1); + disp = *codep++; + if ((disp & 0x80) != 0) + disp -= 0x100; + break; + case 2: + disp = get32s (); + break; + } + + havedisp = havebase || (havesib && (index != 4 || scale != 0)); + + if (!intel_syntax) + if (modrm.mod != 0 || (base & 7) == 5) + { + if (havedisp || riprel) + print_displacement (scratchbuf, disp); + else + print_operand_value (scratchbuf, sizeof(scratchbuf), 1, disp); + oappend (scratchbuf); + if (riprel) + { + set_op (disp, 1); + oappend ("(%rip)"); + } + } + + if (havedisp || (intel_syntax && riprel)) + { + *obufp++ = open_char; + if (intel_syntax && riprel) + { + set_op (disp, 1); + oappend ("rip"); + } + *obufp = '\0'; + if (havebase) + oappend (address_mode == mode_64bit && (sizeflag & AFLAG) + ? names64[base] : names32[base]); + if (havesib) + { + if (index != 4) + { + if (!intel_syntax || havebase) + { + *obufp++ = separator_char; + *obufp = '\0'; + } + oappend (address_mode == mode_64bit && (sizeflag & AFLAG) + ? names64[index] : names32[index]); + } + if (scale != 0 || (!intel_syntax && index != 4)) + { + *obufp++ = scale_char; + *obufp = '\0'; + snprintf (scratchbuf, sizeof(scratchbuf), "%d", 1 << scale); + oappend (scratchbuf); + } + } + if (intel_syntax + && (disp || modrm.mod != 0 || (base & 7) == 5)) + { + if ((bfd_signed_vma) disp >= 0) + { + *obufp++ = '+'; + *obufp = '\0'; + } + else if (modrm.mod != 1) + { + *obufp++ = '-'; + *obufp = '\0'; + disp = - (bfd_signed_vma) disp; + } + + print_displacement (scratchbuf, disp); + oappend (scratchbuf); + } + + *obufp++ = close_char; + *obufp = '\0'; + } + else if (intel_syntax) + { + if (modrm.mod != 0 || (base & 7) == 5) + { + if (prefixes & (PREFIX_CS | PREFIX_SS | PREFIX_DS + | PREFIX_ES | PREFIX_FS | PREFIX_GS)) + ; + else + { + oappend (names_seg[ds_reg - es_reg]); + oappend (":"); + } + print_operand_value (scratchbuf, sizeof(scratchbuf), 1, disp); + oappend (scratchbuf); + } + } + } + else + { /* 16 bit address mode */ + switch (modrm.mod) + { + case 0: + if (modrm.rm == 6) + { + disp = get16 (); + if ((disp & 0x8000) != 0) + disp -= 0x10000; + } + break; + case 1: + fetch_data(the_info, codep + 1); + disp = *codep++; + if ((disp & 0x80) != 0) + disp -= 0x100; + break; + case 2: + disp = get16 (); + if ((disp & 0x8000) != 0) + disp -= 0x10000; + break; + } + + if (!intel_syntax) + if (modrm.mod != 0 || modrm.rm == 6) + { + print_displacement (scratchbuf, disp); + oappend (scratchbuf); + } + + if (modrm.mod != 0 || modrm.rm != 6) + { + *obufp++ = open_char; + *obufp = '\0'; + oappend (index16[modrm.rm]); + if (intel_syntax + && (disp || modrm.mod != 0 || modrm.rm == 6)) + { + if ((bfd_signed_vma) disp >= 0) + { + *obufp++ = '+'; + *obufp = '\0'; + } + else if (modrm.mod != 1) + { + *obufp++ = '-'; + *obufp = '\0'; + disp = - (bfd_signed_vma) disp; + } + + print_displacement (scratchbuf, disp); + oappend (scratchbuf); + } + + *obufp++ = close_char; + *obufp = '\0'; + } + else if (intel_syntax) + { + if (prefixes & (PREFIX_CS | PREFIX_SS | PREFIX_DS + | PREFIX_ES | PREFIX_FS | PREFIX_GS)) + ; + else + { + oappend (names_seg[ds_reg - es_reg]); + oappend (":"); + } + print_operand_value (scratchbuf, sizeof(scratchbuf), 1, + disp & 0xffff); + oappend (scratchbuf); + } + } +} + +static void +OP_G (int bytemode, int sizeflag) +{ + int add = 0; + USED_REX (REX_R); + if (rex & REX_R) + add += 8; + switch (bytemode) + { + case b_mode: + USED_REX (0); + if (rex) + oappend (names8rex[modrm.reg + add]); + else + oappend (names8[modrm.reg + add]); + break; + case w_mode: + oappend (names16[modrm.reg + add]); + break; + case d_mode: + oappend (names32[modrm.reg + add]); + break; + case q_mode: + oappend (names64[modrm.reg + add]); + break; + case v_mode: + case dq_mode: + case dqb_mode: + case dqd_mode: + case dqw_mode: + USED_REX (REX_W); + if (rex & REX_W) + oappend (names64[modrm.reg + add]); + else if ((sizeflag & DFLAG) || bytemode != v_mode) + oappend (names32[modrm.reg + add]); + else + oappend (names16[modrm.reg + add]); + used_prefixes |= (prefixes & PREFIX_DATA); + break; + case m_mode: + if (address_mode == mode_64bit) + oappend (names64[modrm.reg + add]); + else + oappend (names32[modrm.reg + add]); + break; + default: + oappend (INTERNAL_DISASSEMBLER_ERROR); + break; + } +} + +static bfd_vma +get64 (void) +{ + bfd_vma x; +#ifdef BFD64 + unsigned int a; + unsigned int b; + + fetch_data(the_info, codep + 8); + a = *codep++ & 0xff; + a |= (*codep++ & 0xff) << 8; + a |= (*codep++ & 0xff) << 16; + a |= (*codep++ & 0xff) << 24; + b = *codep++ & 0xff; + b |= (*codep++ & 0xff) << 8; + b |= (*codep++ & 0xff) << 16; + b |= (*codep++ & 0xff) << 24; + x = a + ((bfd_vma) b << 32); +#else + abort (); + x = 0; +#endif + return x; +} + +static bfd_signed_vma +get32 (void) +{ + bfd_signed_vma x = 0; + + fetch_data(the_info, codep + 4); + x = *codep++ & (bfd_signed_vma) 0xff; + x |= (*codep++ & (bfd_signed_vma) 0xff) << 8; + x |= (*codep++ & (bfd_signed_vma) 0xff) << 16; + x |= (*codep++ & (bfd_signed_vma) 0xff) << 24; + return x; +} + +static bfd_signed_vma +get32s (void) +{ + bfd_signed_vma x = 0; + + fetch_data(the_info, codep + 4); + x = *codep++ & (bfd_signed_vma) 0xff; + x |= (*codep++ & (bfd_signed_vma) 0xff) << 8; + x |= (*codep++ & (bfd_signed_vma) 0xff) << 16; + x |= (*codep++ & (bfd_signed_vma) 0xff) << 24; + + x = (x ^ ((bfd_signed_vma) 1 << 31)) - ((bfd_signed_vma) 1 << 31); + + return x; +} + +static int +get16 (void) +{ + int x = 0; + + fetch_data(the_info, codep + 2); + x = *codep++ & 0xff; + x |= (*codep++ & 0xff) << 8; + return x; +} + +static void +set_op (bfd_vma op, int riprel) +{ + op_index[op_ad] = op_ad; + if (address_mode == mode_64bit) + { + op_address[op_ad] = op; + op_riprel[op_ad] = riprel; + } + else + { + /* Mask to get a 32-bit address. */ + op_address[op_ad] = op & 0xffffffff; + op_riprel[op_ad] = riprel & 0xffffffff; + } +} + +static void +OP_REG (int code, int sizeflag) +{ + const char *s; + int add = 0; + USED_REX (REX_B); + if (rex & REX_B) + add = 8; + + switch (code) + { + case ax_reg: case cx_reg: case dx_reg: case bx_reg: + case sp_reg: case bp_reg: case si_reg: case di_reg: + s = names16[code - ax_reg + add]; + break; + case es_reg: case ss_reg: case cs_reg: + case ds_reg: case fs_reg: case gs_reg: + s = names_seg[code - es_reg + add]; + break; + case al_reg: case ah_reg: case cl_reg: case ch_reg: + case dl_reg: case dh_reg: case bl_reg: case bh_reg: + USED_REX (0); + if (rex) + s = names8rex[code - al_reg + add]; + else + s = names8[code - al_reg]; + break; + case rAX_reg: case rCX_reg: case rDX_reg: case rBX_reg: + case rSP_reg: case rBP_reg: case rSI_reg: case rDI_reg: + if (address_mode == mode_64bit && (sizeflag & DFLAG)) + { + s = names64[code - rAX_reg + add]; + break; + } + code += eAX_reg - rAX_reg; + /* Fall through. */ + case eAX_reg: case eCX_reg: case eDX_reg: case eBX_reg: + case eSP_reg: case eBP_reg: case eSI_reg: case eDI_reg: + USED_REX (REX_W); + if (rex & REX_W) + s = names64[code - eAX_reg + add]; + else if (sizeflag & DFLAG) + s = names32[code - eAX_reg + add]; + else + s = names16[code - eAX_reg + add]; + used_prefixes |= (prefixes & PREFIX_DATA); + break; + default: + s = INTERNAL_DISASSEMBLER_ERROR; + break; + } + oappend (s); +} + +static void +OP_IMREG (int code, int sizeflag) +{ + const char *s; + + switch (code) + { + case indir_dx_reg: + if (intel_syntax) + s = "dx"; + else + s = "(%dx)"; + break; + case ax_reg: case cx_reg: case dx_reg: case bx_reg: + case sp_reg: case bp_reg: case si_reg: case di_reg: + s = names16[code - ax_reg]; + break; + case es_reg: case ss_reg: case cs_reg: + case ds_reg: case fs_reg: case gs_reg: + s = names_seg[code - es_reg]; + break; + case al_reg: case ah_reg: case cl_reg: case ch_reg: + case dl_reg: case dh_reg: case bl_reg: case bh_reg: + USED_REX (0); + if (rex) + s = names8rex[code - al_reg]; + else + s = names8[code - al_reg]; + break; + case eAX_reg: case eCX_reg: case eDX_reg: case eBX_reg: + case eSP_reg: case eBP_reg: case eSI_reg: case eDI_reg: + USED_REX (REX_W); + if (rex & REX_W) + s = names64[code - eAX_reg]; + else if (sizeflag & DFLAG) + s = names32[code - eAX_reg]; + else + s = names16[code - eAX_reg]; + used_prefixes |= (prefixes & PREFIX_DATA); + break; + case z_mode_ax_reg: + if ((rex & REX_W) || (sizeflag & DFLAG)) + s = *names32; + else + s = *names16; + if (!(rex & REX_W)) + used_prefixes |= (prefixes & PREFIX_DATA); + break; + default: + s = INTERNAL_DISASSEMBLER_ERROR; + break; + } + oappend (s); +} + +static void +OP_I (int bytemode, int sizeflag) +{ + bfd_signed_vma op; + bfd_signed_vma mask = -1; + + switch (bytemode) + { + case b_mode: + fetch_data(the_info, codep + 1); + op = *codep++; + mask = 0xff; + break; + case q_mode: + if (address_mode == mode_64bit) + { + op = get32s (); + break; + } + /* Fall through. */ + case v_mode: + USED_REX (REX_W); + if (rex & REX_W) + op = get32s (); + else if (sizeflag & DFLAG) + { + op = get32 (); + mask = 0xffffffff; + } + else + { + op = get16 (); + mask = 0xfffff; + } + used_prefixes |= (prefixes & PREFIX_DATA); + break; + case w_mode: + mask = 0xfffff; + op = get16 (); + break; + case const_1_mode: + if (intel_syntax) + oappend ("1"); + return; + default: + oappend (INTERNAL_DISASSEMBLER_ERROR); + return; + } + + op &= mask; + scratchbuf[0] = '$'; + print_operand_value (scratchbuf + 1, sizeof(scratchbuf) - 1, 1, op); + oappend (scratchbuf + intel_syntax); + scratchbuf[0] = '\0'; +} + +static void +OP_I64 (int bytemode, int sizeflag) +{ + bfd_signed_vma op; + bfd_signed_vma mask = -1; + + if (address_mode != mode_64bit) + { + OP_I (bytemode, sizeflag); + return; + } + + switch (bytemode) + { + case b_mode: + fetch_data(the_info, codep + 1); + op = *codep++; + mask = 0xff; + break; + case v_mode: + USED_REX (REX_W); + if (rex & REX_W) + op = get64 (); + else if (sizeflag & DFLAG) + { + op = get32 (); + mask = 0xffffffff; + } + else + { + op = get16 (); + mask = 0xfffff; + } + used_prefixes |= (prefixes & PREFIX_DATA); + break; + case w_mode: + mask = 0xfffff; + op = get16 (); + break; + default: + oappend (INTERNAL_DISASSEMBLER_ERROR); + return; + } + + op &= mask; + scratchbuf[0] = '$'; + print_operand_value (scratchbuf + 1, sizeof(scratchbuf) - 1, 1, op); + oappend (scratchbuf + intel_syntax); + scratchbuf[0] = '\0'; +} + +static void +OP_sI (int bytemode, int sizeflag) +{ + bfd_signed_vma op; + + switch (bytemode) + { + case b_mode: + fetch_data(the_info, codep + 1); + op = *codep++; + if ((op & 0x80) != 0) + op -= 0x100; + break; + case v_mode: + USED_REX (REX_W); + if (rex & REX_W) + op = get32s (); + else if (sizeflag & DFLAG) + { + op = get32s (); + } + else + { + op = get16 (); + if ((op & 0x8000) != 0) + op -= 0x10000; + } + used_prefixes |= (prefixes & PREFIX_DATA); + break; + case w_mode: + op = get16 (); + if ((op & 0x8000) != 0) + op -= 0x10000; + break; + default: + oappend (INTERNAL_DISASSEMBLER_ERROR); + return; + } + + scratchbuf[0] = '$'; + print_operand_value (scratchbuf + 1, sizeof(scratchbuf) - 1, 1, op); + oappend (scratchbuf + intel_syntax); +} + +static void +OP_J (int bytemode, int sizeflag) +{ + bfd_vma disp; + bfd_vma mask = -1; + bfd_vma segment = 0; + + switch (bytemode) + { + case b_mode: + fetch_data(the_info, codep + 1); + disp = *codep++; + if ((disp & 0x80) != 0) + disp -= 0x100; + break; + case v_mode: + if ((sizeflag & DFLAG) || (rex & REX_W)) + disp = get32s (); + else + { + disp = get16 (); + if ((disp & 0x8000) != 0) + disp -= 0x10000; + /* In 16bit mode, address is wrapped around at 64k within + the same segment. Otherwise, a data16 prefix on a jump + instruction means that the pc is masked to 16 bits after + the displacement is added! */ + mask = 0xffff; + if ((prefixes & PREFIX_DATA) == 0) + segment = ((start_pc + codep - start_codep) + & ~((bfd_vma) 0xffff)); + } + used_prefixes |= (prefixes & PREFIX_DATA); + break; + default: + oappend (INTERNAL_DISASSEMBLER_ERROR); + return; + } + disp = ((start_pc + codep - start_codep + disp) & mask) | segment; + set_op (disp, 0); + print_operand_value (scratchbuf, sizeof(scratchbuf), 1, disp); + oappend (scratchbuf); +} + +static void +OP_SEG (int bytemode, int sizeflag) +{ + if (bytemode == w_mode) + oappend (names_seg[modrm.reg]); + else + OP_E (modrm.mod == 3 ? bytemode : w_mode, sizeflag); +} + +static void +OP_DIR (int dummy ATTRIBUTE_UNUSED, int sizeflag) +{ + int seg, offset; + + if (sizeflag & DFLAG) + { + offset = get32 (); + seg = get16 (); + } + else + { + offset = get16 (); + seg = get16 (); + } + used_prefixes |= (prefixes & PREFIX_DATA); + if (intel_syntax) + snprintf (scratchbuf, sizeof(scratchbuf), "0x%x:0x%x", seg, offset); + else + snprintf (scratchbuf, sizeof(scratchbuf), "$0x%x,$0x%x", seg, offset); + oappend (scratchbuf); +} + +static void +OP_OFF (int bytemode, int sizeflag) +{ + bfd_vma off; + + if (intel_syntax && (sizeflag & SUFFIX_ALWAYS)) + intel_operand_size (bytemode, sizeflag); + append_seg (); + + if ((sizeflag & AFLAG) || address_mode == mode_64bit) + off = get32 (); + else + off = get16 (); + + if (intel_syntax) + { + if (!(prefixes & (PREFIX_CS | PREFIX_SS | PREFIX_DS + | PREFIX_ES | PREFIX_FS | PREFIX_GS))) + { + oappend (names_seg[ds_reg - es_reg]); + oappend (":"); + } + } + print_operand_value (scratchbuf, sizeof(scratchbuf), 1, off); + oappend (scratchbuf); +} + +static void +OP_OFF64 (int bytemode, int sizeflag) +{ + bfd_vma off; + + if (address_mode != mode_64bit + || (prefixes & PREFIX_ADDR)) + { + OP_OFF (bytemode, sizeflag); + return; + } + + if (intel_syntax && (sizeflag & SUFFIX_ALWAYS)) + intel_operand_size (bytemode, sizeflag); + append_seg (); + + off = get64 (); + + if (intel_syntax) + { + if (!(prefixes & (PREFIX_CS | PREFIX_SS | PREFIX_DS + | PREFIX_ES | PREFIX_FS | PREFIX_GS))) + { + oappend (names_seg[ds_reg - es_reg]); + oappend (":"); + } + } + print_operand_value (scratchbuf, sizeof(scratchbuf), 1, off); + oappend (scratchbuf); +} + +static void +ptr_reg (int code, int sizeflag) +{ + const char *s; + + *obufp++ = open_char; + used_prefixes |= (prefixes & PREFIX_ADDR); + if (address_mode == mode_64bit) + { + if (!(sizeflag & AFLAG)) + s = names32[code - eAX_reg]; + else + s = names64[code - eAX_reg]; + } + else if (sizeflag & AFLAG) + s = names32[code - eAX_reg]; + else + s = names16[code - eAX_reg]; + oappend (s); + *obufp++ = close_char; + *obufp = 0; +} + +static void +OP_ESreg (int code, int sizeflag) +{ + if (intel_syntax) + { + switch (codep[-1]) + { + case 0x6d: /* insw/insl */ + intel_operand_size (z_mode, sizeflag); + break; + case 0xa5: /* movsw/movsl/movsq */ + case 0xa7: /* cmpsw/cmpsl/cmpsq */ + case 0xab: /* stosw/stosl */ + case 0xaf: /* scasw/scasl */ + intel_operand_size (v_mode, sizeflag); + break; + default: + intel_operand_size (b_mode, sizeflag); + } + } + oappend ("%es:" + intel_syntax); + ptr_reg (code, sizeflag); +} + +static void +OP_DSreg (int code, int sizeflag) +{ + if (intel_syntax) + { + switch (codep[-1]) + { + case 0x6f: /* outsw/outsl */ + intel_operand_size (z_mode, sizeflag); + break; + case 0xa5: /* movsw/movsl/movsq */ + case 0xa7: /* cmpsw/cmpsl/cmpsq */ + case 0xad: /* lodsw/lodsl/lodsq */ + intel_operand_size (v_mode, sizeflag); + break; + default: + intel_operand_size (b_mode, sizeflag); + } + } + if ((prefixes + & (PREFIX_CS + | PREFIX_DS + | PREFIX_SS + | PREFIX_ES + | PREFIX_FS + | PREFIX_GS)) == 0) + prefixes |= PREFIX_DS; + append_seg (); + ptr_reg (code, sizeflag); +} + +static void +OP_C (int dummy ATTRIBUTE_UNUSED, int sizeflag ATTRIBUTE_UNUSED) +{ + int add = 0; + if (rex & REX_R) + { + USED_REX (REX_R); + add = 8; + } + else if (address_mode != mode_64bit && (prefixes & PREFIX_LOCK)) + { + used_prefixes |= PREFIX_LOCK; + add = 8; + } + snprintf (scratchbuf, sizeof(scratchbuf), "%%cr%d", modrm.reg + add); + oappend (scratchbuf + intel_syntax); +} + +static void +OP_D (int dummy ATTRIBUTE_UNUSED, int sizeflag ATTRIBUTE_UNUSED) +{ + int add = 0; + USED_REX (REX_R); + if (rex & REX_R) + add = 8; + if (intel_syntax) + snprintf (scratchbuf, sizeof(scratchbuf), "db%d", modrm.reg + add); + else + snprintf (scratchbuf, sizeof(scratchbuf), "%%db%d", modrm.reg + add); + oappend (scratchbuf); +} + +static void +OP_T (int dummy ATTRIBUTE_UNUSED, int sizeflag ATTRIBUTE_UNUSED) +{ + snprintf (scratchbuf, sizeof(scratchbuf), "%%tr%d", modrm.reg); + oappend (scratchbuf + intel_syntax); +} + +static void +OP_R (int bytemode, int sizeflag) +{ + if (modrm.mod == 3) + OP_E (bytemode, sizeflag); + else + BadOp (); +} + +static void +OP_MMX (int bytemode ATTRIBUTE_UNUSED, int sizeflag ATTRIBUTE_UNUSED) +{ + used_prefixes |= (prefixes & PREFIX_DATA); + if (prefixes & PREFIX_DATA) + { + int add = 0; + USED_REX (REX_R); + if (rex & REX_R) + add = 8; + snprintf (scratchbuf, sizeof(scratchbuf), "%%xmm%d", modrm.reg + add); + } + else + snprintf (scratchbuf, sizeof(scratchbuf), "%%mm%d", modrm.reg); + oappend (scratchbuf + intel_syntax); +} + +static void +OP_XMM (int bytemode ATTRIBUTE_UNUSED, int sizeflag ATTRIBUTE_UNUSED) +{ + int add = 0; + USED_REX (REX_R); + if (rex & REX_R) + add = 8; + snprintf (scratchbuf, sizeof(scratchbuf), "%%xmm%d", modrm.reg + add); + oappend (scratchbuf + intel_syntax); +} + +static void +OP_EM (int bytemode, int sizeflag) +{ + if (modrm.mod != 3) + { + if (intel_syntax && bytemode == v_mode) + { + bytemode = (prefixes & PREFIX_DATA) ? x_mode : q_mode; + used_prefixes |= (prefixes & PREFIX_DATA); + } + OP_E (bytemode, sizeflag); + return; + } + + /* Skip mod/rm byte. */ + MODRM_CHECK; + codep++; + used_prefixes |= (prefixes & PREFIX_DATA); + if (prefixes & PREFIX_DATA) + { + int add = 0; + + USED_REX (REX_B); + if (rex & REX_B) + add = 8; + snprintf (scratchbuf, sizeof(scratchbuf), "%%xmm%d", modrm.rm + add); + } + else + snprintf (scratchbuf, sizeof(scratchbuf), "%%mm%d", modrm.rm); + oappend (scratchbuf + intel_syntax); +} + +/* cvt* are the only instructions in sse2 which have + both SSE and MMX operands and also have 0x66 prefix + in their opcode. 0x66 was originally used to differentiate + between SSE and MMX instruction(operands). So we have to handle the + cvt* separately using OP_EMC and OP_MXC */ +static void +OP_EMC (int bytemode, int sizeflag) +{ + if (modrm.mod != 3) + { + if (intel_syntax && bytemode == v_mode) + { + bytemode = (prefixes & PREFIX_DATA) ? x_mode : q_mode; + used_prefixes |= (prefixes & PREFIX_DATA); + } + OP_E (bytemode, sizeflag); + return; + } + + /* Skip mod/rm byte. */ + MODRM_CHECK; + codep++; + used_prefixes |= (prefixes & PREFIX_DATA); + snprintf (scratchbuf, sizeof(scratchbuf), "%%mm%d", modrm.rm); + oappend (scratchbuf + intel_syntax); +} + +static void +OP_MXC (int bytemode ATTRIBUTE_UNUSED, int sizeflag ATTRIBUTE_UNUSED) +{ + used_prefixes |= (prefixes & PREFIX_DATA); + snprintf (scratchbuf, sizeof(scratchbuf), "%%mm%d", modrm.reg); + oappend (scratchbuf + intel_syntax); +} + +static void +OP_EX (int bytemode, int sizeflag) +{ + int add = 0; + if (modrm.mod != 3) + { + OP_E (bytemode, sizeflag); + return; + } + USED_REX (REX_B); + if (rex & REX_B) + add = 8; + + /* Skip mod/rm byte. */ + MODRM_CHECK; + codep++; + snprintf (scratchbuf, sizeof(scratchbuf), "%%xmm%d", modrm.rm + add); + oappend (scratchbuf + intel_syntax); +} + +static void +OP_MS (int bytemode, int sizeflag) +{ + if (modrm.mod == 3) + OP_EM (bytemode, sizeflag); + else + BadOp (); +} + +static void +OP_XS (int bytemode, int sizeflag) +{ + if (modrm.mod == 3) + OP_EX (bytemode, sizeflag); + else + BadOp (); +} + +static void +OP_M (int bytemode, int sizeflag) +{ + if (modrm.mod == 3) + /* bad bound,lea,lds,les,lfs,lgs,lss,cmpxchg8b,vmptrst modrm */ + BadOp (); + else + OP_E (bytemode, sizeflag); +} + +static void +OP_0f07 (int bytemode, int sizeflag) +{ + if (modrm.mod != 3 || modrm.rm != 0) + BadOp (); + else + OP_E (bytemode, sizeflag); +} + +static void +OP_0fae (int bytemode, int sizeflag) +{ + if (modrm.mod == 3) + { + if (modrm.reg == 7) + strcpy (obuf + strlen (obuf) - sizeof ("clflush") + 1, "sfence"); + + if (modrm.reg < 5 || modrm.rm != 0) + { + BadOp (); /* bad sfence, mfence, or lfence */ + return; + } + } + else if (modrm.reg != 7) + { + BadOp (); /* bad clflush */ + return; + } + + OP_E (bytemode, sizeflag); +} + +/* NOP is an alias of "xchg %ax,%ax" in 16bit mode, "xchg %eax,%eax" in + 32bit mode and "xchg %rax,%rax" in 64bit mode. */ + +static void +NOP_Fixup1 (int bytemode, int sizeflag) +{ + if ((prefixes & PREFIX_DATA) != 0 + || (rex != 0 + && rex != 0x48 + && address_mode == mode_64bit)) + OP_REG (bytemode, sizeflag); + else + strcpy (obuf, "nop"); +} + +static void +NOP_Fixup2 (int bytemode, int sizeflag) +{ + if ((prefixes & PREFIX_DATA) != 0 + || (rex != 0 + && rex != 0x48 + && address_mode == mode_64bit)) + OP_IMREG (bytemode, sizeflag); +} + +static const char *Suffix3DNow[] = { +/* 00 */ NULL, NULL, NULL, NULL, +/* 04 */ NULL, NULL, NULL, NULL, +/* 08 */ NULL, NULL, NULL, NULL, +/* 0C */ "pi2fw", "pi2fd", NULL, NULL, +/* 10 */ NULL, NULL, NULL, NULL, +/* 14 */ NULL, NULL, NULL, NULL, +/* 18 */ NULL, NULL, NULL, NULL, +/* 1C */ "pf2iw", "pf2id", NULL, NULL, +/* 20 */ NULL, NULL, NULL, NULL, +/* 24 */ NULL, NULL, NULL, NULL, +/* 28 */ NULL, NULL, NULL, NULL, +/* 2C */ NULL, NULL, NULL, NULL, +/* 30 */ NULL, NULL, NULL, NULL, +/* 34 */ NULL, NULL, NULL, NULL, +/* 38 */ NULL, NULL, NULL, NULL, +/* 3C */ NULL, NULL, NULL, NULL, +/* 40 */ NULL, NULL, NULL, NULL, +/* 44 */ NULL, NULL, NULL, NULL, +/* 48 */ NULL, NULL, NULL, NULL, +/* 4C */ NULL, NULL, NULL, NULL, +/* 50 */ NULL, NULL, NULL, NULL, +/* 54 */ NULL, NULL, NULL, NULL, +/* 58 */ NULL, NULL, NULL, NULL, +/* 5C */ NULL, NULL, NULL, NULL, +/* 60 */ NULL, NULL, NULL, NULL, +/* 64 */ NULL, NULL, NULL, NULL, +/* 68 */ NULL, NULL, NULL, NULL, +/* 6C */ NULL, NULL, NULL, NULL, +/* 70 */ NULL, NULL, NULL, NULL, +/* 74 */ NULL, NULL, NULL, NULL, +/* 78 */ NULL, NULL, NULL, NULL, +/* 7C */ NULL, NULL, NULL, NULL, +/* 80 */ NULL, NULL, NULL, NULL, +/* 84 */ NULL, NULL, NULL, NULL, +/* 88 */ NULL, NULL, "pfnacc", NULL, +/* 8C */ NULL, NULL, "pfpnacc", NULL, +/* 90 */ "pfcmpge", NULL, NULL, NULL, +/* 94 */ "pfmin", NULL, "pfrcp", "pfrsqrt", +/* 98 */ NULL, NULL, "pfsub", NULL, +/* 9C */ NULL, NULL, "pfadd", NULL, +/* A0 */ "pfcmpgt", NULL, NULL, NULL, +/* A4 */ "pfmax", NULL, "pfrcpit1", "pfrsqit1", +/* A8 */ NULL, NULL, "pfsubr", NULL, +/* AC */ NULL, NULL, "pfacc", NULL, +/* B0 */ "pfcmpeq", NULL, NULL, NULL, +/* B4 */ "pfmul", NULL, "pfrcpit2", "pmulhrw", +/* B8 */ NULL, NULL, NULL, "pswapd", +/* BC */ NULL, NULL, NULL, "pavgusb", +/* C0 */ NULL, NULL, NULL, NULL, +/* C4 */ NULL, NULL, NULL, NULL, +/* C8 */ NULL, NULL, NULL, NULL, +/* CC */ NULL, NULL, NULL, NULL, +/* D0 */ NULL, NULL, NULL, NULL, +/* D4 */ NULL, NULL, NULL, NULL, +/* D8 */ NULL, NULL, NULL, NULL, +/* DC */ NULL, NULL, NULL, NULL, +/* E0 */ NULL, NULL, NULL, NULL, +/* E4 */ NULL, NULL, NULL, NULL, +/* E8 */ NULL, NULL, NULL, NULL, +/* EC */ NULL, NULL, NULL, NULL, +/* F0 */ NULL, NULL, NULL, NULL, +/* F4 */ NULL, NULL, NULL, NULL, +/* F8 */ NULL, NULL, NULL, NULL, +/* FC */ NULL, NULL, NULL, NULL, +}; + +static void +OP_3DNowSuffix (int bytemode ATTRIBUTE_UNUSED, int sizeflag ATTRIBUTE_UNUSED) +{ + const char *mnemonic; + + fetch_data(the_info, codep + 1); + /* AMD 3DNow! instructions are specified by an opcode suffix in the + place where an 8-bit immediate would normally go. ie. the last + byte of the instruction. */ + obufp = obuf + strlen (obuf); + mnemonic = Suffix3DNow[*codep++ & 0xff]; + if (mnemonic) + oappend (mnemonic); + else + { + /* Since a variable sized modrm/sib chunk is between the start + of the opcode (0x0f0f) and the opcode suffix, we need to do + all the modrm processing first, and don't know until now that + we have a bad opcode. This necessitates some cleaning up. */ + op_out[0][0] = '\0'; + op_out[1][0] = '\0'; + BadOp (); + } +} + +static const char *simd_cmp_op[] = { + "eq", + "lt", + "le", + "unord", + "neq", + "nlt", + "nle", + "ord" +}; + +static void +OP_SIMD_Suffix (int bytemode ATTRIBUTE_UNUSED, int sizeflag ATTRIBUTE_UNUSED) +{ + unsigned int cmp_type; + + fetch_data(the_info, codep + 1); + obufp = obuf + strlen (obuf); + cmp_type = *codep++ & 0xff; + if (cmp_type < 8) + { + char suffix1 = 'p', suffix2 = 's'; + used_prefixes |= (prefixes & PREFIX_REPZ); + if (prefixes & PREFIX_REPZ) + suffix1 = 's'; + else + { + used_prefixes |= (prefixes & PREFIX_DATA); + if (prefixes & PREFIX_DATA) + suffix2 = 'd'; + else + { + used_prefixes |= (prefixes & PREFIX_REPNZ); + if (prefixes & PREFIX_REPNZ) + suffix1 = 's', suffix2 = 'd'; + } + } + snprintf (scratchbuf, sizeof(scratchbuf), "cmp%s%c%c", + simd_cmp_op[cmp_type], suffix1, suffix2); + used_prefixes |= (prefixes & PREFIX_REPZ); + oappend (scratchbuf); + } + else + { + /* We have a bad extension byte. Clean up. */ + op_out[0][0] = '\0'; + op_out[1][0] = '\0'; + BadOp (); + } +} + +static void +SIMD_Fixup (int extrachar, int sizeflag ATTRIBUTE_UNUSED) +{ + /* Change movlps/movhps to movhlps/movlhps for 2 register operand + forms of these instructions. */ + if (modrm.mod == 3) + { + char *p = obuf + strlen (obuf); + *(p + 1) = '\0'; + *p = *(p - 1); + *(p - 1) = *(p - 2); + *(p - 2) = *(p - 3); + *(p - 3) = extrachar; + } +} + +static void +PNI_Fixup (int extrachar ATTRIBUTE_UNUSED, int sizeflag) +{ + if (modrm.mod == 3 && modrm.reg == 1 && modrm.rm <= 1) + { + /* Override "sidt". */ + size_t olen = strlen (obuf); + char *p = obuf + olen - 4; + const char * const *names = (address_mode == mode_64bit + ? names64 : names32); + + /* We might have a suffix when disassembling with -Msuffix. */ + if (*p == 'i') + --p; + + /* Remove "addr16/addr32" if we aren't in Intel mode. */ + if (!intel_syntax + && (prefixes & PREFIX_ADDR) + && olen >= (4 + 7) + && *(p - 1) == ' ' + && strncmp (p - 7, "addr", 4) == 0 + && (strncmp (p - 3, "16", 2) == 0 + || strncmp (p - 3, "32", 2) == 0)) + p -= 7; + + if (modrm.rm) + { + /* mwait %eax,%ecx */ + strcpy (p, "mwait"); + if (!intel_syntax) + strcpy (op_out[0], names[0]); + } + else + { + /* monitor %eax,%ecx,%edx" */ + strcpy (p, "monitor"); + if (!intel_syntax) + { + const char * const *op1_names; + if (!(prefixes & PREFIX_ADDR)) + op1_names = (address_mode == mode_16bit + ? names16 : names); + else + { + op1_names = (address_mode != mode_32bit + ? names32 : names16); + used_prefixes |= PREFIX_ADDR; + } + strcpy (op_out[0], op1_names[0]); + strcpy (op_out[2], names[2]); + } + } + if (!intel_syntax) + { + strcpy (op_out[1], names[1]); + two_source_ops = 1; + } + + codep++; + } + else + OP_M (0, sizeflag); +} + +static void +SVME_Fixup (int bytemode, int sizeflag) +{ + const char *alt; + char *p; + + switch (*codep) + { + case 0xd8: + alt = "vmrun"; + break; + case 0xd9: + alt = "vmmcall"; + break; + case 0xda: + alt = "vmload"; + break; + case 0xdb: + alt = "vmsave"; + break; + case 0xdc: + alt = "stgi"; + break; + case 0xdd: + alt = "clgi"; + break; + case 0xde: + alt = "skinit"; + break; + case 0xdf: + alt = "invlpga"; + break; + default: + OP_M (bytemode, sizeflag); + return; + } + /* Override "lidt". */ + p = obuf + strlen (obuf) - 4; + /* We might have a suffix. */ + if (*p == 'i') + --p; + strcpy (p, alt); + if (!(prefixes & PREFIX_ADDR)) + { + ++codep; + return; + } + used_prefixes |= PREFIX_ADDR; + switch (*codep++) + { + case 0xdf: + strcpy (op_out[1], names32[1]); + two_source_ops = 1; + /* Fall through. */ + case 0xd8: + case 0xda: + case 0xdb: + *obufp++ = open_char; + if (address_mode == mode_64bit || (sizeflag & AFLAG)) + alt = names32[0]; + else + alt = names16[0]; + strcpy (obufp, alt); + obufp += strlen (alt); + *obufp++ = close_char; + *obufp = '\0'; + break; + } +} + +static void +INVLPG_Fixup (int bytemode, int sizeflag) +{ + const char *alt; + + switch (*codep) + { + case 0xf8: + alt = "swapgs"; + break; + case 0xf9: + alt = "rdtscp"; + break; + default: + OP_M (bytemode, sizeflag); + return; + } + /* Override "invlpg". */ + strcpy (obuf + strlen (obuf) - 6, alt); + codep++; +} + +static void +BadOp (void) +{ + /* Throw away prefixes and 1st. opcode byte. */ + codep = insn_codep + 1; + oappend ("(bad)"); +} + +static void +VMX_Fixup (int extrachar ATTRIBUTE_UNUSED, int sizeflag) +{ + if (modrm.mod == 3 + && modrm.reg == 0 + && modrm.rm >=1 + && modrm.rm <= 4) + { + /* Override "sgdt". */ + char *p = obuf + strlen (obuf) - 4; + + /* We might have a suffix when disassembling with -Msuffix. */ + if (*p == 'g') + --p; + + switch (modrm.rm) + { + case 1: + strcpy (p, "vmcall"); + break; + case 2: + strcpy (p, "vmlaunch"); + break; + case 3: + strcpy (p, "vmresume"); + break; + case 4: + strcpy (p, "vmxoff"); + break; + } + + codep++; + } + else + OP_E (0, sizeflag); +} + +static void +OP_VMX (int bytemode, int sizeflag) +{ + used_prefixes |= (prefixes & (PREFIX_DATA | PREFIX_REPZ)); + if (prefixes & PREFIX_DATA) + strcpy (obuf, "vmclear"); + else if (prefixes & PREFIX_REPZ) + strcpy (obuf, "vmxon"); + else + strcpy (obuf, "vmptrld"); + OP_E (bytemode, sizeflag); +} + +static void +REP_Fixup (int bytemode, int sizeflag) +{ + /* The 0xf3 prefix should be displayed as "rep" for ins, outs, movs, + lods and stos. */ + size_t ilen = 0; + + if (prefixes & PREFIX_REPZ) + switch (*insn_codep) + { + case 0x6e: /* outsb */ + case 0x6f: /* outsw/outsl */ + case 0xa4: /* movsb */ + case 0xa5: /* movsw/movsl/movsq */ + if (!intel_syntax) + ilen = 5; + else + ilen = 4; + break; + case 0xaa: /* stosb */ + case 0xab: /* stosw/stosl/stosq */ + case 0xac: /* lodsb */ + case 0xad: /* lodsw/lodsl/lodsq */ + if (!intel_syntax && (sizeflag & SUFFIX_ALWAYS)) + ilen = 5; + else + ilen = 4; + break; + case 0x6c: /* insb */ + case 0x6d: /* insl/insw */ + if (!intel_syntax) + ilen = 4; + else + ilen = 3; + break; + default: + abort (); + break; + } + + if (ilen != 0) + { + size_t olen; + char *p; + + olen = strlen (obuf); + p = obuf + olen - ilen - 1 - 4; + /* Handle "repz [addr16|addr32]". */ + if ((prefixes & PREFIX_ADDR)) + p -= 1 + 6; + + memmove (p + 3, p + 4, olen - (p + 3 - obuf)); + } + + switch (bytemode) + { + case al_reg: + case eAX_reg: + case indir_dx_reg: + OP_IMREG (bytemode, sizeflag); + break; + case eDI_reg: + OP_ESreg (bytemode, sizeflag); + break; + case eSI_reg: + OP_DSreg (bytemode, sizeflag); + break; + default: + abort (); + break; + } +} + +static void +CMPXCHG8B_Fixup (int bytemode, int sizeflag) +{ + USED_REX (REX_W); + if (rex & REX_W) + { + /* Change cmpxchg8b to cmpxchg16b. */ + char *p = obuf + strlen (obuf) - 2; + strcpy (p, "16b"); + bytemode = o_mode; + } + OP_M (bytemode, sizeflag); +} + +static void +XMM_Fixup (int reg, int sizeflag ATTRIBUTE_UNUSED) +{ + snprintf (scratchbuf, sizeof(scratchbuf), "%%xmm%d", reg); + oappend (scratchbuf + intel_syntax); +} + +static void +CRC32_Fixup (int bytemode, int sizeflag) +{ + /* Add proper suffix to "crc32". */ + char *p = obuf + strlen (obuf); + + switch (bytemode) + { + case b_mode: + if (intel_syntax) + break; + + *p++ = 'b'; + break; + case v_mode: + if (intel_syntax) + break; + + USED_REX (REX_W); + if (rex & REX_W) + *p++ = 'q'; + else if (sizeflag & DFLAG) + *p++ = 'l'; + else + *p++ = 'w'; + used_prefixes |= (prefixes & PREFIX_DATA); + break; + default: + oappend (INTERNAL_DISASSEMBLER_ERROR); + break; + } + *p = '\0'; + + if (modrm.mod == 3) + { + int add; + + /* Skip mod/rm byte. */ + MODRM_CHECK; + codep++; + + USED_REX (REX_B); + add = (rex & REX_B) ? 8 : 0; + if (bytemode == b_mode) + { + USED_REX (0); + if (rex) + oappend (names8rex[modrm.rm + add]); + else + oappend (names8[modrm.rm + add]); + } + else + { + USED_REX (REX_W); + if (rex & REX_W) + oappend (names64[modrm.rm + add]); + else if ((prefixes & PREFIX_DATA)) + oappend (names16[modrm.rm + add]); + else + oappend (names32[modrm.rm + add]); + } + } + else + OP_E (bytemode, sizeflag); +} diff --git a/i386.ld b/i386.ld new file mode 100644 index 0000000..f8df7bf --- /dev/null +++ b/i386.ld @@ -0,0 +1,153 @@ +/* ld script to make i386 Linux kernel + * Written by Martin Mares ; + */ +OUTPUT_FORMAT("elf32-i386", "elf32-i386", "elf32-i386") +OUTPUT_ARCH(i386) +ENTRY(_start) +SECTIONS +{ + /* Read-only sections, merged into text segment: */ + . = 0x60000000 + SIZEOF_HEADERS; + .interp : { *(.interp) } + .hash : { *(.hash) } + .dynsym : { *(.dynsym) } + .dynstr : { *(.dynstr) } + .gnu.version : { *(.gnu.version) } + .gnu.version_d : { *(.gnu.version_d) } + .gnu.version_r : { *(.gnu.version_r) } + .rel.text : + { *(.rel.text) *(.rel.gnu.linkonce.t*) } + .rela.text : + { *(.rela.text) *(.rela.gnu.linkonce.t*) } + .rel.data : + { *(.rel.data) *(.rel.gnu.linkonce.d*) } + .rela.data : + { *(.rela.data) *(.rela.gnu.linkonce.d*) } + .rel.rodata : + { *(.rel.rodata) *(.rel.gnu.linkonce.r*) } + .rela.rodata : + { *(.rela.rodata) *(.rela.gnu.linkonce.r*) } + .rel.got : { *(.rel.got) } + .rela.got : { *(.rela.got) } + .rel.ctors : { *(.rel.ctors) } + .rela.ctors : { *(.rela.ctors) } + .rel.dtors : { *(.rel.dtors) } + .rela.dtors : { *(.rela.dtors) } + .rel.init : { *(.rel.init) } + .rela.init : { *(.rela.init) } + .rel.fini : { *(.rel.fini) } + .rela.fini : { *(.rela.fini) } + .rel.bss : { *(.rel.bss) } + .rela.bss : { *(.rela.bss) } + .rel.plt : + { + *(.rel.plt) + PROVIDE_HIDDEN (__rel_iplt_start = .); + *(.rel.iplt) + PROVIDE_HIDDEN (__rel_iplt_end = .); + } + .rela.plt : + { + *(.rela.plt) + PROVIDE_HIDDEN (__rela_iplt_start = .); + *(.rela.iplt) + PROVIDE_HIDDEN (__rela_iplt_end = .); + } + .init : { *(.init) } =0x47ff041f + .text : + { + *(.text) + /* .gnu.warning sections are handled specially by elf32.em. */ + *(.gnu.warning) + *(.gnu.linkonce.t*) + } =0x47ff041f + _etext = .; + PROVIDE (etext = .); + .fini : { *(.fini) } =0x47ff041f + . = ALIGN(32 / 8); + PROVIDE (__preinit_array_start = .); + .preinit_array : { *(.preinit_array) } + PROVIDE (__preinit_array_end = .); + PROVIDE (__init_array_start = .); + .init_array : { *(.init_array) } + PROVIDE (__init_array_end = .); + PROVIDE (__fini_array_start = .); + .fini_array : { *(.fini_array) } + PROVIDE (__fini_array_end = .); + .rodata : { *(.rodata) *(.gnu.linkonce.r*) } + .rodata1 : { *(.rodata1) } + .reginfo : { *(.reginfo) } + /* Adjust the address for the data segment. We want to adjust up to + the same address within the page on the next page up. */ + . = ALIGN(0x100000) + (. & (0x100000 - 1)); + .tdata : { *(.tdata .tdata.* .gnu.linkonce.td.*) } + .tbss : { *(.tbss .tbss.* .gnu.linkonce.tb.*) *(.tcommon) } + .data : + { + *(.data) + *(.gnu.linkonce.d*) + CONSTRUCTORS + } + .data1 : { *(.data1) } + .ctors : + { + *(.ctors) + } + .dtors : + { + *(.dtors) + } + .plt : { *(.plt) } + .got : { *(.got.plt) *(.got) } + .dynamic : { *(.dynamic) } + /* We want the small data sections together, so single-instruction offsets + can access them all, and initialized data all before uninitialized, so + we can shorten the on-disk segment size. */ + .sdata : { *(.sdata) } + _edata = .; + PROVIDE (edata = .); + __bss_start = .; + .sbss : { *(.sbss) *(.scommon) } + .bss : + { + *(.dynbss) + *(.bss) + *(COMMON) + } + _end = . ; + PROVIDE (end = .); + /* Stabs debugging sections. */ + .stab 0 : { *(.stab) } + .stabstr 0 : { *(.stabstr) } + .stab.excl 0 : { *(.stab.excl) } + .stab.exclstr 0 : { *(.stab.exclstr) } + .stab.index 0 : { *(.stab.index) } + .stab.indexstr 0 : { *(.stab.indexstr) } + .comment 0 : { *(.comment) } + /* DWARF debug sections. + Symbols in the DWARF debugging sections are relative to the beginning + of the section so we begin them at 0. */ + /* DWARF 1 */ + .debug 0 : { *(.debug) } + .line 0 : { *(.line) } + /* GNU DWARF 1 extensions */ + .debug_srcinfo 0 : { *(.debug_srcinfo) } + .debug_sfnames 0 : { *(.debug_sfnames) } + /* DWARF 1.1 and DWARF 2 */ + .debug_aranges 0 : { *(.debug_aranges) } + .debug_pubnames 0 : { *(.debug_pubnames) } + /* DWARF 2 */ + .debug_info 0 : { *(.debug_info) } + .debug_abbrev 0 : { *(.debug_abbrev) } + .debug_line 0 : { *(.debug_line) } + .debug_frame 0 : { *(.debug_frame) } + .debug_str 0 : { *(.debug_str) } + .debug_loc 0 : { *(.debug_loc) } + .debug_macinfo 0 : { *(.debug_macinfo) } + /* SGI/MIPS DWARF 2 extensions */ + .debug_weaknames 0 : { *(.debug_weaknames) } + .debug_funcnames 0 : { *(.debug_funcnames) } + .debug_typenames 0 : { *(.debug_typenames) } + .debug_varnames 0 : { *(.debug_varnames) } + /* These must appear regardless of . */ +} diff --git a/ia64-dis.c b/ia64-dis.c new file mode 100644 index 0000000..2886df3 --- /dev/null +++ b/ia64-dis.c @@ -0,0 +1,10599 @@ +/* ia64-dis.c -- Disassemble ia64 instructions + Copyright 1998, 1999, 2000, 2002 Free Software Foundation, Inc. + Contributed by David Mosberger-Tang + + This file is part of GDB, GAS, and the GNU binutils. + + GDB, GAS, and the GNU binutils are free software; you can redistribute + them and/or modify them 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. + + GDB, GAS, and the GNU binutils are distributed in the hope that they + 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 file; see the file COPYING. If not, see + . */ + +#include +#include + +#include "dis-asm.h" + +/* ia64.h -- Header file for ia64 opcode table + Copyright (C) 1998, 1999, 2000, 2002, 2005, 2006 + Free Software Foundation, Inc. + Contributed by David Mosberger-Tang */ + +#include + +typedef uint64_t ia64_insn; + +enum ia64_insn_type + { + IA64_TYPE_NIL = 0, /* illegal type */ + IA64_TYPE_A, /* integer alu (I- or M-unit) */ + IA64_TYPE_I, /* non-alu integer (I-unit) */ + IA64_TYPE_M, /* memory (M-unit) */ + IA64_TYPE_B, /* branch (B-unit) */ + IA64_TYPE_F, /* floating-point (F-unit) */ + IA64_TYPE_X, /* long encoding (X-unit) */ + IA64_TYPE_DYN, /* Dynamic opcode */ + IA64_NUM_TYPES + }; + +enum ia64_unit + { + IA64_UNIT_NIL = 0, /* illegal unit */ + IA64_UNIT_I, /* integer unit */ + IA64_UNIT_M, /* memory unit */ + IA64_UNIT_B, /* branching unit */ + IA64_UNIT_F, /* floating-point unit */ + IA64_UNIT_L, /* long "unit" */ + IA64_UNIT_X, /* may be integer or branch unit */ + IA64_NUM_UNITS + }; + +/* Changes to this enumeration must be propagated to the operand table in + bfd/cpu-ia64-opc.c + */ +enum ia64_opnd + { + IA64_OPND_NIL, /* no operand---MUST BE FIRST!*/ + + /* constants */ + IA64_OPND_AR_CSD, /* application register csd (ar.csd) */ + IA64_OPND_AR_CCV, /* application register ccv (ar.ccv) */ + IA64_OPND_AR_PFS, /* application register pfs (ar.pfs) */ + IA64_OPND_C1, /* the constant 1 */ + IA64_OPND_C8, /* the constant 8 */ + IA64_OPND_C16, /* the constant 16 */ + IA64_OPND_GR0, /* gr0 */ + IA64_OPND_IP, /* instruction pointer (ip) */ + IA64_OPND_PR, /* predicate register (pr) */ + IA64_OPND_PR_ROT, /* rotating predicate register (pr.rot) */ + IA64_OPND_PSR, /* processor status register (psr) */ + IA64_OPND_PSR_L, /* processor status register L (psr.l) */ + IA64_OPND_PSR_UM, /* processor status register UM (psr.um) */ + + /* register operands: */ + IA64_OPND_AR3, /* third application register # (bits 20-26) */ + IA64_OPND_B1, /* branch register # (bits 6-8) */ + IA64_OPND_B2, /* branch register # (bits 13-15) */ + IA64_OPND_CR3, /* third control register # (bits 20-26) */ + IA64_OPND_F1, /* first floating-point register # */ + IA64_OPND_F2, /* second floating-point register # */ + IA64_OPND_F3, /* third floating-point register # */ + IA64_OPND_F4, /* fourth floating-point register # */ + IA64_OPND_P1, /* first predicate # */ + IA64_OPND_P2, /* second predicate # */ + IA64_OPND_R1, /* first register # */ + IA64_OPND_R2, /* second register # */ + IA64_OPND_R3, /* third register # */ + IA64_OPND_R3_2, /* third register # (limited to gr0-gr3) */ + + /* memory operands: */ + IA64_OPND_MR3, /* memory at addr of third register # */ + + /* indirect operands: */ + IA64_OPND_CPUID_R3, /* cpuid[reg] */ + IA64_OPND_DBR_R3, /* dbr[reg] */ + IA64_OPND_DTR_R3, /* dtr[reg] */ + IA64_OPND_ITR_R3, /* itr[reg] */ + IA64_OPND_IBR_R3, /* ibr[reg] */ + IA64_OPND_MSR_R3, /* msr[reg] */ + IA64_OPND_PKR_R3, /* pkr[reg] */ + IA64_OPND_PMC_R3, /* pmc[reg] */ + IA64_OPND_PMD_R3, /* pmd[reg] */ + IA64_OPND_RR_R3, /* rr[reg] */ + + /* immediate operands: */ + IA64_OPND_CCNT5, /* 5-bit count (31 - bits 20-24) */ + IA64_OPND_CNT2a, /* 2-bit count (1 + bits 27-28) */ + IA64_OPND_CNT2b, /* 2-bit count (bits 27-28): 1, 2, 3 */ + IA64_OPND_CNT2c, /* 2-bit count (bits 30-31): 0, 7, 15, or 16 */ + IA64_OPND_CNT5, /* 5-bit count (bits 14-18) */ + IA64_OPND_CNT6, /* 6-bit count (bits 27-32) */ + IA64_OPND_CPOS6a, /* 6-bit count (63 - bits 20-25) */ + IA64_OPND_CPOS6b, /* 6-bit count (63 - bits 14-19) */ + IA64_OPND_CPOS6c, /* 6-bit count (63 - bits 31-36) */ + IA64_OPND_IMM1, /* signed 1-bit immediate (bit 36) */ + IA64_OPND_IMMU2, /* unsigned 2-bit immediate (bits 13-14) */ + IA64_OPND_IMMU5b, /* unsigned 5-bit immediate (32 + bits 14-18) */ + IA64_OPND_IMMU7a, /* unsigned 7-bit immediate (bits 13-19) */ + IA64_OPND_IMMU7b, /* unsigned 7-bit immediate (bits 20-26) */ + IA64_OPND_SOF, /* 8-bit stack frame size */ + IA64_OPND_SOL, /* 8-bit size of locals */ + IA64_OPND_SOR, /* 6-bit number of rotating registers (scaled by 8) */ + IA64_OPND_IMM8, /* signed 8-bit immediate (bits 13-19 & 36) */ + IA64_OPND_IMM8U4, /* cmp4*u signed 8-bit immediate (bits 13-19 & 36) */ + IA64_OPND_IMM8M1, /* signed 8-bit immediate -1 (bits 13-19 & 36) */ + IA64_OPND_IMM8M1U4, /* cmp4*u signed 8-bit immediate -1 (bits 13-19 & 36)*/ + IA64_OPND_IMM8M1U8, /* cmp*u signed 8-bit immediate -1 (bits 13-19 & 36) */ + IA64_OPND_IMMU9, /* unsigned 9-bit immediate (bits 33-34, 20-26) */ + IA64_OPND_IMM9a, /* signed 9-bit immediate (bits 6-12, 27, 36) */ + IA64_OPND_IMM9b, /* signed 9-bit immediate (bits 13-19, 27, 36) */ + IA64_OPND_IMM14, /* signed 14-bit immediate (bits 13-19, 27-32, 36) */ + IA64_OPND_IMM17, /* signed 17-bit immediate (2*bits 6-12, 24-31, 36) */ + IA64_OPND_IMMU21, /* unsigned 21-bit immediate (bits 6-25, 36) */ + IA64_OPND_IMM22, /* signed 22-bit immediate (bits 13-19, 22-36) */ + IA64_OPND_IMMU24, /* unsigned 24-bit immediate (bits 6-26, 31-32, 36) */ + IA64_OPND_IMM44, /* signed 44-bit immediate (2^16*bits 6-32, 36) */ + IA64_OPND_IMMU62, /* unsigned 62-bit immediate */ + IA64_OPND_IMMU64, /* unsigned 64-bit immediate (lotsa bits...) */ + IA64_OPND_INC3, /* signed 3-bit (bits 13-15): +/-1, 4, 8, 16 */ + IA64_OPND_LEN4, /* 4-bit count (bits 27-30 + 1) */ + IA64_OPND_LEN6, /* 6-bit count (bits 27-32 + 1) */ + IA64_OPND_MBTYPE4, /* 4-bit mux type (bits 20-23) */ + IA64_OPND_MHTYPE8, /* 8-bit mux type (bits 20-27) */ + IA64_OPND_POS6, /* 6-bit count (bits 14-19) */ + IA64_OPND_TAG13, /* signed 13-bit tag (ip + 16*bits 6-12, 33-34) */ + IA64_OPND_TAG13b, /* signed 13-bit tag (ip + 16*bits 24-32) */ + IA64_OPND_TGT25, /* signed 25-bit (ip + 16*bits 6-25, 36) */ + IA64_OPND_TGT25b, /* signed 25-bit (ip + 16*bits 6-12, 20-32, 36) */ + IA64_OPND_TGT25c, /* signed 25-bit (ip + 16*bits 13-32, 36) */ + IA64_OPND_TGT64, /* 64-bit (ip + 16*bits 13-32, 36, 2-40(L)) */ + IA64_OPND_LDXMOV, /* any symbol, generates R_IA64_LDXMOV. */ + + IA64_OPND_COUNT /* # of operand types (MUST BE LAST!) */ + }; + +enum ia64_dependency_mode +{ + IA64_DV_RAW, + IA64_DV_WAW, + IA64_DV_WAR, +}; + +enum ia64_dependency_semantics +{ + IA64_DVS_NONE, + IA64_DVS_IMPLIED, + IA64_DVS_IMPLIEDF, + IA64_DVS_DATA, + IA64_DVS_INSTR, + IA64_DVS_SPECIFIC, + IA64_DVS_STOP, + IA64_DVS_OTHER, +}; + +enum ia64_resource_specifier +{ + IA64_RS_ANY, + IA64_RS_AR_K, + IA64_RS_AR_UNAT, + IA64_RS_AR, /* 8-15, 20, 22-23, 31, 33-35, 37-39, 41-43, 45-47, 67-111 */ + IA64_RS_ARb, /* 48-63, 112-127 */ + IA64_RS_BR, + IA64_RS_CFM, + IA64_RS_CPUID, + IA64_RS_CR_IRR, + IA64_RS_CR_LRR, + IA64_RS_CR, /* 3-7,10-15,18,26-63,75-79,82-127 */ + IA64_RS_DBR, + IA64_RS_FR, + IA64_RS_FRb, + IA64_RS_GR0, + IA64_RS_GR, + IA64_RS_IBR, + IA64_RS_INSERVICE, /* CR[EOI] or CR[IVR] */ + IA64_RS_MSR, + IA64_RS_PKR, + IA64_RS_PMC, + IA64_RS_PMD, + IA64_RS_PR, /* non-rotating, 1-15 */ + IA64_RS_PRr, /* rotating, 16-62 */ + IA64_RS_PR63, + IA64_RS_RR, + + IA64_RS_ARX, /* ARs not in RS_AR or RS_ARb */ + IA64_RS_CRX, /* CRs not in RS_CR */ + IA64_RS_PSR, /* PSR bits */ + IA64_RS_RSE, /* implementation-specific RSE resources */ + IA64_RS_AR_FPSR, +}; + +enum ia64_rse_resource +{ + IA64_RSE_N_STACKED_PHYS, + IA64_RSE_BOF, + IA64_RSE_STORE_REG, + IA64_RSE_LOAD_REG, + IA64_RSE_BSPLOAD, + IA64_RSE_RNATBITINDEX, + IA64_RSE_CFLE, + IA64_RSE_NDIRTY, +}; + +/* Information about a given resource dependency */ +struct ia64_dependency +{ + /* Name of the resource */ + const char *name; + /* Does this dependency need further specification? */ + enum ia64_resource_specifier specifier; + /* Mode of dependency */ + enum ia64_dependency_mode mode; + /* Dependency semantics */ + enum ia64_dependency_semantics semantics; + /* Register index, if applicable (distinguishes AR, CR, and PSR deps) */ +#define REG_NONE (-1) + int regindex; + /* Special info on semantics */ + const char *info; +}; + +/* Two arrays of indexes into the ia64_dependency table. + chks are dependencies to check for conflicts when an opcode is + encountered; regs are dependencies to register (mark as used) when an + opcode is used. chks correspond to readers (RAW) or writers (WAW or + WAR) of a resource, while regs correspond to writers (RAW or WAW) and + readers (WAR) of a resource. */ +struct ia64_opcode_dependency +{ + int nchks; + const unsigned short *chks; + int nregs; + const unsigned short *regs; +}; + +/* encode/extract the note/index for a dependency */ +#define RDEP(N,X) (((N)<<11)|(X)) +#define NOTE(X) (((X)>>11)&0x1F) +#define DEP(X) ((X)&0x7FF) + +/* A template descriptor describes the execution units that are active + for each of the three slots. It also specifies the location of + instruction group boundaries that may be present between two slots. */ +struct ia64_templ_desc + { + int group_boundary; /* 0=no boundary, 1=between slot 0 & 1, etc. */ + enum ia64_unit exec_unit[3]; + const char *name; + }; + +/* The opcode table is an array of struct ia64_opcode. */ + +struct ia64_opcode + { + /* The opcode name. */ + const char *name; + + /* The type of the instruction: */ + enum ia64_insn_type type; + + /* Number of output operands: */ + int num_outputs; + + /* The opcode itself. Those bits which will be filled in with + operands are zeroes. */ + ia64_insn opcode; + + /* The opcode mask. This is used by the disassembler. This is a + mask containing ones indicating those bits which must match the + opcode field, and zeroes indicating those bits which need not + match (and are presumably filled in by operands). */ + ia64_insn mask; + + /* An array of operand codes. Each code is an index into the + operand table. They appear in the order which the operands must + appear in assembly code, and are terminated by a zero. */ + enum ia64_opnd operands[5]; + + /* One bit flags for the opcode. These are primarily used to + indicate specific processors and environments support the + instructions. The defined values are listed below. */ + unsigned int flags; + + /* Used by ia64_find_next_opcode (). */ + short ent_index; + + /* Opcode dependencies. */ + const struct ia64_opcode_dependency *dependencies; + }; + +/* Values defined for the flags field of a struct ia64_opcode. */ + +#define IA64_OPCODE_FIRST (1<<0) /* must be first in an insn group */ +#define IA64_OPCODE_X_IN_MLX (1<<1) /* insn is allowed in X slot of MLX */ +#define IA64_OPCODE_LAST (1<<2) /* must be last in an insn group */ +#define IA64_OPCODE_PRIV (1<<3) /* privileged instruct */ +#define IA64_OPCODE_SLOT2 (1<<4) /* insn allowed in slot 2 only */ +#define IA64_OPCODE_NO_PRED (1<<5) /* insn cannot be predicated */ +#define IA64_OPCODE_PSEUDO (1<<6) /* insn is a pseudo-op */ +#define IA64_OPCODE_F2_EQ_F3 (1<<7) /* constraint: F2 == F3 */ +#define IA64_OPCODE_LEN_EQ_64MCNT (1<<8) /* constraint: LEN == 64-CNT */ +#define IA64_OPCODE_MOD_RRBS (1<<9) /* modifies all rrbs in CFM */ +#define IA64_OPCODE_POSTINC (1<<10) /* postincrement MR3 operand */ + +/* A macro to extract the major opcode from an instruction. */ +#define IA64_OP(i) (((i) >> 37) & 0xf) + +enum ia64_operand_class + { + IA64_OPND_CLASS_CST, /* constant */ + IA64_OPND_CLASS_REG, /* register */ + IA64_OPND_CLASS_IND, /* indirect register */ + IA64_OPND_CLASS_ABS, /* absolute value */ + IA64_OPND_CLASS_REL, /* IP-relative value */ + }; + +/* The operands table is an array of struct ia64_operand. */ + +struct ia64_operand +{ + enum ia64_operand_class class; + + /* Set VALUE as the operand bits for the operand of type SELF in the + instruction pointed to by CODE. If an error occurs, *CODE is not + modified and the returned string describes the cause of the + error. If no error occurs, NULL is returned. */ + const char *(*insert) (const struct ia64_operand *self, ia64_insn value, + ia64_insn *code); + + /* Extract the operand bits for an operand of type SELF from + instruction CODE store them in *VALUE. If an error occurs, the + cause of the error is described by the string returned. If no + error occurs, NULL is returned. */ + const char *(*extract) (const struct ia64_operand *self, ia64_insn code, + ia64_insn *value); + + /* A string whose meaning depends on the operand class. */ + + const char *str; + + struct bit_field + { + /* The number of bits in the operand. */ + int bits; + + /* How far the operand is left shifted in the instruction. */ + int shift; + } + field[4]; /* no operand has more than this many bit-fields */ + + unsigned int flags; + + const char *desc; /* brief description */ +}; + +/* Values defined for the flags field of a struct ia64_operand. */ + +/* Disassemble as signed decimal (instead of hex): */ +#define IA64_OPND_FLAG_DECIMAL_SIGNED (1<<0) +/* Disassemble as unsigned decimal (instead of hex): */ +#define IA64_OPND_FLAG_DECIMAL_UNSIGNED (1<<1) + +#define NELEMS(a) ((int) (sizeof (a) / sizeof (a[0]))) + +static const char* +ins_rsvd (const struct ia64_operand *self ATTRIBUTE_UNUSED, + ia64_insn value ATTRIBUTE_UNUSED, ia64_insn *code ATTRIBUTE_UNUSED) +{ + return "internal error---this shouldn't happen"; +} + +static const char* +ext_rsvd (const struct ia64_operand *self ATTRIBUTE_UNUSED, + ia64_insn code ATTRIBUTE_UNUSED, ia64_insn *valuep ATTRIBUTE_UNUSED) +{ + return "internal error---this shouldn't happen"; +} + +static const char* +ins_const (const struct ia64_operand *self ATTRIBUTE_UNUSED, + ia64_insn value ATTRIBUTE_UNUSED, ia64_insn *code ATTRIBUTE_UNUSED) +{ + return 0; +} + +static const char* +ext_const (const struct ia64_operand *self ATTRIBUTE_UNUSED, + ia64_insn code ATTRIBUTE_UNUSED, ia64_insn *valuep ATTRIBUTE_UNUSED) +{ + return 0; +} + +static const char* +ins_reg (const struct ia64_operand *self, ia64_insn value, ia64_insn *code) +{ + if (value >= 1u << self->field[0].bits) + return "register number out of range"; + + *code |= value << self->field[0].shift; + return 0; +} + +static const char* +ext_reg (const struct ia64_operand *self, ia64_insn code, ia64_insn *valuep) +{ + *valuep = ((code >> self->field[0].shift) + & ((1u << self->field[0].bits) - 1)); + return 0; +} + +static const char* +ins_immu (const struct ia64_operand *self, ia64_insn value, ia64_insn *code) +{ + ia64_insn new = 0; + int i; + + for (i = 0; i < NELEMS (self->field) && self->field[i].bits; ++i) + { + new |= ((value & ((((ia64_insn) 1) << self->field[i].bits) - 1)) + << self->field[i].shift); + value >>= self->field[i].bits; + } + if (value) + return "integer operand out of range"; + + *code |= new; + return 0; +} + +static const char* +ext_immu (const struct ia64_operand *self, ia64_insn code, ia64_insn *valuep) +{ + uint64_t value = 0; + int i, bits = 0, total = 0; + + for (i = 0; i < NELEMS (self->field) && self->field[i].bits; ++i) + { + bits = self->field[i].bits; + value |= ((code >> self->field[i].shift) + & ((((uint64_t) 1) << bits) - 1)) << total; + total += bits; + } + *valuep = value; + return 0; +} + +static const char* +ins_immu5b (const struct ia64_operand *self, ia64_insn value, + ia64_insn *code) +{ + if (value < 32 || value > 63) + return "value must be between 32 and 63"; + return ins_immu (self, value - 32, code); +} + +static const char* +ext_immu5b (const struct ia64_operand *self, ia64_insn code, + ia64_insn *valuep) +{ + const char *result; + + result = ext_immu (self, code, valuep); + if (result) + return result; + + *valuep = *valuep + 32; + return 0; +} + +static const char* +ins_immus8 (const struct ia64_operand *self, ia64_insn value, ia64_insn *code) +{ + if (value & 0x7) + return "value not an integer multiple of 8"; + return ins_immu (self, value >> 3, code); +} + +static const char* +ext_immus8 (const struct ia64_operand *self, ia64_insn code, ia64_insn *valuep) +{ + const char *result; + + result = ext_immu (self, code, valuep); + if (result) + return result; + + *valuep = *valuep << 3; + return 0; +} + +static const char* +ins_imms_scaled (const struct ia64_operand *self, ia64_insn value, + ia64_insn *code, int scale) +{ + int64_t svalue = value, sign_bit = 0; + ia64_insn new = 0; + int i; + + svalue >>= scale; + + for (i = 0; i < NELEMS (self->field) && self->field[i].bits; ++i) + { + new |= ((svalue & ((((ia64_insn) 1) << self->field[i].bits) - 1)) + << self->field[i].shift); + sign_bit = (svalue >> (self->field[i].bits - 1)) & 1; + svalue >>= self->field[i].bits; + } + if ((!sign_bit && svalue != 0) || (sign_bit && svalue != -1)) + return "integer operand out of range"; + + *code |= new; + return 0; +} + +static const char* +ext_imms_scaled (const struct ia64_operand *self, ia64_insn code, + ia64_insn *valuep, int scale) +{ + int i, bits = 0, total = 0; + int64_t val = 0, sign; + + for (i = 0; i < NELEMS (self->field) && self->field[i].bits; ++i) + { + bits = self->field[i].bits; + val |= ((code >> self->field[i].shift) + & ((((uint64_t) 1) << bits) - 1)) << total; + total += bits; + } + /* sign extend: */ + sign = (int64_t) 1 << (total - 1); + val = (val ^ sign) - sign; + + *valuep = (val << scale); + return 0; +} + +static const char* +ins_imms (const struct ia64_operand *self, ia64_insn value, ia64_insn *code) +{ + return ins_imms_scaled (self, value, code, 0); +} + +static const char* +ins_immsu4 (const struct ia64_operand *self, ia64_insn value, ia64_insn *code) +{ + value = ((value & 0xffffffff) ^ 0x80000000) - 0x80000000; + + return ins_imms_scaled (self, value, code, 0); +} + +static const char* +ext_imms (const struct ia64_operand *self, ia64_insn code, ia64_insn *valuep) +{ + return ext_imms_scaled (self, code, valuep, 0); +} + +static const char* +ins_immsm1 (const struct ia64_operand *self, ia64_insn value, ia64_insn *code) +{ + --value; + return ins_imms_scaled (self, value, code, 0); +} + +static const char* +ins_immsm1u4 (const struct ia64_operand *self, ia64_insn value, + ia64_insn *code) +{ + value = ((value & 0xffffffff) ^ 0x80000000) - 0x80000000; + + --value; + return ins_imms_scaled (self, value, code, 0); +} + +static const char* +ext_immsm1 (const struct ia64_operand *self, ia64_insn code, ia64_insn *valuep) +{ + const char *res = ext_imms_scaled (self, code, valuep, 0); + + ++*valuep; + return res; +} + +static const char* +ins_imms1 (const struct ia64_operand *self, ia64_insn value, ia64_insn *code) +{ + return ins_imms_scaled (self, value, code, 1); +} + +static const char* +ext_imms1 (const struct ia64_operand *self, ia64_insn code, ia64_insn *valuep) +{ + return ext_imms_scaled (self, code, valuep, 1); +} + +static const char* +ins_imms4 (const struct ia64_operand *self, ia64_insn value, ia64_insn *code) +{ + return ins_imms_scaled (self, value, code, 4); +} + +static const char* +ext_imms4 (const struct ia64_operand *self, ia64_insn code, ia64_insn *valuep) +{ + return ext_imms_scaled (self, code, valuep, 4); +} + +static const char* +ins_imms16 (const struct ia64_operand *self, ia64_insn value, ia64_insn *code) +{ + return ins_imms_scaled (self, value, code, 16); +} + +static const char* +ext_imms16 (const struct ia64_operand *self, ia64_insn code, ia64_insn *valuep) +{ + return ext_imms_scaled (self, code, valuep, 16); +} + +static const char* +ins_cimmu (const struct ia64_operand *self, ia64_insn value, ia64_insn *code) +{ + ia64_insn mask = (((ia64_insn) 1) << self->field[0].bits) - 1; + return ins_immu (self, value ^ mask, code); +} + +static const char* +ext_cimmu (const struct ia64_operand *self, ia64_insn code, ia64_insn *valuep) +{ + const char *result; + ia64_insn mask; + + mask = (((ia64_insn) 1) << self->field[0].bits) - 1; + result = ext_immu (self, code, valuep); + if (!result) + { + mask = (((ia64_insn) 1) << self->field[0].bits) - 1; + *valuep ^= mask; + } + return result; +} + +static const char* +ins_cnt (const struct ia64_operand *self, ia64_insn value, ia64_insn *code) +{ + --value; + if (value >= ((uint64_t) 1) << self->field[0].bits) + return "count out of range"; + + *code |= value << self->field[0].shift; + return 0; +} + +static const char* +ext_cnt (const struct ia64_operand *self, ia64_insn code, ia64_insn *valuep) +{ + *valuep = ((code >> self->field[0].shift) + & ((((uint64_t) 1) << self->field[0].bits) - 1)) + 1; + return 0; +} + +static const char* +ins_cnt2b (const struct ia64_operand *self, ia64_insn value, ia64_insn *code) +{ + --value; + + if (value > 2) + return "count must be in range 1..3"; + + *code |= value << self->field[0].shift; + return 0; +} + +static const char* +ext_cnt2b (const struct ia64_operand *self, ia64_insn code, ia64_insn *valuep) +{ + *valuep = ((code >> self->field[0].shift) & 0x3) + 1; + return 0; +} + +static const char* +ins_cnt2c (const struct ia64_operand *self, ia64_insn value, ia64_insn *code) +{ + switch (value) + { + case 0: value = 0; break; + case 7: value = 1; break; + case 15: value = 2; break; + case 16: value = 3; break; + default: return "count must be 0, 7, 15, or 16"; + } + *code |= value << self->field[0].shift; + return 0; +} + +static const char* +ext_cnt2c (const struct ia64_operand *self, ia64_insn code, ia64_insn *valuep) +{ + ia64_insn value; + + value = (code >> self->field[0].shift) & 0x3; + switch (value) + { + case 0: value = 0; break; + case 1: value = 7; break; + case 2: value = 15; break; + case 3: value = 16; break; + } + *valuep = value; + return 0; +} + +static const char* +ins_inc3 (const struct ia64_operand *self, ia64_insn value, ia64_insn *code) +{ + int64_t val = value; + uint64_t sign = 0; + + if (val < 0) + { + sign = 0x4; + value = -value; + } + switch (value) + { + case 1: value = 3; break; + case 4: value = 2; break; + case 8: value = 1; break; + case 16: value = 0; break; + default: return "count must be +/- 1, 4, 8, or 16"; + } + *code |= (sign | value) << self->field[0].shift; + return 0; +} + +static const char* +ext_inc3 (const struct ia64_operand *self, ia64_insn code, ia64_insn *valuep) +{ + int64_t val; + int negate; + + val = (code >> self->field[0].shift) & 0x7; + negate = val & 0x4; + switch (val & 0x3) + { + case 0: val = 16; break; + case 1: val = 8; break; + case 2: val = 4; break; + case 3: val = 1; break; + } + if (negate) + val = -val; + + *valuep = val; + return 0; +} + +#define CST IA64_OPND_CLASS_CST +#define REG IA64_OPND_CLASS_REG +#define IND IA64_OPND_CLASS_IND +#define ABS IA64_OPND_CLASS_ABS +#define REL IA64_OPND_CLASS_REL + +#define SDEC IA64_OPND_FLAG_DECIMAL_SIGNED +#define UDEC IA64_OPND_FLAG_DECIMAL_UNSIGNED + +const struct ia64_operand elf64_ia64_operands[IA64_OPND_COUNT] = + { + /* constants: */ + { CST, ins_const, ext_const, "NIL", {{ 0, 0}}, 0, "" }, + { CST, ins_const, ext_const, "ar.csd", {{ 0, 0}}, 0, "ar.csd" }, + { CST, ins_const, ext_const, "ar.ccv", {{ 0, 0}}, 0, "ar.ccv" }, + { CST, ins_const, ext_const, "ar.pfs", {{ 0, 0}}, 0, "ar.pfs" }, + { CST, ins_const, ext_const, "1", {{ 0, 0}}, 0, "1" }, + { CST, ins_const, ext_const, "8", {{ 0, 0}}, 0, "8" }, + { CST, ins_const, ext_const, "16", {{ 0, 0}}, 0, "16" }, + { CST, ins_const, ext_const, "r0", {{ 0, 0}}, 0, "r0" }, + { CST, ins_const, ext_const, "ip", {{ 0, 0}}, 0, "ip" }, + { CST, ins_const, ext_const, "pr", {{ 0, 0}}, 0, "pr" }, + { CST, ins_const, ext_const, "pr.rot", {{ 0, 0}}, 0, "pr.rot" }, + { CST, ins_const, ext_const, "psr", {{ 0, 0}}, 0, "psr" }, + { CST, ins_const, ext_const, "psr.l", {{ 0, 0}}, 0, "psr.l" }, + { CST, ins_const, ext_const, "psr.um", {{ 0, 0}}, 0, "psr.um" }, + + /* register operands: */ + { REG, ins_reg, ext_reg, "ar", {{ 7, 20}}, 0, /* AR3 */ + "an application register" }, + { REG, ins_reg, ext_reg, "b", {{ 3, 6}}, 0, /* B1 */ + "a branch register" }, + { REG, ins_reg, ext_reg, "b", {{ 3, 13}}, 0, /* B2 */ + "a branch register"}, + { REG, ins_reg, ext_reg, "cr", {{ 7, 20}}, 0, /* CR */ + "a control register"}, + { REG, ins_reg, ext_reg, "f", {{ 7, 6}}, 0, /* F1 */ + "a floating-point register" }, + { REG, ins_reg, ext_reg, "f", {{ 7, 13}}, 0, /* F2 */ + "a floating-point register" }, + { REG, ins_reg, ext_reg, "f", {{ 7, 20}}, 0, /* F3 */ + "a floating-point register" }, + { REG, ins_reg, ext_reg, "f", {{ 7, 27}}, 0, /* F4 */ + "a floating-point register" }, + { REG, ins_reg, ext_reg, "p", {{ 6, 6}}, 0, /* P1 */ + "a predicate register" }, + { REG, ins_reg, ext_reg, "p", {{ 6, 27}}, 0, /* P2 */ + "a predicate register" }, + { REG, ins_reg, ext_reg, "r", {{ 7, 6}}, 0, /* R1 */ + "a general register" }, + { REG, ins_reg, ext_reg, "r", {{ 7, 13}}, 0, /* R2 */ + "a general register" }, + { REG, ins_reg, ext_reg, "r", {{ 7, 20}}, 0, /* R3 */ + "a general register" }, + { REG, ins_reg, ext_reg, "r", {{ 2, 20}}, 0, /* R3_2 */ + "a general register r0-r3" }, + + /* memory operands: */ + { IND, ins_reg, ext_reg, "", {{7, 20}}, 0, /* MR3 */ + "a memory address" }, + + /* indirect operands: */ + { IND, ins_reg, ext_reg, "cpuid", {{7, 20}}, 0, /* CPUID_R3 */ + "a cpuid register" }, + { IND, ins_reg, ext_reg, "dbr", {{7, 20}}, 0, /* DBR_R3 */ + "a dbr register" }, + { IND, ins_reg, ext_reg, "dtr", {{7, 20}}, 0, /* DTR_R3 */ + "a dtr register" }, + { IND, ins_reg, ext_reg, "itr", {{7, 20}}, 0, /* ITR_R3 */ + "an itr register" }, + { IND, ins_reg, ext_reg, "ibr", {{7, 20}}, 0, /* IBR_R3 */ + "an ibr register" }, + { IND, ins_reg, ext_reg, "msr", {{7, 20}}, 0, /* MSR_R3 */ + "an msr register" }, + { IND, ins_reg, ext_reg, "pkr", {{7, 20}}, 0, /* PKR_R3 */ + "a pkr register" }, + { IND, ins_reg, ext_reg, "pmc", {{7, 20}}, 0, /* PMC_R3 */ + "a pmc register" }, + { IND, ins_reg, ext_reg, "pmd", {{7, 20}}, 0, /* PMD_R3 */ + "a pmd register" }, + { IND, ins_reg, ext_reg, "rr", {{7, 20}}, 0, /* RR_R3 */ + "an rr register" }, + + /* immediate operands: */ + { ABS, ins_cimmu, ext_cimmu, 0, {{ 5, 20 }}, UDEC, /* CCNT5 */ + "a 5-bit count (0-31)" }, + { ABS, ins_cnt, ext_cnt, 0, {{ 2, 27 }}, UDEC, /* CNT2a */ + "a 2-bit count (1-4)" }, + { ABS, ins_cnt2b, ext_cnt2b, 0, {{ 2, 27 }}, UDEC, /* CNT2b */ + "a 2-bit count (1-3)" }, + { ABS, ins_cnt2c, ext_cnt2c, 0, {{ 2, 30 }}, UDEC, /* CNT2c */ + "a count (0, 7, 15, or 16)" }, + { ABS, ins_immu, ext_immu, 0, {{ 5, 14}}, UDEC, /* CNT5 */ + "a 5-bit count (0-31)" }, + { ABS, ins_immu, ext_immu, 0, {{ 6, 27}}, UDEC, /* CNT6 */ + "a 6-bit count (0-63)" }, + { ABS, ins_cimmu, ext_cimmu, 0, {{ 6, 20}}, UDEC, /* CPOS6a */ + "a 6-bit bit pos (0-63)" }, + { ABS, ins_cimmu, ext_cimmu, 0, {{ 6, 14}}, UDEC, /* CPOS6b */ + "a 6-bit bit pos (0-63)" }, + { ABS, ins_cimmu, ext_cimmu, 0, {{ 6, 31}}, UDEC, /* CPOS6c */ + "a 6-bit bit pos (0-63)" }, + { ABS, ins_imms, ext_imms, 0, {{ 1, 36}}, SDEC, /* IMM1 */ + "a 1-bit integer (-1, 0)" }, + { ABS, ins_immu, ext_immu, 0, {{ 2, 13}}, UDEC, /* IMMU2 */ + "a 2-bit unsigned (0-3)" }, + { ABS, ins_immu5b, ext_immu5b, 0, {{ 5, 14}}, UDEC, /* IMMU5b */ + "a 5-bit unsigned (32 + (0-31))" }, + { ABS, ins_immu, ext_immu, 0, {{ 7, 13}}, 0, /* IMMU7a */ + "a 7-bit unsigned (0-127)" }, + { ABS, ins_immu, ext_immu, 0, {{ 7, 20}}, 0, /* IMMU7b */ + "a 7-bit unsigned (0-127)" }, + { ABS, ins_immu, ext_immu, 0, {{ 7, 13}}, UDEC, /* SOF */ + "a frame size (register count)" }, + { ABS, ins_immu, ext_immu, 0, {{ 7, 20}}, UDEC, /* SOL */ + "a local register count" }, + { ABS, ins_immus8,ext_immus8,0, {{ 4, 27}}, UDEC, /* SOR */ + "a rotating register count (integer multiple of 8)" }, + { ABS, ins_imms, ext_imms, 0, /* IMM8 */ + {{ 7, 13}, { 1, 36}}, SDEC, + "an 8-bit integer (-128-127)" }, + { ABS, ins_immsu4, ext_imms, 0, /* IMM8U4 */ + {{ 7, 13}, { 1, 36}}, SDEC, + "an 8-bit signed integer for 32-bit unsigned compare (-128-127)" }, + { ABS, ins_immsm1, ext_immsm1, 0, /* IMM8M1 */ + {{ 7, 13}, { 1, 36}}, SDEC, + "an 8-bit integer (-127-128)" }, + { ABS, ins_immsm1u4, ext_immsm1, 0, /* IMM8M1U4 */ + {{ 7, 13}, { 1, 36}}, SDEC, + "an 8-bit integer for 32-bit unsigned compare (-127-(-1),1-128,0x100000000)" }, + { ABS, ins_immsm1, ext_immsm1, 0, /* IMM8M1U8 */ + {{ 7, 13}, { 1, 36}}, SDEC, + "an 8-bit integer for 64-bit unsigned compare (-127-(-1),1-128,0x10000000000000000)" }, + { ABS, ins_immu, ext_immu, 0, {{ 2, 33}, { 7, 20}}, 0, /* IMMU9 */ + "a 9-bit unsigned (0-511)" }, + { ABS, ins_imms, ext_imms, 0, /* IMM9a */ + {{ 7, 6}, { 1, 27}, { 1, 36}}, SDEC, + "a 9-bit integer (-256-255)" }, + { ABS, ins_imms, ext_imms, 0, /* IMM9b */ + {{ 7, 13}, { 1, 27}, { 1, 36}}, SDEC, + "a 9-bit integer (-256-255)" }, + { ABS, ins_imms, ext_imms, 0, /* IMM14 */ + {{ 7, 13}, { 6, 27}, { 1, 36}}, SDEC, + "a 14-bit integer (-8192-8191)" }, + { ABS, ins_imms1, ext_imms1, 0, /* IMM17 */ + {{ 7, 6}, { 8, 24}, { 1, 36}}, 0, + "a 17-bit integer (-65536-65535)" }, + { ABS, ins_immu, ext_immu, 0, {{20, 6}, { 1, 36}}, 0, /* IMMU21 */ + "a 21-bit unsigned" }, + { ABS, ins_imms, ext_imms, 0, /* IMM22 */ + {{ 7, 13}, { 9, 27}, { 5, 22}, { 1, 36}}, SDEC, + "a 22-bit signed integer" }, + { ABS, ins_immu, ext_immu, 0, /* IMMU24 */ + {{21, 6}, { 2, 31}, { 1, 36}}, 0, + "a 24-bit unsigned" }, + { ABS, ins_imms16,ext_imms16,0, {{27, 6}, { 1, 36}}, 0, /* IMM44 */ + "a 44-bit unsigned (least 16 bits ignored/zeroes)" }, + { ABS, ins_rsvd, ext_rsvd, 0, {{0, 0}}, 0, /* IMMU62 */ + "a 62-bit unsigned" }, + { ABS, ins_rsvd, ext_rsvd, 0, {{0, 0}}, 0, /* IMMU64 */ + "a 64-bit unsigned" }, + { ABS, ins_inc3, ext_inc3, 0, {{ 3, 13}}, SDEC, /* INC3 */ + "an increment (+/- 1, 4, 8, or 16)" }, + { ABS, ins_cnt, ext_cnt, 0, {{ 4, 27}}, UDEC, /* LEN4 */ + "a 4-bit length (1-16)" }, + { ABS, ins_cnt, ext_cnt, 0, {{ 6, 27}}, UDEC, /* LEN6 */ + "a 6-bit length (1-64)" }, + { ABS, ins_immu, ext_immu, 0, {{ 4, 20}}, 0, /* MBTYPE4 */ + "a mix type (@rev, @mix, @shuf, @alt, or @brcst)" }, + { ABS, ins_immu, ext_immu, 0, {{ 8, 20}}, 0, /* MBTYPE8 */ + "an 8-bit mix type" }, + { ABS, ins_immu, ext_immu, 0, {{ 6, 14}}, UDEC, /* POS6 */ + "a 6-bit bit pos (0-63)" }, + { REL, ins_imms4, ext_imms4, 0, {{ 7, 6}, { 2, 33}}, 0, /* TAG13 */ + "a branch tag" }, + { REL, ins_imms4, ext_imms4, 0, {{ 9, 24}}, 0, /* TAG13b */ + "a branch tag" }, + { REL, ins_imms4, ext_imms4, 0, {{20, 6}, { 1, 36}}, 0, /* TGT25 */ + "a branch target" }, + { REL, ins_imms4, ext_imms4, 0, /* TGT25b */ + {{ 7, 6}, {13, 20}, { 1, 36}}, 0, + "a branch target" }, + { REL, ins_imms4, ext_imms4, 0, {{20, 13}, { 1, 36}}, 0, /* TGT25c */ + "a branch target" }, + { REL, ins_rsvd, ext_rsvd, 0, {{0, 0}}, 0, /* TGT64 */ + "a branch target" }, + + { ABS, ins_const, ext_const, 0, {{0, 0}}, 0, /* LDXMOV */ + "ldxmov target" }, + }; + + +/* ia64-asmtab.h -- Header for compacted IA-64 opcode tables. + Copyright 1999, 2000 Free Software Foundation, Inc. + Contributed by Bob Manson of Cygnus Support + + This file is part of GDB, GAS, and the GNU binutils. + + GDB, GAS, and the GNU binutils are free software; you can redistribute + them and/or modify them 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. + + GDB, GAS, and the GNU binutils are distributed in the hope that they + 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 file; see the file COPYING. If not, see + . */ + +/* The primary opcode table is made up of the following: */ +struct ia64_main_table +{ + /* The entry in the string table that corresponds to the name of this + opcode. */ + unsigned short name_index; + + /* The type of opcode; corresponds to the TYPE field in + struct ia64_opcode. */ + unsigned char opcode_type; + + /* The number of outputs for this opcode. */ + unsigned char num_outputs; + + /* The base insn value for this opcode. It may be modified by completers. */ + ia64_insn opcode; + + /* The mask of valid bits in OPCODE. Zeros indicate operand fields. */ + ia64_insn mask; + + /* The operands of this instruction. Corresponds to the OPERANDS field + in struct ia64_opcode. */ + unsigned char operands[5]; + + /* The flags for this instruction. Corresponds to the FLAGS field in + struct ia64_opcode. */ + short flags; + + /* The tree of completers for this instruction; this is an offset into + completer_table. */ + short completers; +}; + +/* Each instruction has a set of possible "completers", or additional + suffixes that can alter the instruction's behavior, and which has + potentially different dependencies. + + The completer entries modify certain bits in the instruction opcode. + Which bits are to be modified are marked by the BITS, MASK and + OFFSET fields. The completer entry may also note dependencies for the + opcode. + + These completers are arranged in a DAG; the pointers are indexes + into the completer_table array. The completer DAG is searched by + find_completer () and ia64_find_matching_opcode (). + + Note that each completer needs to be applied in turn, so that if we + have the instruction + cmp.lt.unc + the completer entries for both "lt" and "unc" would need to be applied + to the opcode's value. + + Some instructions do not require any completers; these contain an + empty completer entry. Instructions that require a completer do + not contain an empty entry. + + Terminal completers (those completers that validly complete an + instruction) are marked by having the TERMINAL_COMPLETER flag set. + + Only dependencies listed in the terminal completer for an opcode are + considered to apply to that opcode instance. */ + +struct ia64_completer_table +{ + /* The bit value that this completer sets. */ + unsigned int bits; + + /* And its mask. 1s are bits that are to be modified in the + instruction. */ + unsigned int mask; + + /* The entry in the string table that corresponds to the name of this + completer. */ + unsigned short name_index; + + /* An alternative completer, or -1 if this is the end of the chain. */ + short alternative; + + /* A pointer to the DAG of completers that can potentially follow + this one, or -1. */ + short subentries; + + /* The bit offset in the instruction where BITS and MASK should be + applied. */ + unsigned char offset : 7; + + unsigned char terminal_completer : 1; + + /* Index into the dependency list table */ + short dependencies; +}; + +/* This contains sufficient information for the disassembler to resolve + the complete name of the original instruction. */ +struct ia64_dis_names +{ + /* COMPLETER_INDEX represents the tree of completers that make up + the instruction. The LSB represents the top of the tree for the + specified instruction. + + A 0 bit indicates to go to the next alternate completer via the + alternative field; a 1 bit indicates that the current completer + is part of the instruction, and to go down the subentries index. + We know we've reached the final completer when we run out of 1 + bits. + + There is always at least one 1 bit. */ + unsigned int completer_index : 20; + + /* The index in the main_table[] array for the instruction. */ + unsigned short insn_index : 11; + + /* If set, the next entry in this table is an alternate possibility + for this instruction encoding. Which one to use is determined by + the instruction type and other factors (see opcode_verify ()). */ + unsigned int next_flag : 1; + + /* The disassembly priority of this entry among instructions. */ + unsigned short priority; +}; + +static const char * const ia64_strings[] = { + "", "0", "1", "a", "acq", "add", "addl", "addp4", "adds", "alloc", "and", + "andcm", "b", "bias", "br", "break", "brl", "brp", "bsw", "c", "call", + "cexit", "chk", "cloop", "clr", "clrrrb", "cmp", "cmp4", "cmp8xchg16", + "cmpxchg1", "cmpxchg2", "cmpxchg4", "cmpxchg8", "cond", "cover", "ctop", + "czx1", "czx2", "d", "dep", "dpnt", "dptk", "e", "epc", "eq", "excl", + "exit", "exp", "extr", "f", "fabs", "fadd", "famax", "famin", "fand", + "fandcm", "fault", "fc", "fchkf", "fclass", "fclrf", "fcmp", "fcvt", + "fetchadd4", "fetchadd8", "few", "fill", "flushrs", "fma", "fmax", + "fmerge", "fmin", "fmix", "fmpy", "fms", "fneg", "fnegabs", "fnma", + "fnmpy", "fnorm", "for", "fpabs", "fpack", "fpamax", "fpamin", "fpcmp", + "fpcvt", "fpma", "fpmax", "fpmerge", "fpmin", "fpmpy", "fpms", "fpneg", + "fpnegabs", "fpnma", "fpnmpy", "fprcpa", "fprsqrta", "frcpa", "frsqrta", + "fselect", "fsetc", "fsub", "fswap", "fsxt", "fwb", "fx", "fxor", "fxu", + "g", "ga", "ge", "getf", "geu", "gt", "gtu", "h", "hint", "hu", "i", "ia", + "imp", "invala", "itc", "itr", "l", "ld1", "ld16", "ld2", "ld4", "ld8", + "ldf", "ldf8", "ldfd", "ldfe", "ldfp8", "ldfpd", "ldfps", "ldfs", "le", + "leu", "lfetch", "loadrs", "loop", "lr", "lt", "ltu", "lu", "m", "many", + "mf", "mix1", "mix2", "mix4", "mov", "movl", "mux1", "mux2", "nc", "ne", + "neq", "nge", "ngt", "nl", "nle", "nlt", "nm", "nop", "nr", "ns", "nt1", + "nt2", "nta", "nz", "or", "orcm", "ord", "pack2", "pack4", "padd1", + "padd2", "padd4", "pavg1", "pavg2", "pavgsub1", "pavgsub2", "pcmp1", + "pcmp2", "pcmp4", "pmax1", "pmax2", "pmin1", "pmin2", "pmpy2", "pmpyshr2", + "popcnt", "pr", "probe", "psad1", "pshl2", "pshl4", "pshladd2", "pshr2", + "pshr4", "pshradd2", "psub1", "psub2", "psub4", "ptc", "ptr", "r", "raz", + "rel", "ret", "rfi", "rsm", "rum", "rw", "s", "s0", "s1", "s2", "s3", + "sa", "se", "setf", "shl", "shladd", "shladdp4", "shr", "shrp", "sig", + "spill", "spnt", "sptk", "srlz", "ssm", "sss", "st1", "st16", "st2", + "st4", "st8", "stf", "stf8", "stfd", "stfe", "stfs", "sub", "sum", "sxt1", + "sxt2", "sxt4", "sync", "tak", "tbit", "tf", "thash", "tnat", "tpa", + "trunc", "ttag", "u", "unc", "unord", "unpack1", "unpack2", "unpack4", + "uss", "uus", "uuu", "vmsw", "w", "wexit", "wtop", "x", "xchg1", "xchg2", + "xchg4", "xchg8", "xf", "xma", "xmpy", "xor", "xuf", "z", "zxt1", "zxt2", + "zxt4", +}; + +static const struct ia64_dependency +dependencies[] = { + { "ALAT", 0, 0, 0, -1, NULL, }, + { "AR[BSP]", 26, 0, 2, 17, NULL, }, + { "AR[BSPSTORE]", 26, 0, 2, 18, NULL, }, + { "AR[CCV]", 26, 0, 2, 32, NULL, }, + { "AR[CFLG]", 26, 0, 2, 27, NULL, }, + { "AR[CSD]", 26, 0, 2, 25, NULL, }, + { "AR[EC]", 26, 0, 2, 66, NULL, }, + { "AR[EFLAG]", 26, 0, 2, 24, NULL, }, + { "AR[FCR]", 26, 0, 2, 21, NULL, }, + { "AR[FDR]", 26, 0, 2, 30, NULL, }, + { "AR[FIR]", 26, 0, 2, 29, NULL, }, + { "AR[FPSR].sf0.controls", 30, 0, 2, -1, NULL, }, + { "AR[FPSR].sf1.controls", 30, 0, 2, -1, NULL, }, + { "AR[FPSR].sf2.controls", 30, 0, 2, -1, NULL, }, + { "AR[FPSR].sf3.controls", 30, 0, 2, -1, NULL, }, + { "AR[FPSR].sf0.flags", 30, 0, 2, -1, NULL, }, + { "AR[FPSR].sf1.flags", 30, 0, 2, -1, NULL, }, + { "AR[FPSR].sf2.flags", 30, 0, 2, -1, NULL, }, + { "AR[FPSR].sf3.flags", 30, 0, 2, -1, NULL, }, + { "AR[FPSR].traps", 30, 0, 2, -1, NULL, }, + { "AR[FPSR].rv", 30, 0, 2, -1, NULL, }, + { "AR[FSR]", 26, 0, 2, 28, NULL, }, + { "AR[ITC]", 26, 0, 2, 44, NULL, }, + { "AR[K%], % in 0 - 7", 1, 0, 2, -1, NULL, }, + { "AR[LC]", 26, 0, 2, 65, NULL, }, + { "AR[PFS]", 26, 0, 2, 64, NULL, }, + { "AR[PFS]", 26, 0, 2, 64, NULL, }, + { "AR[PFS]", 26, 0, 0, 64, NULL, }, + { "AR[RNAT]", 26, 0, 2, 19, NULL, }, + { "AR[RSC]", 26, 0, 2, 16, NULL, }, + { "AR[SSD]", 26, 0, 2, 26, NULL, }, + { "AR[UNAT]{%}, % in 0 - 63", 2, 0, 2, -1, NULL, }, + { "AR%, % in 8-15, 20, 22-23, 31, 33-35, 37-39, 41-43, 45-47, 67-111", 3, 0, 0, -1, NULL, }, + { "AR%, % in 48-63, 112-127", 4, 0, 2, -1, NULL, }, + { "BR%, % in 0 - 7", 5, 0, 2, -1, NULL, }, + { "BR%, % in 0 - 7", 5, 0, 0, -1, NULL, }, + { "BR%, % in 0 - 7", 5, 0, 2, -1, NULL, }, + { "CFM", 6, 0, 2, -1, NULL, }, + { "CFM", 6, 0, 2, -1, NULL, }, + { "CFM", 6, 0, 2, -1, NULL, }, + { "CFM", 6, 0, 2, -1, NULL, }, + { "CFM", 6, 0, 0, -1, NULL, }, + { "CPUID#", 7, 0, 5, -1, NULL, }, + { "CR[CMCV]", 27, 0, 3, 74, NULL, }, + { "CR[DCR]", 27, 0, 3, 0, NULL, }, + { "CR[EOI]", 27, 0, 7, 67, "SC Section 5.8.3.4, \"End of External Interrupt Register (EOI Ð CR67)\" on page 2:119", }, + { "CR[GPTA]", 27, 0, 3, 9, NULL, }, + { "CR[IFA]", 27, 0, 1, 20, NULL, }, + { "CR[IFA]", 27, 0, 3, 20, NULL, }, + { "CR[IFS]", 27, 0, 3, 23, NULL, }, + { "CR[IFS]", 27, 0, 1, 23, NULL, }, + { "CR[IFS]", 27, 0, 1, 23, NULL, }, + { "CR[IHA]", 27, 0, 3, 25, NULL, }, + { "CR[IIM]", 27, 0, 3, 24, NULL, }, + { "CR[IIP]", 27, 0, 3, 19, NULL, }, + { "CR[IIP]", 27, 0, 1, 19, NULL, }, + { "CR[IIPA]", 27, 0, 3, 22, NULL, }, + { "CR[IPSR]", 27, 0, 3, 16, NULL, }, + { "CR[IPSR]", 27, 0, 1, 16, NULL, }, + { "CR[IRR%], % in 0 - 3", 8, 0, 3, -1, NULL, }, + { "CR[ISR]", 27, 0, 3, 17, NULL, }, + { "CR[ITIR]", 27, 0, 3, 21, NULL, }, + { "CR[ITIR]", 27, 0, 1, 21, NULL, }, + { "CR[ITM]", 27, 0, 3, 1, NULL, }, + { "CR[ITV]", 27, 0, 3, 72, NULL, }, + { "CR[IVA]", 27, 0, 4, 2, NULL, }, + { "CR[IVR]", 27, 0, 7, 65, "SC Section 5.8.3.2, \"External Interrupt Vector Register (IVR Ð CR65)\" on page 2:118", }, + { "CR[LID]", 27, 0, 7, 64, "SC Section 5.8.3.1, \"Local ID (LID Ð CR64)\" on page 2:117", }, + { "CR[LRR%], % in 0 - 1", 9, 0, 3, -1, NULL, }, + { "CR[PMV]", 27, 0, 3, 73, NULL, }, + { "CR[PTA]", 27, 0, 3, 8, NULL, }, + { "CR[TPR]", 27, 0, 3, 66, NULL, }, + { "CR[TPR]", 27, 0, 7, 66, "SC Section 5.8.3.3, \"Task Priority Register (TPR Ð CR66)\" on page 2:119", }, + { "CR[TPR]", 27, 0, 1, 66, NULL, }, + { "CR%, % in 3-7, 10-15, 18, 26-63, 75-79, 82-127", 10, 0, 0, -1, NULL, }, + { "DBR#", 11, 0, 2, -1, NULL, }, + { "DBR#", 11, 0, 3, -1, NULL, }, + { "DTC", 0, 0, 3, -1, NULL, }, + { "DTC", 0, 0, 2, -1, NULL, }, + { "DTC", 0, 0, 0, -1, NULL, }, + { "DTC", 0, 0, 2, -1, NULL, }, + { "DTC_LIMIT*", 0, 0, 2, -1, NULL, }, + { "DTR", 0, 0, 3, -1, NULL, }, + { "DTR", 0, 0, 2, -1, NULL, }, + { "DTR", 0, 0, 3, -1, NULL, }, + { "DTR", 0, 0, 0, -1, NULL, }, + { "DTR", 0, 0, 2, -1, NULL, }, + { "FR%, % in 0 - 1", 12, 0, 0, -1, NULL, }, + { "FR%, % in 2 - 127", 13, 0, 2, -1, NULL, }, + { "FR%, % in 2 - 127", 13, 0, 0, -1, NULL, }, + { "GR0", 14, 0, 0, -1, NULL, }, + { "GR%, % in 1 - 127", 15, 0, 0, -1, NULL, }, + { "GR%, % in 1 - 127", 15, 0, 2, -1, NULL, }, + { "IBR#", 16, 0, 2, -1, NULL, }, + { "InService*", 17, 0, 3, -1, NULL, }, + { "InService*", 17, 0, 2, -1, NULL, }, + { "InService*", 17, 0, 2, -1, NULL, }, + { "IP", 0, 0, 0, -1, NULL, }, + { "ITC", 0, 0, 4, -1, NULL, }, + { "ITC", 0, 0, 2, -1, NULL, }, + { "ITC", 0, 0, 0, -1, NULL, }, + { "ITC", 0, 0, 4, -1, NULL, }, + { "ITC", 0, 0, 2, -1, NULL, }, + { "ITC_LIMIT*", 0, 0, 2, -1, NULL, }, + { "ITR", 0, 0, 2, -1, NULL, }, + { "ITR", 0, 0, 4, -1, NULL, }, + { "ITR", 0, 0, 2, -1, NULL, }, + { "ITR", 0, 0, 0, -1, NULL, }, + { "ITR", 0, 0, 4, -1, NULL, }, + { "memory", 0, 0, 0, -1, NULL, }, + { "MSR#", 18, 0, 5, -1, NULL, }, + { "PKR#", 19, 0, 3, -1, NULL, }, + { "PKR#", 19, 0, 0, -1, NULL, }, + { "PKR#", 19, 0, 2, -1, NULL, }, + { "PKR#", 19, 0, 2, -1, NULL, }, + { "PMC#", 20, 0, 2, -1, NULL, }, + { "PMC#", 20, 0, 7, -1, "SC Section 7.2.1, \"Generic Performance Counter Registers\" for PMC[0].fr on page 2:150", }, + { "PMD#", 21, 0, 2, -1, NULL, }, + { "PR0", 0, 0, 0, -1, NULL, }, + { "PR%, % in 1 - 15", 22, 0, 2, -1, NULL, }, + { "PR%, % in 1 - 15", 22, 0, 2, -1, NULL, }, + { "PR%, % in 1 - 15", 22, 0, 0, -1, NULL, }, + { "PR%, % in 16 - 62", 23, 0, 2, -1, NULL, }, + { "PR%, % in 16 - 62", 23, 0, 2, -1, NULL, }, + { "PR%, % in 16 - 62", 23, 0, 0, -1, NULL, }, + { "PR63", 24, 0, 2, -1, NULL, }, + { "PR63", 24, 0, 2, -1, NULL, }, + { "PR63", 24, 0, 0, -1, NULL, }, + { "PSR.ac", 28, 0, 1, 3, NULL, }, + { "PSR.ac", 28, 0, 3, 3, NULL, }, + { "PSR.ac", 28, 0, 2, 3, NULL, }, + { "PSR.ac", 28, 0, 2, 3, NULL, }, + { "PSR.be", 28, 0, 1, 1, NULL, }, + { "PSR.be", 28, 0, 3, 1, NULL, }, + { "PSR.be", 28, 0, 2, 1, NULL, }, + { "PSR.be", 28, 0, 2, 1, NULL, }, + { "PSR.bn", 28, 0, 2, 44, NULL, }, + { "PSR.cpl", 28, 0, 1, 32, NULL, }, + { "PSR.cpl", 28, 0, 2, 32, NULL, }, + { "PSR.da", 28, 0, 2, 38, NULL, }, + { "PSR.db", 28, 0, 3, 24, NULL, }, + { "PSR.db", 28, 0, 2, 24, NULL, }, + { "PSR.db", 28, 0, 2, 24, NULL, }, + { "PSR.dd", 28, 0, 2, 39, NULL, }, + { "PSR.dfh", 28, 0, 3, 19, NULL, }, + { "PSR.dfh", 28, 0, 2, 19, NULL, }, + { "PSR.dfh", 28, 0, 2, 19, NULL, }, + { "PSR.dfl", 28, 0, 3, 18, NULL, }, + { "PSR.dfl", 28, 0, 2, 18, NULL, }, + { "PSR.dfl", 28, 0, 2, 18, NULL, }, + { "PSR.di", 28, 0, 3, 22, NULL, }, + { "PSR.di", 28, 0, 2, 22, NULL, }, + { "PSR.di", 28, 0, 2, 22, NULL, }, + { "PSR.dt", 28, 0, 3, 17, NULL, }, + { "PSR.dt", 28, 0, 2, 17, NULL, }, + { "PSR.dt", 28, 0, 2, 17, NULL, }, + { "PSR.ed", 28, 0, 2, 43, NULL, }, + { "PSR.i", 28, 0, 2, 14, NULL, }, + { "PSR.ia", 28, 0, 0, 14, NULL, }, + { "PSR.ic", 28, 0, 2, 13, NULL, }, + { "PSR.ic", 28, 0, 3, 13, NULL, }, + { "PSR.ic", 28, 0, 2, 13, NULL, }, + { "PSR.id", 28, 0, 0, 14, NULL, }, + { "PSR.is", 28, 0, 0, 14, NULL, }, + { "PSR.it", 28, 0, 2, 14, NULL, }, + { "PSR.lp", 28, 0, 2, 25, NULL, }, + { "PSR.lp", 28, 0, 3, 25, NULL, }, + { "PSR.lp", 28, 0, 2, 25, NULL, }, + { "PSR.mc", 28, 0, 2, 35, NULL, }, + { "PSR.mfh", 28, 0, 2, 5, NULL, }, + { "PSR.mfl", 28, 0, 2, 4, NULL, }, + { "PSR.pk", 28, 0, 3, 15, NULL, }, + { "PSR.pk", 28, 0, 2, 15, NULL, }, + { "PSR.pk", 28, 0, 2, 15, NULL, }, + { "PSR.pp", 28, 0, 2, 21, NULL, }, + { "PSR.ri", 28, 0, 0, 41, NULL, }, + { "PSR.rt", 28, 0, 2, 27, NULL, }, + { "PSR.rt", 28, 0, 3, 27, NULL, }, + { "PSR.rt", 28, 0, 2, 27, NULL, }, + { "PSR.si", 28, 0, 2, 23, NULL, }, + { "PSR.si", 28, 0, 3, 23, NULL, }, + { "PSR.si", 28, 0, 2, 23, NULL, }, + { "PSR.sp", 28, 0, 2, 20, NULL, }, + { "PSR.sp", 28, 0, 3, 20, NULL, }, + { "PSR.sp", 28, 0, 2, 20, NULL, }, + { "PSR.ss", 28, 0, 2, 40, NULL, }, + { "PSR.tb", 28, 0, 3, 26, NULL, }, + { "PSR.tb", 28, 0, 2, 26, NULL, }, + { "PSR.tb", 28, 0, 2, 26, NULL, }, + { "PSR.up", 28, 0, 2, 2, NULL, }, + { "PSR.vm", 28, 0, 1, 46, NULL, }, + { "PSR.vm", 28, 0, 2, 46, NULL, }, + { "RR#", 25, 0, 3, -1, NULL, }, + { "RR#", 25, 0, 2, -1, NULL, }, + { "RSE", 29, 0, 2, -1, NULL, }, + { "ALAT", 0, 1, 0, -1, NULL, }, + { "AR[BSP]", 26, 1, 2, 17, NULL, }, + { "AR[BSPSTORE]", 26, 1, 2, 18, NULL, }, + { "AR[CCV]", 26, 1, 2, 32, NULL, }, + { "AR[CFLG]", 26, 1, 2, 27, NULL, }, + { "AR[CSD]", 26, 1, 2, 25, NULL, }, + { "AR[EC]", 26, 1, 2, 66, NULL, }, + { "AR[EFLAG]", 26, 1, 2, 24, NULL, }, + { "AR[FCR]", 26, 1, 2, 21, NULL, }, + { "AR[FDR]", 26, 1, 2, 30, NULL, }, + { "AR[FIR]", 26, 1, 2, 29, NULL, }, + { "AR[FPSR].sf0.controls", 30, 1, 2, -1, NULL, }, + { "AR[FPSR].sf1.controls", 30, 1, 2, -1, NULL, }, + { "AR[FPSR].sf2.controls", 30, 1, 2, -1, NULL, }, + { "AR[FPSR].sf3.controls", 30, 1, 2, -1, NULL, }, + { "AR[FPSR].sf0.flags", 30, 1, 0, -1, NULL, }, + { "AR[FPSR].sf0.flags", 30, 1, 2, -1, NULL, }, + { "AR[FPSR].sf0.flags", 30, 1, 2, -1, NULL, }, + { "AR[FPSR].sf1.flags", 30, 1, 0, -1, NULL, }, + { "AR[FPSR].sf1.flags", 30, 1, 2, -1, NULL, }, + { "AR[FPSR].sf1.flags", 30, 1, 2, -1, NULL, }, + { "AR[FPSR].sf2.flags", 30, 1, 0, -1, NULL, }, + { "AR[FPSR].sf2.flags", 30, 1, 2, -1, NULL, }, + { "AR[FPSR].sf2.flags", 30, 1, 2, -1, NULL, }, + { "AR[FPSR].sf3.flags", 30, 1, 0, -1, NULL, }, + { "AR[FPSR].sf3.flags", 30, 1, 2, -1, NULL, }, + { "AR[FPSR].sf3.flags", 30, 1, 2, -1, NULL, }, + { "AR[FPSR].rv", 30, 1, 2, -1, NULL, }, + { "AR[FPSR].traps", 30, 1, 2, -1, NULL, }, + { "AR[FSR]", 26, 1, 2, 28, NULL, }, + { "AR[ITC]", 26, 1, 2, 44, NULL, }, + { "AR[K%], % in 0 - 7", 1, 1, 2, -1, NULL, }, + { "AR[LC]", 26, 1, 2, 65, NULL, }, + { "AR[PFS]", 26, 1, 0, 64, NULL, }, + { "AR[PFS]", 26, 1, 2, 64, NULL, }, + { "AR[PFS]", 26, 1, 2, 64, NULL, }, + { "AR[RNAT]", 26, 1, 2, 19, NULL, }, + { "AR[RSC]", 26, 1, 2, 16, NULL, }, + { "AR[SSD]", 26, 1, 2, 26, NULL, }, + { "AR[UNAT]{%}, % in 0 - 63", 2, 1, 2, -1, NULL, }, + { "AR%, % in 8-15, 20, 22-23, 31, 33-35, 37-39, 41-43, 45-47, 67-111", 3, 1, 0, -1, NULL, }, + { "AR%, % in 48 - 63, 112-127", 4, 1, 2, -1, NULL, }, + { "BR%, % in 0 - 7", 5, 1, 2, -1, NULL, }, + { "BR%, % in 0 - 7", 5, 1, 2, -1, NULL, }, + { "BR%, % in 0 - 7", 5, 1, 2, -1, NULL, }, + { "BR%, % in 0 - 7", 5, 1, 0, -1, NULL, }, + { "CFM", 6, 1, 2, -1, NULL, }, + { "CPUID#", 7, 1, 0, -1, NULL, }, + { "CR[CMCV]", 27, 1, 2, 74, NULL, }, + { "CR[DCR]", 27, 1, 2, 0, NULL, }, + { "CR[EOI]", 27, 1, 7, 67, "SC Section 5.8.3.4, \"End of External Interrupt Register (EOI Ð CR67)\" on page 2:119", }, + { "CR[GPTA]", 27, 1, 2, 9, NULL, }, + { "CR[IFA]", 27, 1, 2, 20, NULL, }, + { "CR[IFS]", 27, 1, 2, 23, NULL, }, + { "CR[IHA]", 27, 1, 2, 25, NULL, }, + { "CR[IIM]", 27, 1, 2, 24, NULL, }, + { "CR[IIP]", 27, 1, 2, 19, NULL, }, + { "CR[IIPA]", 27, 1, 2, 22, NULL, }, + { "CR[IPSR]", 27, 1, 2, 16, NULL, }, + { "CR[IRR%], % in 0 - 3", 8, 1, 2, -1, NULL, }, + { "CR[ISR]", 27, 1, 2, 17, NULL, }, + { "CR[ITIR]", 27, 1, 2, 21, NULL, }, + { "CR[ITM]", 27, 1, 2, 1, NULL, }, + { "CR[ITV]", 27, 1, 2, 72, NULL, }, + { "CR[IVA]", 27, 1, 2, 2, NULL, }, + { "CR[IVR]", 27, 1, 7, 65, "SC", }, + { "CR[LID]", 27, 1, 7, 64, "SC", }, + { "CR[LRR%], % in 0 - 1", 9, 1, 2, -1, NULL, }, + { "CR[PMV]", 27, 1, 2, 73, NULL, }, + { "CR[PTA]", 27, 1, 2, 8, NULL, }, + { "CR[TPR]", 27, 1, 2, 66, NULL, }, + { "CR%, % in 3-7, 10-15, 18, 26-63, 75-79, 82-127", 10, 1, 0, -1, NULL, }, + { "DBR#", 11, 1, 2, -1, NULL, }, + { "DTC", 0, 1, 0, -1, NULL, }, + { "DTC", 0, 1, 2, -1, NULL, }, + { "DTC", 0, 1, 2, -1, NULL, }, + { "DTC_LIMIT*", 0, 1, 2, -1, NULL, }, + { "DTR", 0, 1, 2, -1, NULL, }, + { "DTR", 0, 1, 2, -1, NULL, }, + { "DTR", 0, 1, 2, -1, NULL, }, + { "DTR", 0, 1, 0, -1, NULL, }, + { "FR%, % in 0 - 1", 12, 1, 0, -1, NULL, }, + { "FR%, % in 2 - 127", 13, 1, 2, -1, NULL, }, + { "GR0", 14, 1, 0, -1, NULL, }, + { "GR%, % in 1 - 127", 15, 1, 2, -1, NULL, }, + { "IBR#", 16, 1, 2, -1, NULL, }, + { "InService*", 17, 1, 7, -1, "SC", }, + { "IP", 0, 1, 0, -1, NULL, }, + { "ITC", 0, 1, 0, -1, NULL, }, + { "ITC", 0, 1, 2, -1, NULL, }, + { "ITC", 0, 1, 2, -1, NULL, }, + { "ITR", 0, 1, 2, -1, NULL, }, + { "ITR", 0, 1, 2, -1, NULL, }, + { "ITR", 0, 1, 0, -1, NULL, }, + { "memory", 0, 1, 0, -1, NULL, }, + { "MSR#", 18, 1, 7, -1, "SC", }, + { "PKR#", 19, 1, 0, -1, NULL, }, + { "PKR#", 19, 1, 0, -1, NULL, }, + { "PKR#", 19, 1, 2, -1, NULL, }, + { "PMC#", 20, 1, 2, -1, NULL, }, + { "PMD#", 21, 1, 2, -1, NULL, }, + { "PR0", 0, 1, 0, -1, NULL, }, + { "PR%, % in 1 - 15", 22, 1, 0, -1, NULL, }, + { "PR%, % in 1 - 15", 22, 1, 0, -1, NULL, }, + { "PR%, % in 1 - 15", 22, 1, 2, -1, NULL, }, + { "PR%, % in 1 - 15", 22, 1, 2, -1, NULL, }, + { "PR%, % in 16 - 62", 23, 1, 0, -1, NULL, }, + { "PR%, % in 16 - 62", 23, 1, 0, -1, NULL, }, + { "PR%, % in 16 - 62", 23, 1, 2, -1, NULL, }, + { "PR%, % in 16 - 62", 23, 1, 2, -1, NULL, }, + { "PR63", 24, 1, 0, -1, NULL, }, + { "PR63", 24, 1, 0, -1, NULL, }, + { "PR63", 24, 1, 2, -1, NULL, }, + { "PR63", 24, 1, 2, -1, NULL, }, + { "PSR.ac", 28, 1, 2, 3, NULL, }, + { "PSR.be", 28, 1, 2, 1, NULL, }, + { "PSR.bn", 28, 1, 2, 44, NULL, }, + { "PSR.cpl", 28, 1, 2, 32, NULL, }, + { "PSR.da", 28, 1, 2, 38, NULL, }, + { "PSR.db", 28, 1, 2, 24, NULL, }, + { "PSR.dd", 28, 1, 2, 39, NULL, }, + { "PSR.dfh", 28, 1, 2, 19, NULL, }, + { "PSR.dfl", 28, 1, 2, 18, NULL, }, + { "PSR.di", 28, 1, 2, 22, NULL, }, + { "PSR.dt", 28, 1, 2, 17, NULL, }, + { "PSR.ed", 28, 1, 2, 43, NULL, }, + { "PSR.i", 28, 1, 2, 14, NULL, }, + { "PSR.ia", 28, 1, 2, 14, NULL, }, + { "PSR.ic", 28, 1, 2, 13, NULL, }, + { "PSR.id", 28, 1, 2, 14, NULL, }, + { "PSR.is", 28, 1, 2, 14, NULL, }, + { "PSR.it", 28, 1, 2, 14, NULL, }, + { "PSR.lp", 28, 1, 2, 25, NULL, }, + { "PSR.mc", 28, 1, 2, 35, NULL, }, + { "PSR.mfh", 28, 1, 0, 5, NULL, }, + { "PSR.mfh", 28, 1, 2, 5, NULL, }, + { "PSR.mfh", 28, 1, 2, 5, NULL, }, + { "PSR.mfl", 28, 1, 0, 4, NULL, }, + { "PSR.mfl", 28, 1, 2, 4, NULL, }, + { "PSR.mfl", 28, 1, 2, 4, NULL, }, + { "PSR.pk", 28, 1, 2, 15, NULL, }, + { "PSR.pp", 28, 1, 2, 21, NULL, }, + { "PSR.ri", 28, 1, 2, 41, NULL, }, + { "PSR.rt", 28, 1, 2, 27, NULL, }, + { "PSR.si", 28, 1, 2, 23, NULL, }, + { "PSR.sp", 28, 1, 2, 20, NULL, }, + { "PSR.ss", 28, 1, 2, 40, NULL, }, + { "PSR.tb", 28, 1, 2, 26, NULL, }, + { "PSR.up", 28, 1, 2, 2, NULL, }, + { "PSR.vm", 28, 1, 2, 46, NULL, }, + { "RR#", 25, 1, 2, -1, NULL, }, + { "RSE", 29, 1, 2, -1, NULL, }, + { "PR63", 24, 2, 6, -1, NULL, }, +}; + +static const unsigned short dep0[] = { + 97, 282, 2140, 2327, +}; + +static const unsigned short dep1[] = { + 40, 41, 97, 158, 162, 175, 185, 282, 2138, 2139, 2140, 2166, 2167, 2170, 2173, + 2327, 4135, 20616, +}; + +static const unsigned short dep2[] = { + 97, 282, 2166, 2167, 2169, 2170, 2172, 2173, 2175, 2344, 2347, 2348, 2351, + 2352, 2355, 2356, +}; + +static const unsigned short dep3[] = { + 40, 41, 97, 158, 162, 175, 185, 282, 2138, 2139, 2140, 2166, 2167, 2170, 2173, + 2344, 2347, 2348, 2351, 2352, 2355, 2356, 4135, 20616, +}; + +static const unsigned short dep4[] = { + 97, 282, 22646, 22647, 22649, 22650, 22652, 22653, 22655, 22824, 22827, 22828, + 22831, 22832, 22835, 22836, +}; + +static const unsigned short dep5[] = { + 40, 41, 97, 158, 162, 175, 185, 282, 2138, 2139, 2140, 2166, 2167, 2170, 2173, + 4135, 20616, 22824, 22827, 22828, 22831, 22832, 22835, 22836, +}; + +static const unsigned short dep6[] = { + 97, 282, 2166, 2167, 2169, 2170, 2172, 2173, 2175, 2344, 2345, 2347, 2349, + 2351, 2353, 2355, +}; + +static const unsigned short dep7[] = { + 40, 41, 97, 158, 162, 175, 185, 282, 2138, 2139, 2140, 2166, 2167, 2170, 2173, + 2344, 2345, 2348, 2349, 2352, 2353, 2356, 4135, 20616, +}; + +static const unsigned short dep8[] = { + 97, 282, 2166, 2167, 2169, 2170, 2172, 2173, 2175, 2344, 2346, 2348, 2350, + 2352, 2354, 2356, +}; + +static const unsigned short dep9[] = { + 40, 41, 97, 158, 162, 175, 185, 282, 2138, 2139, 2140, 2166, 2167, 2170, 2173, + 2344, 2346, 2347, 2350, 2351, 2354, 2355, 4135, 20616, +}; + +static const unsigned short dep10[] = { + 97, 282, 2166, 2167, 2169, 2170, 2172, 2173, 2175, 2344, 2345, 2346, 2347, + 2348, 2349, 2350, 2351, 2352, 2353, 2354, 2355, 2356, +}; + +static const unsigned short dep11[] = { + 40, 41, 97, 158, 162, 175, 185, 282, 2138, 2139, 2140, 2166, 2167, 2170, 2173, + 2344, 2345, 2346, 2347, 2348, 2349, 2350, 2351, 2352, 2353, 2354, 2355, 2356, + 4135, 20616, +}; + +static const unsigned short dep12[] = { + 97, 282, 2395, +}; + +static const unsigned short dep13[] = { + 40, 41, 97, 158, 162, 164, 175, 185, 186, 188, 282, 2082, 2083, 2166, 2168, + 2169, 2171, 2172, 2174, 2175, 4135, +}; + +static const unsigned short dep14[] = { + 97, 163, 282, 325, 2395, 28866, 29018, +}; + +static const unsigned short dep15[] = { + 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, 28, 29, 30, 31, 32, 33, 40, 41, 97, 150, 152, 158, 162, + 164, 175, 185, 186, 188, 282, 325, 2082, 2083, 2166, 2168, 2169, 2171, 2172, + 2174, 2175, 4135, 28866, 29018, +}; + +static const unsigned short dep16[] = { + 1, 6, 40, 97, 137, 196, 201, 241, 282, 312, 2395, 28866, 29018, +}; + +static const unsigned short dep17[] = { + 1, 25, 27, 38, 40, 41, 97, 158, 162, 164, 166, 167, 175, 185, 186, 188, 196, + 201, 241, 282, 312, 2082, 2083, 2166, 2168, 2169, 2171, 2172, 2174, 2175, + 4135, 28866, 29018, +}; + +static const unsigned short dep18[] = { + 1, 40, 51, 97, 196, 241, 248, 282, 28866, 29018, +}; + +static const unsigned short dep19[] = { + 1, 38, 40, 41, 97, 158, 160, 161, 162, 175, 185, 190, 191, 196, 241, 248, + 282, 4135, 28866, 29018, +}; + +static const unsigned short dep20[] = { + 40, 97, 241, 282, +}; + +static const unsigned short dep21[] = { + 97, 158, 162, 175, 185, 241, 282, +}; + +static const unsigned short dep22[] = { + 1, 40, 97, 131, 135, 136, 138, 139, 142, 143, 146, 149, 152, 155, 156, 157, + 158, 161, 162, 163, 164, 167, 168, 169, 170, 173, 174, 175, 178, 181, 184, + 185, 188, 189, 191, 196, 241, 282, 309, 310, 311, 312, 313, 314, 315, 316, + 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 330, 331, 333, + 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 28866, 29018, +}; + +static const unsigned short dep23[] = { + 1, 38, 40, 41, 50, 51, 55, 58, 73, 97, 137, 138, 158, 162, 175, 185, 190, + 191, 196, 241, 282, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, + 320, 321, 322, 323, 324, 325, 326, 327, 328, 330, 331, 333, 334, 335, 336, + 337, 338, 339, 340, 341, 342, 343, 344, 4135, 28866, 29018, +}; + +static const unsigned short dep24[] = { + 97, 136, 282, 311, +}; + +static const unsigned short dep25[] = { + 97, 137, 138, 158, 162, 175, 185, 190, 191, 282, 311, +}; + +static const unsigned short dep26[] = { + 97, 137, 282, 312, +}; + +static const unsigned short dep27[] = { + 25, 26, 97, 98, 101, 105, 108, 137, 138, 158, 162, 164, 175, 185, 282, 312, + +}; + +static const unsigned short dep28[] = { + 97, 190, 282, 344, +}; + +static const unsigned short dep29[] = { + 97, 98, 101, 105, 108, 137, 138, 158, 162, 164, 175, 185, 282, 344, +}; + +static const unsigned short dep30[] = { + 40, 41, 97, 158, 162, 175, 185, 282, 2166, 2168, 2169, 2171, 2172, 2174, 2175, + 4135, +}; + +static const unsigned short dep31[] = { + 1, 25, 40, 97, 196, 228, 229, 241, 282, 2082, 2285, 2288, 2395, 28866, 29018, + +}; + +static const unsigned short dep32[] = { + 1, 6, 38, 40, 41, 97, 137, 138, 158, 162, 164, 175, 185, 186, 188, 196, 228, + 230, 241, 282, 2082, 2083, 2166, 2168, 2169, 2171, 2172, 2174, 2175, 2286, + 2288, 4135, 28866, 29018, +}; + +static const unsigned short dep33[] = { + 97, 282, +}; + +static const unsigned short dep34[] = { + 97, 158, 162, 175, 185, 282, 2082, 2084, +}; + +static const unsigned short dep35[] = { + 40, 41, 97, 158, 162, 164, 175, 185, 186, 188, 282, 2166, 2168, 2169, 2171, + 2172, 2174, 2175, 4135, +}; + +static const unsigned short dep36[] = { + 6, 37, 38, 39, 97, 125, 126, 201, 241, 282, 307, 308, 2395, +}; + +static const unsigned short dep37[] = { + 6, 37, 40, 41, 97, 158, 162, 164, 175, 185, 186, 188, 201, 241, 282, 307, + 308, 347, 2166, 2168, 2169, 2171, 2172, 2174, 2175, 4135, +}; + +static const unsigned short dep38[] = { + 24, 97, 227, 282, 2395, +}; + +static const unsigned short dep39[] = { + 24, 40, 41, 97, 158, 162, 164, 175, 185, 186, 188, 227, 282, 2166, 2168, 2169, + 2171, 2172, 2174, 2175, 4135, +}; + +static const unsigned short dep40[] = { + 6, 24, 37, 38, 39, 97, 125, 126, 201, 227, 241, 282, 307, 308, 2395, +}; + +static const unsigned short dep41[] = { + 6, 24, 37, 40, 41, 97, 158, 162, 164, 175, 185, 186, 188, 201, 227, 241, 282, + 307, 308, 347, 2166, 2168, 2169, 2171, 2172, 2174, 2175, 4135, +}; + +static const unsigned short dep42[] = { + 1, 6, 38, 40, 41, 97, 137, 138, 158, 162, 164, 175, 185, 186, 188, 196, 228, + 230, 241, 282, 2166, 2168, 2169, 2171, 2172, 2174, 2175, 2286, 2288, 4135, + 28866, 29018, +}; + +static const unsigned short dep43[] = { + 97, 158, 162, 175, 185, 282, +}; + +static const unsigned short dep44[] = { + 15, 97, 210, 211, 282, 2136, 2325, 18601, 18602, 18761, 18762, 18764, 18765, + 22646, 22647, 22648, 22650, 22651, 22653, 22654, 22824, 22827, 22828, 22831, + 22832, 22835, 22836, +}; + +static const unsigned short dep45[] = { + 11, 19, 20, 40, 41, 97, 158, 162, 175, 185, 210, 212, 282, 2135, 2136, 2137, + 2166, 2167, 2170, 2173, 2325, 4135, 16528, 16530, 16531, 16533, 18761, 18763, + 18764, 18766, 22824, 22827, 22828, 22831, 22832, 22835, 22836, +}; + +static const unsigned short dep46[] = { + 15, 16, 17, 18, 97, 210, 211, 213, 214, 216, 217, 219, 220, 282, 2136, 2325, + 18601, 18602, 18761, 18762, 18764, 18765, 22646, 22647, 22648, 22650, 22651, + 22653, 22654, 22824, 22827, 22828, 22831, 22832, 22835, 22836, +}; + +static const unsigned short dep47[] = { + 11, 12, 13, 14, 19, 20, 40, 41, 97, 158, 162, 175, 185, 210, 212, 213, 215, + 216, 218, 219, 221, 282, 2135, 2136, 2137, 2166, 2167, 2170, 2173, 2325, 4135, + 16528, 16530, 16531, 16533, 18761, 18763, 18764, 18766, 22824, 22827, 22828, + 22831, 22832, 22835, 22836, +}; + +static const unsigned short dep48[] = { + 16, 97, 213, 214, 282, 2136, 2325, 18601, 18602, 18761, 18762, 18764, 18765, + 22646, 22647, 22648, 22650, 22651, 22653, 22654, 22824, 22827, 22828, 22831, + 22832, 22835, 22836, +}; + +static const unsigned short dep49[] = { + 12, 19, 20, 40, 41, 97, 158, 162, 175, 185, 213, 215, 282, 2135, 2136, 2137, + 2166, 2167, 2170, 2173, 2325, 4135, 16528, 16530, 16531, 16533, 18761, 18763, + 18764, 18766, 22824, 22827, 22828, 22831, 22832, 22835, 22836, +}; + +static const unsigned short dep50[] = { + 17, 97, 216, 217, 282, 2136, 2325, 18601, 18602, 18761, 18762, 18764, 18765, + 22646, 22647, 22648, 22650, 22651, 22653, 22654, 22824, 22827, 22828, 22831, + 22832, 22835, 22836, +}; + +static const unsigned short dep51[] = { + 13, 19, 20, 40, 41, 97, 158, 162, 175, 185, 216, 218, 282, 2135, 2136, 2137, + 2166, 2167, 2170, 2173, 2325, 4135, 16528, 16530, 16531, 16533, 18761, 18763, + 18764, 18766, 22824, 22827, 22828, 22831, 22832, 22835, 22836, +}; + +static const unsigned short dep52[] = { + 18, 97, 219, 220, 282, 2136, 2325, 18601, 18602, 18761, 18762, 18764, 18765, + 22646, 22647, 22648, 22650, 22651, 22653, 22654, 22824, 22827, 22828, 22831, + 22832, 22835, 22836, +}; + +static const unsigned short dep53[] = { + 14, 19, 20, 40, 41, 97, 158, 162, 175, 185, 219, 221, 282, 2135, 2136, 2137, + 2166, 2167, 2170, 2173, 2325, 4135, 16528, 16530, 16531, 16533, 18761, 18763, + 18764, 18766, 22824, 22827, 22828, 22831, 22832, 22835, 22836, +}; + +static const unsigned short dep54[] = { + 15, 97, 210, 211, 282, 2136, 2325, 18601, 18602, 18761, 18762, 18764, 18765, + +}; + +static const unsigned short dep55[] = { + 11, 19, 20, 40, 41, 97, 158, 162, 175, 185, 210, 212, 282, 2135, 2136, 2137, + 2166, 2167, 2170, 2173, 2325, 4135, 16528, 16530, 16531, 16533, 18761, 18763, + 18764, 18766, +}; + +static const unsigned short dep56[] = { + 15, 16, 17, 18, 97, 210, 211, 213, 214, 216, 217, 219, 220, 282, 2136, 2325, + 18601, 18602, 18761, 18762, 18764, 18765, +}; + +static const unsigned short dep57[] = { + 11, 12, 13, 14, 19, 20, 40, 41, 97, 158, 162, 175, 185, 210, 212, 213, 215, + 216, 218, 219, 221, 282, 2135, 2136, 2137, 2166, 2167, 2170, 2173, 2325, 4135, + 16528, 16530, 16531, 16533, 18761, 18763, 18764, 18766, +}; + +static const unsigned short dep58[] = { + 16, 97, 213, 214, 282, 2136, 2325, 18601, 18602, 18761, 18762, 18764, 18765, + +}; + +static const unsigned short dep59[] = { + 12, 19, 20, 40, 41, 97, 158, 162, 175, 185, 213, 215, 282, 2135, 2136, 2137, + 2166, 2167, 2170, 2173, 2325, 4135, 16528, 16530, 16531, 16533, 18761, 18763, + 18764, 18766, +}; + +static const unsigned short dep60[] = { + 17, 97, 216, 217, 282, 2136, 2325, 18601, 18602, 18761, 18762, 18764, 18765, + +}; + +static const unsigned short dep61[] = { + 13, 19, 20, 40, 41, 97, 158, 162, 175, 185, 216, 218, 282, 2135, 2136, 2137, + 2166, 2167, 2170, 2173, 2325, 4135, 16528, 16530, 16531, 16533, 18761, 18763, + 18764, 18766, +}; + +static const unsigned short dep62[] = { + 18, 97, 219, 220, 282, 2136, 2325, 18601, 18602, 18761, 18762, 18764, 18765, + +}; + +static const unsigned short dep63[] = { + 14, 19, 20, 40, 41, 97, 158, 162, 175, 185, 219, 221, 282, 2135, 2136, 2137, + 2166, 2167, 2170, 2173, 2325, 4135, 16528, 16530, 16531, 16533, 18761, 18763, + 18764, 18766, +}; + +static const unsigned short dep64[] = { + 97, 282, 2136, 2325, 18601, 18602, 18761, 18762, 18764, 18765, +}; + +static const unsigned short dep65[] = { + 40, 41, 97, 158, 162, 175, 185, 282, 2135, 2136, 2137, 2166, 2167, 2170, 2173, + 2325, 4135, 16528, 16530, 16531, 16533, 18761, 18763, 18764, 18766, +}; + +static const unsigned short dep66[] = { + 11, 97, 206, 282, +}; + +static const unsigned short dep67[] = { + 11, 40, 41, 97, 158, 162, 175, 185, 206, 282, 2166, 2167, 2170, 2173, 4135, + +}; + +static const unsigned short dep68[] = { + 11, 40, 41, 97, 158, 162, 175, 185, 282, 2166, 2167, 2170, 2173, 4135, +}; + +static const unsigned short dep69[] = { + 12, 97, 207, 282, +}; + +static const unsigned short dep70[] = { + 11, 40, 41, 97, 158, 162, 175, 185, 207, 282, 2166, 2167, 2170, 2173, 4135, + +}; + +static const unsigned short dep71[] = { + 13, 97, 208, 282, +}; + +static const unsigned short dep72[] = { + 11, 40, 41, 97, 158, 162, 175, 185, 208, 282, 2166, 2167, 2170, 2173, 4135, + +}; + +static const unsigned short dep73[] = { + 14, 97, 209, 282, +}; + +static const unsigned short dep74[] = { + 11, 40, 41, 97, 158, 162, 175, 185, 209, 282, 2166, 2167, 2170, 2173, 4135, + +}; + +static const unsigned short dep75[] = { + 15, 97, 211, 212, 282, +}; + +static const unsigned short dep76[] = { + 40, 41, 97, 158, 162, 175, 185, 211, 212, 282, 2166, 2167, 2170, 2173, 4135, + +}; + +static const unsigned short dep77[] = { + 40, 41, 97, 158, 162, 175, 185, 282, 2166, 2167, 2170, 2173, 4135, +}; + +static const unsigned short dep78[] = { + 16, 97, 214, 215, 282, +}; + +static const unsigned short dep79[] = { + 40, 41, 97, 158, 162, 175, 185, 214, 215, 282, 2166, 2167, 2170, 2173, 4135, + +}; + +static const unsigned short dep80[] = { + 17, 97, 217, 218, 282, +}; + +static const unsigned short dep81[] = { + 40, 41, 97, 158, 162, 175, 185, 217, 218, 282, 2166, 2167, 2170, 2173, 4135, + +}; + +static const unsigned short dep82[] = { + 18, 97, 220, 221, 282, +}; + +static const unsigned short dep83[] = { + 40, 41, 97, 158, 162, 175, 185, 220, 221, 282, 2166, 2167, 2170, 2173, 4135, + +}; + +static const unsigned short dep84[] = { + 15, 19, 20, 40, 41, 97, 158, 162, 164, 175, 185, 186, 188, 282, 2166, 2167, + 2170, 2173, 4135, +}; + +static const unsigned short dep85[] = { + 15, 16, 19, 20, 40, 41, 97, 158, 162, 164, 175, 185, 186, 188, 282, 2166, + 2167, 2170, 2173, 4135, +}; + +static const unsigned short dep86[] = { + 15, 17, 19, 20, 40, 41, 97, 158, 162, 164, 175, 185, 186, 188, 282, 2166, + 2167, 2170, 2173, 4135, +}; + +static const unsigned short dep87[] = { + 15, 18, 19, 20, 40, 41, 97, 158, 162, 164, 175, 185, 186, 188, 282, 2166, + 2167, 2170, 2173, 4135, +}; + +static const unsigned short dep88[] = { + 15, 97, 210, 211, 282, +}; + +static const unsigned short dep89[] = { + 11, 19, 20, 40, 41, 97, 158, 162, 175, 185, 210, 212, 282, 2166, 2167, 2170, + 2173, 4135, +}; + +static const unsigned short dep90[] = { + 15, 16, 17, 18, 97, 210, 211, 213, 214, 216, 217, 219, 220, 282, +}; + +static const unsigned short dep91[] = { + 11, 12, 13, 14, 19, 20, 40, 41, 97, 158, 162, 175, 185, 210, 212, 213, 215, + 216, 218, 219, 221, 282, 2166, 2167, 2170, 2173, 4135, +}; + +static const unsigned short dep92[] = { + 16, 97, 213, 214, 282, +}; + +static const unsigned short dep93[] = { + 12, 19, 20, 40, 41, 97, 158, 162, 175, 185, 213, 215, 282, 2166, 2167, 2170, + 2173, 4135, +}; + +static const unsigned short dep94[] = { + 17, 97, 216, 217, 282, +}; + +static const unsigned short dep95[] = { + 13, 19, 20, 40, 41, 97, 158, 162, 175, 185, 216, 218, 282, 2166, 2167, 2170, + 2173, 4135, +}; + +static const unsigned short dep96[] = { + 18, 97, 219, 220, 282, +}; + +static const unsigned short dep97[] = { + 14, 19, 20, 40, 41, 97, 158, 162, 175, 185, 219, 221, 282, 2166, 2167, 2170, + 2173, 4135, +}; + +static const unsigned short dep98[] = { + 15, 97, 210, 211, 282, 2166, 2167, 2168, 2170, 2171, 2173, 2174, 2344, 2347, + 2348, 2351, 2352, 2355, 2356, +}; + +static const unsigned short dep99[] = { + 11, 19, 20, 40, 41, 97, 158, 162, 175, 185, 210, 212, 282, 2135, 2136, 2137, + 2166, 2167, 2170, 2173, 2344, 2347, 2348, 2351, 2352, 2355, 2356, 4135, 16528, + 16530, 16531, 16533, +}; + +static const unsigned short dep100[] = { + 15, 16, 17, 18, 97, 210, 211, 213, 214, 216, 217, 219, 220, 282, 2166, 2167, + 2168, 2170, 2171, 2173, 2174, 2344, 2347, 2348, 2351, 2352, 2355, 2356, +}; + +static const unsigned short dep101[] = { + 11, 12, 13, 14, 19, 20, 40, 41, 97, 158, 162, 175, 185, 210, 212, 213, 215, + 216, 218, 219, 221, 282, 2135, 2136, 2137, 2166, 2167, 2170, 2173, 2344, 2347, + 2348, 2351, 2352, 2355, 2356, 4135, 16528, 16530, 16531, 16533, +}; + +static const unsigned short dep102[] = { + 16, 97, 213, 214, 282, 2166, 2167, 2168, 2170, 2171, 2173, 2174, 2344, 2347, + 2348, 2351, 2352, 2355, 2356, +}; + +static const unsigned short dep103[] = { + 12, 19, 20, 40, 41, 97, 158, 162, 175, 185, 213, 215, 282, 2135, 2136, 2137, + 2166, 2167, 2170, 2173, 2344, 2347, 2348, 2351, 2352, 2355, 2356, 4135, 16528, + 16530, 16531, 16533, +}; + +static const unsigned short dep104[] = { + 17, 97, 216, 217, 282, 2166, 2167, 2168, 2170, 2171, 2173, 2174, 2344, 2347, + 2348, 2351, 2352, 2355, 2356, +}; + +static const unsigned short dep105[] = { + 13, 19, 20, 40, 41, 97, 158, 162, 175, 185, 216, 218, 282, 2135, 2136, 2137, + 2166, 2167, 2170, 2173, 2344, 2347, 2348, 2351, 2352, 2355, 2356, 4135, 16528, + 16530, 16531, 16533, +}; + +static const unsigned short dep106[] = { + 18, 97, 219, 220, 282, 2166, 2167, 2168, 2170, 2171, 2173, 2174, 2344, 2347, + 2348, 2351, 2352, 2355, 2356, +}; + +static const unsigned short dep107[] = { + 14, 19, 20, 40, 41, 97, 158, 162, 175, 185, 219, 221, 282, 2135, 2136, 2137, + 2166, 2167, 2170, 2173, 2344, 2347, 2348, 2351, 2352, 2355, 2356, 4135, 16528, + 16530, 16531, 16533, +}; + +static const unsigned short dep108[] = { + 15, 97, 210, 211, 282, 22646, 22647, 22648, 22650, 22651, 22653, 22654, 22824, + 22827, 22828, 22831, 22832, 22835, 22836, +}; + +static const unsigned short dep109[] = { + 11, 19, 20, 40, 41, 97, 158, 162, 175, 185, 210, 212, 282, 2135, 2136, 2137, + 2166, 2167, 2170, 2173, 4135, 16528, 16530, 16531, 16533, 22824, 22827, 22828, + 22831, 22832, 22835, 22836, +}; + +static const unsigned short dep110[] = { + 15, 16, 17, 18, 97, 210, 211, 213, 214, 216, 217, 219, 220, 282, 22646, 22647, + 22648, 22650, 22651, 22653, 22654, 22824, 22827, 22828, 22831, 22832, 22835, + 22836, +}; + +static const unsigned short dep111[] = { + 11, 12, 13, 14, 19, 20, 40, 41, 97, 158, 162, 175, 185, 210, 212, 213, 215, + 216, 218, 219, 221, 282, 2135, 2136, 2137, 2166, 2167, 2170, 2173, 4135, 16528, + 16530, 16531, 16533, 22824, 22827, 22828, 22831, 22832, 22835, 22836, +}; + +static const unsigned short dep112[] = { + 16, 97, 213, 214, 282, 22646, 22647, 22648, 22650, 22651, 22653, 22654, 22824, + 22827, 22828, 22831, 22832, 22835, 22836, +}; + +static const unsigned short dep113[] = { + 12, 19, 20, 40, 41, 97, 158, 162, 175, 185, 213, 215, 282, 2135, 2136, 2137, + 2166, 2167, 2170, 2173, 4135, 16528, 16530, 16531, 16533, 22824, 22827, 22828, + 22831, 22832, 22835, 22836, +}; + +static const unsigned short dep114[] = { + 17, 97, 216, 217, 282, 22646, 22647, 22648, 22650, 22651, 22653, 22654, 22824, + 22827, 22828, 22831, 22832, 22835, 22836, +}; + +static const unsigned short dep115[] = { + 13, 19, 20, 40, 41, 97, 158, 162, 175, 185, 216, 218, 282, 2135, 2136, 2137, + 2166, 2167, 2170, 2173, 4135, 16528, 16530, 16531, 16533, 22824, 22827, 22828, + 22831, 22832, 22835, 22836, +}; + +static const unsigned short dep116[] = { + 18, 97, 219, 220, 282, 22646, 22647, 22648, 22650, 22651, 22653, 22654, 22824, + 22827, 22828, 22831, 22832, 22835, 22836, +}; + +static const unsigned short dep117[] = { + 14, 19, 20, 40, 41, 97, 158, 162, 175, 185, 219, 221, 282, 2135, 2136, 2137, + 2166, 2167, 2170, 2173, 4135, 16528, 16530, 16531, 16533, 22824, 22827, 22828, + 22831, 22832, 22835, 22836, +}; + +static const unsigned short dep118[] = { + 97, 282, 2166, 2167, 2168, 2170, 2171, 2173, 2174, 2344, 2347, 2348, 2351, + 2352, 2355, 2356, +}; + +static const unsigned short dep119[] = { + 40, 41, 97, 158, 162, 175, 185, 282, 2135, 2136, 2137, 2166, 2167, 2170, 2173, + 2344, 2347, 2348, 2351, 2352, 2355, 2356, 4135, 16528, 16530, 16531, 16533, + +}; + +static const unsigned short dep120[] = { + 97, 282, 22646, 22647, 22648, 22650, 22651, 22653, 22654, 22824, 22827, 22828, + 22831, 22832, 22835, 22836, +}; + +static const unsigned short dep121[] = { + 40, 41, 97, 158, 162, 175, 185, 282, 2135, 2136, 2137, 2166, 2167, 2170, 2173, + 4135, 16528, 16530, 16531, 16533, 22824, 22827, 22828, 22831, 22832, 22835, + 22836, +}; + +static const unsigned short dep122[] = { + 19, 20, 40, 41, 97, 158, 162, 175, 185, 282, 2135, 2136, 2137, 2166, 2167, + 2170, 2173, 2325, 4135, 16528, 16530, 16531, 16533, 18761, 18763, 18764, 18766, + +}; + +static const unsigned short dep123[] = { + 40, 41, 97, 158, 162, 164, 175, 185, 186, 188, 282, 2138, 2139, 2140, 2166, + 2167, 2170, 2173, 4135, 20616, +}; + +static const unsigned short dep124[] = { + 97, 282, 2083, 2084, 2286, 2287, +}; + +static const unsigned short dep125[] = { + 40, 41, 97, 158, 162, 175, 185, 282, 2138, 2139, 2140, 2166, 2167, 2170, 2173, + 2285, 2287, 4135, 20616, +}; + +static const unsigned short dep126[] = { + 40, 41, 97, 158, 162, 175, 185, 282, 2082, 2084, 2166, 2167, 2170, 2173, 2327, + 4135, 20616, +}; + +static const unsigned short dep127[] = { + 97, 282, 14455, 14457, 14458, 14460, 14461, 14463, 14635, 14636, 14639, 14640, + 14643, 14644, +}; + +static const unsigned short dep128[] = { + 40, 41, 97, 158, 162, 175, 185, 282, 2138, 2139, 2140, 4135, 14635, 14636, + 14639, 14640, 14643, 14644, 20616, 24694, 24695, 24698, 24701, +}; + +static const unsigned short dep129[] = { + 97, 122, 124, 125, 127, 282, 303, 304, 307, 308, +}; + +static const unsigned short dep130[] = { + 40, 41, 97, 158, 162, 175, 185, 282, 303, 304, 307, 308, 4135, 24694, 24695, + 24698, 24701, +}; + +static const unsigned short dep131[] = { + 40, 41, 97, 158, 162, 175, 185, 282, 2166, 2167, 2170, 2173, 2327, 4135, 20616, + +}; + +static const unsigned short dep132[] = { + 40, 41, 97, 119, 122, 125, 158, 162, 175, 185, 282, 2327, 4135, 20616, 24694, + +}; + +static const unsigned short dep133[] = { + 6, 24, 26, 27, 97, 201, 227, 230, 282, 2081, 2284, +}; + +static const unsigned short dep134[] = { + 40, 41, 97, 158, 162, 175, 185, 201, 227, 229, 282, 2138, 2139, 2140, 2166, + 2167, 2170, 2173, 2284, 4135, 20616, +}; + +static const unsigned short dep135[] = { + 6, 24, 25, 26, 40, 41, 97, 158, 162, 175, 185, 282, 2081, 2166, 2167, 2170, + 2173, 2327, 4135, 20616, +}; + +static const unsigned short dep136[] = { + 40, 41, 97, 158, 162, 175, 185, 282, 2166, 2167, 2170, 2173, 2344, 2347, 2348, + 2351, 2352, 2355, 2356, 4135, +}; + +static const unsigned short dep137[] = { + 40, 41, 97, 158, 162, 175, 185, 282, 2166, 2167, 2170, 2173, 4135, 22824, + 22827, 22828, 22831, 22832, 22835, 22836, +}; + +static const unsigned short dep138[] = { + 40, 41, 97, 158, 162, 175, 185, 282, 2166, 2167, 2170, 2173, 2344, 2345, 2348, + 2349, 2352, 2353, 2356, 4135, +}; + +static const unsigned short dep139[] = { + 40, 41, 97, 158, 162, 175, 185, 282, 2166, 2167, 2170, 2173, 2344, 2346, 2347, + 2350, 2351, 2354, 2355, 4135, +}; + +static const unsigned short dep140[] = { + 40, 41, 97, 158, 162, 175, 185, 282, 2166, 2167, 2170, 2173, 2344, 2345, 2346, + 2347, 2348, 2349, 2350, 2351, 2352, 2353, 2354, 2355, 2356, 4135, +}; + +static const unsigned short dep141[] = { + 0, 40, 41, 97, 158, 162, 164, 175, 185, 186, 188, 282, 2166, 2167, 2170, 2173, + 4135, +}; + +static const unsigned short dep142[] = { + 0, 97, 195, 282, +}; + +static const unsigned short dep143[] = { + 0, 40, 41, 97, 158, 162, 164, 175, 185, 186, 188, 195, 282, 2166, 2167, 2170, + 2173, 4135, +}; + +static const unsigned short dep144[] = { + 40, 41, 97, 158, 162, 175, 185, 195, 282, 2166, 2167, 2170, 2173, 4135, +}; + +static const unsigned short dep145[] = { + 2, 28, 97, 197, 231, 282, 28866, 29018, +}; + +static const unsigned short dep146[] = { + 1, 2, 28, 29, 97, 158, 162, 175, 177, 178, 185, 197, 231, 282, 28866, 29018, + +}; + +static const unsigned short dep147[] = { + 1, 28, 29, 38, 40, 41, 97, 158, 162, 175, 177, 178, 185, 197, 231, 282, 4135, + 28866, 29018, +}; + +static const unsigned short dep148[] = { + 0, 40, 41, 97, 158, 162, 175, 185, 195, 282, 2166, 2167, 2170, 2173, 4135, + +}; + +static const unsigned short dep149[] = { + 1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, + 28, 29, 30, 31, 97, 196, 197, 198, 199, 200, 202, 203, 204, 205, 206, 207, + 208, 209, 211, 212, 214, 215, 217, 218, 220, 221, 222, 223, 224, 225, 231, + 232, 233, 234, 282, 2071, 2081, 2274, 2284, 28866, 29018, +}; + +static const unsigned short dep150[] = { + 29, 40, 41, 97, 137, 138, 158, 162, 175, 185, 190, 191, 196, 197, 198, 199, + 200, 202, 203, 204, 205, 206, 207, 208, 209, 211, 212, 214, 215, 217, 218, + 220, 221, 222, 223, 224, 225, 231, 232, 233, 234, 282, 2138, 2139, 2140, 2166, + 2167, 2170, 2173, 2274, 2284, 4135, 20616, 28866, 29018, +}; + +static const unsigned short dep151[] = { + 97, 282, 14464, 14466, 14468, 14470, 14505, 14506, 14525, 14645, 14646, 14666, + 14667, 14669, 14670, 14679, +}; + +static const unsigned short dep152[] = { + 40, 41, 97, 158, 162, 175, 183, 184, 185, 282, 2166, 2167, 2170, 2173, 4135, + 14645, 14646, 14666, 14667, 14669, 14670, 14679, +}; + +static const unsigned short dep153[] = { + 14464, 14466, 14468, 14470, 14505, 14506, 14525, 14645, 14646, 14666, 14667, + 14669, 14670, 14679, +}; + +static const unsigned short dep154[] = { + 183, 184, 14645, 14646, 14666, 14667, 14669, 14670, 14679, +}; + +static const unsigned short dep155[] = { + 97, 282, 14465, 14466, 14469, 14470, 14480, 14481, 14483, 14484, 14486, 14487, + 14489, 14490, 14493, 14495, 14496, 14505, 14506, 14507, 14508, 14510, 14515, + 14516, 14518, 14519, 14525, 14645, 14646, 14652, 14653, 14654, 14655, 14657, + 14659, 14666, 14667, 14669, 14670, 14671, 14672, 14675, 14676, 14679, +}; + +static const unsigned short dep156[] = { + 40, 41, 97, 137, 138, 158, 162, 175, 185, 190, 191, 282, 2166, 2167, 2170, + 2173, 4135, 14645, 14646, 14652, 14653, 14654, 14655, 14657, 14659, 14666, + 14667, 14669, 14670, 14671, 14672, 14675, 14676, 14679, 34888, +}; + +static const unsigned short dep157[] = { + 40, 41, 97, 137, 138, 158, 162, 175, 185, 190, 191, 282, 2166, 2167, 2170, + 2173, 4135, 14645, 14646, 14652, 14653, 14654, 14655, 14657, 14659, 14666, + 14667, 14669, 14670, 14671, 14672, 14675, 14676, 14679, +}; + +static const unsigned short dep158[] = { + 1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, + 28, 29, 30, 31, 40, 41, 97, 137, 138, 158, 162, 175, 180, 181, 185, 190, 191, + 282, 2071, 2081, 2166, 2167, 2170, 2173, 2327, 4135, 20616, 28866, +}; + +static const unsigned short dep159[] = { + 43, 44, 45, 46, 47, 48, 49, 50, 52, 53, 54, 55, 56, 57, 58, 60, 61, 62, 63, + 64, 65, 67, 69, 70, 71, 72, 73, 94, 96, 97, 243, 244, 245, 246, 247, 248, + 249, 250, 251, 252, 253, 255, 256, 257, 258, 259, 261, 263, 264, 265, 281, + 282, 2116, 2310, +}; + +static const unsigned short dep160[] = { + 40, 41, 96, 97, 137, 138, 158, 160, 161, 162, 175, 185, 190, 191, 243, 244, + 245, 246, 247, 248, 249, 250, 251, 252, 253, 255, 256, 257, 258, 259, 261, + 263, 264, 265, 281, 282, 2138, 2139, 2140, 2166, 2167, 2170, 2173, 2310, 4135, + 20616, +}; + +static const unsigned short dep161[] = { + 59, 95, 97, 254, 281, 282, 2140, 2327, +}; + +static const unsigned short dep162[] = { + 40, 41, 43, 44, 46, 48, 49, 51, 52, 53, 54, 56, 57, 60, 61, 63, 64, 65, 66, + 67, 69, 70, 71, 94, 95, 97, 137, 138, 158, 160, 161, 162, 175, 185, 190, 191, + 254, 281, 282, 2107, 2116, 2166, 2167, 2170, 2173, 2327, 4135, 20616, +}; + +static const unsigned short dep163[] = { + 2, 28, 41, 97, 197, 231, 241, 282, 2140, 2327, 28866, 29018, +}; + +static const unsigned short dep164[] = { + 2, 25, 26, 28, 29, 38, 40, 41, 97, 158, 162, 175, 177, 178, 185, 197, 231, + 241, 282, 2327, 4135, 20616, 28866, 29018, +}; + +static const unsigned short dep165[] = { + 97, 129, 130, 133, 134, 140, 141, 144, 145, 147, 148, 150, 151, 153, 154, + 157, 159, 160, 165, 166, 169, 170, 171, 172, 174, 176, 177, 179, 180, 182, + 183, 186, 187, 189, 282, 309, 310, 314, 316, 317, 318, 319, 321, 323, 327, + 330, 331, 333, 334, 335, 336, 338, 339, 340, 342, 343, +}; + +static const unsigned short dep166[] = { + 40, 41, 97, 137, 138, 158, 162, 175, 185, 190, 191, 282, 309, 310, 314, 316, + 317, 318, 319, 321, 323, 327, 330, 331, 333, 334, 335, 336, 338, 339, 340, + 342, 343, 2138, 2139, 2140, 2166, 2167, 2170, 2173, 4135, 20616, 34888, +}; + +static const unsigned short dep167[] = { + 97, 128, 130, 132, 134, 169, 170, 189, 282, 309, 310, 330, 331, 333, 334, + 343, +}; + +static const unsigned short dep168[] = { + 40, 41, 97, 158, 162, 175, 183, 184, 185, 282, 309, 310, 330, 331, 333, 334, + 343, 2138, 2139, 2140, 2166, 2167, 2170, 2173, 4135, 20616, +}; + +static const unsigned short dep169[] = { + 40, 41, 97, 130, 131, 134, 135, 137, 138, 141, 142, 145, 146, 148, 149, 151, + 152, 154, 155, 157, 158, 159, 161, 162, 164, 165, 167, 168, 169, 170, 172, + 173, 174, 175, 176, 178, 179, 181, 182, 184, 185, 187, 188, 189, 190, 191, + 282, 2166, 2167, 2170, 2173, 2327, 4135, 20616, +}; + +static const unsigned short dep170[] = { + 40, 41, 97, 130, 131, 134, 135, 158, 162, 169, 170, 175, 185, 189, 282, 2166, + 2167, 2170, 2173, 2327, 4135, 20616, +}; + +static const unsigned short dep171[] = { + 40, 41, 70, 76, 77, 82, 84, 97, 111, 137, 138, 153, 155, 158, 162, 171, 173, + 175, 185, 192, 282, 2138, 2139, 2140, 2166, 2167, 2170, 2173, 2327, 4135, + 20616, +}; + +static const unsigned short dep172[] = { + 40, 41, 70, 76, 77, 82, 84, 97, 111, 137, 138, 139, 140, 142, 143, 153, 155, + 158, 162, 171, 173, 175, 185, 192, 282, 2138, 2139, 2140, 2166, 2167, 2170, + 2173, 4135, 20616, +}; + +static const unsigned short dep173[] = { + 77, 78, 97, 101, 102, 269, 270, 282, 284, 285, +}; + +static const unsigned short dep174[] = { + 40, 41, 47, 62, 78, 80, 86, 97, 99, 102, 137, 138, 158, 160, 161, 162, 175, + 185, 190, 191, 192, 269, 270, 282, 284, 285, 2138, 2139, 2140, 2166, 2167, + 2170, 2173, 4135, 20616, +}; + +static const unsigned short dep175[] = { + 40, 41, 47, 62, 78, 80, 97, 99, 102, 104, 106, 137, 138, 158, 160, 161, 162, + 175, 185, 190, 191, 192, 269, 270, 282, 284, 285, 2138, 2139, 2140, 2166, + 2167, 2170, 2173, 4135, 20616, +}; + +static const unsigned short dep176[] = { + 97, 282, 12480, 12481, 12633, +}; + +static const unsigned short dep177[] = { + 40, 41, 97, 137, 138, 158, 162, 175, 185, 190, 191, 282, 2138, 2139, 2140, + 2166, 2167, 2170, 2173, 4135, 12633, 20616, +}; + +static const unsigned short dep178[] = { + 97, 282, 6219, 6220, 6411, +}; + +static const unsigned short dep179[] = { + 40, 41, 97, 137, 138, 158, 162, 175, 185, 190, 191, 282, 2138, 2139, 2140, + 2166, 2167, 2170, 2173, 4135, 6411, 20616, +}; + +static const unsigned short dep180[] = { + 97, 282, 6237, 6424, +}; + +static const unsigned short dep181[] = { + 40, 41, 97, 137, 138, 158, 162, 175, 185, 190, 191, 282, 2138, 2139, 2140, + 2166, 2167, 2170, 2173, 4135, 6424, 20616, +}; + +static const unsigned short dep182[] = { + 97, 282, 6255, 6256, 6257, 6258, 6435, 6437, 8484, +}; + +static const unsigned short dep183[] = { + 40, 41, 97, 137, 138, 158, 162, 175, 185, 190, 191, 282, 2138, 2139, 2140, + 2166, 2167, 2170, 2173, 4135, 6258, 6436, 6437, 8304, 8483, 20616, +}; + +static const unsigned short dep184[] = { + 97, 282, 6259, 6260, 6438, +}; + +static const unsigned short dep185[] = { + 40, 41, 97, 137, 138, 158, 162, 175, 185, 190, 191, 282, 2138, 2139, 2140, + 2166, 2167, 2170, 2173, 4135, 6438, 20616, +}; + +static const unsigned short dep186[] = { + 97, 282, 6261, 6439, +}; + +static const unsigned short dep187[] = { + 40, 41, 97, 137, 138, 158, 162, 175, 185, 190, 191, 282, 2138, 2139, 2140, + 2166, 2167, 2170, 2173, 4135, 6439, 20616, +}; + +static const unsigned short dep188[] = { + 97, 282, 10350, 10530, +}; + +static const unsigned short dep189[] = { + 40, 41, 97, 137, 138, 158, 162, 175, 185, 190, 191, 282, 2138, 2139, 2140, + 2166, 2167, 2170, 2173, 4135, 10530, 20616, +}; + +static const unsigned short dep190[] = { + 77, 78, 82, 83, 97, 101, 102, 269, 270, 272, 273, 282, 284, 285, +}; + +static const unsigned short dep191[] = { + 40, 41, 47, 62, 78, 80, 83, 86, 97, 99, 102, 137, 138, 158, 160, 161, 162, + 175, 185, 190, 191, 192, 269, 270, 272, 274, 282, 284, 285, 2138, 2139, 2140, + 2166, 2167, 2170, 2173, 4135, 20616, +}; + +static const unsigned short dep192[] = { + 77, 78, 97, 101, 102, 104, 105, 269, 270, 282, 284, 285, 286, 287, +}; + +static const unsigned short dep193[] = { + 40, 41, 47, 62, 78, 80, 97, 99, 102, 104, 106, 137, 138, 158, 160, 161, 162, + 175, 185, 190, 191, 192, 269, 270, 282, 284, 285, 286, 287, 2138, 2139, 2140, + 2166, 2167, 2170, 2173, 4135, 20616, +}; + +static const unsigned short dep194[] = { + 40, 41, 97, 137, 138, 158, 162, 175, 185, 190, 191, 282, 2138, 2139, 2140, + 2166, 2167, 2170, 2173, 2327, 4135, 12481, 20616, +}; + +static const unsigned short dep195[] = { + 40, 41, 97, 137, 138, 158, 162, 175, 185, 190, 191, 282, 2138, 2139, 2140, + 2166, 2167, 2170, 2173, 2327, 4135, 6219, 20616, +}; + +static const unsigned short dep196[] = { + 40, 41, 97, 137, 138, 158, 162, 175, 185, 190, 191, 282, 2138, 2139, 2140, + 2166, 2167, 2170, 2173, 2327, 4135, 6237, 20616, +}; + +static const unsigned short dep197[] = { + 40, 41, 97, 137, 138, 158, 162, 175, 185, 190, 191, 282, 2138, 2139, 2140, + 2166, 2167, 2170, 2173, 2327, 4135, 6257, 8303, 20616, +}; + +static const unsigned short dep198[] = { + 40, 41, 97, 137, 138, 158, 162, 175, 185, 190, 191, 282, 2138, 2139, 2140, + 2166, 2167, 2170, 2173, 2327, 4135, 6259, 20616, +}; + +static const unsigned short dep199[] = { + 40, 41, 97, 137, 138, 158, 162, 175, 183, 184, 185, 282, 2138, 2139, 2140, + 2166, 2167, 2170, 2173, 2327, 4135, 6260, 6261, 20616, +}; + +static const unsigned short dep200[] = { + 40, 41, 97, 158, 162, 175, 185, 282, 2138, 2139, 2140, 2166, 2167, 2170, 2173, + 2327, 4135, 10350, 20616, +}; + +static const unsigned short dep201[] = { + 40, 41, 97, 158, 162, 175, 185, 190, 191, 282, 2138, 2139, 2140, 2166, 2167, + 2170, 2173, 2327, 4135, 6186, 20616, +}; + +static const unsigned short dep202[] = { + 77, 79, 80, 97, 98, 99, 100, 268, 269, 282, 283, 284, +}; + +static const unsigned short dep203[] = { + 40, 41, 78, 79, 83, 85, 97, 100, 102, 104, 107, 137, 138, 158, 162, 175, 185, + 190, 191, 192, 268, 270, 282, 283, 285, 2138, 2139, 2140, 2166, 2167, 2170, + 2173, 4135, 20616, +}; + +static const unsigned short dep204[] = { + 77, 79, 80, 81, 97, 98, 99, 100, 103, 268, 269, 271, 282, 283, 284, +}; + +static const unsigned short dep205[] = { + 40, 41, 78, 79, 81, 83, 85, 97, 100, 102, 103, 104, 107, 137, 138, 158, 162, + 175, 185, 190, 191, 192, 268, 270, 271, 282, 283, 285, 2138, 2139, 2140, 2166, + 2167, 2170, 2173, 4135, 20616, +}; + +static const unsigned short dep206[] = { + 77, 79, 80, 84, 85, 86, 97, 98, 99, 100, 268, 269, 274, 275, 282, 283, 284, + +}; + +static const unsigned short dep207[] = { + 40, 41, 78, 79, 83, 85, 97, 100, 102, 137, 138, 158, 162, 175, 185, 190, 191, + 192, 268, 270, 273, 275, 282, 283, 285, 2138, 2139, 2140, 2166, 2167, 2170, + 2173, 4135, 20616, +}; + +static const unsigned short dep208[] = { + 77, 79, 80, 97, 98, 99, 100, 106, 107, 108, 268, 269, 282, 283, 284, 287, + 288, +}; + +static const unsigned short dep209[] = { + 40, 41, 78, 79, 97, 100, 102, 104, 107, 137, 138, 158, 162, 175, 185, 190, + 191, 192, 268, 270, 282, 283, 285, 286, 288, 2138, 2139, 2140, 2166, 2167, + 2170, 2173, 4135, 20616, +}; + +static const unsigned short dep210[] = { + 40, 41, 46, 70, 97, 158, 162, 175, 185, 190, 191, 192, 282, 2138, 2139, 2140, + 2166, 2167, 2170, 2173, 2327, 4135, 20616, +}; + +static const unsigned short dep211[] = { + 40, 41, 97, 158, 162, 175, 185, 190, 191, 192, 282, 2138, 2139, 2140, 2166, + 2167, 2170, 2173, 2327, 4135, 20616, +}; + +static const unsigned short dep212[] = { + 40, 41, 70, 77, 82, 84, 97, 137, 138, 153, 155, 158, 162, 175, 185, 190, 191, + 192, 282, 2138, 2139, 2140, 2166, 2167, 2170, 2173, 2327, 4135, 20616, +}; + +static const unsigned short dep213[] = { + 40, 41, 97, 158, 162, 164, 175, 185, 186, 188, 282, 2135, 2136, 2137, 2138, + 2139, 2140, 2166, 2167, 2170, 2173, 4135, 16528, 16530, 16531, 16533, 20616, + +}; + +static const unsigned short dep214[] = { + 40, 41, 70, 77, 82, 84, 97, 153, 155, 158, 162, 175, 185, 192, 282, 2138, + 2139, 2140, 2166, 2167, 2170, 2173, 4135, 20616, +}; + +static const unsigned short dep215[] = { + 40, 41, 78, 79, 97, 100, 137, 138, 158, 162, 175, 185, 190, 191, 268, 270, + 282, 283, 285, 2138, 2139, 2140, 2166, 2167, 2170, 2173, 4135, 20616, +}; + +static const unsigned short dep216[] = { + 40, 41, 70, 76, 77, 82, 84, 97, 109, 111, 128, 129, 131, 132, 133, 135, 137, + 138, 139, 140, 142, 143, 153, 155, 158, 162, 171, 173, 175, 185, 190, 191, + 192, 282, 2138, 2139, 2140, 2166, 2167, 2170, 2173, 2327, 4135, 20616, +}; + +static const unsigned short dep217[] = { + 5, 97, 200, 282, 2140, 2327, +}; + +static const unsigned short dep218[] = { + 40, 41, 70, 76, 77, 82, 84, 97, 109, 111, 128, 129, 131, 132, 133, 135, 137, + 138, 139, 140, 142, 143, 153, 155, 158, 162, 171, 173, 175, 185, 190, 191, + 192, 200, 282, 2138, 2139, 2140, 2166, 2167, 2170, 2173, 2327, 4135, 20616, + +}; + +static const unsigned short dep219[] = { + 40, 41, 44, 70, 76, 77, 82, 84, 97, 109, 111, 128, 129, 131, 132, 133, 135, + 137, 138, 139, 140, 142, 143, 153, 155, 156, 158, 162, 171, 173, 175, 185, + 190, 191, 192, 282, 2138, 2139, 2140, 2166, 2167, 2170, 2173, 2327, 4135, + 20616, +}; + +static const unsigned short dep220[] = { + 0, 97, 195, 282, 2140, 2327, +}; + +static const unsigned short dep221[] = { + 0, 40, 41, 70, 76, 77, 82, 84, 97, 109, 111, 128, 129, 131, 132, 133, 135, + 137, 138, 139, 140, 142, 143, 153, 155, 158, 162, 171, 173, 175, 185, 190, + 191, 192, 195, 282, 2138, 2139, 2140, 2166, 2167, 2170, 2173, 2327, 4135, + 20616, +}; + +static const unsigned short dep222[] = { + 0, 40, 41, 44, 70, 76, 77, 82, 84, 97, 109, 111, 128, 129, 131, 132, 133, + 135, 137, 138, 139, 140, 142, 143, 153, 155, 156, 158, 162, 171, 173, 175, + 185, 190, 191, 192, 195, 282, 2138, 2139, 2140, 2166, 2167, 2170, 2173, 2327, + 4135, 20616, +}; + +static const unsigned short dep223[] = { + 31, 40, 41, 70, 76, 77, 82, 84, 97, 109, 111, 128, 129, 131, 132, 133, 135, + 137, 138, 139, 140, 142, 143, 153, 155, 158, 162, 171, 173, 175, 185, 190, + 191, 192, 282, 2138, 2139, 2140, 2166, 2167, 2170, 2173, 2327, 4135, 20616, + +}; + +static const unsigned short dep224[] = { + 0, 97, 195, 282, 2327, 26715, +}; + +static const unsigned short dep225[] = { + 0, 97, 109, 195, 282, 289, +}; + +static const unsigned short dep226[] = { + 0, 40, 41, 70, 76, 77, 82, 84, 97, 111, 128, 129, 131, 132, 133, 135, 137, + 138, 139, 140, 142, 143, 153, 155, 158, 162, 171, 173, 175, 185, 190, 191, + 192, 195, 282, 289, 2138, 2139, 2140, 2166, 2167, 2170, 2173, 4135, 20616, + +}; + +static const unsigned short dep227[] = { + 0, 5, 40, 41, 70, 76, 77, 82, 84, 97, 111, 128, 129, 131, 132, 133, 135, 137, + 138, 139, 140, 142, 143, 153, 155, 158, 162, 171, 173, 175, 185, 190, 191, + 192, 195, 282, 289, 2138, 2139, 2140, 2166, 2167, 2170, 2173, 4135, 20616, + +}; + +static const unsigned short dep228[] = { + 0, 31, 97, 109, 195, 234, 282, 289, +}; + +static const unsigned short dep229[] = { + 0, 40, 41, 70, 76, 77, 82, 84, 97, 111, 128, 129, 131, 132, 133, 135, 137, + 138, 139, 140, 142, 143, 153, 155, 158, 162, 171, 173, 175, 185, 190, 191, + 192, 195, 234, 282, 289, 2138, 2139, 2140, 2166, 2167, 2170, 2173, 4135, 20616, + +}; + +static const unsigned short dep230[] = { + 0, 97, 109, 195, 282, 289, 2140, 2327, +}; + +static const unsigned short dep231[] = { + 0, 3, 40, 41, 70, 76, 77, 82, 84, 97, 109, 111, 128, 129, 131, 132, 133, 135, + 137, 138, 139, 140, 142, 143, 153, 155, 158, 162, 171, 173, 175, 185, 190, + 191, 192, 195, 282, 289, 2138, 2139, 2140, 2166, 2167, 2170, 2173, 2327, 4135, + 20616, +}; + +static const unsigned short dep232[] = { + 0, 3, 5, 40, 41, 70, 76, 77, 82, 84, 97, 109, 111, 128, 129, 131, 132, 133, + 135, 137, 138, 139, 140, 142, 143, 153, 155, 158, 162, 171, 173, 175, 185, + 190, 191, 192, 195, 282, 289, 2138, 2139, 2140, 2166, 2167, 2170, 2173, 2327, + 4135, 20616, +}; + +static const unsigned short dep233[] = { + 0, 40, 41, 70, 76, 77, 82, 84, 97, 109, 111, 128, 129, 131, 132, 133, 135, + 137, 138, 139, 140, 142, 143, 153, 155, 158, 162, 171, 173, 175, 185, 190, + 191, 192, 195, 282, 289, 2138, 2139, 2140, 2166, 2167, 2170, 2173, 2327, 4135, + 20616, +}; + +static const unsigned short dep234[] = { + 40, 41, 97, 158, 162, 175, 185, 282, 2135, 2136, 2137, 2166, 2167, 2170, 2173, + 2327, 4135, 16528, 16530, 16531, 16533, 20616, +}; + +static const unsigned short dep235[] = { + 0, 40, 41, 70, 76, 77, 82, 84, 97, 111, 128, 129, 131, 132, 133, 135, 137, + 138, 139, 140, 142, 143, 153, 155, 158, 162, 171, 173, 175, 185, 190, 191, + 192, 195, 282, 289, 2138, 2139, 2140, 2166, 2167, 2170, 2173, 2327, 4135, + 20616, +}; + +static const unsigned short dep236[] = { + 0, 31, 97, 109, 195, 234, 282, 289, 2140, 2327, +}; + +static const unsigned short dep237[] = { + 0, 40, 41, 70, 76, 77, 82, 84, 97, 111, 128, 129, 131, 132, 133, 135, 137, + 138, 139, 140, 142, 143, 153, 155, 158, 162, 171, 173, 175, 185, 190, 191, + 192, 195, 234, 282, 289, 2138, 2139, 2140, 2166, 2167, 2170, 2173, 2327, 4135, + 20616, +}; + +static const unsigned short dep238[] = { + 40, 41, 70, 76, 77, 82, 84, 97, 109, 111, 128, 129, 131, 132, 133, 135, 137, + 138, 139, 140, 142, 143, 153, 155, 158, 162, 171, 173, 175, 185, 190, 191, + 192, 282, 2138, 2139, 2140, 2166, 2167, 2170, 2173, 2325, 4135, 16528, 16530, + 16531, 16533, 18761, 18763, 18764, 18766, 20616, +}; + +static const unsigned short dep239[] = { + 40, 41, 44, 70, 76, 77, 82, 84, 97, 109, 111, 128, 129, 131, 132, 133, 135, + 137, 138, 139, 140, 142, 143, 153, 155, 156, 158, 162, 171, 173, 175, 185, + 190, 191, 192, 282, 2138, 2139, 2140, 2166, 2167, 2170, 2173, 2325, 4135, + 16528, 16530, 16531, 16533, 18761, 18763, 18764, 18766, 20616, +}; + +static const unsigned short dep240[] = { + 0, 97, 195, 282, 2136, 2325, 18601, 18602, 18761, 18762, 18764, 18765, +}; + +static const unsigned short dep241[] = { + 0, 40, 41, 70, 76, 77, 82, 84, 97, 109, 111, 128, 129, 131, 132, 133, 135, + 137, 138, 139, 140, 142, 143, 153, 155, 158, 162, 171, 173, 175, 185, 190, + 191, 192, 195, 282, 2138, 2139, 2140, 2166, 2167, 2170, 2173, 2325, 4135, + 16528, 16530, 16531, 16533, 18761, 18763, 18764, 18766, 20616, +}; + +static const unsigned short dep242[] = { + 0, 40, 41, 44, 70, 76, 77, 82, 84, 97, 109, 111, 128, 129, 131, 132, 133, + 135, 137, 138, 139, 140, 142, 143, 153, 155, 156, 158, 162, 171, 173, 175, + 185, 190, 191, 192, 195, 282, 2138, 2139, 2140, 2166, 2167, 2170, 2173, 2325, + 4135, 16528, 16530, 16531, 16533, 18761, 18763, 18764, 18766, 20616, +}; + +static const unsigned short dep243[] = { + 0, 97, 195, 282, 2137, 2325, 18601, 18602, 18761, 18762, 18764, 18765, +}; + +static const unsigned short dep244[] = { + 97, 282, 2136, 2140, 2325, 2327, 18601, 18602, 18761, 18762, 18764, 18765, + +}; + +static const unsigned short dep245[] = { + 40, 41, 70, 76, 77, 82, 84, 97, 109, 111, 128, 129, 131, 132, 133, 135, 137, + 138, 139, 140, 142, 143, 153, 155, 158, 162, 171, 173, 175, 185, 190, 191, + 192, 282, 2138, 2139, 2140, 2166, 2167, 2170, 2173, 2325, 2327, 4135, 16528, + 16530, 16531, 16533, 18761, 18763, 18764, 18766, 20616, +}; + +static const unsigned short dep246[] = { + 40, 41, 44, 70, 76, 77, 82, 84, 97, 109, 111, 128, 129, 131, 132, 133, 135, + 137, 138, 139, 140, 142, 143, 153, 155, 156, 158, 162, 171, 173, 175, 185, + 190, 191, 192, 282, 2138, 2139, 2140, 2166, 2167, 2170, 2173, 2325, 2327, + 4135, 16528, 16530, 16531, 16533, 18761, 18763, 18764, 18766, 20616, +}; + +static const unsigned short dep247[] = { + 0, 97, 195, 282, 2136, 2140, 2325, 2327, 18601, 18602, 18761, 18762, 18764, + 18765, +}; + +static const unsigned short dep248[] = { + 0, 40, 41, 70, 76, 77, 82, 84, 97, 109, 111, 128, 129, 131, 132, 133, 135, + 137, 138, 139, 140, 142, 143, 153, 155, 158, 162, 171, 173, 175, 185, 190, + 191, 192, 195, 282, 2138, 2139, 2140, 2166, 2167, 2170, 2173, 2325, 2327, + 4135, 16528, 16530, 16531, 16533, 18761, 18763, 18764, 18766, 20616, +}; + +static const unsigned short dep249[] = { + 0, 40, 41, 44, 70, 76, 77, 82, 84, 97, 109, 111, 128, 129, 131, 132, 133, + 135, 137, 138, 139, 140, 142, 143, 153, 155, 156, 158, 162, 171, 173, 175, + 185, 190, 191, 192, 195, 282, 2138, 2139, 2140, 2166, 2167, 2170, 2173, 2325, + 2327, 4135, 16528, 16530, 16531, 16533, 18761, 18763, 18764, 18766, 20616, + +}; + +static const unsigned short dep250[] = { + 0, 97, 195, 282, 2137, 2140, 2325, 2327, 18601, 18602, 18761, 18762, 18764, + 18765, +}; + +static const unsigned short dep251[] = { + 0, 40, 41, 70, 76, 77, 82, 84, 97, 111, 128, 129, 131, 132, 133, 135, 137, + 138, 139, 140, 142, 143, 153, 155, 158, 162, 171, 173, 175, 185, 190, 191, + 192, 195, 282, 289, 2135, 2136, 2137, 2138, 2139, 2140, 2166, 2167, 2170, + 2173, 4135, 16528, 16530, 16531, 16533, 20616, +}; + +static const unsigned short dep252[] = { + 40, 41, 70, 76, 77, 82, 84, 97, 137, 138, 139, 140, 142, 143, 153, 155, 156, + 158, 162, 171, 173, 175, 185, 192, 282, 2166, 2167, 2170, 2173, 4135, +}; + +static const unsigned short dep253[] = { + 40, 41, 70, 76, 77, 82, 84, 97, 137, 138, 139, 140, 142, 143, 153, 155, 156, + 158, 162, 171, 173, 175, 185, 192, 282, 2138, 2139, 2140, 2166, 2167, 2170, + 2173, 2327, 4135, 20616, +}; + +static const unsigned short dep254[] = { + 40, 41, 97, 158, 162, 175, 185, 282, 2138, 2139, 2140, 2166, 2167, 2170, 2173, + 2325, 4135, 16528, 16530, 16531, 16533, 18761, 18763, 18764, 18766, 20616, + +}; + +static const unsigned short dep255[] = { + 0, 40, 41, 70, 76, 77, 82, 84, 97, 111, 128, 129, 131, 132, 133, 135, 137, + 138, 139, 140, 142, 143, 153, 155, 158, 162, 171, 173, 175, 185, 190, 191, + 192, 195, 282, 289, 2135, 2136, 2137, 2138, 2139, 2140, 2166, 2167, 2170, + 2173, 2327, 4135, 16528, 16530, 16531, 16533, 20616, +}; + +static const unsigned short dep256[] = { + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, + 22, 24, 26, 27, 28, 29, 30, 31, 97, 196, 197, 198, 199, 200, 201, 202, 203, + 204, 205, 206, 207, 208, 209, 211, 212, 214, 215, 217, 218, 220, 221, 222, + 223, 224, 225, 227, 230, 231, 232, 233, 234, 282, 2071, 2081, 2140, 2274, + 2284, 2327, 28866, 29018, +}; + +static const unsigned short dep257[] = { + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, + 22, 24, 25, 26, 28, 29, 30, 31, 40, 41, 97, 137, 138, 158, 162, 175, 180, + 181, 185, 190, 191, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, + 207, 208, 209, 211, 212, 214, 215, 217, 218, 220, 221, 222, 223, 224, 225, + 227, 229, 231, 232, 233, 234, 282, 2071, 2081, 2138, 2139, 2140, 2166, 2167, + 2170, 2173, 2274, 2284, 2327, 4135, 20616, 28866, 29018, +}; + +#define NELS(X) (sizeof(X)/sizeof(X[0])) +static const struct ia64_opcode_dependency +op_dependencies[] = { + { NELS(dep1), dep1, NELS(dep0), dep0, }, + { NELS(dep3), dep3, NELS(dep2), dep2, }, + { NELS(dep5), dep5, NELS(dep4), dep4, }, + { NELS(dep7), dep7, NELS(dep6), dep6, }, + { NELS(dep9), dep9, NELS(dep8), dep8, }, + { NELS(dep11), dep11, NELS(dep10), dep10, }, + { NELS(dep13), dep13, NELS(dep12), dep12, }, + { NELS(dep15), dep15, NELS(dep14), dep14, }, + { NELS(dep17), dep17, NELS(dep16), dep16, }, + { NELS(dep19), dep19, NELS(dep18), dep18, }, + { NELS(dep21), dep21, NELS(dep20), dep20, }, + { NELS(dep23), dep23, NELS(dep22), dep22, }, + { NELS(dep25), dep25, NELS(dep24), dep24, }, + { NELS(dep27), dep27, NELS(dep26), dep26, }, + { NELS(dep29), dep29, NELS(dep28), dep28, }, + { NELS(dep30), dep30, NELS(dep12), dep12, }, + { NELS(dep32), dep32, NELS(dep31), dep31, }, + { NELS(dep34), dep34, NELS(dep33), dep33, }, + { NELS(dep35), dep35, NELS(dep12), dep12, }, + { NELS(dep37), dep37, NELS(dep36), dep36, }, + { NELS(dep39), dep39, NELS(dep38), dep38, }, + { NELS(dep41), dep41, NELS(dep40), dep40, }, + { NELS(dep42), dep42, NELS(dep31), dep31, }, + { NELS(dep43), dep43, NELS(dep33), dep33, }, + { NELS(dep45), dep45, NELS(dep44), dep44, }, + { NELS(dep47), dep47, NELS(dep46), dep46, }, + { NELS(dep49), dep49, NELS(dep48), dep48, }, + { NELS(dep51), dep51, NELS(dep50), dep50, }, + { NELS(dep53), dep53, NELS(dep52), dep52, }, + { NELS(dep55), dep55, NELS(dep54), dep54, }, + { NELS(dep57), dep57, NELS(dep56), dep56, }, + { NELS(dep59), dep59, NELS(dep58), dep58, }, + { NELS(dep61), dep61, NELS(dep60), dep60, }, + { NELS(dep63), dep63, NELS(dep62), dep62, }, + { NELS(dep65), dep65, NELS(dep64), dep64, }, + { NELS(dep67), dep67, NELS(dep66), dep66, }, + { NELS(dep68), dep68, NELS(dep33), dep33, }, + { NELS(dep70), dep70, NELS(dep69), dep69, }, + { NELS(dep72), dep72, NELS(dep71), dep71, }, + { NELS(dep74), dep74, NELS(dep73), dep73, }, + { NELS(dep76), dep76, NELS(dep75), dep75, }, + { NELS(dep77), dep77, NELS(dep33), dep33, }, + { NELS(dep79), dep79, NELS(dep78), dep78, }, + { NELS(dep81), dep81, NELS(dep80), dep80, }, + { NELS(dep83), dep83, NELS(dep82), dep82, }, + { NELS(dep84), dep84, NELS(dep33), dep33, }, + { NELS(dep85), dep85, NELS(dep33), dep33, }, + { NELS(dep86), dep86, NELS(dep33), dep33, }, + { NELS(dep87), dep87, NELS(dep33), dep33, }, + { NELS(dep89), dep89, NELS(dep88), dep88, }, + { NELS(dep91), dep91, NELS(dep90), dep90, }, + { NELS(dep93), dep93, NELS(dep92), dep92, }, + { NELS(dep95), dep95, NELS(dep94), dep94, }, + { NELS(dep97), dep97, NELS(dep96), dep96, }, + { NELS(dep99), dep99, NELS(dep98), dep98, }, + { NELS(dep101), dep101, NELS(dep100), dep100, }, + { NELS(dep103), dep103, NELS(dep102), dep102, }, + { NELS(dep105), dep105, NELS(dep104), dep104, }, + { NELS(dep107), dep107, NELS(dep106), dep106, }, + { NELS(dep109), dep109, NELS(dep108), dep108, }, + { NELS(dep111), dep111, NELS(dep110), dep110, }, + { NELS(dep113), dep113, NELS(dep112), dep112, }, + { NELS(dep115), dep115, NELS(dep114), dep114, }, + { NELS(dep117), dep117, NELS(dep116), dep116, }, + { NELS(dep119), dep119, NELS(dep118), dep118, }, + { NELS(dep121), dep121, NELS(dep120), dep120, }, + { NELS(dep122), dep122, NELS(dep64), dep64, }, + { NELS(dep123), dep123, NELS(dep33), dep33, }, + { NELS(dep125), dep125, NELS(dep124), dep124, }, + { NELS(dep126), dep126, NELS(dep0), dep0, }, + { NELS(dep128), dep128, NELS(dep127), dep127, }, + { NELS(dep130), dep130, NELS(dep129), dep129, }, + { NELS(dep131), dep131, NELS(dep0), dep0, }, + { NELS(dep132), dep132, NELS(dep0), dep0, }, + { NELS(dep134), dep134, NELS(dep133), dep133, }, + { NELS(dep135), dep135, NELS(dep0), dep0, }, + { NELS(dep136), dep136, NELS(dep2), dep2, }, + { NELS(dep137), dep137, NELS(dep4), dep4, }, + { NELS(dep138), dep138, NELS(dep6), dep6, }, + { NELS(dep139), dep139, NELS(dep8), dep8, }, + { NELS(dep140), dep140, NELS(dep10), dep10, }, + { NELS(dep141), dep141, NELS(dep33), dep33, }, + { NELS(dep143), dep143, NELS(dep142), dep142, }, + { NELS(dep144), dep144, NELS(dep142), dep142, }, + { NELS(dep146), dep146, NELS(dep145), dep145, }, + { NELS(dep147), dep147, NELS(dep145), dep145, }, + { NELS(dep148), dep148, NELS(dep142), dep142, }, + { NELS(dep150), dep150, NELS(dep149), dep149, }, + { NELS(dep152), dep152, NELS(dep151), dep151, }, + { NELS(dep154), dep154, NELS(dep153), dep153, }, + { NELS(dep156), dep156, NELS(dep155), dep155, }, + { NELS(dep157), dep157, NELS(dep155), dep155, }, + { NELS(dep158), dep158, NELS(dep0), dep0, }, + { NELS(dep160), dep160, NELS(dep159), dep159, }, + { NELS(dep162), dep162, NELS(dep161), dep161, }, + { NELS(dep164), dep164, NELS(dep163), dep163, }, + { NELS(dep166), dep166, NELS(dep165), dep165, }, + { NELS(dep168), dep168, NELS(dep167), dep167, }, + { NELS(dep169), dep169, NELS(dep0), dep0, }, + { NELS(dep170), dep170, NELS(dep0), dep0, }, + { NELS(dep171), dep171, NELS(dep0), dep0, }, + { NELS(dep172), dep172, NELS(dep33), dep33, }, + { NELS(dep174), dep174, NELS(dep173), dep173, }, + { NELS(dep175), dep175, NELS(dep173), dep173, }, + { NELS(dep177), dep177, NELS(dep176), dep176, }, + { NELS(dep179), dep179, NELS(dep178), dep178, }, + { NELS(dep181), dep181, NELS(dep180), dep180, }, + { NELS(dep183), dep183, NELS(dep182), dep182, }, + { NELS(dep185), dep185, NELS(dep184), dep184, }, + { NELS(dep187), dep187, NELS(dep186), dep186, }, + { NELS(dep189), dep189, NELS(dep188), dep188, }, + { NELS(dep191), dep191, NELS(dep190), dep190, }, + { NELS(dep193), dep193, NELS(dep192), dep192, }, + { NELS(dep194), dep194, NELS(dep0), dep0, }, + { NELS(dep195), dep195, NELS(dep0), dep0, }, + { NELS(dep196), dep196, NELS(dep0), dep0, }, + { NELS(dep197), dep197, NELS(dep0), dep0, }, + { NELS(dep198), dep198, NELS(dep0), dep0, }, + { NELS(dep199), dep199, NELS(dep0), dep0, }, + { NELS(dep200), dep200, NELS(dep0), dep0, }, + { NELS(dep201), dep201, NELS(dep0), dep0, }, + { NELS(dep203), dep203, NELS(dep202), dep202, }, + { NELS(dep205), dep205, NELS(dep204), dep204, }, + { NELS(dep207), dep207, NELS(dep206), dep206, }, + { NELS(dep209), dep209, NELS(dep208), dep208, }, + { NELS(dep210), dep210, NELS(dep0), dep0, }, + { NELS(dep211), dep211, NELS(dep0), dep0, }, + { NELS(dep212), dep212, NELS(dep0), dep0, }, + { NELS(dep213), dep213, NELS(dep33), dep33, }, + { NELS(dep214), dep214, NELS(dep33), dep33, }, + { NELS(dep215), dep215, NELS(dep202), dep202, }, + { NELS(dep216), dep216, NELS(dep0), dep0, }, + { NELS(dep218), dep218, NELS(dep217), dep217, }, + { NELS(dep219), dep219, NELS(dep0), dep0, }, + { NELS(dep221), dep221, NELS(dep220), dep220, }, + { NELS(dep222), dep222, NELS(dep220), dep220, }, + { NELS(dep223), dep223, NELS(dep0), dep0, }, + { NELS(dep221), dep221, NELS(dep224), dep224, }, + { NELS(dep226), dep226, NELS(dep225), dep225, }, + { NELS(dep227), dep227, NELS(dep225), dep225, }, + { NELS(dep229), dep229, NELS(dep228), dep228, }, + { NELS(dep231), dep231, NELS(dep230), dep230, }, + { NELS(dep232), dep232, NELS(dep230), dep230, }, + { NELS(dep233), dep233, NELS(dep230), dep230, }, + { NELS(dep234), dep234, NELS(dep0), dep0, }, + { NELS(dep235), dep235, NELS(dep230), dep230, }, + { NELS(dep237), dep237, NELS(dep236), dep236, }, + { NELS(dep238), dep238, NELS(dep64), dep64, }, + { NELS(dep239), dep239, NELS(dep64), dep64, }, + { NELS(dep241), dep241, NELS(dep240), dep240, }, + { NELS(dep242), dep242, NELS(dep240), dep240, }, + { NELS(dep241), dep241, NELS(dep243), dep243, }, + { NELS(dep245), dep245, NELS(dep244), dep244, }, + { NELS(dep246), dep246, NELS(dep244), dep244, }, + { NELS(dep248), dep248, NELS(dep247), dep247, }, + { NELS(dep249), dep249, NELS(dep247), dep247, }, + { NELS(dep248), dep248, NELS(dep250), dep250, }, + { NELS(dep251), dep251, NELS(dep225), dep225, }, + { NELS(dep252), dep252, NELS(dep33), dep33, }, + { NELS(dep253), dep253, NELS(dep0), dep0, }, + { NELS(dep254), dep254, NELS(dep64), dep64, }, + { NELS(dep255), dep255, NELS(dep230), dep230, }, + { 0, NULL, 0, NULL, }, + { NELS(dep257), dep257, NELS(dep256), dep256, }, +}; + +static const struct ia64_completer_table +completer_table[] = { + { 0x0, 0x0, 0, -1, -1, 0, 1, 0 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 0 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 0 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 0 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 0 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 0 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 0 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 0 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 95 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 95 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 0 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 0 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 0 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 0 }, + { 0x0, 0x0, 0, 594, -1, 0, 1, 6 }, + { 0x0, 0x0, 0, 657, -1, 0, 1, 18 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 162 }, + { 0x0, 0x0, 0, 756, -1, 0, 1, 18 }, + { 0x0, 0x0, 0, 2198, -1, 0, 1, 10 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 9 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 0 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 0 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 13 }, + { 0x1, 0x1, 0, -1, -1, 13, 1, 0 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 34 }, + { 0x0, 0x0, 0, 2406, -1, 0, 1, 30 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 30 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 30 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 34 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 34 }, + { 0x0, 0x0, 0, 1140, -1, 0, 1, 129 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 45 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 41 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 84 }, + { 0x0, 0x0, 0, 2246, -1, 0, 1, 30 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 30 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 30 }, + { 0x0, 0x0, 0, 2473, -1, 0, 1, 30 }, + { 0x0, 0x0, 0, 2250, -1, 0, 1, 30 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 34 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 34 }, + { 0x0, 0x0, 0, 2252, -1, 0, 1, 30 }, + { 0x0, 0x0, 0, 2482, -1, 0, 1, 30 }, + { 0x0, 0x0, 0, 2485, -1, 0, 1, 30 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 34 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 34 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 34 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 30 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 30 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 30 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 30 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 30 }, + { 0x0, 0x0, 0, 2507, -1, 0, 1, 30 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 30 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 34 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 34 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 30 }, + { 0x0, 0x0, 0, 2510, -1, 0, 1, 30 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 25 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 25 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 25 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 25 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 34 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 36 }, + { 0x0, 0x0, 0, 2518, -1, 0, 1, 30 }, + { 0x0, 0x0, 0, 1409, -1, 0, 1, 34 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 41 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 34 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 162 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 83 }, + { 0x0, 0x0, 0, 1457, -1, 0, 1, 131 }, + { 0x0, 0x0, 0, 1466, -1, 0, 1, 131 }, + { 0x0, 0x0, 0, 1475, -1, 0, 1, 131 }, + { 0x0, 0x0, 0, 1477, -1, 0, 1, 132 }, + { 0x0, 0x0, 0, 1479, -1, 0, 1, 132 }, + { 0x0, 0x0, 0, 1488, -1, 0, 1, 131 }, + { 0x0, 0x0, 0, 1497, -1, 0, 1, 131 }, + { 0x0, 0x0, 0, 1506, -1, 0, 1, 131 }, + { 0x0, 0x0, 0, 1515, -1, 0, 1, 131 }, + { 0x0, 0x0, 0, 1524, -1, 0, 1, 131 }, + { 0x0, 0x0, 0, 1533, -1, 0, 1, 131 }, + { 0x0, 0x0, 0, 1543, -1, 0, 1, 131 }, + { 0x0, 0x0, 0, 1553, -1, 0, 1, 131 }, + { 0x0, 0x0, 0, 1563, -1, 0, 1, 131 }, + { 0x0, 0x0, 0, 1572, -1, 0, 1, 147 }, + { 0x0, 0x0, 0, 1578, -1, 0, 1, 152 }, + { 0x0, 0x0, 0, 1584, -1, 0, 1, 152 }, + { 0x0, 0x0, 0, 1590, -1, 0, 1, 147 }, + { 0x0, 0x0, 0, 1596, -1, 0, 1, 152 }, + { 0x0, 0x0, 0, 1602, -1, 0, 1, 152 }, + { 0x0, 0x0, 0, 1608, -1, 0, 1, 147 }, + { 0x0, 0x0, 0, 1614, -1, 0, 1, 152 }, + { 0x0, 0x0, 0, 1620, -1, 0, 1, 152 }, + { 0x0, 0x0, 0, 1626, -1, 0, 1, 147 }, + { 0x0, 0x0, 0, 1632, -1, 0, 1, 152 }, + { 0x0, 0x0, 0, 1638, -1, 0, 1, 147 }, + { 0x0, 0x0, 0, 1644, -1, 0, 1, 152 }, + { 0x0, 0x0, 0, 1650, -1, 0, 1, 147 }, + { 0x0, 0x0, 0, 1656, -1, 0, 1, 152 }, + { 0x0, 0x0, 0, 1662, -1, 0, 1, 147 }, + { 0x0, 0x0, 0, 1668, -1, 0, 1, 152 }, + { 0x0, 0x0, 0, 1674, -1, 0, 1, 152 }, + { 0x0, 0x0, 0, 1678, -1, 0, 1, 158 }, + { 0x0, 0x0, 0, 1682, -1, 0, 1, 159 }, + { 0x0, 0x0, 0, 1686, -1, 0, 1, 159 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 85 }, + { 0x0, 0x0, 0, 258, -1, 0, 1, 41 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 0 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 0 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 34 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 68 }, + { 0x1, 0x1, 0, 1166, -1, 20, 1, 68 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 69 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 70 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 70 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 71 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 72 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 73 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 93 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 94 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 96 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 97 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 98 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 99 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 104 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 105 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 106 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 107 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 108 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 109 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 110 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 113 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 114 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 115 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 116 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 117 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 118 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 119 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 120 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 163 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 163 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 163 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 72 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 0 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 0 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 162 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 0 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 0 }, + { 0x0, 0x0, 0, 2858, -1, 0, 1, 0 }, + { 0x0, 0x0, 0, 2859, -1, 0, 1, 0 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 0 }, + { 0x0, 0x0, 0, 2210, -1, 0, 1, 0 }, + { 0x0, 0x0, 0, 2211, -1, 0, 1, 0 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 0 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 0 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 0 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 0 }, + { 0x0, 0x0, 0, 2873, -1, 0, 1, 0 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 0 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 0 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 0 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 0 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 0 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 0 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 0 }, + { 0x0, 0x0, 0, 2874, -1, 0, 1, 0 }, + { 0x0, 0x0, 0, 2875, -1, 0, 1, 0 }, + { 0x0, 0x0, 0, 2876, -1, 0, 1, 0 }, + { 0x0, 0x0, 0, 2877, -1, 0, 1, 0 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 0 }, + { 0x0, 0x0, 0, 2860, -1, 0, 1, 0 }, + { 0x0, 0x0, 0, 2861, -1, 0, 1, 0 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 0 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 11 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 91 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 89 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 0 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 0 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 0 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 0 }, + { 0x1, 0x1, 0, -1, -1, 13, 1, 0 }, + { 0x0, 0x0, 0, 2879, -1, 0, 1, 0 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 0 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 90 }, + { 0x0, 0x0, 0, 1966, -1, 0, 1, 138 }, + { 0x0, 0x0, 0, 1968, -1, 0, 1, 145 }, + { 0x0, 0x0, 0, 1970, -1, 0, 1, 139 }, + { 0x0, 0x0, 0, 1972, -1, 0, 1, 139 }, + { 0x0, 0x0, 0, 1974, -1, 0, 1, 138 }, + { 0x0, 0x0, 0, 1976, -1, 0, 1, 145 }, + { 0x0, 0x0, 0, 1978, -1, 0, 1, 138 }, + { 0x0, 0x0, 0, 1980, -1, 0, 1, 145 }, + { 0x0, 0x0, 0, 1983, -1, 0, 1, 138 }, + { 0x0, 0x0, 0, 1986, -1, 0, 1, 145 }, + { 0x0, 0x0, 0, 1989, -1, 0, 1, 157 }, + { 0x0, 0x0, 0, 1990, -1, 0, 1, 161 }, + { 0x0, 0x0, 0, 1991, -1, 0, 1, 157 }, + { 0x0, 0x0, 0, 1992, -1, 0, 1, 161 }, + { 0x0, 0x0, 0, 1993, -1, 0, 1, 157 }, + { 0x0, 0x0, 0, 1994, -1, 0, 1, 161 }, + { 0x0, 0x0, 0, 1995, -1, 0, 1, 157 }, + { 0x0, 0x0, 0, 1996, -1, 0, 1, 161 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 0 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 0 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 0 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 88 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 0 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 0 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 0 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 127 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 125 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 127 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 126 }, + { 0x0, 0x0, 0, 1687, -1, 0, 1, 143 }, + { 0x0, 0x0, 0, 1688, -1, 0, 1, 143 }, + { 0x0, 0x0, 0, 1689, -1, 0, 1, 143 }, + { 0x0, 0x0, 0, 1690, -1, 0, 1, 143 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 0 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 0 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 0 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 0 }, + { 0x0, 0x0, 0, -1, -1, 0, 1, 0 }, + { 0x0, 0x0, 1, 224, -1, 0, 1, 12 }, + { 0x0, 0x0, 1, 225, -1, 0, 1, 14 }, + { 0x1, 0x1, 2, -1, -1, 27, 1, 12 }, + { 0x1, 0x1, 2, -1, -1, 27, 1, 14 }, + { 0x0, 0x0, 3, -1, 1340, 0, 0, -1 }, + { 0x0, 0x0, 3, -1, 1341, 0, 0, -1 }, + { 0x1, 0x1, 3, 2749, 1450, 33, 1, 134 }, + { 0x1, 0x1, 3, 2750, 1459, 33, 1, 134 }, + { 0x1, 0x1, 3, 2751, 1468, 33, 1, 134 }, + { 0x1, 0x1, 3, 2752, 1481, 33, 1, 134 }, + { 0x1, 0x1, 3, 2753, 1490, 33, 1, 134 }, + { 0x1, 0x1, 3, 2754, 1499, 33, 1, 134 }, + { 0x1, 0x1, 3, 2755, 1508, 33, 1, 134 }, + { 0x1, 0x1, 3, 2756, 1517, 33, 1, 134 }, + { 0x1, 0x1, 3, 2757, 1526, 33, 1, 134 }, + { 0x1, 0x1, 3, 2758, 1535, 33, 1, 134 }, + { 0x1, 0x1, 3, 2759, 1545, 33, 1, 134 }, + { 0x1, 0x1, 3, 2760, 1555, 33, 1, 134 }, + { 0x1, 0x1, 3, 2761, 1568, 33, 1, 149 }, + { 0x1, 0x1, 3, 2762, 1574, 33, 1, 154 }, + { 0x1, 0x1, 3, 2763, 1580, 33, 1, 154 }, + { 0x1, 0x1, 3, 2764, 1586, 33, 1, 149 }, + { 0x1, 0x1, 3, 2765, 1592, 33, 1, 154 }, + { 0x1, 0x1, 3, 2766, 1598, 33, 1, 154 }, + { 0x1, 0x1, 3, 2767, 1604, 33, 1, 149 }, + { 0x1, 0x1, 3, 2768, 1610, 33, 1, 154 }, + { 0x1, 0x1, 3, 2769, 1616, 33, 1, 154 }, + { 0x1, 0x1, 3, 2770, 1622, 33, 1, 149 }, + { 0x1, 0x1, 3, 2771, 1628, 33, 1, 154 }, + { 0x1, 0x1, 3, 2772, 1634, 33, 1, 149 }, + { 0x1, 0x1, 3, 2773, 1640, 33, 1, 154 }, + { 0x1, 0x1, 3, 2774, 1646, 33, 1, 149 }, + { 0x1, 0x1, 3, 2775, 1652, 33, 1, 154 }, + { 0x1, 0x1, 3, 2776, 1658, 33, 1, 149 }, + { 0x1, 0x1, 3, 2777, 1664, 33, 1, 154 }, + { 0x1, 0x1, 3, 2778, 1670, 33, 1, 154 }, + { 0x1, 0x1, 3, -1, -1, 27, 1, 41 }, + { 0x0, 0x0, 4, 2212, 1425, 0, 1, 142 }, + { 0x0, 0x0, 4, 2213, 1427, 0, 1, 142 }, + { 0x0, 0x0, 4, 2214, 1429, 0, 1, 141 }, + { 0x0, 0x0, 4, 2215, 1431, 0, 1, 141 }, + { 0x0, 0x0, 4, 2216, 1433, 0, 1, 141 }, + { 0x0, 0x0, 4, 2217, 1435, 0, 1, 141 }, + { 0x0, 0x0, 4, 2218, 1437, 0, 1, 141 }, + { 0x0, 0x0, 4, 2219, 1439, 0, 1, 141 }, + { 0x0, 0x0, 4, 2220, 1441, 0, 1, 141 }, + { 0x0, 0x0, 4, 2221, 1443, 0, 1, 141 }, + { 0x0, 0x0, 4, 2222, 1445, 0, 1, 143 }, + { 0x0, 0x0, 4, 2223, 1447, 0, 1, 143 }, + { 0x1, 0x1, 4, -1, 1454, 33, 1, 137 }, + { 0x5, 0x5, 4, 552, 1453, 32, 1, 131 }, + { 0x1, 0x1, 4, -1, 1463, 33, 1, 137 }, + { 0x5, 0x5, 4, 553, 1462, 32, 1, 131 }, + { 0x1, 0x1, 4, -1, 1472, 33, 1, 137 }, + { 0x5, 0x5, 4, 554, 1471, 32, 1, 131 }, + { 0x1, 0x1, 4, -1, 1476, 32, 1, 132 }, + { 0x1, 0x1, 4, -1, 1478, 32, 1, 132 }, + { 0x1, 0x1, 4, -1, 1485, 33, 1, 137 }, + { 0x5, 0x5, 4, 555, 1484, 32, 1, 131 }, + { 0x1, 0x1, 4, -1, 1494, 33, 1, 137 }, + { 0x5, 0x5, 4, 556, 1493, 32, 1, 131 }, + { 0x1, 0x1, 4, -1, 1503, 33, 1, 137 }, + { 0x5, 0x5, 4, 557, 1502, 32, 1, 131 }, + { 0x1, 0x1, 4, -1, 1512, 33, 1, 137 }, + { 0x5, 0x5, 4, 558, 1511, 32, 1, 131 }, + { 0x1, 0x1, 4, -1, 1521, 33, 1, 137 }, + { 0x5, 0x5, 4, 559, 1520, 32, 1, 131 }, + { 0x1, 0x1, 4, -1, 1530, 33, 1, 137 }, + { 0x5, 0x5, 4, 560, 1529, 32, 1, 131 }, + { 0x1, 0x1, 4, -1, 1540, 33, 1, 137 }, + { 0x5, 0x5, 4, 1036, 1538, 32, 1, 131 }, + { 0x1, 0x1, 4, -1, 1550, 33, 1, 137 }, + { 0x5, 0x5, 4, 1037, 1548, 32, 1, 131 }, + { 0x1, 0x1, 4, -1, 1560, 33, 1, 137 }, + { 0x5, 0x5, 4, 1038, 1558, 32, 1, 131 }, + { 0x1, 0x21, 10, 2013, -1, 33, 1, 3 }, + { 0x200001, 0x200001, 10, 2014, -1, 12, 1, 3 }, + { 0x1, 0x21, 10, 420, -1, 33, 1, 3 }, + { 0x200001, 0x200001, 10, 2074, -1, 12, 1, 3 }, + { 0x0, 0x0, 10, -1, 2075, 0, 0, -1 }, + { 0x0, 0x0, 10, -1, 2076, 0, 0, -1 }, + { 0x0, 0x0, 10, 2017, -1, 0, 1, 3 }, + { 0x1, 0x1, 10, 2018, -1, 12, 1, 3 }, + { 0x1, 0x1, 10, 2019, -1, 33, 1, 3 }, + { 0x200001, 0x200001, 10, 2020, -1, 12, 1, 3 }, + { 0x0, 0x0, 10, 430, -1, 0, 1, 3 }, + { 0x1, 0x1, 10, 2080, -1, 12, 1, 3 }, + { 0x1, 0x1, 10, 434, -1, 33, 1, 3 }, + { 0x200001, 0x200001, 10, 2082, -1, 12, 1, 3 }, + { 0x0, 0x0, 10, 438, -1, 0, 1, 3 }, + { 0x1, 0x1, 10, 2084, -1, 12, 1, 3 }, + { 0x1, 0x1, 10, 442, -1, 33, 1, 3 }, + { 0x200001, 0x200001, 10, 2086, -1, 12, 1, 3 }, + { 0x0, 0x0, 10, 446, -1, 0, 1, 3 }, + { 0x1, 0x1, 10, 2088, -1, 12, 1, 3 }, + { 0x1, 0x1, 10, 450, -1, 33, 1, 3 }, + { 0x200001, 0x200001, 10, 2090, -1, 12, 1, 3 }, + { 0x1, 0x21, 10, 2033, -1, 33, 1, 3 }, + { 0x200001, 0x200001, 10, 2034, -1, 12, 1, 3 }, + { 0x1, 0x21, 10, 460, -1, 33, 1, 3 }, + { 0x200001, 0x200001, 10, 2096, -1, 12, 1, 3 }, + { 0x0, 0x0, 10, -1, 2097, 0, 0, -1 }, + { 0x0, 0x0, 10, -1, 2098, 0, 0, -1 }, + { 0x0, 0x0, 10, -1, 2101, 0, 0, -1 }, + { 0x0, 0x0, 10, -1, 2102, 0, 0, -1 }, + { 0x0, 0x0, 10, -1, 2103, 0, 0, -1 }, + { 0x0, 0x0, 10, -1, 2104, 0, 0, -1 }, + { 0x0, 0x0, 10, -1, 2105, 0, 0, -1 }, + { 0x0, 0x0, 10, -1, 2106, 0, 0, -1 }, + { 0x0, 0x0, 10, -1, 2107, 0, 0, -1 }, + { 0x0, 0x0, 10, -1, 2108, 0, 0, -1 }, + { 0x0, 0x0, 10, -1, 2109, 0, 0, -1 }, + { 0x0, 0x0, 10, -1, 2110, 0, 0, -1 }, + { 0x0, 0x0, 10, -1, 2111, 0, 0, -1 }, + { 0x0, 0x0, 10, -1, 2112, 0, 0, -1 }, + { 0x0, 0x0, 10, -1, 2113, 0, 0, -1 }, + { 0x0, 0x0, 10, -1, 2114, 0, 0, -1 }, + { 0x0, 0x0, 10, -1, 2115, 0, 0, -1 }, + { 0x0, 0x0, 10, -1, 2116, 0, 0, -1 }, + { 0x0, 0x0, 10, -1, 2117, 0, 0, -1 }, + { 0x0, 0x0, 10, -1, 2118, 0, 0, -1 }, + { 0x0, 0x0, 10, -1, 2119, 0, 0, -1 }, + { 0x0, 0x0, 10, -1, 2120, 0, 0, -1 }, + { 0x1, 0x21, 10, 2037, -1, 33, 1, 3 }, + { 0x200001, 0x200001, 10, 2038, -1, 12, 1, 3 }, + { 0x1, 0x21, 10, 468, -1, 33, 1, 3 }, + { 0x200001, 0x200001, 10, 2122, -1, 12, 1, 3 }, + { 0x0, 0x0, 10, -1, 2123, 0, 0, -1 }, + { 0x0, 0x0, 10, -1, 2124, 0, 0, -1 }, + { 0x0, 0x0, 10, 2041, -1, 0, 1, 3 }, + { 0x1, 0x1, 10, 2042, -1, 12, 1, 3 }, + { 0x1, 0x1, 10, 2043, -1, 33, 1, 3 }, + { 0x200001, 0x200001, 10, 2044, -1, 12, 1, 3 }, + { 0x0, 0x0, 10, 478, -1, 0, 1, 3 }, + { 0x1, 0x1, 10, 2128, -1, 12, 1, 3 }, + { 0x1, 0x1, 10, 482, -1, 33, 1, 3 }, + { 0x200001, 0x200001, 10, 2130, -1, 12, 1, 3 }, + { 0x0, 0x0, 10, 486, -1, 0, 1, 3 }, + { 0x1, 0x1, 10, 2132, -1, 12, 1, 3 }, + { 0x1, 0x1, 10, 490, -1, 33, 1, 3 }, + { 0x200001, 0x200001, 10, 2134, -1, 12, 1, 3 }, + { 0x0, 0x0, 10, 494, -1, 0, 1, 3 }, + { 0x1, 0x1, 10, 2136, -1, 12, 1, 3 }, + { 0x1, 0x1, 10, 498, -1, 33, 1, 3 }, + { 0x200001, 0x200001, 10, 2138, -1, 12, 1, 3 }, + { 0x1, 0x21, 10, 2057, -1, 33, 1, 3 }, + { 0x200001, 0x200001, 10, 2058, -1, 12, 1, 3 }, + { 0x1, 0x21, 10, 508, -1, 33, 1, 3 }, + { 0x200001, 0x200001, 10, 2144, -1, 12, 1, 3 }, + { 0x0, 0x0, 10, -1, 2145, 0, 0, -1 }, + { 0x0, 0x0, 10, -1, 2146, 0, 0, -1 }, + { 0x0, 0x0, 10, -1, 2149, 0, 0, -1 }, + { 0x0, 0x0, 10, -1, 2150, 0, 0, -1 }, + { 0x0, 0x0, 10, -1, 2151, 0, 0, -1 }, + { 0x0, 0x0, 10, -1, 2152, 0, 0, -1 }, + { 0x0, 0x0, 10, -1, 2153, 0, 0, -1 }, + { 0x0, 0x0, 10, -1, 2154, 0, 0, -1 }, + { 0x0, 0x0, 10, -1, 2155, 0, 0, -1 }, + { 0x0, 0x0, 10, -1, 2156, 0, 0, -1 }, + { 0x0, 0x0, 10, -1, 2157, 0, 0, -1 }, + { 0x0, 0x0, 10, -1, 2158, 0, 0, -1 }, + { 0x0, 0x0, 10, -1, 2159, 0, 0, -1 }, + { 0x0, 0x0, 10, -1, 2160, 0, 0, -1 }, + { 0x0, 0x0, 10, -1, 2161, 0, 0, -1 }, + { 0x0, 0x0, 10, -1, 2162, 0, 0, -1 }, + { 0x0, 0x0, 10, -1, 2163, 0, 0, -1 }, + { 0x0, 0x0, 10, -1, 2164, 0, 0, -1 }, + { 0x0, 0x0, 10, -1, 2165, 0, 0, -1 }, + { 0x0, 0x0, 10, -1, 2166, 0, 0, -1 }, + { 0x0, 0x0, 10, -1, 2167, 0, 0, -1 }, + { 0x0, 0x0, 10, -1, 2168, 0, 0, -1 }, + { 0x1, 0x1, 10, 2061, -1, 36, 1, 3 }, + { 0x1000001, 0x1000001, 10, 2062, -1, 12, 1, 3 }, + { 0x1, 0x1, 10, 2063, -1, 36, 1, 3 }, + { 0x1000001, 0x1000001, 10, 2064, -1, 12, 1, 3 }, + { 0x0, 0x0, 10, -1, 2169, 0, 0, -1 }, + { 0x0, 0x0, 10, -1, 2171, 0, 0, -1 }, + { 0x0, 0x0, 10, -1, 2173, 0, 0, -1 }, + { 0x0, 0x0, 10, -1, 2175, 0, 0, -1 }, + { 0x1, 0x1, 10, 2065, -1, 36, 1, 78 }, + { 0x1000001, 0x1000001, 10, 2066, -1, 12, 1, 78 }, + { 0x1, 0x1, 10, 2067, -1, 36, 1, 78 }, + { 0x1000001, 0x1000001, 10, 2068, -1, 12, 1, 78 }, + { 0x0, 0x0, 10, -1, 2177, 0, 0, -1 }, + { 0x0, 0x0, 10, -1, 2179, 0, 0, -1 }, + { 0x0, 0x0, 10, -1, 2181, 0, 0, -1 }, + { 0x0, 0x0, 10, -1, 2183, 0, 0, -1 }, + { 0x1, 0x1, 10, 2069, -1, 36, 1, 3 }, + { 0x1000001, 0x1000001, 10, 2070, -1, 12, 1, 3 }, + { 0x1, 0x1, 10, 2071, -1, 36, 1, 3 }, + { 0x1000001, 0x1000001, 10, 2072, -1, 12, 1, 3 }, + { 0x0, 0x0, 10, -1, 2185, 0, 0, -1 }, + { 0x0, 0x0, 10, -1, 2187, 0, 0, -1 }, + { 0x0, 0x0, 10, -1, 2189, 0, 0, -1 }, + { 0x0, 0x0, 10, -1, 2191, 0, 0, -1 }, + { 0x2, 0x3, 11, -1, -1, 37, 1, 5 }, + { 0x2, 0x3, 11, -1, -1, 37, 1, 5 }, + { 0x2, 0x3, 11, -1, -1, 37, 1, 5 }, + { 0x200001, 0x4200001, 11, 2015, -1, 12, 1, 3 }, + { 0x2, 0x3, 11, -1, -1, 37, 1, 5 }, + { 0x1, 0x1, 11, 300, -1, 33, 1, 3 }, + { 0x0, 0x0, 11, 2077, -1, 0, 1, 3 }, + { 0x1, 0x1, 11, 2078, -1, 12, 1, 3 }, + { 0x2, 0x3, 11, -1, -1, 37, 1, 5 }, + { 0x2, 0x3, 11, -1, -1, 37, 1, 5 }, + { 0x2, 0x3, 11, -1, -1, 37, 1, 5 }, + { 0x2, 0x3, 11, -1, -1, 37, 1, 5 }, + { 0x2, 0x3, 11, -1, -1, 37, 1, 5 }, + { 0x1, 0x1, 11, 2021, -1, 12, 1, 3 }, + { 0x2, 0x3, 11, -1, -1, 37, 1, 5 }, + { 0x0, 0x0, 11, 308, -1, 0, 1, 3 }, + { 0x2, 0x3, 11, -1, -1, 37, 1, 5 }, + { 0x200001, 0x200001, 11, 2023, -1, 12, 1, 3 }, + { 0x2, 0x3, 11, -1, -1, 37, 1, 5 }, + { 0x1, 0x1, 11, 310, -1, 33, 1, 3 }, + { 0x2, 0x3, 11, -1, -1, 37, 1, 5 }, + { 0x1, 0x1, 11, 2025, -1, 12, 1, 3 }, + { 0x2, 0x3, 11, -1, -1, 37, 1, 5 }, + { 0x0, 0x0, 11, 312, -1, 0, 1, 3 }, + { 0x2, 0x3, 11, -1, -1, 37, 1, 5 }, + { 0x200001, 0x200001, 11, 2027, -1, 12, 1, 3 }, + { 0x2, 0x3, 11, -1, -1, 37, 1, 5 }, + { 0x1, 0x1, 11, 314, -1, 33, 1, 3 }, + { 0x2, 0x3, 11, -1, -1, 37, 1, 5 }, + { 0x1, 0x1, 11, 2029, -1, 12, 1, 3 }, + { 0x2, 0x3, 11, -1, -1, 37, 1, 5 }, + { 0x0, 0x0, 11, 316, -1, 0, 1, 3 }, + { 0x2, 0x3, 11, -1, -1, 37, 1, 5 }, + { 0x200001, 0x200001, 11, 2031, -1, 12, 1, 3 }, + { 0x2, 0x3, 11, -1, -1, 37, 1, 5 }, + { 0x1, 0x1, 11, 318, -1, 33, 1, 3 }, + { 0x0, 0x0, 11, 2091, -1, 0, 1, 3 }, + { 0x1, 0x1, 11, 2092, -1, 12, 1, 3 }, + { 0x1, 0x1, 11, 2093, -1, 33, 1, 3 }, + { 0x200001, 0x200001, 11, 2094, -1, 12, 1, 3 }, + { 0x2, 0x3, 11, -1, -1, 37, 1, 5 }, + { 0x2, 0x3, 11, -1, -1, 37, 1, 5 }, + { 0x2, 0x3, 11, -1, -1, 37, 1, 5 }, + { 0x200001, 0x4200001, 11, 2035, -1, 12, 1, 3 }, + { 0x2, 0x3, 11, -1, -1, 37, 1, 5 }, + { 0x1, 0x1, 11, 322, -1, 33, 1, 3 }, + { 0x0, 0x0, 11, 2099, -1, 0, 1, 3 }, + { 0x1, 0x1, 11, 2100, -1, 12, 1, 3 }, + { 0x2, 0x3, 11, -1, -1, 37, 1, 5 }, + { 0x2, 0x3, 11, -1, -1, 37, 1, 5 }, + { 0x2, 0x3, 11, -1, -1, 37, 1, 5 }, + { 0x200001, 0x4200001, 11, 2039, -1, 12, 1, 3 }, + { 0x2, 0x3, 11, -1, -1, 37, 1, 5 }, + { 0x1, 0x1, 11, 348, -1, 33, 1, 3 }, + { 0x0, 0x0, 11, 2125, -1, 0, 1, 3 }, + { 0x1, 0x1, 11, 2126, -1, 12, 1, 3 }, + { 0x2, 0x3, 11, -1, -1, 37, 1, 5 }, + { 0x2, 0x3, 11, -1, -1, 37, 1, 5 }, + { 0x2, 0x3, 11, -1, -1, 37, 1, 5 }, + { 0x2, 0x3, 11, -1, -1, 37, 1, 5 }, + { 0x2, 0x3, 11, -1, -1, 37, 1, 5 }, + { 0x1, 0x1, 11, 2045, -1, 12, 1, 3 }, + { 0x2, 0x3, 11, -1, -1, 37, 1, 5 }, + { 0x0, 0x0, 11, 356, -1, 0, 1, 3 }, + { 0x2, 0x3, 11, -1, -1, 37, 1, 5 }, + { 0x200001, 0x200001, 11, 2047, -1, 12, 1, 3 }, + { 0x2, 0x3, 11, -1, -1, 37, 1, 5 }, + { 0x1, 0x1, 11, 358, -1, 33, 1, 3 }, + { 0x2, 0x3, 11, -1, -1, 37, 1, 5 }, + { 0x1, 0x1, 11, 2049, -1, 12, 1, 3 }, + { 0x2, 0x3, 11, -1, -1, 37, 1, 5 }, + { 0x0, 0x0, 11, 360, -1, 0, 1, 3 }, + { 0x2, 0x3, 11, -1, -1, 37, 1, 5 }, + { 0x200001, 0x200001, 11, 2051, -1, 12, 1, 3 }, + { 0x2, 0x3, 11, -1, -1, 37, 1, 5 }, + { 0x1, 0x1, 11, 362, -1, 33, 1, 3 }, + { 0x2, 0x3, 11, -1, -1, 37, 1, 5 }, + { 0x1, 0x1, 11, 2053, -1, 12, 1, 3 }, + { 0x2, 0x3, 11, -1, -1, 37, 1, 5 }, + { 0x0, 0x0, 11, 364, -1, 0, 1, 3 }, + { 0x2, 0x3, 11, -1, -1, 37, 1, 5 }, + { 0x200001, 0x200001, 11, 2055, -1, 12, 1, 3 }, + { 0x2, 0x3, 11, -1, -1, 37, 1, 5 }, + { 0x1, 0x1, 11, 366, -1, 33, 1, 3 }, + { 0x0, 0x0, 11, 2139, -1, 0, 1, 3 }, + { 0x1, 0x1, 11, 2140, -1, 12, 1, 3 }, + { 0x1, 0x1, 11, 2141, -1, 33, 1, 3 }, + { 0x200001, 0x200001, 11, 2142, -1, 12, 1, 3 }, + { 0x2, 0x3, 11, -1, -1, 37, 1, 5 }, + { 0x2, 0x3, 11, -1, -1, 37, 1, 5 }, + { 0x2, 0x3, 11, -1, -1, 37, 1, 5 }, + { 0x200001, 0x4200001, 11, 2059, -1, 12, 1, 3 }, + { 0x2, 0x3, 11, -1, -1, 37, 1, 5 }, + { 0x1, 0x1, 11, 370, -1, 33, 1, 3 }, + { 0x0, 0x0, 11, 2147, -1, 0, 1, 3 }, + { 0x1, 0x1, 11, 2148, -1, 12, 1, 3 }, + { 0x1, 0x1, 11, -1, -1, 36, 1, 5 }, + { 0x1, 0x1, 11, -1, -1, 36, 1, 5 }, + { 0x1, 0x1, 11, -1, -1, 36, 1, 5 }, + { 0x1, 0x1, 11, -1, -1, 36, 1, 5 }, + { 0x1, 0x1, 11, 2170, -1, 36, 1, 3 }, + { 0x1000001, 0x1000001, 11, 2172, -1, 12, 1, 3 }, + { 0x1, 0x1, 11, 2174, -1, 36, 1, 3 }, + { 0x1000001, 0x1000001, 11, 2176, -1, 12, 1, 3 }, + { 0x1, 0x1, 11, -1, -1, 36, 1, 80 }, + { 0x1, 0x1, 11, -1, -1, 36, 1, 80 }, + { 0x1, 0x1, 11, -1, -1, 36, 1, 80 }, + { 0x1, 0x1, 11, -1, -1, 36, 1, 80 }, + { 0x1, 0x1, 11, 2178, -1, 36, 1, 78 }, + { 0x1000001, 0x1000001, 11, 2180, -1, 12, 1, 78 }, + { 0x1, 0x1, 11, 2182, -1, 36, 1, 78 }, + { 0x1000001, 0x1000001, 11, 2184, -1, 12, 1, 78 }, + { 0x1, 0x1, 11, -1, -1, 36, 1, 5 }, + { 0x1, 0x1, 11, -1, -1, 36, 1, 5 }, + { 0x1, 0x1, 11, -1, -1, 36, 1, 5 }, + { 0x1, 0x1, 11, -1, -1, 36, 1, 5 }, + { 0x1, 0x1, 11, 2186, -1, 36, 1, 3 }, + { 0x1000001, 0x1000001, 11, 2188, -1, 12, 1, 3 }, + { 0x1, 0x1, 11, 2190, -1, 36, 1, 3 }, + { 0x1000001, 0x1000001, 11, 2192, -1, 12, 1, 3 }, + { 0x0, 0x0, 12, -1, -1, 0, 1, 15 }, + { 0x0, 0x0, 12, -1, -1, 0, 1, 15 }, + { 0x0, 0x0, 12, -1, -1, 0, 1, 15 }, + { 0x1, 0x1, 13, 272, 1452, 34, 1, 131 }, + { 0x1, 0x1, 13, 274, 1461, 34, 1, 131 }, + { 0x1, 0x1, 13, 276, 1470, 34, 1, 131 }, + { 0x1, 0x1, 13, 280, 1483, 34, 1, 131 }, + { 0x1, 0x1, 13, 282, 1492, 34, 1, 131 }, + { 0x1, 0x1, 13, 284, 1501, 34, 1, 131 }, + { 0x1, 0x1, 13, 286, 1510, 34, 1, 131 }, + { 0x1, 0x1, 13, 288, 1519, 34, 1, 131 }, + { 0x1, 0x1, 13, 290, 1528, 34, 1, 131 }, + { 0x1, 0x1, 13, 292, 1537, 34, 1, 131 }, + { 0x1, 0x1, 13, 294, 1547, 34, 1, 131 }, + { 0x1, 0x1, 13, 296, 1557, 34, 1, 131 }, + { 0x0, 0x0, 19, -1, 795, 0, 0, -1 }, + { 0x0, 0x0, 19, -1, 796, 0, 0, -1 }, + { 0x0, 0x0, 19, -1, 797, 0, 0, -1 }, + { 0x0, 0x0, 19, -1, 798, 0, 0, -1 }, + { 0x0, 0x0, 19, -1, 799, 0, 0, -1 }, + { 0x0, 0x0, 19, -1, 800, 0, 0, -1 }, + { 0x0, 0x0, 19, -1, 801, 0, 0, -1 }, + { 0x0, 0x0, 19, -1, 802, 0, 0, -1 }, + { 0x0, 0x0, 19, -1, 803, 0, 0, -1 }, + { 0x0, 0x0, 19, -1, 804, 0, 0, -1 }, + { 0x0, 0x0, 19, -1, 805, 0, 0, -1 }, + { 0x0, 0x0, 19, -1, 806, 0, 0, -1 }, + { 0x0, 0x0, 19, -1, 807, 0, 0, -1 }, + { 0x0, 0x0, 19, -1, 808, 0, 0, -1 }, + { 0x0, 0x0, 19, -1, 809, 0, 0, -1 }, + { 0x0, 0x0, 19, -1, 810, 0, 0, -1 }, + { 0x0, 0x0, 19, -1, 811, 0, 0, -1 }, + { 0x0, 0x0, 19, -1, 812, 0, 0, -1 }, + { 0x0, 0x0, 19, -1, 813, 0, 0, -1 }, + { 0x0, 0x0, 19, -1, 814, 0, 0, -1 }, + { 0x0, 0x0, 19, -1, 815, 0, 0, -1 }, + { 0x0, 0x0, 19, -1, 816, 0, 0, -1 }, + { 0x0, 0x0, 19, -1, 817, 0, 0, -1 }, + { 0x0, 0x0, 19, -1, 818, 0, 0, -1 }, + { 0x0, 0x0, 19, -1, 819, 0, 0, -1 }, + { 0x0, 0x0, 19, -1, 820, 0, 0, -1 }, + { 0x0, 0x0, 19, -1, 821, 0, 0, -1 }, + { 0x0, 0x0, 19, -1, 822, 0, 0, -1 }, + { 0x0, 0x0, 19, -1, 823, 0, 0, -1 }, + { 0x0, 0x0, 19, -1, 824, 0, 0, -1 }, + { 0x0, 0x0, 20, -1, 2827, 0, 0, -1 }, + { 0x0, 0x0, 20, -1, 2828, 0, 0, -1 }, + { 0x0, 0x0, 20, -1, 2843, 0, 0, -1 }, + { 0x0, 0x0, 20, -1, 2844, 0, 0, -1 }, + { 0x0, 0x0, 20, -1, 2849, 0, 0, -1 }, + { 0x0, 0x0, 20, -1, 2850, 0, 0, -1 }, + { 0x0, 0x0, 21, 831, 2839, 0, 0, -1 }, + { 0x0, 0x0, 21, 832, 2841, 0, 0, -1 }, + { 0x0, 0x0, 23, -1, 2837, 0, 0, -1 }, + { 0x0, 0x0, 23, -1, 2838, 0, 0, -1 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 6 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 6 }, + { 0x1, 0x1, 24, 1272, -1, 35, 1, 6 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 6 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 6 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 6 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 6 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 6 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 6 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 6 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 6 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 6 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 6 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 6 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 6 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 6 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 6 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 6 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 6 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 7 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 7 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 7 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 7 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 7 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 7 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 7 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 7 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 6 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 6 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 6 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 6 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 6 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 6 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 6 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 6 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 7 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 7 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 7 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 7 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 8 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 8 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 8 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 8 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 8 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 8 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 8 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 8 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 8 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 8 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 8 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 8 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 16 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 16 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 16 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 16 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 16 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 16 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 16 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 16 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 16 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 16 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 16 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 16 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 18 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 18 }, + { 0x1, 0x1, 24, 1293, -1, 35, 1, 18 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 18 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 18 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 18 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 18 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 18 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 18 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 18 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 18 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 18 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 18 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 18 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 18 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 18 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 18 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 18 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 18 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 18 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 18 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 18 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 18 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 18 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 18 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 18 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 18 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 19 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 19 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 19 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 19 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 19 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 19 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 19 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 19 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 19 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 19 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 19 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 19 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 19 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 19 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 19 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 19 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 19 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 19 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 19 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 19 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 19 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 19 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 19 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 19 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 20 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 20 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 20 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 20 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 20 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 20 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 20 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 20 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 20 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 20 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 20 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 20 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 21 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 21 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 21 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 21 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 21 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 21 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 21 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 21 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 21 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 21 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 21 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 21 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 21 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 21 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 21 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 21 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 21 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 21 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 21 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 21 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 21 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 21 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 21 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 21 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 22 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 22 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 22 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 22 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 22 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 22 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 22 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 22 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 22 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 22 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 22 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 22 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 18 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 18 }, + { 0x1, 0x1, 24, 1326, -1, 35, 1, 18 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 18 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 18 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 18 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 18 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 18 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 18 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 18 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 18 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 18 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 18 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 18 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 18 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 18 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 18 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 18 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 18 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 18 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 18 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 18 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 18 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 18 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 18 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 18 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 18 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 22 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 22 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 22 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 22 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 22 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 22 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 22 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 22 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 22 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 22 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 22 }, + { 0x1, 0x1, 24, -1, -1, 35, 1, 22 }, + { 0x1, 0x1, 24, -1, -1, 33, 1, 82 }, + { 0x1, 0x1, 24, -1, -1, 33, 1, 82 }, + { 0x1, 0x1, 24, 1342, 1455, 35, 1, 137 }, + { 0x1, 0x1, 24, 1343, 1464, 35, 1, 137 }, + { 0x1, 0x1, 24, 1344, 1473, 35, 1, 137 }, + { 0x1, 0x1, 24, 1345, 1486, 35, 1, 137 }, + { 0x1, 0x1, 24, 1346, 1495, 35, 1, 137 }, + { 0x1, 0x1, 24, 1347, 1504, 35, 1, 137 }, + { 0x1, 0x1, 24, 1348, 1513, 35, 1, 137 }, + { 0x1, 0x1, 24, 1349, 1522, 35, 1, 137 }, + { 0x1, 0x1, 24, 1350, 1531, 35, 1, 137 }, + { 0x1, 0x1, 24, 1351, 1541, 35, 1, 137 }, + { 0x1, 0x1, 24, 1352, 1551, 35, 1, 137 }, + { 0x1, 0x1, 24, 1353, 1561, 35, 1, 137 }, + { 0x1, 0x1, 24, 1354, 1570, 35, 1, 151 }, + { 0x1, 0x1, 24, 1355, 1576, 35, 1, 156 }, + { 0x1, 0x1, 24, 1356, 1582, 35, 1, 156 }, + { 0x1, 0x1, 24, 1357, 1588, 35, 1, 151 }, + { 0x1, 0x1, 24, 1358, 1594, 35, 1, 156 }, + { 0x1, 0x1, 24, 1359, 1600, 35, 1, 156 }, + { 0x1, 0x1, 24, 1360, 1606, 35, 1, 151 }, + { 0x1, 0x1, 24, 1361, 1612, 35, 1, 156 }, + { 0x1, 0x1, 24, 1362, 1618, 35, 1, 156 }, + { 0x1, 0x1, 24, 1363, 1624, 35, 1, 151 }, + { 0x1, 0x1, 24, 1364, 1630, 35, 1, 156 }, + { 0x1, 0x1, 24, 1365, 1636, 35, 1, 151 }, + { 0x1, 0x1, 24, 1366, 1642, 35, 1, 156 }, + { 0x1, 0x1, 24, 1367, 1648, 35, 1, 151 }, + { 0x1, 0x1, 24, 1368, 1654, 35, 1, 156 }, + { 0x1, 0x1, 24, 1369, 1660, 35, 1, 151 }, + { 0x1, 0x1, 24, 1370, 1666, 35, 1, 156 }, + { 0x1, 0x1, 24, 1371, 1672, 35, 1, 156 }, + { 0x0, 0x0, 33, 2821, 2819, 0, 0, -1 }, + { 0x0, 0x0, 33, 2824, 2822, 0, 0, -1 }, + { 0x0, 0x0, 33, 2830, 2829, 0, 0, -1 }, + { 0x0, 0x0, 33, 2832, 2831, 0, 0, -1 }, + { 0x0, 0x0, 33, 2846, 2845, 0, 0, -1 }, + { 0x0, 0x0, 33, 2848, 2847, 0, 0, -1 }, + { 0x0, 0x0, 35, -1, 2840, 0, 0, -1 }, + { 0x0, 0x0, 35, -1, 2842, 0, 0, -1 }, + { 0x1, 0x1, 38, -1, 2290, 37, 1, 30 }, + { 0x1, 0x1, 38, -1, 2349, 37, 1, 30 }, + { 0x0, 0x0, 38, -1, 2352, 0, 0, -1 }, + { 0x1, 0x1, 38, -1, -1, 37, 1, 30 }, + { 0x1, 0x1, 38, -1, 2357, 37, 1, 30 }, + { 0x0, 0x0, 38, -1, 2360, 0, 0, -1 }, + { 0x1, 0x1, 38, -1, -1, 37, 1, 30 }, + { 0x0, 0x0, 38, -1, 2363, 0, 0, -1 }, + { 0x1, 0x1, 38, -1, -1, 37, 1, 30 }, + { 0x1, 0x1, 38, -1, 2366, 37, 1, 30 }, + { 0x1, 0x1, 38, -1, 2369, 37, 1, 30 }, + { 0x1, 0x1, 38, -1, 2402, 37, 1, 30 }, + { 0x3, 0x3, 38, -1, -1, 30, 1, 144 }, + { 0x0, 0x0, 38, 1142, -1, 0, 1, 102 }, + { 0x0, 0x0, 38, -1, -1, 0, 1, 111 }, + { 0x0, 0x0, 38, 1148, -1, 0, 1, 123 }, + { 0x3, 0x3, 38, -1, -1, 30, 1, 160 }, + { 0x0, 0x0, 38, 1149, -1, 0, 1, 41 }, + { 0x0, 0x0, 40, -1, 973, 0, 0, -1 }, + { 0x0, 0x0, 40, -1, 981, 0, 0, -1 }, + { 0x0, 0x0, 40, 1151, 977, 0, 0, -1 }, + { 0x3, 0x3, 40, -1, 622, 33, 1, 6 }, + { 0x18000001, 0x18000001, 40, -1, 630, 6, 1, 7 }, + { 0x3, 0x3, 40, 1152, 626, 33, 1, 6 }, + { 0x0, 0x0, 40, -1, 985, 0, 0, -1 }, + { 0x3, 0x3, 40, -1, 642, 33, 1, 8 }, + { 0x0, 0x0, 40, -1, 989, 0, 0, -1 }, + { 0x3, 0x3, 40, -1, 654, 33, 1, 16 }, + { 0x0, 0x0, 40, -1, 994, 0, 0, -1 }, + { 0x0, 0x0, 40, -1, 998, 0, 0, -1 }, + { 0x3, 0x3, 40, -1, 677, 33, 1, 18 }, + { 0x3, 0x3, 40, -1, 681, 33, 1, 18 }, + { 0x0, 0x0, 40, -1, 1002, 0, 0, -1 }, + { 0x0, 0x0, 40, -1, 1006, 0, 0, -1 }, + { 0x3, 0x3, 40, -1, 701, 33, 1, 19 }, + { 0x18000001, 0x18000001, 40, -1, 705, 6, 1, 19 }, + { 0x0, 0x0, 40, -1, 1010, 0, 0, -1 }, + { 0x3, 0x3, 40, -1, 717, 33, 1, 20 }, + { 0x0, 0x0, 40, -1, 1014, 0, 0, -1 }, + { 0x0, 0x0, 40, -1, 1018, 0, 0, -1 }, + { 0x3, 0x3, 40, -1, 737, 33, 1, 21 }, + { 0x18000001, 0x18000001, 40, -1, 741, 6, 1, 21 }, + { 0x0, 0x0, 40, -1, 1022, 0, 0, -1 }, + { 0x3, 0x3, 40, -1, 753, 33, 1, 22 }, + { 0x0, 0x0, 40, -1, 1027, 0, 0, -1 }, + { 0x0, 0x0, 40, -1, 1031, 0, 0, -1 }, + { 0x3, 0x3, 40, -1, 776, 33, 1, 18 }, + { 0x3, 0x3, 40, -1, 780, 33, 1, 18 }, + { 0x0, 0x0, 40, -1, 1035, 0, 0, -1 }, + { 0x3, 0x3, 40, -1, 792, 33, 1, 22 }, + { 0x0, 0x0, 41, 851, 972, 0, 0, -1 }, + { 0x0, 0x0, 41, 852, 980, 0, 0, -1 }, + { 0x0, 0x0, 41, 853, 976, 0, 0, -1 }, + { 0x1, 0x1, 41, 854, 621, 34, 1, 6 }, + { 0x10000001, 0x10000001, 41, 855, 629, 6, 1, 7 }, + { 0x1, 0x1, 41, 856, 625, 34, 1, 6 }, + { 0x0, 0x0, 41, 857, 984, 0, 0, -1 }, + { 0x1, 0x1, 41, 858, 641, 34, 1, 8 }, + { 0x0, 0x0, 41, 859, 988, 0, 0, -1 }, + { 0x1, 0x1, 41, 860, 653, 34, 1, 16 }, + { 0x0, 0x0, 41, 861, 993, 0, 0, -1 }, + { 0x0, 0x0, 41, 862, 997, 0, 0, -1 }, + { 0x1, 0x1, 41, 863, 676, 34, 1, 18 }, + { 0x1, 0x1, 41, 864, 680, 34, 1, 18 }, + { 0x0, 0x0, 41, 865, 1001, 0, 0, -1 }, + { 0x0, 0x0, 41, 866, 1005, 0, 0, -1 }, + { 0x1, 0x1, 41, 867, 700, 34, 1, 19 }, + { 0x10000001, 0x10000001, 41, 868, 704, 6, 1, 19 }, + { 0x0, 0x0, 41, 869, 1009, 0, 0, -1 }, + { 0x1, 0x1, 41, 870, 716, 34, 1, 20 }, + { 0x0, 0x0, 41, 871, 1013, 0, 0, -1 }, + { 0x0, 0x0, 41, 872, 1017, 0, 0, -1 }, + { 0x1, 0x1, 41, 873, 736, 34, 1, 21 }, + { 0x10000001, 0x10000001, 41, 874, 740, 6, 1, 21 }, + { 0x0, 0x0, 41, 875, 1021, 0, 0, -1 }, + { 0x1, 0x1, 41, 876, 752, 34, 1, 22 }, + { 0x0, 0x0, 41, 877, 1026, 0, 0, -1 }, + { 0x0, 0x0, 41, 878, 1030, 0, 0, -1 }, + { 0x1, 0x1, 41, 879, 775, 34, 1, 18 }, + { 0x1, 0x1, 41, 880, 779, 34, 1, 18 }, + { 0x0, 0x0, 41, 881, 1034, 0, 0, -1 }, + { 0x1, 0x1, 41, 882, 791, 34, 1, 22 }, + { 0x800001, 0x800001, 41, -1, 1156, 4, 1, 17 }, + { 0x1, 0x1, 41, 2236, 1154, 4, 1, 17 }, + { 0x1, 0x1, 41, 957, 1159, 4, 1, 23 }, + { 0x2, 0x3, 41, -1, 1164, 20, 1, 68 }, + { 0x1, 0x1, 41, 2237, 1162, 21, 1, 68 }, + { 0x0, 0x0, 42, -1, -1, 0, 1, 86 }, + { 0x0, 0x0, 42, -1, -1, 0, 1, 86 }, + { 0x0, 0x0, 42, -1, -1, 0, 1, 130 }, + { 0x1, 0x1, 44, 1372, 297, 38, 1, 1 }, + { 0x1, 0x1, 44, 1373, 299, 38, 1, 1 }, + { 0x0, 0x0, 44, -1, 302, 0, 0, -1 }, + { 0x0, 0x0, 44, -1, 424, 0, 0, -1 }, + { 0x1, 0x1, 44, 1377, 319, 38, 1, 1 }, + { 0x1, 0x1, 44, 1378, 321, 38, 1, 1 }, + { 0x0, 0x0, 44, -1, 324, 0, 0, -1 }, + { 0x0, 0x0, 44, -1, 464, 0, 0, -1 }, + { 0x0, 0x0, 44, -1, 326, 0, 0, -1 }, + { 0x0, 0x0, 44, -1, 344, 0, 0, -1 }, + { 0x1, 0x1, 44, 1384, 345, 38, 1, 1 }, + { 0x1, 0x1, 44, 1385, 347, 38, 1, 1 }, + { 0x0, 0x0, 44, -1, 350, 0, 0, -1 }, + { 0x0, 0x0, 44, -1, 472, 0, 0, -1 }, + { 0x1, 0x1, 44, 1389, 367, 38, 1, 1 }, + { 0x1, 0x1, 44, 1390, 369, 38, 1, 1 }, + { 0x0, 0x0, 44, -1, 372, 0, 0, -1 }, + { 0x0, 0x0, 44, -1, 512, 0, 0, -1 }, + { 0x0, 0x0, 44, -1, 374, 0, 0, -1 }, + { 0x0, 0x0, 44, -1, 392, 0, 0, -1 }, + { 0x0, 0x0, 44, 1248, 2297, 0, 0, -1 }, + { 0x0, 0x0, 44, 1249, 2305, 0, 1, 55 }, + { 0x0, 0x0, 44, 1250, 2972, 0, 1, 55 }, + { 0x0, 0x0, 44, 1251, 2373, 0, 0, -1 }, + { 0x0, 0x0, 44, 1252, -1, 0, 1, 50 }, + { 0x0, 0x0, 44, 1120, -1, 0, 1, 0 }, + { 0x0, 0x0, 44, 1121, -1, 0, 1, 0 }, + { 0x0, 0x0, 44, 1122, -1, 0, 1, 0 }, + { 0x1, 0x1, 45, -1, 1676, 30, 1, 158 }, + { 0x1, 0x1, 45, 963, 1675, 30, 1, 158 }, + { 0x1, 0x1, 45, -1, 1680, 30, 1, 159 }, + { 0x1, 0x1, 45, 964, 1679, 30, 1, 159 }, + { 0x1, 0x1, 45, -1, 1684, 30, 1, 159 }, + { 0x1, 0x1, 45, 965, 1683, 30, 1, 159 }, + { 0x3, 0x3, 46, -1, 1160, 3, 1, 23 }, + { 0x1, 0x1, 47, 2257, -1, 30, 1, 144 }, + { 0x1, 0x1, 47, 2288, -1, 30, 1, 160 }, + { 0x0, 0x0, 49, -1, -1, 0, 1, 41 }, + { 0x0, 0x0, 49, -1, -1, 0, 1, 41 }, + { 0x0, 0x0, 49, -1, -1, 0, 1, 41 }, + { 0x1, 0x1, 56, -1, 1677, 31, 1, 158 }, + { 0x1, 0x1, 56, -1, 1681, 31, 1, 159 }, + { 0x1, 0x1, 56, -1, 1685, 31, 1, 159 }, + { 0x0, 0x0, 56, -1, -1, 0, 1, 101 }, + { 0x2, 0x3, 56, -1, -1, 27, 1, 101 }, + { 0x1, 0x1, 56, -1, -1, 28, 1, 101 }, + { 0x0, 0x0, 65, 14, 592, 0, 1, 6 }, + { 0x0, 0x0, 65, 1273, 595, 0, 1, 6 }, + { 0x1, 0x1, 65, 1274, 597, 33, 1, 6 }, + { 0x1, 0x1, 65, 1275, 599, 34, 1, 6 }, + { 0x3, 0x3, 65, 1276, 601, 33, 1, 6 }, + { 0x0, 0x0, 65, 1277, 603, 0, 1, 6 }, + { 0x1, 0x1, 65, 1278, 605, 33, 1, 6 }, + { 0x1, 0x1, 65, 1279, 607, 34, 1, 6 }, + { 0x3, 0x3, 65, 1280, 609, 33, 1, 6 }, + { 0x1, 0x1, 65, 1281, 611, 6, 1, 7 }, + { 0x8000001, 0x8000001, 65, 1282, 613, 6, 1, 7 }, + { 0x10000001, 0x10000001, 65, 1283, 615, 6, 1, 7 }, + { 0x18000001, 0x18000001, 65, 1284, 617, 6, 1, 7 }, + { 0x0, 0x0, 65, 1285, 631, 0, 1, 8 }, + { 0x1, 0x1, 65, 1286, 633, 33, 1, 8 }, + { 0x1, 0x1, 65, 1287, 635, 34, 1, 8 }, + { 0x3, 0x3, 65, 1288, 637, 33, 1, 8 }, + { 0x0, 0x0, 65, 1289, 643, 0, 1, 16 }, + { 0x1, 0x1, 65, 1290, 645, 33, 1, 16 }, + { 0x1, 0x1, 65, 1291, 647, 34, 1, 16 }, + { 0x3, 0x3, 65, 1292, 649, 33, 1, 16 }, + { 0x0, 0x0, 65, 15, 655, 0, 1, 18 }, + { 0x0, 0x0, 65, 1294, 658, 0, 1, 18 }, + { 0x1, 0x1, 65, 1295, 660, 33, 1, 18 }, + { 0x1, 0x1, 65, 1296, 662, 34, 1, 18 }, + { 0x3, 0x3, 65, 1297, 664, 33, 1, 18 }, + { 0x0, 0x0, 65, 1298, 666, 0, 1, 18 }, + { 0x1, 0x1, 65, 1299, 668, 33, 1, 18 }, + { 0x1, 0x1, 65, 1300, 670, 34, 1, 18 }, + { 0x3, 0x3, 65, 1301, 672, 33, 1, 18 }, + { 0x0, 0x0, 65, 1302, 682, 0, 1, 19 }, + { 0x1, 0x1, 65, 1303, 684, 33, 1, 19 }, + { 0x1, 0x1, 65, 1304, 686, 34, 1, 19 }, + { 0x3, 0x3, 65, 1305, 688, 33, 1, 19 }, + { 0x1, 0x1, 65, 1306, 690, 6, 1, 19 }, + { 0x8000001, 0x8000001, 65, 1307, 692, 6, 1, 19 }, + { 0x10000001, 0x10000001, 65, 1308, 694, 6, 1, 19 }, + { 0x18000001, 0x18000001, 65, 1309, 696, 6, 1, 19 }, + { 0x0, 0x0, 65, 1310, 706, 0, 1, 20 }, + { 0x1, 0x1, 65, 1311, 708, 33, 1, 20 }, + { 0x1, 0x1, 65, 1312, 710, 34, 1, 20 }, + { 0x3, 0x3, 65, 1313, 712, 33, 1, 20 }, + { 0x0, 0x0, 65, 1314, 718, 0, 1, 21 }, + { 0x1, 0x1, 65, 1315, 720, 33, 1, 21 }, + { 0x1, 0x1, 65, 1316, 722, 34, 1, 21 }, + { 0x3, 0x3, 65, 1317, 724, 33, 1, 21 }, + { 0x1, 0x1, 65, 1318, 726, 6, 1, 21 }, + { 0x8000001, 0x8000001, 65, 1319, 728, 6, 1, 21 }, + { 0x10000001, 0x10000001, 65, 1320, 730, 6, 1, 21 }, + { 0x18000001, 0x18000001, 65, 1321, 732, 6, 1, 21 }, + { 0x0, 0x0, 65, 1322, 742, 0, 1, 22 }, + { 0x1, 0x1, 65, 1323, 744, 33, 1, 22 }, + { 0x1, 0x1, 65, 1324, 746, 34, 1, 22 }, + { 0x3, 0x3, 65, 1325, 748, 33, 1, 22 }, + { 0x0, 0x0, 65, 17, 754, 0, 1, 18 }, + { 0x0, 0x0, 65, 1327, 757, 0, 1, 18 }, + { 0x1, 0x1, 65, 1328, 759, 33, 1, 18 }, + { 0x1, 0x1, 65, 1329, 761, 34, 1, 18 }, + { 0x3, 0x3, 65, 1330, 763, 33, 1, 18 }, + { 0x0, 0x0, 65, 1331, 765, 0, 1, 18 }, + { 0x1, 0x1, 65, 1332, 767, 33, 1, 18 }, + { 0x1, 0x1, 65, 1333, 769, 34, 1, 18 }, + { 0x3, 0x3, 65, 1334, 771, 33, 1, 18 }, + { 0x0, 0x0, 65, 1335, 781, 0, 1, 22 }, + { 0x1, 0x1, 65, 1336, 783, 33, 1, 22 }, + { 0x1, 0x1, 65, 1337, 785, 34, 1, 22 }, + { 0x3, 0x3, 65, 1338, 787, 33, 1, 22 }, + { 0x3, 0x3, 66, 561, 1539, 33, 1, 136 }, + { 0x3, 0x3, 66, 562, 1549, 33, 1, 136 }, + { 0x3, 0x3, 66, 563, 1559, 33, 1, 136 }, + { 0x0, 0x0, 66, -1, 1564, 0, 1, 147 }, + { 0x0, 0x0, 66, -1, 1565, 0, 1, 152 }, + { 0x0, 0x0, 66, -1, 1566, 0, 1, 152 }, + { 0x0, 0x0, 107, 1046, 2345, 0, 0, -1 }, + { 0x0, 0x0, 107, 1047, 2864, 0, 1, 30 }, + { 0x0, 0x0, 107, 1048, 2386, 0, 0, -1 }, + { 0x0, 0x0, 107, 1049, 2868, 0, 1, 30 }, + { 0x0, 0x0, 109, -1, 2347, 0, 0, -1 }, + { 0x1, 0x1, 109, -1, 2865, 27, 1, 30 }, + { 0x0, 0x0, 109, -1, 2388, 0, 0, -1 }, + { 0x1, 0x1, 109, -1, 2869, 27, 1, 30 }, + { 0x0, 0x0, 110, 1051, -1, 0, 1, 122 }, + { 0x1, 0x1, 111, -1, -1, 27, 1, 122 }, + { 0x0, 0x0, 112, 1082, 2894, 0, 1, 1 }, + { 0x0, 0x0, 112, 1083, 2897, 0, 1, 1 }, + { 0x0, 0x0, 112, 1224, 305, 0, 0, -1 }, + { 0x0, 0x0, 112, 1225, 309, 0, 0, -1 }, + { 0x0, 0x0, 112, 1185, 440, 0, 0, -1 }, + { 0x0, 0x0, 112, 1186, 448, 0, 0, -1 }, + { 0x0, 0x0, 112, -1, 456, 0, 0, -1 }, + { 0x0, 0x0, 112, 1084, 2910, 0, 1, 1 }, + { 0x0, 0x0, 112, 1085, 2913, 0, 1, 1 }, + { 0x0, 0x0, 112, -1, 330, 0, 0, -1 }, + { 0x0, 0x0, 112, -1, 334, 0, 0, -1 }, + { 0x0, 0x0, 112, 1233, 335, 0, 0, -1 }, + { 0x0, 0x0, 112, 1234, 339, 0, 0, -1 }, + { 0x0, 0x0, 112, 1086, 2934, 0, 1, 1 }, + { 0x0, 0x0, 112, 1087, 2937, 0, 1, 1 }, + { 0x0, 0x0, 112, 1237, 353, 0, 0, -1 }, + { 0x0, 0x0, 112, 1238, 357, 0, 0, -1 }, + { 0x0, 0x0, 112, 1198, 488, 0, 0, -1 }, + { 0x0, 0x0, 112, 1199, 496, 0, 0, -1 }, + { 0x0, 0x0, 112, -1, 504, 0, 0, -1 }, + { 0x0, 0x0, 112, 1391, 2948, 0, 1, 1 }, + { 0x0, 0x0, 112, 1392, 2950, 0, 1, 1 }, + { 0x0, 0x0, 112, -1, 378, 0, 0, -1 }, + { 0x0, 0x0, 112, -1, 382, 0, 0, -1 }, + { 0x0, 0x0, 112, 1246, 383, 0, 0, -1 }, + { 0x0, 0x0, 112, 1247, 387, 0, 0, -1 }, + { 0x0, 0x0, 112, -1, 2315, 0, 0, -1 }, + { 0x1, 0x9, 112, -1, 2319, 33, 1, 55 }, + { 0x1, 0x9, 112, -1, 2981, 33, 1, 55 }, + { 0x2, 0x3, 112, 1408, 2382, 27, 1, 50 }, + { 0x1, 0x1, 114, 1374, 2895, 37, 1, 1 }, + { 0x1, 0x1, 114, 1375, 2898, 37, 1, 1 }, + { 0x1, 0x1, 114, 1379, 2911, 37, 1, 1 }, + { 0x1, 0x1, 114, 1380, 2914, 37, 1, 1 }, + { 0x1, 0x1, 114, 1386, 2935, 37, 1, 1 }, + { 0x1, 0x1, 114, 1387, 2938, 37, 1, 1 }, + { 0x0, 0x0, 114, -1, 2958, 0, 1, 1 }, + { 0x0, 0x0, 114, -1, 2959, 0, 1, 1 }, + { 0x0, 0x0, 115, 1123, 2890, 0, 1, 1 }, + { 0x0, 0x0, 115, 1124, 2892, 0, 1, 1 }, + { 0x0, 0x0, 115, 1183, 303, 0, 0, -1 }, + { 0x0, 0x0, 115, 1184, 307, 0, 0, -1 }, + { 0x0, 0x0, 115, -1, 444, 0, 0, -1 }, + { 0x0, 0x0, 115, -1, 452, 0, 0, -1 }, + { 0x0, 0x0, 115, 1228, 454, 0, 0, -1 }, + { 0x0, 0x0, 115, -1, 2908, 0, 1, 1 }, + { 0x0, 0x0, 115, -1, 2909, 0, 1, 1 }, + { 0x0, 0x0, 115, 1231, 328, 0, 0, -1 }, + { 0x0, 0x0, 115, 1232, 332, 0, 0, -1 }, + { 0x0, 0x0, 115, 1192, 337, 0, 0, -1 }, + { 0x0, 0x0, 115, 1193, 341, 0, 0, -1 }, + { 0x0, 0x0, 115, 1127, 2930, 0, 1, 1 }, + { 0x0, 0x0, 115, 1128, 2932, 0, 1, 1 }, + { 0x0, 0x0, 115, 1196, 351, 0, 0, -1 }, + { 0x0, 0x0, 115, 1197, 355, 0, 0, -1 }, + { 0x0, 0x0, 115, -1, 492, 0, 0, -1 }, + { 0x0, 0x0, 115, -1, 500, 0, 0, -1 }, + { 0x0, 0x0, 115, 1241, 502, 0, 0, -1 }, + { 0x0, 0x0, 115, -1, 2946, 0, 1, 1 }, + { 0x0, 0x0, 115, -1, 2947, 0, 1, 1 }, + { 0x0, 0x0, 115, 1244, 376, 0, 0, -1 }, + { 0x0, 0x0, 115, 1245, 380, 0, 0, -1 }, + { 0x0, 0x0, 115, 1205, 385, 0, 0, -1 }, + { 0x0, 0x0, 115, 1206, 389, 0, 0, -1 }, + { 0x0, 0x0, 115, 1078, 2313, 0, 0, -1 }, + { 0x0, 0x0, 115, 1079, 2317, 0, 1, 55 }, + { 0x0, 0x0, 115, 1080, 2980, 0, 1, 55 }, + { 0x0, 0x0, 115, 1081, 2381, 0, 1, 50 }, + { 0x1, 0x1, 115, -1, -1, 27, 1, 0 }, + { 0x1, 0x1, 115, -1, -1, 27, 1, 0 }, + { 0x1, 0x1, 115, -1, -1, 27, 1, 0 }, + { 0x1, 0x1, 116, -1, 2891, 37, 1, 1 }, + { 0x1, 0x1, 116, -1, 2893, 37, 1, 1 }, + { 0x0, 0x0, 116, -1, 2918, 0, 1, 1 }, + { 0x0, 0x0, 116, -1, 2919, 0, 1, 1 }, + { 0x1, 0x1, 116, -1, 2931, 37, 1, 1 }, + { 0x1, 0x1, 116, -1, 2933, 37, 1, 1 }, + { 0x0, 0x0, 116, -1, 2956, 0, 1, 1 }, + { 0x0, 0x0, 116, -1, 2957, 0, 1, 1 }, + { 0x0, 0x0, 117, 1176, -1, 0, 1, 0 }, + { 0x0, 0x0, 117, 1177, -1, 0, 1, 0 }, + { 0x0, 0x0, 117, 1178, -1, 0, 1, 0 }, + { 0x3, 0x3, 117, 1136, -1, 34, 1, 34 }, + { 0x3, 0x3, 117, 1137, -1, 34, 1, 41 }, + { 0x1, 0x1, 119, -1, -1, 35, 1, 34 }, + { 0x1, 0x1, 119, -1, -1, 35, 1, 41 }, + { 0x0, 0x0, 120, -1, -1, 0, 1, 41 }, + { 0x0, 0x0, 120, -1, -1, 0, 1, 67 }, + { 0x1, 0x1, 120, -1, -1, 36, 1, 129 }, + { 0x0, 0x0, 120, -1, -1, 0, 1, 41 }, + { 0x1, 0x1, 120, -1, -1, 27, 1, 103 }, + { 0x0, 0x0, 120, -1, -1, 0, 1, 112 }, + { 0x0, 0x0, 120, -1, -1, 0, 1, 74 }, + { 0x0, 0x0, 120, -1, -1, 0, 1, 74 }, + { 0x0, 0x0, 120, -1, -1, 0, 1, 75 }, + { 0x0, 0x0, 120, -1, -1, 0, 1, 41 }, + { 0x1, 0x1, 120, -1, -1, 27, 1, 124 }, + { 0x1, 0x1, 120, -1, -1, 27, 1, 41 }, + { 0x0, 0x0, 120, -1, -1, 0, 1, 41 }, + { 0x0, 0x0, 121, -1, 2820, 0, 0, -1 }, + { 0x0, 0x0, 121, -1, 2823, 0, 0, -1 }, + { 0x1, 0x1, 122, -1, -1, 35, 1, 17 }, + { 0x1, 0x1, 122, -1, -1, 35, 1, 17 }, + { 0x1, 0x1, 122, -1, -1, 35, 1, 17 }, + { 0x1, 0x1, 122, -1, -1, 35, 1, 17 }, + { 0x1, 0x1, 122, -1, -1, 35, 1, 23 }, + { 0x1, 0x1, 122, -1, -1, 35, 1, 23 }, + { 0x1, 0x1, 122, -1, -1, 35, 1, 23 }, + { 0x1, 0x1, 122, -1, -1, 35, 1, 23 }, + { 0x1, 0x1, 122, -1, -1, 23, 1, 68 }, + { 0x1, 0x1, 122, -1, -1, 23, 1, 68 }, + { 0x1, 0x1, 122, -1, -1, 23, 1, 68 }, + { 0x1, 0x1, 122, -1, -1, 23, 1, 68 }, + { 0x1, 0x1, 122, 918, -1, 23, 1, 68 }, + { 0x9, 0x9, 122, 919, -1, 20, 1, 68 }, + { 0x0, 0x0, 126, 2199, -1, 0, 1, 0 }, + { 0x0, 0x0, 126, 2200, -1, 0, 1, 0 }, + { 0x1, 0x1, 126, -1, -1, 28, 1, 34 }, + { 0x1, 0x1, 126, -1, -1, 27, 1, 34 }, + { 0x1, 0x1, 126, -1, -1, 29, 1, 0 }, + { 0x1, 0x1, 126, -1, -1, 29, 1, 0 }, + { 0x1, 0x1, 126, -1, -1, 29, 1, 0 }, + { 0x1, 0x1, 126, -1, -1, 29, 1, 0 }, + { 0x0, 0x0, 126, -1, -1, 0, 1, 121 }, + { 0x1, 0x1, 126, -1, -1, 29, 1, 0 }, + { 0x1, 0x1, 126, -1, -1, 29, 1, 0 }, + { 0x1, 0x1, 126, -1, -1, 29, 1, 0 }, + { 0x0, 0x0, 126, 1134, -1, 0, 1, 34 }, + { 0x0, 0x0, 126, 1262, -1, 0, 1, 41 }, + { 0x0, 0x0, 140, 1212, 2886, 0, 1, 1 }, + { 0x0, 0x0, 140, 1213, 2888, 0, 1, 1 }, + { 0x0, 0x0, 140, 1054, 304, 0, 0, -1 }, + { 0x0, 0x0, 140, 1055, 432, 0, 0, -1 }, + { 0x0, 0x0, 140, 1094, 313, 0, 0, -1 }, + { 0x0, 0x0, 140, 1095, 317, 0, 0, -1 }, + { 0x0, 0x0, 140, 1096, 453, 0, 0, -1 }, + { 0x0, 0x0, 140, -1, 2906, 0, 1, 1 }, + { 0x0, 0x0, 140, -1, 2907, 0, 1, 1 }, + { 0x0, 0x0, 140, 1099, 327, 0, 0, -1 }, + { 0x0, 0x0, 140, 1100, 331, 0, 0, -1 }, + { 0x0, 0x0, 140, -1, 338, 0, 0, -1 }, + { 0x0, 0x0, 140, -1, 342, 0, 0, -1 }, + { 0x0, 0x0, 140, 1216, 2926, 0, 1, 1 }, + { 0x0, 0x0, 140, 1217, 2928, 0, 1, 1 }, + { 0x0, 0x0, 140, 1067, 352, 0, 0, -1 }, + { 0x0, 0x0, 140, 1068, 480, 0, 0, -1 }, + { 0x0, 0x0, 140, 1107, 361, 0, 0, -1 }, + { 0x0, 0x0, 140, 1108, 365, 0, 0, -1 }, + { 0x0, 0x0, 140, 1109, 501, 0, 0, -1 }, + { 0x0, 0x0, 140, -1, 2944, 0, 1, 1 }, + { 0x0, 0x0, 140, -1, 2945, 0, 1, 1 }, + { 0x0, 0x0, 140, 1112, 375, 0, 0, -1 }, + { 0x0, 0x0, 140, 1113, 379, 0, 0, -1 }, + { 0x0, 0x0, 140, -1, 386, 0, 0, -1 }, + { 0x0, 0x0, 140, -1, 390, 0, 0, -1 }, + { 0x0, 0x0, 140, 3012, 2301, 0, 0, -1 }, + { 0x1, 0x1, 140, 3013, 2309, 33, 1, 55 }, + { 0x1, 0x1, 140, 3014, 2974, 33, 1, 55 }, + { 0x0, 0x0, 140, 3015, 2375, 0, 0, -1 }, + { 0x1, 0x1, 140, 3016, -1, 28, 1, 50 }, + { 0x1, 0x1, 141, -1, 2887, 37, 1, 1 }, + { 0x1, 0x1, 141, -1, 2889, 37, 1, 1 }, + { 0x0, 0x0, 141, -1, 2916, 0, 1, 1 }, + { 0x0, 0x0, 141, -1, 2917, 0, 1, 1 }, + { 0x1, 0x1, 141, -1, 2927, 37, 1, 1 }, + { 0x1, 0x1, 141, -1, 2929, 37, 1, 1 }, + { 0x0, 0x0, 141, -1, 2954, 0, 1, 1 }, + { 0x0, 0x0, 141, -1, 2955, 0, 1, 1 }, + { 0x1, 0x1, 144, 917, 1158, 3, 1, 23 }, + { 0x0, 0x0, 145, 2201, -1, 0, 1, 34 }, + { 0x0, 0x0, 146, 923, 2880, 0, 1, 1 }, + { 0x0, 0x0, 146, 924, 2883, 0, 1, 1 }, + { 0x0, 0x0, 146, -1, 306, 0, 0, -1 }, + { 0x0, 0x0, 146, -1, 436, 0, 0, -1 }, + { 0x0, 0x0, 146, 1056, 311, 0, 0, -1 }, + { 0x0, 0x0, 146, 1057, 315, 0, 0, -1 }, + { 0x0, 0x0, 146, 1058, 455, 0, 0, -1 }, + { 0x0, 0x0, 146, 927, 2900, 0, 1, 1 }, + { 0x0, 0x0, 146, 928, 2903, 0, 1, 1 }, + { 0x0, 0x0, 146, 1061, 329, 0, 0, -1 }, + { 0x0, 0x0, 146, 1062, 333, 0, 0, -1 }, + { 0x0, 0x0, 146, 1101, 336, 0, 0, -1 }, + { 0x0, 0x0, 146, 1102, 340, 0, 0, -1 }, + { 0x0, 0x0, 146, 933, 2920, 0, 1, 1 }, + { 0x0, 0x0, 146, 934, 2923, 0, 1, 1 }, + { 0x0, 0x0, 146, -1, 354, 0, 0, -1 }, + { 0x0, 0x0, 146, -1, 484, 0, 0, -1 }, + { 0x0, 0x0, 146, 1069, 359, 0, 0, -1 }, + { 0x0, 0x0, 146, 1070, 363, 0, 0, -1 }, + { 0x0, 0x0, 146, 1071, 503, 0, 0, -1 }, + { 0x0, 0x0, 146, 937, 2940, 0, 1, 1 }, + { 0x0, 0x0, 146, 938, 2942, 0, 1, 1 }, + { 0x0, 0x0, 146, 1074, 377, 0, 0, -1 }, + { 0x0, 0x0, 146, 1075, 381, 0, 0, -1 }, + { 0x0, 0x0, 146, 1114, 384, 0, 0, -1 }, + { 0x0, 0x0, 146, 1115, 388, 0, 0, -1 }, + { 0x0, 0x0, 146, 1207, 2299, 0, 0, -1 }, + { 0x1, 0x1, 146, 1208, 2307, 36, 1, 55 }, + { 0x1, 0x1, 146, 1209, 2973, 36, 1, 55 }, + { 0x0, 0x0, 146, 1210, 2374, 0, 0, -1 }, + { 0x1, 0x1, 146, 1211, -1, 27, 1, 50 }, + { 0x1, 0x1, 147, -1, 2882, 37, 1, 1 }, + { 0x1, 0x1, 147, -1, 2885, 37, 1, 1 }, + { 0x1, 0x1, 147, -1, 2902, 37, 1, 1 }, + { 0x1, 0x1, 147, -1, 2905, 37, 1, 1 }, + { 0x1, 0x1, 147, -1, 2922, 37, 1, 1 }, + { 0x1, 0x1, 147, -1, 2925, 37, 1, 1 }, + { 0x0, 0x0, 147, -1, 2952, 0, 1, 1 }, + { 0x0, 0x0, 147, -1, 2953, 0, 1, 1 }, + { 0x0, 0x0, 148, -1, -1, 0, 1, 34 }, + { 0x0, 0x0, 148, 1135, -1, 0, 1, 41 }, + { 0x0, 0x0, 149, -1, -1, 0, 1, 41 }, + { 0x0, 0x0, 149, -1, -1, 0, 1, 67 }, + { 0x0, 0x0, 149, -1, 2960, 0, 1, 64 }, + { 0x0, 0x0, 149, -1, 2961, 0, 1, 64 }, + { 0x0, 0x0, 149, -1, -1, 0, 1, 41 }, + { 0x0, 0x0, 149, -1, -1, 0, 1, 87 }, + { 0x0, 0x0, 149, -1, -1, 0, 1, 87 }, + { 0x0, 0x0, 149, -1, -1, 0, 1, 92 }, + { 0x0, 0x0, 149, -1, -1, 0, 1, 41 }, + { 0x1, 0x1, 150, -1, 593, 12, 1, 6 }, + { 0x1, 0x1, 150, -1, 596, 12, 1, 6 }, + { 0x200001, 0x200001, 150, -1, 598, 12, 1, 6 }, + { 0x400001, 0x400001, 150, -1, 600, 12, 1, 6 }, + { 0x600001, 0x600001, 150, -1, 602, 12, 1, 6 }, + { 0x1, 0x1, 150, -1, 604, 12, 1, 6 }, + { 0x200001, 0x200001, 150, -1, 606, 12, 1, 6 }, + { 0x400001, 0x400001, 150, -1, 608, 12, 1, 6 }, + { 0x600001, 0x600001, 150, -1, 610, 12, 1, 6 }, + { 0x41, 0x41, 150, -1, 612, 6, 1, 7 }, + { 0x8000041, 0x8000041, 150, -1, 614, 6, 1, 7 }, + { 0x10000041, 0x10000041, 150, -1, 616, 6, 1, 7 }, + { 0x18000041, 0x18000041, 150, -1, 618, 6, 1, 7 }, + { 0x1, 0x1, 150, -1, 632, 12, 1, 8 }, + { 0x200001, 0x200001, 150, -1, 634, 12, 1, 8 }, + { 0x400001, 0x400001, 150, -1, 636, 12, 1, 8 }, + { 0x600001, 0x600001, 150, -1, 638, 12, 1, 8 }, + { 0x1, 0x1, 150, -1, 644, 12, 1, 16 }, + { 0x200001, 0x200001, 150, -1, 646, 12, 1, 16 }, + { 0x400001, 0x400001, 150, -1, 648, 12, 1, 16 }, + { 0x600001, 0x600001, 150, -1, 650, 12, 1, 16 }, + { 0x1, 0x1, 150, -1, 656, 12, 1, 18 }, + { 0x1, 0x1, 150, -1, 659, 12, 1, 18 }, + { 0x200001, 0x200001, 150, -1, 661, 12, 1, 18 }, + { 0x400001, 0x400001, 150, -1, 663, 12, 1, 18 }, + { 0x600001, 0x600001, 150, -1, 665, 12, 1, 18 }, + { 0x1, 0x1, 150, -1, 667, 12, 1, 18 }, + { 0x200001, 0x200001, 150, -1, 669, 12, 1, 18 }, + { 0x400001, 0x400001, 150, -1, 671, 12, 1, 18 }, + { 0x600001, 0x600001, 150, -1, 673, 12, 1, 18 }, + { 0x1, 0x1, 150, -1, 683, 12, 1, 19 }, + { 0x200001, 0x200001, 150, -1, 685, 12, 1, 19 }, + { 0x400001, 0x400001, 150, -1, 687, 12, 1, 19 }, + { 0x600001, 0x600001, 150, -1, 689, 12, 1, 19 }, + { 0x41, 0x41, 150, -1, 691, 6, 1, 19 }, + { 0x8000041, 0x8000041, 150, -1, 693, 6, 1, 19 }, + { 0x10000041, 0x10000041, 150, -1, 695, 6, 1, 19 }, + { 0x18000041, 0x18000041, 150, -1, 697, 6, 1, 19 }, + { 0x1, 0x1, 150, -1, 707, 12, 1, 20 }, + { 0x200001, 0x200001, 150, -1, 709, 12, 1, 20 }, + { 0x400001, 0x400001, 150, -1, 711, 12, 1, 20 }, + { 0x600001, 0x600001, 150, -1, 713, 12, 1, 20 }, + { 0x1, 0x1, 150, -1, 719, 12, 1, 21 }, + { 0x200001, 0x200001, 150, -1, 721, 12, 1, 21 }, + { 0x400001, 0x400001, 150, -1, 723, 12, 1, 21 }, + { 0x600001, 0x600001, 150, -1, 725, 12, 1, 21 }, + { 0x41, 0x41, 150, -1, 727, 6, 1, 21 }, + { 0x8000041, 0x8000041, 150, -1, 729, 6, 1, 21 }, + { 0x10000041, 0x10000041, 150, -1, 731, 6, 1, 21 }, + { 0x18000041, 0x18000041, 150, -1, 733, 6, 1, 21 }, + { 0x1, 0x1, 150, -1, 743, 12, 1, 22 }, + { 0x200001, 0x200001, 150, -1, 745, 12, 1, 22 }, + { 0x400001, 0x400001, 150, -1, 747, 12, 1, 22 }, + { 0x600001, 0x600001, 150, -1, 749, 12, 1, 22 }, + { 0x1, 0x1, 150, -1, 755, 12, 1, 18 }, + { 0x1, 0x1, 150, -1, 758, 12, 1, 18 }, + { 0x200001, 0x200001, 150, -1, 760, 12, 1, 18 }, + { 0x400001, 0x400001, 150, -1, 762, 12, 1, 18 }, + { 0x600001, 0x600001, 150, -1, 764, 12, 1, 18 }, + { 0x1, 0x1, 150, -1, 766, 12, 1, 18 }, + { 0x200001, 0x200001, 150, -1, 768, 12, 1, 18 }, + { 0x400001, 0x400001, 150, -1, 770, 12, 1, 18 }, + { 0x600001, 0x600001, 150, -1, 772, 12, 1, 18 }, + { 0x1, 0x1, 150, -1, 782, 12, 1, 22 }, + { 0x200001, 0x200001, 150, -1, 784, 12, 1, 22 }, + { 0x400001, 0x400001, 150, -1, 786, 12, 1, 22 }, + { 0x600001, 0x600001, 150, -1, 788, 12, 1, 22 }, + { 0x0, 0x0, 155, -1, -1, 0, 1, 131 }, + { 0x0, 0x0, 159, 793, -1, 0, 1, 81 }, + { 0x0, 0x0, 159, 794, -1, 0, 1, 81 }, + { 0x9, 0x9, 159, -1, 1456, 32, 1, 137 }, + { 0x9, 0x9, 159, -1, 1465, 32, 1, 137 }, + { 0x9, 0x9, 159, -1, 1474, 32, 1, 137 }, + { 0x9, 0x9, 159, -1, 1487, 32, 1, 137 }, + { 0x9, 0x9, 159, -1, 1496, 32, 1, 137 }, + { 0x9, 0x9, 159, -1, 1505, 32, 1, 137 }, + { 0x9, 0x9, 159, -1, 1514, 32, 1, 137 }, + { 0x9, 0x9, 159, -1, 1523, 32, 1, 137 }, + { 0x9, 0x9, 159, -1, 1532, 32, 1, 137 }, + { 0x9, 0x9, 159, -1, 1542, 32, 1, 137 }, + { 0x9, 0x9, 159, -1, 1552, 32, 1, 137 }, + { 0x9, 0x9, 159, -1, 1562, 32, 1, 137 }, + { 0x9, 0x9, 159, -1, 1571, 32, 1, 151 }, + { 0x9, 0x9, 159, -1, 1577, 32, 1, 156 }, + { 0x9, 0x9, 159, -1, 1583, 32, 1, 156 }, + { 0x9, 0x9, 159, -1, 1589, 32, 1, 151 }, + { 0x9, 0x9, 159, -1, 1595, 32, 1, 156 }, + { 0x9, 0x9, 159, -1, 1601, 32, 1, 156 }, + { 0x9, 0x9, 159, -1, 1607, 32, 1, 151 }, + { 0x9, 0x9, 159, -1, 1613, 32, 1, 156 }, + { 0x9, 0x9, 159, -1, 1619, 32, 1, 156 }, + { 0x9, 0x9, 159, -1, 1625, 32, 1, 151 }, + { 0x9, 0x9, 159, -1, 1631, 32, 1, 156 }, + { 0x9, 0x9, 159, -1, 1637, 32, 1, 151 }, + { 0x9, 0x9, 159, -1, 1643, 32, 1, 156 }, + { 0x9, 0x9, 159, -1, 1649, 32, 1, 151 }, + { 0x9, 0x9, 159, -1, 1655, 32, 1, 156 }, + { 0x9, 0x9, 159, -1, 1661, 32, 1, 151 }, + { 0x9, 0x9, 159, -1, 1667, 32, 1, 156 }, + { 0x9, 0x9, 159, -1, 1673, 32, 1, 156 }, + { 0x0, 0x0, 160, 1253, 298, 0, 0, -1 }, + { 0x0, 0x0, 160, 1254, 422, 0, 0, -1 }, + { 0x1, 0x1, 160, -1, 2896, 38, 1, 1 }, + { 0x1, 0x1, 160, 925, 2899, 38, 1, 1 }, + { 0x0, 0x0, 160, 926, 423, 0, 0, -1 }, + { 0x0, 0x0, 160, 1255, 320, 0, 0, -1 }, + { 0x0, 0x0, 160, 1256, 462, 0, 0, -1 }, + { 0x1, 0x1, 160, -1, 2912, 38, 1, 1 }, + { 0x1, 0x1, 160, 929, 2915, 38, 1, 1 }, + { 0x0, 0x0, 160, 930, 463, 0, 0, -1 }, + { 0x0, 0x0, 160, 931, 325, 0, 0, -1 }, + { 0x0, 0x0, 160, 932, 343, 0, 0, -1 }, + { 0x0, 0x0, 160, 1257, 346, 0, 0, -1 }, + { 0x0, 0x0, 160, 1258, 470, 0, 0, -1 }, + { 0x1, 0x1, 160, -1, 2936, 38, 1, 1 }, + { 0x1, 0x1, 160, 935, 2939, 38, 1, 1 }, + { 0x0, 0x0, 160, 936, 471, 0, 0, -1 }, + { 0x0, 0x0, 160, -1, 368, 0, 0, -1 }, + { 0x0, 0x0, 160, -1, 510, 0, 0, -1 }, + { 0x1, 0x1, 160, -1, 2949, 38, 1, 1 }, + { 0x1, 0x1, 160, 939, 2951, 38, 1, 1 }, + { 0x0, 0x0, 160, 940, 511, 0, 0, -1 }, + { 0x0, 0x0, 160, 941, 373, 0, 0, -1 }, + { 0x0, 0x0, 160, 942, 391, 0, 0, -1 }, + { 0x0, 0x0, 161, 1415, 2321, 0, 0, -1 }, + { 0x0, 0x0, 161, 1416, 2329, 0, 1, 55 }, + { 0x0, 0x0, 161, 1417, 2990, 0, 1, 55 }, + { 0x0, 0x0, 161, 1418, 2377, 0, 0, -1 }, + { 0x1, 0x1, 161, 1419, -1, 29, 1, 50 }, + { 0x0, 0x0, 162, -1, 2339, 0, 0, -1 }, + { 0x1, 0x9, 162, -1, 2343, 33, 1, 55 }, + { 0x1, 0x9, 162, -1, 2999, 33, 1, 55 }, + { 0x6, 0x7, 162, -1, 2384, 27, 1, 50 }, + { 0x0, 0x0, 163, 1401, 2337, 0, 0, -1 }, + { 0x0, 0x0, 163, 1402, 2341, 0, 1, 55 }, + { 0x0, 0x0, 163, 1403, 2998, 0, 1, 55 }, + { 0x1, 0x1, 163, 1404, 2383, 29, 1, 50 }, + { 0x1, 0x1, 164, 1422, -1, 27, 1, 34 }, + { 0x0, 0x0, 165, 2193, 2325, 0, 0, -1 }, + { 0x1, 0x1, 165, 2194, 2333, 33, 1, 55 }, + { 0x1, 0x1, 165, 2195, 2992, 33, 1, 55 }, + { 0x0, 0x0, 165, 2196, 2379, 0, 0, -1 }, + { 0x3, 0x3, 165, 2197, -1, 28, 1, 50 }, + { 0x0, 0x0, 166, 1410, 2323, 0, 0, -1 }, + { 0x1, 0x1, 166, 1411, 2331, 36, 1, 55 }, + { 0x1, 0x1, 166, 1412, 2991, 36, 1, 55 }, + { 0x0, 0x0, 166, 1413, 2378, 0, 0, -1 }, + { 0x5, 0x5, 166, 1414, -1, 27, 1, 50 }, + { 0x0, 0x0, 167, -1, 2962, 0, 1, 64 }, + { 0x0, 0x0, 167, -1, 2963, 0, 1, 64 }, + { 0x1, 0x1, 169, -1, -1, 28, 1, 34 }, + { 0x1, 0x1, 170, 2779, -1, 27, 1, 34 }, + { 0x1, 0x1, 170, 2780, -1, 27, 1, 34 }, + { 0x1, 0x1, 171, 1703, -1, 28, 1, 142 }, + { 0x1, 0x1, 171, 1704, -1, 28, 1, 142 }, + { 0x1, 0x1, 171, 1705, -1, 28, 1, 142 }, + { 0x1, 0x1, 171, 1706, -1, 28, 1, 142 }, + { 0x1, 0x1, 171, 1707, -1, 28, 1, 141 }, + { 0x1, 0x1, 171, 1708, -1, 28, 1, 141 }, + { 0x1, 0x1, 171, 1709, -1, 28, 1, 141 }, + { 0x1, 0x1, 171, 1710, -1, 28, 1, 141 }, + { 0x1, 0x1, 171, 1711, -1, 28, 1, 141 }, + { 0x1, 0x1, 171, 1712, -1, 28, 1, 141 }, + { 0x1, 0x1, 171, 1713, -1, 28, 1, 141 }, + { 0x1, 0x1, 171, 1714, -1, 28, 1, 141 }, + { 0x1, 0x1, 171, 1715, -1, 28, 1, 141 }, + { 0x1, 0x1, 171, 1716, -1, 28, 1, 141 }, + { 0x1, 0x1, 171, 1717, -1, 28, 1, 141 }, + { 0x1, 0x1, 171, 1718, -1, 28, 1, 141 }, + { 0x1, 0x1, 171, 1719, -1, 28, 1, 141 }, + { 0x1, 0x1, 171, 1720, -1, 28, 1, 141 }, + { 0x1, 0x1, 171, 1721, -1, 28, 1, 141 }, + { 0x1, 0x1, 171, 1722, -1, 28, 1, 141 }, + { 0x1, 0x1, 171, 1723, -1, 28, 1, 143 }, + { 0x1, 0x1, 171, 1724, -1, 28, 1, 143 }, + { 0x1, 0x1, 171, 1725, -1, 28, 1, 143 }, + { 0x1, 0x1, 171, 1726, -1, 28, 1, 143 }, + { 0x1, 0x1, 171, 1727, -1, 28, 1, 133 }, + { 0x1, 0x1, 171, 1728, -1, 28, 1, 134 }, + { 0x1, 0x1, 171, 1729, -1, 28, 1, 135 }, + { 0x1, 0x1, 171, 1730, -1, 28, 1, 131 }, + { 0x1, 0x1, 171, 1731, -1, 28, 1, 131 }, + { 0x1, 0x1, 171, 1732, -1, 28, 1, 137 }, + { 0x1, 0x1, 171, 1733, -1, 28, 1, 137 }, + { 0x1, 0x1, 171, 1734, -1, 28, 1, 137 }, + { 0x1, 0x1, 171, 1735, -1, 28, 1, 131 }, + { 0x1, 0x1, 171, 1736, -1, 28, 1, 133 }, + { 0x1, 0x1, 171, 1737, -1, 28, 1, 134 }, + { 0x1, 0x1, 171, 1738, -1, 28, 1, 135 }, + { 0x1, 0x1, 171, 1739, -1, 28, 1, 131 }, + { 0x1, 0x1, 171, 1740, -1, 28, 1, 131 }, + { 0x1, 0x1, 171, 1741, -1, 28, 1, 137 }, + { 0x1, 0x1, 171, 1742, -1, 28, 1, 137 }, + { 0x1, 0x1, 171, 1743, -1, 28, 1, 137 }, + { 0x1, 0x1, 171, 1744, -1, 28, 1, 131 }, + { 0x1, 0x1, 171, 1745, -1, 28, 1, 133 }, + { 0x1, 0x1, 171, 1746, -1, 28, 1, 134 }, + { 0x1, 0x1, 171, 1747, -1, 28, 1, 135 }, + { 0x1, 0x1, 171, 1748, -1, 28, 1, 131 }, + { 0x1, 0x1, 171, 1749, -1, 28, 1, 131 }, + { 0x1, 0x1, 171, 1750, -1, 28, 1, 137 }, + { 0x1, 0x1, 171, 1751, -1, 28, 1, 137 }, + { 0x1, 0x1, 171, 1752, -1, 28, 1, 137 }, + { 0x1, 0x1, 171, 1753, -1, 28, 1, 131 }, + { 0x1, 0x1, 171, 1754, -1, 28, 1, 132 }, + { 0x1, 0x1, 171, 1755, -1, 28, 1, 132 }, + { 0x1, 0x1, 171, 1756, -1, 28, 1, 132 }, + { 0x1, 0x1, 171, 1757, -1, 28, 1, 132 }, + { 0x1, 0x1, 171, 1758, -1, 28, 1, 133 }, + { 0x1, 0x1, 171, 1759, -1, 28, 1, 134 }, + { 0x1, 0x1, 171, 1760, -1, 28, 1, 135 }, + { 0x1, 0x1, 171, 1761, -1, 28, 1, 131 }, + { 0x1, 0x1, 171, 1762, -1, 28, 1, 131 }, + { 0x1, 0x1, 171, 1763, -1, 28, 1, 137 }, + { 0x1, 0x1, 171, 1764, -1, 28, 1, 137 }, + { 0x1, 0x1, 171, 1765, -1, 28, 1, 137 }, + { 0x1, 0x1, 171, 1766, -1, 28, 1, 131 }, + { 0x1, 0x1, 171, 1767, -1, 28, 1, 133 }, + { 0x1, 0x1, 171, 1768, -1, 28, 1, 134 }, + { 0x1, 0x1, 171, 1769, -1, 28, 1, 135 }, + { 0x1, 0x1, 171, 1770, -1, 28, 1, 131 }, + { 0x1, 0x1, 171, 1771, -1, 28, 1, 131 }, + { 0x1, 0x1, 171, 1772, -1, 28, 1, 137 }, + { 0x1, 0x1, 171, 1773, -1, 28, 1, 137 }, + { 0x1, 0x1, 171, 1774, -1, 28, 1, 137 }, + { 0x1, 0x1, 171, 1775, -1, 28, 1, 131 }, + { 0x1, 0x1, 171, 1776, -1, 28, 1, 133 }, + { 0x1, 0x1, 171, 1777, -1, 28, 1, 134 }, + { 0x1, 0x1, 171, 1778, -1, 28, 1, 135 }, + { 0x1, 0x1, 171, 1779, -1, 28, 1, 131 }, + { 0x1, 0x1, 171, 1780, -1, 28, 1, 131 }, + { 0x1, 0x1, 171, 1781, -1, 28, 1, 137 }, + { 0x1, 0x1, 171, 1782, -1, 28, 1, 137 }, + { 0x1, 0x1, 171, 1783, -1, 28, 1, 137 }, + { 0x1, 0x1, 171, 1784, -1, 28, 1, 131 }, + { 0x1, 0x1, 171, 1785, -1, 28, 1, 133 }, + { 0x1, 0x1, 171, 1786, -1, 28, 1, 134 }, + { 0x1, 0x1, 171, 1787, -1, 28, 1, 135 }, + { 0x1, 0x1, 171, 1788, -1, 28, 1, 131 }, + { 0x1, 0x1, 171, 1789, -1, 28, 1, 131 }, + { 0x1, 0x1, 171, 1790, -1, 28, 1, 137 }, + { 0x1, 0x1, 171, 1791, -1, 28, 1, 137 }, + { 0x1, 0x1, 171, 1792, -1, 28, 1, 137 }, + { 0x1, 0x1, 171, 1793, -1, 28, 1, 131 }, + { 0x1, 0x1, 171, 1794, -1, 28, 1, 133 }, + { 0x1, 0x1, 171, 1795, -1, 28, 1, 134 }, + { 0x1, 0x1, 171, 1796, -1, 28, 1, 135 }, + { 0x1, 0x1, 171, 1797, -1, 28, 1, 131 }, + { 0x1, 0x1, 171, 1798, -1, 28, 1, 131 }, + { 0x1, 0x1, 171, 1799, -1, 28, 1, 137 }, + { 0x1, 0x1, 171, 1800, -1, 28, 1, 137 }, + { 0x1, 0x1, 171, 1801, -1, 28, 1, 137 }, + { 0x1, 0x1, 171, 1802, -1, 28, 1, 131 }, + { 0x1, 0x1, 171, 1803, -1, 28, 1, 133 }, + { 0x1, 0x1, 171, 1804, -1, 28, 1, 134 }, + { 0x1, 0x1, 171, 1805, -1, 28, 1, 135 }, + { 0x1, 0x1, 171, 1806, -1, 28, 1, 131 }, + { 0x1, 0x1, 171, 1807, -1, 28, 1, 131 }, + { 0x1, 0x1, 171, 1808, -1, 28, 1, 137 }, + { 0x1, 0x1, 171, 1809, -1, 28, 1, 137 }, + { 0x1, 0x1, 171, 1810, -1, 28, 1, 137 }, + { 0x1, 0x1, 171, 1811, -1, 28, 1, 131 }, + { 0x1, 0x1, 171, 1812, -1, 28, 1, 133 }, + { 0x1, 0x1, 171, 1813, -1, 28, 1, 134 }, + { 0x1, 0x1, 171, 1814, -1, 28, 1, 135 }, + { 0x1, 0x1, 171, 1815, -1, 28, 1, 131 }, + { 0x1, 0x1, 171, 1816, -1, 28, 1, 131 }, + { 0x1, 0x1, 171, 1817, -1, 28, 1, 136 }, + { 0x1, 0x1, 171, 1818, -1, 28, 1, 137 }, + { 0x1, 0x1, 171, 1819, -1, 28, 1, 137 }, + { 0x1, 0x1, 171, 1820, -1, 28, 1, 137 }, + { 0x1, 0x1, 171, 1821, -1, 28, 1, 131 }, + { 0x1, 0x1, 171, 1822, -1, 28, 1, 133 }, + { 0x1, 0x1, 171, 1823, -1, 28, 1, 134 }, + { 0x1, 0x1, 171, 1824, -1, 28, 1, 135 }, + { 0x1, 0x1, 171, 1825, -1, 28, 1, 131 }, + { 0x1, 0x1, 171, 1826, -1, 28, 1, 131 }, + { 0x1, 0x1, 171, 1827, -1, 28, 1, 136 }, + { 0x1, 0x1, 171, 1828, -1, 28, 1, 137 }, + { 0x1, 0x1, 171, 1829, -1, 28, 1, 137 }, + { 0x1, 0x1, 171, 1830, -1, 28, 1, 137 }, + { 0x1, 0x1, 171, 1831, -1, 28, 1, 131 }, + { 0x1, 0x1, 171, 1832, -1, 28, 1, 133 }, + { 0x1, 0x1, 171, 1833, -1, 28, 1, 134 }, + { 0x1, 0x1, 171, 1834, -1, 28, 1, 135 }, + { 0x1, 0x1, 171, 1835, -1, 28, 1, 131 }, + { 0x1, 0x1, 171, 1836, -1, 28, 1, 131 }, + { 0x1, 0x1, 171, 1837, -1, 28, 1, 136 }, + { 0x1, 0x1, 171, 1838, -1, 28, 1, 137 }, + { 0x1, 0x1, 171, 1839, -1, 28, 1, 137 }, + { 0x1, 0x1, 171, 1840, -1, 28, 1, 137 }, + { 0x1, 0x1, 171, 1841, -1, 28, 1, 131 }, + { 0x1, 0x1, 171, 1842, -1, 28, 1, 147 }, + { 0x1, 0x1, 171, 1843, -1, 28, 1, 152 }, + { 0x1, 0x1, 171, 1844, -1, 28, 1, 152 }, + { 0x1, 0x1, 171, 1845, -1, 28, 1, 148 }, + { 0x1, 0x1, 171, 1846, -1, 28, 1, 149 }, + { 0x1, 0x1, 171, 1847, -1, 28, 1, 150 }, + { 0x1, 0x1, 171, 1848, -1, 28, 1, 151 }, + { 0x1, 0x1, 171, 1849, -1, 28, 1, 151 }, + { 0x1, 0x1, 171, 1850, -1, 28, 1, 147 }, + { 0x1, 0x1, 171, 1851, -1, 28, 1, 153 }, + { 0x1, 0x1, 171, 1852, -1, 28, 1, 154 }, + { 0x1, 0x1, 171, 1853, -1, 28, 1, 155 }, + { 0x1, 0x1, 171, 1854, -1, 28, 1, 156 }, + { 0x1, 0x1, 171, 1855, -1, 28, 1, 156 }, + { 0x1, 0x1, 171, 1856, -1, 28, 1, 152 }, + { 0x1, 0x1, 171, 1857, -1, 28, 1, 153 }, + { 0x1, 0x1, 171, 1858, -1, 28, 1, 154 }, + { 0x1, 0x1, 171, 1859, -1, 28, 1, 155 }, + { 0x1, 0x1, 171, 1860, -1, 28, 1, 156 }, + { 0x1, 0x1, 171, 1861, -1, 28, 1, 156 }, + { 0x1, 0x1, 171, 1862, -1, 28, 1, 152 }, + { 0x1, 0x1, 171, 1863, -1, 28, 1, 148 }, + { 0x1, 0x1, 171, 1864, -1, 28, 1, 149 }, + { 0x1, 0x1, 171, 1865, -1, 28, 1, 150 }, + { 0x1, 0x1, 171, 1866, -1, 28, 1, 151 }, + { 0x1, 0x1, 171, 1867, -1, 28, 1, 151 }, + { 0x1, 0x1, 171, 1868, -1, 28, 1, 147 }, + { 0x1, 0x1, 171, 1869, -1, 28, 1, 153 }, + { 0x1, 0x1, 171, 1870, -1, 28, 1, 154 }, + { 0x1, 0x1, 171, 1871, -1, 28, 1, 155 }, + { 0x1, 0x1, 171, 1872, -1, 28, 1, 156 }, + { 0x1, 0x1, 171, 1873, -1, 28, 1, 156 }, + { 0x1, 0x1, 171, 1874, -1, 28, 1, 152 }, + { 0x1, 0x1, 171, 1875, -1, 28, 1, 153 }, + { 0x1, 0x1, 171, 1876, -1, 28, 1, 154 }, + { 0x1, 0x1, 171, 1877, -1, 28, 1, 155 }, + { 0x1, 0x1, 171, 1878, -1, 28, 1, 156 }, + { 0x1, 0x1, 171, 1879, -1, 28, 1, 156 }, + { 0x1, 0x1, 171, 1880, -1, 28, 1, 152 }, + { 0x1, 0x1, 171, 1881, -1, 28, 1, 148 }, + { 0x1, 0x1, 171, 1882, -1, 28, 1, 149 }, + { 0x1, 0x1, 171, 1883, -1, 28, 1, 150 }, + { 0x1, 0x1, 171, 1884, -1, 28, 1, 151 }, + { 0x1, 0x1, 171, 1885, -1, 28, 1, 151 }, + { 0x1, 0x1, 171, 1886, -1, 28, 1, 147 }, + { 0x1, 0x1, 171, 1887, -1, 28, 1, 153 }, + { 0x1, 0x1, 171, 1888, -1, 28, 1, 154 }, + { 0x1, 0x1, 171, 1889, -1, 28, 1, 155 }, + { 0x1, 0x1, 171, 1890, -1, 28, 1, 156 }, + { 0x1, 0x1, 171, 1891, -1, 28, 1, 156 }, + { 0x1, 0x1, 171, 1892, -1, 28, 1, 152 }, + { 0x1, 0x1, 171, 1893, -1, 28, 1, 153 }, + { 0x1, 0x1, 171, 1894, -1, 28, 1, 154 }, + { 0x1, 0x1, 171, 1895, -1, 28, 1, 155 }, + { 0x1, 0x1, 171, 1896, -1, 28, 1, 156 }, + { 0x1, 0x1, 171, 1897, -1, 28, 1, 156 }, + { 0x1, 0x1, 171, 1898, -1, 28, 1, 152 }, + { 0x1, 0x1, 171, 1899, -1, 28, 1, 148 }, + { 0x1, 0x1, 171, 1900, -1, 28, 1, 149 }, + { 0x1, 0x1, 171, 1901, -1, 28, 1, 150 }, + { 0x1, 0x1, 171, 1902, -1, 28, 1, 151 }, + { 0x1, 0x1, 171, 1903, -1, 28, 1, 151 }, + { 0x1, 0x1, 171, 1904, -1, 28, 1, 147 }, + { 0x1, 0x1, 171, 1905, -1, 28, 1, 153 }, + { 0x1, 0x1, 171, 1906, -1, 28, 1, 154 }, + { 0x1, 0x1, 171, 1907, -1, 28, 1, 155 }, + { 0x1, 0x1, 171, 1908, -1, 28, 1, 156 }, + { 0x1, 0x1, 171, 1909, -1, 28, 1, 156 }, + { 0x1, 0x1, 171, 1910, -1, 28, 1, 152 }, + { 0x1, 0x1, 171, 1911, -1, 28, 1, 148 }, + { 0x1, 0x1, 171, 1912, -1, 28, 1, 149 }, + { 0x1, 0x1, 171, 1913, -1, 28, 1, 150 }, + { 0x1, 0x1, 171, 1914, -1, 28, 1, 151 }, + { 0x1, 0x1, 171, 1915, -1, 28, 1, 151 }, + { 0x1, 0x1, 171, 1916, -1, 28, 1, 147 }, + { 0x1, 0x1, 171, 1917, -1, 28, 1, 153 }, + { 0x1, 0x1, 171, 1918, -1, 28, 1, 154 }, + { 0x1, 0x1, 171, 1919, -1, 28, 1, 155 }, + { 0x1, 0x1, 171, 1920, -1, 28, 1, 156 }, + { 0x1, 0x1, 171, 1921, -1, 28, 1, 156 }, + { 0x1, 0x1, 171, 1922, -1, 28, 1, 152 }, + { 0x1, 0x1, 171, 1923, -1, 28, 1, 148 }, + { 0x1, 0x1, 171, 1924, -1, 28, 1, 149 }, + { 0x1, 0x1, 171, 1925, -1, 28, 1, 150 }, + { 0x1, 0x1, 171, 1926, -1, 28, 1, 151 }, + { 0x1, 0x1, 171, 1927, -1, 28, 1, 151 }, + { 0x1, 0x1, 171, 1928, -1, 28, 1, 147 }, + { 0x1, 0x1, 171, 1929, -1, 28, 1, 153 }, + { 0x1, 0x1, 171, 1930, -1, 28, 1, 154 }, + { 0x1, 0x1, 171, 1931, -1, 28, 1, 155 }, + { 0x1, 0x1, 171, 1932, -1, 28, 1, 156 }, + { 0x1, 0x1, 171, 1933, -1, 28, 1, 156 }, + { 0x1, 0x1, 171, 1934, -1, 28, 1, 152 }, + { 0x1, 0x1, 171, 1935, -1, 28, 1, 148 }, + { 0x1, 0x1, 171, 1936, -1, 28, 1, 149 }, + { 0x1, 0x1, 171, 1937, -1, 28, 1, 150 }, + { 0x1, 0x1, 171, 1938, -1, 28, 1, 151 }, + { 0x1, 0x1, 171, 1939, -1, 28, 1, 151 }, + { 0x1, 0x1, 171, 1940, -1, 28, 1, 147 }, + { 0x1, 0x1, 171, 1941, -1, 28, 1, 153 }, + { 0x1, 0x1, 171, 1942, -1, 28, 1, 154 }, + { 0x1, 0x1, 171, 1943, -1, 28, 1, 155 }, + { 0x1, 0x1, 171, 1944, -1, 28, 1, 156 }, + { 0x1, 0x1, 171, 1945, -1, 28, 1, 156 }, + { 0x1, 0x1, 171, 1946, -1, 28, 1, 152 }, + { 0x1, 0x1, 171, 1947, -1, 28, 1, 153 }, + { 0x1, 0x1, 171, 1948, -1, 28, 1, 154 }, + { 0x1, 0x1, 171, 1949, -1, 28, 1, 155 }, + { 0x1, 0x1, 171, 1950, -1, 28, 1, 156 }, + { 0x1, 0x1, 171, 1951, -1, 28, 1, 156 }, + { 0x1, 0x1, 171, 1952, -1, 28, 1, 152 }, + { 0x1, 0x1, 171, 1691, -1, 28, 1, 158 }, + { 0x1, 0x1, 171, 1692, -1, 28, 1, 158 }, + { 0x1, 0x1, 171, 1693, -1, 28, 1, 158 }, + { 0x1, 0x1, 171, 1694, -1, 28, 1, 158 }, + { 0x1, 0x1, 171, 1695, -1, 28, 1, 159 }, + { 0x1, 0x1, 171, 1696, -1, 28, 1, 159 }, + { 0x1, 0x1, 171, 1697, -1, 28, 1, 159 }, + { 0x1, 0x1, 171, 1698, -1, 28, 1, 159 }, + { 0x1, 0x1, 171, 1699, -1, 28, 1, 159 }, + { 0x1, 0x1, 171, 1700, -1, 28, 1, 159 }, + { 0x1, 0x1, 171, 1701, -1, 28, 1, 159 }, + { 0x1, 0x1, 171, 1702, -1, 28, 1, 159 }, + { 0x1, 0x1, 171, 1997, -1, 28, 1, 143 }, + { 0x1, 0x1, 171, 1998, -1, 28, 1, 143 }, + { 0x1, 0x1, 171, 1999, -1, 28, 1, 143 }, + { 0x1, 0x1, 171, 2000, -1, 28, 1, 143 }, + { 0x1, 0x1, 172, 1953, -1, 29, 1, 158 }, + { 0x1, 0x1, 172, 1954, -1, 29, 1, 158 }, + { 0x1, 0x1, 172, 1955, -1, 29, 1, 158 }, + { 0x1, 0x1, 172, 1956, -1, 29, 1, 158 }, + { 0x1, 0x1, 172, 1957, -1, 29, 1, 159 }, + { 0x1, 0x1, 172, 1958, -1, 29, 1, 159 }, + { 0x1, 0x1, 172, 1959, -1, 29, 1, 159 }, + { 0x1, 0x1, 172, 1960, -1, 29, 1, 159 }, + { 0x1, 0x1, 172, 1961, -1, 29, 1, 159 }, + { 0x1, 0x1, 172, 1962, -1, 29, 1, 159 }, + { 0x1, 0x1, 172, 1963, -1, 29, 1, 159 }, + { 0x1, 0x1, 172, 1964, -1, 29, 1, 159 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 142 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 142 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 142 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 142 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 141 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 141 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 141 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 141 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 141 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 141 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 141 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 141 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 141 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 141 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 141 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 141 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 141 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 141 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 141 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 141 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 143 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 143 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 143 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 143 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 133 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 134 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 135 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 131 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 131 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 137 }, + { 0x3, 0x3, 173, 271, -1, 28, 1, 137 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 137 }, + { 0x3, 0x3, 173, 2258, -1, 28, 1, 131 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 133 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 134 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 135 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 131 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 131 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 137 }, + { 0x3, 0x3, 173, 273, -1, 28, 1, 137 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 137 }, + { 0x3, 0x3, 173, 2259, -1, 28, 1, 131 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 133 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 134 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 135 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 131 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 131 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 137 }, + { 0x3, 0x3, 173, 275, -1, 28, 1, 137 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 137 }, + { 0x3, 0x3, 173, 2260, -1, 28, 1, 131 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 132 }, + { 0x3, 0x3, 173, 277, -1, 28, 1, 132 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 132 }, + { 0x3, 0x3, 173, 278, -1, 28, 1, 132 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 133 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 134 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 135 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 131 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 131 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 137 }, + { 0x3, 0x3, 173, 279, -1, 28, 1, 137 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 137 }, + { 0x3, 0x3, 173, 2261, -1, 28, 1, 131 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 133 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 134 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 135 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 131 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 131 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 137 }, + { 0x3, 0x3, 173, 281, -1, 28, 1, 137 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 137 }, + { 0x3, 0x3, 173, 2262, -1, 28, 1, 131 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 133 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 134 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 135 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 131 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 131 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 137 }, + { 0x3, 0x3, 173, 283, -1, 28, 1, 137 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 137 }, + { 0x3, 0x3, 173, 2263, -1, 28, 1, 131 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 133 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 134 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 135 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 131 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 131 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 137 }, + { 0x3, 0x3, 173, 285, -1, 28, 1, 137 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 137 }, + { 0x3, 0x3, 173, 2264, -1, 28, 1, 131 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 133 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 134 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 135 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 131 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 131 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 137 }, + { 0x3, 0x3, 173, 287, -1, 28, 1, 137 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 137 }, + { 0x3, 0x3, 173, 2265, -1, 28, 1, 131 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 133 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 134 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 135 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 131 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 131 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 137 }, + { 0x3, 0x3, 173, 289, -1, 28, 1, 137 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 137 }, + { 0x3, 0x3, 173, 2266, -1, 28, 1, 131 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 133 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 134 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 135 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 131 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 131 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 136 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 137 }, + { 0x3, 0x3, 173, 291, -1, 28, 1, 137 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 137 }, + { 0x3, 0x3, 173, 2267, -1, 28, 1, 131 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 133 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 134 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 135 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 131 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 131 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 136 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 137 }, + { 0x3, 0x3, 173, 293, -1, 28, 1, 137 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 137 }, + { 0x3, 0x3, 173, 2268, -1, 28, 1, 131 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 133 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 134 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 135 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 131 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 131 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 136 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 137 }, + { 0x3, 0x3, 173, 295, -1, 28, 1, 137 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 137 }, + { 0x3, 0x3, 173, 2269, -1, 28, 1, 131 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 147 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 152 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 152 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 148 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 149 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 150 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 151 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 151 }, + { 0x3, 0x3, 173, 2270, -1, 28, 1, 147 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 153 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 154 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 155 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 156 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 156 }, + { 0x3, 0x3, 173, 2271, -1, 28, 1, 152 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 153 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 154 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 155 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 156 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 156 }, + { 0x3, 0x3, 173, 2272, -1, 28, 1, 152 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 148 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 149 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 150 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 151 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 151 }, + { 0x3, 0x3, 173, 2273, -1, 28, 1, 147 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 153 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 154 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 155 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 156 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 156 }, + { 0x3, 0x3, 173, 2274, -1, 28, 1, 152 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 153 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 154 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 155 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 156 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 156 }, + { 0x3, 0x3, 173, 2275, -1, 28, 1, 152 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 148 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 149 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 150 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 151 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 151 }, + { 0x3, 0x3, 173, 2276, -1, 28, 1, 147 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 153 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 154 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 155 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 156 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 156 }, + { 0x3, 0x3, 173, 2277, -1, 28, 1, 152 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 153 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 154 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 155 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 156 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 156 }, + { 0x3, 0x3, 173, 2278, -1, 28, 1, 152 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 148 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 149 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 150 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 151 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 151 }, + { 0x3, 0x3, 173, 2279, -1, 28, 1, 147 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 153 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 154 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 155 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 156 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 156 }, + { 0x3, 0x3, 173, 2280, -1, 28, 1, 152 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 148 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 149 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 150 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 151 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 151 }, + { 0x3, 0x3, 173, 2281, -1, 28, 1, 147 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 153 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 154 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 155 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 156 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 156 }, + { 0x3, 0x3, 173, 2282, -1, 28, 1, 152 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 148 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 149 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 150 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 151 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 151 }, + { 0x3, 0x3, 173, 2283, -1, 28, 1, 147 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 153 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 154 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 155 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 156 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 156 }, + { 0x3, 0x3, 173, 2284, -1, 28, 1, 152 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 148 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 149 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 150 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 151 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 151 }, + { 0x3, 0x3, 173, 2285, -1, 28, 1, 147 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 153 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 154 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 155 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 156 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 156 }, + { 0x3, 0x3, 173, 2286, -1, 28, 1, 152 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 153 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 154 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 155 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 156 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 156 }, + { 0x3, 0x3, 173, 2287, -1, 28, 1, 152 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 158 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 158 }, + { 0x3, 0x3, 173, 951, -1, 28, 1, 158 }, + { 0x3, 0x3, 173, 952, -1, 28, 1, 158 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 159 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 159 }, + { 0x3, 0x3, 173, 953, -1, 28, 1, 159 }, + { 0x3, 0x3, 173, 954, -1, 28, 1, 159 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 159 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 159 }, + { 0x3, 0x3, 173, 955, -1, 28, 1, 159 }, + { 0x3, 0x3, 173, 956, -1, 28, 1, 159 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 138 }, + { 0x3, 0x3, 173, 2224, -1, 28, 1, 138 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 145 }, + { 0x3, 0x3, 173, 2225, -1, 28, 1, 145 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 139 }, + { 0x3, 0x3, 173, 2226, -1, 28, 1, 139 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 139 }, + { 0x3, 0x3, 173, 2227, -1, 28, 1, 139 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 138 }, + { 0x3, 0x3, 173, 2228, -1, 28, 1, 138 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 145 }, + { 0x3, 0x3, 173, 2229, -1, 28, 1, 145 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 138 }, + { 0x3, 0x3, 173, 2230, -1, 28, 1, 138 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 145 }, + { 0x3, 0x3, 173, 2231, -1, 28, 1, 145 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 138 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 140 }, + { 0x3, 0x3, 173, 2232, -1, 28, 1, 138 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 145 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 146 }, + { 0x3, 0x3, 173, 2233, -1, 28, 1, 145 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 157 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 161 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 157 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 161 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 157 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 161 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 157 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 161 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 157 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 161 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 143 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 143 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 143 }, + { 0x3, 0x3, 173, -1, -1, 28, 1, 143 }, + { 0x0, 0x0, 174, -1, 394, 0, 0, -1 }, + { 0x0, 0x0, 174, -1, 396, 0, 0, -1 }, + { 0x0, 0x0, 174, 3042, 3002, 0, 1, 1 }, + { 0x0, 0x0, 174, 3043, 3003, 0, 1, 1 }, + { 0x0, 0x0, 174, -1, 402, 0, 0, -1 }, + { 0x0, 0x0, 174, -1, 404, 0, 0, -1 }, + { 0x0, 0x0, 174, 3046, 3006, 0, 1, 76 }, + { 0x0, 0x0, 174, 3047, 3007, 0, 1, 76 }, + { 0x0, 0x0, 174, -1, 410, 0, 0, -1 }, + { 0x0, 0x0, 174, -1, 412, 0, 0, -1 }, + { 0x0, 0x0, 174, 3050, 3010, 0, 1, 1 }, + { 0x0, 0x0, 174, 3051, 3011, 0, 1, 1 }, + { 0x11, 0x31, 175, 2881, 417, 33, 1, 4 }, + { 0x2200001, 0x2200001, 175, -1, 418, 12, 1, 4 }, + { 0x11, 0x31, 175, 2073, 419, 33, 1, 4 }, + { 0x2200001, 0x2200001, 175, -1, 421, 12, 1, 4 }, + { 0x1, 0x1, 175, -1, 425, 37, 1, 4 }, + { 0x2000001, 0x2000001, 175, -1, 426, 12, 1, 4 }, + { 0x11, 0x11, 175, -1, 427, 33, 1, 4 }, + { 0x2200001, 0x2200001, 175, -1, 428, 12, 1, 4 }, + { 0x1, 0x1, 175, 2079, 429, 37, 1, 4 }, + { 0x2000001, 0x2000001, 175, -1, 431, 12, 1, 4 }, + { 0x11, 0x11, 175, 2081, 433, 33, 1, 4 }, + { 0x2200001, 0x2200001, 175, -1, 435, 12, 1, 4 }, + { 0x1, 0x1, 175, 2083, 437, 37, 1, 4 }, + { 0x2000001, 0x2000001, 175, -1, 439, 12, 1, 4 }, + { 0x11, 0x11, 175, 2085, 441, 33, 1, 4 }, + { 0x2200001, 0x2200001, 175, -1, 443, 12, 1, 4 }, + { 0x1, 0x1, 175, 2087, 445, 37, 1, 4 }, + { 0x2000001, 0x2000001, 175, -1, 447, 12, 1, 4 }, + { 0x11, 0x11, 175, 2089, 449, 33, 1, 4 }, + { 0x2200001, 0x2200001, 175, -1, 451, 12, 1, 4 }, + { 0x11, 0x31, 175, 2901, 457, 33, 1, 4 }, + { 0x2200001, 0x2200001, 175, -1, 458, 12, 1, 4 }, + { 0x11, 0x31, 175, 2095, 459, 33, 1, 4 }, + { 0x2200001, 0x2200001, 175, -1, 461, 12, 1, 4 }, + { 0x11, 0x31, 175, 2921, 465, 33, 1, 4 }, + { 0x2200001, 0x2200001, 175, -1, 466, 12, 1, 4 }, + { 0x11, 0x31, 175, 2121, 467, 33, 1, 4 }, + { 0x2200001, 0x2200001, 175, -1, 469, 12, 1, 4 }, + { 0x1, 0x1, 175, -1, 473, 37, 1, 4 }, + { 0x2000001, 0x2000001, 175, -1, 474, 12, 1, 4 }, + { 0x11, 0x11, 175, -1, 475, 33, 1, 4 }, + { 0x2200001, 0x2200001, 175, -1, 476, 12, 1, 4 }, + { 0x1, 0x1, 175, 2127, 477, 37, 1, 4 }, + { 0x2000001, 0x2000001, 175, -1, 479, 12, 1, 4 }, + { 0x11, 0x11, 175, 2129, 481, 33, 1, 4 }, + { 0x2200001, 0x2200001, 175, -1, 483, 12, 1, 4 }, + { 0x1, 0x1, 175, 2131, 485, 37, 1, 4 }, + { 0x2000001, 0x2000001, 175, -1, 487, 12, 1, 4 }, + { 0x11, 0x11, 175, 2133, 489, 33, 1, 4 }, + { 0x2200001, 0x2200001, 175, -1, 491, 12, 1, 4 }, + { 0x1, 0x1, 175, 2135, 493, 37, 1, 4 }, + { 0x2000001, 0x2000001, 175, -1, 495, 12, 1, 4 }, + { 0x11, 0x11, 175, 2137, 497, 33, 1, 4 }, + { 0x2200001, 0x2200001, 175, -1, 499, 12, 1, 4 }, + { 0x11, 0x31, 175, 2941, 505, 33, 1, 4 }, + { 0x2200001, 0x2200001, 175, -1, 506, 12, 1, 4 }, + { 0x11, 0x31, 175, 2143, 507, 33, 1, 4 }, + { 0x2200001, 0x2200001, 175, -1, 509, 12, 1, 4 }, + { 0x1, 0x1, 175, -1, 513, 33, 1, 4 }, + { 0x200001, 0x200001, 175, -1, 514, 12, 1, 4 }, + { 0x1, 0x1, 175, -1, 515, 33, 1, 4 }, + { 0x200001, 0x200001, 175, -1, 516, 12, 1, 4 }, + { 0x1, 0x1, 175, -1, 521, 33, 1, 79 }, + { 0x200001, 0x200001, 175, -1, 522, 12, 1, 79 }, + { 0x1, 0x1, 175, -1, 523, 33, 1, 79 }, + { 0x200001, 0x200001, 175, -1, 524, 12, 1, 79 }, + { 0x1, 0x1, 175, -1, 529, 33, 1, 4 }, + { 0x200001, 0x200001, 175, -1, 530, 12, 1, 4 }, + { 0x1, 0x1, 175, -1, 531, 33, 1, 4 }, + { 0x200001, 0x200001, 175, -1, 532, 12, 1, 4 }, + { 0x2200001, 0x6200001, 176, 2884, -1, 12, 1, 4 }, + { 0x11, 0x11, 176, 2016, -1, 33, 1, 4 }, + { 0x1, 0x1, 176, -1, -1, 33, 1, 5 }, + { 0x4200001, 0x4200001, 176, -1, -1, 12, 1, 5 }, + { 0x1, 0x1, 176, -1, -1, 37, 1, 4 }, + { 0x2000001, 0x2000001, 176, -1, -1, 12, 1, 4 }, + { 0x2000001, 0x2000001, 176, -1, -1, 12, 1, 4 }, + { 0x1, 0x1, 176, 2022, -1, 37, 1, 4 }, + { 0x2200001, 0x2200001, 176, -1, -1, 12, 1, 4 }, + { 0x11, 0x11, 176, 2024, -1, 33, 1, 4 }, + { 0x2000001, 0x2000001, 176, -1, -1, 12, 1, 4 }, + { 0x1, 0x1, 176, 2026, -1, 37, 1, 4 }, + { 0x2200001, 0x2200001, 176, -1, -1, 12, 1, 4 }, + { 0x11, 0x11, 176, 2028, -1, 33, 1, 4 }, + { 0x2000001, 0x2000001, 176, -1, -1, 12, 1, 4 }, + { 0x1, 0x1, 176, 2030, -1, 37, 1, 4 }, + { 0x2200001, 0x2200001, 176, -1, -1, 12, 1, 4 }, + { 0x11, 0x11, 176, 2032, -1, 33, 1, 4 }, + { 0x1, 0x1, 176, -1, -1, 37, 1, 4 }, + { 0x2000001, 0x2000001, 176, -1, -1, 12, 1, 4 }, + { 0x11, 0x11, 176, -1, -1, 33, 1, 4 }, + { 0x2200001, 0x2200001, 176, -1, -1, 12, 1, 4 }, + { 0x2200001, 0x6200001, 176, 2904, -1, 12, 1, 4 }, + { 0x11, 0x11, 176, 2036, -1, 33, 1, 4 }, + { 0x1, 0x1, 176, -1, -1, 33, 1, 5 }, + { 0x4200001, 0x4200001, 176, -1, -1, 12, 1, 5 }, + { 0x1, 0x1, 176, -1, -1, 37, 1, 4 }, + { 0x2000001, 0x2000001, 176, -1, -1, 12, 1, 4 }, + { 0x0, 0x0, 176, -1, -1, 0, 1, 5 }, + { 0x1, 0x1, 176, -1, -1, 12, 1, 5 }, + { 0x0, 0x0, 176, -1, -1, 0, 1, 5 }, + { 0x1, 0x1, 176, -1, -1, 12, 1, 5 }, + { 0x1, 0x1, 176, -1, -1, 33, 1, 5 }, + { 0x200001, 0x200001, 176, -1, -1, 12, 1, 5 }, + { 0x0, 0x0, 176, -1, -1, 0, 1, 5 }, + { 0x1, 0x1, 176, -1, -1, 12, 1, 5 }, + { 0x1, 0x1, 176, -1, -1, 33, 1, 5 }, + { 0x200001, 0x200001, 176, -1, -1, 12, 1, 5 }, + { 0x0, 0x0, 176, -1, -1, 0, 1, 5 }, + { 0x1, 0x1, 176, -1, -1, 12, 1, 5 }, + { 0x1, 0x1, 176, -1, -1, 33, 1, 5 }, + { 0x200001, 0x200001, 176, -1, -1, 12, 1, 5 }, + { 0x0, 0x0, 176, -1, -1, 0, 1, 5 }, + { 0x1, 0x1, 176, -1, -1, 12, 1, 5 }, + { 0x1, 0x1, 176, -1, -1, 33, 1, 5 }, + { 0x200001, 0x200001, 176, -1, -1, 12, 1, 5 }, + { 0x0, 0x0, 176, -1, -1, 0, 1, 5 }, + { 0x1, 0x1, 176, -1, -1, 12, 1, 5 }, + { 0x2200001, 0x6200001, 176, 2924, -1, 12, 1, 4 }, + { 0x11, 0x11, 176, 2040, -1, 33, 1, 4 }, + { 0x1, 0x1, 176, -1, -1, 33, 1, 5 }, + { 0x4200001, 0x4200001, 176, -1, -1, 12, 1, 5 }, + { 0x1, 0x1, 176, -1, -1, 37, 1, 4 }, + { 0x2000001, 0x2000001, 176, -1, -1, 12, 1, 4 }, + { 0x2000001, 0x2000001, 176, -1, -1, 12, 1, 4 }, + { 0x1, 0x1, 176, 2046, -1, 37, 1, 4 }, + { 0x2200001, 0x2200001, 176, -1, -1, 12, 1, 4 }, + { 0x11, 0x11, 176, 2048, -1, 33, 1, 4 }, + { 0x2000001, 0x2000001, 176, -1, -1, 12, 1, 4 }, + { 0x1, 0x1, 176, 2050, -1, 37, 1, 4 }, + { 0x2200001, 0x2200001, 176, -1, -1, 12, 1, 4 }, + { 0x11, 0x11, 176, 2052, -1, 33, 1, 4 }, + { 0x2000001, 0x2000001, 176, -1, -1, 12, 1, 4 }, + { 0x1, 0x1, 176, 2054, -1, 37, 1, 4 }, + { 0x2200001, 0x2200001, 176, -1, -1, 12, 1, 4 }, + { 0x11, 0x11, 176, 2056, -1, 33, 1, 4 }, + { 0x1, 0x1, 176, -1, -1, 37, 1, 4 }, + { 0x2000001, 0x2000001, 176, -1, -1, 12, 1, 4 }, + { 0x11, 0x11, 176, -1, -1, 33, 1, 4 }, + { 0x2200001, 0x2200001, 176, -1, -1, 12, 1, 4 }, + { 0x2200001, 0x6200001, 176, 2943, -1, 12, 1, 4 }, + { 0x11, 0x11, 176, 2060, -1, 33, 1, 4 }, + { 0x1, 0x1, 176, -1, -1, 33, 1, 5 }, + { 0x4200001, 0x4200001, 176, -1, -1, 12, 1, 5 }, + { 0x1, 0x1, 176, -1, -1, 37, 1, 4 }, + { 0x2000001, 0x2000001, 176, -1, -1, 12, 1, 4 }, + { 0x0, 0x0, 176, -1, -1, 0, 1, 5 }, + { 0x1, 0x1, 176, -1, -1, 12, 1, 5 }, + { 0x0, 0x0, 176, -1, -1, 0, 1, 5 }, + { 0x1, 0x1, 176, -1, -1, 12, 1, 5 }, + { 0x1, 0x1, 176, -1, -1, 33, 1, 5 }, + { 0x200001, 0x200001, 176, -1, -1, 12, 1, 5 }, + { 0x0, 0x0, 176, -1, -1, 0, 1, 5 }, + { 0x1, 0x1, 176, -1, -1, 12, 1, 5 }, + { 0x1, 0x1, 176, -1, -1, 33, 1, 5 }, + { 0x200001, 0x200001, 176, -1, -1, 12, 1, 5 }, + { 0x0, 0x0, 176, -1, -1, 0, 1, 5 }, + { 0x1, 0x1, 176, -1, -1, 12, 1, 5 }, + { 0x1, 0x1, 176, -1, -1, 33, 1, 5 }, + { 0x200001, 0x200001, 176, -1, -1, 12, 1, 5 }, + { 0x0, 0x0, 176, -1, -1, 0, 1, 5 }, + { 0x1, 0x1, 176, -1, -1, 12, 1, 5 }, + { 0x1, 0x1, 176, -1, -1, 33, 1, 5 }, + { 0x200001, 0x200001, 176, -1, -1, 12, 1, 5 }, + { 0x0, 0x0, 176, -1, -1, 0, 1, 5 }, + { 0x1, 0x1, 176, -1, -1, 12, 1, 5 }, + { 0x9, 0x9, 176, -1, -1, 33, 1, 5 }, + { 0x1, 0x1, 176, 397, -1, 33, 1, 4 }, + { 0x1200001, 0x1200001, 176, -1, -1, 12, 1, 5 }, + { 0x200001, 0x200001, 176, 398, -1, 12, 1, 4 }, + { 0x9, 0x9, 176, -1, -1, 33, 1, 5 }, + { 0x1, 0x1, 176, 399, -1, 33, 1, 4 }, + { 0x1200001, 0x1200001, 176, -1, -1, 12, 1, 5 }, + { 0x200001, 0x200001, 176, 400, -1, 12, 1, 4 }, + { 0x9, 0x9, 176, -1, -1, 33, 1, 80 }, + { 0x1, 0x1, 176, 405, -1, 33, 1, 79 }, + { 0x1200001, 0x1200001, 176, -1, -1, 12, 1, 80 }, + { 0x200001, 0x200001, 176, 406, -1, 12, 1, 79 }, + { 0x9, 0x9, 176, -1, -1, 33, 1, 80 }, + { 0x1, 0x1, 176, 407, -1, 33, 1, 79 }, + { 0x1200001, 0x1200001, 176, -1, -1, 12, 1, 80 }, + { 0x200001, 0x200001, 176, 408, -1, 12, 1, 79 }, + { 0x9, 0x9, 176, -1, -1, 33, 1, 5 }, + { 0x1, 0x1, 176, 413, -1, 33, 1, 4 }, + { 0x1200001, 0x1200001, 176, -1, -1, 12, 1, 5 }, + { 0x200001, 0x200001, 176, 414, -1, 12, 1, 4 }, + { 0x9, 0x9, 176, -1, -1, 33, 1, 5 }, + { 0x1, 0x1, 176, 415, -1, 33, 1, 4 }, + { 0x1200001, 0x1200001, 176, -1, -1, 12, 1, 5 }, + { 0x200001, 0x200001, 176, 416, -1, 12, 1, 4 }, + { 0x0, 0x0, 177, -1, 2327, 0, 0, -1 }, + { 0x9, 0x9, 177, -1, 2335, 33, 1, 50 }, + { 0x9, 0x9, 177, -1, 2993, 33, 1, 50 }, + { 0x0, 0x0, 177, -1, 2380, 0, 0, -1 }, + { 0x7, 0x7, 177, -1, -1, 27, 1, 50 }, + { 0x1, 0x1, 197, -1, -1, 27, 1, 10 }, + { 0x1, 0x1, 211, -1, -1, 29, 1, 0 }, + { 0x1, 0x1, 211, -1, -1, 29, 1, 0 }, + { 0x2, 0x3, 211, 1169, -1, 27, 1, 34 }, + { 0x0, 0x0, 211, 1170, -1, 0, 1, 34 }, + { 0x0, 0x0, 211, 1171, -1, 0, 1, 0 }, + { 0x0, 0x0, 211, 1172, -1, 0, 1, 0 }, + { 0x0, 0x0, 211, 1173, -1, 0, 1, 0 }, + { 0x0, 0x0, 211, 1174, -1, 0, 1, 0 }, + { 0x0, 0x0, 211, 3026, -1, 0, 1, 100 }, + { 0x0, 0x0, 211, 3027, -1, 0, 1, 100 }, + { 0x0, 0x0, 211, 3028, 967, 0, 0, -1 }, + { 0x1, 0x1, 212, -1, -1, 27, 1, 0 }, + { 0x1, 0x1, 212, -1, -1, 27, 1, 0 }, + { 0x1, 0x1, 213, -1, 1426, 32, 1, 142 }, + { 0x1, 0x1, 213, -1, 1428, 32, 1, 142 }, + { 0x1, 0x1, 213, -1, 1430, 32, 1, 141 }, + { 0x1, 0x1, 213, -1, 1432, 32, 1, 141 }, + { 0x1, 0x1, 213, -1, 1434, 32, 1, 141 }, + { 0x1, 0x1, 213, -1, 1436, 32, 1, 141 }, + { 0x1, 0x1, 213, -1, 1438, 32, 1, 141 }, + { 0x1, 0x1, 213, -1, 1440, 32, 1, 141 }, + { 0x1, 0x1, 213, -1, 1442, 32, 1, 141 }, + { 0x1, 0x1, 213, -1, 1444, 32, 1, 141 }, + { 0x1, 0x1, 213, -1, 1446, 32, 1, 143 }, + { 0x1, 0x1, 213, -1, 1448, 32, 1, 143 }, + { 0x1, 0x1, 213, -1, 1965, 32, 1, 138 }, + { 0x1, 0x1, 213, -1, 1967, 32, 1, 145 }, + { 0x1, 0x1, 213, -1, 1969, 32, 1, 139 }, + { 0x1, 0x1, 213, -1, 1971, 32, 1, 139 }, + { 0x1, 0x1, 213, -1, 1973, 32, 1, 138 }, + { 0x1, 0x1, 213, -1, 1975, 32, 1, 145 }, + { 0x1, 0x1, 213, -1, 1977, 32, 1, 138 }, + { 0x1, 0x1, 213, -1, 1979, 32, 1, 145 }, + { 0x1, 0x1, 213, 2783, 1981, 32, 1, 138 }, + { 0x1, 0x1, 213, 2784, 1984, 32, 1, 145 }, + { 0x0, 0x0, 214, -1, 2825, 0, 0, -1 }, + { 0x0, 0x0, 214, -1, 2826, 0, 0, -1 }, + { 0x0, 0x0, 214, -1, 2851, 0, 0, -1 }, + { 0x5, 0x5, 214, -1, 2854, 20, 1, 68 }, + { 0x0, 0x0, 218, 2209, 966, 0, 0, -1 }, + { 0x0, 0x0, 219, -1, 1139, 0, 0, -1 }, + { 0x0, 0x0, 219, -1, 1264, 0, 0, -1 }, + { 0x0, 0x0, 219, -1, -1, 0, 1, 128 }, + { 0x0, 0x0, 219, -1, -1, 0, 1, 67 }, + { 0x1, 0x1, 219, 833, 2289, 36, 1, 66 }, + { 0x1, 0x1, 219, 834, 2348, 36, 1, 66 }, + { 0x0, 0x0, 219, 835, 2351, 0, 0, -1 }, + { 0x1, 0x1, 219, 836, -1, 36, 1, 66 }, + { 0x0, 0x0, 219, 1423, -1, 0, 1, 34 }, + { 0x1, 0x1, 219, 837, 2356, 36, 1, 66 }, + { 0x0, 0x0, 219, 838, 2359, 0, 0, -1 }, + { 0x1, 0x1, 219, 839, -1, 36, 1, 66 }, + { 0x0, 0x0, 219, 840, 2362, 0, 0, -1 }, + { 0x1, 0x1, 219, 841, -1, 36, 1, 66 }, + { 0x1, 0x1, 219, 842, 2365, 36, 1, 66 }, + { 0x1, 0x1, 219, 843, 2368, 36, 1, 66 }, + { 0x0, 0x0, 219, 1424, -1, 0, 1, 34 }, + { 0x1, 0x1, 219, 844, 2401, 36, 1, 66 }, + { 0x1, 0x1, 219, 845, -1, 31, 1, 144 }, + { 0x1, 0x1, 219, 228, 1449, 32, 1, 133 }, + { 0x1, 0x1, 219, 229, 1458, 32, 1, 133 }, + { 0x1, 0x1, 219, 230, 1467, 32, 1, 133 }, + { 0x1, 0x1, 219, 231, 1480, 32, 1, 133 }, + { 0x1, 0x1, 219, 232, 1489, 32, 1, 133 }, + { 0x1, 0x1, 219, 233, 1498, 32, 1, 133 }, + { 0x1, 0x1, 219, 234, 1507, 32, 1, 133 }, + { 0x1, 0x1, 219, 235, 1516, 32, 1, 133 }, + { 0x1, 0x1, 219, 236, 1525, 32, 1, 133 }, + { 0x1, 0x1, 219, 237, 1534, 32, 1, 133 }, + { 0x1, 0x1, 219, 238, 1544, 32, 1, 133 }, + { 0x1, 0x1, 219, 239, 1554, 32, 1, 133 }, + { 0x1, 0x1, 219, 240, 1567, 32, 1, 148 }, + { 0x1, 0x1, 219, 241, 1573, 32, 1, 153 }, + { 0x1, 0x1, 219, 242, 1579, 32, 1, 153 }, + { 0x1, 0x1, 219, 243, 1585, 32, 1, 148 }, + { 0x1, 0x1, 219, 244, 1591, 32, 1, 153 }, + { 0x1, 0x1, 219, 245, 1597, 32, 1, 153 }, + { 0x1, 0x1, 219, 246, 1603, 32, 1, 148 }, + { 0x1, 0x1, 219, 247, 1609, 32, 1, 153 }, + { 0x1, 0x1, 219, 248, 1615, 32, 1, 153 }, + { 0x1, 0x1, 219, 249, 1621, 32, 1, 148 }, + { 0x1, 0x1, 219, 250, 1627, 32, 1, 153 }, + { 0x1, 0x1, 219, 251, 1633, 32, 1, 148 }, + { 0x1, 0x1, 219, 252, 1639, 32, 1, 153 }, + { 0x1, 0x1, 219, 253, 1645, 32, 1, 148 }, + { 0x1, 0x1, 219, 254, 1651, 32, 1, 153 }, + { 0x1, 0x1, 219, 255, 1657, 32, 1, 148 }, + { 0x1, 0x1, 219, 256, 1663, 32, 1, 153 }, + { 0x1, 0x1, 219, 257, 1669, 32, 1, 153 }, + { 0x1, 0x1, 219, 849, -1, 31, 1, 160 }, + { 0x0, 0x0, 220, 2404, -1, 0, 1, 66 }, + { 0x0, 0x0, 220, 2405, -1, 0, 1, 29 }, + { 0x0, 0x0, 220, 25, -1, 0, 1, 29 }, + { 0x0, 0x0, 220, 2407, -1, 0, 1, 29 }, + { 0x0, 0x0, 220, 2408, -1, 0, 1, 29 }, + { 0x0, 0x0, 220, 2409, -1, 0, 1, 45 }, + { 0x0, 0x0, 220, 2410, -1, 0, 1, 40 }, + { 0x1, 0x1, 220, 2411, -1, 12, 1, 59 }, + { 0x0, 0x0, 220, 2412, -1, 0, 1, 54 }, + { 0x1000001, 0x1000001, 220, 2413, -1, 12, 1, 59 }, + { 0x1, 0x1, 220, 2414, -1, 36, 1, 54 }, + { 0x200001, 0x200001, 220, 2415, -1, 12, 1, 59 }, + { 0x1, 0x1, 220, 2416, -1, 33, 1, 54 }, + { 0x1200001, 0x1200001, 220, 2417, -1, 12, 1, 49 }, + { 0x9, 0x9, 220, 2418, -1, 33, 1, 49 }, + { 0x0, 0x0, 220, 2419, -1, 0, 1, 59 }, + { 0x0, 0x0, 220, 2420, -1, 0, 1, 54 }, + { 0x0, 0x0, 220, 2421, -1, 0, 1, 59 }, + { 0x0, 0x0, 220, 2422, -1, 0, 1, 54 }, + { 0x0, 0x0, 220, 2423, -1, 0, 1, 59 }, + { 0x0, 0x0, 220, 2424, -1, 0, 1, 54 }, + { 0x0, 0x0, 220, 2425, -1, 0, 1, 49 }, + { 0x0, 0x0, 220, 2426, -1, 0, 1, 49 }, + { 0x1, 0x1, 220, 2427, -1, 12, 1, 59 }, + { 0x0, 0x0, 220, 2428, -1, 0, 1, 54 }, + { 0x200001, 0x1200001, 220, 2429, -1, 12, 1, 59 }, + { 0x1, 0x9, 220, 2430, -1, 33, 1, 54 }, + { 0x0, 0x0, 220, 2431, -1, 0, 1, 59 }, + { 0x0, 0x0, 220, 2432, -1, 0, 1, 54 }, + { 0x0, 0x0, 220, 2433, -1, 0, 1, 59 }, + { 0x0, 0x0, 220, 2434, -1, 0, 1, 54 }, + { 0x1, 0x1, 220, 2435, -1, 12, 1, 59 }, + { 0x0, 0x0, 220, 2436, -1, 0, 1, 54 }, + { 0x1000001, 0x1000001, 220, 2437, -1, 12, 1, 59 }, + { 0x1, 0x1, 220, 2438, -1, 36, 1, 54 }, + { 0x200001, 0x200001, 220, 2439, -1, 12, 1, 59 }, + { 0x1, 0x1, 220, 2440, -1, 33, 1, 54 }, + { 0x1200001, 0x1200001, 220, 2441, -1, 12, 1, 49 }, + { 0x9, 0x9, 220, 2442, -1, 33, 1, 49 }, + { 0x0, 0x0, 220, 2443, -1, 0, 1, 59 }, + { 0x0, 0x0, 220, 2444, -1, 0, 1, 54 }, + { 0x0, 0x0, 220, 2445, -1, 0, 1, 59 }, + { 0x0, 0x0, 220, 2446, -1, 0, 1, 54 }, + { 0x0, 0x0, 220, 2447, -1, 0, 1, 59 }, + { 0x0, 0x0, 220, 2448, -1, 0, 1, 54 }, + { 0x0, 0x0, 220, 2449, -1, 0, 1, 49 }, + { 0x0, 0x0, 220, 2450, -1, 0, 1, 49 }, + { 0x1, 0x1, 220, 2451, -1, 12, 1, 59 }, + { 0x0, 0x0, 220, 2452, -1, 0, 1, 54 }, + { 0x200001, 0x1200001, 220, 2453, -1, 12, 1, 59 }, + { 0x1, 0x9, 220, 2454, -1, 33, 1, 54 }, + { 0x0, 0x0, 220, 2455, -1, 0, 1, 59 }, + { 0x0, 0x0, 220, 2456, -1, 0, 1, 54 }, + { 0x0, 0x0, 220, 2457, -1, 0, 1, 59 }, + { 0x0, 0x0, 220, 2458, -1, 0, 1, 54 }, + { 0x1, 0x1, 220, 2459, -1, 28, 1, 29 }, + { 0x0, 0x0, 220, 2460, -1, 0, 1, 29 }, + { 0x3, 0x3, 220, 2461, -1, 27, 1, 29 }, + { 0x1, 0x1, 220, 2462, -1, 27, 1, 29 }, + { 0x0, 0x0, 220, 2463, -1, 0, 1, 66 }, + { 0x0, 0x0, 220, 2464, -1, 0, 1, 29 }, + { 0x0, 0x0, 220, 2465, -1, 0, 1, 29 }, + { 0x1, 0x1, 220, 2466, -1, 36, 1, 66 }, + { 0x1, 0x1, 220, 2467, -1, 37, 1, 29 }, + { 0x0, 0x0, 220, 2468, -1, 0, 1, 29 }, + { 0x0, 0x0, 220, 2469, -1, 0, 1, 29 }, + { 0x0, 0x0, 220, 2470, -1, 0, 1, 29 }, + { 0x0, 0x0, 220, 2471, -1, 0, 1, 66 }, + { 0x0, 0x0, 220, 2472, -1, 0, 1, 29 }, + { 0x0, 0x0, 220, 37, -1, 0, 1, 29 }, + { 0x1, 0x1, 220, 2474, -1, 36, 1, 66 }, + { 0x1, 0x1, 220, 2475, -1, 37, 1, 29 }, + { 0x0, 0x0, 220, 2476, -1, 0, 1, 29 }, + { 0x1, 0x1, 220, 2477, -1, 36, 1, 66 }, + { 0x1, 0x1, 220, 2478, -1, 37, 1, 29 }, + { 0x0, 0x0, 220, 2479, -1, 0, 1, 29 }, + { 0x0, 0x0, 220, 2480, -1, 0, 1, 66 }, + { 0x0, 0x0, 220, 2481, -1, 0, 1, 29 }, + { 0x0, 0x0, 220, 42, -1, 0, 1, 29 }, + { 0x0, 0x0, 220, 2483, -1, 0, 1, 66 }, + { 0x0, 0x0, 220, 2484, -1, 0, 1, 29 }, + { 0x0, 0x0, 220, 43, -1, 0, 1, 29 }, + { 0x0, 0x0, 220, 2486, -1, 0, 1, 29 }, + { 0x0, 0x0, 220, 2487, -1, 0, 1, 29 }, + { 0x0, 0x0, 220, 2488, -1, 0, 1, 49 }, + { 0x1, 0x1, 220, 2489, -1, 27, 1, 49 }, + { 0x1, 0x1, 220, 2490, -1, 28, 1, 49 }, + { 0x3, 0x3, 220, 2491, -1, 27, 1, 49 }, + { 0x1, 0x1, 220, 2492, -1, 29, 1, 49 }, + { 0x5, 0x5, 220, 2493, -1, 27, 1, 49 }, + { 0x3, 0x3, 220, 2494, -1, 28, 1, 49 }, + { 0x7, 0x7, 220, 2495, -1, 27, 1, 49 }, + { 0x0, 0x0, 220, 2496, -1, 0, 1, 49 }, + { 0x0, 0x0, 220, 2497, -1, 0, 1, 49 }, + { 0x0, 0x0, 220, 2498, -1, 0, 1, 49 }, + { 0x0, 0x0, 220, 2499, -1, 0, 1, 49 }, + { 0x1, 0x1, 220, 2500, -1, 28, 1, 29 }, + { 0x0, 0x0, 220, 2501, -1, 0, 1, 29 }, + { 0x3, 0x3, 220, 2502, -1, 27, 1, 29 }, + { 0x1, 0x1, 220, 2503, -1, 27, 1, 29 }, + { 0x0, 0x0, 220, 2504, -1, 0, 1, 29 }, + { 0x0, 0x0, 220, 2505, -1, 0, 1, 29 }, + { 0x0, 0x0, 220, 2506, -1, 0, 1, 29 }, + { 0x0, 0x0, 220, 52, -1, 0, 1, 29 }, + { 0x0, 0x0, 220, 2508, -1, 0, 1, 29 }, + { 0x0, 0x0, 220, 2509, -1, 0, 1, 29 }, + { 0x0, 0x0, 220, 57, -1, 0, 1, 29 }, + { 0x0, 0x0, 220, 2511, -1, 0, 1, 24 }, + { 0x0, 0x0, 220, 2512, -1, 0, 1, 24 }, + { 0x0, 0x0, 220, 2513, -1, 0, 1, 24 }, + { 0x0, 0x0, 220, 2514, -1, 0, 1, 24 }, + { 0x0, 0x0, 220, 2515, -1, 0, 1, 35 }, + { 0x0, 0x0, 220, 2516, -1, 0, 1, 66 }, + { 0x0, 0x0, 220, 2517, -1, 0, 1, 29 }, + { 0x0, 0x0, 220, 64, -1, 0, 1, 29 }, + { 0x1, 0x1, 221, 2519, -1, 34, 1, 66 }, + { 0x1, 0x1, 221, 2520, -1, 34, 1, 31 }, + { 0x1, 0x1, 221, 2521, -1, 34, 1, 31 }, + { 0x1, 0x1, 221, 2522, -1, 34, 1, 31 }, + { 0x1, 0x1, 221, 2523, -1, 34, 1, 31 }, + { 0x1, 0x1, 221, 2524, -1, 34, 1, 46 }, + { 0x1, 0x1, 221, 2525, -1, 34, 1, 42 }, + { 0x400001, 0x400001, 221, 2526, -1, 12, 1, 61 }, + { 0x1, 0x1, 221, 2527, -1, 34, 1, 56 }, + { 0x1400001, 0x1400001, 221, 2528, -1, 12, 1, 61 }, + { 0x5, 0x5, 221, 2529, -1, 34, 1, 56 }, + { 0x600001, 0x600001, 221, 2530, -1, 12, 1, 61 }, + { 0x3, 0x3, 221, 2531, -1, 33, 1, 56 }, + { 0x1600001, 0x1600001, 221, 2532, -1, 12, 1, 51 }, + { 0xb, 0xb, 221, 2533, -1, 33, 1, 51 }, + { 0x1, 0x1, 221, 2534, -1, 34, 1, 61 }, + { 0x1, 0x1, 221, 2535, -1, 34, 1, 56 }, + { 0x1, 0x1, 221, 2536, -1, 34, 1, 61 }, + { 0x1, 0x1, 221, 2537, -1, 34, 1, 56 }, + { 0x1, 0x1, 221, 2538, -1, 34, 1, 61 }, + { 0x1, 0x1, 221, 2539, -1, 34, 1, 56 }, + { 0x1, 0x1, 221, 2540, -1, 34, 1, 51 }, + { 0x1, 0x1, 221, 2541, -1, 34, 1, 51 }, + { 0x400001, 0x400001, 221, 2542, -1, 12, 1, 61 }, + { 0x1, 0x1, 221, 2543, -1, 34, 1, 56 }, + { 0x600001, 0x1600001, 221, 2544, -1, 12, 1, 61 }, + { 0x3, 0xb, 221, 2545, -1, 33, 1, 56 }, + { 0x1, 0x1, 221, 2546, -1, 34, 1, 61 }, + { 0x1, 0x1, 221, 2547, -1, 34, 1, 56 }, + { 0x1, 0x1, 221, 2548, -1, 34, 1, 61 }, + { 0x1, 0x1, 221, 2549, -1, 34, 1, 56 }, + { 0x400001, 0x400001, 221, 2550, -1, 12, 1, 61 }, + { 0x1, 0x1, 221, 2551, -1, 34, 1, 56 }, + { 0x1400001, 0x1400001, 221, 2552, -1, 12, 1, 61 }, + { 0x5, 0x5, 221, 2553, -1, 34, 1, 56 }, + { 0x600001, 0x600001, 221, 2554, -1, 12, 1, 61 }, + { 0x3, 0x3, 221, 2555, -1, 33, 1, 56 }, + { 0x1600001, 0x1600001, 221, 2556, -1, 12, 1, 51 }, + { 0xb, 0xb, 221, 2557, -1, 33, 1, 51 }, + { 0x1, 0x1, 221, 2558, -1, 34, 1, 61 }, + { 0x1, 0x1, 221, 2559, -1, 34, 1, 56 }, + { 0x1, 0x1, 221, 2560, -1, 34, 1, 61 }, + { 0x1, 0x1, 221, 2561, -1, 34, 1, 56 }, + { 0x1, 0x1, 221, 2562, -1, 34, 1, 61 }, + { 0x1, 0x1, 221, 2563, -1, 34, 1, 56 }, + { 0x1, 0x1, 221, 2564, -1, 34, 1, 51 }, + { 0x1, 0x1, 221, 2565, -1, 34, 1, 51 }, + { 0x400001, 0x400001, 221, 2566, -1, 12, 1, 61 }, + { 0x1, 0x1, 221, 2567, -1, 34, 1, 56 }, + { 0x600001, 0x1600001, 221, 2568, -1, 12, 1, 61 }, + { 0x3, 0xb, 221, 2569, -1, 33, 1, 56 }, + { 0x1, 0x1, 221, 2570, -1, 34, 1, 61 }, + { 0x1, 0x1, 221, 2571, -1, 34, 1, 56 }, + { 0x1, 0x1, 221, 2572, -1, 34, 1, 61 }, + { 0x1, 0x1, 221, 2573, -1, 34, 1, 56 }, + { 0x41, 0x41, 221, 2574, -1, 28, 1, 31 }, + { 0x1, 0x1, 221, 2575, -1, 34, 1, 31 }, + { 0x83, 0x83, 221, 2576, -1, 27, 1, 31 }, + { 0x81, 0x81, 221, 2577, -1, 27, 1, 31 }, + { 0x1, 0x1, 221, 2578, -1, 34, 1, 66 }, + { 0x1, 0x1, 221, 2579, -1, 34, 1, 31 }, + { 0x1, 0x1, 221, 2580, -1, 34, 1, 31 }, + { 0x5, 0x5, 221, 2581, -1, 34, 1, 66 }, + { 0x9, 0x9, 221, 2582, -1, 34, 1, 31 }, + { 0x1, 0x1, 221, 2583, -1, 34, 1, 31 }, + { 0x1, 0x1, 221, 2584, -1, 34, 1, 31 }, + { 0x1, 0x1, 221, 2585, -1, 34, 1, 31 }, + { 0x1, 0x1, 221, 2586, -1, 34, 1, 66 }, + { 0x1, 0x1, 221, 2587, -1, 34, 1, 31 }, + { 0x1, 0x1, 221, 2588, -1, 34, 1, 31 }, + { 0x5, 0x5, 221, 2589, -1, 34, 1, 66 }, + { 0x9, 0x9, 221, 2590, -1, 34, 1, 31 }, + { 0x1, 0x1, 221, 2591, -1, 34, 1, 31 }, + { 0x5, 0x5, 221, 2592, -1, 34, 1, 66 }, + { 0x9, 0x9, 221, 2593, -1, 34, 1, 31 }, + { 0x1, 0x1, 221, 2594, -1, 34, 1, 31 }, + { 0x1, 0x1, 221, 2595, -1, 34, 1, 66 }, + { 0x1, 0x1, 221, 2596, -1, 34, 1, 31 }, + { 0x1, 0x1, 221, 2597, -1, 34, 1, 31 }, + { 0x1, 0x1, 221, 2598, -1, 34, 1, 66 }, + { 0x1, 0x1, 221, 2599, -1, 34, 1, 31 }, + { 0x1, 0x1, 221, 2600, -1, 34, 1, 31 }, + { 0x1, 0x1, 221, 2601, -1, 34, 1, 31 }, + { 0x1, 0x1, 221, 2602, -1, 34, 1, 31 }, + { 0x1, 0x1, 221, 2603, -1, 34, 1, 51 }, + { 0x81, 0x81, 221, 2604, -1, 27, 1, 51 }, + { 0x41, 0x41, 221, 2605, -1, 28, 1, 51 }, + { 0x83, 0x83, 221, 2606, -1, 27, 1, 51 }, + { 0x21, 0x21, 221, 2607, -1, 29, 1, 51 }, + { 0x85, 0x85, 221, 2608, -1, 27, 1, 51 }, + { 0x43, 0x43, 221, 2609, -1, 28, 1, 51 }, + { 0x87, 0x87, 221, 2610, -1, 27, 1, 51 }, + { 0x1, 0x1, 221, 2611, -1, 34, 1, 51 }, + { 0x1, 0x1, 221, 2612, -1, 34, 1, 51 }, + { 0x1, 0x1, 221, 2613, -1, 34, 1, 51 }, + { 0x1, 0x1, 221, 2614, -1, 34, 1, 51 }, + { 0x41, 0x41, 221, 2615, -1, 28, 1, 31 }, + { 0x1, 0x1, 221, 2616, -1, 34, 1, 31 }, + { 0x83, 0x83, 221, 2617, -1, 27, 1, 31 }, + { 0x81, 0x81, 221, 2618, -1, 27, 1, 31 }, + { 0x1, 0x1, 221, 2619, -1, 34, 1, 31 }, + { 0x1, 0x1, 221, 2620, -1, 34, 1, 31 }, + { 0x1, 0x1, 221, 2621, -1, 34, 1, 31 }, + { 0x1, 0x1, 221, 2622, -1, 34, 1, 31 }, + { 0x1, 0x1, 221, 2623, -1, 34, 1, 31 }, + { 0x1, 0x1, 221, 2624, -1, 34, 1, 31 }, + { 0x1, 0x1, 221, 2625, -1, 34, 1, 31 }, + { 0x1, 0x1, 221, 2626, -1, 34, 1, 26 }, + { 0x1, 0x1, 221, 2627, -1, 34, 1, 26 }, + { 0x1, 0x1, 221, 2628, -1, 34, 1, 26 }, + { 0x1, 0x1, 221, 2629, -1, 34, 1, 26 }, + { 0x1, 0x1, 221, 2630, -1, 34, 1, 37 }, + { 0x1, 0x1, 221, 2631, -1, 34, 1, 66 }, + { 0x1, 0x1, 221, 2632, -1, 34, 1, 31 }, + { 0x1, 0x1, 221, 2633, -1, 34, 1, 31 }, + { 0x1, 0x1, 222, 2634, -1, 35, 1, 66 }, + { 0x1, 0x1, 222, 2635, -1, 35, 1, 32 }, + { 0x1, 0x1, 222, 2636, -1, 35, 1, 32 }, + { 0x1, 0x1, 222, 2637, -1, 35, 1, 32 }, + { 0x1, 0x1, 222, 2638, -1, 35, 1, 32 }, + { 0x1, 0x1, 222, 2639, -1, 35, 1, 47 }, + { 0x1, 0x1, 222, 2640, -1, 35, 1, 43 }, + { 0x800001, 0x800001, 222, 2641, -1, 12, 1, 62 }, + { 0x1, 0x1, 222, 2642, -1, 35, 1, 57 }, + { 0x1800001, 0x1800001, 222, 2643, -1, 12, 1, 62 }, + { 0x3, 0x3, 222, 2644, -1, 35, 1, 57 }, + { 0xa00001, 0xa00001, 222, 2645, -1, 12, 1, 62 }, + { 0x5, 0x5, 222, 2646, -1, 33, 1, 57 }, + { 0x1a00001, 0x1a00001, 222, 2647, -1, 12, 1, 52 }, + { 0xd, 0xd, 222, 2648, -1, 33, 1, 52 }, + { 0x1, 0x1, 222, 2649, -1, 35, 1, 62 }, + { 0x1, 0x1, 222, 2650, -1, 35, 1, 57 }, + { 0x1, 0x1, 222, 2651, -1, 35, 1, 62 }, + { 0x1, 0x1, 222, 2652, -1, 35, 1, 57 }, + { 0x1, 0x1, 222, 2653, -1, 35, 1, 62 }, + { 0x1, 0x1, 222, 2654, -1, 35, 1, 57 }, + { 0x1, 0x1, 222, 2655, -1, 35, 1, 52 }, + { 0x1, 0x1, 222, 2656, -1, 35, 1, 52 }, + { 0x800001, 0x800001, 222, 2657, -1, 12, 1, 62 }, + { 0x1, 0x1, 222, 2658, -1, 35, 1, 57 }, + { 0xa00001, 0x1a00001, 222, 2659, -1, 12, 1, 62 }, + { 0x5, 0xd, 222, 2660, -1, 33, 1, 57 }, + { 0x1, 0x1, 222, 2661, -1, 35, 1, 62 }, + { 0x1, 0x1, 222, 2662, -1, 35, 1, 57 }, + { 0x1, 0x1, 222, 2663, -1, 35, 1, 62 }, + { 0x1, 0x1, 222, 2664, -1, 35, 1, 57 }, + { 0x800001, 0x800001, 222, 2665, -1, 12, 1, 62 }, + { 0x1, 0x1, 222, 2666, -1, 35, 1, 57 }, + { 0x1800001, 0x1800001, 222, 2667, -1, 12, 1, 62 }, + { 0x3, 0x3, 222, 2668, -1, 35, 1, 57 }, + { 0xa00001, 0xa00001, 222, 2669, -1, 12, 1, 62 }, + { 0x5, 0x5, 222, 2670, -1, 33, 1, 57 }, + { 0x1a00001, 0x1a00001, 222, 2671, -1, 12, 1, 52 }, + { 0xd, 0xd, 222, 2672, -1, 33, 1, 52 }, + { 0x1, 0x1, 222, 2673, -1, 35, 1, 62 }, + { 0x1, 0x1, 222, 2674, -1, 35, 1, 57 }, + { 0x1, 0x1, 222, 2675, -1, 35, 1, 62 }, + { 0x1, 0x1, 222, 2676, -1, 35, 1, 57 }, + { 0x1, 0x1, 222, 2677, -1, 35, 1, 62 }, + { 0x1, 0x1, 222, 2678, -1, 35, 1, 57 }, + { 0x1, 0x1, 222, 2679, -1, 35, 1, 52 }, + { 0x1, 0x1, 222, 2680, -1, 35, 1, 52 }, + { 0x800001, 0x800001, 222, 2681, -1, 12, 1, 62 }, + { 0x1, 0x1, 222, 2682, -1, 35, 1, 57 }, + { 0xa00001, 0x1a00001, 222, 2683, -1, 12, 1, 62 }, + { 0x5, 0xd, 222, 2684, -1, 33, 1, 57 }, + { 0x1, 0x1, 222, 2685, -1, 35, 1, 62 }, + { 0x1, 0x1, 222, 2686, -1, 35, 1, 57 }, + { 0x1, 0x1, 222, 2687, -1, 35, 1, 62 }, + { 0x1, 0x1, 222, 2688, -1, 35, 1, 57 }, + { 0x81, 0x81, 222, 2689, -1, 28, 1, 32 }, + { 0x1, 0x1, 222, 2690, -1, 35, 1, 32 }, + { 0x103, 0x103, 222, 2691, -1, 27, 1, 32 }, + { 0x101, 0x101, 222, 2692, -1, 27, 1, 32 }, + { 0x1, 0x1, 222, 2693, -1, 35, 1, 66 }, + { 0x1, 0x1, 222, 2694, -1, 35, 1, 32 }, + { 0x1, 0x1, 222, 2695, -1, 35, 1, 32 }, + { 0x3, 0x3, 222, 2696, -1, 35, 1, 66 }, + { 0x5, 0x5, 222, 2697, -1, 35, 1, 32 }, + { 0x1, 0x1, 222, 2698, -1, 35, 1, 32 }, + { 0x1, 0x1, 222, 2699, -1, 35, 1, 32 }, + { 0x1, 0x1, 222, 2700, -1, 35, 1, 32 }, + { 0x1, 0x1, 222, 2701, -1, 35, 1, 66 }, + { 0x1, 0x1, 222, 2702, -1, 35, 1, 32 }, + { 0x1, 0x1, 222, 2703, -1, 35, 1, 32 }, + { 0x3, 0x3, 222, 2704, -1, 35, 1, 66 }, + { 0x5, 0x5, 222, 2705, -1, 35, 1, 32 }, + { 0x1, 0x1, 222, 2706, -1, 35, 1, 32 }, + { 0x3, 0x3, 222, 2707, -1, 35, 1, 66 }, + { 0x5, 0x5, 222, 2708, -1, 35, 1, 32 }, + { 0x1, 0x1, 222, 2709, -1, 35, 1, 32 }, + { 0x1, 0x1, 222, 2710, -1, 35, 1, 66 }, + { 0x1, 0x1, 222, 2711, -1, 35, 1, 32 }, + { 0x1, 0x1, 222, 2712, -1, 35, 1, 32 }, + { 0x1, 0x1, 222, 2713, -1, 35, 1, 66 }, + { 0x1, 0x1, 222, 2714, -1, 35, 1, 32 }, + { 0x1, 0x1, 222, 2715, -1, 35, 1, 32 }, + { 0x1, 0x1, 222, 2716, -1, 35, 1, 32 }, + { 0x1, 0x1, 222, 2717, -1, 35, 1, 32 }, + { 0x1, 0x1, 222, 2718, -1, 35, 1, 52 }, + { 0x101, 0x101, 222, 2719, -1, 27, 1, 52 }, + { 0x81, 0x81, 222, 2720, -1, 28, 1, 52 }, + { 0x103, 0x103, 222, 2721, -1, 27, 1, 52 }, + { 0x41, 0x41, 222, 2722, -1, 29, 1, 52 }, + { 0x105, 0x105, 222, 2723, -1, 27, 1, 52 }, + { 0x83, 0x83, 222, 2724, -1, 28, 1, 52 }, + { 0x107, 0x107, 222, 2725, -1, 27, 1, 52 }, + { 0x1, 0x1, 222, 2726, -1, 35, 1, 52 }, + { 0x1, 0x1, 222, 2727, -1, 35, 1, 52 }, + { 0x1, 0x1, 222, 2728, -1, 35, 1, 52 }, + { 0x1, 0x1, 222, 2729, -1, 35, 1, 52 }, + { 0x81, 0x81, 222, 2730, -1, 28, 1, 32 }, + { 0x1, 0x1, 222, 2731, -1, 35, 1, 32 }, + { 0x103, 0x103, 222, 2732, -1, 27, 1, 32 }, + { 0x101, 0x101, 222, 2733, -1, 27, 1, 32 }, + { 0x1, 0x1, 222, 2734, -1, 35, 1, 32 }, + { 0x1, 0x1, 222, 2735, -1, 35, 1, 32 }, + { 0x1, 0x1, 222, 2736, -1, 35, 1, 32 }, + { 0x1, 0x1, 222, 2737, -1, 35, 1, 32 }, + { 0x1, 0x1, 222, 2738, -1, 35, 1, 32 }, + { 0x1, 0x1, 222, 2739, -1, 35, 1, 32 }, + { 0x1, 0x1, 222, 2740, -1, 35, 1, 32 }, + { 0x1, 0x1, 222, 2741, -1, 35, 1, 27 }, + { 0x1, 0x1, 222, 2742, -1, 35, 1, 27 }, + { 0x1, 0x1, 222, 2743, -1, 35, 1, 27 }, + { 0x1, 0x1, 222, 2744, -1, 35, 1, 27 }, + { 0x1, 0x1, 222, 2745, -1, 35, 1, 38 }, + { 0x1, 0x1, 222, 2746, -1, 35, 1, 66 }, + { 0x1, 0x1, 222, 2747, -1, 35, 1, 32 }, + { 0x1, 0x1, 222, 2748, -1, 35, 1, 32 }, + { 0x3, 0x3, 223, -1, -1, 34, 1, 66 }, + { 0x3, 0x3, 223, -1, -1, 34, 1, 33 }, + { 0x3, 0x3, 223, 2243, -1, 34, 1, 33 }, + { 0x3, 0x3, 223, -1, -1, 34, 1, 33 }, + { 0x3, 0x3, 223, -1, -1, 34, 1, 33 }, + { 0x3, 0x3, 223, -1, -1, 34, 1, 48 }, + { 0x3, 0x3, 223, -1, -1, 34, 1, 44 }, + { 0xc00001, 0xc00001, 223, -1, -1, 12, 1, 63 }, + { 0x3, 0x3, 223, 2964, -1, 34, 1, 58 }, + { 0x1c00001, 0x1c00001, 223, -1, -1, 12, 1, 63 }, + { 0x7, 0x7, 223, 2965, -1, 34, 1, 58 }, + { 0xe00001, 0xe00001, 223, -1, -1, 12, 1, 63 }, + { 0x7, 0x7, 223, 2966, -1, 33, 1, 58 }, + { 0x1e00001, 0x1e00001, 223, -1, -1, 12, 1, 53 }, + { 0xf, 0xf, 223, 2967, -1, 33, 1, 53 }, + { 0x3, 0x3, 223, -1, -1, 34, 1, 63 }, + { 0x3, 0x3, 223, 2968, -1, 34, 1, 58 }, + { 0x3, 0x3, 223, -1, -1, 34, 1, 63 }, + { 0x3, 0x3, 223, 2969, -1, 34, 1, 58 }, + { 0x3, 0x3, 223, -1, -1, 34, 1, 63 }, + { 0x3, 0x3, 223, 2970, -1, 34, 1, 58 }, + { 0x3, 0x3, 223, -1, -1, 34, 1, 53 }, + { 0x3, 0x3, 223, 2971, -1, 34, 1, 53 }, + { 0xc00001, 0xc00001, 223, -1, -1, 12, 1, 63 }, + { 0x3, 0x3, 223, 2976, -1, 34, 1, 58 }, + { 0xe00001, 0x1e00001, 223, -1, -1, 12, 1, 63 }, + { 0x7, 0xf, 223, 2977, -1, 33, 1, 58 }, + { 0x3, 0x3, 223, -1, -1, 34, 1, 63 }, + { 0x3, 0x3, 223, 2978, -1, 34, 1, 58 }, + { 0x3, 0x3, 223, -1, -1, 34, 1, 63 }, + { 0x3, 0x3, 223, 2979, -1, 34, 1, 58 }, + { 0xc00001, 0xc00001, 223, -1, -1, 12, 1, 63 }, + { 0x3, 0x3, 223, 2982, -1, 34, 1, 58 }, + { 0x1c00001, 0x1c00001, 223, -1, -1, 12, 1, 63 }, + { 0x7, 0x7, 223, 2983, -1, 34, 1, 58 }, + { 0xe00001, 0xe00001, 223, -1, -1, 12, 1, 63 }, + { 0x7, 0x7, 223, 2984, -1, 33, 1, 58 }, + { 0x1e00001, 0x1e00001, 223, -1, -1, 12, 1, 53 }, + { 0xf, 0xf, 223, 2985, -1, 33, 1, 53 }, + { 0x3, 0x3, 223, -1, -1, 34, 1, 63 }, + { 0x3, 0x3, 223, 2986, -1, 34, 1, 58 }, + { 0x3, 0x3, 223, -1, -1, 34, 1, 63 }, + { 0x3, 0x3, 223, 2987, -1, 34, 1, 58 }, + { 0x3, 0x3, 223, -1, -1, 34, 1, 63 }, + { 0x3, 0x3, 223, 2988, -1, 34, 1, 58 }, + { 0x3, 0x3, 223, -1, -1, 34, 1, 53 }, + { 0x3, 0x3, 223, 2989, -1, 34, 1, 53 }, + { 0xc00001, 0xc00001, 223, -1, -1, 12, 1, 63 }, + { 0x3, 0x3, 223, 2994, -1, 34, 1, 58 }, + { 0xe00001, 0x1e00001, 223, -1, -1, 12, 1, 63 }, + { 0x7, 0xf, 223, 2995, -1, 33, 1, 58 }, + { 0x3, 0x3, 223, -1, -1, 34, 1, 63 }, + { 0x3, 0x3, 223, 2996, -1, 34, 1, 58 }, + { 0x3, 0x3, 223, -1, -1, 34, 1, 63 }, + { 0x3, 0x3, 223, 2997, -1, 34, 1, 58 }, + { 0xc1, 0xc1, 223, -1, -1, 28, 1, 33 }, + { 0x3, 0x3, 223, 2862, -1, 34, 1, 33 }, + { 0x183, 0x183, 223, -1, -1, 27, 1, 33 }, + { 0x181, 0x181, 223, 2863, -1, 27, 1, 33 }, + { 0x3, 0x3, 223, -1, -1, 34, 1, 66 }, + { 0x3, 0x3, 223, -1, -1, 34, 1, 33 }, + { 0x3, 0x3, 223, 2244, -1, 34, 1, 33 }, + { 0x7, 0x7, 223, -1, -1, 34, 1, 66 }, + { 0xb, 0xb, 223, -1, -1, 34, 1, 33 }, + { 0x3, 0x3, 223, 2245, -1, 34, 1, 33 }, + { 0x3, 0x3, 223, -1, -1, 34, 1, 33 }, + { 0x3, 0x3, 223, -1, -1, 34, 1, 33 }, + { 0x3, 0x3, 223, -1, -1, 34, 1, 66 }, + { 0x3, 0x3, 223, -1, -1, 34, 1, 33 }, + { 0x3, 0x3, 223, 2248, -1, 34, 1, 33 }, + { 0x7, 0x7, 223, -1, -1, 34, 1, 66 }, + { 0xb, 0xb, 223, -1, -1, 34, 1, 33 }, + { 0x3, 0x3, 223, 2249, -1, 34, 1, 33 }, + { 0x7, 0x7, 223, -1, -1, 34, 1, 66 }, + { 0xb, 0xb, 223, -1, -1, 34, 1, 33 }, + { 0x3, 0x3, 223, 2251, -1, 34, 1, 33 }, + { 0x3, 0x3, 223, -1, -1, 34, 1, 66 }, + { 0x3, 0x3, 223, -1, -1, 34, 1, 33 }, + { 0x3, 0x3, 223, 2253, -1, 34, 1, 33 }, + { 0x3, 0x3, 223, -1, -1, 34, 1, 66 }, + { 0x3, 0x3, 223, -1, -1, 34, 1, 33 }, + { 0x3, 0x3, 223, 2254, -1, 34, 1, 33 }, + { 0x3, 0x3, 223, -1, -1, 34, 1, 33 }, + { 0x3, 0x3, 223, -1, -1, 34, 1, 33 }, + { 0x3, 0x3, 223, -1, -1, 34, 1, 53 }, + { 0x181, 0x181, 223, -1, -1, 27, 1, 53 }, + { 0xc1, 0xc1, 223, -1, -1, 28, 1, 53 }, + { 0x183, 0x183, 223, -1, -1, 27, 1, 53 }, + { 0x61, 0x61, 223, -1, -1, 29, 1, 53 }, + { 0x185, 0x185, 223, -1, -1, 27, 1, 53 }, + { 0xc3, 0xc3, 223, -1, -1, 28, 1, 53 }, + { 0x187, 0x187, 223, -1, -1, 27, 1, 53 }, + { 0x3, 0x3, 223, -1, -1, 34, 1, 53 }, + { 0x3, 0x3, 223, -1, -1, 34, 1, 53 }, + { 0x3, 0x3, 223, -1, -1, 34, 1, 53 }, + { 0x3, 0x3, 223, -1, -1, 34, 1, 53 }, + { 0xc1, 0xc1, 223, -1, -1, 28, 1, 33 }, + { 0x3, 0x3, 223, 2866, -1, 34, 1, 33 }, + { 0x183, 0x183, 223, -1, -1, 27, 1, 33 }, + { 0x181, 0x181, 223, 2867, -1, 27, 1, 33 }, + { 0x3, 0x3, 223, -1, -1, 34, 1, 33 }, + { 0x3, 0x3, 223, -1, -1, 34, 1, 33 }, + { 0x3, 0x3, 223, -1, -1, 34, 1, 33 }, + { 0x3, 0x3, 223, -1, -1, 34, 1, 33 }, + { 0x3, 0x3, 223, -1, -1, 34, 1, 33 }, + { 0x3, 0x3, 223, -1, -1, 34, 1, 33 }, + { 0x3, 0x3, 223, -1, -1, 34, 1, 33 }, + { 0x3, 0x3, 223, -1, -1, 34, 1, 28 }, + { 0x3, 0x3, 223, -1, -1, 34, 1, 28 }, + { 0x3, 0x3, 223, -1, -1, 34, 1, 28 }, + { 0x3, 0x3, 223, -1, -1, 34, 1, 28 }, + { 0x3, 0x3, 223, -1, -1, 34, 1, 39 }, + { 0x3, 0x3, 223, -1, -1, 34, 1, 66 }, + { 0x3, 0x3, 223, -1, -1, 34, 1, 33 }, + { 0x3, 0x3, 223, 2256, -1, 34, 1, 33 }, + { 0x3, 0x3, 224, 540, 1451, 32, 1, 135 }, + { 0x3, 0x3, 224, 541, 1460, 32, 1, 135 }, + { 0x3, 0x3, 224, 542, 1469, 32, 1, 135 }, + { 0x3, 0x3, 224, 543, 1482, 32, 1, 135 }, + { 0x3, 0x3, 224, 544, 1491, 32, 1, 135 }, + { 0x3, 0x3, 224, 545, 1500, 32, 1, 135 }, + { 0x3, 0x3, 224, 546, 1509, 32, 1, 135 }, + { 0x3, 0x3, 224, 547, 1518, 32, 1, 135 }, + { 0x3, 0x3, 224, 548, 1527, 32, 1, 135 }, + { 0x3, 0x3, 224, 549, 1536, 32, 1, 135 }, + { 0x3, 0x3, 224, 550, 1546, 32, 1, 135 }, + { 0x3, 0x3, 224, 551, 1556, 32, 1, 135 }, + { 0x3, 0x3, 224, 564, 1569, 32, 1, 150 }, + { 0x3, 0x3, 224, 565, 1575, 32, 1, 155 }, + { 0x3, 0x3, 224, 566, 1581, 32, 1, 155 }, + { 0x3, 0x3, 224, 567, 1587, 32, 1, 150 }, + { 0x3, 0x3, 224, 568, 1593, 32, 1, 155 }, + { 0x3, 0x3, 224, 569, 1599, 32, 1, 155 }, + { 0x3, 0x3, 224, 570, 1605, 32, 1, 150 }, + { 0x3, 0x3, 224, 571, 1611, 32, 1, 155 }, + { 0x3, 0x3, 224, 572, 1617, 32, 1, 155 }, + { 0x3, 0x3, 224, 573, 1623, 32, 1, 150 }, + { 0x3, 0x3, 224, 574, 1629, 32, 1, 155 }, + { 0x3, 0x3, 224, 575, 1635, 32, 1, 150 }, + { 0x3, 0x3, 224, 576, 1641, 32, 1, 155 }, + { 0x3, 0x3, 224, 577, 1647, 32, 1, 150 }, + { 0x3, 0x3, 224, 578, 1653, 32, 1, 155 }, + { 0x3, 0x3, 224, 579, 1659, 32, 1, 150 }, + { 0x3, 0x3, 224, 580, 1665, 32, 1, 155 }, + { 0x3, 0x3, 224, 581, 1671, 32, 1, 155 }, + { 0x1, 0x1, 225, -1, -1, 28, 1, 34 }, + { 0x1, 0x1, 225, -1, -1, 28, 1, 34 }, + { 0x0, 0x0, 232, 958, -1, 0, 1, 144 }, + { 0x0, 0x0, 232, 959, -1, 0, 1, 160 }, + { 0x1, 0x1, 233, -1, 1982, 33, 1, 140 }, + { 0x1, 0x1, 233, -1, 1985, 33, 1, 146 }, + { 0x0, 0x0, 233, -1, 1987, 0, 1, 157 }, + { 0x0, 0x0, 233, -1, 1988, 0, 1, 161 }, + { 0x0, 0x0, 234, 883, 971, 0, 0, -1 }, + { 0x0, 0x0, 234, 884, 979, 0, 0, -1 }, + { 0x0, 0x0, 234, 885, 975, 0, 0, -1 }, + { 0x1, 0x1, 234, 886, 620, 33, 1, 6 }, + { 0x8000001, 0x8000001, 234, 887, 628, 6, 1, 7 }, + { 0x1, 0x1, 234, 888, 624, 33, 1, 6 }, + { 0x0, 0x0, 234, 889, 983, 0, 0, -1 }, + { 0x1, 0x1, 234, 890, 640, 33, 1, 8 }, + { 0x0, 0x0, 234, 891, 987, 0, 0, -1 }, + { 0x1, 0x1, 234, 892, 652, 33, 1, 16 }, + { 0x0, 0x0, 234, 893, 992, 0, 0, -1 }, + { 0x0, 0x0, 234, 894, 996, 0, 0, -1 }, + { 0x1, 0x1, 234, 895, 675, 33, 1, 18 }, + { 0x1, 0x1, 234, 896, 679, 33, 1, 18 }, + { 0x0, 0x0, 234, 897, 1000, 0, 0, -1 }, + { 0x0, 0x0, 234, 898, 1004, 0, 0, -1 }, + { 0x1, 0x1, 234, 899, 699, 33, 1, 19 }, + { 0x8000001, 0x8000001, 234, 900, 703, 6, 1, 19 }, + { 0x0, 0x0, 234, 901, 1008, 0, 0, -1 }, + { 0x1, 0x1, 234, 902, 715, 33, 1, 20 }, + { 0x0, 0x0, 234, 903, 1012, 0, 0, -1 }, + { 0x0, 0x0, 234, 904, 1016, 0, 0, -1 }, + { 0x1, 0x1, 234, 905, 735, 33, 1, 21 }, + { 0x8000001, 0x8000001, 234, 906, 739, 6, 1, 21 }, + { 0x0, 0x0, 234, 907, 1020, 0, 0, -1 }, + { 0x1, 0x1, 234, 908, 751, 33, 1, 22 }, + { 0x0, 0x0, 234, 909, 1025, 0, 0, -1 }, + { 0x0, 0x0, 234, 910, 1029, 0, 0, -1 }, + { 0x1, 0x1, 234, 911, 774, 33, 1, 18 }, + { 0x1, 0x1, 234, 912, 778, 33, 1, 18 }, + { 0x0, 0x0, 234, 913, 1033, 0, 0, -1 }, + { 0x1, 0x1, 234, 914, 790, 33, 1, 22 }, + { 0x0, 0x0, 235, 2787, 970, 0, 0, -1 }, + { 0x0, 0x0, 235, 2788, 978, 0, 0, -1 }, + { 0x0, 0x0, 235, 2789, 974, 0, 0, -1 }, + { 0x0, 0x0, 235, 2790, 619, 0, 1, 6 }, + { 0x1, 0x1, 235, 2791, 627, 6, 1, 7 }, + { 0x0, 0x0, 235, 2792, 623, 0, 1, 6 }, + { 0x0, 0x0, 235, 2793, 982, 0, 0, -1 }, + { 0x0, 0x0, 235, 2794, 639, 0, 1, 8 }, + { 0x0, 0x0, 235, 2795, 986, 0, 0, -1 }, + { 0x0, 0x0, 235, 2796, 651, 0, 1, 16 }, + { 0x0, 0x0, 235, 2797, 991, 0, 0, -1 }, + { 0x0, 0x0, 235, 2798, 995, 0, 0, -1 }, + { 0x0, 0x0, 235, 2799, 674, 0, 1, 18 }, + { 0x0, 0x0, 235, 2800, 678, 0, 1, 18 }, + { 0x0, 0x0, 235, 2801, 999, 0, 0, -1 }, + { 0x0, 0x0, 235, 2802, 1003, 0, 0, -1 }, + { 0x0, 0x0, 235, 2803, 698, 0, 1, 19 }, + { 0x1, 0x1, 235, 2804, 702, 6, 1, 19 }, + { 0x0, 0x0, 235, 2805, 1007, 0, 0, -1 }, + { 0x0, 0x0, 235, 2806, 714, 0, 1, 20 }, + { 0x0, 0x0, 235, 2807, 1011, 0, 0, -1 }, + { 0x0, 0x0, 235, 2808, 1015, 0, 0, -1 }, + { 0x0, 0x0, 235, 2809, 734, 0, 1, 21 }, + { 0x1, 0x1, 235, 2810, 738, 6, 1, 21 }, + { 0x0, 0x0, 235, 2811, 1019, 0, 0, -1 }, + { 0x0, 0x0, 235, 2812, 750, 0, 1, 22 }, + { 0x0, 0x0, 235, 2813, 1024, 0, 0, -1 }, + { 0x0, 0x0, 235, 2814, 1028, 0, 0, -1 }, + { 0x0, 0x0, 235, 2815, 773, 0, 1, 18 }, + { 0x0, 0x0, 235, 2816, 777, 0, 1, 18 }, + { 0x0, 0x0, 235, 2817, 1032, 0, 0, -1 }, + { 0x0, 0x0, 235, 2818, 789, 0, 1, 22 }, + { 0x1, 0x1, 235, 915, 1155, 27, 1, 17 }, + { 0x0, 0x0, 235, 916, 1153, 0, 1, 17 }, + { 0x0, 0x0, 235, 1220, 1157, 0, 1, 23 }, + { 0x0, 0x1, 235, 1165, 1163, 20, 1, 68 }, + { 0x0, 0x0, 235, 111, 1161, 0, 1, 68 }, + { 0x1, 0x1, 238, -1, -1, 29, 1, 0 }, + { 0x0, 0x0, 238, -1, -1, 0, 1, 0 }, + { 0x1, 0x1, 238, 3022, -1, 27, 1, 0 }, + { 0x1, 0x1, 238, 3023, -1, 27, 1, 0 }, + { 0x1, 0x1, 238, 3024, -1, 27, 1, 0 }, + { 0x1, 0x1, 238, 3025, -1, 27, 1, 0 }, + { 0x0, 0x0, 261, -1, 2344, 0, 0, -1 }, + { 0x0, 0x0, 261, -1, 2346, 0, 0, -1 }, + { 0x1, 0x1, 261, -1, -1, 28, 1, 30 }, + { 0x1, 0x1, 261, -1, -1, 28, 1, 30 }, + { 0x0, 0x0, 261, -1, 2385, 0, 0, -1 }, + { 0x0, 0x0, 261, -1, 2387, 0, 0, -1 }, + { 0x1, 0x1, 261, -1, -1, 28, 1, 30 }, + { 0x1, 0x1, 261, -1, -1, 28, 1, 30 }, + { 0x0, 0x0, 263, 23, -1, 0, 1, 0 }, + { 0x0, 0x0, 263, -1, -1, 0, 1, 0 }, + { 0x0, 0x0, 263, -1, -1, 0, 1, 0 }, + { 0x0, 0x1, 263, -1, -1, 29, 1, 0 }, + { 0x0, 0x1, 263, -1, -1, 29, 1, 0 }, + { 0x0, 0x1, 263, -1, -1, 29, 1, 0 }, + { 0x0, 0x1, 263, -1, -1, 29, 1, 0 }, + { 0x0, 0x1, 263, -1, -1, 29, 1, 0 }, + { 0x0, 0x0, 263, 180, -1, 0, 1, 0 }, + { 0x0, 0x1, 263, -1, -1, 29, 1, 0 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 2 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 2 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 2 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 2 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 2 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 2 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 2 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 2 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 2 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 2 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 2 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 2 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 2 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 2 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 2 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 2 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 2 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 2 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 2 }, + { 0x1, 0x1, 264, 301, -1, 12, 1, 2 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 2 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 2 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 2 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 2 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 2 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 2 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 2 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 2 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 2 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 2 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 2 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 2 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 2 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 2 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 2 }, + { 0x1, 0x1, 264, 323, -1, 12, 1, 2 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 2 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 2 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 2 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 2 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 2 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 2 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 2 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 2 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 2 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 2 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 2 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 2 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 2 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 2 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 2 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 2 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 2 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 2 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 2 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 2 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 2 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 2 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 2 }, + { 0x1, 0x1, 264, 349, -1, 12, 1, 2 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 2 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 2 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 2 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 2 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 2 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 2 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 2 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 2 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 2 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 2 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 2 }, + { 0x1, 0x1, 264, 371, -1, 12, 1, 2 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 2 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 2 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 2 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 2 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 2 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 2 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 2 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 2 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 65 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 65 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 65 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 65 }, + { 0x0, 0x0, 264, -1, 2296, 0, 0, -1 }, + { 0x0, 0x0, 264, -1, 2298, 0, 0, -1 }, + { 0x0, 0x0, 264, -1, 2300, 0, 0, -1 }, + { 0x0, 0x0, 264, -1, 2302, 0, 0, -1 }, + { 0x1, 0x1, 264, -1, 2304, 12, 1, 60 }, + { 0x1, 0x1, 264, -1, 2306, 12, 1, 60 }, + { 0x1, 0x1, 264, -1, 2308, 12, 1, 60 }, + { 0x1, 0x1, 264, -1, 2310, 12, 1, 50 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 60 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 60 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 60 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 50 }, + { 0x0, 0x0, 264, -1, 2312, 0, 0, -1 }, + { 0x0, 0x0, 264, -1, 2314, 0, 0, -1 }, + { 0x1, 0x1, 264, -1, 2316, 12, 1, 60 }, + { 0x1, 0x1, 264, -1, 2318, 12, 1, 60 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 60 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 60 }, + { 0x0, 0x0, 264, -1, 2320, 0, 0, -1 }, + { 0x0, 0x0, 264, -1, 2322, 0, 0, -1 }, + { 0x0, 0x0, 264, -1, 2324, 0, 0, -1 }, + { 0x0, 0x0, 264, -1, 2326, 0, 0, -1 }, + { 0x1, 0x1, 264, -1, 2328, 12, 1, 60 }, + { 0x1, 0x1, 264, -1, 2330, 12, 1, 60 }, + { 0x1, 0x1, 264, -1, 2332, 12, 1, 60 }, + { 0x1, 0x1, 264, -1, 2334, 12, 1, 50 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 60 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 60 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 60 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 50 }, + { 0x0, 0x0, 264, -1, 2336, 0, 0, -1 }, + { 0x0, 0x0, 264, -1, 2338, 0, 0, -1 }, + { 0x1, 0x1, 264, -1, 2340, 12, 1, 60 }, + { 0x1, 0x1, 264, -1, 2342, 12, 1, 60 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 60 }, + { 0x1, 0x1, 264, -1, -1, 12, 1, 60 }, + { 0x1, 0x1, 264, 393, -1, 12, 1, 2 }, + { 0x1, 0x1, 264, 395, -1, 12, 1, 2 }, + { 0x1, 0x1, 264, 517, -1, 12, 1, 2 }, + { 0x1, 0x1, 264, 519, -1, 12, 1, 2 }, + { 0x1, 0x1, 264, 401, -1, 12, 1, 77 }, + { 0x1, 0x1, 264, 403, -1, 12, 1, 77 }, + { 0x1, 0x1, 264, 525, -1, 12, 1, 77 }, + { 0x1, 0x1, 264, 527, -1, 12, 1, 77 }, + { 0x1, 0x1, 264, 409, -1, 12, 1, 2 }, + { 0x1, 0x1, 264, 411, -1, 12, 1, 2 }, + { 0x1, 0x1, 264, 533, -1, 12, 1, 2 }, + { 0x1, 0x1, 264, 535, -1, 12, 1, 2 }, + { 0x0, 0x0, 265, -1, 2303, 0, 0, -1 }, + { 0x9, 0x9, 265, -1, 2311, 33, 1, 50 }, + { 0x9, 0x9, 265, -1, 2975, 33, 1, 50 }, + { 0x0, 0x0, 265, 1399, 2376, 0, 0, -1 }, + { 0x3, 0x3, 265, 1400, -1, 27, 1, 50 }, + { 0x0, 0x0, 269, 2856, -1, 0, 1, 0 }, + { 0x3, 0x3, 270, -1, -1, 27, 1, 0 }, + { 0x3, 0x3, 270, -1, -1, 27, 1, 0 }, + { 0x3, 0x3, 270, -1, -1, 27, 1, 0 }, + { 0x3, 0x3, 270, -1, -1, 27, 1, 0 }, + { 0x1, 0x1, 271, 3018, -1, 28, 1, 0 }, + { 0x1, 0x1, 271, 3019, -1, 28, 1, 0 }, + { 0x1, 0x1, 271, 3020, -1, 28, 1, 0 }, + { 0x1, 0x1, 271, 3021, -1, 28, 1, 0 }, + { 0x1, 0x1, 273, -1, -1, 27, 1, 100 }, + { 0x1, 0x1, 273, -1, -1, 27, 1, 100 }, + { 0x0, 0x0, 273, -1, 968, 0, 0, -1 }, + { 0x0, 0x0, 274, 3031, 2833, 0, 0, -1 }, + { 0x0, 0x0, 274, 3032, 2835, 0, 0, -1 }, + { 0x0, 0x0, 275, -1, 2834, 0, 0, -1 }, + { 0x0, 0x0, 275, -1, 2836, 0, 0, -1 }, + { 0x0, 0x0, 276, -1, -1, 0, 1, 41 }, + { 0x0, 0x0, 276, -1, -1, 0, 1, 41 }, + { 0x0, 0x0, 276, -1, -1, 0, 1, 41 }, + { 0x0, 0x0, 281, -1, -1, 0, 1, 34 }, + { 0x0, 0x0, 285, -1, 2350, 0, 1, 30 }, + { 0x0, 0x0, 286, -1, -1, 0, 1, 0 }, + { 0x0, 0x0, 286, -1, -1, 0, 1, 72 }, + { 0x0, 0x0, 286, 2001, 3000, 0, 1, 1 }, + { 0x0, 0x0, 286, 2002, 3001, 0, 1, 1 }, + { 0x0, 0x0, 286, -1, 518, 0, 0, -1 }, + { 0x0, 0x0, 286, -1, 520, 0, 0, -1 }, + { 0x0, 0x0, 286, 2005, 3004, 0, 1, 76 }, + { 0x0, 0x0, 286, 2006, 3005, 0, 1, 76 }, + { 0x0, 0x0, 286, -1, 526, 0, 0, -1 }, + { 0x0, 0x0, 286, -1, 528, 0, 0, -1 }, + { 0x0, 0x0, 286, 2009, 3008, 0, 1, 1 }, + { 0x0, 0x0, 286, 2010, 3009, 0, 1, 1 }, + { 0x0, 0x0, 286, -1, 534, 0, 0, -1 }, + { 0x0, 0x0, 286, -1, 536, 0, 0, -1 }, +}; + +static const struct ia64_main_table +main_table[] = { + { 5, 1, 1, 0x0000010000000000ull, 0x000001eff8000000ull, { 24, 25, 26, 0, 0 }, 0x0, 0, }, + { 5, 1, 1, 0x0000010008000000ull, 0x000001eff8000000ull, { 24, 25, 26, 4, 0 }, 0x0, 1, }, + { 5, 7, 1, 0x0000000000000000ull, 0x0000000000000000ull, { 24, 67, 27, 0, 0 }, 0x0, 2, }, + { 5, 7, 1, 0x0000000000000000ull, 0x0000000000000000ull, { 24, 64, 26, 0, 0 }, 0x0, 3, }, + { 6, 1, 1, 0x0000012000000000ull, 0x000001e000000000ull, { 24, 67, 27, 0, 0 }, 0x0, 4, }, + { 7, 1, 1, 0x0000010040000000ull, 0x000001eff8000000ull, { 24, 25, 26, 0, 0 }, 0x0, 5, }, + { 7, 1, 1, 0x0000010c00000000ull, 0x000001ee00000000ull, { 24, 64, 26, 0, 0 }, 0x0, 6, }, + { 8, 1, 1, 0x0000010800000000ull, 0x000001ee00000000ull, { 24, 64, 26, 0, 0 }, 0x0, 7, }, + { 9, 3, 1, 0x0000002c00000000ull, 0x000001ee00000000ull, { 24, 3, 53, 54, 55 }, 0x221, 8, }, + { 9, 3, 1, 0x0000002c00000000ull, 0x000001ee00000000ull, { 24, 53, 54, 55, 0 }, 0x261, 9, }, + { 10, 1, 1, 0x0000010060000000ull, 0x000001eff8000000ull, { 24, 25, 26, 0, 0 }, 0x0, 10, }, + { 10, 1, 1, 0x0000010160000000ull, 0x000001eff8000000ull, { 24, 56, 26, 0, 0 }, 0x0, 11, }, + { 11, 1, 1, 0x0000010068000000ull, 0x000001eff8000000ull, { 24, 25, 26, 0, 0 }, 0x0, 12, }, + { 11, 1, 1, 0x0000010168000000ull, 0x000001eff8000000ull, { 24, 56, 26, 0, 0 }, 0x0, 13, }, + { 14, 4, 0, 0x0000000100000000ull, 0x000001eff80011ffull, { 16, 0, 0, 0, 0 }, 0x40, 969, }, + { 14, 4, 0, 0x0000000100000000ull, 0x000001eff80011c0ull, { 16, 0, 0, 0, 0 }, 0x0, 825, }, + { 14, 4, 0, 0x0000000100000000ull, 0x000001eff80011c0ull, { 16, 0, 0, 0, 0 }, 0x40, 826, }, + { 14, 4, 0, 0x0000000108000100ull, 0x000001eff80011c0ull, { 16, 0, 0, 0, 0 }, 0x200, 2234, }, + { 14, 4, 0, 0x0000000108000100ull, 0x000001eff80011c0ull, { 16, 0, 0, 0, 0 }, 0x240, 2235, }, + { 14, 4, 1, 0x0000002100000000ull, 0x000001ef00001000ull, { 15, 16, 0, 0, 0 }, 0x0, 582, }, + { 14, 4, 1, 0x0000002100000000ull, 0x000001ef00001000ull, { 15, 16, 0, 0, 0 }, 0x40, 583, }, + { 14, 4, 0, 0x0000008000000000ull, 0x000001ee000011ffull, { 82, 0, 0, 0, 0 }, 0x40, 990, }, + { 14, 4, 0, 0x0000008000000000ull, 0x000001ee000011c0ull, { 82, 0, 0, 0, 0 }, 0x0, 827, }, + { 14, 4, 0, 0x0000008000000000ull, 0x000001ee000011c0ull, { 82, 0, 0, 0, 0 }, 0x40, 828, }, + { 14, 4, 0, 0x0000008000000080ull, 0x000001ee000011c0ull, { 82, 0, 0, 0, 0 }, 0x210, 3029, }, + { 14, 4, 0, 0x0000008000000080ull, 0x000001ee000011c0ull, { 82, 0, 0, 0, 0 }, 0x250, 3030, }, + { 14, 4, 0, 0x0000008000000140ull, 0x000001ee000011c0ull, { 82, 0, 0, 0, 0 }, 0x30, 590, }, + { 14, 4, 0, 0x0000008000000140ull, 0x000001ee000011c0ull, { 82, 0, 0, 0, 0 }, 0x70, 591, }, + { 14, 4, 0, 0x0000008000000180ull, 0x000001ee000011c0ull, { 82, 0, 0, 0, 0 }, 0x230, 588, }, + { 14, 4, 0, 0x0000008000000180ull, 0x000001ee000011c0ull, { 82, 0, 0, 0, 0 }, 0x270, 589, }, + { 14, 4, 1, 0x000000a000000000ull, 0x000001ee00001000ull, { 15, 82, 0, 0, 0 }, 0x0, 584, }, + { 14, 4, 1, 0x000000a000000000ull, 0x000001ee00001000ull, { 15, 82, 0, 0, 0 }, 0x40, 585, }, + { 15, 4, 0, 0x0000000000000000ull, 0x000001e1f8000000ull, { 66, 0, 0, 0, 0 }, 0x0, 537, }, + { 15, 5, 0, 0x0000000000000000ull, 0x000001e3f8000000ull, { 66, 0, 0, 0, 0 }, 0x0, 960, }, + { 15, 2, 0, 0x0000000000000000ull, 0x000001eff8000000ull, { 66, 0, 0, 0, 0 }, 0x2, 1138, }, + { 15, 3, 0, 0x0000000000000000ull, 0x000001eff8000000ull, { 66, 0, 0, 0, 0 }, 0x0, 1263, }, + { 15, 6, 0, 0x0000000000000000ull, 0x000001eff8000000ull, { 70, 0, 0, 0, 0 }, 0x0, 3033, }, + { 15, 7, 0, 0x0000000000000000ull, 0x0000000000000000ull, { 66, 0, 0, 0, 0 }, 0x0, 16, }, + { 16, 6, 0, 0x0000018000000000ull, 0x000001ee000011ffull, { 83, 0, 0, 0, 0 }, 0x40, 1023, }, + { 16, 6, 0, 0x0000018000000000ull, 0x000001ee000011c0ull, { 83, 0, 0, 0, 0 }, 0x0, 829, }, + { 16, 6, 0, 0x0000018000000000ull, 0x000001ee000011c0ull, { 83, 0, 0, 0, 0 }, 0x40, 830, }, + { 16, 6, 1, 0x000001a000000000ull, 0x000001ee00001000ull, { 15, 83, 0, 0, 0 }, 0x0, 586, }, + { 16, 6, 1, 0x000001a000000000ull, 0x000001ee00001000ull, { 15, 83, 0, 0, 0 }, 0x40, 587, }, + { 17, 4, 0, 0x0000004080000000ull, 0x000001e9f8000018ull, { 16, 78, 0, 0, 0 }, 0x20, 2852, }, + { 17, 4, 0, 0x000000e000000000ull, 0x000001e800000018ull, { 82, 78, 0, 0, 0 }, 0x20, 2853, }, + { 18, 4, 0, 0x0000000060000000ull, 0x000001e1f8000000ull, { 0, 0, 0, 0, 0 }, 0x2c, 222, }, + { 22, 2, 0, 0x0000000200000000ull, 0x000001ee00000000ull, { 25, 81, 0, 0, 0 }, 0x0, 2239, }, + { 22, 3, 0, 0x0000000800000000ull, 0x000001ee00000000ull, { 24, 82, 0, 0, 0 }, 0x0, 226, }, + { 22, 3, 0, 0x0000000c00000000ull, 0x000001ee00000000ull, { 18, 82, 0, 0, 0 }, 0x0, 227, }, + { 22, 3, 0, 0x0000002200000000ull, 0x000001ee00000000ull, { 25, 81, 0, 0, 0 }, 0x0, 2240, }, + { 22, 3, 0, 0x0000002600000000ull, 0x000001ee00000000ull, { 19, 81, 0, 0, 0 }, 0x0, 2241, }, + { 22, 7, 0, 0x0000000000000000ull, 0x0000000000000000ull, { 25, 81, 0, 0, 0 }, 0x0, 2242, }, + { 25, 4, 0, 0x0000000020000000ull, 0x000001e1f8000000ull, { 0, 0, 0, 0, 0 }, 0x224, 18, }, + { 26, 1, 2, 0x0000018000000000ull, 0x000001fe00001000ull, { 22, 23, 25, 26, 0 }, 0x0, 1222, }, + { 26, 1, 1, 0x0000018000000000ull, 0x000001fe00001000ull, { 22, 25, 26, 0, 0 }, 0x40, 1223, }, + { 26, 1, 2, 0x0000018000000000ull, 0x000001fe00001000ull, { 23, 22, 26, 25, 0 }, 0x0, 1181, }, + { 26, 1, 1, 0x0000018000000000ull, 0x000001fe00001000ull, { 23, 26, 25, 0, 0 }, 0x40, 1182, }, + { 26, 1, 2, 0x0000018000000000ull, 0x000001fe00001000ull, { 22, 23, 26, 25, 0 }, 0x0, 1090, }, + { 26, 1, 1, 0x0000018000000000ull, 0x000001fe00001000ull, { 22, 26, 25, 0, 0 }, 0x40, 1091, }, + { 26, 1, 2, 0x0000018000000000ull, 0x000001fe00001000ull, { 23, 22, 25, 26, 0 }, 0x0, 1052, }, + { 26, 1, 1, 0x0000018000000000ull, 0x000001fe00001000ull, { 23, 25, 26, 0, 0 }, 0x40, 1053, }, + { 26, 1, 2, 0x0000018200000000ull, 0x000001fe00001000ull, { 22, 23, 25, 26, 0 }, 0x40, 1376, }, + { 26, 1, 2, 0x0000019000000000ull, 0x000001fe00001000ull, { 22, 23, 7, 26, 0 }, 0x0, 1092, }, + { 26, 1, 1, 0x0000019000000000ull, 0x000001fe00001000ull, { 22, 7, 26, 0, 0 }, 0x40, 1093, }, + { 26, 1, 2, 0x0000019000000000ull, 0x000001fe00001000ull, { 22, 23, 26, 7, 0 }, 0x40, 1226, }, + { 26, 1, 1, 0x0000019000000000ull, 0x000001fe00001000ull, { 22, 26, 7, 0, 0 }, 0x40, 1227, }, + { 26, 1, 2, 0x0000019000000000ull, 0x000001fe00001000ull, { 22, 23, 7, 26, 0 }, 0x40, 1187, }, + { 26, 1, 2, 0x0000018800000000ull, 0x000001ee00001000ull, { 22, 23, 56, 26, 0 }, 0x0, 1229, }, + { 26, 1, 1, 0x0000018800000000ull, 0x000001ee00001000ull, { 22, 56, 26, 0, 0 }, 0x40, 1230, }, + { 26, 1, 2, 0x0000018800000000ull, 0x000001ee00001000ull, { 22, 23, 58, 26, 0 }, 0x0, 1188, }, + { 26, 1, 1, 0x0000018800000000ull, 0x000001ee00001000ull, { 22, 58, 26, 0, 0 }, 0x40, 1189, }, + { 26, 1, 2, 0x0000018800000000ull, 0x000001ee00001000ull, { 23, 22, 58, 26, 0 }, 0x0, 1097, }, + { 26, 1, 1, 0x0000018800000000ull, 0x000001ee00001000ull, { 23, 58, 26, 0, 0 }, 0x40, 1098, }, + { 26, 1, 2, 0x0000018800000000ull, 0x000001ee00001000ull, { 23, 22, 56, 26, 0 }, 0x0, 1059, }, + { 26, 1, 1, 0x0000018800000000ull, 0x000001ee00001000ull, { 23, 56, 26, 0, 0 }, 0x40, 1060, }, + { 26, 1, 2, 0x0000018a00000000ull, 0x000001ee00001000ull, { 22, 23, 56, 26, 0 }, 0x40, 1381, }, + { 26, 1, 2, 0x000001a800000000ull, 0x000001ee00001000ull, { 22, 23, 60, 26, 0 }, 0x0, 1214, }, + { 26, 1, 1, 0x000001a800000000ull, 0x000001ee00001000ull, { 22, 60, 26, 0, 0 }, 0x40, 1215, }, + { 26, 1, 2, 0x000001a800000000ull, 0x000001ee00001000ull, { 23, 22, 60, 26, 0 }, 0x0, 1125, }, + { 26, 1, 1, 0x000001a800000000ull, 0x000001ee00001000ull, { 23, 60, 26, 0, 0 }, 0x40, 1126, }, + { 26, 1, 2, 0x000001c200000000ull, 0x000001fe00001000ull, { 23, 22, 25, 26, 0 }, 0x40, 1382, }, + { 26, 1, 2, 0x000001d000000000ull, 0x000001fe00001000ull, { 23, 22, 7, 26, 0 }, 0x40, 1190, }, + { 26, 1, 1, 0x000001d000000000ull, 0x000001fe00001000ull, { 23, 7, 26, 0, 0 }, 0x40, 1191, }, + { 26, 1, 2, 0x000001d000000000ull, 0x000001fe00001000ull, { 23, 22, 26, 7, 0 }, 0x40, 1063, }, + { 26, 1, 1, 0x000001d000000000ull, 0x000001fe00001000ull, { 23, 26, 7, 0, 0 }, 0x40, 1064, }, + { 26, 1, 2, 0x000001ca00000000ull, 0x000001ee00001000ull, { 23, 22, 56, 26, 0 }, 0x40, 1383, }, + { 27, 1, 2, 0x0000018400000000ull, 0x000001fe00001000ull, { 22, 23, 25, 26, 0 }, 0x0, 1235, }, + { 27, 1, 1, 0x0000018400000000ull, 0x000001fe00001000ull, { 22, 25, 26, 0, 0 }, 0x40, 1236, }, + { 27, 1, 2, 0x0000018400000000ull, 0x000001fe00001000ull, { 23, 22, 26, 25, 0 }, 0x0, 1194, }, + { 27, 1, 1, 0x0000018400000000ull, 0x000001fe00001000ull, { 23, 26, 25, 0, 0 }, 0x40, 1195, }, + { 27, 1, 2, 0x0000018400000000ull, 0x000001fe00001000ull, { 22, 23, 26, 25, 0 }, 0x0, 1103, }, + { 27, 1, 1, 0x0000018400000000ull, 0x000001fe00001000ull, { 22, 26, 25, 0, 0 }, 0x40, 1104, }, + { 27, 1, 2, 0x0000018400000000ull, 0x000001fe00001000ull, { 23, 22, 25, 26, 0 }, 0x0, 1065, }, + { 27, 1, 1, 0x0000018400000000ull, 0x000001fe00001000ull, { 23, 25, 26, 0, 0 }, 0x40, 1066, }, + { 27, 1, 2, 0x0000018600000000ull, 0x000001fe00001000ull, { 22, 23, 25, 26, 0 }, 0x40, 1388, }, + { 27, 1, 2, 0x0000019400000000ull, 0x000001fe00001000ull, { 22, 23, 7, 26, 0 }, 0x0, 1105, }, + { 27, 1, 1, 0x0000019400000000ull, 0x000001fe00001000ull, { 22, 7, 26, 0, 0 }, 0x40, 1106, }, + { 27, 1, 2, 0x0000019400000000ull, 0x000001fe00001000ull, { 22, 23, 26, 7, 0 }, 0x40, 1239, }, + { 27, 1, 1, 0x0000019400000000ull, 0x000001fe00001000ull, { 22, 26, 7, 0, 0 }, 0x40, 1240, }, + { 27, 1, 2, 0x0000019400000000ull, 0x000001fe00001000ull, { 22, 23, 7, 26, 0 }, 0x40, 1200, }, + { 27, 1, 2, 0x0000018c00000000ull, 0x000001ee00001000ull, { 22, 23, 56, 26, 0 }, 0x0, 1242, }, + { 27, 1, 1, 0x0000018c00000000ull, 0x000001ee00001000ull, { 22, 56, 26, 0, 0 }, 0x40, 1243, }, + { 27, 1, 2, 0x0000018c00000000ull, 0x000001ee00001000ull, { 22, 23, 58, 26, 0 }, 0x0, 1201, }, + { 27, 1, 1, 0x0000018c00000000ull, 0x000001ee00001000ull, { 22, 58, 26, 0, 0 }, 0x40, 1202, }, + { 27, 1, 2, 0x0000018c00000000ull, 0x000001ee00001000ull, { 23, 22, 58, 26, 0 }, 0x0, 1110, }, + { 27, 1, 1, 0x0000018c00000000ull, 0x000001ee00001000ull, { 23, 58, 26, 0, 0 }, 0x40, 1111, }, + { 27, 1, 2, 0x0000018c00000000ull, 0x000001ee00001000ull, { 23, 22, 56, 26, 0 }, 0x0, 1072, }, + { 27, 1, 1, 0x0000018c00000000ull, 0x000001ee00001000ull, { 23, 56, 26, 0, 0 }, 0x40, 1073, }, + { 27, 1, 2, 0x0000018e00000000ull, 0x000001ee00001000ull, { 22, 23, 56, 26, 0 }, 0x40, 1393, }, + { 27, 1, 2, 0x000001ac00000000ull, 0x000001ee00001000ull, { 22, 23, 57, 26, 0 }, 0x0, 1259, }, + { 27, 1, 1, 0x000001ac00000000ull, 0x000001ee00001000ull, { 22, 57, 26, 0, 0 }, 0x40, 1260, }, + { 27, 1, 2, 0x000001ac00000000ull, 0x000001ee00001000ull, { 22, 23, 59, 26, 0 }, 0x0, 1218, }, + { 27, 1, 1, 0x000001ac00000000ull, 0x000001ee00001000ull, { 22, 59, 26, 0, 0 }, 0x40, 1219, }, + { 27, 1, 2, 0x000001ac00000000ull, 0x000001ee00001000ull, { 23, 22, 59, 26, 0 }, 0x0, 1129, }, + { 27, 1, 1, 0x000001ac00000000ull, 0x000001ee00001000ull, { 23, 59, 26, 0, 0 }, 0x40, 1130, }, + { 27, 1, 2, 0x000001ac00000000ull, 0x000001ee00001000ull, { 23, 22, 57, 26, 0 }, 0x0, 1088, }, + { 27, 1, 1, 0x000001ac00000000ull, 0x000001ee00001000ull, { 23, 57, 26, 0, 0 }, 0x40, 1089, }, + { 27, 1, 2, 0x000001c600000000ull, 0x000001fe00001000ull, { 23, 22, 25, 26, 0 }, 0x40, 1394, }, + { 27, 1, 2, 0x000001d400000000ull, 0x000001fe00001000ull, { 23, 22, 7, 26, 0 }, 0x40, 1203, }, + { 27, 1, 1, 0x000001d400000000ull, 0x000001fe00001000ull, { 23, 7, 26, 0, 0 }, 0x40, 1204, }, + { 27, 1, 2, 0x000001d400000000ull, 0x000001fe00001000ull, { 23, 22, 26, 7, 0 }, 0x40, 1076, }, + { 27, 1, 1, 0x000001d400000000ull, 0x000001fe00001000ull, { 23, 26, 7, 0, 0 }, 0x40, 1077, }, + { 27, 1, 2, 0x000001ce00000000ull, 0x000001ee00001000ull, { 23, 22, 56, 26, 0 }, 0x40, 1395, }, + { 28, 3, 1, 0x0000008808000000ull, 0x000001fff8000000ull, { 24, 28, 25, 1, 2 }, 0x0, 259, }, + { 28, 3, 1, 0x0000008808000000ull, 0x000001fff8000000ull, { 24, 28, 25, 0, 0 }, 0x40, 260, }, + { 29, 3, 1, 0x0000008008000000ull, 0x000001fff8000000ull, { 24, 28, 25, 2, 0 }, 0x0, 261, }, + { 29, 3, 1, 0x0000008008000000ull, 0x000001fff8000000ull, { 24, 28, 25, 0, 0 }, 0x40, 262, }, + { 30, 3, 1, 0x0000008048000000ull, 0x000001fff8000000ull, { 24, 28, 25, 2, 0 }, 0x0, 263, }, + { 30, 3, 1, 0x0000008048000000ull, 0x000001fff8000000ull, { 24, 28, 25, 0, 0 }, 0x40, 264, }, + { 31, 3, 1, 0x0000008088000000ull, 0x000001fff8000000ull, { 24, 28, 25, 2, 0 }, 0x0, 265, }, + { 31, 3, 1, 0x0000008088000000ull, 0x000001fff8000000ull, { 24, 28, 25, 0, 0 }, 0x40, 266, }, + { 32, 3, 1, 0x00000080c8000000ull, 0x000001fff8000000ull, { 24, 28, 25, 2, 0 }, 0x0, 267, }, + { 32, 3, 1, 0x00000080c8000000ull, 0x000001fff8000000ull, { 24, 28, 25, 0, 0 }, 0x40, 268, }, + { 34, 4, 0, 0x0000000010000000ull, 0x000001e1f8000000ull, { 0, 0, 0, 0, 0 }, 0x224, 19, }, + { 36, 2, 1, 0x00000000c0000000ull, 0x000001eff8000000ull, { 24, 26, 0, 0, 0 }, 0x0, 1167, }, + { 37, 2, 1, 0x00000000c8000000ull, 0x000001eff8000000ull, { 24, 26, 0, 0, 0 }, 0x0, 1168, }, + { 39, 2, 1, 0x0000008000000000ull, 0x000001e000000000ull, { 24, 25, 26, 47, 73 }, 0x0, 20, }, + { 39, 2, 1, 0x000000a600000000ull, 0x000001ee04000000ull, { 24, 25, 45, 74, 0 }, 0x0, 3038, }, + { 39, 2, 1, 0x000000a604000000ull, 0x000001ee04000000ull, { 24, 56, 45, 74, 0 }, 0x0, 3039, }, + { 39, 2, 1, 0x000000ae00000000ull, 0x000001ee00000000ull, { 24, 48, 26, 46, 74 }, 0x0, 21, }, + { 43, 4, 0, 0x0000000080000000ull, 0x000001e1f8000000ull, { 0, 0, 0, 0, 0 }, 0x20, 22, }, + { 48, 2, 1, 0x000000a400000000ull, 0x000001ee00002000ull, { 24, 26, 77, 74, 0 }, 0x0, 2870, }, + { 50, 5, 1, 0x0000000080000000ull, 0x000001e3f80fe000ull, { 18, 20, 0, 0, 0 }, 0x40, 24, }, + { 51, 5, 1, 0x0000010008000000ull, 0x000001fff8000000ull, { 18, 20, 19, 0, 0 }, 0x40, 2291, }, + { 52, 5, 1, 0x00000000b8000000ull, 0x000001eff8000000ull, { 18, 19, 20, 0, 0 }, 0x0, 2292, }, + { 52, 5, 1, 0x00000000b8000000ull, 0x000001eff8000000ull, { 18, 19, 20, 0, 0 }, 0x40, 26, }, + { 53, 5, 1, 0x00000000b0000000ull, 0x000001eff8000000ull, { 18, 19, 20, 0, 0 }, 0x0, 2293, }, + { 53, 5, 1, 0x00000000b0000000ull, 0x000001eff8000000ull, { 18, 19, 20, 0, 0 }, 0x40, 27, }, + { 54, 5, 1, 0x0000000160000000ull, 0x000001e3f8000000ull, { 18, 19, 20, 0, 0 }, 0x0, 28, }, + { 55, 5, 1, 0x0000000168000000ull, 0x000001e3f8000000ull, { 18, 19, 20, 0, 0 }, 0x0, 29, }, + { 57, 3, 0, 0x0000002180000000ull, 0x000001fff8000000ull, { 26, 0, 0, 0, 0 }, 0x0, 30, }, + { 58, 5, 0, 0x0000000040000000ull, 0x000001eff8000000ull, { 80, 0, 0, 0, 0 }, 0x0, 2294, }, + { 58, 5, 0, 0x0000000040000000ull, 0x000001eff8000000ull, { 80, 0, 0, 0, 0 }, 0x40, 31, }, + { 59, 5, 2, 0x000000a000000000ull, 0x000001e000001000ull, { 22, 23, 19, 61, 0 }, 0x0, 1265, }, + { 59, 5, 1, 0x000000a000000000ull, 0x000001e000001000ull, { 22, 19, 61, 0, 0 }, 0x40, 1266, }, + { 59, 5, 2, 0x000000a000000000ull, 0x000001e000001000ull, { 23, 22, 19, 61, 0 }, 0x40, 1420, }, + { 59, 5, 1, 0x000000a000000000ull, 0x000001e000001000ull, { 23, 19, 61, 0, 0 }, 0x40, 1421, }, + { 60, 5, 0, 0x0000000028000000ull, 0x000001eff8000000ull, { 0, 0, 0, 0, 0 }, 0x0, 2295, }, + { 60, 5, 0, 0x0000000028000000ull, 0x000001eff8000000ull, { 0, 0, 0, 0, 0 }, 0x40, 32, }, + { 61, 5, 2, 0x0000008000000000ull, 0x000001fe00001000ull, { 22, 23, 19, 20, 0 }, 0x0, 943, }, + { 61, 5, 1, 0x0000008000000000ull, 0x000001fe00001000ull, { 22, 19, 20, 0, 0 }, 0x40, 944, }, + { 61, 5, 2, 0x0000008000000000ull, 0x000001fe00001000ull, { 22, 23, 19, 20, 0 }, 0x40, 945, }, + { 61, 5, 2, 0x0000009000000000ull, 0x000001fe00001000ull, { 22, 23, 20, 19, 0 }, 0x0, 1116, }, + { 61, 5, 1, 0x0000009000000000ull, 0x000001fe00001000ull, { 22, 20, 19, 0, 0 }, 0x40, 1117, }, + { 61, 5, 2, 0x0000009000000000ull, 0x000001fe00001000ull, { 22, 23, 20, 19, 0 }, 0x40, 1118, }, + { 61, 5, 2, 0x0000008000000000ull, 0x000001fe00001000ull, { 23, 22, 19, 20, 0 }, 0x0, 1396, }, + { 61, 5, 1, 0x0000008000000000ull, 0x000001fe00001000ull, { 23, 19, 20, 0, 0 }, 0x40, 1397, }, + { 61, 5, 2, 0x0000008000000000ull, 0x000001fe00001000ull, { 23, 22, 19, 20, 0 }, 0x40, 1398, }, + { 61, 5, 2, 0x0000009000000000ull, 0x000001fe00001000ull, { 23, 22, 20, 19, 0 }, 0x0, 1405, }, + { 61, 5, 1, 0x0000009000000000ull, 0x000001fe00001000ull, { 23, 20, 19, 0, 0 }, 0x40, 1406, }, + { 61, 5, 2, 0x0000009000000000ull, 0x000001fe00001000ull, { 23, 22, 20, 19, 0 }, 0x40, 1407, }, + { 62, 5, 1, 0x00000000c0000000ull, 0x000001eff8000000ull, { 18, 19, 0, 0, 0 }, 0x0, 1042, }, + { 62, 5, 1, 0x00000000c0000000ull, 0x000001eff8000000ull, { 18, 19, 0, 0, 0 }, 0x40, 1043, }, + { 62, 5, 1, 0x00000000e0000000ull, 0x000001e3f8000000ull, { 18, 19, 0, 0, 0 }, 0x0, 3036, }, + { 62, 5, 1, 0x0000010008000000ull, 0x000001fff80fe000ull, { 18, 20, 0, 0, 0 }, 0x40, 3037, }, + { 63, 3, 1, 0x0000008488000000ull, 0x000001fff8000000ull, { 24, 28, 72, 0, 0 }, 0x0, 269, }, + { 64, 3, 1, 0x00000084c8000000ull, 0x000001fff8000000ull, { 24, 28, 72, 0, 0 }, 0x0, 270, }, + { 67, 3, 0, 0x0000000060000000ull, 0x000001eff8000000ull, { 0, 0, 0, 0, 0 }, 0x21, 33, }, + { 68, 5, 1, 0x0000010000000000ull, 0x000001fc00000000ull, { 18, 20, 21, 19, 0 }, 0x0, 2353, }, + { 68, 5, 1, 0x0000010000000000ull, 0x000001fc00000000ull, { 18, 20, 21, 19, 0 }, 0x40, 34, }, + { 69, 5, 1, 0x00000000a8000000ull, 0x000001eff8000000ull, { 18, 19, 20, 0, 0 }, 0x0, 2354, }, + { 69, 5, 1, 0x00000000a8000000ull, 0x000001eff8000000ull, { 18, 19, 20, 0, 0 }, 0x40, 35, }, + { 70, 5, 1, 0x0000000080000000ull, 0x000001e3f8000000ull, { 18, 19, 20, 0, 0 }, 0x0, 2247, }, + { 71, 5, 1, 0x00000000a0000000ull, 0x000001eff8000000ull, { 18, 19, 20, 0, 0 }, 0x0, 2355, }, + { 71, 5, 1, 0x00000000a0000000ull, 0x000001eff8000000ull, { 18, 19, 20, 0, 0 }, 0x40, 36, }, + { 72, 5, 1, 0x00000001c8000000ull, 0x000001e3f8000000ull, { 18, 19, 20, 0, 0 }, 0x0, 1221, }, + { 73, 5, 1, 0x0000010000000000ull, 0x000001fc000fe000ull, { 18, 20, 21, 0, 0 }, 0x40, 2358, }, + { 74, 5, 1, 0x0000014000000000ull, 0x000001fc00000000ull, { 18, 20, 21, 19, 0 }, 0x0, 2361, }, + { 74, 5, 1, 0x0000014000000000ull, 0x000001fc00000000ull, { 18, 20, 21, 19, 0 }, 0x40, 38, }, + { 75, 5, 1, 0x0000000088000000ull, 0x000001e3f8000000ull, { 18, 20, 0, 0, 0 }, 0xc0, 39, }, + { 76, 5, 1, 0x0000000088000000ull, 0x000001e3f80fe000ull, { 18, 20, 0, 0, 0 }, 0x40, 40, }, + { 77, 5, 1, 0x0000018000000000ull, 0x000001fc00000000ull, { 18, 20, 21, 19, 0 }, 0x0, 2364, }, + { 77, 5, 1, 0x0000018000000000ull, 0x000001fc00000000ull, { 18, 20, 21, 19, 0 }, 0x40, 41, }, + { 78, 5, 1, 0x0000018000000000ull, 0x000001fc000fe000ull, { 18, 20, 21, 0, 0 }, 0x40, 2367, }, + { 79, 5, 1, 0x0000010008000000ull, 0x000001fff80fe000ull, { 18, 20, 0, 0, 0 }, 0x40, 2370, }, + { 80, 5, 1, 0x0000000170000000ull, 0x000001e3f8000000ull, { 18, 19, 20, 0, 0 }, 0x0, 44, }, + { 81, 5, 1, 0x0000002080000000ull, 0x000001e3f80fe000ull, { 18, 20, 0, 0, 0 }, 0x40, 45, }, + { 82, 5, 1, 0x0000000140000000ull, 0x000001e3f8000000ull, { 18, 19, 20, 0, 0 }, 0x0, 46, }, + { 83, 5, 1, 0x00000020b8000000ull, 0x000001eff8000000ull, { 18, 19, 20, 0, 0 }, 0x0, 2371, }, + { 83, 5, 1, 0x00000020b8000000ull, 0x000001eff8000000ull, { 18, 19, 20, 0, 0 }, 0x40, 47, }, + { 84, 5, 1, 0x00000020b0000000ull, 0x000001eff8000000ull, { 18, 19, 20, 0, 0 }, 0x0, 2372, }, + { 84, 5, 1, 0x00000020b0000000ull, 0x000001eff8000000ull, { 18, 19, 20, 0, 0 }, 0x40, 48, }, + { 85, 5, 1, 0x0000002180000000ull, 0x000001eff8000000ull, { 18, 19, 20, 0, 0 }, 0x0, 946, }, + { 85, 5, 1, 0x0000002180000000ull, 0x000001eff8000000ull, { 18, 19, 20, 0, 0 }, 0x40, 947, }, + { 85, 5, 1, 0x0000002188000000ull, 0x000001eff8000000ull, { 18, 20, 19, 0, 0 }, 0x40, 1119, }, + { 86, 5, 1, 0x00000020c0000000ull, 0x000001eff8000000ull, { 18, 19, 0, 0, 0 }, 0x0, 1044, }, + { 86, 5, 1, 0x00000020c0000000ull, 0x000001eff8000000ull, { 18, 19, 0, 0, 0 }, 0x40, 1045, }, + { 87, 5, 1, 0x0000013000000000ull, 0x000001fc00000000ull, { 18, 20, 21, 19, 0 }, 0x0, 2389, }, + { 87, 5, 1, 0x0000013000000000ull, 0x000001fc00000000ull, { 18, 20, 21, 19, 0 }, 0x40, 49, }, + { 88, 5, 1, 0x00000020a8000000ull, 0x000001eff8000000ull, { 18, 19, 20, 0, 0 }, 0x0, 2390, }, + { 88, 5, 1, 0x00000020a8000000ull, 0x000001eff8000000ull, { 18, 19, 20, 0, 0 }, 0x40, 50, }, + { 89, 5, 1, 0x0000002080000000ull, 0x000001e3f8000000ull, { 18, 19, 20, 0, 0 }, 0x0, 2255, }, + { 90, 5, 1, 0x00000020a0000000ull, 0x000001eff8000000ull, { 18, 19, 20, 0, 0 }, 0x0, 2391, }, + { 90, 5, 1, 0x00000020a0000000ull, 0x000001eff8000000ull, { 18, 19, 20, 0, 0 }, 0x40, 51, }, + { 91, 5, 1, 0x0000013000000000ull, 0x000001fc000fe000ull, { 18, 20, 21, 0, 0 }, 0x40, 2392, }, + { 92, 5, 1, 0x0000017000000000ull, 0x000001fc00000000ull, { 18, 20, 21, 19, 0 }, 0x0, 2393, }, + { 92, 5, 1, 0x0000017000000000ull, 0x000001fc00000000ull, { 18, 20, 21, 19, 0 }, 0x40, 53, }, + { 93, 5, 1, 0x0000002088000000ull, 0x000001e3f8000000ull, { 18, 20, 0, 0, 0 }, 0xc0, 54, }, + { 94, 5, 1, 0x0000002088000000ull, 0x000001e3f80fe000ull, { 18, 20, 0, 0, 0 }, 0x40, 55, }, + { 95, 5, 1, 0x000001b000000000ull, 0x000001fc00000000ull, { 18, 20, 21, 19, 0 }, 0x0, 2394, }, + { 95, 5, 1, 0x000001b000000000ull, 0x000001fc00000000ull, { 18, 20, 21, 19, 0 }, 0x40, 56, }, + { 96, 5, 1, 0x000001b000000000ull, 0x000001fc000fe000ull, { 18, 20, 21, 0, 0 }, 0x40, 2395, }, + { 97, 5, 2, 0x0000002200000000ull, 0x000001fe00000000ull, { 18, 23, 19, 20, 0 }, 0x0, 2396, }, + { 97, 5, 2, 0x0000002200000000ull, 0x000001fe00000000ull, { 18, 23, 19, 20, 0 }, 0x40, 58, }, + { 98, 5, 2, 0x0000003200000000ull, 0x000001fe00000000ull, { 18, 23, 20, 0, 0 }, 0x0, 2397, }, + { 98, 5, 2, 0x0000003200000000ull, 0x000001fe00000000ull, { 18, 23, 20, 0, 0 }, 0x40, 59, }, + { 99, 5, 2, 0x0000000200000000ull, 0x000001fe00000000ull, { 18, 23, 19, 20, 0 }, 0x0, 2398, }, + { 99, 5, 2, 0x0000000200000000ull, 0x000001fe00000000ull, { 18, 23, 19, 20, 0 }, 0x40, 60, }, + { 100, 5, 2, 0x0000001200000000ull, 0x000001fe00000000ull, { 18, 23, 20, 0, 0 }, 0x0, 2399, }, + { 100, 5, 2, 0x0000001200000000ull, 0x000001fe00000000ull, { 18, 23, 20, 0, 0 }, 0x40, 61, }, + { 101, 5, 1, 0x000001c000000000ull, 0x000001f000000000ull, { 18, 20, 21, 19, 0 }, 0x0, 62, }, + { 102, 5, 0, 0x0000000020000000ull, 0x000001eff8000000ull, { 51, 52, 0, 0, 0 }, 0x0, 2400, }, + { 102, 5, 0, 0x0000000020000000ull, 0x000001eff8000000ull, { 51, 52, 0, 0, 0 }, 0x40, 63, }, + { 103, 5, 1, 0x0000014008000000ull, 0x000001fff8000000ull, { 18, 20, 19, 0, 0 }, 0x40, 2403, }, + { 104, 5, 1, 0x00000001a0000000ull, 0x000001e3f8000000ull, { 18, 19, 20, 0, 0 }, 0x0, 65, }, + { 105, 5, 1, 0x00000001e0000000ull, 0x000001e3f8000000ull, { 18, 19, 20, 0, 0 }, 0x0, 2202, }, + { 106, 3, 0, 0x0000000100000000ull, 0x000001eff8000000ull, { 0, 0, 0, 0, 0 }, 0x0, 66, }, + { 108, 5, 1, 0x0000000178000000ull, 0x000001e3f8000000ull, { 18, 19, 20, 0, 0 }, 0x0, 67, }, + { 113, 3, 1, 0x0000008708000000ull, 0x000001ffc8000000ull, { 24, 19, 0, 0, 0 }, 0x0, 2781, }, + { 118, 4, 0, 0x0000004008000000ull, 0x000001e1f8000000ull, { 66, 0, 0, 0, 0 }, 0x0, 538, }, + { 118, 5, 0, 0x000000000c000000ull, 0x000001e3fc000000ull, { 66, 0, 0, 0, 0 }, 0x0, 961, }, + { 118, 2, 0, 0x000000000c000000ull, 0x000001effc000000ull, { 66, 0, 0, 0, 0 }, 0x2, 1141, }, + { 118, 3, 0, 0x000000000c000000ull, 0x000001effc000000ull, { 66, 0, 0, 0, 0 }, 0x0, 1267, }, + { 118, 6, 0, 0x000000000c000000ull, 0x000001effc000000ull, { 70, 0, 0, 0, 0 }, 0x0, 3034, }, + { 118, 7, 0, 0x0000000000000000ull, 0x0000000000000000ull, { 66, 0, 0, 0, 0 }, 0x0, 68, }, + { 123, 3, 0, 0x0000000080000000ull, 0x000001eff8000000ull, { 0, 0, 0, 0, 0 }, 0x0, 69, }, + { 123, 3, 0, 0x0000000090000000ull, 0x000001eff8000000ull, { 24, 0, 0, 0, 0 }, 0x0, 920, }, + { 123, 3, 0, 0x0000000098000000ull, 0x000001eff8000000ull, { 18, 0, 0, 0, 0 }, 0x0, 921, }, + { 124, 3, 0, 0x0000002170000000ull, 0x000001eff8000000ull, { 25, 0, 0, 0, 0 }, 0xc, 846, }, + { 125, 3, 1, 0x0000002070000000ull, 0x000001eff8000000ull, { 31, 25, 0, 0, 0 }, 0x8, 847, }, + { 125, 3, 1, 0x0000002078000000ull, 0x000001eff8000000ull, { 32, 25, 0, 0, 0 }, 0x8, 1143, }, + { 127, 3, 1, 0x0000008000000000ull, 0x000001fff8000000ull, { 24, 28, 0, 0, 0 }, 0x0, 70, }, + { 127, 3, 1, 0x0000009000000000ull, 0x000001fff8000000ull, { 24, 28, 25, 0, 0 }, 0x400, 71, }, + { 127, 3, 1, 0x000000a000000000ull, 0x000001eff0000000ull, { 24, 28, 63, 0, 0 }, 0x400, 72, }, + { 128, 3, 2, 0x0000008a08000000ull, 0x000001fff8000000ull, { 24, 1, 28, 0, 0 }, 0x0, 73, }, + { 128, 3, 1, 0x0000008a08000000ull, 0x000001fff8000000ull, { 24, 28, 0, 0, 0 }, 0x40, 74, }, + { 129, 3, 1, 0x0000008040000000ull, 0x000001fff8000000ull, { 24, 28, 0, 0, 0 }, 0x0, 75, }, + { 129, 3, 1, 0x0000009040000000ull, 0x000001fff8000000ull, { 24, 28, 25, 0, 0 }, 0x400, 76, }, + { 129, 3, 1, 0x000000a040000000ull, 0x000001eff0000000ull, { 24, 28, 63, 0, 0 }, 0x400, 77, }, + { 130, 3, 1, 0x0000008080000000ull, 0x000001fff8000000ull, { 24, 28, 0, 0, 0 }, 0x0, 78, }, + { 130, 3, 1, 0x0000009080000000ull, 0x000001fff8000000ull, { 24, 28, 25, 0, 0 }, 0x400, 79, }, + { 130, 3, 1, 0x000000a080000000ull, 0x000001eff0000000ull, { 24, 28, 63, 0, 0 }, 0x400, 80, }, + { 131, 3, 1, 0x00000080c0000000ull, 0x000001fff8000000ull, { 24, 28, 0, 0, 0 }, 0x0, 81, }, + { 131, 3, 1, 0x00000080c0000000ull, 0x000001fff8000000ull, { 24, 28, 84, 0, 0 }, 0x0, 1339, }, + { 131, 3, 1, 0x00000090c0000000ull, 0x000001fff8000000ull, { 24, 28, 25, 0, 0 }, 0x400, 82, }, + { 131, 3, 1, 0x000000a0c0000000ull, 0x000001eff0000000ull, { 24, 28, 63, 0, 0 }, 0x400, 83, }, + { 132, 3, 1, 0x000000c6c0000000ull, 0x000001fff8000000ull, { 18, 28, 0, 0, 0 }, 0x0, 1039, }, + { 132, 3, 1, 0x000000d6c0000000ull, 0x000001fff8000000ull, { 18, 28, 25, 0, 0 }, 0x400, 1040, }, + { 132, 3, 1, 0x000000e6c0000000ull, 0x000001eff0000000ull, { 18, 28, 63, 0, 0 }, 0x400, 1041, }, + { 133, 3, 1, 0x000000c040000000ull, 0x000001fff8000000ull, { 18, 28, 0, 0, 0 }, 0x0, 84, }, + { 133, 3, 1, 0x000000d040000000ull, 0x000001fff8000000ull, { 18, 28, 25, 0, 0 }, 0x400, 85, }, + { 133, 3, 1, 0x000000e040000000ull, 0x000001eff0000000ull, { 18, 28, 63, 0, 0 }, 0x400, 86, }, + { 134, 3, 1, 0x000000c0c0000000ull, 0x000001fff8000000ull, { 18, 28, 0, 0, 0 }, 0x0, 87, }, + { 134, 3, 1, 0x000000d0c0000000ull, 0x000001fff8000000ull, { 18, 28, 25, 0, 0 }, 0x400, 88, }, + { 134, 3, 1, 0x000000e0c0000000ull, 0x000001eff0000000ull, { 18, 28, 63, 0, 0 }, 0x400, 89, }, + { 135, 3, 1, 0x000000c000000000ull, 0x000001fff8000000ull, { 18, 28, 0, 0, 0 }, 0x0, 90, }, + { 135, 3, 1, 0x000000d000000000ull, 0x000001fff8000000ull, { 18, 28, 25, 0, 0 }, 0x400, 91, }, + { 135, 3, 1, 0x000000e000000000ull, 0x000001eff0000000ull, { 18, 28, 63, 0, 0 }, 0x400, 92, }, + { 136, 3, 2, 0x000000c048000000ull, 0x000001fff8000000ull, { 18, 19, 28, 0, 0 }, 0x0, 93, }, + { 136, 3, 2, 0x000000d048000000ull, 0x000001fff8000000ull, { 18, 19, 28, 6, 0 }, 0x400, 94, }, + { 137, 3, 2, 0x000000c0c8000000ull, 0x000001fff8000000ull, { 18, 19, 28, 0, 0 }, 0x0, 95, }, + { 137, 3, 2, 0x000000d0c8000000ull, 0x000001fff8000000ull, { 18, 19, 28, 6, 0 }, 0x400, 96, }, + { 138, 3, 2, 0x000000c088000000ull, 0x000001fff8000000ull, { 18, 19, 28, 0, 0 }, 0x0, 97, }, + { 138, 3, 2, 0x000000d088000000ull, 0x000001fff8000000ull, { 18, 19, 28, 5, 0 }, 0x400, 98, }, + { 139, 3, 1, 0x000000c080000000ull, 0x000001fff8000000ull, { 18, 28, 0, 0, 0 }, 0x0, 99, }, + { 139, 3, 1, 0x000000d080000000ull, 0x000001fff8000000ull, { 18, 28, 25, 0, 0 }, 0x400, 100, }, + { 139, 3, 1, 0x000000e080000000ull, 0x000001eff0000000ull, { 18, 28, 63, 0, 0 }, 0x400, 101, }, + { 142, 3, 0, 0x000000cb00000000ull, 0x000001fff8000000ull, { 28, 0, 0, 0, 0 }, 0x0, 102, }, + { 142, 3, 0, 0x000000db00000000ull, 0x000001fff8000000ull, { 28, 25, 0, 0, 0 }, 0x400, 103, }, + { 142, 3, 0, 0x000000eb00000000ull, 0x000001eff0000000ull, { 28, 63, 0, 0, 0 }, 0x400, 104, }, + { 143, 3, 0, 0x0000000050000000ull, 0x000001eff8000000ull, { 0, 0, 0, 0, 0 }, 0x21, 105, }, + { 151, 3, 0, 0x0000000110000000ull, 0x000001eff8000000ull, { 0, 0, 0, 0, 0 }, 0x0, 106, }, + { 152, 2, 1, 0x000000e880000000ull, 0x000001fff0000000ull, { 24, 25, 26, 0, 0 }, 0x0, 2203, }, + { 153, 2, 1, 0x000000ea80000000ull, 0x000001fff0000000ull, { 24, 25, 26, 0, 0 }, 0x0, 2204, }, + { 154, 2, 1, 0x000000f880000000ull, 0x000001fff0000000ull, { 24, 25, 26, 0, 0 }, 0x0, 2205, }, + { 155, 1, 1, 0x0000010800000000ull, 0x000001fff80fe000ull, { 24, 26, 0, 0, 0 }, 0x0, 107, }, + { 155, 1, 1, 0x0000012000000000ull, 0x000001e000300000ull, { 24, 67, 0, 0, 0 }, 0x40, 108, }, + { 155, 5, 1, 0x0000000080000000ull, 0x000001e3f8000000ull, { 18, 20, 0, 0, 0 }, 0xc0, 109, }, + { 155, 2, 1, 0x0000000e00100000ull, 0x000001ee00f00000ull, { 15, 25, 0, 0, 0 }, 0x40, 110, }, + { 155, 2, 1, 0x0000000e00000000ull, 0x000001ee00f00000ull, { 15, 25, 79, 0, 0 }, 0x0, 2855, }, + { 155, 2, 1, 0x0000000188000000ull, 0x000001eff8000000ull, { 24, 16, 0, 0, 0 }, 0x0, 112, }, + { 155, 2, 1, 0x0000000600000000ull, 0x000001ee00000000ull, { 9, 25, 65, 0, 0 }, 0x0, 113, }, + { 155, 2, 1, 0x00000016ff001fc0ull, 0x000001feff001fc0ull, { 9, 25, 0, 0, 0 }, 0x40, 114, }, + { 155, 2, 1, 0x0000000400000000ull, 0x000001ee00000000ull, { 10, 69, 0, 0, 0 }, 0x0, 115, }, + { 155, 2, 1, 0x0000000180000000ull, 0x000001eff8000000ull, { 24, 8, 0, 0, 0 }, 0x0, 116, }, + { 155, 2, 1, 0x0000000198000000ull, 0x000001eff8000000ull, { 24, 9, 0, 0, 0 }, 0x0, 117, }, + { 155, 2, 1, 0x0000000150000000ull, 0x000001eff8000000ull, { 14, 25, 0, 0, 0 }, 0x0, 1144, }, + { 155, 2, 1, 0x0000000050000000ull, 0x000001eff8000000ull, { 14, 56, 0, 0, 0 }, 0x0, 1145, }, + { 155, 2, 1, 0x0000000190000000ull, 0x000001eff8000000ull, { 24, 14, 0, 0, 0 }, 0x0, 1146, }, + { 155, 3, 1, 0x0000000140000000ull, 0x000001eff8000000ull, { 14, 56, 0, 0, 0 }, 0x0, 1268, }, + { 155, 3, 1, 0x0000002150000000ull, 0x000001eff8000000ull, { 14, 25, 0, 0, 0 }, 0x0, 1269, }, + { 155, 3, 1, 0x0000002110000000ull, 0x000001eff8000000ull, { 24, 14, 0, 0, 0 }, 0x0, 1270, }, + { 155, 3, 1, 0x0000002160000000ull, 0x000001eff8000000ull, { 17, 25, 0, 0, 0 }, 0x8, 118, }, + { 155, 3, 1, 0x0000002120000000ull, 0x000001eff8000000ull, { 24, 17, 0, 0, 0 }, 0x8, 119, }, + { 155, 3, 1, 0x0000002168000000ull, 0x000001eff8000000ull, { 12, 25, 0, 0, 0 }, 0x8, 120, }, + { 155, 3, 1, 0x0000002148000000ull, 0x000001eff8000000ull, { 13, 25, 0, 0, 0 }, 0x0, 121, }, + { 155, 3, 1, 0x0000002128000000ull, 0x000001eff8000000ull, { 24, 11, 0, 0, 0 }, 0x8, 122, }, + { 155, 3, 1, 0x0000002108000000ull, 0x000001eff8000000ull, { 24, 13, 0, 0, 0 }, 0x0, 123, }, + { 155, 3, 1, 0x0000002000000000ull, 0x000001eff8000000ull, { 38, 25, 0, 0, 0 }, 0x8, 124, }, + { 155, 3, 1, 0x0000002008000000ull, 0x000001eff8000000ull, { 30, 25, 0, 0, 0 }, 0x8, 125, }, + { 155, 3, 1, 0x0000002010000000ull, 0x000001eff8000000ull, { 33, 25, 0, 0, 0 }, 0x8, 126, }, + { 155, 3, 1, 0x0000002018000000ull, 0x000001eff8000000ull, { 35, 25, 0, 0, 0 }, 0x8, 127, }, + { 155, 3, 1, 0x0000002020000000ull, 0x000001eff8000000ull, { 36, 25, 0, 0, 0 }, 0x8, 128, }, + { 155, 3, 1, 0x0000002028000000ull, 0x000001eff8000000ull, { 37, 25, 0, 0, 0 }, 0x8, 129, }, + { 155, 3, 1, 0x0000002030000000ull, 0x000001eff8000000ull, { 34, 25, 0, 0, 0 }, 0x8, 130, }, + { 155, 3, 1, 0x0000002080000000ull, 0x000001eff8000000ull, { 24, 38, 0, 0, 0 }, 0x8, 131, }, + { 155, 3, 1, 0x0000002088000000ull, 0x000001eff8000000ull, { 24, 30, 0, 0, 0 }, 0x8, 132, }, + { 155, 3, 1, 0x0000002090000000ull, 0x000001eff8000000ull, { 24, 33, 0, 0, 0 }, 0x8, 133, }, + { 155, 3, 1, 0x0000002098000000ull, 0x000001eff8000000ull, { 24, 35, 0, 0, 0 }, 0x8, 134, }, + { 155, 3, 1, 0x00000020a0000000ull, 0x000001eff8000000ull, { 24, 36, 0, 0, 0 }, 0x8, 135, }, + { 155, 3, 1, 0x00000020a8000000ull, 0x000001eff8000000ull, { 24, 37, 0, 0, 0 }, 0x0, 136, }, + { 155, 3, 1, 0x00000020b0000000ull, 0x000001eff8000000ull, { 24, 34, 0, 0, 0 }, 0x8, 137, }, + { 155, 3, 1, 0x00000020b8000000ull, 0x000001eff8000000ull, { 24, 29, 0, 0, 0 }, 0x0, 138, }, + { 155, 7, 1, 0x0000000000000000ull, 0x0000000000000000ull, { 24, 14, 0, 0, 0 }, 0x0, 139, }, + { 155, 7, 1, 0x0000000000000000ull, 0x0000000000000000ull, { 14, 56, 0, 0, 0 }, 0x0, 140, }, + { 155, 7, 1, 0x0000000000000000ull, 0x0000000000000000ull, { 14, 25, 0, 0, 0 }, 0x0, 141, }, + { 156, 6, 1, 0x000000c000000000ull, 0x000001e000100000ull, { 24, 71, 0, 0, 0 }, 0x0, 142, }, + { 157, 2, 1, 0x000000eca0000000ull, 0x000001fff0000000ull, { 24, 25, 75, 0, 0 }, 0x0, 143, }, + { 158, 2, 1, 0x000000eea0000000ull, 0x000001fff0000000ull, { 24, 25, 76, 0, 0 }, 0x0, 144, }, + { 168, 4, 0, 0x0000004000000000ull, 0x000001e1f8000000ull, { 66, 0, 0, 0, 0 }, 0x0, 539, }, + { 168, 5, 0, 0x0000000008000000ull, 0x000001e3fc000000ull, { 66, 0, 0, 0, 0 }, 0x0, 962, }, + { 168, 2, 0, 0x0000000008000000ull, 0x000001effc000000ull, { 66, 0, 0, 0, 0 }, 0x2, 1147, }, + { 168, 3, 0, 0x0000000008000000ull, 0x000001effc000000ull, { 66, 0, 0, 0, 0 }, 0x0, 1271, }, + { 168, 6, 0, 0x0000000008000000ull, 0x000001effc000000ull, { 70, 0, 0, 0, 0 }, 0x0, 3035, }, + { 168, 7, 0, 0x0000000000000000ull, 0x0000000000000000ull, { 66, 0, 0, 0, 0 }, 0x0, 145, }, + { 175, 1, 1, 0x0000010070000000ull, 0x000001eff8000000ull, { 24, 25, 26, 0, 0 }, 0x0, 146, }, + { 175, 1, 1, 0x0000010170000000ull, 0x000001eff8000000ull, { 24, 56, 26, 0, 0 }, 0x0, 147, }, + { 178, 2, 1, 0x000000ea00000000ull, 0x000001fff0000000ull, { 24, 25, 26, 0, 0 }, 0x0, 3017, }, + { 179, 2, 1, 0x000000f820000000ull, 0x000001fff0000000ull, { 24, 25, 26, 0, 0 }, 0x0, 2857, }, + { 180, 1, 1, 0x0000010400000000ull, 0x000001fff8000000ull, { 24, 25, 26, 0, 0 }, 0x0, 148, }, + { 181, 1, 1, 0x0000010600000000ull, 0x000001fff8000000ull, { 24, 25, 26, 0, 0 }, 0x0, 149, }, + { 182, 1, 1, 0x0000011400000000ull, 0x000001fff8000000ull, { 24, 25, 26, 0, 0 }, 0x0, 150, }, + { 183, 1, 1, 0x0000010450000000ull, 0x000001fff8000000ull, { 24, 25, 26, 0, 0 }, 0x0, 151, }, + { 184, 1, 1, 0x0000010650000000ull, 0x000001fff8000000ull, { 24, 25, 26, 0, 0 }, 0x0, 152, }, + { 185, 1, 1, 0x0000010470000000ull, 0x000001fff8000000ull, { 24, 25, 26, 0, 0 }, 0x0, 153, }, + { 186, 1, 1, 0x0000010670000000ull, 0x000001fff8000000ull, { 24, 25, 26, 0, 0 }, 0x0, 154, }, + { 187, 1, 1, 0x0000010520000000ull, 0x000001fff8000000ull, { 24, 25, 26, 0, 0 }, 0x0, 948, }, + { 188, 1, 1, 0x0000010720000000ull, 0x000001fff8000000ull, { 24, 25, 26, 0, 0 }, 0x0, 949, }, + { 189, 1, 1, 0x0000011520000000ull, 0x000001fff8000000ull, { 24, 25, 26, 0, 0 }, 0x0, 950, }, + { 190, 2, 1, 0x000000e850000000ull, 0x000001fff0000000ull, { 24, 25, 26, 0, 0 }, 0x0, 2871, }, + { 191, 2, 1, 0x000000ea70000000ull, 0x000001fff0000000ull, { 24, 25, 26, 0, 0 }, 0x0, 155, }, + { 192, 2, 1, 0x000000e810000000ull, 0x000001fff0000000ull, { 24, 25, 26, 0, 0 }, 0x0, 2872, }, + { 193, 2, 1, 0x000000ea30000000ull, 0x000001fff0000000ull, { 24, 25, 26, 0, 0 }, 0x0, 156, }, + { 194, 2, 1, 0x000000ead0000000ull, 0x000001fff0000000ull, { 24, 25, 26, 0, 0 }, 0x0, 2206, }, + { 195, 2, 1, 0x000000e230000000ull, 0x000001ff30000000ull, { 24, 25, 26, 42, 0 }, 0x0, 157, }, + { 196, 2, 1, 0x000000e690000000ull, 0x000001fff0000000ull, { 24, 26, 0, 0, 0 }, 0x0, 158, }, + { 198, 3, 1, 0x00000021c0000000ull, 0x000001eff8000000ull, { 24, 26, 25, 0, 0 }, 0x0, 2207, }, + { 198, 3, 1, 0x00000020c0000000ull, 0x000001eff8000000ull, { 24, 26, 49, 0, 0 }, 0x0, 2208, }, + { 198, 3, 0, 0x0000002188000000ull, 0x000001eff8000000ull, { 26, 49, 0, 0, 0 }, 0x0, 2238, }, + { 199, 2, 1, 0x000000e8b0000000ull, 0x000001fff0000000ull, { 24, 25, 26, 0, 0 }, 0x0, 159, }, + { 200, 2, 1, 0x000000e240000000ull, 0x000001fff0000000ull, { 24, 25, 26, 0, 0 }, 0x0, 160, }, + { 200, 2, 1, 0x000000ee50000000ull, 0x000001fff0000000ull, { 24, 25, 39, 0, 0 }, 0x0, 161, }, + { 201, 2, 1, 0x000000f040000000ull, 0x000001fff0000000ull, { 24, 25, 26, 0, 0 }, 0x0, 162, }, + { 201, 2, 1, 0x000000fc50000000ull, 0x000001fff0000000ull, { 24, 25, 39, 0, 0 }, 0x0, 163, }, + { 202, 1, 1, 0x0000010680000000ull, 0x000001ffe0000000ull, { 24, 25, 41, 26, 0 }, 0x0, 164, }, + { 203, 2, 1, 0x000000e220000000ull, 0x000001fff0000000ull, { 24, 26, 25, 0, 0 }, 0x0, 165, }, + { 203, 2, 1, 0x000000e630000000ull, 0x000001fff0000000ull, { 24, 26, 43, 0, 0 }, 0x0, 166, }, + { 204, 2, 1, 0x000000f020000000ull, 0x000001fff0000000ull, { 24, 26, 25, 0, 0 }, 0x0, 167, }, + { 204, 2, 1, 0x000000f430000000ull, 0x000001fff0000000ull, { 24, 26, 43, 0, 0 }, 0x0, 168, }, + { 205, 1, 1, 0x00000106c0000000ull, 0x000001ffe0000000ull, { 24, 25, 41, 26, 0 }, 0x0, 169, }, + { 206, 1, 1, 0x0000010420000000ull, 0x000001fff8000000ull, { 24, 25, 26, 0, 0 }, 0x0, 170, }, + { 207, 1, 1, 0x0000010620000000ull, 0x000001fff8000000ull, { 24, 25, 26, 0, 0 }, 0x0, 171, }, + { 208, 1, 1, 0x0000011420000000ull, 0x000001fff8000000ull, { 24, 25, 26, 0, 0 }, 0x0, 172, }, + { 209, 3, 0, 0x0000002048000000ull, 0x000001eff8000000ull, { 26, 25, 0, 0, 0 }, 0x8, 1175, }, + { 209, 3, 0, 0x0000002050000000ull, 0x000001eff8000000ull, { 26, 25, 0, 0, 0 }, 0xc, 1050, }, + { 209, 3, 0, 0x00000021a0000000ull, 0x000001eff8000000ull, { 26, 0, 0, 0, 0 }, 0x8, 922, }, + { 210, 3, 0, 0x0000002060000000ull, 0x000001eff8000000ull, { 26, 25, 0, 0, 0 }, 0x8, 848, }, + { 215, 4, 0, 0x0000000040000000ull, 0x000001e1f8000000ull, { 0, 0, 0, 0, 0 }, 0x22c, 173, }, + { 216, 3, 0, 0x0000000038000000ull, 0x000001ee78000000ull, { 68, 0, 0, 0, 0 }, 0x8, 174, }, + { 217, 3, 0, 0x0000000028000000ull, 0x000001ee78000000ull, { 68, 0, 0, 0, 0 }, 0x0, 175, }, + { 226, 3, 1, 0x000000c708000000ull, 0x000001ffc8000000ull, { 18, 25, 0, 0, 0 }, 0x0, 2782, }, + { 227, 2, 1, 0x000000a600000000ull, 0x000001ee04000000ull, { 24, 25, 45, 0, 0 }, 0x140, 176, }, + { 227, 2, 1, 0x000000f240000000ull, 0x000001fff0000000ull, { 24, 25, 26, 0, 0 }, 0x0, 177, }, + { 228, 1, 1, 0x0000010080000000ull, 0x000001efe0000000ull, { 24, 25, 40, 26, 0 }, 0x0, 178, }, + { 229, 1, 1, 0x00000100c0000000ull, 0x000001efe0000000ull, { 24, 25, 40, 26, 0 }, 0x0, 179, }, + { 230, 2, 1, 0x000000a400000000ull, 0x000001ee00002000ull, { 24, 26, 77, 0, 0 }, 0x140, 2878, }, + { 230, 2, 1, 0x000000f220000000ull, 0x000001fff0000000ull, { 24, 26, 25, 0, 0 }, 0x0, 181, }, + { 231, 2, 1, 0x000000ac00000000ull, 0x000001ee00000000ull, { 24, 25, 26, 44, 0 }, 0x0, 182, }, + { 236, 3, 0, 0x0000000180000000ull, 0x000001eff8000000ull, { 0, 0, 0, 0, 0 }, 0x0, 850, }, + { 237, 3, 0, 0x0000000030000000ull, 0x000001ee78000000ull, { 68, 0, 0, 0, 0 }, 0x8, 183, }, + { 239, 3, 1, 0x0000008c00000000ull, 0x000001fff8000000ull, { 28, 25, 0, 0, 0 }, 0x0, 184, }, + { 239, 3, 1, 0x000000ac00000000ull, 0x000001eff0000000ull, { 28, 25, 62, 0, 0 }, 0x400, 185, }, + { 240, 3, 1, 0x0000008c08000000ull, 0x000001fff8000000ull, { 28, 25, 1, 0, 0 }, 0x0, 186, }, + { 240, 3, 1, 0x0000008c08000000ull, 0x000001fff8000000ull, { 28, 25, 0, 0, 0 }, 0x40, 187, }, + { 241, 3, 1, 0x0000008c40000000ull, 0x000001fff8000000ull, { 28, 25, 0, 0, 0 }, 0x0, 188, }, + { 241, 3, 1, 0x000000ac40000000ull, 0x000001eff0000000ull, { 28, 25, 62, 0, 0 }, 0x400, 189, }, + { 242, 3, 1, 0x0000008c80000000ull, 0x000001fff8000000ull, { 28, 25, 0, 0, 0 }, 0x0, 190, }, + { 242, 3, 1, 0x000000ac80000000ull, 0x000001eff0000000ull, { 28, 25, 62, 0, 0 }, 0x400, 191, }, + { 243, 3, 1, 0x0000008cc0000000ull, 0x000001fff8000000ull, { 28, 25, 0, 0, 0 }, 0x0, 192, }, + { 243, 3, 1, 0x000000acc0000000ull, 0x000001eff0000000ull, { 28, 25, 62, 0, 0 }, 0x400, 193, }, + { 244, 3, 1, 0x000000cec0000000ull, 0x000001fff8000000ull, { 28, 19, 0, 0, 0 }, 0x0, 2785, }, + { 244, 3, 1, 0x000000eec0000000ull, 0x000001eff0000000ull, { 28, 19, 62, 0, 0 }, 0x400, 2786, }, + { 245, 3, 1, 0x000000cc40000000ull, 0x000001fff8000000ull, { 28, 19, 0, 0, 0 }, 0x0, 194, }, + { 245, 3, 1, 0x000000ec40000000ull, 0x000001eff0000000ull, { 28, 19, 62, 0, 0 }, 0x400, 195, }, + { 246, 3, 1, 0x000000ccc0000000ull, 0x000001fff8000000ull, { 28, 19, 0, 0, 0 }, 0x0, 196, }, + { 246, 3, 1, 0x000000ecc0000000ull, 0x000001eff0000000ull, { 28, 19, 62, 0, 0 }, 0x400, 197, }, + { 247, 3, 1, 0x000000cc00000000ull, 0x000001fff8000000ull, { 28, 19, 0, 0, 0 }, 0x0, 198, }, + { 247, 3, 1, 0x000000ec00000000ull, 0x000001eff0000000ull, { 28, 19, 62, 0, 0 }, 0x400, 199, }, + { 248, 3, 1, 0x000000cc80000000ull, 0x000001fff8000000ull, { 28, 19, 0, 0, 0 }, 0x0, 200, }, + { 248, 3, 1, 0x000000ec80000000ull, 0x000001eff0000000ull, { 28, 19, 62, 0, 0 }, 0x400, 201, }, + { 249, 1, 1, 0x0000010028000000ull, 0x000001eff8000000ull, { 24, 25, 26, 0, 0 }, 0x0, 202, }, + { 249, 1, 1, 0x0000010020000000ull, 0x000001eff8000000ull, { 24, 25, 26, 4, 0 }, 0x0, 203, }, + { 249, 1, 1, 0x0000010128000000ull, 0x000001eff8000000ull, { 24, 56, 26, 0, 0 }, 0x0, 204, }, + { 250, 3, 0, 0x0000000020000000ull, 0x000001ee78000000ull, { 68, 0, 0, 0, 0 }, 0x0, 205, }, + { 251, 2, 1, 0x00000000a0000000ull, 0x000001eff8000000ull, { 24, 26, 0, 0, 0 }, 0x0, 206, }, + { 252, 2, 1, 0x00000000a8000000ull, 0x000001eff8000000ull, { 24, 26, 0, 0, 0 }, 0x0, 207, }, + { 253, 2, 1, 0x00000000b0000000ull, 0x000001eff8000000ull, { 24, 26, 0, 0, 0 }, 0x0, 208, }, + { 254, 3, 0, 0x0000000198000000ull, 0x000001eff8000000ull, { 0, 0, 0, 0, 0 }, 0x0, 1150, }, + { 255, 3, 1, 0x00000020f8000000ull, 0x000001eff8000000ull, { 24, 26, 0, 0, 0 }, 0x8, 209, }, + { 256, 2, 2, 0x000000a000000000ull, 0x000001fe00003000ull, { 22, 23, 26, 77, 0 }, 0x0, 3040, }, + { 256, 2, 1, 0x000000a000000000ull, 0x000001fe00003000ull, { 22, 26, 77, 0, 0 }, 0x40, 3041, }, + { 256, 2, 2, 0x000000a000000000ull, 0x000001fe00003000ull, { 23, 22, 26, 77, 0 }, 0x40, 2003, }, + { 256, 2, 1, 0x000000a000000000ull, 0x000001fe00003000ull, { 23, 26, 77, 0, 0 }, 0x40, 2004, }, + { 257, 2, 2, 0x000000a000082000ull, 0x000001fe00083000ull, { 22, 23, 50, 0, 0 }, 0x0, 3044, }, + { 257, 2, 1, 0x000000a000082000ull, 0x000001fe00083000ull, { 22, 50, 0, 0, 0 }, 0x40, 3045, }, + { 257, 2, 2, 0x000000a000082000ull, 0x000001fe00083000ull, { 23, 22, 50, 0, 0 }, 0x40, 2007, }, + { 257, 2, 1, 0x000000a000082000ull, 0x000001fe00083000ull, { 23, 50, 0, 0, 0 }, 0x40, 2008, }, + { 258, 3, 1, 0x00000020d0000000ull, 0x000001eff8000000ull, { 24, 26, 0, 0, 0 }, 0x0, 210, }, + { 259, 2, 2, 0x000000a000002000ull, 0x000001fe00003000ull, { 22, 23, 26, 0, 0 }, 0x0, 3048, }, + { 259, 2, 1, 0x000000a000002000ull, 0x000001fe00003000ull, { 22, 26, 0, 0, 0 }, 0x40, 3049, }, + { 259, 2, 2, 0x000000a000002000ull, 0x000001fe00003000ull, { 23, 22, 26, 0, 0 }, 0x40, 2011, }, + { 259, 2, 1, 0x000000a000002000ull, 0x000001fe00003000ull, { 23, 26, 0, 0, 0 }, 0x40, 2012, }, + { 260, 3, 1, 0x00000020f0000000ull, 0x000001eff8000000ull, { 24, 26, 0, 0, 0 }, 0x8, 211, }, + { 262, 3, 1, 0x00000020d8000000ull, 0x000001eff8000000ull, { 24, 26, 0, 0, 0 }, 0x0, 212, }, + { 266, 2, 1, 0x000000e840000000ull, 0x000001fff0000000ull, { 24, 25, 26, 0, 0 }, 0x0, 1131, }, + { 267, 2, 1, 0x000000ea40000000ull, 0x000001fff0000000ull, { 24, 25, 26, 0, 0 }, 0x0, 1132, }, + { 268, 2, 1, 0x000000f840000000ull, 0x000001fff0000000ull, { 24, 25, 26, 0, 0 }, 0x0, 1133, }, + { 272, 4, 0, 0x00000000c0000000ull, 0x000001e1f8000000ull, { 0, 0, 0, 0, 0 }, 0x28, 223, }, + { 277, 3, 1, 0x0000008208000000ull, 0x000001fff8000000ull, { 24, 28, 25, 0, 0 }, 0x0, 213, }, + { 278, 3, 1, 0x0000008248000000ull, 0x000001fff8000000ull, { 24, 28, 25, 0, 0 }, 0x0, 214, }, + { 279, 3, 1, 0x0000008288000000ull, 0x000001fff8000000ull, { 24, 28, 25, 0, 0 }, 0x0, 215, }, + { 280, 3, 1, 0x00000082c8000000ull, 0x000001fff8000000ull, { 24, 28, 25, 0, 0 }, 0x0, 216, }, + { 282, 5, 1, 0x000001d000000000ull, 0x000001fc00000000ull, { 18, 20, 21, 19, 0 }, 0x0, 1179, }, + { 282, 5, 1, 0x000001d000000000ull, 0x000001fc00000000ull, { 18, 20, 21, 19, 0 }, 0x40, 1261, }, + { 283, 5, 1, 0x000001d000000000ull, 0x000001fc000fe000ull, { 18, 20, 21, 0, 0 }, 0x40, 1180, }, + { 284, 1, 1, 0x0000010078000000ull, 0x000001eff8000000ull, { 24, 25, 26, 0, 0 }, 0x0, 217, }, + { 284, 1, 1, 0x0000010178000000ull, 0x000001eff8000000ull, { 24, 56, 26, 0, 0 }, 0x0, 218, }, + { 287, 2, 1, 0x0000000080000000ull, 0x000001eff8000000ull, { 24, 26, 0, 0, 0 }, 0x0, 219, }, + { 288, 2, 1, 0x0000000088000000ull, 0x000001eff8000000ull, { 24, 26, 0, 0, 0 }, 0x0, 220, }, + { 289, 2, 1, 0x0000000090000000ull, 0x000001eff8000000ull, { 24, 26, 0, 0, 0 }, 0x0, 221, }, +}; + +static const char dis_table[] = { +0xa0, 0xc7, 0xc8, 0xa0, 0x2e, 0xd8, 0xa0, 0x2c, 0xc0, 0xa0, 0x1c, 0x00, +0x98, 0xb0, 0x02, 0x50, 0x90, 0x50, 0x90, 0x28, 0x24, 0x39, 0x28, 0x24, +0x39, 0x20, 0x90, 0x28, 0x24, 0x39, 0x18, 0x24, 0x39, 0x10, 0x91, 0x60, +0x90, 0x28, 0x24, 0x39, 0x00, 0x10, 0x10, 0x58, 0x41, 0x61, 0xc7, 0xc0, +0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, +0x10, 0x10, 0x52, 0xc0, 0xc0, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, +0x10, 0x10, 0x10, 0x24, 0x24, 0x70, 0x90, 0x28, 0x24, 0x38, 0xf0, 0x24, +0x38, 0xe8, 0xa8, 0x0b, 0x48, 0x15, 0x20, 0x97, 0x20, 0x95, 0xc8, 0x9a, +0xb8, 0x05, 0x38, 0x91, 0x18, 0x90, 0xa0, 0x90, 0x60, 0x80, 0x90, 0x20, +0x34, 0xa6, 0xa4, 0x25, 0x00, 0x34, 0xa3, 0x80, 0xa4, 0x36, 0xa0, 0x36, +0xd9, 0x90, 0x50, 0x90, 0x28, 0x80, 0x36, 0xcf, 0x80, 0x34, 0x86, 0x81, +0x33, 0xe2, 0x90, 0xe0, 0x90, 0x70, 0x90, 0x38, 0xa4, 0x24, 0x10, 0x34, +0x83, 0xa4, 0x1f, 0x08, 0x34, 0x80, 0x90, 0x38, 0xa4, 0x38, 0xa0, 0x37, +0x1a, 0xa4, 0x38, 0x48, 0x37, 0x0e, 0x90, 0x70, 0x90, 0x38, 0xa4, 0x37, +0x20, 0x36, 0xef, 0xa4, 0x36, 0xf8, 0x36, 0xea, 0x80, 0xa4, 0x23, 0xf0, +0x34, 0x7f, 0x92, 0x18, 0x91, 0xc0, 0x80, 0x91, 0x80, 0x90, 0xf8, 0xdb, +0x84, 0x60, 0xf9, 0x40, 0xc0, 0xc0, 0x80, 0xa4, 0x42, 0x68, 0x8c, 0x43, +0xc8, 0x84, 0x38, 0x83, 0xc0, 0xc0, 0x80, 0xa4, 0x42, 0x58, 0x8c, 0x43, +0xa8, 0x84, 0x38, 0x81, 0xd3, 0x82, 0x40, 0x50, 0xc0, 0xc0, 0x81, 0x38, +0x35, 0x50, 0xc0, 0xc0, 0x81, 0x38, 0x33, 0xa4, 0x1f, 0x18, 0x33, 0xe4, +0x80, 0x90, 0x28, 0x80, 0x33, 0xe0, 0x80, 0x34, 0x88, 0x81, 0x90, 0x38, +0xa4, 0x24, 0x80, 0x34, 0x8b, 0xa4, 0x24, 0x48, 0x34, 0x85, 0xc0, 0x40, +0x10, 0x10, 0x90, 0x38, 0xa4, 0x1e, 0xf0, 0x33, 0xdf, 0xa4, 0x1e, 0xe0, +0x33, 0xdd, 0x18, 0x24, 0x24, 0xf8, 0x83, 0x90, 0xa8, 0xd3, 0x82, 0xc0, +0xc0, 0xc0, 0x80, 0xa4, 0x42, 0x38, 0x38, 0x6d, 0xc0, 0xc0, 0x80, 0xa4, +0x42, 0x28, 0x38, 0x69, 0xd3, 0x82, 0x40, 0x50, 0xc0, 0xc0, 0x81, 0x38, +0x2f, 0x50, 0xc0, 0xc0, 0x81, 0x38, 0x2d, 0x92, 0xb8, 0x99, 0x84, 0x24, +0x68, 0x90, 0x78, 0x90, 0x50, 0x10, 0x10, 0x80, 0xa4, 0x36, 0x98, 0x36, +0xd8, 0x82, 0x36, 0xce, 0x90, 0x80, 0x10, 0x10, 0x90, 0x38, 0xa4, 0x38, +0x98, 0x37, 0x19, 0xa4, 0x38, 0x40, 0x37, 0x0d, 0x80, 0x90, 0x38, 0xa4, +0x37, 0x18, 0x36, 0xee, 0xa4, 0x36, 0xf0, 0x36, 0xe9, 0x83, 0x90, 0xa8, +0xd3, 0x82, 0xc0, 0xc0, 0xc0, 0x80, 0xa4, 0x42, 0x08, 0x38, 0x61, 0xc0, +0xc0, 0x80, 0xa4, 0x41, 0xf8, 0x38, 0x5d, 0xd3, 0x82, 0x40, 0x50, 0xc0, +0xc0, 0x81, 0x38, 0x29, 0x50, 0xc0, 0xc0, 0x81, 0x38, 0x27, 0x18, 0x24, +0x24, 0x78, 0x83, 0x90, 0xa8, 0xd3, 0x82, 0xc0, 0xc0, 0xc0, 0x80, 0xa4, +0x41, 0xd8, 0x38, 0x55, 0xc0, 0xc0, 0x80, 0xa4, 0x41, 0xc8, 0x38, 0x51, +0xd3, 0x82, 0x40, 0x50, 0xc0, 0xc0, 0x81, 0x38, 0x23, 0x50, 0xc0, 0xc0, +0x81, 0x38, 0x21, 0x94, 0x50, 0x92, 0xf8, 0x99, 0x84, 0x1f, 0x48, 0x90, +0x78, 0x90, 0x50, 0x10, 0x10, 0x80, 0xa4, 0x36, 0x90, 0x36, 0xd7, 0x82, +0x36, 0xcd, 0x90, 0x80, 0x10, 0x10, 0x90, 0x38, 0xa4, 0x38, 0x90, 0x37, +0x18, 0xa4, 0x38, 0x38, 0x37, 0x0c, 0x80, 0x90, 0x38, 0xa4, 0x37, 0x10, +0x36, 0xed, 0xa4, 0x36, 0xe8, 0x36, 0xe8, 0x83, 0x90, 0xe8, 0xd3, 0x83, +0xc0, 0xc0, 0xc0, 0x80, 0xa4, 0x42, 0x78, 0x8c, 0x43, 0xe8, 0x84, 0x38, +0x85, 0xc0, 0xc0, 0x80, 0xa4, 0x42, 0x60, 0x8c, 0x43, 0xb8, 0x84, 0x38, +0x82, 0xd3, 0x82, 0x40, 0x50, 0xc0, 0xc0, 0x81, 0x38, 0x37, 0x50, 0xc0, +0xc0, 0x81, 0x38, 0x34, 0x18, 0x24, 0x1f, 0x40, 0x83, 0x90, 0xa8, 0xd3, +0x82, 0xc0, 0xc0, 0xc0, 0x80, 0xa4, 0x42, 0x48, 0x38, 0x71, 0xc0, 0xc0, +0x80, 0xa4, 0x42, 0x30, 0x38, 0x6b, 0xd3, 0x82, 0x40, 0x50, 0xc0, 0xc0, +0x81, 0x38, 0x31, 0x50, 0xc0, 0xc0, 0x81, 0x38, 0x2e, 0x92, 0xb8, 0x99, +0x84, 0x1f, 0x38, 0x90, 0x78, 0x90, 0x50, 0x10, 0x10, 0x80, 0xa4, 0x36, +0x88, 0x36, 0xd6, 0x82, 0x36, 0xcc, 0x90, 0x80, 0x10, 0x10, 0x90, 0x38, +0xa4, 0x38, 0x88, 0x37, 0x17, 0xa4, 0x38, 0x30, 0x37, 0x0b, 0x80, 0x90, +0x38, 0xa4, 0x37, 0x08, 0x36, 0xec, 0xa4, 0x36, 0xe0, 0x36, 0xe7, 0x83, +0x90, 0xa8, 0xd3, 0x82, 0xc0, 0xc0, 0xc0, 0x80, 0xa4, 0x42, 0x18, 0x38, +0x65, 0xc0, 0xc0, 0x80, 0xa4, 0x42, 0x00, 0x38, 0x5f, 0xd3, 0x82, 0x40, +0x50, 0xc0, 0xc0, 0x81, 0x38, 0x2b, 0x50, 0xc0, 0xc0, 0x81, 0x38, 0x28, +0x18, 0x20, 0x01, 0x48, 0x83, 0x90, 0xa8, 0xd3, 0x82, 0xc0, 0xc0, 0xc0, +0x80, 0xa4, 0x41, 0xe8, 0x38, 0x59, 0xc0, 0xc0, 0x80, 0xa4, 0x41, 0xd0, +0x38, 0x53, 0xd3, 0x82, 0x40, 0x50, 0xc0, 0xc0, 0x81, 0x38, 0x25, 0x50, +0xc0, 0xc0, 0x81, 0x38, 0x22, 0xda, 0x06, 0xe0, 0xf9, 0x80, 0x90, 0x60, +0x90, 0x38, 0xa4, 0x24, 0xe8, 0x34, 0x9b, 0x80, 0x34, 0x98, 0x90, 0x38, +0xa4, 0x24, 0x90, 0x34, 0x96, 0x80, 0x34, 0x93, 0x90, 0x60, 0x90, 0x38, +0xa4, 0x24, 0xd0, 0x34, 0x9c, 0x80, 0x34, 0x99, 0x90, 0x38, 0xa4, 0x24, +0xa8, 0x34, 0x97, 0x80, 0x34, 0x94, 0xc8, 0x40, 0x19, 0x00, 0x91, 0x58, +0x90, 0x60, 0x82, 0x90, 0x20, 0x36, 0xcb, 0xa4, 0x36, 0x48, 0x36, 0xca, +0x90, 0xc0, 0x80, 0x90, 0x90, 0x90, 0x48, 0xc9, 0xe1, 0xc1, 0x00, 0x85, +0x37, 0x03, 0xc9, 0xe1, 0xc0, 0x40, 0x85, 0x37, 0x00, 0x80, 0x36, 0xff, +0x10, 0x10, 0x81, 0x36, 0xdb, 0x90, 0xa8, 0x10, 0x10, 0x90, 0x28, 0x81, +0x36, 0xf9, 0x90, 0x38, 0xa4, 0x37, 0xa0, 0x36, 0xf5, 0xa4, 0x37, 0x90, +0x36, 0xf3, 0x90, 0x70, 0x10, 0x10, 0x90, 0x38, 0xa4, 0x37, 0xb8, 0x36, +0xf8, 0x80, 0x36, 0xf6, 0x90, 0x60, 0x90, 0x28, 0x24, 0x37, 0xf0, 0xa4, +0x37, 0xe0, 0x36, 0xfd, 0x80, 0xa4, 0x37, 0xd0, 0x36, 0xfb, 0x80, 0x90, +0xf8, 0x90, 0x90, 0x90, 0x50, 0x90, 0x28, 0x80, 0x38, 0x17, 0x80, 0x38, +0x20, 0x80, 0xa4, 0x40, 0xf0, 0x38, 0x1f, 0x90, 0x28, 0x81, 0x38, 0x1d, +0x80, 0xa4, 0x40, 0xd8, 0x38, 0x1c, 0x90, 0x28, 0x82, 0x38, 0x1a, 0x81, +0xa4, 0x40, 0xc0, 0x38, 0x19, 0x98, 0xe8, 0x01, 0xb0, 0x90, 0x88, 0x90, +0x60, 0xa4, 0x36, 0x38, 0x10, 0x10, 0x10, 0x10, 0x83, 0x33, 0xb7, 0x24, +0x36, 0x30, 0x90, 0x28, 0x24, 0x36, 0x28, 0x24, 0x36, 0x20, 0x90, 0x88, +0x90, 0x60, 0xa4, 0x36, 0x10, 0x10, 0x10, 0x10, 0x10, 0x83, 0x33, 0xb6, +0x24, 0x36, 0x08, 0x90, 0x28, 0x24, 0x36, 0x00, 0x24, 0x35, 0xf8, 0xa8, +0x09, 0x00, 0x0e, 0x20, 0x96, 0x48, 0x95, 0xe8, 0x93, 0x38, 0x91, 0xa0, +0x90, 0xd0, 0x90, 0x70, 0x90, 0x38, 0xa4, 0x1e, 0x60, 0x33, 0xcd, 0xa4, +0x1e, 0x50, 0x33, 0xcb, 0x90, 0x38, 0xa4, 0x1e, 0x40, 0x33, 0xc9, 0x80, +0x33, 0xc7, 0x90, 0x60, 0x90, 0x28, 0x24, 0x1e, 0x00, 0xa4, 0x1d, 0xf0, +0x33, 0xbf, 0x90, 0x38, 0xa4, 0x1d, 0xe0, 0x33, 0xbd, 0xa4, 0x1e, 0x28, +0x33, 0xc6, 0x90, 0xe0, 0x90, 0x70, 0x90, 0x38, 0xa4, 0x1e, 0x18, 0x33, +0xc4, 0xa4, 0x1e, 0x08, 0x33, 0xc2, 0x90, 0x38, 0xa4, 0x35, 0xb0, 0x36, +0xbc, 0xa4, 0x35, 0x50, 0x36, 0xb0, 0x90, 0x70, 0x90, 0x38, 0xa4, 0x32, +0x90, 0x36, 0x5e, 0xa4, 0x32, 0x60, 0x36, 0x58, 0x10, 0x10, 0xa4, 0x1d, +0xd0, 0x33, 0xbb, 0x99, 0x60, 0x02, 0x70, 0x90, 0x90, 0x90, 0x50, 0x90, +0x28, 0x24, 0x1e, 0x90, 0x80, 0x33, 0xda, 0x80, 0xa4, 0x1e, 0x98, 0x33, +0xd8, 0x90, 0x50, 0x90, 0x28, 0x24, 0x1e, 0xa0, 0x80, 0x33, 0xdb, 0x90, +0x38, 0xa4, 0x1e, 0xa8, 0x33, 0xd9, 0xa4, 0x1e, 0x70, 0x33, 0xcf, 0x90, +0xe0, 0x90, 0x70, 0x90, 0x38, 0xa4, 0x34, 0xe8, 0x36, 0xa5, 0xa4, 0x34, +0x48, 0x36, 0x92, 0x90, 0x38, 0xa4, 0x33, 0xe0, 0x36, 0x83, 0xa4, 0x33, +0x50, 0x36, 0x72, 0x81, 0xa4, 0x1e, 0x80, 0x33, 0xd1, 0xe4, 0xa2, 0x04, +0x40, 0x38, 0x13, 0x18, 0x24, 0x1d, 0xc8, 0xe4, 0xe2, 0x02, 0xc0, 0x38, +0x0d, 0x92, 0x40, 0x91, 0x08, 0x10, 0x10, 0x90, 0x80, 0x10, 0x10, 0x90, +0x38, 0xa4, 0x35, 0xa8, 0x36, 0xbb, 0xa4, 0x35, 0x48, 0x36, 0xaf, 0x80, +0x90, 0x38, 0xa4, 0x32, 0x88, 0x36, 0x5d, 0xa4, 0x32, 0x58, 0x36, 0x57, +0x18, 0x20, 0x00, 0xf8, 0x80, 0x90, 0x70, 0x90, 0x38, 0xa4, 0x34, 0xd8, +0x36, 0xa4, 0xa4, 0x34, 0x40, 0x36, 0x90, 0x90, 0x38, 0xa4, 0x33, 0xd0, +0x36, 0x82, 0xa4, 0x33, 0x48, 0x36, 0x70, 0xe4, 0xa2, 0x01, 0x40, 0x38, +0x07, 0x18, 0x24, 0x1d, 0xc0, 0xe4, 0xe1, 0xff, 0xc0, 0x38, 0x01, 0x92, +0x90, 0x92, 0x40, 0x91, 0x08, 0x10, 0x10, 0x90, 0x80, 0x10, 0x10, 0x90, +0x38, 0xa4, 0x35, 0xa0, 0x36, 0xba, 0xa4, 0x35, 0x40, 0x36, 0xae, 0x80, +0x90, 0x38, 0xa4, 0x32, 0x80, 0x36, 0x5c, 0xa4, 0x32, 0x50, 0x36, 0x56, +0x18, 0x20, 0x00, 0xf8, 0x80, 0x90, 0x70, 0x90, 0x38, 0xa4, 0x34, 0xc8, +0x36, 0xa3, 0xa4, 0x34, 0x38, 0x36, 0x8e, 0x90, 0x38, 0xa4, 0x33, 0xc0, +0x36, 0x81, 0xa4, 0x33, 0x40, 0x36, 0x6e, 0xe4, 0xa2, 0x04, 0x80, 0x38, +0x15, 0x10, 0x10, 0xe4, 0xe2, 0x03, 0x00, 0x38, 0x0f, 0x92, 0x50, 0x99, +0x1c, 0x1e, 0xb0, 0x10, 0x10, 0x90, 0x80, 0x10, 0x10, 0x90, 0x38, 0xa4, +0x35, 0x98, 0x36, 0xb9, 0xa4, 0x35, 0x38, 0x36, 0xad, 0x80, 0x90, 0x38, +0xa4, 0x32, 0x78, 0x36, 0x5b, 0xa4, 0x32, 0x48, 0x36, 0x55, 0x18, 0x20, +0x00, 0xf8, 0x80, 0x90, 0x70, 0x90, 0x38, 0xa4, 0x34, 0xb8, 0x36, 0xa2, +0xa4, 0x34, 0x30, 0x36, 0x8c, 0x90, 0x38, 0xa4, 0x33, 0xb0, 0x36, 0x80, +0xa4, 0x33, 0x38, 0x36, 0x6c, 0xe4, 0xa2, 0x01, 0x80, 0x38, 0x09, 0x10, +0x10, 0xe4, 0xe2, 0x00, 0x00, 0x38, 0x03, 0xc0, 0x40, 0x80, 0x10, 0x10, +0x81, 0x90, 0x90, 0x90, 0x48, 0xc9, 0xe1, 0x98, 0x80, 0x85, 0x36, 0x66, +0xc9, 0xe1, 0x99, 0x00, 0x85, 0x36, 0x63, 0x80, 0x36, 0x61, 0x80, 0xd8, +0x47, 0x80, 0x0d, 0xc0, 0xc0, 0x80, 0x10, 0x10, 0x82, 0x90, 0x58, 0xd5, +0x81, 0x80, 0x80, 0x37, 0xfd, 0x80, 0x37, 0xfb, 0xd5, 0x81, 0x80, 0x80, +0x37, 0xf9, 0x80, 0x37, 0xf7, 0xc0, 0x80, 0x10, 0x10, 0x82, 0x90, 0x58, +0xd5, 0x81, 0x80, 0x80, 0x37, 0xfe, 0x80, 0x37, 0xfc, 0xd5, 0x81, 0x80, +0x80, 0x37, 0xfa, 0x80, 0x37, 0xf8, 0xc0, 0x80, 0x83, 0xa4, 0x3f, 0xa8, +0x37, 0xf6, 0xa0, 0x59, 0x60, 0xa0, 0x41, 0xe0, 0xa8, 0x1e, 0xb0, 0x34, +0x88, 0xa0, 0x12, 0x38, 0xa0, 0x0b, 0x48, 0x96, 0x00, 0x9a, 0xf0, 0x05, +0xc0, 0x91, 0x70, 0x90, 0xb8, 0x90, 0x70, 0x90, 0x38, 0xa4, 0x15, 0x58, +0x33, 0xb5, 0xa4, 0x15, 0x78, 0x33, 0xb4, 0x10, 0x10, 0xa4, 0x15, 0x68, +0x33, 0xb3, 0x90, 0x70, 0x90, 0x38, 0xa4, 0x14, 0xf8, 0x33, 0x9a, 0xa4, +0x15, 0x18, 0x33, 0x99, 0x10, 0x10, 0xa4, 0x15, 0x08, 0x33, 0x98, 0x90, +0xb8, 0x90, 0x70, 0x90, 0x38, 0xa4, 0x14, 0x98, 0x33, 0x7f, 0xa4, 0x14, +0xb8, 0x33, 0x7e, 0x10, 0x10, 0xa4, 0x14, 0xa8, 0x33, 0x7d, 0x90, 0x70, +0x90, 0x38, 0xa4, 0x14, 0x38, 0x33, 0x63, 0xa4, 0x14, 0x58, 0x33, 0x62, +0x10, 0x10, 0xa4, 0x14, 0x48, 0x33, 0x61, 0x91, 0x70, 0x90, 0xb8, 0x90, +0x70, 0x90, 0x38, 0xa4, 0x15, 0x28, 0x33, 0xb0, 0xa4, 0x15, 0x48, 0x33, +0xb2, 0x10, 0x10, 0xa4, 0x15, 0x38, 0x33, 0xb1, 0x90, 0x70, 0x90, 0x38, +0xa4, 0x14, 0xc8, 0x33, 0x95, 0xa4, 0x14, 0xe8, 0x33, 0x97, 0x10, 0x10, +0xa4, 0x14, 0xd8, 0x33, 0x96, 0x90, 0xb8, 0x90, 0x70, 0x90, 0x38, 0xa4, +0x14, 0x68, 0x33, 0x7a, 0xa4, 0x14, 0x88, 0x33, 0x7c, 0x10, 0x10, 0xa4, +0x14, 0x78, 0x33, 0x7b, 0x90, 0x70, 0x90, 0x38, 0xa4, 0x14, 0x08, 0x33, +0x5e, 0xa4, 0x14, 0x28, 0x33, 0x60, 0x10, 0x10, 0xa4, 0x14, 0x18, 0x33, +0x5f, 0xe4, 0xe1, 0x8b, 0x40, 0x36, 0x41, 0x9a, 0xf0, 0x05, 0x00, 0x91, +0x70, 0x90, 0xb8, 0x90, 0x70, 0x90, 0x38, 0xa4, 0x13, 0xa0, 0x33, 0xad, +0xa4, 0x13, 0x98, 0x33, 0xaf, 0x10, 0x10, 0xa4, 0x13, 0x90, 0x33, 0xae, +0x90, 0x70, 0x90, 0x38, 0xa4, 0x13, 0x88, 0x33, 0x92, 0xa4, 0x13, 0x80, +0x33, 0x94, 0x10, 0x10, 0xa4, 0x13, 0x78, 0x33, 0x93, 0x90, 0xb8, 0x90, +0x70, 0x90, 0x38, 0xa4, 0x13, 0x70, 0x33, 0x77, 0xa4, 0x13, 0x68, 0x33, +0x79, 0x10, 0x10, 0xa4, 0x13, 0x60, 0x33, 0x78, 0x90, 0x70, 0x90, 0x38, +0xa4, 0x13, 0x58, 0x33, 0x5b, 0xa4, 0x13, 0x50, 0x33, 0x5d, 0x10, 0x10, +0xa4, 0x13, 0x48, 0x33, 0x5c, 0x91, 0x10, 0x90, 0x88, 0x90, 0x50, 0x90, +0x28, 0x80, 0x33, 0xaa, 0x80, 0x33, 0xac, 0x10, 0x10, 0x80, 0x33, 0xab, +0x90, 0x50, 0x90, 0x28, 0x80, 0x33, 0x8f, 0x80, 0x33, 0x91, 0x10, 0x10, +0x80, 0x33, 0x90, 0x90, 0x88, 0x90, 0x50, 0x90, 0x28, 0x80, 0x33, 0x74, +0x80, 0x33, 0x76, 0x10, 0x10, 0x80, 0x33, 0x75, 0x90, 0x50, 0x90, 0x28, +0x80, 0x33, 0x58, 0x80, 0x33, 0x5a, 0x10, 0x10, 0x80, 0x33, 0x59, 0xe4, +0xe1, 0x66, 0x40, 0x35, 0xc1, 0x95, 0x40, 0x9a, 0x90, 0x05, 0x00, 0x91, +0x10, 0x90, 0x88, 0x90, 0x50, 0x90, 0x28, 0x80, 0x33, 0xa7, 0x80, 0x33, +0xa9, 0x10, 0x10, 0x80, 0x33, 0xa8, 0x90, 0x50, 0x90, 0x28, 0x80, 0x33, +0x8c, 0x80, 0x33, 0x8e, 0x10, 0x10, 0x80, 0x33, 0x8d, 0x90, 0xb8, 0x90, +0x70, 0x90, 0x38, 0xa4, 0x13, 0x30, 0x33, 0x71, 0xa4, 0x13, 0x40, 0x33, +0x73, 0x10, 0x10, 0xa4, 0x13, 0x38, 0x33, 0x72, 0x90, 0x70, 0x90, 0x38, +0xa4, 0x13, 0x00, 0x33, 0x55, 0xa4, 0x13, 0x10, 0x33, 0x57, 0x10, 0x10, +0xa4, 0x13, 0x08, 0x33, 0x56, 0x91, 0x10, 0x90, 0x88, 0x90, 0x50, 0x90, +0x28, 0x80, 0x33, 0xa4, 0x80, 0x33, 0xa6, 0x10, 0x10, 0x80, 0x33, 0xa5, +0x90, 0x50, 0x90, 0x28, 0x80, 0x33, 0x89, 0x80, 0x33, 0x8b, 0x10, 0x10, +0x80, 0x33, 0x8a, 0x90, 0xb8, 0x90, 0x70, 0x90, 0x38, 0xa4, 0x13, 0x18, +0x33, 0x6e, 0xa4, 0x13, 0x28, 0x33, 0x70, 0x10, 0x10, 0xa4, 0x13, 0x20, +0x33, 0x6f, 0x90, 0x70, 0x90, 0x38, 0xa4, 0x12, 0xe8, 0x33, 0x52, 0xa4, +0x12, 0xf8, 0x33, 0x54, 0x10, 0x10, 0xa4, 0x12, 0xf0, 0x33, 0x53, 0xe4, +0xe1, 0x8a, 0x40, 0x36, 0x3d, 0x98, 0xb8, 0x01, 0x68, 0x10, 0x10, 0x10, +0x10, 0x90, 0x50, 0x90, 0x28, 0x80, 0x33, 0x4f, 0x80, 0x33, 0x51, 0x10, +0x10, 0x80, 0x33, 0x50, 0x90, 0x60, 0x90, 0x30, 0x60, 0xa0, 0x97, 0x00, +0x60, 0xa0, 0x96, 0xc0, 0x90, 0x30, 0x60, 0xa0, 0x96, 0x80, 0x60, 0xa0, +0x96, 0x40, 0xe4, 0xe1, 0x64, 0x40, 0x35, 0xb9, 0xa0, 0x08, 0x08, 0x94, +0xe0, 0x9a, 0x60, 0x04, 0xa0, 0x91, 0x40, 0x90, 0xb8, 0x90, 0x70, 0x90, +0x38, 0xa4, 0x13, 0xd8, 0x33, 0x9e, 0xa4, 0x13, 0xf8, 0x33, 0xa3, 0x10, +0x10, 0xa4, 0x13, 0xe8, 0x33, 0xa2, 0x90, 0x50, 0x90, 0x28, 0x80, 0x33, +0x83, 0x80, 0x33, 0x88, 0x10, 0x10, 0x80, 0x33, 0x87, 0x90, 0x88, 0x90, +0x50, 0x90, 0x28, 0x80, 0x33, 0x68, 0x80, 0x33, 0x6d, 0x10, 0x10, 0x80, +0x33, 0x6c, 0x90, 0x50, 0x90, 0x28, 0x80, 0x33, 0x49, 0x80, 0x33, 0x4e, +0x10, 0x10, 0x80, 0x33, 0x4d, 0x91, 0x40, 0x90, 0xb8, 0x90, 0x70, 0x90, +0x38, 0xa4, 0x13, 0xa8, 0x33, 0x9b, 0xa4, 0x13, 0xc8, 0x33, 0x9d, 0x10, +0x10, 0xa4, 0x13, 0xb8, 0x33, 0x9c, 0x90, 0x50, 0x90, 0x28, 0x80, 0x33, +0x80, 0x80, 0x33, 0x82, 0x10, 0x10, 0x80, 0x33, 0x81, 0x90, 0x88, 0x90, +0x50, 0x90, 0x28, 0x80, 0x33, 0x65, 0x80, 0x33, 0x67, 0x10, 0x10, 0x80, +0x33, 0x66, 0x90, 0x50, 0x90, 0x28, 0x80, 0x33, 0x46, 0x80, 0x33, 0x48, +0x10, 0x10, 0x80, 0x33, 0x47, 0xe4, 0xe1, 0x89, 0x40, 0x36, 0x39, 0x9a, +0x60, 0x02, 0xe0, 0x91, 0x40, 0x90, 0xb8, 0x90, 0x70, 0x90, 0x38, 0xa4, +0x1a, 0x20, 0x33, 0x9f, 0xa4, 0x1a, 0x10, 0x33, 0xa1, 0x10, 0x10, 0xa4, +0x1a, 0x00, 0x33, 0xa0, 0x90, 0x50, 0x90, 0x28, 0x80, 0x33, 0x84, 0x80, +0x33, 0x86, 0x10, 0x10, 0x80, 0x33, 0x85, 0x90, 0x88, 0x90, 0x50, 0x90, +0x28, 0x80, 0x33, 0x69, 0x80, 0x33, 0x6b, 0x10, 0x10, 0x80, 0x33, 0x6a, +0x90, 0x50, 0x90, 0x28, 0x80, 0x33, 0x4a, 0x80, 0x33, 0x4c, 0x10, 0x10, +0x80, 0x33, 0x4b, 0x81, 0x90, 0x50, 0x90, 0x28, 0x24, 0x19, 0xd0, 0x24, +0x19, 0xf0, 0x10, 0x10, 0x24, 0x19, 0xe0, 0xe4, 0xe1, 0x62, 0x40, 0x35, +0xb1, 0x93, 0x90, 0x99, 0xb8, 0x03, 0x50, 0x90, 0xe8, 0x90, 0x88, 0x90, +0x40, 0x80, 0xa4, 0x15, 0xb8, 0x32, 0xca, 0x10, 0x10, 0xa4, 0x15, 0xa8, +0x32, 0xc9, 0x90, 0x28, 0x81, 0x32, 0xc6, 0x10, 0x10, 0x80, 0x32, 0xc5, +0x90, 0x60, 0x90, 0x28, 0x81, 0x32, 0xc2, 0x10, 0x10, 0x80, 0x32, 0xc1, +0x90, 0x28, 0x81, 0x32, 0xbe, 0x10, 0x10, 0x80, 0x32, 0xbd, 0x90, 0xe8, +0x90, 0x88, 0x90, 0x40, 0x80, 0xa4, 0x15, 0x88, 0x32, 0xc7, 0x10, 0x10, +0xa4, 0x15, 0x98, 0x32, 0xc8, 0x90, 0x28, 0x81, 0x32, 0xc3, 0x10, 0x10, +0x80, 0x32, 0xc4, 0x90, 0x60, 0x90, 0x28, 0x81, 0x32, 0xbf, 0x10, 0x10, +0x80, 0x32, 0xc0, 0x90, 0x28, 0x81, 0x32, 0xbb, 0x10, 0x10, 0x80, 0x32, +0xbc, 0xe4, 0xe1, 0x88, 0x40, 0x36, 0x35, 0x88, 0x00, 0x88, 0x10, 0x10, +0x10, 0x10, 0x90, 0x28, 0x81, 0x32, 0xb9, 0x10, 0x10, 0x80, 0x32, 0xba, +0xe4, 0xe1, 0x60, 0x40, 0x35, 0xa9, 0xa0, 0x0e, 0x80, 0xa0, 0x09, 0x08, +0x94, 0x80, 0x9a, 0x30, 0x04, 0x40, 0x91, 0x10, 0x90, 0x88, 0x90, 0x50, +0x90, 0x28, 0x80, 0x33, 0x39, 0x80, 0x33, 0x38, 0x10, 0x10, 0x80, 0x33, +0x37, 0x90, 0x50, 0x90, 0x28, 0x80, 0x33, 0x1e, 0x80, 0x33, 0x1d, 0x10, +0x10, 0x80, 0x33, 0x1c, 0x90, 0x88, 0x90, 0x50, 0x90, 0x28, 0x80, 0x33, +0x03, 0x80, 0x33, 0x02, 0x10, 0x10, 0x80, 0x33, 0x01, 0x90, 0x50, 0x90, +0x28, 0x80, 0x32, 0xe8, 0x80, 0x32, 0xe7, 0x10, 0x10, 0x80, 0x32, 0xe6, +0x91, 0x10, 0x90, 0x88, 0x90, 0x50, 0x90, 0x28, 0x80, 0x33, 0x34, 0x80, +0x33, 0x36, 0x10, 0x10, 0x80, 0x33, 0x35, 0x90, 0x50, 0x90, 0x28, 0x80, +0x33, 0x19, 0x80, 0x33, 0x1b, 0x10, 0x10, 0x80, 0x33, 0x1a, 0x90, 0x88, +0x90, 0x50, 0x90, 0x28, 0x80, 0x32, 0xfe, 0x80, 0x33, 0x00, 0x10, 0x10, +0x80, 0x32, 0xff, 0x90, 0x50, 0x90, 0x28, 0x80, 0x32, 0xe3, 0x80, 0x32, +0xe5, 0x10, 0x10, 0x80, 0x32, 0xe4, 0xe4, 0xe1, 0x7a, 0x40, 0x36, 0x11, +0x9a, 0x30, 0x04, 0x40, 0x91, 0x10, 0x90, 0x88, 0x90, 0x50, 0x90, 0x28, +0x80, 0x33, 0x31, 0x80, 0x33, 0x33, 0x10, 0x10, 0x80, 0x33, 0x32, 0x90, +0x50, 0x90, 0x28, 0x80, 0x33, 0x16, 0x80, 0x33, 0x18, 0x10, 0x10, 0x80, +0x33, 0x17, 0x90, 0x88, 0x90, 0x50, 0x90, 0x28, 0x80, 0x32, 0xfb, 0x80, +0x32, 0xfd, 0x10, 0x10, 0x80, 0x32, 0xfc, 0x90, 0x50, 0x90, 0x28, 0x80, +0x32, 0xe0, 0x80, 0x32, 0xe2, 0x10, 0x10, 0x80, 0x32, 0xe1, 0x91, 0x10, +0x90, 0x88, 0x90, 0x50, 0x90, 0x28, 0x80, 0x33, 0x2e, 0x80, 0x33, 0x30, +0x10, 0x10, 0x80, 0x33, 0x2f, 0x90, 0x50, 0x90, 0x28, 0x80, 0x33, 0x13, +0x80, 0x33, 0x15, 0x10, 0x10, 0x80, 0x33, 0x14, 0x90, 0x88, 0x90, 0x50, +0x90, 0x28, 0x80, 0x32, 0xf8, 0x80, 0x32, 0xfa, 0x10, 0x10, 0x80, 0x32, +0xf9, 0x90, 0x50, 0x90, 0x28, 0x80, 0x32, 0xdd, 0x80, 0x32, 0xdf, 0x10, +0x10, 0x80, 0x32, 0xde, 0xe4, 0xe1, 0x59, 0x40, 0x35, 0x79, 0x94, 0x80, +0x9a, 0x30, 0x04, 0x40, 0x91, 0x10, 0x90, 0x88, 0x90, 0x50, 0x90, 0x28, +0x80, 0x33, 0x2b, 0x80, 0x33, 0x2d, 0x10, 0x10, 0x80, 0x33, 0x2c, 0x90, +0x50, 0x90, 0x28, 0x80, 0x33, 0x10, 0x80, 0x33, 0x12, 0x10, 0x10, 0x80, +0x33, 0x11, 0x90, 0x88, 0x90, 0x50, 0x90, 0x28, 0x80, 0x32, 0xf5, 0x80, +0x32, 0xf7, 0x10, 0x10, 0x80, 0x32, 0xf6, 0x90, 0x50, 0x90, 0x28, 0x80, +0x32, 0xda, 0x80, 0x32, 0xdc, 0x10, 0x10, 0x80, 0x32, 0xdb, 0x91, 0x10, +0x90, 0x88, 0x90, 0x50, 0x90, 0x28, 0x80, 0x33, 0x28, 0x80, 0x33, 0x2a, +0x10, 0x10, 0x80, 0x33, 0x29, 0x90, 0x50, 0x90, 0x28, 0x80, 0x33, 0x0d, +0x80, 0x33, 0x0f, 0x10, 0x10, 0x80, 0x33, 0x0e, 0x90, 0x88, 0x90, 0x50, +0x90, 0x28, 0x80, 0x32, 0xf2, 0x80, 0x32, 0xf4, 0x10, 0x10, 0x80, 0x32, +0xf3, 0x90, 0x50, 0x90, 0x28, 0x80, 0x32, 0xd7, 0x80, 0x32, 0xd9, 0x10, +0x10, 0x80, 0x32, 0xd8, 0xe4, 0xe1, 0x78, 0x40, 0x36, 0x09, 0x88, 0x00, +0xb0, 0x10, 0x10, 0x10, 0x10, 0x90, 0x50, 0x90, 0x28, 0x80, 0x32, 0xd4, +0x80, 0x32, 0xd6, 0x10, 0x10, 0x80, 0x32, 0xd5, 0xe4, 0xe1, 0x58, 0x40, +0x35, 0x75, 0x96, 0xe8, 0x94, 0x80, 0x9a, 0x30, 0x04, 0x40, 0x91, 0x10, +0x90, 0x88, 0x90, 0x50, 0x90, 0x28, 0x80, 0x33, 0x22, 0x80, 0x33, 0x27, +0x10, 0x10, 0x80, 0x33, 0x26, 0x90, 0x50, 0x90, 0x28, 0x80, 0x33, 0x07, +0x80, 0x33, 0x0c, 0x10, 0x10, 0x80, 0x33, 0x0b, 0x90, 0x88, 0x90, 0x50, +0x90, 0x28, 0x80, 0x32, 0xec, 0x80, 0x32, 0xf1, 0x10, 0x10, 0x80, 0x32, +0xf0, 0x90, 0x50, 0x90, 0x28, 0x80, 0x32, 0xce, 0x80, 0x32, 0xd3, 0x10, +0x10, 0x80, 0x32, 0xd2, 0x91, 0x10, 0x90, 0x88, 0x90, 0x50, 0x90, 0x28, +0x80, 0x33, 0x1f, 0x80, 0x33, 0x21, 0x10, 0x10, 0x80, 0x33, 0x20, 0x90, +0x50, 0x90, 0x28, 0x80, 0x33, 0x04, 0x80, 0x33, 0x06, 0x10, 0x10, 0x80, +0x33, 0x05, 0x90, 0x88, 0x90, 0x50, 0x90, 0x28, 0x80, 0x32, 0xe9, 0x80, +0x32, 0xeb, 0x10, 0x10, 0x80, 0x32, 0xea, 0x90, 0x50, 0x90, 0x28, 0x80, +0x32, 0xcb, 0x80, 0x32, 0xcd, 0x10, 0x10, 0x80, 0x32, 0xcc, 0xe4, 0xe1, +0x76, 0x40, 0x36, 0x01, 0x88, 0x02, 0x28, 0x91, 0x10, 0x90, 0x88, 0x90, +0x50, 0x90, 0x28, 0x80, 0x33, 0x23, 0x80, 0x33, 0x25, 0x10, 0x10, 0x80, +0x33, 0x24, 0x90, 0x50, 0x90, 0x28, 0x80, 0x33, 0x08, 0x80, 0x33, 0x0a, +0x10, 0x10, 0x80, 0x33, 0x09, 0x90, 0x88, 0x90, 0x50, 0x90, 0x28, 0x80, +0x32, 0xed, 0x80, 0x32, 0xef, 0x10, 0x10, 0x80, 0x32, 0xee, 0x90, 0x50, +0x90, 0x28, 0x80, 0x32, 0xcf, 0x80, 0x32, 0xd1, 0x10, 0x10, 0x80, 0x32, +0xd0, 0xe4, 0xe1, 0x57, 0x40, 0x35, 0x71, 0x90, 0x40, 0xe5, 0x21, 0x74, +0x40, 0x35, 0xf9, 0xe5, 0x21, 0x56, 0x40, 0x35, 0x6d, 0x9e, 0xb4, 0x23, +0xe8, 0x93, 0x70, 0x91, 0xd8, 0xd5, 0x07, 0x80, 0xd0, 0xc4, 0x40, 0x90, +0x48, 0x80, 0x8c, 0x3f, 0x38, 0x84, 0x37, 0xf1, 0xa4, 0x3d, 0x18, 0x37, +0xbb, 0x90, 0x28, 0x24, 0x3c, 0x58, 0xa4, 0x3a, 0xd8, 0x37, 0x73, 0xd0, +0xc4, 0x40, 0x90, 0x48, 0x80, 0x8c, 0x3f, 0x18, 0x84, 0x37, 0xef, 0xa4, +0x3d, 0x08, 0x37, 0xb9, 0x90, 0x28, 0x24, 0x3c, 0x48, 0xa4, 0x3a, 0xc8, +0x37, 0x71, 0xd5, 0x06, 0x80, 0xd0, 0xc3, 0x40, 0x90, 0x28, 0x80, 0x37, +0xdb, 0xa4, 0x3c, 0xe8, 0x37, 0xb5, 0x90, 0x28, 0x24, 0x3c, 0x28, 0xa4, +0x3a, 0xa8, 0x37, 0x6d, 0xd0, 0xc3, 0x40, 0x90, 0x28, 0x80, 0x37, 0xd7, +0xa4, 0x3c, 0xd8, 0x37, 0xb3, 0x90, 0x28, 0x24, 0x3c, 0x18, 0xa4, 0x3a, +0x98, 0x37, 0x6b, 0x91, 0x98, 0xd5, 0x06, 0x80, 0xd0, 0xc3, 0x40, 0x90, +0x28, 0x80, 0x37, 0xcf, 0xa4, 0x3c, 0xb8, 0x37, 0xaf, 0x90, 0x28, 0x24, +0x3b, 0xf8, 0xa4, 0x3a, 0x78, 0x37, 0x67, 0xd0, 0xc3, 0x40, 0x90, 0x28, +0x80, 0x37, 0xcb, 0xa4, 0x3c, 0xa8, 0x37, 0xad, 0x90, 0x28, 0x24, 0x3b, +0xe8, 0xa4, 0x3a, 0x68, 0x37, 0x65, 0xd5, 0x06, 0x80, 0xd0, 0xc3, 0x40, +0x90, 0x28, 0x80, 0x37, 0xc3, 0xa4, 0x3c, 0x88, 0x37, 0xa9, 0x90, 0x28, +0x24, 0x3b, 0xc8, 0xa4, 0x3a, 0x48, 0x37, 0x61, 0xd0, 0xc3, 0x40, 0x90, +0x28, 0x80, 0x37, 0xbf, 0xa4, 0x3c, 0x78, 0x37, 0xa7, 0x90, 0x28, 0x24, +0x3b, 0xb8, 0xa4, 0x3a, 0x38, 0x37, 0x5f, 0x93, 0x70, 0x91, 0xd8, 0xd5, +0x07, 0x80, 0xd0, 0xc4, 0x40, 0x90, 0x48, 0x80, 0x8c, 0x3f, 0x58, 0x84, +0x37, 0xf3, 0xa4, 0x3d, 0x28, 0x37, 0xbd, 0x90, 0x28, 0x24, 0x3c, 0x68, +0xa4, 0x3a, 0xe8, 0x37, 0x75, 0xd0, 0xc4, 0x40, 0x90, 0x48, 0x80, 0x8c, +0x3f, 0x28, 0x84, 0x37, 0xf0, 0xa4, 0x3d, 0x10, 0x37, 0xba, 0x90, 0x28, +0x24, 0x3c, 0x50, 0xa4, 0x3a, 0xd0, 0x37, 0x72, 0xd5, 0x06, 0x80, 0xd0, +0xc3, 0x40, 0x90, 0x28, 0x80, 0x37, 0xdf, 0xa4, 0x3c, 0xf8, 0x37, 0xb7, +0x90, 0x28, 0x24, 0x3c, 0x38, 0xa4, 0x3a, 0xb8, 0x37, 0x6f, 0xd0, 0xc3, +0x40, 0x90, 0x28, 0x80, 0x37, 0xd9, 0xa4, 0x3c, 0xe0, 0x37, 0xb4, 0x90, +0x28, 0x24, 0x3c, 0x20, 0xa4, 0x3a, 0xa0, 0x37, 0x6c, 0x91, 0x98, 0xd5, +0x06, 0x80, 0xd0, 0xc3, 0x40, 0x90, 0x28, 0x80, 0x37, 0xd3, 0xa4, 0x3c, +0xc8, 0x37, 0xb1, 0x90, 0x28, 0x24, 0x3c, 0x08, 0xa4, 0x3a, 0x88, 0x37, +0x69, 0xd0, 0xc3, 0x40, 0x90, 0x28, 0x80, 0x37, 0xcd, 0xa4, 0x3c, 0xb0, +0x37, 0xae, 0x90, 0x28, 0x24, 0x3b, 0xf0, 0xa4, 0x3a, 0x70, 0x37, 0x66, +0xd5, 0x06, 0x80, 0xd0, 0xc3, 0x40, 0x90, 0x28, 0x80, 0x37, 0xc7, 0xa4, +0x3c, 0x98, 0x37, 0xab, 0x90, 0x28, 0x24, 0x3b, 0xd8, 0xa4, 0x3a, 0x58, +0x37, 0x63, 0xd0, 0xc3, 0x40, 0x90, 0x28, 0x80, 0x37, 0xc1, 0xa4, 0x3c, +0x80, 0x37, 0xa8, 0x90, 0x28, 0x24, 0x3b, 0xc0, 0xa4, 0x3a, 0x40, 0x37, +0x60, 0x99, 0xd8, 0x03, 0x90, 0x81, 0x90, 0xe0, 0x5b, 0x41, 0x40, 0x03, +0x40, 0x51, 0x40, 0xc0, 0xa4, 0x23, 0x80, 0x34, 0x60, 0xd1, 0x42, 0x00, +0xa4, 0x22, 0x80, 0x34, 0x40, 0xa4, 0x21, 0x80, 0x34, 0x20, 0x5b, 0x41, +0x40, 0x03, 0x40, 0x51, 0x40, 0xc0, 0xa4, 0x22, 0xa0, 0x34, 0x64, 0xd1, +0x42, 0x00, 0xa4, 0x21, 0xa0, 0x34, 0x44, 0xa4, 0x20, 0xa0, 0x34, 0x24, +0x81, 0x90, 0xe0, 0x5b, 0x41, 0x40, 0x03, 0x40, 0x51, 0x40, 0xc0, 0xa4, +0x22, 0xe0, 0x34, 0x6c, 0xd1, 0x42, 0x00, 0xa4, 0x21, 0xe0, 0x34, 0x4c, +0xa4, 0x20, 0xe0, 0x34, 0x2c, 0x5b, 0x41, 0x40, 0x03, 0x40, 0x51, 0x40, +0xc0, 0xa4, 0x22, 0xc0, 0x34, 0x68, 0xd1, 0x42, 0x00, 0xa4, 0x21, 0xc0, +0x34, 0x48, 0xa4, 0x20, 0xc0, 0x34, 0x28, 0xa8, 0x0b, 0x18, 0x13, 0xa8, +0x96, 0x80, 0x93, 0x40, 0x99, 0x90, 0x03, 0x00, 0x90, 0xc0, 0x90, 0x60, +0x90, 0x38, 0xa4, 0x12, 0xb8, 0x32, 0x58, 0x24, 0x12, 0xb0, 0x90, 0x38, +0xa4, 0x11, 0xe0, 0x32, 0x3d, 0x24, 0x11, 0xd8, 0x90, 0x60, 0x90, 0x38, +0xa4, 0x11, 0x08, 0x32, 0x22, 0x24, 0x11, 0x00, 0x90, 0x38, 0xa4, 0x10, +0x30, 0x32, 0x07, 0x24, 0x10, 0x28, 0x90, 0xc0, 0x90, 0x60, 0x90, 0x38, +0xa4, 0x12, 0xa8, 0x32, 0x53, 0x24, 0x12, 0xa0, 0x90, 0x38, 0xa4, 0x11, +0xd0, 0x32, 0x38, 0x24, 0x11, 0xc8, 0x90, 0x60, 0x90, 0x38, 0xa4, 0x10, +0xf8, 0x32, 0x1d, 0x24, 0x10, 0xf0, 0x90, 0x38, 0xa4, 0x10, 0x20, 0x32, +0x02, 0x24, 0x10, 0x18, 0xe4, 0xe1, 0xd0, 0x40, 0x37, 0x43, 0x99, 0x90, +0x03, 0x00, 0x90, 0xc0, 0x90, 0x60, 0x90, 0x38, 0xa4, 0x12, 0x90, 0x32, +0x50, 0x24, 0x12, 0x88, 0x90, 0x38, 0xa4, 0x11, 0xb8, 0x32, 0x35, 0x24, +0x11, 0xb0, 0x90, 0x60, 0x90, 0x38, 0xa4, 0x10, 0xe0, 0x32, 0x1a, 0x24, +0x10, 0xd8, 0x90, 0x38, 0xa4, 0x10, 0x08, 0x31, 0xff, 0x24, 0x10, 0x00, +0x90, 0xc0, 0x90, 0x60, 0x90, 0x38, 0xa4, 0x12, 0x78, 0x32, 0x4d, 0x24, +0x12, 0x70, 0x90, 0x38, 0xa4, 0x11, 0xa0, 0x32, 0x32, 0x24, 0x11, 0x98, +0x90, 0x60, 0x90, 0x38, 0xa4, 0x10, 0xc8, 0x32, 0x17, 0x24, 0x10, 0xc0, +0x90, 0x38, 0xa4, 0x0f, 0xf0, 0x31, 0xfc, 0x24, 0x0f, 0xe8, 0xe4, 0xe1, +0xce, 0xc0, 0x37, 0x3d, 0x93, 0x78, 0x99, 0x90, 0x03, 0x00, 0x90, 0xc0, +0x90, 0x60, 0x90, 0x38, 0xa4, 0x12, 0x60, 0x32, 0x4a, 0x24, 0x12, 0x58, +0x90, 0x38, 0xa4, 0x11, 0x88, 0x32, 0x2f, 0x24, 0x11, 0x80, 0x90, 0x60, +0x90, 0x38, 0xa4, 0x10, 0xb0, 0x32, 0x14, 0x24, 0x10, 0xa8, 0x90, 0x38, +0xa4, 0x0f, 0xd8, 0x31, 0xf9, 0x24, 0x0f, 0xd0, 0x90, 0xc0, 0x90, 0x60, +0x90, 0x38, 0xa4, 0x12, 0x48, 0x32, 0x47, 0x24, 0x12, 0x40, 0x90, 0x38, +0xa4, 0x11, 0x70, 0x32, 0x2c, 0x24, 0x11, 0x68, 0x90, 0x60, 0x90, 0x38, +0xa4, 0x10, 0x98, 0x32, 0x11, 0x24, 0x10, 0x90, 0x90, 0x38, 0xa4, 0x0f, +0xc0, 0x31, 0xf6, 0x24, 0x0f, 0xb8, 0xec, 0xa1, 0x1e, 0x00, 0x02, 0x00, +0x34, 0x7a, 0xa4, 0x39, 0xa8, 0x37, 0x37, 0x88, 0x00, 0x88, 0x10, 0x10, +0x10, 0x10, 0x90, 0x38, 0xa4, 0x0f, 0xa8, 0x31, 0xf3, 0x24, 0x0f, 0xa0, +0xe9, 0x61, 0x1d, 0x40, 0x02, 0x00, 0x34, 0x76, 0xe3, 0x61, 0xcb, 0xc0, +0x37, 0x31, 0x95, 0x08, 0x93, 0x40, 0x99, 0x90, 0x03, 0x00, 0x90, 0xc0, +0x90, 0x60, 0x90, 0x38, 0xa4, 0x12, 0x30, 0x32, 0x41, 0x24, 0x12, 0x28, +0x90, 0x38, 0xa4, 0x11, 0x58, 0x32, 0x26, 0x24, 0x11, 0x50, 0x90, 0x60, +0x90, 0x38, 0xa4, 0x10, 0x80, 0x32, 0x0b, 0x24, 0x10, 0x78, 0x90, 0x38, +0xa4, 0x0f, 0x90, 0x31, 0xed, 0x24, 0x0f, 0x88, 0x90, 0xc0, 0x90, 0x60, +0x90, 0x38, 0xa4, 0x12, 0x00, 0x32, 0x3e, 0x24, 0x11, 0xf8, 0x90, 0x38, +0xa4, 0x11, 0x28, 0x32, 0x23, 0x24, 0x11, 0x20, 0x90, 0x60, 0x90, 0x38, +0xa4, 0x10, 0x50, 0x32, 0x08, 0x24, 0x10, 0x48, 0x90, 0x38, 0xa4, 0x0f, +0x60, 0x31, 0xea, 0x24, 0x0f, 0x58, 0xe4, 0xe1, 0xd0, 0x80, 0x37, 0x45, +0x88, 0x01, 0x88, 0x90, 0xc0, 0x90, 0x60, 0x90, 0x38, 0xa4, 0x12, 0x20, +0x32, 0x42, 0x24, 0x12, 0x18, 0x90, 0x38, 0xa4, 0x11, 0x48, 0x32, 0x27, +0x24, 0x11, 0x40, 0x90, 0x60, 0x90, 0x38, 0xa4, 0x10, 0x70, 0x32, 0x0c, +0x24, 0x10, 0x68, 0x90, 0x38, 0xa4, 0x0f, 0x80, 0x31, 0xee, 0x24, 0x0f, +0x78, 0xe4, 0xe1, 0xcf, 0x00, 0x37, 0x3f, 0x92, 0xd0, 0x99, 0x50, 0x02, +0x80, 0x90, 0xa0, 0x90, 0x50, 0x90, 0x28, 0x80, 0x31, 0xe9, 0x24, 0x0f, +0x40, 0x90, 0x28, 0x80, 0x31, 0xe5, 0x24, 0x0f, 0x20, 0x90, 0x50, 0x90, +0x28, 0x80, 0x31, 0xe1, 0x24, 0x0f, 0x00, 0x90, 0x28, 0x80, 0x31, 0xdd, +0x24, 0x0e, 0xe0, 0x90, 0xa0, 0x90, 0x50, 0x90, 0x28, 0x80, 0x31, 0xe6, +0x24, 0x0f, 0x38, 0x90, 0x28, 0x80, 0x31, 0xe2, 0x24, 0x0f, 0x18, 0x90, +0x50, 0x90, 0x28, 0x80, 0x31, 0xde, 0x24, 0x0e, 0xf8, 0x90, 0x28, 0x80, +0x31, 0xda, 0x24, 0x0e, 0xd8, 0xec, 0xe1, 0xcd, 0xa1, 0x1f, 0x00, 0x37, +0x39, 0x88, 0x00, 0x78, 0x10, 0x10, 0x10, 0x10, 0x90, 0x28, 0x80, 0x31, +0xd8, 0x24, 0x0e, 0xc8, 0xec, 0xe1, 0xcc, 0x21, 0x1d, 0x00, 0x37, 0x33, +0xe5, 0xa1, 0x55, 0x40, 0x35, 0x51, 0xa0, 0x2a, 0x10, 0xa8, 0x16, 0x60, +0x29, 0xd8, 0xa0, 0x0c, 0x48, 0xa0, 0x0a, 0xc8, 0x95, 0x60, 0x92, 0xb0, +0x91, 0x40, 0x90, 0x88, 0x90, 0x50, 0x90, 0x28, 0x80, 0x31, 0xa1, 0x80, +0x31, 0xa0, 0x10, 0x10, 0x80, 0x31, 0x9f, 0x90, 0x70, 0x90, 0x38, 0xa4, +0x08, 0x98, 0x31, 0xb3, 0xa4, 0x08, 0x90, 0x31, 0xb2, 0x10, 0x10, 0xa4, +0x08, 0x88, 0x31, 0xb1, 0x90, 0xb8, 0x90, 0x70, 0x90, 0x38, 0xa4, 0x09, +0xb8, 0x31, 0xd7, 0xa4, 0x09, 0xb0, 0x31, 0xd6, 0x10, 0x10, 0xa4, 0x09, +0xa8, 0x31, 0xd5, 0x90, 0x70, 0x90, 0x38, 0xa4, 0x09, 0x28, 0x31, 0xc5, +0xa4, 0x09, 0x20, 0x31, 0xc4, 0x10, 0x10, 0xa4, 0x09, 0x18, 0x31, 0xc3, +0x91, 0x40, 0x90, 0x88, 0x90, 0x50, 0x90, 0x28, 0x80, 0x31, 0x9c, 0x80, +0x31, 0x9e, 0x10, 0x10, 0x80, 0x31, 0x9d, 0x90, 0x70, 0x90, 0x38, 0xa4, +0x08, 0x70, 0x31, 0xae, 0xa4, 0x08, 0x80, 0x31, 0xb0, 0x10, 0x10, 0xa4, +0x08, 0x78, 0x31, 0xaf, 0x90, 0xb8, 0x90, 0x70, 0x90, 0x38, 0xa4, 0x09, +0x90, 0x31, 0xd2, 0xa4, 0x09, 0xa0, 0x31, 0xd4, 0x10, 0x10, 0xa4, 0x09, +0x98, 0x31, 0xd3, 0x90, 0x70, 0x90, 0x38, 0xa4, 0x09, 0x00, 0x31, 0xc0, +0xa4, 0x09, 0x10, 0x31, 0xc2, 0x10, 0x10, 0xa4, 0x09, 0x08, 0x31, 0xc1, +0x92, 0xb0, 0x91, 0x40, 0x90, 0x88, 0x90, 0x50, 0x90, 0x28, 0x80, 0x31, +0x99, 0x80, 0x31, 0x9b, 0x10, 0x10, 0x80, 0x31, 0x9a, 0x90, 0x70, 0x90, +0x38, 0xa4, 0x08, 0x58, 0x31, 0xab, 0xa4, 0x08, 0x68, 0x31, 0xad, 0x10, +0x10, 0xa4, 0x08, 0x60, 0x31, 0xac, 0x90, 0xb8, 0x90, 0x70, 0x90, 0x38, +0xa4, 0x09, 0x78, 0x31, 0xcf, 0xa4, 0x09, 0x88, 0x31, 0xd1, 0x10, 0x10, +0xa4, 0x09, 0x80, 0x31, 0xd0, 0x90, 0x70, 0x90, 0x38, 0xa4, 0x08, 0xe8, +0x31, 0xbd, 0xa4, 0x08, 0xf8, 0x31, 0xbf, 0x10, 0x10, 0xa4, 0x08, 0xf0, +0x31, 0xbe, 0x91, 0x40, 0x90, 0x88, 0x90, 0x50, 0x90, 0x28, 0x80, 0x31, +0x96, 0x80, 0x31, 0x98, 0x10, 0x10, 0x80, 0x31, 0x97, 0x90, 0x70, 0x90, +0x38, 0xa4, 0x08, 0x40, 0x31, 0xa8, 0xa4, 0x08, 0x50, 0x31, 0xaa, 0x10, +0x10, 0xa4, 0x08, 0x48, 0x31, 0xa9, 0x90, 0xb8, 0x90, 0x70, 0x90, 0x38, +0xa4, 0x09, 0x60, 0x31, 0xcc, 0xa4, 0x09, 0x70, 0x31, 0xce, 0x10, 0x10, +0xa4, 0x09, 0x68, 0x31, 0xcd, 0x90, 0x70, 0x90, 0x38, 0xa4, 0x08, 0xd0, +0x31, 0xba, 0xa4, 0x08, 0xe0, 0x31, 0xbc, 0x10, 0x10, 0xa4, 0x08, 0xd8, +0x31, 0xbb, 0x10, 0x10, 0x90, 0xa8, 0x10, 0x10, 0x10, 0x10, 0x90, 0x50, +0x90, 0x28, 0x80, 0x31, 0x8d, 0x80, 0x31, 0x8f, 0x10, 0x10, 0x80, 0x31, +0x8e, 0x90, 0x60, 0x90, 0x30, 0x60, 0xa0, 0x2a, 0xc0, 0x60, 0xa0, 0x2a, +0x80, 0x90, 0x30, 0x60, 0xa0, 0x2a, 0x40, 0x60, 0xa0, 0x2a, 0x00, 0x97, +0xf0, 0x95, 0x60, 0x92, 0xb0, 0x91, 0x40, 0x90, 0x88, 0x90, 0x50, 0x90, +0x28, 0x80, 0x31, 0x93, 0x80, 0x31, 0x95, 0x10, 0x10, 0x80, 0x31, 0x94, +0x90, 0x70, 0x90, 0x38, 0xa4, 0x08, 0x28, 0x31, 0xa5, 0xa4, 0x08, 0x38, +0x31, 0xa7, 0x10, 0x10, 0xa4, 0x08, 0x30, 0x31, 0xa6, 0x90, 0xb8, 0x90, +0x70, 0x90, 0x38, 0xa4, 0x09, 0x48, 0x31, 0xc9, 0xa4, 0x09, 0x58, 0x31, +0xcb, 0x10, 0x10, 0xa4, 0x09, 0x50, 0x31, 0xca, 0x90, 0x70, 0x90, 0x38, +0xa4, 0x08, 0xb8, 0x31, 0xb7, 0xa4, 0x08, 0xc8, 0x31, 0xb9, 0x10, 0x10, +0xa4, 0x08, 0xc0, 0x31, 0xb8, 0x91, 0x40, 0x90, 0x88, 0x90, 0x50, 0x90, +0x28, 0x80, 0x31, 0x90, 0x80, 0x31, 0x92, 0x10, 0x10, 0x80, 0x31, 0x91, +0x90, 0x70, 0x90, 0x38, 0xa4, 0x08, 0x10, 0x31, 0xa2, 0xa4, 0x08, 0x20, +0x31, 0xa4, 0x10, 0x10, 0xa4, 0x08, 0x18, 0x31, 0xa3, 0x90, 0xb8, 0x90, +0x70, 0x90, 0x38, 0xa4, 0x09, 0x30, 0x31, 0xc6, 0xa4, 0x09, 0x40, 0x31, +0xc8, 0x10, 0x10, 0xa4, 0x09, 0x38, 0x31, 0xc7, 0x90, 0x70, 0x90, 0x38, +0xa4, 0x08, 0xa0, 0x31, 0xb4, 0xa4, 0x08, 0xb0, 0x31, 0xb6, 0x10, 0x10, +0xa4, 0x08, 0xa8, 0x31, 0xb5, 0x10, 0x10, 0x91, 0x40, 0x90, 0xa0, 0x90, +0x50, 0x90, 0x28, 0x80, 0x30, 0xcb, 0x80, 0x30, 0xca, 0x90, 0x28, 0x80, +0x30, 0xc9, 0x80, 0x30, 0xc8, 0x90, 0x50, 0x90, 0x28, 0x80, 0x30, 0xc4, +0x80, 0x30, 0xc7, 0x90, 0x28, 0x80, 0x30, 0xc6, 0x80, 0x30, 0xc5, 0x90, +0xa0, 0x90, 0x50, 0x90, 0x28, 0x80, 0x30, 0xbc, 0x80, 0x30, 0xc3, 0x90, +0x28, 0x80, 0x30, 0xc2, 0x80, 0x30, 0xc1, 0x90, 0x50, 0x90, 0x28, 0x80, +0x30, 0xbd, 0x80, 0x30, 0xc0, 0x90, 0x28, 0x80, 0x30, 0xbf, 0x80, 0x30, +0xbe, 0x91, 0x88, 0x80, 0x90, 0xc0, 0x90, 0x60, 0x90, 0x28, 0x81, 0x31, +0x3b, 0x10, 0x10, 0x80, 0x31, 0x3a, 0x90, 0x28, 0x81, 0x31, 0x3d, 0x10, +0x10, 0x80, 0x31, 0x3c, 0x90, 0x60, 0x90, 0x28, 0x81, 0x31, 0x41, 0x10, +0x10, 0x80, 0x31, 0x40, 0x90, 0x28, 0x81, 0x31, 0x3f, 0x10, 0x10, 0x80, +0x31, 0x3e, 0x80, 0x10, 0x10, 0x10, 0x10, 0x90, 0x28, 0x81, 0x31, 0x38, +0x10, 0x10, 0x80, 0x31, 0x39, 0xa0, 0x0b, 0x90, 0xa0, 0x0a, 0xc8, 0x95, +0x60, 0x92, 0xb0, 0x91, 0x40, 0x90, 0x88, 0x90, 0x50, 0x90, 0x28, 0x80, +0x31, 0x56, 0x80, 0x31, 0x55, 0x10, 0x10, 0x80, 0x31, 0x54, 0x90, 0x70, +0x90, 0x38, 0xa4, 0x06, 0xe8, 0x31, 0x68, 0xa4, 0x06, 0xe0, 0x31, 0x67, +0x10, 0x10, 0xa4, 0x06, 0xd8, 0x31, 0x66, 0x90, 0xb8, 0x90, 0x70, 0x90, +0x38, 0xa4, 0x08, 0x08, 0x31, 0x8c, 0xa4, 0x08, 0x00, 0x31, 0x8b, 0x10, +0x10, 0xa4, 0x07, 0xf8, 0x31, 0x8a, 0x90, 0x70, 0x90, 0x38, 0xa4, 0x07, +0x78, 0x31, 0x7a, 0xa4, 0x07, 0x70, 0x31, 0x79, 0x10, 0x10, 0xa4, 0x07, +0x68, 0x31, 0x78, 0x91, 0x40, 0x90, 0x88, 0x90, 0x50, 0x90, 0x28, 0x80, +0x31, 0x51, 0x80, 0x31, 0x53, 0x10, 0x10, 0x80, 0x31, 0x52, 0x90, 0x70, +0x90, 0x38, 0xa4, 0x06, 0xc0, 0x31, 0x63, 0xa4, 0x06, 0xd0, 0x31, 0x65, +0x10, 0x10, 0xa4, 0x06, 0xc8, 0x31, 0x64, 0x90, 0xb8, 0x90, 0x70, 0x90, +0x38, 0xa4, 0x07, 0xe0, 0x31, 0x87, 0xa4, 0x07, 0xf0, 0x31, 0x89, 0x10, +0x10, 0xa4, 0x07, 0xe8, 0x31, 0x88, 0x90, 0x70, 0x90, 0x38, 0xa4, 0x07, +0x50, 0x31, 0x75, 0xa4, 0x07, 0x60, 0x31, 0x77, 0x10, 0x10, 0xa4, 0x07, +0x58, 0x31, 0x76, 0x92, 0xb0, 0x91, 0x40, 0x90, 0x88, 0x90, 0x50, 0x90, +0x28, 0x80, 0x31, 0x4e, 0x80, 0x31, 0x50, 0x10, 0x10, 0x80, 0x31, 0x4f, +0x90, 0x70, 0x90, 0x38, 0xa4, 0x06, 0xa8, 0x31, 0x60, 0xa4, 0x06, 0xb8, +0x31, 0x62, 0x10, 0x10, 0xa4, 0x06, 0xb0, 0x31, 0x61, 0x90, 0xb8, 0x90, +0x70, 0x90, 0x38, 0xa4, 0x07, 0xc8, 0x31, 0x84, 0xa4, 0x07, 0xd8, 0x31, +0x86, 0x10, 0x10, 0xa4, 0x07, 0xd0, 0x31, 0x85, 0x90, 0x70, 0x90, 0x38, +0xa4, 0x07, 0x38, 0x31, 0x72, 0xa4, 0x07, 0x48, 0x31, 0x74, 0x10, 0x10, +0xa4, 0x07, 0x40, 0x31, 0x73, 0x91, 0x40, 0x90, 0x88, 0x90, 0x50, 0x90, +0x28, 0x80, 0x31, 0x4b, 0x80, 0x31, 0x4d, 0x10, 0x10, 0x80, 0x31, 0x4c, +0x90, 0x70, 0x90, 0x38, 0xa4, 0x06, 0x90, 0x31, 0x5d, 0xa4, 0x06, 0xa0, +0x31, 0x5f, 0x10, 0x10, 0xa4, 0x06, 0x98, 0x31, 0x5e, 0x90, 0xb8, 0x90, +0x70, 0x90, 0x38, 0xa4, 0x07, 0xb0, 0x31, 0x81, 0xa4, 0x07, 0xc0, 0x31, +0x83, 0x10, 0x10, 0xa4, 0x07, 0xb8, 0x31, 0x82, 0x90, 0x70, 0x90, 0x38, +0xa4, 0x07, 0x20, 0x31, 0x6f, 0xa4, 0x07, 0x30, 0x31, 0x71, 0x10, 0x10, +0xa4, 0x07, 0x28, 0x31, 0x70, 0x10, 0x10, 0x80, 0x10, 0x10, 0x10, 0x10, +0x90, 0x50, 0x90, 0x28, 0x80, 0x31, 0x42, 0x80, 0x31, 0x44, 0x10, 0x10, +0x80, 0x31, 0x43, 0x80, 0x95, 0x60, 0x92, 0xb0, 0x91, 0x40, 0x90, 0x88, +0x90, 0x50, 0x90, 0x28, 0x80, 0x31, 0x48, 0x80, 0x31, 0x4a, 0x10, 0x10, +0x80, 0x31, 0x49, 0x90, 0x70, 0x90, 0x38, 0xa4, 0x06, 0x78, 0x31, 0x5a, +0xa4, 0x06, 0x88, 0x31, 0x5c, 0x10, 0x10, 0xa4, 0x06, 0x80, 0x31, 0x5b, +0x90, 0xb8, 0x90, 0x70, 0x90, 0x38, 0xa4, 0x07, 0x98, 0x31, 0x7e, 0xa4, +0x07, 0xa8, 0x31, 0x80, 0x10, 0x10, 0xa4, 0x07, 0xa0, 0x31, 0x7f, 0x90, +0x70, 0x90, 0x38, 0xa4, 0x07, 0x08, 0x31, 0x6c, 0xa4, 0x07, 0x18, 0x31, +0x6e, 0x10, 0x10, 0xa4, 0x07, 0x10, 0x31, 0x6d, 0x91, 0x40, 0x90, 0x88, +0x90, 0x50, 0x90, 0x28, 0x80, 0x31, 0x45, 0x80, 0x31, 0x47, 0x10, 0x10, +0x80, 0x31, 0x46, 0x90, 0x70, 0x90, 0x38, 0xa4, 0x06, 0x60, 0x31, 0x57, +0xa4, 0x06, 0x70, 0x31, 0x59, 0x10, 0x10, 0xa4, 0x06, 0x68, 0x31, 0x58, +0x90, 0xb8, 0x90, 0x70, 0x90, 0x38, 0xa4, 0x07, 0x80, 0x31, 0x7b, 0xa4, +0x07, 0x90, 0x31, 0x7d, 0x10, 0x10, 0xa4, 0x07, 0x88, 0x31, 0x7c, 0x90, +0x70, 0x90, 0x38, 0xa4, 0x06, 0xf0, 0x31, 0x69, 0xa4, 0x07, 0x00, 0x31, +0x6b, 0x10, 0x10, 0xa4, 0x06, 0xf8, 0x31, 0x6a, 0x10, 0x10, 0x91, 0x40, +0x90, 0xa0, 0x90, 0x50, 0x90, 0x28, 0x80, 0x30, 0xbb, 0x80, 0x30, 0xba, +0x90, 0x28, 0x80, 0x30, 0xb9, 0x80, 0x30, 0xb8, 0x90, 0x50, 0x90, 0x28, +0x80, 0x30, 0xb4, 0x80, 0x30, 0xb7, 0x90, 0x28, 0x80, 0x30, 0xb6, 0x80, +0x30, 0xb5, 0x90, 0xa0, 0x90, 0x50, 0x90, 0x28, 0x80, 0x30, 0xac, 0x80, +0x30, 0xb3, 0x90, 0x28, 0x80, 0x30, 0xb2, 0x80, 0x30, 0xb1, 0x90, 0x50, +0x90, 0x28, 0x80, 0x30, 0xad, 0x80, 0x30, 0xb0, 0x90, 0x28, 0x80, 0x30, +0xaf, 0x80, 0x30, 0xae, 0xc3, 0xc0, 0x30, 0x42, 0x9c, 0xe8, 0x07, 0x60, +0x91, 0x90, 0x90, 0xf0, 0x10, 0x10, 0x80, 0x88, 0x00, 0x80, 0x90, 0x50, +0x90, 0x28, 0x80, 0x33, 0xf8, 0x80, 0x33, 0xf9, 0x81, 0x33, 0xef, 0xd0, +0x41, 0x80, 0x24, 0x20, 0x90, 0x24, 0x20, 0x98, 0x10, 0x10, 0x80, 0x90, +0x58, 0x80, 0x90, 0x28, 0x24, 0x1f, 0x90, 0x24, 0x1f, 0x98, 0x81, 0x24, +0x1f, 0x50, 0x92, 0x68, 0x91, 0x00, 0x80, 0x90, 0x90, 0x90, 0x30, 0x80, +0x24, 0x20, 0x00, 0x90, 0x38, 0xa4, 0x1f, 0xf8, 0x34, 0x06, 0x80, 0x34, +0x05, 0x80, 0x90, 0x28, 0x80, 0x34, 0x0f, 0xa4, 0x1f, 0xe0, 0x34, 0x0e, +0x80, 0x90, 0xc0, 0x90, 0x60, 0x90, 0x28, 0x80, 0x34, 0x09, 0xa4, 0x1f, +0xf0, 0x34, 0x08, 0x90, 0x28, 0x80, 0x34, 0x04, 0xa4, 0x1f, 0xe8, 0x34, +0x03, 0x90, 0x50, 0x90, 0x28, 0x80, 0x34, 0x0d, 0x80, 0x34, 0x0c, 0x90, +0x28, 0x24, 0x20, 0x88, 0x24, 0x20, 0x80, 0x90, 0x58, 0x80, 0x10, 0x10, +0x80, 0x10, 0x10, 0x80, 0x33, 0xfb, 0x80, 0x90, 0x40, 0x10, 0x10, 0x80, +0x24, 0x1f, 0x60, 0x80, 0x10, 0x10, 0x80, 0x33, 0xfa, 0x91, 0x58, 0x91, +0x00, 0x90, 0x80, 0x81, 0x90, 0x50, 0x90, 0x28, 0x80, 0x33, 0xf6, 0x80, +0x33, 0xf7, 0x81, 0x33, 0xee, 0x81, 0x90, 0x50, 0x90, 0x28, 0x80, 0x33, +0xf4, 0x80, 0x33, 0xf5, 0x81, 0x33, 0xed, 0x83, 0x90, 0x28, 0x24, 0x1f, +0x80, 0x24, 0x1f, 0x88, 0x90, 0xe8, 0x81, 0x90, 0x88, 0x90, 0x38, 0x10, +0x10, 0x80, 0x34, 0x07, 0x90, 0x28, 0x80, 0x34, 0x02, 0x80, 0x34, 0x01, +0x80, 0x90, 0x28, 0x80, 0x34, 0x0b, 0x80, 0x34, 0x0a, 0x82, 0x10, 0x10, +0x80, 0x24, 0x1f, 0x58, 0x97, 0x10, 0x9e, 0x10, 0x06, 0x98, 0x93, 0x00, +0x91, 0x80, 0x90, 0xc0, 0x90, 0x60, 0x90, 0x38, 0xa4, 0x03, 0x80, 0x30, +0x71, 0x24, 0x03, 0x78, 0x90, 0x38, 0xa4, 0x04, 0x10, 0x30, 0x83, 0x24, +0x04, 0x08, 0x90, 0x60, 0x90, 0x38, 0xa4, 0x05, 0x30, 0x30, 0xa7, 0x24, +0x05, 0x28, 0x90, 0x38, 0xa4, 0x04, 0xa0, 0x30, 0x95, 0x24, 0x04, 0x98, +0x90, 0xc0, 0x90, 0x60, 0x90, 0x38, 0xa4, 0x03, 0x70, 0x30, 0x6c, 0x24, +0x03, 0x68, 0x90, 0x38, 0xa4, 0x04, 0x00, 0x30, 0x7e, 0x24, 0x03, 0xf8, +0x90, 0x60, 0x90, 0x38, 0xa4, 0x05, 0x20, 0x30, 0xa2, 0x24, 0x05, 0x18, +0x90, 0x38, 0xa4, 0x04, 0x90, 0x30, 0x90, 0x24, 0x04, 0x88, 0x91, 0x80, +0x90, 0xc0, 0x90, 0x60, 0x90, 0x38, 0xa4, 0x03, 0x58, 0x30, 0x69, 0x24, +0x03, 0x50, 0x90, 0x38, 0xa4, 0x03, 0xe8, 0x30, 0x7b, 0x24, 0x03, 0xe0, +0x90, 0x60, 0x90, 0x38, 0xa4, 0x05, 0x08, 0x30, 0x9f, 0x24, 0x05, 0x00, +0x90, 0x38, 0xa4, 0x04, 0x78, 0x30, 0x8d, 0x24, 0x04, 0x70, 0x90, 0xc0, +0x90, 0x60, 0x90, 0x38, 0xa4, 0x03, 0x40, 0x30, 0x66, 0x24, 0x03, 0x38, +0x90, 0x38, 0xa4, 0x03, 0xd0, 0x30, 0x78, 0x24, 0x03, 0xc8, 0x90, 0x60, +0x90, 0x38, 0xa4, 0x04, 0xf0, 0x30, 0x9c, 0x24, 0x04, 0xe8, 0x90, 0x38, +0xa4, 0x04, 0x60, 0x30, 0x8a, 0x24, 0x04, 0x58, 0x10, 0x10, 0x80, 0x10, +0x10, 0x10, 0x10, 0x90, 0x38, 0xa4, 0x02, 0xf8, 0x30, 0x5d, 0x24, 0x02, +0xf0, 0xd7, 0x42, 0x00, 0xa4, 0x39, 0x58, 0x37, 0x2d, 0xa4, 0x39, 0x38, +0x37, 0x29, 0x9c, 0xe0, 0x06, 0x90, 0x93, 0x00, 0x91, 0x80, 0x90, 0xc0, +0x90, 0x60, 0x90, 0x38, 0xa4, 0x03, 0x28, 0x30, 0x63, 0x24, 0x03, 0x20, +0x90, 0x38, 0xa4, 0x03, 0xb8, 0x30, 0x75, 0x24, 0x03, 0xb0, 0x90, 0x60, +0x90, 0x38, 0xa4, 0x04, 0xd8, 0x30, 0x99, 0x24, 0x04, 0xd0, 0x90, 0x38, +0xa4, 0x04, 0x48, 0x30, 0x87, 0x24, 0x04, 0x40, 0x90, 0xc0, 0x90, 0x60, +0x90, 0x38, 0xa4, 0x03, 0x10, 0x30, 0x60, 0x24, 0x03, 0x08, 0x90, 0x38, +0xa4, 0x03, 0xa0, 0x30, 0x72, 0x24, 0x03, 0x98, 0x90, 0x60, 0x90, 0x38, +0xa4, 0x04, 0xc0, 0x30, 0x96, 0x24, 0x04, 0xb8, 0x90, 0x38, 0xa4, 0x04, +0x30, 0x30, 0x84, 0x24, 0x04, 0x28, 0x10, 0x10, 0x90, 0xe0, 0x90, 0x70, +0x90, 0x38, 0xa4, 0x02, 0x88, 0x30, 0x52, 0xa4, 0x02, 0x78, 0x30, 0x50, +0x90, 0x38, 0xa4, 0x02, 0x70, 0x30, 0x4b, 0xa4, 0x02, 0x60, 0x30, 0x4d, +0x90, 0x70, 0x90, 0x38, 0xa4, 0x02, 0x50, 0x30, 0x43, 0xa4, 0x02, 0x40, +0x30, 0x49, 0x90, 0x38, 0xa4, 0x02, 0x38, 0x30, 0x44, 0xa4, 0x02, 0x28, +0x30, 0x46, 0x91, 0x48, 0x80, 0x90, 0xa0, 0x90, 0x50, 0x90, 0x28, 0x80, +0x30, 0x56, 0x24, 0x02, 0xa8, 0x90, 0x28, 0x80, 0x30, 0x58, 0x24, 0x02, +0xb8, 0x90, 0x50, 0x90, 0x28, 0x80, 0x30, 0x5c, 0x24, 0x02, 0xd8, 0x90, +0x28, 0x80, 0x30, 0x5a, 0x24, 0x02, 0xc8, 0x80, 0x10, 0x10, 0x10, 0x10, +0x90, 0x28, 0x80, 0x30, 0x53, 0x24, 0x02, 0xa0, 0xd7, 0x42, 0x00, 0xa4, +0x39, 0x60, 0x37, 0x2e, 0xa4, 0x39, 0x40, 0x37, 0x2a, 0xa0, 0x14, 0x68, +0xa0, 0x10, 0x90, 0xa0, 0x0c, 0x60, 0x9e, 0x88, 0x09, 0xd0, 0x94, 0xf0, +0x90, 0xb0, 0x88, 0x00, 0x68, 0x84, 0x10, 0x10, 0xc9, 0xe1, 0x4c, 0x40, +0x85, 0x35, 0x4d, 0xcb, 0x61, 0x45, 0x00, 0x85, 0x35, 0x23, 0x9a, 0x00, +0x03, 0xf8, 0x91, 0x98, 0x80, 0x91, 0x10, 0x90, 0xa0, 0x90, 0x68, 0x90, +0x20, 0x3a, 0x75, 0xc9, 0xe2, 0x9c, 0xc0, 0x85, 0x35, 0x4b, 0xa4, 0x53, +0x88, 0x3a, 0x72, 0x90, 0x38, 0xa4, 0x53, 0x50, 0x3a, 0x6b, 0xa4, 0x53, +0x40, 0x3a, 0x69, 0x90, 0x48, 0x10, 0x10, 0xa4, 0x53, 0x08, 0x3a, 0x62, +0x10, 0x10, 0x80, 0x3a, 0x5e, 0x81, 0x10, 0x10, 0x80, 0xa4, 0x52, 0xd8, +0x3a, 0x5c, 0x91, 0xb0, 0x91, 0x60, 0x90, 0xe0, 0x90, 0x70, 0x90, 0x38, +0xa4, 0x53, 0x78, 0x3a, 0x70, 0xa4, 0x53, 0x68, 0x3a, 0x6e, 0x90, 0x38, +0xa4, 0x53, 0x30, 0x3a, 0x67, 0xa4, 0x53, 0x20, 0x3a, 0x65, 0x90, 0x48, +0x10, 0x10, 0xa4, 0x52, 0xf8, 0x3a, 0x60, 0x10, 0x10, 0x80, 0x3a, 0x5d, +0x90, 0x28, 0x80, 0x3a, 0x56, 0x80, 0x3a, 0x55, 0x81, 0x10, 0x10, 0x80, +0xa4, 0x52, 0xc8, 0x3a, 0x5a, 0xcb, 0x61, 0x44, 0xc0, 0x85, 0x35, 0x22, +0x90, 0xd8, 0x88, 0x00, 0x90, 0x84, 0x90, 0x38, 0xc1, 0xc0, 0x85, 0x3a, +0x78, 0xc9, 0xe1, 0x4c, 0x00, 0x85, 0x35, 0x49, 0xcb, 0x61, 0x44, 0x80, +0x85, 0x35, 0x21, 0x88, 0x00, 0x68, 0x84, 0x10, 0x10, 0xc9, 0xe1, 0x4b, +0xc0, 0x85, 0x35, 0x47, 0xcb, 0x61, 0x44, 0x40, 0x85, 0x35, 0x20, 0x91, +0xf8, 0x90, 0xb0, 0x88, 0x00, 0x68, 0x84, 0x10, 0x10, 0xc9, 0xe1, 0x4b, +0x40, 0x85, 0x35, 0x43, 0xcb, 0x61, 0x43, 0xc0, 0x85, 0x35, 0x1e, 0x88, +0x01, 0x00, 0x90, 0xa0, 0x81, 0x90, 0x70, 0x80, 0x90, 0x20, 0x3a, 0x6c, +0xc9, 0xe1, 0x4b, 0x00, 0x85, 0x35, 0x41, 0x81, 0x3a, 0x63, 0x81, 0x10, +0x10, 0x80, 0xa4, 0x52, 0xb8, 0x3a, 0x58, 0xcb, 0x61, 0x43, 0x80, 0x85, +0x35, 0x1d, 0x90, 0xb0, 0x88, 0x00, 0x68, 0x84, 0x10, 0x10, 0xc9, 0xe1, +0x4a, 0xc0, 0x85, 0x35, 0x3f, 0xcb, 0x61, 0x43, 0x40, 0x85, 0x35, 0x1c, +0x88, 0x00, 0x68, 0x84, 0x10, 0x10, 0xc9, 0xe1, 0x4a, 0x80, 0x85, 0x35, +0x3d, 0xcb, 0x61, 0x43, 0x00, 0x85, 0x35, 0x1b, 0x92, 0x38, 0x81, 0x91, +0x68, 0x91, 0x18, 0x90, 0x80, 0x90, 0x40, 0x80, 0xa4, 0x54, 0x38, 0x3a, +0x88, 0x80, 0xa4, 0x54, 0x30, 0x3a, 0x85, 0x90, 0x28, 0x81, 0x3a, 0x84, +0x90, 0x38, 0xa4, 0x54, 0x10, 0x3a, 0x83, 0xa4, 0x54, 0x00, 0x3a, 0x81, +0x90, 0x28, 0x80, 0x3a, 0x7f, 0x80, 0x3a, 0x7e, 0x80, 0x90, 0x40, 0x10, +0x10, 0x80, 0x24, 0x53, 0xe8, 0x10, 0x10, 0x90, 0x38, 0xa4, 0x53, 0xd8, +0x3a, 0x7c, 0xa4, 0x53, 0xc8, 0x3a, 0x7a, 0x90, 0x28, 0x80, 0x3a, 0x77, +0x80, 0x3a, 0x76, 0x9a, 0xd0, 0x03, 0xe0, 0x91, 0x60, 0x90, 0xb0, 0x88, +0x00, 0x68, 0x84, 0x10, 0x10, 0xc9, 0xe1, 0x4a, 0x00, 0x85, 0x35, 0x39, +0xcb, 0x61, 0x42, 0x80, 0x85, 0x35, 0x19, 0x88, 0x00, 0x68, 0x84, 0x10, +0x10, 0xc9, 0xe1, 0x49, 0xc0, 0x85, 0x35, 0x37, 0xcb, 0x61, 0x42, 0x40, +0x85, 0x35, 0x18, 0x90, 0xb0, 0x88, 0x00, 0x68, 0x84, 0x10, 0x10, 0xc9, +0xe1, 0x49, 0x80, 0x85, 0x35, 0x35, 0xcb, 0x61, 0x42, 0x00, 0x85, 0x35, +0x17, 0x88, 0x00, 0x68, 0x84, 0x10, 0x10, 0xc9, 0xe1, 0x49, 0x40, 0x85, +0x35, 0x33, 0xcb, 0x61, 0x41, 0xc0, 0x85, 0x35, 0x16, 0x90, 0x90, 0x90, +0x48, 0xcb, 0xa1, 0x40, 0x00, 0x85, 0x35, 0x05, 0xcb, 0xa1, 0x3f, 0xc0, +0x85, 0x35, 0x04, 0x90, 0x48, 0xcb, 0xa1, 0x3f, 0x80, 0x85, 0x35, 0x03, +0xcb, 0xa1, 0x3f, 0x40, 0x85, 0x35, 0x02, 0xcb, 0xa2, 0x94, 0xc0, 0x80, +0x3a, 0x54, 0x92, 0x40, 0x91, 0x20, 0x90, 0x90, 0x90, 0x48, 0x8c, 0x27, +0x60, 0x84, 0x24, 0x27, 0xd8, 0x8c, 0x27, 0x58, 0x84, 0x24, 0x27, 0xd0, +0x90, 0x48, 0x8c, 0x27, 0x50, 0x84, 0x24, 0x27, 0xc8, 0x8c, 0x27, 0x48, +0x84, 0x24, 0x27, 0xc0, 0x90, 0x90, 0x90, 0x48, 0x8c, 0x27, 0x38, 0x84, +0x24, 0x27, 0xb0, 0x8c, 0x27, 0x30, 0x84, 0x24, 0x27, 0xa8, 0x90, 0x48, +0x8c, 0x27, 0x28, 0x84, 0x24, 0x27, 0xa0, 0x8c, 0x27, 0x20, 0x84, 0x24, +0x27, 0x98, 0x91, 0x20, 0x90, 0x90, 0x90, 0x48, 0x8c, 0x27, 0x10, 0x84, +0x24, 0x27, 0x88, 0x8c, 0x27, 0x08, 0x84, 0x24, 0x27, 0x80, 0x90, 0x48, +0x8c, 0x27, 0x00, 0x84, 0x24, 0x27, 0x78, 0x8c, 0x26, 0xf8, 0x84, 0x24, +0x27, 0x70, 0x90, 0x38, 0xa4, 0x26, 0xe0, 0x34, 0xdd, 0xa4, 0x26, 0xd0, +0x34, 0xdb, 0xa0, 0x0f, 0x50, 0xa0, 0x09, 0x08, 0x9a, 0x30, 0x04, 0x40, +0x91, 0x90, 0x90, 0xc8, 0x98, 0x50, 0x00, 0x80, 0xe5, 0x22, 0x92, 0xc0, +0x3a, 0x43, 0xe5, 0x22, 0x8a, 0xc0, 0x3a, 0x3f, 0xcb, 0x61, 0x32, 0x40, +0x85, 0x34, 0xd8, 0x98, 0x50, 0x00, 0x80, 0xe5, 0x22, 0x82, 0xc0, 0x3a, +0x03, 0xe5, 0x22, 0x7a, 0xc0, 0x39, 0xff, 0xcb, 0x61, 0x32, 0x00, 0x85, +0x34, 0xd7, 0x90, 0x48, 0xcb, 0xa1, 0x31, 0xc0, 0x85, 0x34, 0xd6, 0xcb, +0xa1, 0x31, 0x80, 0x85, 0x34, 0xd5, 0x91, 0x90, 0x90, 0xc8, 0x98, 0x50, +0x00, 0x80, 0xe5, 0x22, 0x6c, 0xc0, 0x39, 0xcb, 0xe5, 0x22, 0x60, 0xc0, +0x39, 0x9b, 0xcb, 0x61, 0x31, 0x00, 0x85, 0x34, 0xd3, 0x98, 0x50, 0x00, +0x80, 0xe5, 0x22, 0x54, 0xc0, 0x39, 0x6b, 0xe5, 0x22, 0x48, 0xc0, 0x39, +0x3b, 0xcb, 0x61, 0x30, 0xc0, 0x85, 0x34, 0xd2, 0x90, 0x48, 0xcb, 0xa1, +0x30, 0x80, 0x85, 0x34, 0xd1, 0xcb, 0xa1, 0x30, 0x40, 0x85, 0x34, 0xd0, +0x92, 0x20, 0x91, 0x30, 0x90, 0xb8, 0xd5, 0x03, 0x00, 0xc0, 0xc0, 0x81, +0x8c, 0x01, 0xa0, 0x84, 0x30, 0x3e, 0xc0, 0xc0, 0x81, 0x8c, 0x01, 0x80, +0x84, 0x30, 0x3c, 0xd5, 0x02, 0x00, 0xc0, 0xc0, 0x81, 0x30, 0x28, 0xc0, +0xc0, 0x81, 0x30, 0x24, 0x90, 0x78, 0xd5, 0x02, 0x00, 0xc0, 0xc0, 0x81, +0x30, 0x1c, 0xc0, 0xc0, 0x81, 0x30, 0x18, 0xd5, 0x02, 0x00, 0xc0, 0xc0, +0x81, 0x30, 0x10, 0xc0, 0xc0, 0x81, 0x30, 0x0c, 0x91, 0x70, 0x90, 0xd8, +0xd5, 0x03, 0x80, 0xc8, 0xe2, 0x40, 0xc0, 0x81, 0x8c, 0x01, 0xc0, 0x84, +0x30, 0x40, 0xc8, 0xe2, 0x42, 0xc0, 0x81, 0x8c, 0x01, 0x90, 0x84, 0x30, +0x3d, 0xd5, 0x02, 0x80, 0xc8, 0xe2, 0x3f, 0xc0, 0x81, 0x30, 0x2c, 0xc8, +0xe2, 0x3a, 0x40, 0x81, 0x30, 0x26, 0x90, 0x98, 0xd5, 0x02, 0x80, 0xc8, +0xe2, 0x2f, 0x40, 0x81, 0x30, 0x20, 0xc8, 0xe2, 0x31, 0x40, 0x81, 0x30, +0x1a, 0xd5, 0x02, 0x80, 0xc8, 0xe2, 0x2e, 0x40, 0x81, 0x30, 0x14, 0xc8, +0xe2, 0x28, 0xc0, 0x81, 0x30, 0x0e, 0x9a, 0x30, 0x04, 0x40, 0x91, 0x90, +0x90, 0xc8, 0x98, 0x50, 0x00, 0x80, 0xe5, 0x22, 0x86, 0xc0, 0x3a, 0x13, +0xe5, 0x22, 0x88, 0xc0, 0x3a, 0x37, 0xcb, 0x61, 0x2f, 0xc0, 0x85, 0x34, +0xce, 0x98, 0x50, 0x00, 0x80, 0xe5, 0x22, 0x76, 0xc0, 0x39, 0xd3, 0xe5, +0x22, 0x78, 0xc0, 0x39, 0xf7, 0xcb, 0x61, 0x2f, 0x80, 0x85, 0x34, 0xcd, +0x90, 0x48, 0xcb, 0xa1, 0x2f, 0x40, 0x85, 0x34, 0xcc, 0xcb, 0xa1, 0x2f, +0x00, 0x85, 0x34, 0xcb, 0x91, 0x90, 0x90, 0xc8, 0x98, 0x50, 0x00, 0x80, +0xe5, 0x22, 0x68, 0xc0, 0x39, 0xbb, 0xe5, 0x22, 0x5c, 0xc0, 0x39, 0x8b, +0xcb, 0x61, 0x2d, 0x40, 0x85, 0x34, 0xba, 0x98, 0x50, 0x00, 0x80, 0xe5, +0x22, 0x50, 0xc0, 0x39, 0x5b, 0xe5, 0x22, 0x44, 0xc0, 0x39, 0x2b, 0xcb, +0x61, 0x2d, 0x00, 0x85, 0x34, 0xb9, 0x90, 0x48, 0xcb, 0xa1, 0x2c, 0xc0, +0x85, 0x34, 0xb8, 0xcb, 0xa1, 0x2c, 0x80, 0x85, 0x34, 0xb7, 0x91, 0x00, +0x90, 0x80, 0x90, 0x40, 0xe5, 0x20, 0x02, 0x40, 0x30, 0x0a, 0xe5, 0x20, +0x01, 0x80, 0x30, 0x07, 0x90, 0x40, 0xe5, 0x20, 0x00, 0xc0, 0x30, 0x04, +0xe5, 0x20, 0x00, 0x00, 0x30, 0x01, 0x90, 0x80, 0x90, 0x40, 0xe5, 0x22, +0x35, 0xc0, 0x38, 0xcd, 0xe5, 0x22, 0x38, 0x00, 0x38, 0xf5, 0x90, 0x40, +0xe5, 0x22, 0x24, 0x40, 0x38, 0x87, 0xe5, 0x22, 0x26, 0x80, 0x38, 0xaf, +0x80, 0x99, 0x28, 0x02, 0xf0, 0x8c, 0x25, 0x48, 0x90, 0x80, 0x90, 0x40, +0xe5, 0x22, 0x8c, 0xc0, 0x3a, 0x2f, 0xe5, 0x22, 0x89, 0xc0, 0x3a, 0x3b, +0x90, 0x40, 0xe5, 0x22, 0x7c, 0xc0, 0x39, 0xef, 0xe5, 0x22, 0x79, 0xc0, +0x39, 0xfb, 0x91, 0x48, 0x90, 0xc8, 0x98, 0x50, 0x00, 0x80, 0xe5, 0x22, +0x6a, 0xc0, 0x39, 0xc3, 0xe5, 0x22, 0x5e, 0xc0, 0x39, 0x93, 0xcb, 0x61, +0x2b, 0x00, 0x85, 0x34, 0xb0, 0x90, 0x40, 0xe5, 0x22, 0x52, 0xc0, 0x39, +0x63, 0xe5, 0x22, 0x46, 0xc0, 0x39, 0x33, 0x90, 0x48, 0xcb, 0xa1, 0x2a, +0x80, 0x85, 0x34, 0xae, 0xcb, 0xa1, 0x2a, 0xc0, 0x85, 0x34, 0xaf, 0x10, +0x10, 0x90, 0x80, 0x90, 0x40, 0xe5, 0x22, 0x3c, 0x40, 0x38, 0xed, 0xe5, +0x22, 0x39, 0x40, 0x38, 0xfb, 0x90, 0x40, 0xe5, 0x22, 0x2a, 0xc0, 0x38, +0xa7, 0xe5, 0x22, 0x27, 0xc0, 0x38, 0xb5, +}; + +static const struct ia64_dis_names ia64_dis_names[] = { +{ 0x51, 41, 0, 10 }, +{ 0x31, 41, 1, 20 }, +{ 0x11, 42, 0, 19 }, +{ 0x29, 41, 0, 12 }, +{ 0x19, 41, 1, 24 }, +{ 0x9, 42, 0, 23 }, +{ 0x15, 41, 0, 14 }, +{ 0xd, 41, 1, 28 }, +{ 0x5, 42, 0, 27 }, +{ 0xb, 41, 0, 16 }, +{ 0x7, 41, 1, 32 }, +{ 0x3, 42, 0, 31 }, +{ 0x51, 39, 1, 58 }, +{ 0x50, 39, 0, 34 }, +{ 0xd1, 39, 1, 57 }, +{ 0xd0, 39, 0, 33 }, +{ 0x31, 39, 1, 68 }, +{ 0x30, 39, 1, 44 }, +{ 0x11, 40, 1, 67 }, +{ 0x10, 40, 0, 43 }, +{ 0x71, 39, 1, 66 }, +{ 0x70, 39, 1, 42 }, +{ 0x31, 40, 1, 65 }, +{ 0x30, 40, 0, 41 }, +{ 0x29, 39, 1, 60 }, +{ 0x28, 39, 0, 36 }, +{ 0x69, 39, 1, 59 }, +{ 0x68, 39, 0, 35 }, +{ 0x19, 39, 1, 72 }, +{ 0x18, 39, 1, 48 }, +{ 0x9, 40, 1, 71 }, +{ 0x8, 40, 0, 47 }, +{ 0x39, 39, 1, 70 }, +{ 0x38, 39, 1, 46 }, +{ 0x19, 40, 1, 69 }, +{ 0x18, 40, 0, 45 }, +{ 0x15, 39, 1, 62 }, +{ 0x14, 39, 0, 38 }, +{ 0x35, 39, 1, 61 }, +{ 0x34, 39, 0, 37 }, +{ 0xd, 39, 1, 76 }, +{ 0xc, 39, 1, 52 }, +{ 0x5, 40, 1, 75 }, +{ 0x4, 40, 0, 51 }, +{ 0x1d, 39, 1, 74 }, +{ 0x1c, 39, 1, 50 }, +{ 0xd, 40, 1, 73 }, +{ 0xc, 40, 0, 49 }, +{ 0xb, 39, 1, 64 }, +{ 0xa, 39, 0, 40 }, +{ 0x1b, 39, 1, 63 }, +{ 0x1a, 39, 0, 39 }, +{ 0x7, 39, 1, 80 }, +{ 0x6, 39, 1, 56 }, +{ 0x3, 40, 1, 79 }, +{ 0x2, 40, 0, 55 }, +{ 0xf, 39, 1, 78 }, +{ 0xe, 39, 1, 54 }, +{ 0x7, 40, 1, 77 }, +{ 0x6, 40, 0, 53 }, +{ 0x8, 38, 0, 82 }, +{ 0x18, 38, 0, 81 }, +{ 0x1, 38, 1, 86 }, +{ 0x2, 38, 0, 85 }, +{ 0x3, 38, 1, 84 }, +{ 0x4, 38, 0, 83 }, +{ 0x1, 336, 0, 87 }, +{ 0x20, 289, 0, 98 }, +{ 0x220, 289, 0, 94 }, +{ 0x1220, 289, 0, 91 }, +{ 0xa20, 289, 0, 92 }, +{ 0x620, 289, 0, 93 }, +{ 0x120, 289, 0, 95 }, +{ 0xa0, 289, 0, 96 }, +{ 0x60, 289, 0, 97 }, +{ 0x10, 289, 0, 102 }, +{ 0x90, 289, 0, 99 }, +{ 0x50, 289, 0, 100 }, +{ 0x30, 289, 0, 101 }, +{ 0x8, 289, 0, 103 }, +{ 0x4, 289, 0, 104 }, +{ 0x2, 289, 0, 105 }, +{ 0x1, 289, 0, 106 }, +{ 0x1, 411, 0, 108 }, +{ 0x3, 411, 0, 107 }, +{ 0x2, 417, 0, 109 }, +{ 0x1, 417, 0, 110 }, +{ 0x2, 413, 0, 111 }, +{ 0x1, 413, 0, 112 }, +{ 0x2, 415, 0, 113 }, +{ 0x1, 415, 0, 114 }, +{ 0x2, 419, 0, 115 }, +{ 0x1, 419, 0, 116 }, +{ 0x1, 268, 0, 143 }, +{ 0x5, 268, 0, 141 }, +{ 0x3, 268, 0, 142 }, +{ 0x140, 277, 0, 119 }, +{ 0x540, 277, 0, 117 }, +{ 0x340, 277, 0, 118 }, +{ 0xc0, 277, 0, 131 }, +{ 0x2c0, 277, 0, 129 }, +{ 0x1c0, 277, 0, 130 }, +{ 0x20, 277, 0, 146 }, +{ 0xa0, 277, 0, 144 }, +{ 0x60, 277, 0, 145 }, +{ 0x10, 277, 0, 158 }, +{ 0x50, 277, 0, 156 }, +{ 0x30, 277, 0, 157 }, +{ 0x8, 277, 0, 170 }, +{ 0x28, 277, 0, 168 }, +{ 0x18, 277, 0, 169 }, +{ 0x4, 277, 0, 180 }, +{ 0x2, 277, 0, 181 }, +{ 0x1, 277, 0, 182 }, +{ 0x140, 271, 0, 122 }, +{ 0x540, 271, 0, 120 }, +{ 0x340, 271, 0, 121 }, +{ 0xc0, 271, 0, 134 }, +{ 0x2c0, 271, 0, 132 }, +{ 0x1c0, 271, 0, 133 }, +{ 0x20, 271, 0, 149 }, +{ 0xa0, 271, 0, 147 }, +{ 0x60, 271, 0, 148 }, +{ 0x10, 271, 0, 161 }, +{ 0x50, 271, 0, 159 }, +{ 0x30, 271, 0, 160 }, +{ 0x8, 271, 0, 173 }, +{ 0x28, 271, 0, 171 }, +{ 0x18, 271, 0, 172 }, +{ 0x4, 271, 0, 183 }, +{ 0x2, 271, 0, 184 }, +{ 0x1, 271, 0, 185 }, +{ 0x140, 274, 0, 125 }, +{ 0x540, 274, 0, 123 }, +{ 0x340, 274, 0, 124 }, +{ 0xc0, 274, 0, 137 }, +{ 0x2c0, 274, 0, 135 }, +{ 0x1c0, 274, 0, 136 }, +{ 0x20, 274, 0, 152 }, +{ 0xa0, 274, 0, 150 }, +{ 0x60, 274, 0, 151 }, +{ 0x10, 274, 0, 164 }, +{ 0x50, 274, 0, 162 }, +{ 0x30, 274, 0, 163 }, +{ 0x8, 274, 0, 176 }, +{ 0x28, 274, 0, 174 }, +{ 0x18, 274, 0, 175 }, +{ 0x4, 274, 0, 186 }, +{ 0x2, 274, 0, 187 }, +{ 0x1, 274, 0, 188 }, +{ 0x140, 286, 0, 128 }, +{ 0x540, 286, 0, 126 }, +{ 0x340, 286, 0, 127 }, +{ 0xc0, 286, 0, 140 }, +{ 0x2c0, 286, 0, 138 }, +{ 0x1c0, 286, 0, 139 }, +{ 0x20, 286, 0, 155 }, +{ 0xa0, 286, 0, 153 }, +{ 0x60, 286, 0, 154 }, +{ 0x10, 286, 0, 167 }, +{ 0x50, 286, 0, 165 }, +{ 0x30, 286, 0, 166 }, +{ 0x8, 286, 0, 179 }, +{ 0x28, 286, 0, 177 }, +{ 0x18, 286, 0, 178 }, +{ 0x4, 286, 0, 189 }, +{ 0x2, 286, 0, 190 }, +{ 0x1, 286, 0, 191 }, +{ 0x8, 390, 0, 192 }, +{ 0x4, 390, 0, 193 }, +{ 0x2, 390, 0, 194 }, +{ 0x1, 390, 0, 195 }, +{ 0x20, 288, 0, 203 }, +{ 0x220, 288, 0, 199 }, +{ 0x1220, 288, 0, 196 }, +{ 0xa20, 288, 0, 197 }, +{ 0x620, 288, 0, 198 }, +{ 0x120, 288, 0, 200 }, +{ 0xa0, 288, 0, 201 }, +{ 0x60, 288, 0, 202 }, +{ 0x10, 288, 0, 207 }, +{ 0x90, 288, 0, 204 }, +{ 0x50, 288, 0, 205 }, +{ 0x30, 288, 0, 206 }, +{ 0x8, 288, 0, 208 }, +{ 0x4, 288, 0, 209 }, +{ 0x2, 288, 0, 210 }, +{ 0x1, 288, 0, 211 }, +{ 0x20, 287, 0, 219 }, +{ 0x220, 287, 0, 215 }, +{ 0x1220, 287, 0, 212 }, +{ 0xa20, 287, 0, 213 }, +{ 0x620, 287, 0, 214 }, +{ 0x120, 287, 0, 216 }, +{ 0xa0, 287, 0, 217 }, +{ 0x60, 287, 0, 218 }, +{ 0x10, 287, 0, 223 }, +{ 0x90, 287, 0, 220 }, +{ 0x50, 287, 0, 221 }, +{ 0x30, 287, 0, 222 }, +{ 0x8, 287, 0, 224 }, +{ 0x4, 287, 0, 225 }, +{ 0x2, 287, 0, 226 }, +{ 0x1, 287, 0, 227 }, +{ 0x140, 279, 0, 230 }, +{ 0x540, 279, 0, 228 }, +{ 0x340, 279, 0, 229 }, +{ 0xc0, 279, 0, 239 }, +{ 0x2c0, 279, 0, 237 }, +{ 0x1c0, 279, 0, 238 }, +{ 0x20, 279, 0, 248 }, +{ 0xa0, 279, 0, 246 }, +{ 0x60, 279, 0, 247 }, +{ 0x10, 279, 0, 257 }, +{ 0x50, 279, 0, 255 }, +{ 0x30, 279, 0, 256 }, +{ 0x8, 279, 0, 266 }, +{ 0x28, 279, 0, 264 }, +{ 0x18, 279, 0, 265 }, +{ 0x4, 279, 0, 273 }, +{ 0x2, 279, 0, 274 }, +{ 0x1, 279, 0, 275 }, +{ 0x140, 281, 0, 233 }, +{ 0x540, 281, 0, 231 }, +{ 0x340, 281, 0, 232 }, +{ 0xc0, 281, 0, 242 }, +{ 0x2c0, 281, 0, 240 }, +{ 0x1c0, 281, 0, 241 }, +{ 0x20, 281, 0, 251 }, +{ 0xa0, 281, 0, 249 }, +{ 0x60, 281, 0, 250 }, +{ 0x10, 281, 0, 260 }, +{ 0x50, 281, 0, 258 }, +{ 0x30, 281, 0, 259 }, +{ 0x8, 281, 0, 269 }, +{ 0x28, 281, 0, 267 }, +{ 0x18, 281, 0, 268 }, +{ 0x4, 281, 0, 276 }, +{ 0x2, 281, 0, 277 }, +{ 0x1, 281, 0, 278 }, +{ 0x140, 283, 0, 236 }, +{ 0x540, 283, 0, 234 }, +{ 0x340, 283, 0, 235 }, +{ 0xc0, 283, 0, 245 }, +{ 0x2c0, 283, 0, 243 }, +{ 0x1c0, 283, 0, 244 }, +{ 0x20, 283, 0, 254 }, +{ 0xa0, 283, 0, 252 }, +{ 0x60, 283, 0, 253 }, +{ 0x10, 283, 0, 263 }, +{ 0x50, 283, 0, 261 }, +{ 0x30, 283, 0, 262 }, +{ 0x8, 283, 0, 272 }, +{ 0x28, 283, 0, 270 }, +{ 0x18, 283, 0, 271 }, +{ 0x4, 283, 0, 279 }, +{ 0x2, 283, 0, 280 }, +{ 0x1, 283, 0, 281 }, +{ 0x140, 278, 0, 284 }, +{ 0x540, 278, 0, 282 }, +{ 0x340, 278, 0, 283 }, +{ 0xc0, 278, 0, 293 }, +{ 0x2c0, 278, 0, 291 }, +{ 0x1c0, 278, 0, 292 }, +{ 0x20, 278, 0, 302 }, +{ 0xa0, 278, 0, 300 }, +{ 0x60, 278, 0, 301 }, +{ 0x10, 278, 0, 311 }, +{ 0x50, 278, 0, 309 }, +{ 0x30, 278, 0, 310 }, +{ 0x8, 278, 0, 320 }, +{ 0x28, 278, 0, 318 }, +{ 0x18, 278, 0, 319 }, +{ 0x4, 278, 0, 327 }, +{ 0x2, 278, 0, 328 }, +{ 0x1, 278, 0, 329 }, +{ 0x140, 280, 0, 287 }, +{ 0x540, 280, 0, 285 }, +{ 0x340, 280, 0, 286 }, +{ 0xc0, 280, 0, 296 }, +{ 0x2c0, 280, 0, 294 }, +{ 0x1c0, 280, 0, 295 }, +{ 0x20, 280, 0, 305 }, +{ 0xa0, 280, 0, 303 }, +{ 0x60, 280, 0, 304 }, +{ 0x10, 280, 0, 314 }, +{ 0x50, 280, 0, 312 }, +{ 0x30, 280, 0, 313 }, +{ 0x8, 280, 0, 323 }, +{ 0x28, 280, 0, 321 }, +{ 0x18, 280, 0, 322 }, +{ 0x4, 280, 0, 330 }, +{ 0x2, 280, 0, 331 }, +{ 0x1, 280, 0, 332 }, +{ 0x140, 282, 0, 290 }, +{ 0x540, 282, 0, 288 }, +{ 0x340, 282, 0, 289 }, +{ 0xc0, 282, 0, 299 }, +{ 0x2c0, 282, 0, 297 }, +{ 0x1c0, 282, 0, 298 }, +{ 0x20, 282, 0, 308 }, +{ 0xa0, 282, 0, 306 }, +{ 0x60, 282, 0, 307 }, +{ 0x10, 282, 0, 317 }, +{ 0x50, 282, 0, 315 }, +{ 0x30, 282, 0, 316 }, +{ 0x8, 282, 0, 326 }, +{ 0x28, 282, 0, 324 }, +{ 0x18, 282, 0, 325 }, +{ 0x4, 282, 0, 333 }, +{ 0x2, 282, 0, 334 }, +{ 0x1, 282, 0, 335 }, +{ 0x1, 410, 0, 337 }, +{ 0x3, 410, 0, 336 }, +{ 0x2, 416, 0, 338 }, +{ 0x1, 416, 0, 339 }, +{ 0x2, 412, 0, 340 }, +{ 0x1, 412, 0, 341 }, +{ 0x2, 414, 0, 342 }, +{ 0x1, 414, 0, 343 }, +{ 0x2, 418, 0, 344 }, +{ 0x1, 418, 0, 345 }, +{ 0x1, 267, 0, 372 }, +{ 0x5, 267, 0, 370 }, +{ 0x3, 267, 0, 371 }, +{ 0x140, 276, 0, 348 }, +{ 0x540, 276, 0, 346 }, +{ 0x340, 276, 0, 347 }, +{ 0xc0, 276, 0, 360 }, +{ 0x2c0, 276, 0, 358 }, +{ 0x1c0, 276, 0, 359 }, +{ 0x20, 276, 0, 375 }, +{ 0xa0, 276, 0, 373 }, +{ 0x60, 276, 0, 374 }, +{ 0x10, 276, 0, 387 }, +{ 0x50, 276, 0, 385 }, +{ 0x30, 276, 0, 386 }, +{ 0x8, 276, 0, 399 }, +{ 0x28, 276, 0, 397 }, +{ 0x18, 276, 0, 398 }, +{ 0x4, 276, 0, 409 }, +{ 0x2, 276, 0, 410 }, +{ 0x1, 276, 0, 411 }, +{ 0x140, 270, 0, 351 }, +{ 0x540, 270, 0, 349 }, +{ 0x340, 270, 0, 350 }, +{ 0xc0, 270, 0, 363 }, +{ 0x2c0, 270, 0, 361 }, +{ 0x1c0, 270, 0, 362 }, +{ 0x20, 270, 0, 378 }, +{ 0xa0, 270, 0, 376 }, +{ 0x60, 270, 0, 377 }, +{ 0x10, 270, 0, 390 }, +{ 0x50, 270, 0, 388 }, +{ 0x30, 270, 0, 389 }, +{ 0x8, 270, 0, 402 }, +{ 0x28, 270, 0, 400 }, +{ 0x18, 270, 0, 401 }, +{ 0x4, 270, 0, 412 }, +{ 0x2, 270, 0, 413 }, +{ 0x1, 270, 0, 414 }, +{ 0x140, 273, 0, 354 }, +{ 0x540, 273, 0, 352 }, +{ 0x340, 273, 0, 353 }, +{ 0xc0, 273, 0, 366 }, +{ 0x2c0, 273, 0, 364 }, +{ 0x1c0, 273, 0, 365 }, +{ 0x20, 273, 0, 381 }, +{ 0xa0, 273, 0, 379 }, +{ 0x60, 273, 0, 380 }, +{ 0x10, 273, 0, 393 }, +{ 0x50, 273, 0, 391 }, +{ 0x30, 273, 0, 392 }, +{ 0x8, 273, 0, 405 }, +{ 0x28, 273, 0, 403 }, +{ 0x18, 273, 0, 404 }, +{ 0x4, 273, 0, 415 }, +{ 0x2, 273, 0, 416 }, +{ 0x1, 273, 0, 417 }, +{ 0x140, 285, 0, 357 }, +{ 0x540, 285, 0, 355 }, +{ 0x340, 285, 0, 356 }, +{ 0xc0, 285, 0, 369 }, +{ 0x2c0, 285, 0, 367 }, +{ 0x1c0, 285, 0, 368 }, +{ 0x20, 285, 0, 384 }, +{ 0xa0, 285, 0, 382 }, +{ 0x60, 285, 0, 383 }, +{ 0x10, 285, 0, 396 }, +{ 0x50, 285, 0, 394 }, +{ 0x30, 285, 0, 395 }, +{ 0x8, 285, 0, 408 }, +{ 0x28, 285, 0, 406 }, +{ 0x18, 285, 0, 407 }, +{ 0x4, 285, 0, 418 }, +{ 0x2, 285, 0, 419 }, +{ 0x1, 285, 0, 420 }, +{ 0x1, 266, 0, 447 }, +{ 0x5, 266, 0, 445 }, +{ 0x3, 266, 0, 446 }, +{ 0x140, 275, 0, 423 }, +{ 0x540, 275, 0, 421 }, +{ 0x340, 275, 0, 422 }, +{ 0xc0, 275, 0, 435 }, +{ 0x2c0, 275, 0, 433 }, +{ 0x1c0, 275, 0, 434 }, +{ 0x20, 275, 0, 450 }, +{ 0xa0, 275, 0, 448 }, +{ 0x60, 275, 0, 449 }, +{ 0x10, 275, 0, 462 }, +{ 0x50, 275, 0, 460 }, +{ 0x30, 275, 0, 461 }, +{ 0x8, 275, 0, 474 }, +{ 0x28, 275, 0, 472 }, +{ 0x18, 275, 0, 473 }, +{ 0x4, 275, 0, 484 }, +{ 0x2, 275, 0, 485 }, +{ 0x1, 275, 0, 486 }, +{ 0x140, 269, 0, 426 }, +{ 0x540, 269, 0, 424 }, +{ 0x340, 269, 0, 425 }, +{ 0xc0, 269, 0, 438 }, +{ 0x2c0, 269, 0, 436 }, +{ 0x1c0, 269, 0, 437 }, +{ 0x20, 269, 0, 453 }, +{ 0xa0, 269, 0, 451 }, +{ 0x60, 269, 0, 452 }, +{ 0x10, 269, 0, 465 }, +{ 0x50, 269, 0, 463 }, +{ 0x30, 269, 0, 464 }, +{ 0x8, 269, 0, 477 }, +{ 0x28, 269, 0, 475 }, +{ 0x18, 269, 0, 476 }, +{ 0x4, 269, 0, 487 }, +{ 0x2, 269, 0, 488 }, +{ 0x1, 269, 0, 489 }, +{ 0x140, 272, 0, 429 }, +{ 0x540, 272, 0, 427 }, +{ 0x340, 272, 0, 428 }, +{ 0xc0, 272, 0, 441 }, +{ 0x2c0, 272, 0, 439 }, +{ 0x1c0, 272, 0, 440 }, +{ 0x20, 272, 0, 456 }, +{ 0xa0, 272, 0, 454 }, +{ 0x60, 272, 0, 455 }, +{ 0x10, 272, 0, 468 }, +{ 0x50, 272, 0, 466 }, +{ 0x30, 272, 0, 467 }, +{ 0x8, 272, 0, 480 }, +{ 0x28, 272, 0, 478 }, +{ 0x18, 272, 0, 479 }, +{ 0x4, 272, 0, 490 }, +{ 0x2, 272, 0, 491 }, +{ 0x1, 272, 0, 492 }, +{ 0x140, 284, 0, 432 }, +{ 0x540, 284, 0, 430 }, +{ 0x340, 284, 0, 431 }, +{ 0xc0, 284, 0, 444 }, +{ 0x2c0, 284, 0, 442 }, +{ 0x1c0, 284, 0, 443 }, +{ 0x20, 284, 0, 459 }, +{ 0xa0, 284, 0, 457 }, +{ 0x60, 284, 0, 458 }, +{ 0x10, 284, 0, 471 }, +{ 0x50, 284, 0, 469 }, +{ 0x30, 284, 0, 470 }, +{ 0x8, 284, 0, 483 }, +{ 0x28, 284, 0, 481 }, +{ 0x18, 284, 0, 482 }, +{ 0x4, 284, 0, 493 }, +{ 0x2, 284, 0, 494 }, +{ 0x1, 284, 0, 495 }, +{ 0x8, 409, 0, 497 }, +{ 0x18, 409, 0, 496 }, +{ 0x4, 409, 0, 499 }, +{ 0xc, 409, 0, 498 }, +{ 0x2, 409, 0, 506 }, +{ 0x1, 409, 0, 507 }, +{ 0x4, 407, 0, 501 }, +{ 0xc, 407, 0, 500 }, +{ 0x2, 407, 0, 508 }, +{ 0x1, 407, 0, 509 }, +{ 0x4, 405, 0, 503 }, +{ 0xc, 405, 0, 502 }, +{ 0x2, 405, 0, 510 }, +{ 0x1, 405, 0, 511 }, +{ 0x4, 401, 0, 505 }, +{ 0xc, 401, 0, 504 }, +{ 0x2, 401, 0, 512 }, +{ 0x1, 401, 0, 513 }, +{ 0xa00, 265, 0, 528 }, +{ 0x2a00, 265, 0, 526 }, +{ 0x1a00, 265, 0, 527 }, +{ 0x600, 265, 0, 540 }, +{ 0x2600, 265, 0, 516 }, +{ 0xa600, 265, 0, 514 }, +{ 0x6600, 265, 0, 515 }, +{ 0x1600, 265, 0, 538 }, +{ 0xe00, 265, 0, 539 }, +{ 0x100, 265, 0, 552 }, +{ 0x500, 265, 0, 550 }, +{ 0x300, 265, 0, 551 }, +{ 0x80, 265, 0, 555 }, +{ 0x280, 265, 0, 553 }, +{ 0x180, 265, 0, 554 }, +{ 0x40, 265, 0, 567 }, +{ 0x140, 265, 0, 565 }, +{ 0xc0, 265, 0, 566 }, +{ 0x20, 265, 0, 579 }, +{ 0xa0, 265, 0, 577 }, +{ 0x60, 265, 0, 578 }, +{ 0x10, 265, 0, 591 }, +{ 0x50, 265, 0, 589 }, +{ 0x30, 265, 0, 590 }, +{ 0x8, 265, 0, 603 }, +{ 0x28, 265, 0, 601 }, +{ 0x18, 265, 0, 602 }, +{ 0x4, 265, 0, 613 }, +{ 0x2, 265, 0, 614 }, +{ 0x1, 265, 0, 615 }, +{ 0x500, 261, 0, 531 }, +{ 0x1500, 261, 0, 529 }, +{ 0xd00, 261, 0, 530 }, +{ 0x300, 261, 0, 543 }, +{ 0x1300, 261, 0, 519 }, +{ 0x5300, 261, 0, 517 }, +{ 0x3300, 261, 0, 518 }, +{ 0xb00, 261, 0, 541 }, +{ 0x700, 261, 0, 542 }, +{ 0x80, 261, 0, 558 }, +{ 0x280, 261, 0, 556 }, +{ 0x180, 261, 0, 557 }, +{ 0x40, 261, 0, 570 }, +{ 0x140, 261, 0, 568 }, +{ 0xc0, 261, 0, 569 }, +{ 0x20, 261, 0, 582 }, +{ 0xa0, 261, 0, 580 }, +{ 0x60, 261, 0, 581 }, +{ 0x10, 261, 0, 594 }, +{ 0x50, 261, 0, 592 }, +{ 0x30, 261, 0, 593 }, +{ 0x8, 261, 0, 606 }, +{ 0x28, 261, 0, 604 }, +{ 0x18, 261, 0, 605 }, +{ 0x4, 261, 0, 616 }, +{ 0x2, 261, 0, 617 }, +{ 0x1, 261, 0, 618 }, +{ 0x500, 258, 0, 534 }, +{ 0x1500, 258, 0, 532 }, +{ 0xd00, 258, 0, 533 }, +{ 0x300, 258, 0, 546 }, +{ 0x1300, 258, 0, 522 }, +{ 0x5300, 258, 0, 520 }, +{ 0x3300, 258, 0, 521 }, +{ 0xb00, 258, 0, 544 }, +{ 0x700, 258, 0, 545 }, +{ 0x80, 258, 0, 561 }, +{ 0x280, 258, 0, 559 }, +{ 0x180, 258, 0, 560 }, +{ 0x40, 258, 0, 573 }, +{ 0x140, 258, 0, 571 }, +{ 0xc0, 258, 0, 572 }, +{ 0x20, 258, 0, 585 }, +{ 0xa0, 258, 0, 583 }, +{ 0x60, 258, 0, 584 }, +{ 0x10, 258, 0, 597 }, +{ 0x50, 258, 0, 595 }, +{ 0x30, 258, 0, 596 }, +{ 0x8, 258, 0, 609 }, +{ 0x28, 258, 0, 607 }, +{ 0x18, 258, 0, 608 }, +{ 0x4, 258, 0, 619 }, +{ 0x2, 258, 0, 620 }, +{ 0x1, 258, 0, 621 }, +{ 0x500, 253, 0, 537 }, +{ 0x1500, 253, 0, 535 }, +{ 0xd00, 253, 0, 536 }, +{ 0x300, 253, 0, 549 }, +{ 0x1300, 253, 0, 525 }, +{ 0x5300, 253, 0, 523 }, +{ 0x3300, 253, 0, 524 }, +{ 0xb00, 253, 0, 547 }, +{ 0x700, 253, 0, 548 }, +{ 0x80, 253, 0, 564 }, +{ 0x280, 253, 0, 562 }, +{ 0x180, 253, 0, 563 }, +{ 0x40, 253, 0, 576 }, +{ 0x140, 253, 0, 574 }, +{ 0xc0, 253, 0, 575 }, +{ 0x20, 253, 0, 588 }, +{ 0xa0, 253, 0, 586 }, +{ 0x60, 253, 0, 587 }, +{ 0x10, 253, 0, 600 }, +{ 0x50, 253, 0, 598 }, +{ 0x30, 253, 0, 599 }, +{ 0x8, 253, 0, 612 }, +{ 0x28, 253, 0, 610 }, +{ 0x18, 253, 0, 611 }, +{ 0x4, 253, 0, 622 }, +{ 0x2, 253, 0, 623 }, +{ 0x1, 253, 0, 624 }, +{ 0x8, 238, 0, 625 }, +{ 0x4, 238, 0, 626 }, +{ 0x2, 238, 0, 627 }, +{ 0x1, 238, 0, 628 }, +{ 0x2, 176, 0, 631 }, +{ 0xa, 176, 0, 629 }, +{ 0x6, 176, 0, 630 }, +{ 0x1, 176, 0, 637 }, +{ 0x5, 176, 0, 635 }, +{ 0x3, 176, 0, 636 }, +{ 0x2, 175, 0, 634 }, +{ 0xa, 175, 0, 632 }, +{ 0x6, 175, 0, 633 }, +{ 0x1, 175, 0, 640 }, +{ 0x5, 175, 0, 638 }, +{ 0x3, 175, 0, 639 }, +{ 0x4, 451, 0, 641 }, +{ 0x2, 451, 0, 642 }, +{ 0x1, 451, 0, 643 }, +{ 0x4, 450, 0, 644 }, +{ 0x2, 450, 0, 645 }, +{ 0x1, 450, 0, 646 }, +{ 0x4, 449, 0, 647 }, +{ 0x2, 449, 0, 648 }, +{ 0x1, 449, 0, 649 }, +{ 0x4, 448, 0, 650 }, +{ 0x2, 448, 0, 651 }, +{ 0x1, 448, 0, 652 }, +{ 0x2, 123, 1, 658 }, +{ 0x2, 124, 0, 657 }, +{ 0xa, 123, 1, 654 }, +{ 0xa, 124, 0, 653 }, +{ 0x6, 123, 1, 656 }, +{ 0x6, 124, 0, 655 }, +{ 0x1, 123, 1, 688 }, +{ 0x1, 124, 0, 687 }, +{ 0x5, 123, 1, 684 }, +{ 0x5, 124, 0, 683 }, +{ 0x3, 123, 1, 686 }, +{ 0x3, 124, 0, 685 }, +{ 0x2, 131, 1, 664 }, +{ 0x2, 132, 0, 663 }, +{ 0xa, 131, 1, 660 }, +{ 0xa, 132, 0, 659 }, +{ 0x6, 131, 1, 662 }, +{ 0x6, 132, 0, 661 }, +{ 0x1, 131, 1, 694 }, +{ 0x1, 132, 0, 693 }, +{ 0x5, 131, 1, 690 }, +{ 0x5, 132, 0, 689 }, +{ 0x3, 131, 1, 692 }, +{ 0x3, 132, 0, 691 }, +{ 0x2, 129, 1, 670 }, +{ 0x2, 130, 0, 669 }, +{ 0xa, 129, 1, 666 }, +{ 0xa, 130, 0, 665 }, +{ 0x6, 129, 1, 668 }, +{ 0x6, 130, 0, 667 }, +{ 0x1, 129, 1, 700 }, +{ 0x1, 130, 0, 699 }, +{ 0x5, 129, 1, 696 }, +{ 0x5, 130, 0, 695 }, +{ 0x3, 129, 1, 698 }, +{ 0x3, 130, 0, 697 }, +{ 0x2, 127, 1, 676 }, +{ 0x2, 128, 0, 675 }, +{ 0xa, 127, 1, 672 }, +{ 0xa, 128, 0, 671 }, +{ 0x6, 127, 1, 674 }, +{ 0x6, 128, 0, 673 }, +{ 0x1, 127, 1, 706 }, +{ 0x1, 128, 0, 705 }, +{ 0x5, 127, 1, 702 }, +{ 0x5, 128, 0, 701 }, +{ 0x3, 127, 1, 704 }, +{ 0x3, 128, 0, 703 }, +{ 0x2, 125, 1, 682 }, +{ 0x2, 126, 0, 681 }, +{ 0xa, 125, 1, 678 }, +{ 0xa, 126, 0, 677 }, +{ 0x6, 125, 1, 680 }, +{ 0x6, 126, 0, 679 }, +{ 0x1, 125, 1, 712 }, +{ 0x1, 126, 0, 711 }, +{ 0x5, 125, 1, 708 }, +{ 0x5, 126, 0, 707 }, +{ 0x3, 125, 1, 710 }, +{ 0x3, 126, 0, 709 }, +{ 0x4, 402, 1, 718 }, +{ 0x4, 403, 0, 717 }, +{ 0xc, 402, 1, 716 }, +{ 0xc, 403, 0, 715 }, +{ 0x2, 402, 1, 728 }, +{ 0x2, 403, 0, 727 }, +{ 0x1, 402, 1, 730 }, +{ 0x1, 403, 0, 729 }, +{ 0x8, 408, 0, 714 }, +{ 0x18, 408, 0, 713 }, +{ 0x4, 408, 0, 720 }, +{ 0xc, 408, 0, 719 }, +{ 0x2, 408, 0, 731 }, +{ 0x1, 408, 0, 732 }, +{ 0x4, 406, 0, 722 }, +{ 0xc, 406, 0, 721 }, +{ 0x2, 406, 0, 733 }, +{ 0x1, 406, 0, 734 }, +{ 0x4, 404, 0, 724 }, +{ 0xc, 404, 0, 723 }, +{ 0x2, 404, 0, 735 }, +{ 0x1, 404, 0, 736 }, +{ 0x4, 400, 0, 726 }, +{ 0xc, 400, 0, 725 }, +{ 0x2, 400, 0, 737 }, +{ 0x1, 400, 0, 738 }, +{ 0xa00, 264, 0, 753 }, +{ 0x2a00, 264, 0, 751 }, +{ 0x1a00, 264, 0, 752 }, +{ 0x600, 264, 0, 765 }, +{ 0x2600, 264, 0, 741 }, +{ 0xa600, 264, 0, 739 }, +{ 0x6600, 264, 0, 740 }, +{ 0x1600, 264, 0, 763 }, +{ 0xe00, 264, 0, 764 }, +{ 0x100, 264, 0, 777 }, +{ 0x500, 264, 0, 775 }, +{ 0x300, 264, 0, 776 }, +{ 0x80, 264, 0, 780 }, +{ 0x280, 264, 0, 778 }, +{ 0x180, 264, 0, 779 }, +{ 0x40, 264, 0, 792 }, +{ 0x140, 264, 0, 790 }, +{ 0xc0, 264, 0, 791 }, +{ 0x20, 264, 0, 804 }, +{ 0xa0, 264, 0, 802 }, +{ 0x60, 264, 0, 803 }, +{ 0x10, 264, 0, 816 }, +{ 0x50, 264, 0, 814 }, +{ 0x30, 264, 0, 815 }, +{ 0x8, 264, 0, 828 }, +{ 0x28, 264, 0, 826 }, +{ 0x18, 264, 0, 827 }, +{ 0x4, 264, 0, 838 }, +{ 0x2, 264, 0, 839 }, +{ 0x1, 264, 0, 840 }, +{ 0x500, 260, 0, 756 }, +{ 0x1500, 260, 0, 754 }, +{ 0xd00, 260, 0, 755 }, +{ 0x300, 260, 0, 768 }, +{ 0x1300, 260, 0, 744 }, +{ 0x5300, 260, 0, 742 }, +{ 0x3300, 260, 0, 743 }, +{ 0xb00, 260, 0, 766 }, +{ 0x700, 260, 0, 767 }, +{ 0x80, 260, 0, 783 }, +{ 0x280, 260, 0, 781 }, +{ 0x180, 260, 0, 782 }, +{ 0x40, 260, 0, 795 }, +{ 0x140, 260, 0, 793 }, +{ 0xc0, 260, 0, 794 }, +{ 0x20, 260, 0, 807 }, +{ 0xa0, 260, 0, 805 }, +{ 0x60, 260, 0, 806 }, +{ 0x10, 260, 0, 819 }, +{ 0x50, 260, 0, 817 }, +{ 0x30, 260, 0, 818 }, +{ 0x8, 260, 0, 831 }, +{ 0x28, 260, 0, 829 }, +{ 0x18, 260, 0, 830 }, +{ 0x4, 260, 0, 841 }, +{ 0x2, 260, 0, 842 }, +{ 0x1, 260, 0, 843 }, +{ 0x500, 257, 0, 759 }, +{ 0x1500, 257, 0, 757 }, +{ 0xd00, 257, 0, 758 }, +{ 0x300, 257, 0, 771 }, +{ 0x1300, 257, 0, 747 }, +{ 0x5300, 257, 0, 745 }, +{ 0x3300, 257, 0, 746 }, +{ 0xb00, 257, 0, 769 }, +{ 0x700, 257, 0, 770 }, +{ 0x80, 257, 0, 786 }, +{ 0x280, 257, 0, 784 }, +{ 0x180, 257, 0, 785 }, +{ 0x40, 257, 0, 798 }, +{ 0x140, 257, 0, 796 }, +{ 0xc0, 257, 0, 797 }, +{ 0x20, 257, 0, 810 }, +{ 0xa0, 257, 0, 808 }, +{ 0x60, 257, 0, 809 }, +{ 0x10, 257, 0, 822 }, +{ 0x50, 257, 0, 820 }, +{ 0x30, 257, 0, 821 }, +{ 0x8, 257, 0, 834 }, +{ 0x28, 257, 0, 832 }, +{ 0x18, 257, 0, 833 }, +{ 0x4, 257, 0, 844 }, +{ 0x2, 257, 0, 845 }, +{ 0x1, 257, 0, 846 }, +{ 0x500, 252, 0, 762 }, +{ 0x1500, 252, 0, 760 }, +{ 0xd00, 252, 0, 761 }, +{ 0x300, 252, 0, 774 }, +{ 0x1300, 252, 0, 750 }, +{ 0x5300, 252, 0, 748 }, +{ 0x3300, 252, 0, 749 }, +{ 0xb00, 252, 0, 772 }, +{ 0x700, 252, 0, 773 }, +{ 0x80, 252, 0, 789 }, +{ 0x280, 252, 0, 787 }, +{ 0x180, 252, 0, 788 }, +{ 0x40, 252, 0, 801 }, +{ 0x140, 252, 0, 799 }, +{ 0xc0, 252, 0, 800 }, +{ 0x20, 252, 0, 813 }, +{ 0xa0, 252, 0, 811 }, +{ 0x60, 252, 0, 812 }, +{ 0x10, 252, 0, 825 }, +{ 0x50, 252, 0, 823 }, +{ 0x30, 252, 0, 824 }, +{ 0x8, 252, 0, 837 }, +{ 0x28, 252, 0, 835 }, +{ 0x18, 252, 0, 836 }, +{ 0x4, 252, 0, 847 }, +{ 0x2, 252, 0, 848 }, +{ 0x1, 252, 0, 849 }, +{ 0x8, 254, 1, 895 }, +{ 0x8, 255, 0, 894 }, +{ 0x28, 254, 1, 891 }, +{ 0x28, 255, 0, 890 }, +{ 0x18, 254, 1, 893 }, +{ 0x18, 255, 0, 892 }, +{ 0x4, 254, 1, 957 }, +{ 0x4, 255, 0, 956 }, +{ 0x2, 254, 1, 959 }, +{ 0x2, 255, 0, 958 }, +{ 0x1, 254, 1, 961 }, +{ 0x1, 255, 0, 960 }, +{ 0xa00, 262, 0, 865 }, +{ 0x2a00, 262, 0, 863 }, +{ 0x1a00, 262, 0, 864 }, +{ 0x600, 262, 0, 877 }, +{ 0x2600, 262, 0, 853 }, +{ 0xa600, 262, 0, 851 }, +{ 0x6600, 262, 0, 852 }, +{ 0x1600, 262, 0, 875 }, +{ 0xe00, 262, 0, 876 }, +{ 0x100, 262, 0, 889 }, +{ 0x500, 262, 0, 887 }, +{ 0x300, 262, 0, 888 }, +{ 0x80, 262, 0, 898 }, +{ 0x280, 262, 0, 896 }, +{ 0x180, 262, 0, 897 }, +{ 0x40, 262, 0, 910 }, +{ 0x140, 262, 0, 908 }, +{ 0xc0, 262, 0, 909 }, +{ 0x20, 262, 0, 922 }, +{ 0xa0, 262, 0, 920 }, +{ 0x60, 262, 0, 921 }, +{ 0x10, 262, 0, 934 }, +{ 0x50, 262, 0, 932 }, +{ 0x30, 262, 0, 933 }, +{ 0x8, 262, 0, 946 }, +{ 0x28, 262, 0, 944 }, +{ 0x18, 262, 0, 945 }, +{ 0x4, 262, 0, 962 }, +{ 0x2, 262, 0, 963 }, +{ 0x1, 262, 1, 964 }, +{ 0x1, 263, 0, 850 }, +{ 0x500, 259, 0, 868 }, +{ 0x1500, 259, 0, 866 }, +{ 0xd00, 259, 0, 867 }, +{ 0x300, 259, 0, 880 }, +{ 0x1300, 259, 0, 856 }, +{ 0x5300, 259, 0, 854 }, +{ 0x3300, 259, 0, 855 }, +{ 0xb00, 259, 0, 878 }, +{ 0x700, 259, 0, 879 }, +{ 0x80, 259, 0, 901 }, +{ 0x280, 259, 0, 899 }, +{ 0x180, 259, 0, 900 }, +{ 0x40, 259, 0, 913 }, +{ 0x140, 259, 0, 911 }, +{ 0xc0, 259, 0, 912 }, +{ 0x20, 259, 0, 925 }, +{ 0xa0, 259, 0, 923 }, +{ 0x60, 259, 0, 924 }, +{ 0x10, 259, 0, 937 }, +{ 0x50, 259, 0, 935 }, +{ 0x30, 259, 0, 936 }, +{ 0x8, 259, 0, 949 }, +{ 0x28, 259, 0, 947 }, +{ 0x18, 259, 0, 948 }, +{ 0x4, 259, 0, 965 }, +{ 0x2, 259, 0, 966 }, +{ 0x1, 259, 0, 967 }, +{ 0x500, 256, 0, 871 }, +{ 0x1500, 256, 0, 869 }, +{ 0xd00, 256, 0, 870 }, +{ 0x300, 256, 0, 883 }, +{ 0x1300, 256, 0, 859 }, +{ 0x5300, 256, 0, 857 }, +{ 0x3300, 256, 0, 858 }, +{ 0xb00, 256, 0, 881 }, +{ 0x700, 256, 0, 882 }, +{ 0x80, 256, 0, 904 }, +{ 0x280, 256, 0, 902 }, +{ 0x180, 256, 0, 903 }, +{ 0x40, 256, 0, 916 }, +{ 0x140, 256, 0, 914 }, +{ 0xc0, 256, 0, 915 }, +{ 0x20, 256, 0, 928 }, +{ 0xa0, 256, 0, 926 }, +{ 0x60, 256, 0, 927 }, +{ 0x10, 256, 0, 940 }, +{ 0x50, 256, 0, 938 }, +{ 0x30, 256, 0, 939 }, +{ 0x8, 256, 0, 952 }, +{ 0x28, 256, 0, 950 }, +{ 0x18, 256, 0, 951 }, +{ 0x4, 256, 0, 968 }, +{ 0x2, 256, 0, 969 }, +{ 0x1, 256, 0, 970 }, +{ 0x500, 251, 0, 874 }, +{ 0x1500, 251, 0, 872 }, +{ 0xd00, 251, 0, 873 }, +{ 0x300, 251, 0, 886 }, +{ 0x1300, 251, 0, 862 }, +{ 0x5300, 251, 0, 860 }, +{ 0x3300, 251, 0, 861 }, +{ 0xb00, 251, 0, 884 }, +{ 0x700, 251, 0, 885 }, +{ 0x80, 251, 0, 907 }, +{ 0x280, 251, 0, 905 }, +{ 0x180, 251, 0, 906 }, +{ 0x40, 251, 0, 919 }, +{ 0x140, 251, 0, 917 }, +{ 0xc0, 251, 0, 918 }, +{ 0x20, 251, 0, 931 }, +{ 0xa0, 251, 0, 929 }, +{ 0x60, 251, 0, 930 }, +{ 0x10, 251, 0, 943 }, +{ 0x50, 251, 0, 941 }, +{ 0x30, 251, 0, 942 }, +{ 0x8, 251, 0, 955 }, +{ 0x28, 251, 0, 953 }, +{ 0x18, 251, 0, 954 }, +{ 0x4, 251, 0, 971 }, +{ 0x2, 251, 0, 972 }, +{ 0x1, 251, 0, 973 }, +{ 0x2, 150, 0, 975 }, +{ 0x1, 150, 0, 976 }, +{ 0x1, 50, 0, 977 }, +{ 0x3, 49, 0, 978 }, +{ 0x1, 428, 0, 979 }, +{ 0x1, 442, 0, 980 }, +{ 0x2, 386, 0, 983 }, +{ 0x1, 386, 0, 984 }, +{ 0x2, 384, 0, 985 }, +{ 0x1, 384, 0, 986 }, +{ 0x1, 383, 0, 987 }, +{ 0x1, 328, 0, 992 }, +{ 0x1, 327, 0, 993 }, +{ 0x1, 326, 0, 994 }, +{ 0x1, 325, 0, 995 }, +{ 0x1, 250, 0, 996 }, +{ 0x1, 249, 0, 997 }, +{ 0x1, 324, 0, 998 }, +{ 0x1, 323, 0, 999 }, +{ 0x1, 322, 0, 1000 }, +{ 0x1, 321, 0, 1001 }, +{ 0x1, 320, 0, 1002 }, +{ 0x1, 319, 0, 1003 }, +{ 0x1, 318, 0, 1004 }, +{ 0x2, 248, 0, 1005 }, +{ 0x1, 248, 0, 1006 }, +{ 0x2, 366, 0, 1012 }, +{ 0x1, 366, 0, 1013 }, +{ 0x1, 317, 0, 1014 }, +{ 0x1, 316, 0, 1015 }, +{ 0x1, 315, 0, 1016 }, +{ 0x1, 314, 0, 1017 }, +{ 0x1, 8, 1, 1019 }, +{ 0x1, 9, 0, 1018 }, +{ 0x1, 313, 0, 1020 }, +{ 0x1, 312, 0, 1021 }, +{ 0x1, 311, 0, 1022 }, +{ 0x1, 310, 0, 1023 }, +{ 0x1, 388, 0, 1024 }, +{ 0x1, 399, 0, 1025 }, +{ 0x1, 389, 0, 1026 }, +{ 0x1, 423, 0, 1027 }, +{ 0x1, 309, 0, 1031 }, +{ 0x1, 247, 0, 1032 }, +{ 0x1, 177, 0, 1035 }, +{ 0x2, 291, 0, 1039 }, +{ 0x1, 291, 0, 1040 }, +{ 0x1, 236, 0, 1041 }, +{ 0x5, 48, 0, 1043 }, +{ 0x3, 48, 0, 1044 }, +{ 0x5, 47, 0, 1045 }, +{ 0x3, 47, 0, 1046 }, +{ 0x1, 365, 0, 1047 }, +{ 0x1, 373, 0, 1048 }, +{ 0x1, 371, 0, 1049 }, +{ 0x1, 392, 0, 1050 }, +{ 0x1, 372, 0, 1051 }, +{ 0x1, 370, 0, 1052 }, +{ 0x2, 378, 0, 1053 }, +{ 0x1, 378, 0, 1055 }, +{ 0x2, 376, 0, 1054 }, +{ 0x1, 376, 0, 1056 }, +{ 0x2, 396, 0, 1057 }, +{ 0x1, 396, 0, 1060 }, +{ 0x2, 377, 0, 1058 }, +{ 0x1, 377, 0, 1061 }, +{ 0x2, 375, 0, 1059 }, +{ 0x1, 375, 0, 1062 }, +{ 0x1, 338, 0, 1063 }, +{ 0x1, 337, 0, 1064 }, +{ 0x1, 369, 0, 1065 }, +{ 0x1, 360, 0, 1066 }, +{ 0x1, 362, 0, 1067 }, +{ 0x1, 359, 0, 1068 }, +{ 0x1, 361, 0, 1069 }, +{ 0x2, 446, 0, 1070 }, +{ 0x1, 446, 0, 1073 }, +{ 0x2, 445, 0, 1071 }, +{ 0x1, 445, 0, 1074 }, +{ 0x2, 444, 0, 1072 }, +{ 0x1, 444, 0, 1075 }, +{ 0x1, 348, 0, 1076 }, +{ 0x2, 347, 0, 1077 }, +{ 0x1, 347, 0, 1078 }, +{ 0x2, 294, 0, 1079 }, +{ 0x1, 294, 0, 1082 }, +{ 0x2, 293, 0, 1080 }, +{ 0x1, 293, 0, 1083 }, +{ 0x2, 292, 0, 1081 }, +{ 0x1, 292, 0, 1084 }, +{ 0x2, 363, 0, 1085 }, +{ 0x1, 363, 0, 1086 }, +{ 0x2, 364, 0, 1087 }, +{ 0x1, 364, 0, 1088 }, +{ 0xa, 438, 1, 1100 }, +{ 0xa, 439, 1, 1099 }, +{ 0xa, 440, 1, 1098 }, +{ 0xa, 441, 0, 1097 }, +{ 0x1a, 438, 1, 1092 }, +{ 0x1a, 439, 1, 1091 }, +{ 0x32, 440, 1, 1090 }, +{ 0x32, 441, 0, 1089 }, +{ 0x6, 438, 1, 1108 }, +{ 0x6, 439, 1, 1107 }, +{ 0x6, 440, 1, 1106 }, +{ 0x6, 441, 0, 1105 }, +{ 0x1, 438, 1, 1120 }, +{ 0x1, 439, 1, 1119 }, +{ 0x1, 440, 1, 1118 }, +{ 0x1, 441, 0, 1117 }, +{ 0x9, 438, 1, 1104 }, +{ 0x9, 439, 1, 1103 }, +{ 0x9, 440, 1, 1102 }, +{ 0x9, 441, 0, 1101 }, +{ 0x19, 438, 1, 1096 }, +{ 0x19, 439, 1, 1095 }, +{ 0x31, 440, 1, 1094 }, +{ 0x31, 441, 0, 1093 }, +{ 0x5, 438, 1, 1112 }, +{ 0x5, 439, 1, 1111 }, +{ 0x5, 440, 1, 1110 }, +{ 0x5, 441, 0, 1109 }, +{ 0x3, 438, 1, 1116 }, +{ 0x3, 439, 1, 1115 }, +{ 0x3, 440, 1, 1114 }, +{ 0x3, 441, 0, 1113 }, +{ 0xa, 429, 1, 1132 }, +{ 0xa, 430, 1, 1131 }, +{ 0xa, 431, 1, 1130 }, +{ 0xa, 432, 0, 1129 }, +{ 0x1a, 429, 1, 1124 }, +{ 0x1a, 430, 1, 1123 }, +{ 0x32, 431, 1, 1122 }, +{ 0x32, 432, 0, 1121 }, +{ 0x6, 429, 1, 1140 }, +{ 0x6, 430, 1, 1139 }, +{ 0x6, 431, 1, 1138 }, +{ 0x6, 432, 0, 1137 }, +{ 0x1, 429, 1, 1152 }, +{ 0x1, 430, 1, 1151 }, +{ 0x1, 431, 1, 1150 }, +{ 0x1, 432, 0, 1149 }, +{ 0x9, 429, 1, 1136 }, +{ 0x9, 430, 1, 1135 }, +{ 0x9, 431, 1, 1134 }, +{ 0x9, 432, 0, 1133 }, +{ 0x19, 429, 1, 1128 }, +{ 0x19, 430, 1, 1127 }, +{ 0x31, 431, 1, 1126 }, +{ 0x31, 432, 0, 1125 }, +{ 0x5, 429, 1, 1144 }, +{ 0x5, 430, 1, 1143 }, +{ 0x5, 431, 1, 1142 }, +{ 0x5, 432, 0, 1141 }, +{ 0x3, 429, 1, 1148 }, +{ 0x3, 430, 1, 1147 }, +{ 0x3, 431, 1, 1146 }, +{ 0x3, 432, 0, 1145 }, +{ 0xa, 433, 1, 1164 }, +{ 0xa, 434, 1, 1163 }, +{ 0xa, 435, 1, 1162 }, +{ 0xa, 436, 0, 1161 }, +{ 0x1a, 433, 1, 1156 }, +{ 0x1a, 434, 1, 1155 }, +{ 0x32, 435, 1, 1154 }, +{ 0x32, 436, 0, 1153 }, +{ 0x6, 433, 1, 1172 }, +{ 0x6, 434, 1, 1171 }, +{ 0x6, 435, 1, 1170 }, +{ 0x6, 436, 0, 1169 }, +{ 0x1, 433, 1, 1184 }, +{ 0x1, 434, 1, 1183 }, +{ 0x1, 435, 1, 1182 }, +{ 0x1, 436, 0, 1181 }, +{ 0x9, 433, 1, 1168 }, +{ 0x9, 434, 1, 1167 }, +{ 0x9, 435, 1, 1166 }, +{ 0x9, 436, 0, 1165 }, +{ 0x19, 433, 1, 1160 }, +{ 0x19, 434, 1, 1159 }, +{ 0x31, 435, 1, 1158 }, +{ 0x31, 436, 0, 1157 }, +{ 0x5, 433, 1, 1176 }, +{ 0x5, 434, 1, 1175 }, +{ 0x5, 435, 1, 1174 }, +{ 0x5, 436, 0, 1173 }, +{ 0x3, 433, 1, 1180 }, +{ 0x3, 434, 1, 1179 }, +{ 0x3, 435, 1, 1178 }, +{ 0x3, 436, 0, 1177 }, +{ 0x1, 139, 0, 1185 }, +{ 0x1, 138, 0, 1186 }, +{ 0x1, 391, 1, 1188 }, +{ 0x1, 137, 0, 1187 }, +{ 0x2, 395, 1, 1190 }, +{ 0x2, 141, 0, 1189 }, +{ 0x1, 395, 1, 1192 }, +{ 0x1, 141, 0, 1191 }, +{ 0x1, 397, 0, 1193 }, +{ 0x1, 136, 0, 1194 }, +{ 0x2, 135, 0, 1195 }, +{ 0x2, 134, 0, 1196 }, +{ 0x1, 459, 1, 1202 }, +{ 0x1, 246, 0, 1033 }, +{ 0x1, 458, 0, 1203 }, +{ 0x1, 457, 1, 1204 }, +{ 0x1, 245, 0, 1042 }, +{ 0x1, 308, 0, 1205 }, +{ 0x1, 307, 1, 1206 }, +{ 0x1, 290, 0, 1034 }, +{ 0x1, 306, 0, 1207 }, +{ 0x1, 305, 1, 1208 }, +{ 0x1, 427, 0, 1036 }, +{ 0x1, 304, 1, 1209 }, +{ 0x1, 398, 0, 1038 }, +{ 0x1, 303, 0, 1210 }, +{ 0x1, 302, 0, 1211 }, +{ 0x1, 301, 0, 1212 }, +{ 0x1, 300, 1, 1213 }, +{ 0x2, 398, 0, 1037 }, +{ 0x10, 299, 0, 1217 }, +{ 0x90, 299, 0, 1215 }, +{ 0x190, 299, 0, 1214 }, +{ 0x50, 299, 0, 1216 }, +{ 0x30, 299, 0, 1219 }, +{ 0x70, 299, 0, 1218 }, +{ 0x8, 299, 0, 1221 }, +{ 0x18, 299, 0, 1220 }, +{ 0x4, 299, 0, 1222 }, +{ 0x1, 299, 0, 1225 }, +{ 0x3, 299, 0, 1224 }, +{ 0x1, 298, 1, 1226 }, +{ 0x2, 299, 0, 1223 }, +{ 0x3, 46, 0, 1227 }, +{ 0x1, 241, 1, 1228 }, +{ 0x1, 242, 1, 1028 }, +{ 0x1, 243, 0, 88 }, +{ 0x1, 341, 1, 1229 }, +{ 0x1, 342, 1, 1029 }, +{ 0x1, 343, 0, 89 }, +{ 0x1, 34, 1, 1230 }, +{ 0x1, 35, 1, 1030 }, +{ 0x1, 36, 0, 90 }, +{ 0x1, 230, 0, 1231 }, +{ 0x4, 452, 0, 1232 }, +{ 0x2, 452, 0, 1233 }, +{ 0x1, 452, 1, 1235 }, +{ 0x1, 453, 0, 1234 }, +{ 0x8, 454, 0, 1236 }, +{ 0x4, 454, 0, 1237 }, +{ 0x1, 454, 1, 1239 }, +{ 0x2, 454, 0, 1238 }, +{ 0x8, 219, 0, 1240 }, +{ 0x4, 219, 0, 1241 }, +{ 0x2, 219, 0, 1242 }, +{ 0x1, 219, 1, 1244 }, +{ 0x1, 220, 0, 1243 }, +{ 0x10, 221, 0, 1245 }, +{ 0x8, 221, 0, 1246 }, +{ 0x4, 221, 0, 1247 }, +{ 0x1, 221, 1, 1249 }, +{ 0x2, 221, 0, 1248 }, +{ 0x220, 191, 0, 1250 }, +{ 0x120, 191, 0, 1251 }, +{ 0xa0, 191, 0, 1252 }, +{ 0x60, 191, 1, 1254 }, +{ 0x4, 192, 0, 1253 }, +{ 0x110, 191, 0, 1260 }, +{ 0x90, 191, 0, 1261 }, +{ 0x50, 191, 0, 1262 }, +{ 0x30, 191, 1, 1264 }, +{ 0x2, 192, 0, 1263 }, +{ 0x8, 191, 0, 1265 }, +{ 0x4, 191, 0, 1266 }, +{ 0x2, 191, 0, 1267 }, +{ 0x1, 191, 1, 1269 }, +{ 0x1, 192, 0, 1268 }, +{ 0x440, 193, 0, 1255 }, +{ 0x240, 193, 0, 1256 }, +{ 0x140, 193, 0, 1257 }, +{ 0xc0, 193, 1, 1259 }, +{ 0x40, 193, 0, 1258 }, +{ 0x220, 193, 0, 1270 }, +{ 0x120, 193, 0, 1271 }, +{ 0xa0, 193, 0, 1272 }, +{ 0x60, 193, 1, 1274 }, +{ 0x20, 193, 0, 1273 }, +{ 0x10, 193, 0, 1275 }, +{ 0x8, 193, 0, 1276 }, +{ 0x4, 193, 0, 1277 }, +{ 0x1, 193, 1, 1279 }, +{ 0x2, 193, 0, 1278 }, +{ 0x8, 215, 0, 1280 }, +{ 0x4, 215, 0, 1281 }, +{ 0x2, 215, 0, 1282 }, +{ 0x1, 215, 1, 1284 }, +{ 0x1, 216, 0, 1283 }, +{ 0x220, 187, 0, 1285 }, +{ 0x120, 187, 0, 1286 }, +{ 0xa0, 187, 0, 1287 }, +{ 0x60, 187, 1, 1289 }, +{ 0x4, 188, 0, 1288 }, +{ 0x110, 187, 0, 1295 }, +{ 0x90, 187, 0, 1296 }, +{ 0x50, 187, 0, 1297 }, +{ 0x30, 187, 1, 1299 }, +{ 0x2, 188, 0, 1298 }, +{ 0x8, 187, 0, 1300 }, +{ 0x4, 187, 0, 1301 }, +{ 0x2, 187, 0, 1302 }, +{ 0x1, 187, 1, 1304 }, +{ 0x1, 188, 0, 1303 }, +{ 0x440, 233, 0, 1290 }, +{ 0x240, 233, 0, 1291 }, +{ 0x140, 233, 0, 1292 }, +{ 0xc0, 233, 1, 1294 }, +{ 0x40, 233, 0, 1293 }, +{ 0x220, 233, 0, 1305 }, +{ 0x120, 233, 0, 1306 }, +{ 0xa0, 233, 0, 1307 }, +{ 0x60, 233, 1, 1309 }, +{ 0x20, 233, 0, 1308 }, +{ 0x10, 233, 0, 1310 }, +{ 0x8, 233, 0, 1311 }, +{ 0x4, 233, 0, 1312 }, +{ 0x1, 233, 1, 1314 }, +{ 0x2, 233, 0, 1313 }, +{ 0x8, 207, 0, 1315 }, +{ 0x4, 207, 0, 1316 }, +{ 0x2, 207, 0, 1317 }, +{ 0x1, 207, 1, 1319 }, +{ 0x1, 208, 0, 1318 }, +{ 0x10, 214, 0, 1320 }, +{ 0x8, 214, 0, 1321 }, +{ 0x4, 214, 0, 1322 }, +{ 0x1, 214, 1, 1324 }, +{ 0x2, 214, 0, 1323 }, +{ 0x220, 178, 0, 1325 }, +{ 0x120, 178, 0, 1326 }, +{ 0xa0, 178, 0, 1327 }, +{ 0x60, 178, 1, 1329 }, +{ 0x4, 179, 0, 1328 }, +{ 0x110, 178, 0, 1350 }, +{ 0x90, 178, 0, 1351 }, +{ 0x50, 178, 0, 1352 }, +{ 0x30, 178, 1, 1354 }, +{ 0x2, 179, 0, 1353 }, +{ 0x8, 178, 0, 1355 }, +{ 0x4, 178, 0, 1356 }, +{ 0x2, 178, 0, 1357 }, +{ 0x1, 178, 1, 1359 }, +{ 0x1, 179, 0, 1358 }, +{ 0x440, 186, 0, 1330 }, +{ 0x240, 186, 0, 1331 }, +{ 0x140, 186, 0, 1332 }, +{ 0xc0, 186, 1, 1334 }, +{ 0x40, 186, 0, 1333 }, +{ 0x220, 186, 0, 1360 }, +{ 0x120, 186, 0, 1361 }, +{ 0xa0, 186, 0, 1362 }, +{ 0x60, 186, 1, 1364 }, +{ 0x20, 186, 0, 1363 }, +{ 0x10, 186, 0, 1365 }, +{ 0x8, 186, 0, 1366 }, +{ 0x4, 186, 0, 1367 }, +{ 0x1, 186, 1, 1369 }, +{ 0x2, 186, 0, 1368 }, +{ 0x440, 143, 0, 1335 }, +{ 0x240, 143, 0, 1336 }, +{ 0x140, 143, 0, 1337 }, +{ 0xc0, 143, 1, 1339 }, +{ 0x40, 143, 0, 1338 }, +{ 0x220, 143, 0, 1370 }, +{ 0x120, 143, 0, 1371 }, +{ 0xa0, 143, 0, 1372 }, +{ 0x60, 143, 1, 1374 }, +{ 0x20, 143, 0, 1373 }, +{ 0x10, 143, 0, 1375 }, +{ 0x8, 143, 0, 1376 }, +{ 0x1, 143, 1, 1379 }, +{ 0x2, 143, 0, 1378 }, +{ 0x440, 194, 1, 1345 }, +{ 0x441, 174, 0, 1340 }, +{ 0x240, 194, 1, 1346 }, +{ 0x241, 174, 0, 1341 }, +{ 0x140, 194, 1, 1347 }, +{ 0x141, 174, 0, 1342 }, +{ 0xc0, 194, 1, 1349 }, +{ 0x40, 194, 1, 1348 }, +{ 0xc1, 174, 1, 1344 }, +{ 0x41, 174, 0, 1343 }, +{ 0x220, 194, 1, 1390 }, +{ 0x221, 174, 0, 1380 }, +{ 0x120, 194, 1, 1391 }, +{ 0x121, 174, 0, 1381 }, +{ 0xa0, 194, 1, 1392 }, +{ 0xa1, 174, 0, 1382 }, +{ 0x60, 194, 1, 1394 }, +{ 0x20, 194, 1, 1393 }, +{ 0x61, 174, 1, 1384 }, +{ 0x21, 174, 0, 1383 }, +{ 0x10, 194, 1, 1395 }, +{ 0x11, 174, 0, 1385 }, +{ 0x8, 194, 1, 1396 }, +{ 0x9, 174, 0, 1386 }, +{ 0x4, 194, 1, 1397 }, +{ 0x5, 174, 0, 1387 }, +{ 0x1, 194, 1, 1399 }, +{ 0x2, 194, 1, 1398 }, +{ 0x3, 174, 1, 1389 }, +{ 0x1, 174, 0, 1388 }, +{ 0x1, 153, 1, 1407 }, +{ 0x1, 154, 1, 1406 }, +{ 0x1, 155, 1, 1405 }, +{ 0x1, 156, 0, 1404 }, +{ 0x3, 153, 1, 1403 }, +{ 0x3, 154, 1, 1402 }, +{ 0x3, 155, 1, 1401 }, +{ 0x3, 156, 0, 1400 }, +{ 0x1108, 159, 1, 1569 }, +{ 0x1108, 160, 1, 1568 }, +{ 0x1108, 165, 1, 1409 }, +{ 0x1108, 166, 0, 1408 }, +{ 0x908, 159, 1, 1571 }, +{ 0x908, 160, 1, 1570 }, +{ 0x908, 165, 1, 1411 }, +{ 0x908, 166, 0, 1410 }, +{ 0x508, 159, 1, 1573 }, +{ 0x508, 160, 1, 1572 }, +{ 0x508, 165, 1, 1413 }, +{ 0x508, 166, 0, 1412 }, +{ 0x308, 159, 1, 1577 }, +{ 0x308, 160, 1, 1576 }, +{ 0x108, 160, 1, 1574 }, +{ 0x18, 161, 1, 1575 }, +{ 0x308, 165, 1, 1417 }, +{ 0x308, 166, 1, 1416 }, +{ 0x108, 166, 1, 1414 }, +{ 0x18, 167, 0, 1415 }, +{ 0x88, 159, 1, 1609 }, +{ 0x88, 160, 1, 1608 }, +{ 0x88, 165, 1, 1489 }, +{ 0x88, 166, 0, 1488 }, +{ 0x48, 159, 1, 1611 }, +{ 0x48, 160, 1, 1610 }, +{ 0x48, 165, 1, 1491 }, +{ 0x48, 166, 0, 1490 }, +{ 0x28, 159, 1, 1613 }, +{ 0x28, 160, 1, 1612 }, +{ 0x28, 165, 1, 1493 }, +{ 0x28, 166, 0, 1492 }, +{ 0x18, 159, 1, 1617 }, +{ 0x18, 160, 1, 1616 }, +{ 0x8, 160, 1, 1614 }, +{ 0x8, 161, 1, 1615 }, +{ 0x18, 165, 1, 1497 }, +{ 0x18, 166, 1, 1496 }, +{ 0x8, 166, 1, 1494 }, +{ 0x8, 167, 0, 1495 }, +{ 0x884, 159, 1, 1579 }, +{ 0x884, 160, 1, 1578 }, +{ 0x442, 162, 1, 1469 }, +{ 0x442, 163, 1, 1468 }, +{ 0x884, 165, 1, 1439 }, +{ 0x884, 166, 1, 1438 }, +{ 0x442, 168, 1, 1419 }, +{ 0x442, 169, 0, 1418 }, +{ 0x484, 159, 1, 1581 }, +{ 0x484, 160, 1, 1580 }, +{ 0x242, 162, 1, 1471 }, +{ 0x242, 163, 1, 1470 }, +{ 0x484, 165, 1, 1441 }, +{ 0x484, 166, 1, 1440 }, +{ 0x242, 168, 1, 1421 }, +{ 0x242, 169, 0, 1420 }, +{ 0x284, 159, 1, 1583 }, +{ 0x284, 160, 1, 1582 }, +{ 0x142, 162, 1, 1473 }, +{ 0x142, 163, 1, 1472 }, +{ 0x284, 165, 1, 1443 }, +{ 0x284, 166, 1, 1442 }, +{ 0x142, 168, 1, 1423 }, +{ 0x142, 169, 0, 1422 }, +{ 0x184, 159, 1, 1587 }, +{ 0x184, 160, 1, 1586 }, +{ 0x84, 160, 1, 1584 }, +{ 0xc, 161, 1, 1585 }, +{ 0xc2, 162, 1, 1477 }, +{ 0xc2, 163, 1, 1476 }, +{ 0x42, 163, 1, 1474 }, +{ 0x6, 164, 1, 1475 }, +{ 0x184, 165, 1, 1447 }, +{ 0x184, 166, 1, 1446 }, +{ 0x84, 166, 1, 1444 }, +{ 0xc, 167, 1, 1445 }, +{ 0xc2, 168, 1, 1427 }, +{ 0xc2, 169, 1, 1426 }, +{ 0x42, 169, 1, 1424 }, +{ 0x6, 170, 0, 1425 }, +{ 0x44, 159, 1, 1619 }, +{ 0x44, 160, 1, 1618 }, +{ 0x22, 162, 1, 1549 }, +{ 0x22, 163, 1, 1548 }, +{ 0x44, 165, 1, 1519 }, +{ 0x44, 166, 1, 1518 }, +{ 0x22, 168, 1, 1499 }, +{ 0x22, 169, 0, 1498 }, +{ 0x24, 159, 1, 1621 }, +{ 0x24, 160, 1, 1620 }, +{ 0x12, 162, 1, 1551 }, +{ 0x12, 163, 1, 1550 }, +{ 0x24, 165, 1, 1521 }, +{ 0x24, 166, 1, 1520 }, +{ 0x12, 168, 1, 1501 }, +{ 0x12, 169, 0, 1500 }, +{ 0x14, 159, 1, 1623 }, +{ 0x14, 160, 1, 1622 }, +{ 0xa, 162, 1, 1553 }, +{ 0xa, 163, 1, 1552 }, +{ 0x14, 165, 1, 1523 }, +{ 0x14, 166, 1, 1522 }, +{ 0xa, 168, 1, 1503 }, +{ 0xa, 169, 0, 1502 }, +{ 0xc, 159, 1, 1627 }, +{ 0xc, 160, 1, 1626 }, +{ 0x4, 160, 1, 1624 }, +{ 0x4, 161, 1, 1625 }, +{ 0x6, 162, 1, 1557 }, +{ 0x6, 163, 1, 1556 }, +{ 0x2, 163, 1, 1554 }, +{ 0x2, 164, 1, 1555 }, +{ 0xc, 165, 1, 1527 }, +{ 0xc, 166, 1, 1526 }, +{ 0x4, 166, 1, 1524 }, +{ 0x4, 167, 1, 1525 }, +{ 0x6, 168, 1, 1507 }, +{ 0x6, 169, 1, 1506 }, +{ 0x2, 169, 1, 1504 }, +{ 0x2, 170, 0, 1505 }, +{ 0x442, 159, 1, 1589 }, +{ 0x442, 160, 1, 1588 }, +{ 0x221, 162, 1, 1479 }, +{ 0x221, 163, 1, 1478 }, +{ 0x442, 165, 1, 1449 }, +{ 0x442, 166, 1, 1448 }, +{ 0x221, 168, 1, 1429 }, +{ 0x221, 169, 0, 1428 }, +{ 0x242, 159, 1, 1591 }, +{ 0x242, 160, 1, 1590 }, +{ 0x121, 162, 1, 1481 }, +{ 0x121, 163, 1, 1480 }, +{ 0x242, 165, 1, 1451 }, +{ 0x242, 166, 1, 1450 }, +{ 0x121, 168, 1, 1431 }, +{ 0x121, 169, 0, 1430 }, +{ 0x142, 159, 1, 1593 }, +{ 0x142, 160, 1, 1592 }, +{ 0xa1, 162, 1, 1483 }, +{ 0xa1, 163, 1, 1482 }, +{ 0x142, 165, 1, 1453 }, +{ 0x142, 166, 1, 1452 }, +{ 0xa1, 168, 1, 1433 }, +{ 0xa1, 169, 0, 1432 }, +{ 0xc2, 159, 1, 1597 }, +{ 0xc2, 160, 1, 1596 }, +{ 0x42, 160, 1, 1594 }, +{ 0x6, 161, 1, 1595 }, +{ 0x61, 162, 1, 1487 }, +{ 0x61, 163, 1, 1486 }, +{ 0x21, 163, 1, 1484 }, +{ 0x3, 164, 1, 1485 }, +{ 0xc2, 165, 1, 1457 }, +{ 0xc2, 166, 1, 1456 }, +{ 0x42, 166, 1, 1454 }, +{ 0x6, 167, 1, 1455 }, +{ 0x61, 168, 1, 1437 }, +{ 0x61, 169, 1, 1436 }, +{ 0x21, 169, 1, 1434 }, +{ 0x3, 170, 0, 1435 }, +{ 0x22, 159, 1, 1629 }, +{ 0x22, 160, 1, 1628 }, +{ 0x11, 162, 1, 1559 }, +{ 0x11, 163, 1, 1558 }, +{ 0x22, 165, 1, 1529 }, +{ 0x22, 166, 1, 1528 }, +{ 0x11, 168, 1, 1509 }, +{ 0x11, 169, 0, 1508 }, +{ 0x12, 159, 1, 1631 }, +{ 0x12, 160, 1, 1630 }, +{ 0x9, 162, 1, 1561 }, +{ 0x9, 163, 1, 1560 }, +{ 0x12, 165, 1, 1531 }, +{ 0x12, 166, 1, 1530 }, +{ 0x9, 168, 1, 1511 }, +{ 0x9, 169, 0, 1510 }, +{ 0xa, 159, 1, 1633 }, +{ 0xa, 160, 1, 1632 }, +{ 0x5, 162, 1, 1563 }, +{ 0x5, 163, 1, 1562 }, +{ 0xa, 165, 1, 1533 }, +{ 0xa, 166, 1, 1532 }, +{ 0x5, 168, 1, 1513 }, +{ 0x5, 169, 0, 1512 }, +{ 0x6, 159, 1, 1637 }, +{ 0x6, 160, 1, 1636 }, +{ 0x2, 160, 1, 1634 }, +{ 0x2, 161, 1, 1635 }, +{ 0x3, 162, 1, 1567 }, +{ 0x3, 163, 1, 1566 }, +{ 0x1, 163, 1, 1564 }, +{ 0x1, 164, 1, 1565 }, +{ 0x6, 165, 1, 1537 }, +{ 0x6, 166, 1, 1536 }, +{ 0x2, 166, 1, 1534 }, +{ 0x2, 167, 1, 1535 }, +{ 0x3, 168, 1, 1517 }, +{ 0x3, 169, 1, 1516 }, +{ 0x1, 169, 1, 1514 }, +{ 0x1, 170, 0, 1515 }, +{ 0x221, 159, 1, 1599 }, +{ 0x221, 160, 1, 1598 }, +{ 0x221, 165, 1, 1459 }, +{ 0x221, 166, 0, 1458 }, +{ 0x121, 159, 1, 1601 }, +{ 0x121, 160, 1, 1600 }, +{ 0x121, 165, 1, 1461 }, +{ 0x121, 166, 0, 1460 }, +{ 0xa1, 159, 1, 1603 }, +{ 0xa1, 160, 1, 1602 }, +{ 0xa1, 165, 1, 1463 }, +{ 0xa1, 166, 0, 1462 }, +{ 0x61, 159, 1, 1607 }, +{ 0x61, 160, 1, 1606 }, +{ 0x21, 160, 1, 1604 }, +{ 0x3, 161, 1, 1605 }, +{ 0x61, 165, 1, 1467 }, +{ 0x61, 166, 1, 1466 }, +{ 0x21, 166, 1, 1464 }, +{ 0x3, 167, 0, 1465 }, +{ 0x11, 159, 1, 1639 }, +{ 0x11, 160, 1, 1638 }, +{ 0x11, 165, 1, 1539 }, +{ 0x11, 166, 0, 1538 }, +{ 0x9, 159, 1, 1641 }, +{ 0x9, 160, 1, 1640 }, +{ 0x9, 165, 1, 1541 }, +{ 0x9, 166, 0, 1540 }, +{ 0x5, 159, 1, 1643 }, +{ 0x5, 160, 1, 1642 }, +{ 0x5, 165, 1, 1543 }, +{ 0x5, 166, 0, 1542 }, +{ 0x3, 159, 1, 1647 }, +{ 0x3, 160, 1, 1646 }, +{ 0x1, 160, 1, 1644 }, +{ 0x1, 161, 1, 1645 }, +{ 0x3, 165, 1, 1547 }, +{ 0x3, 166, 1, 1546 }, +{ 0x1, 166, 1, 1544 }, +{ 0x1, 167, 0, 1545 }, +{ 0x442, 205, 0, 1648 }, +{ 0x242, 205, 0, 1649 }, +{ 0x142, 205, 0, 1650 }, +{ 0xc2, 205, 1, 1652 }, +{ 0x6, 206, 1, 1651 }, +{ 0x1, 443, 0, 981 }, +{ 0x22, 205, 0, 1658 }, +{ 0x12, 205, 0, 1659 }, +{ 0xa, 205, 0, 1660 }, +{ 0x6, 205, 1, 1662 }, +{ 0x2, 206, 1, 1661 }, +{ 0x2, 367, 0, 1010 }, +{ 0x221, 205, 0, 1653 }, +{ 0x121, 205, 0, 1654 }, +{ 0xa1, 205, 0, 1655 }, +{ 0x61, 205, 1, 1657 }, +{ 0x3, 206, 1, 1656 }, +{ 0x1, 437, 0, 982 }, +{ 0x11, 205, 0, 1663 }, +{ 0x9, 205, 0, 1664 }, +{ 0x5, 205, 0, 1665 }, +{ 0x3, 205, 1, 1667 }, +{ 0x1, 206, 1, 1666 }, +{ 0x1, 367, 0, 1011 }, +{ 0x4, 211, 0, 1668 }, +{ 0x1, 211, 0, 1670 }, +{ 0x1, 218, 0, 1671 }, +{ 0x1, 217, 1, 1672 }, +{ 0x2, 211, 0, 1669 }, +{ 0x1, 196, 0, 1673 }, +{ 0x880, 202, 0, 1674 }, +{ 0x480, 202, 0, 1675 }, +{ 0x280, 202, 0, 1676 }, +{ 0x180, 202, 1, 1678 }, +{ 0x80, 203, 0, 1677 }, +{ 0x440, 202, 1, 1689 }, +{ 0x88, 204, 0, 1679 }, +{ 0x240, 202, 1, 1690 }, +{ 0x48, 204, 0, 1680 }, +{ 0x140, 202, 1, 1691 }, +{ 0x28, 204, 0, 1681 }, +{ 0xc0, 202, 1, 1693 }, +{ 0x40, 203, 1, 1692 }, +{ 0x18, 204, 1, 1683 }, +{ 0x8, 204, 0, 1682 }, +{ 0x220, 202, 1, 1694 }, +{ 0x44, 204, 0, 1684 }, +{ 0x120, 202, 1, 1695 }, +{ 0x24, 204, 0, 1685 }, +{ 0xa0, 202, 1, 1696 }, +{ 0x14, 204, 0, 1686 }, +{ 0x60, 202, 1, 1698 }, +{ 0x20, 203, 1, 1697 }, +{ 0xc, 204, 1, 1688 }, +{ 0x4, 204, 0, 1687 }, +{ 0x110, 202, 0, 1699 }, +{ 0x90, 202, 0, 1700 }, +{ 0x50, 202, 0, 1701 }, +{ 0x30, 202, 1, 1703 }, +{ 0x10, 203, 1, 1702 }, +{ 0x1, 385, 0, 974 }, +{ 0x88, 202, 0, 1704 }, +{ 0x48, 202, 0, 1705 }, +{ 0x28, 202, 0, 1706 }, +{ 0x18, 202, 1, 1708 }, +{ 0x8, 203, 1, 1707 }, +{ 0xc, 368, 0, 1007 }, +{ 0x44, 202, 1, 1719 }, +{ 0x22, 204, 0, 1709 }, +{ 0x24, 202, 1, 1720 }, +{ 0x12, 204, 0, 1710 }, +{ 0x14, 202, 1, 1721 }, +{ 0xa, 204, 0, 1711 }, +{ 0xc, 202, 1, 1723 }, +{ 0x4, 203, 1, 1722 }, +{ 0x6, 204, 1, 1713 }, +{ 0x2, 204, 1, 1712 }, +{ 0x6, 368, 0, 1008 }, +{ 0x22, 202, 1, 1724 }, +{ 0x11, 204, 0, 1714 }, +{ 0x12, 202, 1, 1725 }, +{ 0x9, 204, 0, 1715 }, +{ 0xa, 202, 1, 1726 }, +{ 0x5, 204, 0, 1716 }, +{ 0x6, 202, 1, 1728 }, +{ 0x2, 203, 1, 1727 }, +{ 0x3, 204, 1, 1718 }, +{ 0x1, 204, 1, 1717 }, +{ 0x3, 368, 0, 1009 }, +{ 0x11, 202, 0, 1729 }, +{ 0x9, 202, 0, 1730 }, +{ 0x5, 202, 0, 1731 }, +{ 0x3, 202, 1, 1733 }, +{ 0x1, 203, 0, 1732 }, +{ 0x8, 198, 0, 1734 }, +{ 0x4, 198, 0, 1735 }, +{ 0x2, 198, 0, 1736 }, +{ 0x1, 198, 1, 1738 }, +{ 0x1, 199, 1, 1737 }, +{ 0x1, 332, 0, 988 }, +{ 0x8, 200, 0, 1739 }, +{ 0x4, 200, 0, 1740 }, +{ 0x2, 200, 0, 1741 }, +{ 0x1, 200, 1, 1743 }, +{ 0x1, 201, 1, 1742 }, +{ 0x1, 331, 0, 989 }, +{ 0x8, 209, 0, 1744 }, +{ 0x4, 209, 0, 1745 }, +{ 0x2, 209, 0, 1746 }, +{ 0x1, 209, 1, 1748 }, +{ 0x1, 210, 1, 1747 }, +{ 0x1, 330, 0, 990 }, +{ 0x8, 212, 0, 1749 }, +{ 0x4, 212, 0, 1750 }, +{ 0x2, 212, 0, 1751 }, +{ 0x1, 212, 1, 1753 }, +{ 0x1, 213, 1, 1752 }, +{ 0x1, 329, 0, 991 }, +{ 0x8, 224, 0, 1754 }, +{ 0x4, 224, 0, 1755 }, +{ 0x2, 224, 0, 1756 }, +{ 0x1, 224, 1, 1758 }, +{ 0x1, 225, 0, 1757 }, +{ 0x8, 222, 0, 1759 }, +{ 0x4, 222, 0, 1760 }, +{ 0x2, 222, 0, 1761 }, +{ 0x1, 222, 1, 1763 }, +{ 0x1, 223, 0, 1762 }, +{ 0x1, 240, 0, 1764 }, +{ 0x1, 340, 0, 1765 }, +{ 0x1, 33, 0, 1766 }, +{ 0x8, 151, 0, 1767 }, +{ 0x4, 151, 0, 1768 }, +{ 0x2, 151, 0, 1769 }, +{ 0x1, 151, 1, 1771 }, +{ 0x1, 152, 0, 1770 }, +{ 0x8, 157, 0, 1772 }, +{ 0x4, 157, 0, 1773 }, +{ 0x2, 157, 0, 1774 }, +{ 0x1, 157, 1, 1776 }, +{ 0x1, 158, 0, 1775 }, +{ 0x8, 231, 0, 1777 }, +{ 0x4, 231, 0, 1778 }, +{ 0x2, 231, 0, 1779 }, +{ 0x1, 231, 1, 1781 }, +{ 0x1, 232, 0, 1780 }, +{ 0x1, 173, 0, 1782 }, +{ 0x442, 171, 0, 1783 }, +{ 0x242, 171, 0, 1784 }, +{ 0x142, 171, 0, 1785 }, +{ 0xc2, 171, 1, 1787 }, +{ 0x6, 172, 0, 1786 }, +{ 0x22, 171, 0, 1793 }, +{ 0x12, 171, 0, 1794 }, +{ 0xa, 171, 0, 1795 }, +{ 0x6, 171, 1, 1797 }, +{ 0x2, 172, 1, 1796 }, +{ 0x1, 135, 0, 1197 }, +{ 0x221, 171, 0, 1788 }, +{ 0x121, 171, 0, 1789 }, +{ 0xa1, 171, 0, 1790 }, +{ 0x61, 171, 1, 1792 }, +{ 0x3, 172, 0, 1791 }, +{ 0x11, 171, 0, 1798 }, +{ 0x9, 171, 0, 1799 }, +{ 0x5, 171, 0, 1800 }, +{ 0x3, 171, 1, 1802 }, +{ 0x1, 172, 1, 1801 }, +{ 0x1, 134, 0, 1198 }, +{ 0x1, 237, 0, 1803 }, +{ 0x1, 195, 0, 1804 }, +{ 0x1, 149, 0, 1805 }, +{ 0x1, 148, 0, 1806 }, +{ 0x4, 234, 0, 1807 }, +{ 0x2, 234, 0, 1808 }, +{ 0x1, 234, 0, 1809 }, +{ 0x1, 197, 0, 1810 }, +{ 0x2, 235, 0, 1811 }, +{ 0x1, 235, 0, 1812 }, +{ 0x4, 185, 0, 1813 }, +{ 0x2, 185, 0, 1814 }, +{ 0x1, 185, 0, 1815 }, +{ 0x4, 182, 0, 1816 }, +{ 0x1, 190, 0, 1819 }, +{ 0x1, 189, 1, 1820 }, +{ 0x2, 182, 0, 1817 }, +{ 0x1, 142, 0, 1821 }, +{ 0x1, 297, 1, 1822 }, +{ 0x1, 182, 0, 1818 }, +{ 0x8, 144, 0, 1823 }, +{ 0x4, 144, 0, 1824 }, +{ 0x2, 144, 0, 1825 }, +{ 0x1, 144, 1, 1827 }, +{ 0x1, 145, 0, 1826 }, +{ 0x8, 146, 0, 1828 }, +{ 0x4, 146, 0, 1829 }, +{ 0x2, 146, 0, 1830 }, +{ 0x1, 146, 1, 1832 }, +{ 0x1, 147, 1, 1831 }, +{ 0x1, 426, 0, 1199 }, +{ 0x8, 180, 0, 1833 }, +{ 0x4, 180, 0, 1834 }, +{ 0x2, 180, 0, 1835 }, +{ 0x1, 180, 1, 1837 }, +{ 0x1, 181, 1, 1836 }, +{ 0x1, 425, 0, 1200 }, +{ 0x8, 183, 0, 1838 }, +{ 0x4, 183, 0, 1839 }, +{ 0x2, 183, 0, 1840 }, +{ 0x1, 183, 1, 1842 }, +{ 0x1, 184, 1, 1841 }, +{ 0x1, 424, 0, 1201 }, +{ 0x8, 228, 0, 1843 }, +{ 0x4, 228, 0, 1844 }, +{ 0x2, 228, 0, 1845 }, +{ 0x1, 228, 1, 1847 }, +{ 0x1, 229, 0, 1846 }, +{ 0x8, 226, 0, 1848 }, +{ 0x4, 226, 0, 1849 }, +{ 0x2, 226, 0, 1850 }, +{ 0x1, 226, 1, 1852 }, +{ 0x1, 227, 0, 1851 }, +{ 0x8, 44, 0, 1857 }, +{ 0x18, 44, 0, 1853 }, +{ 0x4, 44, 0, 1858 }, +{ 0xc, 44, 0, 1854 }, +{ 0x2, 44, 0, 1859 }, +{ 0x6, 44, 0, 1855 }, +{ 0x1, 44, 0, 1860 }, +{ 0x3, 44, 0, 1856 }, +{ 0x51, 30, 0, 1862 }, +{ 0xd1, 30, 0, 1861 }, +{ 0x31, 30, 1, 1872 }, +{ 0x11, 31, 0, 1871 }, +{ 0x71, 30, 1, 1870 }, +{ 0x31, 31, 0, 1869 }, +{ 0x29, 30, 0, 1864 }, +{ 0x69, 30, 0, 1863 }, +{ 0x19, 30, 1, 1876 }, +{ 0x9, 31, 0, 1875 }, +{ 0x39, 30, 1, 1874 }, +{ 0x19, 31, 0, 1873 }, +{ 0x15, 30, 0, 1866 }, +{ 0x35, 30, 0, 1865 }, +{ 0xd, 30, 1, 1880 }, +{ 0x5, 31, 0, 1879 }, +{ 0x1d, 30, 1, 1878 }, +{ 0xd, 31, 0, 1877 }, +{ 0xb, 30, 0, 1868 }, +{ 0x1b, 30, 0, 1867 }, +{ 0x7, 30, 1, 1884 }, +{ 0x3, 31, 0, 1883 }, +{ 0xf, 30, 1, 1882 }, +{ 0x7, 31, 0, 1881 }, +{ 0xa2, 28, 0, 1886 }, +{ 0x1a2, 28, 0, 1885 }, +{ 0x62, 28, 1, 1896 }, +{ 0x22, 29, 0, 1895 }, +{ 0xe2, 28, 1, 1894 }, +{ 0x62, 29, 0, 1893 }, +{ 0x52, 28, 0, 1888 }, +{ 0xd2, 28, 0, 1887 }, +{ 0x32, 28, 1, 1900 }, +{ 0x12, 29, 0, 1899 }, +{ 0x72, 28, 1, 1898 }, +{ 0x32, 29, 0, 1897 }, +{ 0x2a, 28, 0, 1890 }, +{ 0x6a, 28, 0, 1889 }, +{ 0x1a, 28, 1, 1904 }, +{ 0xa, 29, 0, 1903 }, +{ 0x3a, 28, 1, 1902 }, +{ 0x1a, 29, 0, 1901 }, +{ 0x16, 28, 0, 1892 }, +{ 0x36, 28, 0, 1891 }, +{ 0xe, 28, 1, 1908 }, +{ 0x6, 29, 0, 1907 }, +{ 0x1e, 28, 1, 1906 }, +{ 0xe, 29, 0, 1905 }, +{ 0x51, 28, 0, 1910 }, +{ 0xd1, 28, 0, 1909 }, +{ 0x31, 28, 1, 1920 }, +{ 0x11, 29, 0, 1919 }, +{ 0x71, 28, 1, 1918 }, +{ 0x31, 29, 0, 1917 }, +{ 0x29, 28, 0, 1912 }, +{ 0x69, 28, 0, 1911 }, +{ 0x19, 28, 1, 1924 }, +{ 0x9, 29, 0, 1923 }, +{ 0x39, 28, 1, 1922 }, +{ 0x19, 29, 0, 1921 }, +{ 0x15, 28, 0, 1914 }, +{ 0x35, 28, 0, 1913 }, +{ 0xd, 28, 1, 1928 }, +{ 0x5, 29, 0, 1927 }, +{ 0x1d, 28, 1, 1926 }, +{ 0xd, 29, 0, 1925 }, +{ 0xb, 28, 0, 1916 }, +{ 0x1b, 28, 0, 1915 }, +{ 0x7, 28, 1, 1932 }, +{ 0x3, 29, 0, 1931 }, +{ 0xf, 28, 1, 1930 }, +{ 0x7, 29, 0, 1929 }, +{ 0x51, 26, 0, 1934 }, +{ 0xd1, 26, 0, 1933 }, +{ 0x31, 26, 1, 1944 }, +{ 0x11, 27, 0, 1943 }, +{ 0x71, 26, 1, 1942 }, +{ 0x31, 27, 0, 1941 }, +{ 0x29, 26, 0, 1936 }, +{ 0x69, 26, 0, 1935 }, +{ 0x19, 26, 1, 1948 }, +{ 0x9, 27, 0, 1947 }, +{ 0x39, 26, 1, 1946 }, +{ 0x19, 27, 0, 1945 }, +{ 0x15, 26, 0, 1938 }, +{ 0x35, 26, 0, 1937 }, +{ 0xd, 26, 1, 1952 }, +{ 0x5, 27, 0, 1951 }, +{ 0x1d, 26, 1, 1950 }, +{ 0xd, 27, 0, 1949 }, +{ 0xb, 26, 0, 1940 }, +{ 0x1b, 26, 0, 1939 }, +{ 0x7, 26, 1, 1956 }, +{ 0x3, 27, 0, 1955 }, +{ 0xf, 26, 1, 1954 }, +{ 0x7, 27, 0, 1953 }, +{ 0xa2, 24, 0, 1958 }, +{ 0x1a2, 24, 0, 1957 }, +{ 0x62, 24, 1, 1968 }, +{ 0x22, 25, 0, 1967 }, +{ 0xe2, 24, 1, 1966 }, +{ 0x62, 25, 0, 1965 }, +{ 0x52, 24, 0, 1960 }, +{ 0xd2, 24, 0, 1959 }, +{ 0x32, 24, 1, 1972 }, +{ 0x12, 25, 0, 1971 }, +{ 0x72, 24, 1, 1970 }, +{ 0x32, 25, 0, 1969 }, +{ 0x2a, 24, 0, 1962 }, +{ 0x6a, 24, 0, 1961 }, +{ 0x1a, 24, 1, 1976 }, +{ 0xa, 25, 0, 1975 }, +{ 0x3a, 24, 1, 1974 }, +{ 0x1a, 25, 0, 1973 }, +{ 0x16, 24, 0, 1964 }, +{ 0x36, 24, 0, 1963 }, +{ 0xe, 24, 1, 1980 }, +{ 0x6, 25, 0, 1979 }, +{ 0x1e, 24, 1, 1978 }, +{ 0xe, 25, 0, 1977 }, +{ 0x51, 24, 0, 1982 }, +{ 0xd1, 24, 0, 1981 }, +{ 0x31, 24, 1, 1992 }, +{ 0x11, 25, 0, 1991 }, +{ 0x71, 24, 1, 1990 }, +{ 0x31, 25, 0, 1989 }, +{ 0x29, 24, 0, 1984 }, +{ 0x69, 24, 0, 1983 }, +{ 0x19, 24, 1, 1996 }, +{ 0x9, 25, 0, 1995 }, +{ 0x39, 24, 1, 1994 }, +{ 0x19, 25, 0, 1993 }, +{ 0x15, 24, 0, 1986 }, +{ 0x35, 24, 0, 1985 }, +{ 0xd, 24, 1, 2000 }, +{ 0x5, 25, 0, 1999 }, +{ 0x1d, 24, 1, 1998 }, +{ 0xd, 25, 0, 1997 }, +{ 0xb, 24, 0, 1988 }, +{ 0x1b, 24, 0, 1987 }, +{ 0x7, 24, 1, 2004 }, +{ 0x3, 25, 0, 2003 }, +{ 0xf, 24, 1, 2002 }, +{ 0x7, 25, 0, 2001 }, +{ 0x51, 22, 1, 2030 }, +{ 0x50, 22, 0, 2006 }, +{ 0xd1, 22, 1, 2029 }, +{ 0xd0, 22, 0, 2005 }, +{ 0x31, 22, 1, 2040 }, +{ 0x30, 22, 1, 2016 }, +{ 0x11, 23, 1, 2039 }, +{ 0x10, 23, 0, 2015 }, +{ 0x71, 22, 1, 2038 }, +{ 0x70, 22, 1, 2014 }, +{ 0x31, 23, 1, 2037 }, +{ 0x30, 23, 0, 2013 }, +{ 0x29, 22, 1, 2032 }, +{ 0x28, 22, 0, 2008 }, +{ 0x69, 22, 1, 2031 }, +{ 0x68, 22, 0, 2007 }, +{ 0x19, 22, 1, 2044 }, +{ 0x18, 22, 1, 2020 }, +{ 0x9, 23, 1, 2043 }, +{ 0x8, 23, 0, 2019 }, +{ 0x39, 22, 1, 2042 }, +{ 0x38, 22, 1, 2018 }, +{ 0x19, 23, 1, 2041 }, +{ 0x18, 23, 0, 2017 }, +{ 0x15, 22, 1, 2034 }, +{ 0x14, 22, 0, 2010 }, +{ 0x35, 22, 1, 2033 }, +{ 0x34, 22, 0, 2009 }, +{ 0xd, 22, 1, 2048 }, +{ 0xc, 22, 1, 2024 }, +{ 0x5, 23, 1, 2047 }, +{ 0x4, 23, 0, 2023 }, +{ 0x1d, 22, 1, 2046 }, +{ 0x1c, 22, 1, 2022 }, +{ 0xd, 23, 1, 2045 }, +{ 0xc, 23, 0, 2021 }, +{ 0xb, 22, 1, 2036 }, +{ 0xa, 22, 0, 2012 }, +{ 0x1b, 22, 1, 2035 }, +{ 0x1a, 22, 0, 2011 }, +{ 0x7, 22, 1, 2052 }, +{ 0x6, 22, 1, 2028 }, +{ 0x3, 23, 1, 2051 }, +{ 0x2, 23, 0, 2027 }, +{ 0xf, 22, 1, 2050 }, +{ 0xe, 22, 1, 2026 }, +{ 0x7, 23, 1, 2049 }, +{ 0x6, 23, 0, 2025 }, +{ 0x8, 21, 0, 2054 }, +{ 0x18, 21, 0, 2053 }, +{ 0x1, 21, 1, 2058 }, +{ 0x2, 21, 0, 2057 }, +{ 0x3, 21, 1, 2056 }, +{ 0x4, 21, 0, 2055 }, +{ 0x1, 239, 0, 2059 }, +{ 0x1, 339, 0, 2060 }, +{ 0x14, 43, 0, 2063 }, +{ 0x34, 43, 0, 2061 }, +{ 0xc, 43, 0, 2064 }, +{ 0x1c, 43, 0, 2062 }, +{ 0x2, 43, 0, 2067 }, +{ 0x6, 43, 0, 2065 }, +{ 0x1, 43, 0, 2068 }, +{ 0x3, 43, 0, 2066 }, +{ 0x51, 19, 0, 2070 }, +{ 0xd1, 19, 0, 2069 }, +{ 0x31, 19, 1, 2080 }, +{ 0x11, 20, 0, 2079 }, +{ 0x71, 19, 1, 2078 }, +{ 0x31, 20, 0, 2077 }, +{ 0x29, 19, 0, 2072 }, +{ 0x69, 19, 0, 2071 }, +{ 0x19, 19, 1, 2084 }, +{ 0x9, 20, 0, 2083 }, +{ 0x39, 19, 1, 2082 }, +{ 0x19, 20, 0, 2081 }, +{ 0x15, 19, 0, 2074 }, +{ 0x35, 19, 0, 2073 }, +{ 0xd, 19, 1, 2088 }, +{ 0x5, 20, 0, 2087 }, +{ 0x1d, 19, 1, 2086 }, +{ 0xd, 20, 0, 2085 }, +{ 0xb, 19, 0, 2076 }, +{ 0x1b, 19, 0, 2075 }, +{ 0x7, 19, 1, 2092 }, +{ 0x3, 20, 0, 2091 }, +{ 0xf, 19, 1, 2090 }, +{ 0x7, 20, 0, 2089 }, +{ 0x1, 32, 0, 2093 }, +{ 0x2, 447, 0, 2094 }, +{ 0x1, 447, 0, 2095 }, +{ 0x1, 140, 0, 2096 }, +{ 0x2, 45, 0, 2097 }, +{ 0x1, 45, 0, 2098 }, +{ 0x1, 387, 0, 2099 }, +{ 0x2, 52, 0, 2100 }, +{ 0x1, 52, 0, 2101 }, +{ 0x1, 133, 0, 2102 }, +{ 0x51, 17, 0, 2104 }, +{ 0xd1, 17, 0, 2103 }, +{ 0x31, 17, 1, 2114 }, +{ 0x11, 18, 0, 2113 }, +{ 0x71, 17, 1, 2112 }, +{ 0x31, 18, 0, 2111 }, +{ 0x29, 17, 0, 2106 }, +{ 0x69, 17, 0, 2105 }, +{ 0x19, 17, 1, 2118 }, +{ 0x9, 18, 0, 2117 }, +{ 0x39, 17, 1, 2116 }, +{ 0x19, 18, 0, 2115 }, +{ 0x15, 17, 0, 2108 }, +{ 0x35, 17, 0, 2107 }, +{ 0xd, 17, 1, 2122 }, +{ 0x5, 18, 0, 2121 }, +{ 0x1d, 17, 1, 2120 }, +{ 0xd, 18, 0, 2119 }, +{ 0xb, 17, 0, 2110 }, +{ 0x1b, 17, 0, 2109 }, +{ 0x7, 17, 1, 2126 }, +{ 0x3, 18, 0, 2125 }, +{ 0xf, 17, 1, 2124 }, +{ 0x7, 18, 0, 2123 }, +{ 0xa20, 15, 0, 2128 }, +{ 0x1a20, 15, 0, 2127 }, +{ 0x620, 15, 1, 2138 }, +{ 0x220, 16, 0, 2137 }, +{ 0xe20, 15, 1, 2136 }, +{ 0x620, 16, 0, 2135 }, +{ 0x520, 15, 0, 2130 }, +{ 0xd20, 15, 0, 2129 }, +{ 0x320, 15, 1, 2142 }, +{ 0x120, 16, 0, 2141 }, +{ 0x720, 15, 1, 2140 }, +{ 0x320, 16, 0, 2139 }, +{ 0x2a0, 15, 0, 2132 }, +{ 0x6a0, 15, 0, 2131 }, +{ 0x1a0, 15, 1, 2146 }, +{ 0xa0, 16, 0, 2145 }, +{ 0x3a0, 15, 1, 2144 }, +{ 0x1a0, 16, 0, 2143 }, +{ 0x160, 15, 0, 2134 }, +{ 0x360, 15, 0, 2133 }, +{ 0xe0, 15, 1, 2150 }, +{ 0x60, 16, 0, 2149 }, +{ 0x1e0, 15, 1, 2148 }, +{ 0xe0, 16, 0, 2147 }, +{ 0x51, 15, 1, 2176 }, +{ 0x50, 15, 0, 2152 }, +{ 0xd1, 15, 1, 2175 }, +{ 0xd0, 15, 0, 2151 }, +{ 0x31, 15, 1, 2186 }, +{ 0x30, 15, 1, 2162 }, +{ 0x11, 16, 1, 2185 }, +{ 0x10, 16, 0, 2161 }, +{ 0x71, 15, 1, 2184 }, +{ 0x70, 15, 1, 2160 }, +{ 0x31, 16, 1, 2183 }, +{ 0x30, 16, 0, 2159 }, +{ 0x29, 15, 1, 2178 }, +{ 0x28, 15, 0, 2154 }, +{ 0x69, 15, 1, 2177 }, +{ 0x68, 15, 0, 2153 }, +{ 0x19, 15, 1, 2190 }, +{ 0x18, 15, 1, 2166 }, +{ 0x9, 16, 1, 2189 }, +{ 0x8, 16, 0, 2165 }, +{ 0x39, 15, 1, 2188 }, +{ 0x38, 15, 1, 2164 }, +{ 0x19, 16, 1, 2187 }, +{ 0x18, 16, 0, 2163 }, +{ 0x15, 15, 1, 2180 }, +{ 0x14, 15, 0, 2156 }, +{ 0x35, 15, 1, 2179 }, +{ 0x34, 15, 0, 2155 }, +{ 0xd, 15, 1, 2194 }, +{ 0xc, 15, 1, 2170 }, +{ 0x5, 16, 1, 2193 }, +{ 0x4, 16, 0, 2169 }, +{ 0x1d, 15, 1, 2192 }, +{ 0x1c, 15, 1, 2168 }, +{ 0xd, 16, 1, 2191 }, +{ 0xc, 16, 0, 2167 }, +{ 0xb, 15, 1, 2182 }, +{ 0xa, 15, 0, 2158 }, +{ 0x1b, 15, 1, 2181 }, +{ 0x1a, 15, 0, 2157 }, +{ 0x7, 15, 1, 2198 }, +{ 0x6, 15, 1, 2174 }, +{ 0x3, 16, 1, 2197 }, +{ 0x2, 16, 0, 2173 }, +{ 0xf, 15, 1, 2196 }, +{ 0xe, 15, 1, 2172 }, +{ 0x7, 16, 1, 2195 }, +{ 0x6, 16, 0, 2171 }, +{ 0x8, 14, 0, 2200 }, +{ 0x18, 14, 0, 2199 }, +{ 0x1, 14, 1, 2204 }, +{ 0x2, 14, 0, 2203 }, +{ 0x3, 14, 1, 2202 }, +{ 0x4, 14, 0, 2201 }, +{ 0x1, 109, 1, 2356 }, +{ 0x1, 110, 1, 2355 }, +{ 0x1, 111, 1, 2354 }, +{ 0x1, 112, 1, 2353 }, +{ 0x1, 113, 1, 2352 }, +{ 0x1, 114, 1, 2351 }, +{ 0x1, 115, 1, 2350 }, +{ 0x1, 116, 1, 2349 }, +{ 0x39, 41, 1, 22 }, +{ 0x19, 42, 0, 21 }, +{ 0x3, 109, 1, 2348 }, +{ 0x3, 110, 1, 2347 }, +{ 0x3, 111, 1, 2346 }, +{ 0x3, 112, 1, 2345 }, +{ 0x3, 113, 1, 2344 }, +{ 0x3, 114, 1, 2343 }, +{ 0x3, 115, 1, 2342 }, +{ 0x3, 116, 1, 2341 }, +{ 0x69, 41, 0, 11 }, +{ 0x14, 100, 1, 2336 }, +{ 0x22, 101, 1, 2333 }, +{ 0x44, 101, 1, 2335 }, +{ 0xa, 108, 1, 2334 }, +{ 0xd1, 41, 0, 9 }, +{ 0x34, 100, 1, 2208 }, +{ 0xc4, 101, 1, 2207 }, +{ 0x1c, 107, 1, 2205 }, +{ 0xe, 122, 0, 2206 }, +{ 0xc, 100, 1, 2496 }, +{ 0xa, 101, 1, 2493 }, +{ 0x14, 101, 1, 2495 }, +{ 0x6, 108, 0, 2494 }, +{ 0x2, 100, 1, 2220 }, +{ 0x2, 101, 1, 2219 }, +{ 0x2, 106, 1, 2218 }, +{ 0x2, 107, 0, 2217 }, +{ 0x12, 100, 1, 2216 }, +{ 0x42, 101, 1, 2215 }, +{ 0x6, 106, 1, 2214 }, +{ 0x6, 107, 0, 2213 }, +{ 0xa, 100, 1, 2340 }, +{ 0x12, 101, 1, 2339 }, +{ 0x24, 101, 1, 2337 }, +{ 0x5, 108, 1, 2338 }, +{ 0x71, 41, 1, 18 }, +{ 0x31, 42, 0, 17 }, +{ 0x1a, 100, 1, 2212 }, +{ 0x32, 101, 1, 2211 }, +{ 0x1a, 107, 1, 2209 }, +{ 0x7, 122, 0, 2210 }, +{ 0x6, 100, 1, 2500 }, +{ 0x6, 101, 1, 2499 }, +{ 0xc, 101, 1, 2497 }, +{ 0x3, 108, 0, 2498 }, +{ 0x1, 100, 1, 2516 }, +{ 0x1, 101, 1, 2515 }, +{ 0x1, 102, 1, 2514 }, +{ 0x1, 103, 1, 2513 }, +{ 0x1, 104, 1, 2512 }, +{ 0x1, 105, 1, 2511 }, +{ 0x1, 106, 1, 2510 }, +{ 0x1, 107, 0, 2509 }, +{ 0x3, 100, 1, 2508 }, +{ 0x3, 101, 1, 2507 }, +{ 0x3, 102, 1, 2506 }, +{ 0x3, 103, 1, 2505 }, +{ 0x3, 104, 1, 2504 }, +{ 0x3, 105, 1, 2503 }, +{ 0x3, 106, 1, 2502 }, +{ 0x3, 107, 0, 2501 }, +{ 0x8, 67, 1, 2380 }, +{ 0x8, 68, 1, 2379 }, +{ 0x2, 73, 1, 2374 }, +{ 0x2, 74, 1, 2373 }, +{ 0x1, 76, 1, 2378 }, +{ 0x1, 77, 1, 2377 }, +{ 0x1, 78, 1, 2376 }, +{ 0x1, 79, 1, 2375 }, +{ 0xf, 41, 1, 30 }, +{ 0x7, 42, 0, 29 }, +{ 0x18, 67, 1, 2372 }, +{ 0x18, 68, 1, 2371 }, +{ 0x6, 73, 1, 2366 }, +{ 0x6, 74, 1, 2365 }, +{ 0x3, 76, 1, 2370 }, +{ 0x3, 77, 1, 2369 }, +{ 0x3, 78, 1, 2368 }, +{ 0x3, 79, 1, 2367 }, +{ 0x1b, 41, 0, 15 }, +{ 0x14, 67, 1, 2360 }, +{ 0x22, 68, 1, 2357 }, +{ 0x44, 68, 1, 2359 }, +{ 0xa, 75, 1, 2358 }, +{ 0x35, 41, 0, 13 }, +{ 0x34, 67, 1, 2224 }, +{ 0xc4, 68, 1, 2223 }, +{ 0x38, 74, 1, 2221 }, +{ 0xe, 85, 0, 2222 }, +{ 0xc, 67, 1, 2520 }, +{ 0xa, 68, 1, 2517 }, +{ 0x14, 68, 1, 2519 }, +{ 0x6, 75, 0, 2518 }, +{ 0x2, 67, 1, 2236 }, +{ 0x2, 68, 1, 2235 }, +{ 0x4, 73, 1, 2234 }, +{ 0x4, 74, 0, 2233 }, +{ 0x12, 67, 1, 2232 }, +{ 0x42, 68, 1, 2231 }, +{ 0xc, 73, 1, 2230 }, +{ 0xc, 74, 0, 2229 }, +{ 0xa, 67, 1, 2364 }, +{ 0x12, 68, 1, 2363 }, +{ 0x24, 68, 1, 2361 }, +{ 0x5, 75, 1, 2362 }, +{ 0x1d, 41, 1, 26 }, +{ 0xd, 42, 0, 25 }, +{ 0x1a, 67, 1, 2228 }, +{ 0x32, 68, 1, 2227 }, +{ 0x34, 74, 1, 2225 }, +{ 0x7, 85, 0, 2226 }, +{ 0x6, 67, 1, 2524 }, +{ 0x6, 68, 1, 2523 }, +{ 0xc, 68, 1, 2521 }, +{ 0x3, 75, 0, 2522 }, +{ 0x1, 67, 1, 2540 }, +{ 0x1, 68, 1, 2539 }, +{ 0x1, 69, 1, 2538 }, +{ 0x1, 70, 1, 2537 }, +{ 0x1, 71, 1, 2536 }, +{ 0x1, 72, 1, 2535 }, +{ 0x1, 73, 1, 2534 }, +{ 0x1, 74, 0, 2533 }, +{ 0x3, 67, 1, 2532 }, +{ 0x3, 68, 1, 2531 }, +{ 0x3, 69, 1, 2530 }, +{ 0x3, 70, 1, 2529 }, +{ 0x3, 71, 1, 2528 }, +{ 0x3, 72, 1, 2527 }, +{ 0x3, 73, 1, 2526 }, +{ 0x3, 74, 0, 2525 }, +{ 0x28, 95, 1, 2388 }, +{ 0x44, 96, 1, 2383 }, +{ 0x88, 96, 1, 2387 }, +{ 0x44, 97, 1, 2382 }, +{ 0x88, 97, 1, 2386 }, +{ 0x44, 98, 1, 2381 }, +{ 0x88, 98, 1, 2385 }, +{ 0x28, 99, 0, 2384 }, +{ 0x68, 95, 1, 2244 }, +{ 0x188, 96, 1, 2243 }, +{ 0x188, 97, 1, 2242 }, +{ 0x188, 98, 1, 2241 }, +{ 0x38, 118, 1, 2240 }, +{ 0x38, 119, 1, 2239 }, +{ 0x38, 120, 1, 2238 }, +{ 0x38, 121, 0, 2237 }, +{ 0x18, 95, 1, 2548 }, +{ 0x14, 96, 1, 2543 }, +{ 0x28, 96, 1, 2547 }, +{ 0x14, 97, 1, 2542 }, +{ 0x28, 97, 1, 2546 }, +{ 0x14, 98, 1, 2541 }, +{ 0x28, 98, 1, 2545 }, +{ 0x18, 99, 0, 2544 }, +{ 0x14, 95, 1, 2396 }, +{ 0x24, 96, 1, 2395 }, +{ 0x48, 96, 1, 2391 }, +{ 0x24, 97, 1, 2394 }, +{ 0x48, 97, 1, 2390 }, +{ 0x24, 98, 1, 2393 }, +{ 0x48, 98, 1, 2389 }, +{ 0x14, 99, 0, 2392 }, +{ 0x34, 95, 1, 2252 }, +{ 0x64, 96, 1, 2251 }, +{ 0x64, 97, 1, 2250 }, +{ 0x64, 98, 1, 2249 }, +{ 0x1c, 118, 1, 2248 }, +{ 0x1c, 119, 1, 2247 }, +{ 0x1c, 120, 1, 2246 }, +{ 0x1c, 121, 0, 2245 }, +{ 0xc, 95, 1, 2556 }, +{ 0xc, 96, 1, 2555 }, +{ 0x18, 96, 1, 2551 }, +{ 0xc, 97, 1, 2554 }, +{ 0x18, 97, 1, 2550 }, +{ 0xc, 98, 1, 2553 }, +{ 0x18, 98, 1, 2549 }, +{ 0xc, 99, 0, 2552 }, +{ 0xa, 95, 1, 2404 }, +{ 0x11, 96, 1, 2399 }, +{ 0x22, 96, 1, 2403 }, +{ 0x11, 97, 1, 2398 }, +{ 0x22, 97, 1, 2402 }, +{ 0x11, 98, 1, 2397 }, +{ 0x22, 98, 1, 2401 }, +{ 0xa, 99, 0, 2400 }, +{ 0x1a, 95, 1, 2260 }, +{ 0x62, 96, 1, 2259 }, +{ 0x62, 97, 1, 2258 }, +{ 0x62, 98, 1, 2257 }, +{ 0xe, 118, 1, 2256 }, +{ 0xe, 119, 1, 2255 }, +{ 0xe, 120, 1, 2254 }, +{ 0xe, 121, 0, 2253 }, +{ 0x6, 95, 1, 2564 }, +{ 0x5, 96, 1, 2559 }, +{ 0xa, 96, 1, 2563 }, +{ 0x5, 97, 1, 2558 }, +{ 0xa, 97, 1, 2562 }, +{ 0x5, 98, 1, 2557 }, +{ 0xa, 98, 1, 2561 }, +{ 0x6, 99, 0, 2560 }, +{ 0x5, 95, 1, 2412 }, +{ 0x9, 96, 1, 2411 }, +{ 0x12, 96, 1, 2407 }, +{ 0x9, 97, 1, 2410 }, +{ 0x12, 97, 1, 2406 }, +{ 0x9, 98, 1, 2409 }, +{ 0x12, 98, 1, 2405 }, +{ 0x5, 99, 0, 2408 }, +{ 0xd, 95, 1, 2268 }, +{ 0x19, 96, 1, 2267 }, +{ 0x19, 97, 1, 2266 }, +{ 0x19, 98, 1, 2265 }, +{ 0x7, 118, 1, 2264 }, +{ 0x7, 119, 1, 2263 }, +{ 0x7, 120, 1, 2262 }, +{ 0x7, 121, 0, 2261 }, +{ 0x3, 95, 1, 2572 }, +{ 0x3, 96, 1, 2571 }, +{ 0x6, 96, 1, 2567 }, +{ 0x3, 97, 1, 2570 }, +{ 0x6, 97, 1, 2566 }, +{ 0x3, 98, 1, 2569 }, +{ 0x6, 98, 1, 2565 }, +{ 0x3, 99, 0, 2568 }, +{ 0x28, 62, 1, 2420 }, +{ 0x44, 63, 1, 2415 }, +{ 0x88, 63, 1, 2419 }, +{ 0x44, 64, 1, 2414 }, +{ 0x88, 64, 1, 2418 }, +{ 0x44, 65, 1, 2413 }, +{ 0x88, 65, 1, 2417 }, +{ 0x28, 66, 0, 2416 }, +{ 0x68, 62, 1, 2276 }, +{ 0x188, 63, 1, 2275 }, +{ 0x188, 64, 1, 2274 }, +{ 0x188, 65, 1, 2273 }, +{ 0x38, 81, 1, 2272 }, +{ 0x38, 82, 1, 2271 }, +{ 0x38, 83, 1, 2270 }, +{ 0x38, 84, 0, 2269 }, +{ 0x18, 62, 1, 2580 }, +{ 0x14, 63, 1, 2575 }, +{ 0x28, 63, 1, 2579 }, +{ 0x14, 64, 1, 2574 }, +{ 0x28, 64, 1, 2578 }, +{ 0x14, 65, 1, 2573 }, +{ 0x28, 65, 1, 2577 }, +{ 0x18, 66, 0, 2576 }, +{ 0x14, 62, 1, 2428 }, +{ 0x24, 63, 1, 2427 }, +{ 0x48, 63, 1, 2423 }, +{ 0x24, 64, 1, 2426 }, +{ 0x48, 64, 1, 2422 }, +{ 0x24, 65, 1, 2425 }, +{ 0x48, 65, 1, 2421 }, +{ 0x14, 66, 0, 2424 }, +{ 0x34, 62, 1, 2284 }, +{ 0x64, 63, 1, 2283 }, +{ 0x64, 64, 1, 2282 }, +{ 0x64, 65, 1, 2281 }, +{ 0x1c, 81, 1, 2280 }, +{ 0x1c, 82, 1, 2279 }, +{ 0x1c, 83, 1, 2278 }, +{ 0x1c, 84, 0, 2277 }, +{ 0xc, 62, 1, 2588 }, +{ 0xc, 63, 1, 2587 }, +{ 0x18, 63, 1, 2583 }, +{ 0xc, 64, 1, 2586 }, +{ 0x18, 64, 1, 2582 }, +{ 0xc, 65, 1, 2585 }, +{ 0x18, 65, 1, 2581 }, +{ 0xc, 66, 0, 2584 }, +{ 0xa, 62, 1, 2436 }, +{ 0x11, 63, 1, 2431 }, +{ 0x22, 63, 1, 2435 }, +{ 0x11, 64, 1, 2430 }, +{ 0x22, 64, 1, 2434 }, +{ 0x11, 65, 1, 2429 }, +{ 0x22, 65, 1, 2433 }, +{ 0xa, 66, 0, 2432 }, +{ 0x1a, 62, 1, 2292 }, +{ 0x62, 63, 1, 2291 }, +{ 0x62, 64, 1, 2290 }, +{ 0x62, 65, 1, 2289 }, +{ 0xe, 81, 1, 2288 }, +{ 0xe, 82, 1, 2287 }, +{ 0xe, 83, 1, 2286 }, +{ 0xe, 84, 0, 2285 }, +{ 0x6, 62, 1, 2596 }, +{ 0x5, 63, 1, 2591 }, +{ 0xa, 63, 1, 2595 }, +{ 0x5, 64, 1, 2590 }, +{ 0xa, 64, 1, 2594 }, +{ 0x5, 65, 1, 2589 }, +{ 0xa, 65, 1, 2593 }, +{ 0x6, 66, 0, 2592 }, +{ 0x5, 62, 1, 2444 }, +{ 0x9, 63, 1, 2443 }, +{ 0x12, 63, 1, 2439 }, +{ 0x9, 64, 1, 2442 }, +{ 0x12, 64, 1, 2438 }, +{ 0x9, 65, 1, 2441 }, +{ 0x12, 65, 1, 2437 }, +{ 0x5, 66, 0, 2440 }, +{ 0xd, 62, 1, 2300 }, +{ 0x19, 63, 1, 2299 }, +{ 0x19, 64, 1, 2298 }, +{ 0x19, 65, 1, 2297 }, +{ 0x7, 81, 1, 2296 }, +{ 0x7, 82, 1, 2295 }, +{ 0x7, 83, 1, 2294 }, +{ 0x7, 84, 0, 2293 }, +{ 0x3, 62, 1, 2604 }, +{ 0x3, 63, 1, 2603 }, +{ 0x6, 63, 1, 2599 }, +{ 0x3, 64, 1, 2602 }, +{ 0x6, 64, 1, 2598 }, +{ 0x3, 65, 1, 2601 }, +{ 0x6, 65, 1, 2597 }, +{ 0x3, 66, 0, 2600 }, +{ 0x8, 86, 1, 2468 }, +{ 0x8, 87, 1, 2467 }, +{ 0x2, 88, 1, 2466 }, +{ 0x2, 89, 1, 2465 }, +{ 0x2, 90, 1, 2464 }, +{ 0x2, 91, 1, 2463 }, +{ 0x2, 92, 1, 2462 }, +{ 0x2, 93, 0, 2461 }, +{ 0x18, 86, 1, 2460 }, +{ 0x18, 87, 1, 2459 }, +{ 0x6, 88, 1, 2458 }, +{ 0x6, 89, 1, 2457 }, +{ 0x6, 90, 1, 2456 }, +{ 0x6, 91, 1, 2455 }, +{ 0x6, 92, 1, 2454 }, +{ 0x6, 93, 0, 2453 }, +{ 0x14, 86, 1, 2448 }, +{ 0x22, 87, 1, 2445 }, +{ 0x44, 87, 1, 2447 }, +{ 0xa, 94, 0, 2446 }, +{ 0x34, 86, 1, 2304 }, +{ 0xc4, 87, 1, 2303 }, +{ 0x38, 93, 1, 2301 }, +{ 0xe, 117, 0, 2302 }, +{ 0xc, 86, 1, 2608 }, +{ 0xa, 87, 1, 2605 }, +{ 0x14, 87, 1, 2607 }, +{ 0x6, 94, 0, 2606 }, +{ 0x2, 86, 1, 2316 }, +{ 0x2, 87, 1, 2315 }, +{ 0x4, 92, 1, 2314 }, +{ 0x4, 93, 0, 2313 }, +{ 0x12, 86, 1, 2312 }, +{ 0x42, 87, 1, 2311 }, +{ 0xc, 92, 1, 2310 }, +{ 0xc, 93, 0, 2309 }, +{ 0xa, 86, 1, 2452 }, +{ 0x12, 87, 1, 2451 }, +{ 0x24, 87, 1, 2449 }, +{ 0x5, 94, 0, 2450 }, +{ 0x1a, 86, 1, 2308 }, +{ 0x32, 87, 1, 2307 }, +{ 0x34, 93, 1, 2305 }, +{ 0x7, 117, 0, 2306 }, +{ 0x6, 86, 1, 2612 }, +{ 0x6, 87, 1, 2611 }, +{ 0xc, 87, 1, 2609 }, +{ 0x3, 94, 0, 2610 }, +{ 0x1, 86, 1, 2628 }, +{ 0x1, 87, 1, 2627 }, +{ 0x1, 88, 1, 2626 }, +{ 0x1, 89, 1, 2625 }, +{ 0x1, 90, 1, 2624 }, +{ 0x1, 91, 1, 2623 }, +{ 0x1, 92, 1, 2622 }, +{ 0x1, 93, 0, 2621 }, +{ 0x3, 86, 1, 2620 }, +{ 0x3, 87, 1, 2619 }, +{ 0x3, 88, 1, 2618 }, +{ 0x3, 89, 1, 2617 }, +{ 0x3, 90, 1, 2616 }, +{ 0x3, 91, 1, 2615 }, +{ 0x3, 92, 1, 2614 }, +{ 0x3, 93, 0, 2613 }, +{ 0x8, 53, 1, 2492 }, +{ 0x8, 54, 1, 2491 }, +{ 0x2, 55, 1, 2490 }, +{ 0x2, 56, 1, 2489 }, +{ 0x2, 57, 1, 2488 }, +{ 0x2, 58, 1, 2487 }, +{ 0x2, 59, 1, 2486 }, +{ 0x2, 60, 0, 2485 }, +{ 0x18, 53, 1, 2484 }, +{ 0x18, 54, 1, 2483 }, +{ 0x6, 55, 1, 2482 }, +{ 0x6, 56, 1, 2481 }, +{ 0x6, 57, 1, 2480 }, +{ 0x6, 58, 1, 2479 }, +{ 0x6, 59, 1, 2478 }, +{ 0x6, 60, 0, 2477 }, +{ 0x14, 53, 1, 2472 }, +{ 0x22, 54, 1, 2469 }, +{ 0x44, 54, 1, 2471 }, +{ 0xa, 61, 0, 2470 }, +{ 0x34, 53, 1, 2320 }, +{ 0xc4, 54, 1, 2319 }, +{ 0x38, 60, 1, 2317 }, +{ 0xe, 80, 0, 2318 }, +{ 0xc, 53, 1, 2632 }, +{ 0xa, 54, 1, 2629 }, +{ 0x14, 54, 1, 2631 }, +{ 0x6, 61, 0, 2630 }, +{ 0x2, 53, 1, 2332 }, +{ 0x2, 54, 1, 2331 }, +{ 0x4, 59, 1, 2330 }, +{ 0x4, 60, 0, 2329 }, +{ 0x12, 53, 1, 2328 }, +{ 0x42, 54, 1, 2327 }, +{ 0xc, 59, 1, 2326 }, +{ 0xc, 60, 0, 2325 }, +{ 0xa, 53, 1, 2476 }, +{ 0x12, 54, 1, 2475 }, +{ 0x24, 54, 1, 2473 }, +{ 0x5, 61, 0, 2474 }, +{ 0x1a, 53, 1, 2324 }, +{ 0x32, 54, 1, 2323 }, +{ 0x34, 60, 1, 2321 }, +{ 0x7, 80, 0, 2322 }, +{ 0x6, 53, 1, 2636 }, +{ 0x6, 54, 1, 2635 }, +{ 0xc, 54, 1, 2633 }, +{ 0x3, 61, 0, 2634 }, +{ 0x1, 53, 1, 2652 }, +{ 0x1, 54, 1, 2651 }, +{ 0x1, 55, 1, 2650 }, +{ 0x1, 56, 1, 2649 }, +{ 0x1, 57, 1, 2648 }, +{ 0x1, 58, 1, 2647 }, +{ 0x1, 59, 1, 2646 }, +{ 0x1, 60, 0, 2645 }, +{ 0x3, 53, 1, 2644 }, +{ 0x3, 54, 1, 2643 }, +{ 0x3, 55, 1, 2642 }, +{ 0x3, 56, 1, 2641 }, +{ 0x3, 57, 1, 2640 }, +{ 0x3, 58, 1, 2639 }, +{ 0x3, 59, 1, 2638 }, +{ 0x3, 60, 0, 2637 }, +{ 0x1, 4, 0, 2653 }, +{ 0x1, 296, 0, 2654 }, +{ 0x1, 379, 0, 2655 }, +{ 0x1, 374, 0, 2656 }, +{ 0x2, 358, 0, 2657 }, +{ 0x1, 358, 0, 2660 }, +{ 0x2, 357, 0, 2658 }, +{ 0x1, 357, 0, 2661 }, +{ 0x2, 356, 0, 2659 }, +{ 0x1, 356, 0, 2662 }, +{ 0x1, 355, 0, 2663 }, +{ 0x1, 354, 0, 2664 }, +{ 0x2, 353, 0, 2665 }, +{ 0x1, 353, 0, 2667 }, +{ 0x2, 352, 0, 2666 }, +{ 0x1, 352, 0, 2668 }, +{ 0x1, 382, 0, 2675 }, +{ 0x8, 381, 0, 2669 }, +{ 0x4, 381, 0, 2671 }, +{ 0x2, 381, 0, 2673 }, +{ 0x1, 381, 0, 2676 }, +{ 0x8, 380, 0, 2670 }, +{ 0x4, 380, 0, 2672 }, +{ 0x2, 380, 0, 2674 }, +{ 0x1, 380, 0, 2677 }, +{ 0x1, 351, 0, 2684 }, +{ 0x8, 350, 0, 2678 }, +{ 0x4, 350, 0, 2680 }, +{ 0x2, 350, 0, 2682 }, +{ 0x1, 350, 0, 2685 }, +{ 0x8, 349, 0, 2679 }, +{ 0x4, 349, 0, 2681 }, +{ 0x2, 349, 1, 2683 }, +{ 0x4, 143, 0, 1377 }, +{ 0x1, 349, 0, 2686 }, +{ 0x1, 6, 0, 2687 }, +{ 0x1, 7, 0, 2688 }, +{ 0x1, 295, 0, 2689 }, +{ 0x1, 456, 0, 2690 }, +{ 0x1, 346, 0, 2691 }, +{ 0x1, 13, 0, 2692 }, +{ 0x1, 11, 0, 2693 }, +{ 0x1, 422, 0, 2694 }, +{ 0x1, 394, 0, 2695 }, +{ 0x1, 393, 0, 2696 }, +{ 0x1, 455, 0, 2697 }, +{ 0x1, 345, 0, 2698 }, +{ 0x1, 12, 0, 2699 }, +{ 0x1, 10, 0, 2700 }, +{ 0x1, 5, 0, 2701 }, +{ 0x1, 421, 0, 2702 }, +{ 0x1, 420, 0, 2703 }, +{ 0x1, 1, 0, 2704 }, +{ 0x1, 0, 0, 2705 }, +}; + + +/* ia64-opc.c -- Functions to access the compacted opcode table + Copyright 1999, 2000, 2001, 2003, 2005 Free Software Foundation, Inc. + Written by Bob Manson of Cygnus Solutions, + + This file is part of GDB, GAS, and the GNU binutils. + + GDB, GAS, and the GNU binutils are free software; you can redistribute + them and/or modify them 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. + + GDB, GAS, and the GNU binutils are distributed in the hope that they + 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 file; see the file COPYING. If not, see + . */ + +static const struct ia64_templ_desc ia64_templ_desc[16] = + { + { 0, { IA64_UNIT_M, IA64_UNIT_I, IA64_UNIT_I }, "MII" }, /* 0 */ + { 2, { IA64_UNIT_M, IA64_UNIT_I, IA64_UNIT_I }, "MII" }, + { 0, { IA64_UNIT_M, IA64_UNIT_L, IA64_UNIT_X }, "MLX" }, + { 0, { 0, }, "-3-" }, + { 0, { IA64_UNIT_M, IA64_UNIT_M, IA64_UNIT_I }, "MMI" }, /* 4 */ + { 1, { IA64_UNIT_M, IA64_UNIT_M, IA64_UNIT_I }, "MMI" }, + { 0, { IA64_UNIT_M, IA64_UNIT_F, IA64_UNIT_I }, "MFI" }, + { 0, { IA64_UNIT_M, IA64_UNIT_M, IA64_UNIT_F }, "MMF" }, + { 0, { IA64_UNIT_M, IA64_UNIT_I, IA64_UNIT_B }, "MIB" }, /* 8 */ + { 0, { IA64_UNIT_M, IA64_UNIT_B, IA64_UNIT_B }, "MBB" }, + { 0, { 0, }, "-a-" }, + { 0, { IA64_UNIT_B, IA64_UNIT_B, IA64_UNIT_B }, "BBB" }, + { 0, { IA64_UNIT_M, IA64_UNIT_M, IA64_UNIT_B }, "MMB" }, /* c */ + { 0, { 0, }, "-d-" }, + { 0, { IA64_UNIT_M, IA64_UNIT_F, IA64_UNIT_B }, "MFB" }, + { 0, { 0, }, "-f-" }, + }; + +/* Apply the completer referred to by COMPLETER_INDEX to OPCODE, and + return the result. */ + +static ia64_insn +apply_completer (ia64_insn opcode, int completer_index) +{ + ia64_insn mask = completer_table[completer_index].mask; + ia64_insn bits = completer_table[completer_index].bits; + int shiftamt = (completer_table[completer_index].offset & 63); + + mask = mask << shiftamt; + bits = bits << shiftamt; + opcode = (opcode & ~mask) | bits; + return opcode; +} + +/* Extract BITS number of bits starting from OP_POINTER + BITOFFSET in + the dis_table array, and return its value. (BITOFFSET is numbered + starting from MSB to LSB, so a BITOFFSET of 0 indicates the MSB of the + first byte in OP_POINTER.) */ + +static int +extract_op_bits (int op_pointer, int bitoffset, int bits) +{ + int res = 0; + + op_pointer += (bitoffset / 8); + + if (bitoffset % 8) + { + unsigned int op = dis_table[op_pointer++]; + int numb = 8 - (bitoffset % 8); + int mask = (1 << numb) - 1; + int bata = (bits < numb) ? bits : numb; + int delta = numb - bata; + + res = (res << bata) | ((op & mask) >> delta); + bitoffset += bata; + bits -= bata; + } + while (bits >= 8) + { + res = (res << 8) | (dis_table[op_pointer++] & 255); + bits -= 8; + } + if (bits > 0) + { + unsigned int op = (dis_table[op_pointer++] & 255); + res = (res << bits) | (op >> (8 - bits)); + } + return res; +} + +/* Examine the state machine entry at OP_POINTER in the dis_table + array, and extract its values into OPVAL and OP. The length of the + state entry in bits is returned. */ + +static int +extract_op (int op_pointer, int *opval, unsigned int *op) +{ + int oplen = 5; + + *op = dis_table[op_pointer]; + + if ((*op) & 0x40) + { + opval[0] = extract_op_bits (op_pointer, oplen, 5); + oplen += 5; + } + switch ((*op) & 0x30) + { + case 0x10: + { + opval[1] = extract_op_bits (op_pointer, oplen, 8); + oplen += 8; + opval[1] += op_pointer; + break; + } + case 0x20: + { + opval[1] = extract_op_bits (op_pointer, oplen, 16); + if (! (opval[1] & 32768)) + { + opval[1] += op_pointer; + } + oplen += 16; + break; + } + case 0x30: + { + oplen--; + opval[2] = extract_op_bits (op_pointer, oplen, 12); + oplen += 12; + opval[2] |= 32768; + break; + } + } + if (((*op) & 0x08) && (((*op) & 0x30) != 0x30)) + { + opval[2] = extract_op_bits (op_pointer, oplen, 16); + oplen += 16; + if (! (opval[2] & 32768)) + { + opval[2] += op_pointer; + } + } + return oplen; +} + +/* Returns a non-zero value if the opcode in the main_table list at + PLACE matches OPCODE and is of type TYPE. */ + +static int +opcode_verify (ia64_insn opcode, int place, enum ia64_insn_type type) +{ + if (main_table[place].opcode_type != type) + { + return 0; + } + if (main_table[place].flags + & (IA64_OPCODE_F2_EQ_F3 | IA64_OPCODE_LEN_EQ_64MCNT)) + { + const struct ia64_operand *o1, *o2; + ia64_insn f2, f3; + + if (main_table[place].flags & IA64_OPCODE_F2_EQ_F3) + { + o1 = elf64_ia64_operands + IA64_OPND_F2; + o2 = elf64_ia64_operands + IA64_OPND_F3; + (*o1->extract) (o1, opcode, &f2); + (*o2->extract) (o2, opcode, &f3); + if (f2 != f3) + return 0; + } + else + { + ia64_insn len, count; + + /* length must equal 64-count: */ + o1 = elf64_ia64_operands + IA64_OPND_LEN6; + o2 = elf64_ia64_operands + main_table[place].operands[2]; + (*o1->extract) (o1, opcode, &len); + (*o2->extract) (o2, opcode, &count); + if (len != 64 - count) + return 0; + } + } + return 1; +} + +/* Find an instruction entry in the ia64_dis_names array that matches + opcode OPCODE and is of type TYPE. Returns either a positive index + into the array, or a negative value if an entry for OPCODE could + not be found. Checks all matches and returns the one with the highest + priority. */ + +static int +locate_opcode_ent (ia64_insn opcode, enum ia64_insn_type type) +{ + int currtest[41]; + int bitpos[41]; + int op_ptr[41]; + int currstatenum = 0; + short found_disent = -1; + short found_priority = -1; + + currtest[currstatenum] = 0; + op_ptr[currstatenum] = 0; + bitpos[currstatenum] = 40; + + while (1) + { + int op_pointer = op_ptr[currstatenum]; + unsigned int op; + int currbitnum = bitpos[currstatenum]; + int oplen; + int opval[3] = {0}; + int next_op; + int currbit; + + oplen = extract_op (op_pointer, opval, &op); + + bitpos[currstatenum] = currbitnum; + + /* Skip opval[0] bits in the instruction. */ + if (op & 0x40) + { + currbitnum -= opval[0]; + } + + /* The value of the current bit being tested. */ + currbit = opcode & (((ia64_insn) 1) << currbitnum) ? 1 : 0; + next_op = -1; + + /* We always perform the tests specified in the current state in + a particular order, falling through to the next test if the + previous one failed. */ + switch (currtest[currstatenum]) + { + case 0: + currtest[currstatenum]++; + if (currbit == 0 && (op & 0x80)) + { + /* Check for a zero bit. If this test solely checks for + a zero bit, we can check for up to 8 consecutive zero + bits (the number to check is specified by the lower 3 + bits in the state code.) + + If the state instruction matches, we go to the very + next state instruction; otherwise, try the next test. */ + + if ((op & 0xf8) == 0x80) + { + int count = op & 0x7; + int x; + + for (x = 0; x <= count; x++) + { + int i = + opcode & (((ia64_insn) 1) << (currbitnum - x)) ? 1 : 0; + if (i) + { + break; + } + } + if (x > count) + { + next_op = op_pointer + ((oplen + 7) / 8); + currbitnum -= count; + break; + } + } + else if (! currbit) + { + next_op = op_pointer + ((oplen + 7) / 8); + break; + } + } + /* FALLTHROUGH */ + case 1: + /* If the bit in the instruction is one, go to the state + instruction specified by opval[1]. */ + currtest[currstatenum]++; + if (currbit && (op & 0x30) != 0 && ((op & 0x30) != 0x30)) + { + next_op = opval[1]; + break; + } + /* FALLTHROUGH */ + case 2: + /* Don't care. Skip the current bit and go to the state + instruction specified by opval[2]. + + An encoding of 0x30 is special; this means that a 12-bit + offset into the ia64_dis_names[] array is specified. */ + currtest[currstatenum]++; + if ((op & 0x08) || ((op & 0x30) == 0x30)) + { + next_op = opval[2]; + break; + } + } + + /* If bit 15 is set in the address of the next state, an offset + in the ia64_dis_names array was specified instead. We then + check to see if an entry in the list of opcodes matches the + opcode we were given; if so, we have succeeded. */ + + if ((next_op >= 0) && (next_op & 32768)) + { + short disent = next_op & 32767; + short priority = -1; + + if (next_op > 65535) + { + abort (); + } + + /* Run through the list of opcodes to check, trying to find + one that matches. */ + while (disent >= 0) + { + int place = ia64_dis_names[disent].insn_index; + + priority = ia64_dis_names[disent].priority; + + if (opcode_verify (opcode, place, type) + && priority > found_priority) + { + break; + } + if (ia64_dis_names[disent].next_flag) + { + disent++; + } + else + { + disent = -1; + } + } + + if (disent >= 0) + { + found_disent = disent; + found_priority = priority; + } + /* Try the next test in this state, regardless of whether a match + was found. */ + next_op = -2; + } + + /* next_op == -1 is "back up to the previous state". + next_op == -2 is "stay in this state and try the next test". + Otherwise, transition to the state indicated by next_op. */ + + if (next_op == -1) + { + currstatenum--; + if (currstatenum < 0) + { + return found_disent; + } + } + else if (next_op >= 0) + { + currstatenum++; + bitpos[currstatenum] = currbitnum - 1; + op_ptr[currstatenum] = next_op; + currtest[currstatenum] = 0; + } + } +} + +/* Construct an ia64_opcode entry based on OPCODE, NAME and PLACE. */ + +static struct ia64_opcode * +make_ia64_opcode (ia64_insn opcode, const char *name, int place, int depind) +{ + struct ia64_opcode *res = + (struct ia64_opcode *) malloc (sizeof (struct ia64_opcode)); + res->name = strdup (name); + res->type = main_table[place].opcode_type; + res->num_outputs = main_table[place].num_outputs; + res->opcode = opcode; + res->mask = main_table[place].mask; + res->operands[0] = main_table[place].operands[0]; + res->operands[1] = main_table[place].operands[1]; + res->operands[2] = main_table[place].operands[2]; + res->operands[3] = main_table[place].operands[3]; + res->operands[4] = main_table[place].operands[4]; + res->flags = main_table[place].flags; + res->ent_index = place; + res->dependencies = &op_dependencies[depind]; + return res; +} + +/* Determine the ia64_opcode entry for the opcode specified by INSN + and TYPE. If a valid entry is not found, return NULL. */ +static struct ia64_opcode * +ia64_dis_opcode (ia64_insn insn, enum ia64_insn_type type) +{ + int disent = locate_opcode_ent (insn, type); + + if (disent < 0) + { + return NULL; + } + else + { + unsigned int cb = ia64_dis_names[disent].completer_index; + static char name[128]; + int place = ia64_dis_names[disent].insn_index; + int ci = main_table[place].completers; + ia64_insn tinsn = main_table[place].opcode; + + strcpy (name, ia64_strings [main_table[place].name_index]); + + while (cb) + { + if (cb & 1) + { + int cname = completer_table[ci].name_index; + + tinsn = apply_completer (tinsn, ci); + + if (ia64_strings[cname][0] != '\0') + { + strcat (name, "."); + strcat (name, ia64_strings[cname]); + } + if (cb != 1) + { + ci = completer_table[ci].subentries; + } + } + else + { + ci = completer_table[ci].alternative; + } + if (ci < 0) + { + abort (); + } + cb = cb >> 1; + } + if (tinsn != (insn & main_table[place].mask)) + { + abort (); + } + return make_ia64_opcode (insn, name, place, + completer_table[ci].dependencies); + } +} + +/* Free any resources used by ENT. */ +static void +ia64_free_opcode (struct ia64_opcode *ent) +{ + free ((void *)ent->name); + free (ent); +} + +/* Disassemble ia64 instruction. */ + +/* Return the instruction type for OPCODE found in unit UNIT. */ + +static enum ia64_insn_type +unit_to_type (ia64_insn opcode, enum ia64_unit unit) +{ + enum ia64_insn_type type; + int op; + + op = IA64_OP (opcode); + + if (op >= 8 && (unit == IA64_UNIT_I || unit == IA64_UNIT_M)) + { + type = IA64_TYPE_A; + } + else + { + switch (unit) + { + case IA64_UNIT_I: + type = IA64_TYPE_I; break; + case IA64_UNIT_M: + type = IA64_TYPE_M; break; + case IA64_UNIT_B: + type = IA64_TYPE_B; break; + case IA64_UNIT_F: + type = IA64_TYPE_F; break; + case IA64_UNIT_L: + case IA64_UNIT_X: + type = IA64_TYPE_X; break; + default: + type = -1; + } + } + return type; +} + +int +print_insn_ia64 (bfd_vma memaddr, struct disassemble_info *info) +{ + ia64_insn t0, t1, slot[3], template, s_bit, insn; + int slotnum, j, status, need_comma, retval, slot_multiplier; + const struct ia64_operand *odesc; + const struct ia64_opcode *idesc; + const char *err, *str, *tname; + uint64_t value; + bfd_byte bundle[16]; + enum ia64_unit unit; + char regname[16]; + + if (info->bytes_per_line == 0) + info->bytes_per_line = 6; + info->display_endian = info->endian; + + slot_multiplier = info->bytes_per_line; + retval = slot_multiplier; + + slotnum = (((long) memaddr) & 0xf) / slot_multiplier; + if (slotnum > 2) + return -1; + + memaddr -= (memaddr & 0xf); + status = (*info->read_memory_func) (memaddr, bundle, sizeof (bundle), info); + if (status != 0) + { + (*info->memory_error_func) (status, memaddr, info); + return -1; + } + /* bundles are always in little-endian byte order */ + t0 = bfd_getl64 (bundle); + t1 = bfd_getl64 (bundle + 8); + s_bit = t0 & 1; + template = (t0 >> 1) & 0xf; + slot[0] = (t0 >> 5) & 0x1ffffffffffLL; + slot[1] = ((t0 >> 46) & 0x3ffff) | ((t1 & 0x7fffff) << 18); + slot[2] = (t1 >> 23) & 0x1ffffffffffLL; + + tname = ia64_templ_desc[template].name; + if (slotnum == 0) + (*info->fprintf_func) (info->stream, "[%s] ", tname); + else + (*info->fprintf_func) (info->stream, " "); + + unit = ia64_templ_desc[template].exec_unit[slotnum]; + + if (template == 2 && slotnum == 1) + { + /* skip L slot in MLI template: */ + slotnum = 2; + retval += slot_multiplier; + } + + insn = slot[slotnum]; + + if (unit == IA64_UNIT_NIL) + goto decoding_failed; + + idesc = ia64_dis_opcode (insn, unit_to_type (insn, unit)); + if (idesc == NULL) + goto decoding_failed; + + /* print predicate, if any: */ + + if ((idesc->flags & IA64_OPCODE_NO_PRED) + || (insn & 0x3f) == 0) + (*info->fprintf_func) (info->stream, " "); + else + (*info->fprintf_func) (info->stream, "(p%02d) ", (int)(insn & 0x3f)); + + /* now the actual instruction: */ + + (*info->fprintf_func) (info->stream, "%s", idesc->name); + if (idesc->operands[0]) + (*info->fprintf_func) (info->stream, " "); + + need_comma = 0; + for (j = 0; j < NELEMS (idesc->operands) && idesc->operands[j]; ++j) + { + odesc = elf64_ia64_operands + idesc->operands[j]; + + if (need_comma) + (*info->fprintf_func) (info->stream, ","); + + if (odesc - elf64_ia64_operands == IA64_OPND_IMMU64) + { + /* special case of 64 bit immediate load: */ + value = ((insn >> 13) & 0x7f) | (((insn >> 27) & 0x1ff) << 7) + | (((insn >> 22) & 0x1f) << 16) | (((insn >> 21) & 0x1) << 21) + | (slot[1] << 22) | (((insn >> 36) & 0x1) << 63); + } + else if (odesc - elf64_ia64_operands == IA64_OPND_IMMU62) + { + /* 62-bit immediate for nop.x/break.x */ + value = ((slot[1] & 0x1ffffffffffLL) << 21) + | (((insn >> 36) & 0x1) << 20) + | ((insn >> 6) & 0xfffff); + } + else if (odesc - elf64_ia64_operands == IA64_OPND_TGT64) + { + /* 60-bit immediate for long branches. */ + value = (((insn >> 13) & 0xfffff) + | (((insn >> 36) & 1) << 59) + | (((slot[1] >> 2) & 0x7fffffffffLL) << 20)) << 4; + } + else + { + err = (*odesc->extract) (odesc, insn, &value); + if (err) + { + (*info->fprintf_func) (info->stream, "%s", err); + goto done; + } + } + + switch (odesc->class) + { + case IA64_OPND_CLASS_CST: + (*info->fprintf_func) (info->stream, "%s", odesc->str); + break; + + case IA64_OPND_CLASS_REG: + if (odesc->str[0] == 'a' && odesc->str[1] == 'r') + { + switch (value) + { + case 0: case 1: case 2: case 3: + case 4: case 5: case 6: case 7: + sprintf (regname, "ar.k%u", (unsigned int) value); + break; + case 16: strcpy (regname, "ar.rsc"); break; + case 17: strcpy (regname, "ar.bsp"); break; + case 18: strcpy (regname, "ar.bspstore"); break; + case 19: strcpy (regname, "ar.rnat"); break; + case 32: strcpy (regname, "ar.ccv"); break; + case 36: strcpy (regname, "ar.unat"); break; + case 40: strcpy (regname, "ar.fpsr"); break; + case 44: strcpy (regname, "ar.itc"); break; + case 64: strcpy (regname, "ar.pfs"); break; + case 65: strcpy (regname, "ar.lc"); break; + case 66: strcpy (regname, "ar.ec"); break; + default: + sprintf (regname, "ar%u", (unsigned int) value); + break; + } + (*info->fprintf_func) (info->stream, "%s", regname); + } + else + (*info->fprintf_func) (info->stream, "%s%d", odesc->str, (int)value); + break; + + case IA64_OPND_CLASS_IND: + (*info->fprintf_func) (info->stream, "%s[r%d]", odesc->str, (int)value); + break; + + case IA64_OPND_CLASS_ABS: + str = 0; + if (odesc - elf64_ia64_operands == IA64_OPND_MBTYPE4) + switch (value) + { + case 0x0: str = "@brcst"; break; + case 0x8: str = "@mix"; break; + case 0x9: str = "@shuf"; break; + case 0xa: str = "@alt"; break; + case 0xb: str = "@rev"; break; + } + + if (str) + (*info->fprintf_func) (info->stream, "%s", str); + else if (odesc->flags & IA64_OPND_FLAG_DECIMAL_SIGNED) + (*info->fprintf_func) (info->stream, "%" PRId64, + (int64_t) value); + else if (odesc->flags & IA64_OPND_FLAG_DECIMAL_UNSIGNED) + (*info->fprintf_func) (info->stream, "%" PRIu64, + (uint64_t) value); + else + (*info->fprintf_func) (info->stream, "0x%" PRIx64, + (uint64_t) value); + break; + + case IA64_OPND_CLASS_REL: + (*info->print_address_func) (memaddr + value, info); + break; + } + + need_comma = 1; + if (j + 1 == idesc->num_outputs) + { + (*info->fprintf_func) (info->stream, "="); + need_comma = 0; + } + } + if (slotnum + 1 == ia64_templ_desc[template].group_boundary + || ((slotnum == 2) && s_bit)) + (*info->fprintf_func) (info->stream, ";;"); + + done: + ia64_free_opcode ((struct ia64_opcode *)idesc); + failed: + if (slotnum == 2) + retval += 16 - 3*slot_multiplier; + return retval; + + decoding_failed: + (*info->fprintf_func) (info->stream, " data8 %#011llx", (long long) insn); + goto failed; +} diff --git a/ia64.ld b/ia64.ld new file mode 100644 index 0000000..0c37796 --- /dev/null +++ b/ia64.ld @@ -0,0 +1,209 @@ +/* Default linker script, for normal executables */ +OUTPUT_FORMAT("elf64-ia64-little", "elf64-ia64-little", + "elf64-ia64-little") +OUTPUT_ARCH(ia64) +ENTRY(_start) +/* __DYNAMIC = 0; */ +SECTIONS +{ + /* Read-only sections, merged into text segment: */ + PROVIDE (__executable_start = 0x60000000); . = 0x60000000 + SIZEOF_HEADERS; + .interp : { *(.interp) } + .hash : { *(.hash) } + .dynsym : { *(.dynsym) } + .dynstr : { *(.dynstr) } + .gnu.version : { *(.gnu.version) } + .gnu.version_d : { *(.gnu.version_d) } + .gnu.version_r : { *(.gnu.version_r) } + .rel.init : { *(.rel.init) } + .rela.init : { *(.rela.init) } + .rel.text : { *(.rel.text .rel.text.* .rel.gnu.linkonce.t.*) } + .rela.text : { *(.rela.text .rela.text.* .rela.gnu.linkonce.t.*) } + .rel.fini : { *(.rel.fini) } + .rela.fini : { *(.rela.fini) } + .rel.rodata : { *(.rel.rodata .rel.rodata.* .rel.gnu.linkonce.r.*) } + .rela.rodata : { *(.rela.rodata .rela.rodata.* .rela.gnu.linkonce.r.*) } + .rel.data : { *(.rel.data .rel.data.* .rel.gnu.linkonce.d.*) } + .rela.data : { *(.rela.data .rela.data.* .rela.gnu.linkonce.d.*) } + .rel.tdata : { *(.rel.tdata .rel.tdata.* .rel.gnu.linkonce.td.*) } + .rela.tdata : { *(.rela.tdata .rela.tdata.* .rela.gnu.linkonce.td.*) } + .rel.tbss : { *(.rel.tbss .rel.tbss.* .rel.gnu.linkonce.tb.*) } + .rela.tbss : { *(.rela.tbss .rela.tbss.* .rela.gnu.linkonce.tb.*) } + .rel.ctors : { *(.rel.ctors) } + .rela.ctors : { *(.rela.ctors) } + .rel.dtors : { *(.rel.dtors) } + .rela.dtors : { *(.rela.dtors) } + .rel.got : { *(.rel.got) } + .rela.got : { *(.rela.got) } + .rel.sdata : { *(.rel.sdata .rel.sdata.* .rel.gnu.linkonce.s.*) } + .rela.sdata : { *(.rela.sdata .rela.sdata.* .rela.gnu.linkonce.s.*) } + .rel.sbss : { *(.rel.sbss .rel.sbss.* .rel.gnu.linkonce.sb.*) } + .rela.sbss : { *(.rela.sbss .rela.sbss.* .rela.gnu.linkonce.sb.*) } + .rel.sdata2 : { *(.rel.sdata2 .rel.sdata2.* .rel.gnu.linkonce.s2.*) } + .rela.sdata2 : { *(.rela.sdata2 .rela.sdata2.* .rela.gnu.linkonce.s2.*) } + .rel.sbss2 : { *(.rel.sbss2 .rel.sbss2.* .rel.gnu.linkonce.sb2.*) } + .rela.sbss2 : { *(.rela.sbss2 .rela.sbss2.* .rela.gnu.linkonce.sb2.*) } + .rel.bss : { *(.rel.bss .rel.bss.* .rel.gnu.linkonce.b.*) } + .rela.bss : { *(.rela.bss .rela.bss.* .rela.gnu.linkonce.b.*) } + .rel.plt : { *(.rel.plt) } + .rela.plt : { *(.rela.plt) } + .rela.IA_64.pltoff : { *(.rela.IA_64.pltoff) } + .init : + { + KEEP (*(.init)) + } =0x00300000010070000002000001000400 + .plt : { *(.plt) } + .text : + { + *(.text .stub .text.* .gnu.linkonce.t.*) + /* .gnu.warning sections are handled specially by elf32.em. */ + *(.gnu.warning) + } =0x00300000010070000002000001000400 + .fini : + { + KEEP (*(.fini)) + } =0x00300000010070000002000001000400 + PROVIDE (__etext = .); + PROVIDE (_etext = .); + PROVIDE (etext = .); + .rodata : { *(.rodata .rodata.* .gnu.linkonce.r.*) } + .rodata1 : { *(.rodata1) } + .sdata2 : { *(.sdata2 .sdata2.* .gnu.linkonce.s2.*) } + .sbss2 : { *(.sbss2 .sbss2.* .gnu.linkonce.sb2.*) } + .opd : { *(.opd) } + .IA_64.unwind_info : { *(.IA_64.unwind_info* .gnu.linkonce.ia64unwi.*) } + .IA_64.unwind : { *(.IA_64.unwind* .gnu.linkonce.ia64unw.*) } + .eh_frame_hdr : { *(.eh_frame_hdr) } + /* Adjust the address for the data segment. We want to adjust up to + the same address within the page on the next page up. */ + . = ALIGN(0x10000) + (. & (0x10000 - 1)); + /* Ensure the __preinit_array_start label is properly aligned. We + could instead move the label definition inside the section, but + the linker would then create the section even if it turns out to + be empty, which isn't pretty. */ + . = ALIGN(64 / 8); + PROVIDE (__preinit_array_start = .); + .preinit_array : { *(.preinit_array) } + PROVIDE (__preinit_array_end = .); + PROVIDE (__init_array_start = .); + .init_array : { *(.init_array) } + PROVIDE (__init_array_end = .); + PROVIDE (__fini_array_start = .); + .fini_array : { *(.fini_array) } + PROVIDE (__fini_array_end = .); + .data : + { + *(.data .data.* .gnu.linkonce.d.*) + SORT(CONSTRUCTORS) + } + .data1 : { *(.data1) } + .tdata : { *(.tdata .tdata.* .gnu.linkonce.td.*) } + .tbss : { *(.tbss .tbss.* .gnu.linkonce.tb.*) *(.tcommon) } + .eh_frame : { KEEP (*(.eh_frame)) } + .gcc_except_table : { *(.gcc_except_table) } + .dynamic : { *(.dynamic) } + .ctors : + { + /* gcc uses crtbegin.o to find the start of + the constructors, so we make sure it is + first. Because this is a wildcard, it + doesn't matter if the user does not + actually link against crtbegin.o; the + linker won't look for a file to match a + wildcard. The wildcard also means that it + doesn't matter which directory crtbegin.o + is in. */ + KEEP (*crtbegin*.o(.ctors)) + /* We don't want to include the .ctor section from + from the crtend.o file until after the sorted ctors. + The .ctor section from the crtend file contains the + end of ctors marker and it must be last */ + KEEP (*(EXCLUDE_FILE (*crtend*.o ) .ctors)) + KEEP (*(SORT(.ctors.*))) + KEEP (*(.ctors)) + } + .dtors : + { + KEEP (*crtbegin*.o(.dtors)) + KEEP (*(EXCLUDE_FILE (*crtend*.o ) .dtors)) + KEEP (*(SORT(.dtors.*))) + KEEP (*(.dtors)) + } + .jcr : { KEEP (*(.jcr)) } + /* Ensure __gp is outside the range of any normal data. We need to + do this to avoid the linker optimizing the code in op.o and getting + it out of sync with the relocs that we read when processing that + file. A better solution might be to ensure that the dynamically + generated code and static qemu code share a single gp-value. */ + __gp = . + 0x200000; + .got : { *(.got.plt) *(.got) } + .IA_64.pltoff : { *(.IA_64.pltoff) } + /* We want the small data sections together, so single-instruction offsets + can access them all, and initialized data all before uninitialized, so + we can shorten the on-disk segment size. */ + .sdata : + { + *(.sdata .sdata.* .gnu.linkonce.s.*) + } + _edata = .; + PROVIDE (edata = .); + __bss_start = .; + .sbss : + { + PROVIDE (__sbss_start = .); + PROVIDE (___sbss_start = .); + *(.dynsbss) + *(.sbss .sbss.* .gnu.linkonce.sb.*) + *(.scommon) + PROVIDE (__sbss_end = .); + PROVIDE (___sbss_end = .); + } + .bss : + { + . += 0x400000; /* ensure .bss stuff is out of reach of gp */ + *(.dynbss) + *(.bss .bss.* .gnu.linkonce.b.*) + *(COMMON) + /* Align here to ensure that the .bss section occupies space up to + _end. Align after .bss to ensure correct alignment even if the + .bss section disappears because there are no input sections. */ + . = ALIGN(64 / 8); + } + . = ALIGN(64 / 8); + _end = .; + PROVIDE (end = .); + /* Stabs debugging sections. */ + .stab 0 : { *(.stab) } + .stabstr 0 : { *(.stabstr) } + .stab.excl 0 : { *(.stab.excl) } + .stab.exclstr 0 : { *(.stab.exclstr) } + .stab.index 0 : { *(.stab.index) } + .stab.indexstr 0 : { *(.stab.indexstr) } + .comment 0 : { *(.comment) } + /* DWARF debug sections. + Symbols in the DWARF debugging sections are relative to the beginning + of the section so we begin them at 0. */ + /* DWARF 1 */ + .debug 0 : { *(.debug) } + .line 0 : { *(.line) } + /* GNU DWARF 1 extensions */ + .debug_srcinfo 0 : { *(.debug_srcinfo) } + .debug_sfnames 0 : { *(.debug_sfnames) } + /* DWARF 1.1 and DWARF 2 */ + .debug_aranges 0 : { *(.debug_aranges) } + .debug_pubnames 0 : { *(.debug_pubnames) } + /* DWARF 2 */ + .debug_info 0 : { *(.debug_info .gnu.linkonce.wi.*) } + .debug_abbrev 0 : { *(.debug_abbrev) } + .debug_line 0 : { *(.debug_line) } + .debug_frame 0 : { *(.debug_frame) } + .debug_str 0 : { *(.debug_str) } + .debug_loc 0 : { *(.debug_loc) } + .debug_macinfo 0 : { *(.debug_macinfo) } + /* SGI/MIPS DWARF 2 extensions */ + .debug_weaknames 0 : { *(.debug_weaknames) } + .debug_funcnames 0 : { *(.debug_funcnames) } + .debug_typenames 0 : { *(.debug_typenames) } + .debug_varnames 0 : { *(.debug_varnames) } + /DISCARD/ : { *(.note.GNU-stack) } +} diff --git a/input.c b/input.c new file mode 100644 index 0000000..ec05548 --- /dev/null +++ b/input.c @@ -0,0 +1,288 @@ +/* + * QEMU System Emulator + * + * Copyright (c) 2003-2008 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.h" +#include "net.h" +#include "monitor.h" +#include "console.h" +#include "qjson.h" + +static QEMUPutKBDEvent *qemu_put_kbd_event; +static void *qemu_put_kbd_event_opaque; +static QTAILQ_HEAD(, QEMUPutLEDEntry) led_handlers = QTAILQ_HEAD_INITIALIZER(led_handlers); +static QTAILQ_HEAD(, QEMUPutMouseEntry) mouse_handlers = + QTAILQ_HEAD_INITIALIZER(mouse_handlers); +static NotifierList mouse_mode_notifiers = + NOTIFIER_LIST_INITIALIZER(mouse_mode_notifiers); + +void qemu_add_kbd_event_handler(QEMUPutKBDEvent *func, void *opaque) +{ + qemu_put_kbd_event_opaque = opaque; + qemu_put_kbd_event = func; +} + +void qemu_remove_kbd_event_handler(void) +{ + qemu_put_kbd_event_opaque = NULL; + qemu_put_kbd_event = NULL; +} + +static void check_mode_change(void) +{ + static int current_is_absolute, current_has_absolute; + int is_absolute; + int has_absolute; + + is_absolute = kbd_mouse_is_absolute(); + has_absolute = kbd_mouse_has_absolute(); + + if (is_absolute != current_is_absolute || + has_absolute != current_has_absolute) { + notifier_list_notify(&mouse_mode_notifiers); + } + + current_is_absolute = is_absolute; + current_has_absolute = has_absolute; +} + +QEMUPutMouseEntry *qemu_add_mouse_event_handler(QEMUPutMouseEvent *func, + void *opaque, int absolute, + const char *name) +{ + QEMUPutMouseEntry *s; + static int mouse_index = 0; + + s = qemu_mallocz(sizeof(QEMUPutMouseEntry)); + + s->qemu_put_mouse_event = func; + s->qemu_put_mouse_event_opaque = opaque; + s->qemu_put_mouse_event_absolute = absolute; + s->qemu_put_mouse_event_name = qemu_strdup(name); + s->index = mouse_index++; + + QTAILQ_INSERT_TAIL(&mouse_handlers, s, node); + + check_mode_change(); + + return s; +} + +void qemu_activate_mouse_event_handler(QEMUPutMouseEntry *entry) +{ + QTAILQ_REMOVE(&mouse_handlers, entry, node); + QTAILQ_INSERT_HEAD(&mouse_handlers, entry, node); + + check_mode_change(); +} + +void qemu_remove_mouse_event_handler(QEMUPutMouseEntry *entry) +{ + QTAILQ_REMOVE(&mouse_handlers, entry, node); + + qemu_free(entry->qemu_put_mouse_event_name); + qemu_free(entry); + + check_mode_change(); +} + +QEMUPutLEDEntry *qemu_add_led_event_handler(QEMUPutLEDEvent *func, + void *opaque) +{ + QEMUPutLEDEntry *s; + + s = qemu_mallocz(sizeof(QEMUPutLEDEntry)); + + s->put_led = func; + s->opaque = opaque; + QTAILQ_INSERT_TAIL(&led_handlers, s, next); + return s; +} + +void qemu_remove_led_event_handler(QEMUPutLEDEntry *entry) +{ + if (entry == NULL) + return; + QTAILQ_REMOVE(&led_handlers, entry, next); + qemu_free(entry); +} + +void kbd_put_keycode(int keycode) +{ + if (qemu_put_kbd_event) { + qemu_put_kbd_event(qemu_put_kbd_event_opaque, keycode); + } +} + +void kbd_put_ledstate(int ledstate) +{ + QEMUPutLEDEntry *cursor; + + QTAILQ_FOREACH(cursor, &led_handlers, next) { + cursor->put_led(cursor->opaque, ledstate); + } +} + +void kbd_mouse_event(int dx, int dy, int dz, int buttons_state) +{ + QEMUPutMouseEntry *entry; + QEMUPutMouseEvent *mouse_event; + void *mouse_event_opaque; + int width; + + if (QTAILQ_EMPTY(&mouse_handlers)) { + return; + } + + entry = QTAILQ_FIRST(&mouse_handlers); + + mouse_event = entry->qemu_put_mouse_event; + mouse_event_opaque = entry->qemu_put_mouse_event_opaque; + + if (mouse_event) { + if (graphic_rotate) { + if (entry->qemu_put_mouse_event_absolute) + width = 0x7fff; + else + width = graphic_width - 1; + mouse_event(mouse_event_opaque, + width - dy, dx, dz, buttons_state); + } else + mouse_event(mouse_event_opaque, + dx, dy, dz, buttons_state); + } +} + +int kbd_mouse_is_absolute(void) +{ + if (QTAILQ_EMPTY(&mouse_handlers)) { + return 0; + } + + return QTAILQ_FIRST(&mouse_handlers)->qemu_put_mouse_event_absolute; +} + +int kbd_mouse_has_absolute(void) +{ + QEMUPutMouseEntry *entry; + + QTAILQ_FOREACH(entry, &mouse_handlers, node) { + if (entry->qemu_put_mouse_event_absolute) { + return 1; + } + } + + return 0; +} + +static void info_mice_iter(QObject *data, void *opaque) +{ + QDict *mouse; + Monitor *mon = opaque; + + mouse = qobject_to_qdict(data); + monitor_printf(mon, "%c Mouse #%" PRId64 ": %s%s\n", + (qdict_get_bool(mouse, "current") ? '*' : ' '), + qdict_get_int(mouse, "index"), qdict_get_str(mouse, "name"), + qdict_get_bool(mouse, "absolute") ? " (absolute)" : ""); +} + +void do_info_mice_print(Monitor *mon, const QObject *data) +{ + QList *mice_list; + + mice_list = qobject_to_qlist(data); + if (qlist_empty(mice_list)) { + monitor_printf(mon, "No mouse devices connected\n"); + return; + } + + qlist_iter(mice_list, info_mice_iter, mon); +} + +void do_info_mice(Monitor *mon, QObject **ret_data) +{ + QEMUPutMouseEntry *cursor; + QList *mice_list; + int current; + + mice_list = qlist_new(); + + if (QTAILQ_EMPTY(&mouse_handlers)) { + goto out; + } + + current = QTAILQ_FIRST(&mouse_handlers)->index; + + QTAILQ_FOREACH(cursor, &mouse_handlers, node) { + QObject *obj; + obj = qobject_from_jsonf("{ 'name': %s," + " 'index': %d," + " 'current': %i," + " 'absolute': %i }", + cursor->qemu_put_mouse_event_name, + cursor->index, + cursor->index == current, + !!cursor->qemu_put_mouse_event_absolute); + qlist_append_obj(mice_list, obj); + } + +out: + *ret_data = QOBJECT(mice_list); +} + +void do_mouse_set(Monitor *mon, const QDict *qdict) +{ + QEMUPutMouseEntry *cursor; + int index = qdict_get_int(qdict, "index"); + int found = 0; + + if (QTAILQ_EMPTY(&mouse_handlers)) { + monitor_printf(mon, "No mouse devices connected\n"); + return; + } + + QTAILQ_FOREACH(cursor, &mouse_handlers, node) { + if (cursor->index == index) { + found = 1; + qemu_activate_mouse_event_handler(cursor); + break; + } + } + + if (!found) { + monitor_printf(mon, "Mouse at given index not found\n"); + } + + check_mode_change(); +} + +void qemu_add_mouse_mode_change_notifier(Notifier *notify) +{ + notifier_list_add(&mouse_mode_notifiers, notify); +} + +void qemu_remove_mouse_mode_change_notifier(Notifier *notify) +{ + notifier_list_remove(&mouse_mode_notifiers, notify); +} diff --git a/ioport-user.c b/ioport-user.c new file mode 100644 index 0000000..03fac22 --- /dev/null +++ b/ioport-user.c @@ -0,0 +1,60 @@ +/* + * qemu user ioport functions + * + * Copyright (c) 2003-2008 Fabrice Bellard + * + * 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 . + */ + +#include + +#include "qemu.h" +#include "qemu-common.h" +#include "ioport.h" + +void cpu_outb(pio_addr_t addr, uint8_t val) +{ + fprintf(stderr, "outb: port=0x%04"FMT_pioaddr", data=%02"PRIx8"\n", + addr, val); +} + +void cpu_outw(pio_addr_t addr, uint16_t val) +{ + fprintf(stderr, "outw: port=0x%04"FMT_pioaddr", data=%04"PRIx16"\n", + addr, val); +} + +void cpu_outl(pio_addr_t addr, uint32_t val) +{ + fprintf(stderr, "outl: port=0x%04"FMT_pioaddr", data=%08"PRIx32"\n", + addr, val); +} + +uint8_t cpu_inb(pio_addr_t addr) +{ + fprintf(stderr, "inb: port=0x%04"FMT_pioaddr"\n", addr); + return 0; +} + +uint16_t cpu_inw(pio_addr_t addr) +{ + fprintf(stderr, "inw: port=0x%04"FMT_pioaddr"\n", addr); + return 0; +} + +uint32_t cpu_inl(pio_addr_t addr) +{ + fprintf(stderr, "inl: port=0x%04"FMT_pioaddr"\n", addr); + return 0; +} diff --git a/ioport.c b/ioport.c new file mode 100644 index 0000000..aa4188a --- /dev/null +++ b/ioport.c @@ -0,0 +1,306 @@ +/* + * QEMU System Emulator + * + * Copyright (c) 2003-2008 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. + */ +/* + * splitted out ioport related stuffs from vl.c. + */ + +#include "ioport.h" +#include "trace.h" + +/***********************************************************/ +/* IO Port */ + +//#define DEBUG_UNUSED_IOPORT +//#define DEBUG_IOPORT + +#ifdef DEBUG_UNUSED_IOPORT +# define LOG_UNUSED_IOPORT(fmt, ...) fprintf(stderr, fmt, ## __VA_ARGS__) +#else +# define LOG_UNUSED_IOPORT(fmt, ...) do{ } while (0) +#endif + +#ifdef DEBUG_IOPORT +# define LOG_IOPORT(...) qemu_log_mask(CPU_LOG_IOPORT, ## __VA_ARGS__) +#else +# define LOG_IOPORT(...) do { } while (0) +#endif + +/* XXX: use a two level table to limit memory usage */ + +static void *ioport_opaque[MAX_IOPORTS]; +static IOPortReadFunc *ioport_read_table[3][MAX_IOPORTS]; +static IOPortWriteFunc *ioport_write_table[3][MAX_IOPORTS]; + +static IOPortReadFunc default_ioport_readb, default_ioport_readw, default_ioport_readl; +static IOPortWriteFunc default_ioport_writeb, default_ioport_writew, default_ioport_writel; + +static uint32_t ioport_read(int index, uint32_t address) +{ + static IOPortReadFunc * const default_func[3] = { + default_ioport_readb, + default_ioport_readw, + default_ioport_readl + }; + IOPortReadFunc *func = ioport_read_table[index][address]; + if (!func) + func = default_func[index]; + return func(ioport_opaque[address], address); +} + +static void ioport_write(int index, uint32_t address, uint32_t data) +{ + static IOPortWriteFunc * const default_func[3] = { + default_ioport_writeb, + default_ioport_writew, + default_ioport_writel + }; + IOPortWriteFunc *func = ioport_write_table[index][address]; + if (!func) + func = default_func[index]; + func(ioport_opaque[address], address, data); +} + +static uint32_t default_ioport_readb(void *opaque, uint32_t address) +{ + LOG_UNUSED_IOPORT("unused inb: port=0x%04"PRIx32"\n", address); + return 0xff; +} + +static void default_ioport_writeb(void *opaque, uint32_t address, uint32_t data) +{ + LOG_UNUSED_IOPORT("unused outb: port=0x%04"PRIx32" data=0x%02"PRIx32"\n", + address, data); +} + +/* default is to make two byte accesses */ +static uint32_t default_ioport_readw(void *opaque, uint32_t address) +{ + uint32_t data; + data = ioport_read(0, address); + address = (address + 1) & IOPORTS_MASK; + data |= ioport_read(0, address) << 8; + return data; +} + +static void default_ioport_writew(void *opaque, uint32_t address, uint32_t data) +{ + ioport_write(0, address, data & 0xff); + address = (address + 1) & IOPORTS_MASK; + ioport_write(0, address, (data >> 8) & 0xff); +} + +static uint32_t default_ioport_readl(void *opaque, uint32_t address) +{ + LOG_UNUSED_IOPORT("unused inl: port=0x%04"PRIx32"\n", address); + return 0xffffffff; +} + +static void default_ioport_writel(void *opaque, uint32_t address, uint32_t data) +{ + LOG_UNUSED_IOPORT("unused outl: port=0x%04"PRIx32" data=0x%02"PRIx32"\n", + address, data); +} + +static int ioport_bsize(int size, int *bsize) +{ + if (size == 1) { + *bsize = 0; + } else if (size == 2) { + *bsize = 1; + } else if (size == 4) { + *bsize = 2; + } else { + return -1; + } + return 0; +} + +/* size is the word size in byte */ +int register_ioport_read(pio_addr_t start, int length, int size, + IOPortReadFunc *func, void *opaque) +{ + int i, bsize; + + if (ioport_bsize(size, &bsize)) { + hw_error("register_ioport_read: invalid size"); + return -1; + } + for(i = start; i < start + length; i += size) { + ioport_read_table[bsize][i] = func; + if (ioport_opaque[i] != NULL && ioport_opaque[i] != opaque) + hw_error("register_ioport_read: invalid opaque"); + ioport_opaque[i] = opaque; + } + return 0; +} + +/* size is the word size in byte */ +int register_ioport_write(pio_addr_t start, int length, int size, + IOPortWriteFunc *func, void *opaque) +{ + int i, bsize; + + if (ioport_bsize(size, &bsize)) { + hw_error("register_ioport_write: invalid size"); + return -1; + } + for(i = start; i < start + length; i += size) { + ioport_write_table[bsize][i] = func; + if (ioport_opaque[i] != NULL && ioport_opaque[i] != opaque) + hw_error("register_ioport_write: invalid opaque"); + ioport_opaque[i] = opaque; + } + return 0; +} + +static uint32_t ioport_readb_thunk(void *opaque, uint32_t addr) +{ + IORange *ioport = opaque; + uint64_t data; + + ioport->ops->read(ioport, addr - ioport->base, 1, &data); + return data; +} + +static uint32_t ioport_readw_thunk(void *opaque, uint32_t addr) +{ + IORange *ioport = opaque; + uint64_t data; + + ioport->ops->read(ioport, addr - ioport->base, 2, &data); + return data; +} + +static uint32_t ioport_readl_thunk(void *opaque, uint32_t addr) +{ + IORange *ioport = opaque; + uint64_t data; + + ioport->ops->read(ioport, addr - ioport->base, 4, &data); + return data; +} + +static void ioport_writeb_thunk(void *opaque, uint32_t addr, uint32_t data) +{ + IORange *ioport = opaque; + + ioport->ops->write(ioport, addr - ioport->base, 1, data); +} + +static void ioport_writew_thunk(void *opaque, uint32_t addr, uint32_t data) +{ + IORange *ioport = opaque; + + ioport->ops->write(ioport, addr - ioport->base, 2, data); +} + +static void ioport_writel_thunk(void *opaque, uint32_t addr, uint32_t data) +{ + IORange *ioport = opaque; + + ioport->ops->write(ioport, addr - ioport->base, 4, data); +} + +void ioport_register(IORange *ioport) +{ + register_ioport_read(ioport->base, ioport->len, 1, + ioport_readb_thunk, ioport); + register_ioport_read(ioport->base, ioport->len, 2, + ioport_readw_thunk, ioport); + register_ioport_read(ioport->base, ioport->len, 4, + ioport_readl_thunk, ioport); + register_ioport_write(ioport->base, ioport->len, 1, + ioport_writeb_thunk, ioport); + register_ioport_write(ioport->base, ioport->len, 2, + ioport_writew_thunk, ioport); + register_ioport_write(ioport->base, ioport->len, 4, + ioport_writel_thunk, ioport); +} + +void isa_unassign_ioport(pio_addr_t start, int length) +{ + int i; + + for(i = start; i < start + length; i++) { + ioport_read_table[0][i] = default_ioport_readb; + ioport_read_table[1][i] = default_ioport_readw; + ioport_read_table[2][i] = default_ioport_readl; + + ioport_write_table[0][i] = default_ioport_writeb; + ioport_write_table[1][i] = default_ioport_writew; + ioport_write_table[2][i] = default_ioport_writel; + + ioport_opaque[i] = NULL; + } +} + +/***********************************************************/ + +void cpu_outb(pio_addr_t addr, uint8_t val) +{ + LOG_IOPORT("outb: %04"FMT_pioaddr" %02"PRIx8"\n", addr, val); + trace_cpu_out(addr, val); + ioport_write(0, addr, val); +} + +void cpu_outw(pio_addr_t addr, uint16_t val) +{ + LOG_IOPORT("outw: %04"FMT_pioaddr" %04"PRIx16"\n", addr, val); + trace_cpu_out(addr, val); + ioport_write(1, addr, val); +} + +void cpu_outl(pio_addr_t addr, uint32_t val) +{ + LOG_IOPORT("outl: %04"FMT_pioaddr" %08"PRIx32"\n", addr, val); + trace_cpu_out(addr, val); + ioport_write(2, addr, val); +} + +uint8_t cpu_inb(pio_addr_t addr) +{ + uint8_t val; + val = ioport_read(0, addr); + trace_cpu_in(addr, val); + LOG_IOPORT("inb : %04"FMT_pioaddr" %02"PRIx8"\n", addr, val); + return val; +} + +uint16_t cpu_inw(pio_addr_t addr) +{ + uint16_t val; + val = ioport_read(1, addr); + trace_cpu_in(addr, val); + LOG_IOPORT("inw : %04"FMT_pioaddr" %04"PRIx16"\n", addr, val); + return val; +} + +uint32_t cpu_inl(pio_addr_t addr) +{ + uint32_t val; + val = ioport_read(2, addr); + trace_cpu_in(addr, val); + LOG_IOPORT("inl : %04"FMT_pioaddr" %08"PRIx32"\n", addr, val); + return val; +} diff --git a/ioport.h b/ioport.h new file mode 100644 index 0000000..5ae62a3 --- /dev/null +++ b/ioport.h @@ -0,0 +1,55 @@ +/* + * defines ioport related functions + * + * Copyright (c) 2003 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 . + */ + +/************************************************************************** + * IO ports API + */ + +#ifndef IOPORT_H +#define IOPORT_H + +#include "qemu-common.h" +#include "iorange.h" + +typedef uint32_t pio_addr_t; +#define FMT_pioaddr PRIx32 + +#define MAX_IOPORTS (64 * 1024) +#define IOPORTS_MASK (MAX_IOPORTS - 1) + +/* These should really be in isa.h, but are here to make pc.h happy. */ +typedef void (IOPortWriteFunc)(void *opaque, uint32_t address, uint32_t data); +typedef uint32_t (IOPortReadFunc)(void *opaque, uint32_t address); + +void ioport_register(IORange *iorange); +int register_ioport_read(pio_addr_t start, int length, int size, + IOPortReadFunc *func, void *opaque); +int register_ioport_write(pio_addr_t start, int length, int size, + IOPortWriteFunc *func, void *opaque); +void isa_unassign_ioport(pio_addr_t start, int length); + + +void cpu_outb(pio_addr_t addr, uint8_t val); +void cpu_outw(pio_addr_t addr, uint16_t val); +void cpu_outl(pio_addr_t addr, uint32_t val); +uint8_t cpu_inb(pio_addr_t addr); +uint16_t cpu_inw(pio_addr_t addr); +uint32_t cpu_inl(pio_addr_t addr); + +#endif /* IOPORT_H */ diff --git a/iorange.h b/iorange.h new file mode 100644 index 0000000..9783168 --- /dev/null +++ b/iorange.h @@ -0,0 +1,30 @@ +#ifndef IORANGE_H +#define IORANGE_H + +#include + +typedef struct IORange IORange; +typedef struct IORangeOps IORangeOps; + +struct IORangeOps { + void (*read)(IORange *iorange, uint64_t offset, unsigned width, + uint64_t *data); + void (*write)(IORange *iorange, uint64_t offset, unsigned width, + uint64_t data); +}; + +struct IORange { + const IORangeOps *ops; + uint64_t base; + uint64_t len; +}; + +static inline void iorange_init(IORange *iorange, const IORangeOps *ops, + uint64_t base, uint64_t len) +{ + iorange->ops = ops; + iorange->base = base; + iorange->len = len; +} + +#endif diff --git a/iov.c b/iov.c new file mode 100644 index 0000000..588cd04 --- /dev/null +++ b/iov.c @@ -0,0 +1,70 @@ +/* + * Helpers for getting linearized buffers from iov / filling buffers into iovs + * + * Copyright IBM, Corp. 2007, 2008 + * Copyright (C) 2010 Red Hat, Inc. + * + * Author(s): + * Anthony Liguori + * Amit Shah + * + * This work is licensed under the terms of the GNU GPL, version 2. See + * the COPYING file in the top-level directory. + */ + +#include "iov.h" + +size_t iov_from_buf(struct iovec *iov, unsigned int iovcnt, + const void *buf, size_t size) +{ + size_t offset; + unsigned int i; + + offset = 0; + for (i = 0; offset < size && i < iovcnt; i++) { + size_t len; + + len = MIN(iov[i].iov_len, size - offset); + + memcpy(iov[i].iov_base, buf + offset, len); + offset += len; + } + return offset; +} + +size_t iov_to_buf(const struct iovec *iov, const unsigned int iovcnt, + void *buf, size_t offset, size_t size) +{ + uint8_t *ptr; + size_t iov_off, buf_off; + unsigned int i; + + ptr = buf; + iov_off = 0; + buf_off = 0; + for (i = 0; i < iovcnt && size; i++) { + if (offset < (iov_off + iov[i].iov_len)) { + size_t len = MIN((iov_off + iov[i].iov_len) - offset , size); + + memcpy(ptr + buf_off, iov[i].iov_base + (offset - iov_off), len); + + buf_off += len; + offset += len; + size -= len; + } + iov_off += iov[i].iov_len; + } + return buf_off; +} + +size_t iov_size(const struct iovec *iov, const unsigned int iovcnt) +{ + size_t len; + unsigned int i; + + len = 0; + for (i = 0; i < iovcnt; i++) { + len += iov[i].iov_len; + } + return len; +} diff --git a/iov.h b/iov.h new file mode 100644 index 0000000..60a8547 --- /dev/null +++ b/iov.h @@ -0,0 +1,19 @@ +/* + * Helpers for getting linearized buffers from iov / filling buffers into iovs + * + * Copyright (C) 2010 Red Hat, Inc. + * + * Author(s): + * Amit Shah + * + * 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" + +size_t iov_from_buf(struct iovec *iov, unsigned int iovcnt, + const void *buf, size_t size); +size_t iov_to_buf(const struct iovec *iov, const unsigned int iovcnt, + void *buf, size_t offset, size_t size); +size_t iov_size(const struct iovec *iov, const unsigned int iovcnt); diff --git a/json-lexer.c b/json-lexer.c new file mode 100644 index 0000000..c736f42 --- /dev/null +++ b/json-lexer.c @@ -0,0 +1,339 @@ +/* + * JSON lexer + * + * Copyright IBM, Corp. 2009 + * + * Authors: + * Anthony Liguori + * + * This work is licensed under the terms of the GNU LGPL, version 2.1 or later. + * See the COPYING.LIB file in the top-level directory. + * + */ + +#include "qstring.h" +#include "qlist.h" +#include "qdict.h" +#include "qint.h" +#include "qemu-common.h" +#include "json-lexer.h" + +/* + * \"([^\\\"]|(\\\"\\'\\\\\\/\\b\\f\\n\\r\\t\\u[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]))*\" + * '([^\\']|(\\\"\\'\\\\\\/\\b\\f\\n\\r\\t\\u[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]))*' + * 0|([1-9][0-9]*(.[0-9]+)?([eE]([-+])?[0-9]+)) + * [{}\[\],:] + * [a-z]+ + * + */ + +enum json_lexer_state { + ERROR = 0, + IN_DQ_UCODE3, + IN_DQ_UCODE2, + IN_DQ_UCODE1, + IN_DQ_UCODE0, + IN_DQ_STRING_ESCAPE, + IN_DQ_STRING, + IN_SQ_UCODE3, + IN_SQ_UCODE2, + IN_SQ_UCODE1, + IN_SQ_UCODE0, + IN_SQ_STRING_ESCAPE, + IN_SQ_STRING, + IN_ZERO, + IN_DIGITS, + IN_DIGIT, + IN_EXP_E, + IN_MANTISSA, + IN_MANTISSA_DIGITS, + IN_NONZERO_NUMBER, + IN_NEG_NONZERO_NUMBER, + IN_KEYWORD, + IN_ESCAPE, + IN_ESCAPE_L, + IN_ESCAPE_LL, + IN_ESCAPE_I, + IN_ESCAPE_I6, + IN_ESCAPE_I64, + IN_WHITESPACE, + IN_START, +}; + +#define TERMINAL(state) [0 ... 0x7F] = (state) + +/* Return whether TERMINAL is a terminal state and the transition to it + from OLD_STATE required lookahead. This happens whenever the table + below uses the TERMINAL macro. */ +#define TERMINAL_NEEDED_LOOKAHEAD(old_state, terminal) \ + (json_lexer[(old_state)][0] == (terminal)) + +static const uint8_t json_lexer[][256] = { + /* double quote string */ + [IN_DQ_UCODE3] = { + ['0' ... '9'] = IN_DQ_STRING, + ['a' ... 'f'] = IN_DQ_STRING, + ['A' ... 'F'] = IN_DQ_STRING, + }, + [IN_DQ_UCODE2] = { + ['0' ... '9'] = IN_DQ_UCODE3, + ['a' ... 'f'] = IN_DQ_UCODE3, + ['A' ... 'F'] = IN_DQ_UCODE3, + }, + [IN_DQ_UCODE1] = { + ['0' ... '9'] = IN_DQ_UCODE2, + ['a' ... 'f'] = IN_DQ_UCODE2, + ['A' ... 'F'] = IN_DQ_UCODE2, + }, + [IN_DQ_UCODE0] = { + ['0' ... '9'] = IN_DQ_UCODE1, + ['a' ... 'f'] = IN_DQ_UCODE1, + ['A' ... 'F'] = IN_DQ_UCODE1, + }, + [IN_DQ_STRING_ESCAPE] = { + ['b'] = IN_DQ_STRING, + ['f'] = IN_DQ_STRING, + ['n'] = IN_DQ_STRING, + ['r'] = IN_DQ_STRING, + ['t'] = IN_DQ_STRING, + ['/'] = IN_DQ_STRING, + ['\\'] = IN_DQ_STRING, + ['\''] = IN_DQ_STRING, + ['\"'] = IN_DQ_STRING, + ['u'] = IN_DQ_UCODE0, + }, + [IN_DQ_STRING] = { + [1 ... 0xFF] = IN_DQ_STRING, + ['\\'] = IN_DQ_STRING_ESCAPE, + ['"'] = JSON_STRING, + }, + + /* single quote string */ + [IN_SQ_UCODE3] = { + ['0' ... '9'] = IN_SQ_STRING, + ['a' ... 'f'] = IN_SQ_STRING, + ['A' ... 'F'] = IN_SQ_STRING, + }, + [IN_SQ_UCODE2] = { + ['0' ... '9'] = IN_SQ_UCODE3, + ['a' ... 'f'] = IN_SQ_UCODE3, + ['A' ... 'F'] = IN_SQ_UCODE3, + }, + [IN_SQ_UCODE1] = { + ['0' ... '9'] = IN_SQ_UCODE2, + ['a' ... 'f'] = IN_SQ_UCODE2, + ['A' ... 'F'] = IN_SQ_UCODE2, + }, + [IN_SQ_UCODE0] = { + ['0' ... '9'] = IN_SQ_UCODE1, + ['a' ... 'f'] = IN_SQ_UCODE1, + ['A' ... 'F'] = IN_SQ_UCODE1, + }, + [IN_SQ_STRING_ESCAPE] = { + ['b'] = IN_SQ_STRING, + ['f'] = IN_SQ_STRING, + ['n'] = IN_SQ_STRING, + ['r'] = IN_SQ_STRING, + ['t'] = IN_SQ_STRING, + ['/'] = IN_DQ_STRING, + ['\\'] = IN_DQ_STRING, + ['\''] = IN_SQ_STRING, + ['\"'] = IN_SQ_STRING, + ['u'] = IN_SQ_UCODE0, + }, + [IN_SQ_STRING] = { + [1 ... 0xFF] = IN_SQ_STRING, + ['\\'] = IN_SQ_STRING_ESCAPE, + ['\''] = JSON_STRING, + }, + + /* Zero */ + [IN_ZERO] = { + TERMINAL(JSON_INTEGER), + ['0' ... '9'] = ERROR, + ['.'] = IN_MANTISSA, + }, + + /* Float */ + [IN_DIGITS] = { + TERMINAL(JSON_FLOAT), + ['0' ... '9'] = IN_DIGITS, + }, + + [IN_DIGIT] = { + ['0' ... '9'] = IN_DIGITS, + }, + + [IN_EXP_E] = { + ['-'] = IN_DIGIT, + ['+'] = IN_DIGIT, + ['0' ... '9'] = IN_DIGITS, + }, + + [IN_MANTISSA_DIGITS] = { + TERMINAL(JSON_FLOAT), + ['0' ... '9'] = IN_MANTISSA_DIGITS, + ['e'] = IN_EXP_E, + ['E'] = IN_EXP_E, + }, + + [IN_MANTISSA] = { + ['0' ... '9'] = IN_MANTISSA_DIGITS, + }, + + /* Number */ + [IN_NONZERO_NUMBER] = { + TERMINAL(JSON_INTEGER), + ['0' ... '9'] = IN_NONZERO_NUMBER, + ['e'] = IN_EXP_E, + ['E'] = IN_EXP_E, + ['.'] = IN_MANTISSA, + }, + + [IN_NEG_NONZERO_NUMBER] = { + ['0'] = IN_ZERO, + ['1' ... '9'] = IN_NONZERO_NUMBER, + }, + + /* keywords */ + [IN_KEYWORD] = { + TERMINAL(JSON_KEYWORD), + ['a' ... 'z'] = IN_KEYWORD, + }, + + /* whitespace */ + [IN_WHITESPACE] = { + TERMINAL(JSON_SKIP), + [' '] = IN_WHITESPACE, + ['\t'] = IN_WHITESPACE, + ['\r'] = IN_WHITESPACE, + ['\n'] = IN_WHITESPACE, + }, + + /* escape */ + [IN_ESCAPE_LL] = { + ['d'] = JSON_ESCAPE, + }, + + [IN_ESCAPE_L] = { + ['d'] = JSON_ESCAPE, + ['l'] = IN_ESCAPE_LL, + }, + + [IN_ESCAPE_I64] = { + ['d'] = JSON_ESCAPE, + }, + + [IN_ESCAPE_I6] = { + ['4'] = IN_ESCAPE_I64, + }, + + [IN_ESCAPE_I] = { + ['6'] = IN_ESCAPE_I6, + }, + + [IN_ESCAPE] = { + ['d'] = JSON_ESCAPE, + ['i'] = JSON_ESCAPE, + ['p'] = JSON_ESCAPE, + ['s'] = JSON_ESCAPE, + ['f'] = JSON_ESCAPE, + ['l'] = IN_ESCAPE_L, + ['I'] = IN_ESCAPE_I, + }, + + /* top level rule */ + [IN_START] = { + ['"'] = IN_DQ_STRING, + ['\''] = IN_SQ_STRING, + ['0'] = IN_ZERO, + ['1' ... '9'] = IN_NONZERO_NUMBER, + ['-'] = IN_NEG_NONZERO_NUMBER, + ['{'] = JSON_OPERATOR, + ['}'] = JSON_OPERATOR, + ['['] = JSON_OPERATOR, + [']'] = JSON_OPERATOR, + [','] = JSON_OPERATOR, + [':'] = JSON_OPERATOR, + ['a' ... 'z'] = IN_KEYWORD, + ['%'] = IN_ESCAPE, + [' '] = IN_WHITESPACE, + ['\t'] = IN_WHITESPACE, + ['\r'] = IN_WHITESPACE, + ['\n'] = IN_WHITESPACE, + }, +}; + +void json_lexer_init(JSONLexer *lexer, JSONLexerEmitter func) +{ + lexer->emit = func; + lexer->state = IN_START; + lexer->token = qstring_new(); + lexer->x = lexer->y = 0; +} + +static int json_lexer_feed_char(JSONLexer *lexer, char ch) +{ + int char_consumed, new_state; + + lexer->x++; + if (ch == '\n') { + lexer->x = 0; + lexer->y++; + } + + do { + new_state = json_lexer[lexer->state][(uint8_t)ch]; + char_consumed = !TERMINAL_NEEDED_LOOKAHEAD(lexer->state, new_state); + if (char_consumed) { + qstring_append_chr(lexer->token, ch); + } + + switch (new_state) { + case JSON_OPERATOR: + case JSON_ESCAPE: + case JSON_INTEGER: + case JSON_FLOAT: + case JSON_KEYWORD: + case JSON_STRING: + lexer->emit(lexer, lexer->token, new_state, lexer->x, lexer->y); + case JSON_SKIP: + QDECREF(lexer->token); + lexer->token = qstring_new(); + new_state = IN_START; + break; + case ERROR: + return -EINVAL; + default: + break; + } + lexer->state = new_state; + } while (!char_consumed); + return 0; +} + +int json_lexer_feed(JSONLexer *lexer, const char *buffer, size_t size) +{ + size_t i; + + for (i = 0; i < size; i++) { + int err; + + err = json_lexer_feed_char(lexer, buffer[i]); + if (err < 0) { + return err; + } + } + + return 0; +} + +int json_lexer_flush(JSONLexer *lexer) +{ + return lexer->state == IN_START ? 0 : json_lexer_feed_char(lexer, 0); +} + +void json_lexer_destroy(JSONLexer *lexer) +{ + QDECREF(lexer->token); +} diff --git a/json-lexer.h b/json-lexer.h new file mode 100644 index 0000000..3b50c46 --- /dev/null +++ b/json-lexer.h @@ -0,0 +1,50 @@ +/* + * JSON lexer + * + * Copyright IBM, Corp. 2009 + * + * Authors: + * Anthony Liguori + * + * This work is licensed under the terms of the GNU LGPL, version 2.1 or later. + * See the COPYING.LIB file in the top-level directory. + * + */ + +#ifndef QEMU_JSON_LEXER_H +#define QEMU_JSON_LEXER_H + +#include "qstring.h" +#include "qlist.h" + +typedef enum json_token_type { + JSON_OPERATOR = 100, + JSON_INTEGER, + JSON_FLOAT, + JSON_KEYWORD, + JSON_STRING, + JSON_ESCAPE, + JSON_SKIP, +} JSONTokenType; + +typedef struct JSONLexer JSONLexer; + +typedef void (JSONLexerEmitter)(JSONLexer *, QString *, JSONTokenType, int x, int y); + +struct JSONLexer +{ + JSONLexerEmitter *emit; + int state; + QString *token; + int x, y; +}; + +void json_lexer_init(JSONLexer *lexer, JSONLexerEmitter func); + +int json_lexer_feed(JSONLexer *lexer, const char *buffer, size_t size); + +int json_lexer_flush(JSONLexer *lexer); + +void json_lexer_destroy(JSONLexer *lexer); + +#endif diff --git a/json-parser.c b/json-parser.c new file mode 100644 index 0000000..6c06ef9 --- /dev/null +++ b/json-parser.c @@ -0,0 +1,577 @@ +/* + * JSON Parser + * + * Copyright IBM, Corp. 2009 + * + * Authors: + * Anthony Liguori + * + * This work is licensed under the terms of the GNU LGPL, version 2.1 or later. + * See the COPYING.LIB file in the top-level directory. + * + */ + +#include + +#include "qemu-common.h" +#include "qstring.h" +#include "qint.h" +#include "qdict.h" +#include "qlist.h" +#include "qfloat.h" +#include "qbool.h" +#include "json-parser.h" +#include "json-lexer.h" + +typedef struct JSONParserContext +{ +} JSONParserContext; + +#define BUG_ON(cond) assert(!(cond)) + +/** + * TODO + * + * 0) make errors meaningful again + * 1) add geometry information to tokens + * 3) should we return a parsed size? + * 4) deal with premature EOI + */ + +static QObject *parse_value(JSONParserContext *ctxt, QList **tokens, va_list *ap); + +/** + * Token manipulators + * + * tokens are dictionaries that contain a type, a string value, and geometry information + * about a token identified by the lexer. These are routines that make working with + * these objects a bit easier. + */ +static const char *token_get_value(QObject *obj) +{ + return qdict_get_str(qobject_to_qdict(obj), "token"); +} + +static JSONTokenType token_get_type(QObject *obj) +{ + return qdict_get_int(qobject_to_qdict(obj), "type"); +} + +static int token_is_operator(QObject *obj, char op) +{ + const char *val; + + if (token_get_type(obj) != JSON_OPERATOR) { + return 0; + } + + val = token_get_value(obj); + + return (val[0] == op) && (val[1] == 0); +} + +static int token_is_keyword(QObject *obj, const char *value) +{ + if (token_get_type(obj) != JSON_KEYWORD) { + return 0; + } + + return strcmp(token_get_value(obj), value) == 0; +} + +static int token_is_escape(QObject *obj, const char *value) +{ + if (token_get_type(obj) != JSON_ESCAPE) { + return 0; + } + + return (strcmp(token_get_value(obj), value) == 0); +} + +/** + * Error handler + */ +static void GCC_FMT_ATTR(3, 4) parse_error(JSONParserContext *ctxt, + QObject *token, const char *msg, ...) +{ + va_list ap; + va_start(ap, msg); + fprintf(stderr, "parse error: "); + vfprintf(stderr, msg, ap); + fprintf(stderr, "\n"); + va_end(ap); +} + +/** + * String helpers + * + * These helpers are used to unescape strings. + */ +static void wchar_to_utf8(uint16_t wchar, char *buffer, size_t buffer_length) +{ + if (wchar <= 0x007F) { + BUG_ON(buffer_length < 2); + + buffer[0] = wchar & 0x7F; + buffer[1] = 0; + } else if (wchar <= 0x07FF) { + BUG_ON(buffer_length < 3); + + buffer[0] = 0xC0 | ((wchar >> 6) & 0x1F); + buffer[1] = 0x80 | (wchar & 0x3F); + buffer[2] = 0; + } else { + BUG_ON(buffer_length < 4); + + buffer[0] = 0xE0 | ((wchar >> 12) & 0x0F); + buffer[1] = 0x80 | ((wchar >> 6) & 0x3F); + buffer[2] = 0x80 | (wchar & 0x3F); + buffer[3] = 0; + } +} + +static int hex2decimal(char ch) +{ + if (ch >= '0' && ch <= '9') { + return (ch - '0'); + } else if (ch >= 'a' && ch <= 'f') { + return 10 + (ch - 'a'); + } else if (ch >= 'A' && ch <= 'F') { + return 10 + (ch - 'A'); + } + + return -1; +} + +/** + * parse_string(): Parse a json string and return a QObject + * + * string + * "" + * " chars " + * chars + * char + * char chars + * char + * any-Unicode-character- + * except-"-or-\-or- + * control-character + * \" + * \\ + * \/ + * \b + * \f + * \n + * \r + * \t + * \u four-hex-digits + */ +static QString *qstring_from_escaped_str(JSONParserContext *ctxt, QObject *token) +{ + const char *ptr = token_get_value(token); + QString *str; + int double_quote = 1; + + if (*ptr == '"') { + double_quote = 1; + } else { + double_quote = 0; + } + ptr++; + + str = qstring_new(); + while (*ptr && + ((double_quote && *ptr != '"') || (!double_quote && *ptr != '\''))) { + if (*ptr == '\\') { + ptr++; + + switch (*ptr) { + case '"': + qstring_append(str, "\""); + ptr++; + break; + case '\'': + qstring_append(str, "'"); + ptr++; + break; + case '\\': + qstring_append(str, "\\"); + ptr++; + break; + case '/': + qstring_append(str, "/"); + ptr++; + break; + case 'b': + qstring_append(str, "\b"); + ptr++; + break; + case 'f': + qstring_append(str, "\f"); + ptr++; + break; + case 'n': + qstring_append(str, "\n"); + ptr++; + break; + case 'r': + qstring_append(str, "\r"); + ptr++; + break; + case 't': + qstring_append(str, "\t"); + ptr++; + break; + case 'u': { + uint16_t unicode_char = 0; + char utf8_char[4]; + int i = 0; + + ptr++; + + for (i = 0; i < 4; i++) { + if (qemu_isxdigit(*ptr)) { + unicode_char |= hex2decimal(*ptr) << ((3 - i) * 4); + } else { + parse_error(ctxt, token, + "invalid hex escape sequence in string"); + goto out; + } + ptr++; + } + + wchar_to_utf8(unicode_char, utf8_char, sizeof(utf8_char)); + qstring_append(str, utf8_char); + } break; + default: + parse_error(ctxt, token, "invalid escape sequence in string"); + goto out; + } + } else { + char dummy[2]; + + dummy[0] = *ptr++; + dummy[1] = 0; + + qstring_append(str, dummy); + } + } + + return str; + +out: + QDECREF(str); + return NULL; +} + +/** + * Parsing rules + */ +static int parse_pair(JSONParserContext *ctxt, QDict *dict, QList **tokens, va_list *ap) +{ + QObject *key, *token = NULL, *value, *peek; + QList *working = qlist_copy(*tokens); + + peek = qlist_peek(working); + key = parse_value(ctxt, &working, ap); + if (!key || qobject_type(key) != QTYPE_QSTRING) { + parse_error(ctxt, peek, "key is not a string in object"); + goto out; + } + + token = qlist_pop(working); + if (!token_is_operator(token, ':')) { + parse_error(ctxt, token, "missing : in object pair"); + goto out; + } + + value = parse_value(ctxt, &working, ap); + if (value == NULL) { + parse_error(ctxt, token, "Missing value in dict"); + goto out; + } + + qdict_put_obj(dict, qstring_get_str(qobject_to_qstring(key)), value); + + qobject_decref(token); + qobject_decref(key); + QDECREF(*tokens); + *tokens = working; + + return 0; + +out: + qobject_decref(token); + qobject_decref(key); + QDECREF(working); + + return -1; +} + +static QObject *parse_object(JSONParserContext *ctxt, QList **tokens, va_list *ap) +{ + QDict *dict = NULL; + QObject *token, *peek; + QList *working = qlist_copy(*tokens); + + token = qlist_pop(working); + if (!token_is_operator(token, '{')) { + goto out; + } + qobject_decref(token); + token = NULL; + + dict = qdict_new(); + + peek = qlist_peek(working); + if (!token_is_operator(peek, '}')) { + if (parse_pair(ctxt, dict, &working, ap) == -1) { + goto out; + } + + token = qlist_pop(working); + while (!token_is_operator(token, '}')) { + if (!token_is_operator(token, ',')) { + parse_error(ctxt, token, "expected separator in dict"); + goto out; + } + qobject_decref(token); + token = NULL; + + if (parse_pair(ctxt, dict, &working, ap) == -1) { + goto out; + } + + token = qlist_pop(working); + } + qobject_decref(token); + token = NULL; + } else { + token = qlist_pop(working); + qobject_decref(token); + token = NULL; + } + + QDECREF(*tokens); + *tokens = working; + + return QOBJECT(dict); + +out: + qobject_decref(token); + QDECREF(working); + QDECREF(dict); + return NULL; +} + +static QObject *parse_array(JSONParserContext *ctxt, QList **tokens, va_list *ap) +{ + QList *list = NULL; + QObject *token, *peek; + QList *working = qlist_copy(*tokens); + + token = qlist_pop(working); + if (!token_is_operator(token, '[')) { + goto out; + } + qobject_decref(token); + token = NULL; + + list = qlist_new(); + + peek = qlist_peek(working); + if (!token_is_operator(peek, ']')) { + QObject *obj; + + obj = parse_value(ctxt, &working, ap); + if (obj == NULL) { + parse_error(ctxt, token, "expecting value"); + goto out; + } + + qlist_append_obj(list, obj); + + token = qlist_pop(working); + while (!token_is_operator(token, ']')) { + if (!token_is_operator(token, ',')) { + parse_error(ctxt, token, "expected separator in list"); + goto out; + } + + qobject_decref(token); + token = NULL; + + obj = parse_value(ctxt, &working, ap); + if (obj == NULL) { + parse_error(ctxt, token, "expecting value"); + goto out; + } + + qlist_append_obj(list, obj); + + token = qlist_pop(working); + } + + qobject_decref(token); + token = NULL; + } else { + token = qlist_pop(working); + qobject_decref(token); + token = NULL; + } + + QDECREF(*tokens); + *tokens = working; + + return QOBJECT(list); + +out: + qobject_decref(token); + QDECREF(working); + QDECREF(list); + return NULL; +} + +static QObject *parse_keyword(JSONParserContext *ctxt, QList **tokens) +{ + QObject *token, *ret; + QList *working = qlist_copy(*tokens); + + token = qlist_pop(working); + + if (token_get_type(token) != JSON_KEYWORD) { + goto out; + } + + if (token_is_keyword(token, "true")) { + ret = QOBJECT(qbool_from_int(true)); + } else if (token_is_keyword(token, "false")) { + ret = QOBJECT(qbool_from_int(false)); + } else { + parse_error(ctxt, token, "invalid keyword `%s'", token_get_value(token)); + goto out; + } + + qobject_decref(token); + QDECREF(*tokens); + *tokens = working; + + return ret; + +out: + qobject_decref(token); + QDECREF(working); + + return NULL; +} + +static QObject *parse_escape(JSONParserContext *ctxt, QList **tokens, va_list *ap) +{ + QObject *token = NULL, *obj; + QList *working = qlist_copy(*tokens); + + if (ap == NULL) { + goto out; + } + + token = qlist_pop(working); + + if (token_is_escape(token, "%p")) { + obj = va_arg(*ap, QObject *); + } else if (token_is_escape(token, "%i")) { + obj = QOBJECT(qbool_from_int(va_arg(*ap, int))); + } else if (token_is_escape(token, "%d")) { + obj = QOBJECT(qint_from_int(va_arg(*ap, int))); + } else if (token_is_escape(token, "%ld")) { + obj = QOBJECT(qint_from_int(va_arg(*ap, long))); + } else if (token_is_escape(token, "%lld") || + token_is_escape(token, "%I64d")) { + obj = QOBJECT(qint_from_int(va_arg(*ap, long long))); + } else if (token_is_escape(token, "%s")) { + obj = QOBJECT(qstring_from_str(va_arg(*ap, const char *))); + } else if (token_is_escape(token, "%f")) { + obj = QOBJECT(qfloat_from_double(va_arg(*ap, double))); + } else { + goto out; + } + + qobject_decref(token); + QDECREF(*tokens); + *tokens = working; + + return obj; + +out: + qobject_decref(token); + QDECREF(working); + + return NULL; +} + +static QObject *parse_literal(JSONParserContext *ctxt, QList **tokens) +{ + QObject *token, *obj; + QList *working = qlist_copy(*tokens); + + token = qlist_pop(working); + switch (token_get_type(token)) { + case JSON_STRING: + obj = QOBJECT(qstring_from_escaped_str(ctxt, token)); + break; + case JSON_INTEGER: + obj = QOBJECT(qint_from_int(strtoll(token_get_value(token), NULL, 10))); + break; + case JSON_FLOAT: + /* FIXME dependent on locale */ + obj = QOBJECT(qfloat_from_double(strtod(token_get_value(token), NULL))); + break; + default: + goto out; + } + + qobject_decref(token); + QDECREF(*tokens); + *tokens = working; + + return obj; + +out: + qobject_decref(token); + QDECREF(working); + + return NULL; +} + +static QObject *parse_value(JSONParserContext *ctxt, QList **tokens, va_list *ap) +{ + QObject *obj; + + obj = parse_object(ctxt, tokens, ap); + if (obj == NULL) { + obj = parse_array(ctxt, tokens, ap); + } + if (obj == NULL) { + obj = parse_escape(ctxt, tokens, ap); + } + if (obj == NULL) { + obj = parse_keyword(ctxt, tokens); + } + if (obj == NULL) { + obj = parse_literal(ctxt, tokens); + } + + return obj; +} + +QObject *json_parser_parse(QList *tokens, va_list *ap) +{ + JSONParserContext ctxt = {}; + QList *working = qlist_copy(tokens); + QObject *result; + + result = parse_value(&ctxt, &working, ap); + + QDECREF(working); + + return result; +} diff --git a/json-parser.h b/json-parser.h new file mode 100644 index 0000000..97f43f6 --- /dev/null +++ b/json-parser.h @@ -0,0 +1,22 @@ +/* + * JSON Parser + * + * Copyright IBM, Corp. 2009 + * + * Authors: + * Anthony Liguori + * + * This work is licensed under the terms of the GNU LGPL, version 2.1 or later. + * See the COPYING.LIB file in the top-level directory. + * + */ + +#ifndef QEMU_JSON_PARSER_H +#define QEMU_JSON_PARSER_H + +#include "qemu-common.h" +#include "qlist.h" + +QObject *json_parser_parse(QList *tokens, va_list *ap); + +#endif diff --git a/json-streamer.c b/json-streamer.c new file mode 100644 index 0000000..f7e7a68 --- /dev/null +++ b/json-streamer.c @@ -0,0 +1,88 @@ +/* + * JSON streaming support + * + * Copyright IBM, Corp. 2009 + * + * Authors: + * Anthony Liguori + * + * This work is licensed under the terms of the GNU LGPL, version 2.1 or later. + * See the COPYING.LIB file in the top-level directory. + * + */ + +#include "qlist.h" +#include "qint.h" +#include "qdict.h" +#include "qemu-common.h" +#include "json-lexer.h" +#include "json-streamer.h" + +static void json_message_process_token(JSONLexer *lexer, QString *token, JSONTokenType type, int x, int y) +{ + JSONMessageParser *parser = container_of(lexer, JSONMessageParser, lexer); + QDict *dict; + + if (type == JSON_OPERATOR) { + switch (qstring_get_str(token)[0]) { + case '{': + parser->brace_count++; + break; + case '}': + parser->brace_count--; + break; + case '[': + parser->bracket_count++; + break; + case ']': + parser->bracket_count--; + break; + default: + break; + } + } + + dict = qdict_new(); + qdict_put(dict, "type", qint_from_int(type)); + QINCREF(token); + qdict_put(dict, "token", token); + qdict_put(dict, "x", qint_from_int(x)); + qdict_put(dict, "y", qint_from_int(y)); + + qlist_append(parser->tokens, dict); + + if (parser->brace_count == 0 && + parser->bracket_count == 0) { + parser->emit(parser, parser->tokens); + QDECREF(parser->tokens); + parser->tokens = qlist_new(); + } +} + +void json_message_parser_init(JSONMessageParser *parser, + void (*func)(JSONMessageParser *, QList *)) +{ + parser->emit = func; + parser->brace_count = 0; + parser->bracket_count = 0; + parser->tokens = qlist_new(); + + json_lexer_init(&parser->lexer, json_message_process_token); +} + +int json_message_parser_feed(JSONMessageParser *parser, + const char *buffer, size_t size) +{ + return json_lexer_feed(&parser->lexer, buffer, size); +} + +int json_message_parser_flush(JSONMessageParser *parser) +{ + return json_lexer_flush(&parser->lexer); +} + +void json_message_parser_destroy(JSONMessageParser *parser) +{ + json_lexer_destroy(&parser->lexer); + QDECREF(parser->tokens); +} diff --git a/json-streamer.h b/json-streamer.h new file mode 100644 index 0000000..09f3bd7 --- /dev/null +++ b/json-streamer.h @@ -0,0 +1,39 @@ +/* + * JSON streaming support + * + * Copyright IBM, Corp. 2009 + * + * Authors: + * Anthony Liguori + * + * This work is licensed under the terms of the GNU LGPL, version 2.1 or later. + * See the COPYING.LIB file in the top-level directory. + * + */ + +#ifndef QEMU_JSON_STREAMER_H +#define QEMU_JSON_STREAMER_H + +#include "qlist.h" +#include "json-lexer.h" + +typedef struct JSONMessageParser +{ + void (*emit)(struct JSONMessageParser *parser, QList *tokens); + JSONLexer lexer; + int brace_count; + int bracket_count; + QList *tokens; +} JSONMessageParser; + +void json_message_parser_init(JSONMessageParser *parser, + void (*func)(JSONMessageParser *, QList *)); + +int json_message_parser_feed(JSONMessageParser *parser, + const char *buffer, size_t size); + +int json_message_parser_flush(JSONMessageParser *parser); + +void json_message_parser_destroy(JSONMessageParser *parser); + +#endif diff --git a/kvm-all.c b/kvm-all.c new file mode 100644 index 0000000..2ec9e09 --- /dev/null +++ b/kvm-all.c @@ -0,0 +1,1367 @@ +/* + * QEMU KVM support + * + * Copyright IBM, Corp. 2008 + * Red Hat, Inc. 2008 + * + * Authors: + * Anthony Liguori + * Glauber Costa + * + * 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 +#include +#include +#include + +#include + +#include "qemu-common.h" +#include "qemu-barrier.h" +#include "sysemu.h" +#include "hw/hw.h" +#include "gdbstub.h" +#include "kvm.h" +#include "bswap.h" + +/* This check must be after config-host.h is included */ +#ifdef CONFIG_EVENTFD +#include +#endif + +/* KVM uses PAGE_SIZE in it's definition of COALESCED_MMIO_MAX */ +#define PAGE_SIZE TARGET_PAGE_SIZE + +//#define DEBUG_KVM + +#ifdef DEBUG_KVM +#define DPRINTF(fmt, ...) \ + do { fprintf(stderr, fmt, ## __VA_ARGS__); } while (0) +#else +#define DPRINTF(fmt, ...) \ + do { } while (0) +#endif + +typedef struct KVMSlot +{ + target_phys_addr_t start_addr; + ram_addr_t memory_size; + ram_addr_t phys_offset; + int slot; + int flags; +} KVMSlot; + +typedef struct kvm_dirty_log KVMDirtyLog; + +struct KVMState +{ + KVMSlot slots[32]; + int fd; + int vmfd; + int coalesced_mmio; + struct kvm_coalesced_mmio_ring *coalesced_mmio_ring; + int broken_set_mem_region; + int migration_log; + int vcpu_events; + int robust_singlestep; + int debugregs; +#ifdef KVM_CAP_SET_GUEST_DEBUG + struct kvm_sw_breakpoint_head kvm_sw_breakpoints; +#endif + int irqchip_in_kernel; + int pit_in_kernel; + int xsave, xcrs; + int many_ioeventfds; +}; + +static KVMState *kvm_state; + +static const KVMCapabilityInfo kvm_required_capabilites[] = { + KVM_CAP_INFO(USER_MEMORY), + KVM_CAP_INFO(DESTROY_MEMORY_REGION_WORKS), + KVM_CAP_LAST_INFO +}; + +static KVMSlot *kvm_alloc_slot(KVMState *s) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(s->slots); i++) { + /* KVM private memory slots */ + if (i >= 8 && i < 12) { + continue; + } + if (s->slots[i].memory_size == 0) { + return &s->slots[i]; + } + } + + fprintf(stderr, "%s: no free slot available\n", __func__); + abort(); +} + +static KVMSlot *kvm_lookup_matching_slot(KVMState *s, + target_phys_addr_t start_addr, + target_phys_addr_t end_addr) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(s->slots); i++) { + KVMSlot *mem = &s->slots[i]; + + if (start_addr == mem->start_addr && + end_addr == mem->start_addr + mem->memory_size) { + return mem; + } + } + + return NULL; +} + +/* + * Find overlapping slot with lowest start address + */ +static KVMSlot *kvm_lookup_overlapping_slot(KVMState *s, + target_phys_addr_t start_addr, + target_phys_addr_t end_addr) +{ + KVMSlot *found = NULL; + int i; + + for (i = 0; i < ARRAY_SIZE(s->slots); i++) { + KVMSlot *mem = &s->slots[i]; + + if (mem->memory_size == 0 || + (found && found->start_addr < mem->start_addr)) { + continue; + } + + if (end_addr > mem->start_addr && + start_addr < mem->start_addr + mem->memory_size) { + found = mem; + } + } + + return found; +} + +int kvm_physical_memory_addr_from_ram(KVMState *s, ram_addr_t ram_addr, + target_phys_addr_t *phys_addr) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(s->slots); i++) { + KVMSlot *mem = &s->slots[i]; + + if (ram_addr >= mem->phys_offset && + ram_addr < mem->phys_offset + mem->memory_size) { + *phys_addr = mem->start_addr + (ram_addr - mem->phys_offset); + return 1; + } + } + + return 0; +} + +static int kvm_set_user_memory_region(KVMState *s, KVMSlot *slot) +{ + struct kvm_userspace_memory_region mem; + + mem.slot = slot->slot; + mem.guest_phys_addr = slot->start_addr; + mem.memory_size = slot->memory_size; + mem.userspace_addr = (unsigned long)qemu_safe_ram_ptr(slot->phys_offset); + mem.flags = slot->flags; + if (s->migration_log) { + mem.flags |= KVM_MEM_LOG_DIRTY_PAGES; + } + return kvm_vm_ioctl(s, KVM_SET_USER_MEMORY_REGION, &mem); +} + +static void kvm_reset_vcpu(void *opaque) +{ + CPUState *env = opaque; + + kvm_arch_reset_vcpu(env); +} + +int kvm_irqchip_in_kernel(void) +{ + return kvm_state->irqchip_in_kernel; +} + +int kvm_pit_in_kernel(void) +{ + return kvm_state->pit_in_kernel; +} + + +int kvm_init_vcpu(CPUState *env) +{ + KVMState *s = kvm_state; + long mmap_size; + int ret; + + DPRINTF("kvm_init_vcpu\n"); + + ret = kvm_vm_ioctl(s, KVM_CREATE_VCPU, env->cpu_index); + if (ret < 0) { + DPRINTF("kvm_create_vcpu failed\n"); + goto err; + } + + env->kvm_fd = ret; + env->kvm_state = s; + + mmap_size = kvm_ioctl(s, KVM_GET_VCPU_MMAP_SIZE, 0); + if (mmap_size < 0) { + DPRINTF("KVM_GET_VCPU_MMAP_SIZE failed\n"); + goto err; + } + + env->kvm_run = mmap(NULL, mmap_size, PROT_READ | PROT_WRITE, MAP_SHARED, + env->kvm_fd, 0); + if (env->kvm_run == MAP_FAILED) { + ret = -errno; + DPRINTF("mmap'ing vcpu state failed\n"); + goto err; + } + + if (s->coalesced_mmio && !s->coalesced_mmio_ring) { + s->coalesced_mmio_ring = + (void *)env->kvm_run + s->coalesced_mmio * PAGE_SIZE; + } + + ret = kvm_arch_init_vcpu(env); + if (ret == 0) { + qemu_register_reset(kvm_reset_vcpu, env); + kvm_arch_reset_vcpu(env); + } +err: + return ret; +} + +/* + * dirty pages logging control + */ +static int kvm_dirty_pages_log_change(target_phys_addr_t phys_addr, + ram_addr_t size, int flags, int mask) +{ + KVMState *s = kvm_state; + KVMSlot *mem = kvm_lookup_matching_slot(s, phys_addr, phys_addr + size); + int old_flags; + + if (mem == NULL) { + fprintf(stderr, "BUG: %s: invalid parameters " TARGET_FMT_plx "-" + TARGET_FMT_plx "\n", __func__, phys_addr, + (target_phys_addr_t)(phys_addr + size - 1)); + return -EINVAL; + } + + old_flags = mem->flags; + + flags = (mem->flags & ~mask) | flags; + mem->flags = flags; + + /* If nothing changed effectively, no need to issue ioctl */ + if (s->migration_log) { + flags |= KVM_MEM_LOG_DIRTY_PAGES; + } + if (flags == old_flags) { + return 0; + } + + return kvm_set_user_memory_region(s, mem); +} + +int kvm_log_start(target_phys_addr_t phys_addr, ram_addr_t size) +{ + return kvm_dirty_pages_log_change(phys_addr, size, KVM_MEM_LOG_DIRTY_PAGES, + KVM_MEM_LOG_DIRTY_PAGES); +} + +int kvm_log_stop(target_phys_addr_t phys_addr, ram_addr_t size) +{ + return kvm_dirty_pages_log_change(phys_addr, size, 0, + KVM_MEM_LOG_DIRTY_PAGES); +} + +static int kvm_set_migration_log(int enable) +{ + KVMState *s = kvm_state; + KVMSlot *mem; + int i, err; + + s->migration_log = enable; + + for (i = 0; i < ARRAY_SIZE(s->slots); i++) { + mem = &s->slots[i]; + + if (!mem->memory_size) { + continue; + } + if (!!(mem->flags & KVM_MEM_LOG_DIRTY_PAGES) == enable) { + continue; + } + err = kvm_set_user_memory_region(s, mem); + if (err) { + return err; + } + } + return 0; +} + +/* get kvm's dirty pages bitmap and update qemu's */ +static int kvm_get_dirty_pages_log_range(unsigned long start_addr, + unsigned long *bitmap, + unsigned long offset, + unsigned long mem_size) +{ + unsigned int i, j; + unsigned long page_number, addr, addr1, c; + ram_addr_t ram_addr; + unsigned int len = ((mem_size / TARGET_PAGE_SIZE) + HOST_LONG_BITS - 1) / + HOST_LONG_BITS; + + /* + * bitmap-traveling is faster than memory-traveling (for addr...) + * especially when most of the memory is not dirty. + */ + for (i = 0; i < len; i++) { + if (bitmap[i] != 0) { + c = leul_to_cpu(bitmap[i]); + do { + j = ffsl(c) - 1; + c &= ~(1ul << j); + page_number = i * HOST_LONG_BITS + j; + addr1 = page_number * TARGET_PAGE_SIZE; + addr = offset + addr1; + ram_addr = cpu_get_physical_page_desc(addr); + cpu_physical_memory_set_dirty(ram_addr); + } while (c != 0); + } + } + return 0; +} + +#define ALIGN(x, y) (((x)+(y)-1) & ~((y)-1)) + +/** + * kvm_physical_sync_dirty_bitmap - Grab dirty bitmap from kernel space + * This function updates qemu's dirty bitmap using cpu_physical_memory_set_dirty(). + * This means all bits are set to dirty. + * + * @start_add: start of logged region. + * @end_addr: end of logged region. + */ +static int kvm_physical_sync_dirty_bitmap(target_phys_addr_t start_addr, + target_phys_addr_t end_addr) +{ + KVMState *s = kvm_state; + unsigned long size, allocated_size = 0; + KVMDirtyLog d; + KVMSlot *mem; + int ret = 0; + + d.dirty_bitmap = NULL; + while (start_addr < end_addr) { + mem = kvm_lookup_overlapping_slot(s, start_addr, end_addr); + if (mem == NULL) { + break; + } + + size = ALIGN(((mem->memory_size) >> TARGET_PAGE_BITS), HOST_LONG_BITS) / 8; + if (!d.dirty_bitmap) { + d.dirty_bitmap = qemu_malloc(size); + } else if (size > allocated_size) { + d.dirty_bitmap = qemu_realloc(d.dirty_bitmap, size); + } + allocated_size = size; + memset(d.dirty_bitmap, 0, allocated_size); + + d.slot = mem->slot; + + if (kvm_vm_ioctl(s, KVM_GET_DIRTY_LOG, &d) == -1) { + DPRINTF("ioctl failed %d\n", errno); + ret = -1; + break; + } + + kvm_get_dirty_pages_log_range(mem->start_addr, d.dirty_bitmap, + mem->start_addr, mem->memory_size); + start_addr = mem->start_addr + mem->memory_size; + } + qemu_free(d.dirty_bitmap); + + return ret; +} + +int kvm_coalesce_mmio_region(target_phys_addr_t start, ram_addr_t size) +{ + int ret = -ENOSYS; + KVMState *s = kvm_state; + + if (s->coalesced_mmio) { + struct kvm_coalesced_mmio_zone zone; + + zone.addr = start; + zone.size = size; + + ret = kvm_vm_ioctl(s, KVM_REGISTER_COALESCED_MMIO, &zone); + } + + return ret; +} + +int kvm_uncoalesce_mmio_region(target_phys_addr_t start, ram_addr_t size) +{ + int ret = -ENOSYS; + KVMState *s = kvm_state; + + if (s->coalesced_mmio) { + struct kvm_coalesced_mmio_zone zone; + + zone.addr = start; + zone.size = size; + + ret = kvm_vm_ioctl(s, KVM_UNREGISTER_COALESCED_MMIO, &zone); + } + + return ret; +} + +int kvm_check_extension(KVMState *s, unsigned int extension) +{ + int ret; + + ret = kvm_ioctl(s, KVM_CHECK_EXTENSION, extension); + if (ret < 0) { + ret = 0; + } + + return ret; +} + +static int kvm_check_many_ioeventfds(void) +{ + /* Userspace can use ioeventfd for io notification. This requires a host + * that supports eventfd(2) and an I/O thread; since eventfd does not + * support SIGIO it cannot interrupt the vcpu. + * + * Older kernels have a 6 device limit on the KVM io bus. Find out so we + * can avoid creating too many ioeventfds. + */ +#if defined(CONFIG_EVENTFD) && defined(CONFIG_IOTHREAD) + int ioeventfds[7]; + int i, ret = 0; + for (i = 0; i < ARRAY_SIZE(ioeventfds); i++) { + ioeventfds[i] = eventfd(0, EFD_CLOEXEC); + if (ioeventfds[i] < 0) { + break; + } + ret = kvm_set_ioeventfd_pio_word(ioeventfds[i], 0, i, true); + if (ret < 0) { + close(ioeventfds[i]); + break; + } + } + + /* Decide whether many devices are supported or not */ + ret = i == ARRAY_SIZE(ioeventfds); + + while (i-- > 0) { + kvm_set_ioeventfd_pio_word(ioeventfds[i], 0, i, false); + close(ioeventfds[i]); + } + return ret; +#else + return 0; +#endif +} + +static const KVMCapabilityInfo * +kvm_check_extension_list(KVMState *s, const KVMCapabilityInfo *list) +{ + while (list->name) { + if (!kvm_check_extension(s, list->value)) { + return list; + } + list++; + } + return NULL; +} + +static void kvm_set_phys_mem(target_phys_addr_t start_addr, ram_addr_t size, + ram_addr_t phys_offset) +{ + KVMState *s = kvm_state; + ram_addr_t flags = phys_offset & ~TARGET_PAGE_MASK; + KVMSlot *mem, old; + int err; + + /* kvm works in page size chunks, but the function may be called + with sub-page size and unaligned start address. */ + size = TARGET_PAGE_ALIGN(size); + start_addr = TARGET_PAGE_ALIGN(start_addr); + + /* KVM does not support read-only slots */ + phys_offset &= ~IO_MEM_ROM; + + while (1) { + mem = kvm_lookup_overlapping_slot(s, start_addr, start_addr + size); + if (!mem) { + break; + } + + if (flags < IO_MEM_UNASSIGNED && start_addr >= mem->start_addr && + (start_addr + size <= mem->start_addr + mem->memory_size) && + (phys_offset - start_addr == mem->phys_offset - mem->start_addr)) { + /* The new slot fits into the existing one and comes with + * identical parameters - nothing to be done. */ + return; + } + + old = *mem; + + /* unregister the overlapping slot */ + mem->memory_size = 0; + err = kvm_set_user_memory_region(s, mem); + if (err) { + fprintf(stderr, "%s: error unregistering overlapping slot: %s\n", + __func__, strerror(-err)); + abort(); + } + + /* Workaround for older KVM versions: we can't join slots, even not by + * unregistering the previous ones and then registering the larger + * slot. We have to maintain the existing fragmentation. Sigh. + * + * This workaround assumes that the new slot starts at the same + * address as the first existing one. If not or if some overlapping + * slot comes around later, we will fail (not seen in practice so far) + * - and actually require a recent KVM version. */ + if (s->broken_set_mem_region && + old.start_addr == start_addr && old.memory_size < size && + flags < IO_MEM_UNASSIGNED) { + mem = kvm_alloc_slot(s); + mem->memory_size = old.memory_size; + mem->start_addr = old.start_addr; + mem->phys_offset = old.phys_offset; + mem->flags = 0; + + err = kvm_set_user_memory_region(s, mem); + if (err) { + fprintf(stderr, "%s: error updating slot: %s\n", __func__, + strerror(-err)); + abort(); + } + + start_addr += old.memory_size; + phys_offset += old.memory_size; + size -= old.memory_size; + continue; + } + + /* register prefix slot */ + if (old.start_addr < start_addr) { + mem = kvm_alloc_slot(s); + mem->memory_size = start_addr - old.start_addr; + mem->start_addr = old.start_addr; + mem->phys_offset = old.phys_offset; + mem->flags = 0; + + err = kvm_set_user_memory_region(s, mem); + if (err) { + fprintf(stderr, "%s: error registering prefix slot: %s\n", + __func__, strerror(-err)); + abort(); + } + } + + /* register suffix slot */ + if (old.start_addr + old.memory_size > start_addr + size) { + ram_addr_t size_delta; + + mem = kvm_alloc_slot(s); + mem->start_addr = start_addr + size; + size_delta = mem->start_addr - old.start_addr; + mem->memory_size = old.memory_size - size_delta; + mem->phys_offset = old.phys_offset + size_delta; + mem->flags = 0; + + err = kvm_set_user_memory_region(s, mem); + if (err) { + fprintf(stderr, "%s: error registering suffix slot: %s\n", + __func__, strerror(-err)); + abort(); + } + } + } + + /* in case the KVM bug workaround already "consumed" the new slot */ + if (!size) { + return; + } + /* KVM does not need to know about this memory */ + if (flags >= IO_MEM_UNASSIGNED) { + return; + } + mem = kvm_alloc_slot(s); + mem->memory_size = size; + mem->start_addr = start_addr; + mem->phys_offset = phys_offset; + mem->flags = 0; + + err = kvm_set_user_memory_region(s, mem); + if (err) { + fprintf(stderr, "%s: error registering slot: %s\n", __func__, + strerror(-err)); + abort(); + } +} + +static void kvm_client_set_memory(struct CPUPhysMemoryClient *client, + target_phys_addr_t start_addr, + ram_addr_t size, ram_addr_t phys_offset) +{ + kvm_set_phys_mem(start_addr, size, phys_offset); +} + +static int kvm_client_sync_dirty_bitmap(struct CPUPhysMemoryClient *client, + target_phys_addr_t start_addr, + target_phys_addr_t end_addr) +{ + return kvm_physical_sync_dirty_bitmap(start_addr, end_addr); +} + +static int kvm_client_migration_log(struct CPUPhysMemoryClient *client, + int enable) +{ + return kvm_set_migration_log(enable); +} + +static CPUPhysMemoryClient kvm_cpu_phys_memory_client = { + .set_memory = kvm_client_set_memory, + .sync_dirty_bitmap = kvm_client_sync_dirty_bitmap, + .migration_log = kvm_client_migration_log, +}; + +int kvm_init(void) +{ + static const char upgrade_note[] = + "Please upgrade to at least kernel 2.6.29 or recent kvm-kmod\n" + "(see http://sourceforge.net/projects/kvm).\n"; + KVMState *s; + const KVMCapabilityInfo *missing_cap; + int ret; + int i; + + s = qemu_mallocz(sizeof(KVMState)); + +#ifdef KVM_CAP_SET_GUEST_DEBUG + QTAILQ_INIT(&s->kvm_sw_breakpoints); +#endif + for (i = 0; i < ARRAY_SIZE(s->slots); i++) { + s->slots[i].slot = i; + } + s->vmfd = -1; + s->fd = qemu_open("/dev/kvm", O_RDWR); + if (s->fd == -1) { + fprintf(stderr, "Could not access KVM kernel module: %m\n"); + ret = -errno; + goto err; + } + + ret = kvm_ioctl(s, KVM_GET_API_VERSION, 0); + if (ret < KVM_API_VERSION) { + if (ret > 0) { + ret = -EINVAL; + } + fprintf(stderr, "kvm version too old\n"); + goto err; + } + + if (ret > KVM_API_VERSION) { + ret = -EINVAL; + fprintf(stderr, "kvm version not supported\n"); + goto err; + } + + s->vmfd = kvm_ioctl(s, KVM_CREATE_VM, 0); + if (s->vmfd < 0) { +#ifdef TARGET_S390X + fprintf(stderr, "Please add the 'switch_amode' kernel parameter to " + "your host kernel command line\n"); +#endif + goto err; + } + + missing_cap = kvm_check_extension_list(s, kvm_required_capabilites); + if (!missing_cap) { + missing_cap = + kvm_check_extension_list(s, kvm_arch_required_capabilities); + } + if (missing_cap) { + ret = -EINVAL; + fprintf(stderr, "kvm does not support %s\n%s", + missing_cap->name, upgrade_note); + goto err; + } + + s->coalesced_mmio = kvm_check_extension(s, KVM_CAP_COALESCED_MMIO); + + s->broken_set_mem_region = 1; +#ifdef KVM_CAP_JOIN_MEMORY_REGIONS_WORKS + ret = kvm_check_extension(s, KVM_CAP_JOIN_MEMORY_REGIONS_WORKS); + if (ret > 0) { + s->broken_set_mem_region = 0; + } +#endif + + s->vcpu_events = 0; +#ifdef KVM_CAP_VCPU_EVENTS + s->vcpu_events = kvm_check_extension(s, KVM_CAP_VCPU_EVENTS); +#endif + + s->robust_singlestep = 0; +#ifdef KVM_CAP_X86_ROBUST_SINGLESTEP + s->robust_singlestep = + kvm_check_extension(s, KVM_CAP_X86_ROBUST_SINGLESTEP); +#endif + + s->debugregs = 0; +#ifdef KVM_CAP_DEBUGREGS + s->debugregs = kvm_check_extension(s, KVM_CAP_DEBUGREGS); +#endif + + s->xsave = 0; +#ifdef KVM_CAP_XSAVE + s->xsave = kvm_check_extension(s, KVM_CAP_XSAVE); +#endif + + s->xcrs = 0; +#ifdef KVM_CAP_XCRS + s->xcrs = kvm_check_extension(s, KVM_CAP_XCRS); +#endif + + ret = kvm_arch_init(s); + if (ret < 0) { + goto err; + } + + kvm_state = s; + cpu_register_phys_memory_client(&kvm_cpu_phys_memory_client); + + s->many_ioeventfds = kvm_check_many_ioeventfds(); + + return 0; + +err: + if (s) { + if (s->vmfd != -1) { + close(s->vmfd); + } + if (s->fd != -1) { + close(s->fd); + } + } + qemu_free(s); + + return ret; +} + +static int kvm_handle_io(uint16_t port, void *data, int direction, int size, + uint32_t count) +{ + int i; + uint8_t *ptr = data; + + for (i = 0; i < count; i++) { + if (direction == KVM_EXIT_IO_IN) { + switch (size) { + case 1: + stb_p(ptr, cpu_inb(port)); + break; + case 2: + stw_p(ptr, cpu_inw(port)); + break; + case 4: + stl_p(ptr, cpu_inl(port)); + break; + } + } else { + switch (size) { + case 1: + cpu_outb(port, ldub_p(ptr)); + break; + case 2: + cpu_outw(port, lduw_p(ptr)); + break; + case 4: + cpu_outl(port, ldl_p(ptr)); + break; + } + } + + ptr += size; + } + + return 1; +} + +#ifdef KVM_CAP_INTERNAL_ERROR_DATA +static int kvm_handle_internal_error(CPUState *env, struct kvm_run *run) +{ + fprintf(stderr, "KVM internal error."); + if (kvm_check_extension(kvm_state, KVM_CAP_INTERNAL_ERROR_DATA)) { + int i; + + fprintf(stderr, " Suberror: %d\n", run->internal.suberror); + for (i = 0; i < run->internal.ndata; ++i) { + fprintf(stderr, "extra data[%d]: %"PRIx64"\n", + i, (uint64_t)run->internal.data[i]); + } + } else { + fprintf(stderr, "\n"); + } + if (run->internal.suberror == KVM_INTERNAL_ERROR_EMULATION) { + fprintf(stderr, "emulation failure\n"); + if (!kvm_arch_stop_on_emulation_error(env)) { + cpu_dump_state(env, stderr, fprintf, CPU_DUMP_CODE); + return 0; + } + } + /* FIXME: Should trigger a qmp message to let management know + * something went wrong. + */ + return -1; +} +#endif + +void kvm_flush_coalesced_mmio_buffer(void) +{ + KVMState *s = kvm_state; + if (s->coalesced_mmio_ring) { + struct kvm_coalesced_mmio_ring *ring = s->coalesced_mmio_ring; + while (ring->first != ring->last) { + struct kvm_coalesced_mmio *ent; + + ent = &ring->coalesced_mmio[ring->first]; + + cpu_physical_memory_write(ent->phys_addr, ent->data, ent->len); + smp_wmb(); + ring->first = (ring->first + 1) % KVM_COALESCED_MMIO_MAX; + } + } +} + +static void do_kvm_cpu_synchronize_state(void *_env) +{ + CPUState *env = _env; + + if (!env->kvm_vcpu_dirty) { + kvm_arch_get_registers(env); + env->kvm_vcpu_dirty = 1; + } +} + +void kvm_cpu_synchronize_state(CPUState *env) +{ + if (!env->kvm_vcpu_dirty) { + run_on_cpu(env, do_kvm_cpu_synchronize_state, env); + } +} + +void kvm_cpu_synchronize_post_reset(CPUState *env) +{ + kvm_arch_put_registers(env, KVM_PUT_RESET_STATE); + env->kvm_vcpu_dirty = 0; +} + +void kvm_cpu_synchronize_post_init(CPUState *env) +{ + kvm_arch_put_registers(env, KVM_PUT_FULL_STATE); + env->kvm_vcpu_dirty = 0; +} + +int kvm_cpu_exec(CPUState *env) +{ + struct kvm_run *run = env->kvm_run; + int ret; + + DPRINTF("kvm_cpu_exec()\n"); + + do { +#ifndef CONFIG_IOTHREAD + if (env->exit_request) { + DPRINTF("interrupt exit requested\n"); + ret = 0; + break; + } +#endif + + if (kvm_arch_process_irqchip_events(env)) { + ret = 0; + break; + } + + if (env->kvm_vcpu_dirty) { + kvm_arch_put_registers(env, KVM_PUT_RUNTIME_STATE); + env->kvm_vcpu_dirty = 0; + } + + kvm_arch_pre_run(env, run); + cpu_single_env = NULL; + qemu_mutex_unlock_iothread(); + ret = kvm_vcpu_ioctl(env, KVM_RUN, 0); + qemu_mutex_lock_iothread(); + cpu_single_env = env; + kvm_arch_post_run(env, run); + + kvm_flush_coalesced_mmio_buffer(); + + if (ret == -EINTR || ret == -EAGAIN) { + cpu_exit(env); + DPRINTF("io window exit\n"); + ret = 0; + break; + } + + if (ret < 0) { + DPRINTF("kvm run failed %s\n", strerror(-ret)); + abort(); + } + + ret = 0; /* exit loop */ + switch (run->exit_reason) { + case KVM_EXIT_IO: + DPRINTF("handle_io\n"); + ret = kvm_handle_io(run->io.port, + (uint8_t *)run + run->io.data_offset, + run->io.direction, + run->io.size, + run->io.count); + break; + case KVM_EXIT_MMIO: + DPRINTF("handle_mmio\n"); + cpu_physical_memory_rw(run->mmio.phys_addr, + run->mmio.data, + run->mmio.len, + run->mmio.is_write); + ret = 1; + break; + case KVM_EXIT_IRQ_WINDOW_OPEN: + DPRINTF("irq_window_open\n"); + break; + case KVM_EXIT_SHUTDOWN: + DPRINTF("shutdown\n"); + qemu_system_reset_request(); + ret = 1; + break; + case KVM_EXIT_UNKNOWN: + fprintf(stderr, "KVM: unknown exit, hardware reason %" PRIx64 "\n", + (uint64_t)run->hw.hardware_exit_reason); + ret = -1; + break; +#ifdef KVM_CAP_INTERNAL_ERROR_DATA + case KVM_EXIT_INTERNAL_ERROR: + ret = kvm_handle_internal_error(env, run); + break; +#endif + case KVM_EXIT_DEBUG: + DPRINTF("kvm_exit_debug\n"); +#ifdef KVM_CAP_SET_GUEST_DEBUG + if (kvm_arch_debug(&run->debug.arch)) { + env->exception_index = EXCP_DEBUG; + return 0; + } + /* re-enter, this exception was guest-internal */ + ret = 1; +#endif /* KVM_CAP_SET_GUEST_DEBUG */ + break; + default: + DPRINTF("kvm_arch_handle_exit\n"); + ret = kvm_arch_handle_exit(env, run); + break; + } + } while (ret > 0); + + if (ret < 0) { + cpu_dump_state(env, stderr, fprintf, CPU_DUMP_CODE); + vm_stop(0); + env->exit_request = 1; + } + if (env->exit_request) { + env->exit_request = 0; + env->exception_index = EXCP_INTERRUPT; + } + + return ret; +} + +int kvm_ioctl(KVMState *s, int type, ...) +{ + int ret; + void *arg; + va_list ap; + + va_start(ap, type); + arg = va_arg(ap, void *); + va_end(ap); + + ret = ioctl(s->fd, type, arg); + if (ret == -1) { + ret = -errno; + } + return ret; +} + +int kvm_vm_ioctl(KVMState *s, int type, ...) +{ + int ret; + void *arg; + va_list ap; + + va_start(ap, type); + arg = va_arg(ap, void *); + va_end(ap); + + ret = ioctl(s->vmfd, type, arg); + if (ret == -1) { + ret = -errno; + } + return ret; +} + +int kvm_vcpu_ioctl(CPUState *env, int type, ...) +{ + int ret; + void *arg; + va_list ap; + + va_start(ap, type); + arg = va_arg(ap, void *); + va_end(ap); + + ret = ioctl(env->kvm_fd, type, arg); + if (ret == -1) { + ret = -errno; + } + return ret; +} + +int kvm_has_sync_mmu(void) +{ + return kvm_check_extension(kvm_state, KVM_CAP_SYNC_MMU); +} + +int kvm_has_vcpu_events(void) +{ + return kvm_state->vcpu_events; +} + +int kvm_has_robust_singlestep(void) +{ + return kvm_state->robust_singlestep; +} + +int kvm_has_debugregs(void) +{ + return kvm_state->debugregs; +} + +int kvm_has_xsave(void) +{ + return kvm_state->xsave; +} + +int kvm_has_xcrs(void) +{ + return kvm_state->xcrs; +} + +int kvm_has_many_ioeventfds(void) +{ + if (!kvm_enabled()) { + return 0; + } + return kvm_state->many_ioeventfds; +} + +void kvm_setup_guest_memory(void *start, size_t size) +{ + if (!kvm_has_sync_mmu()) { + int ret = qemu_madvise(start, size, QEMU_MADV_DONTFORK); + + if (ret) { + perror("qemu_madvise"); + fprintf(stderr, + "Need MADV_DONTFORK in absence of synchronous KVM MMU\n"); + exit(1); + } + } +} + +#ifdef KVM_CAP_SET_GUEST_DEBUG +struct kvm_sw_breakpoint *kvm_find_sw_breakpoint(CPUState *env, + target_ulong pc) +{ + struct kvm_sw_breakpoint *bp; + + QTAILQ_FOREACH(bp, &env->kvm_state->kvm_sw_breakpoints, entry) { + if (bp->pc == pc) { + return bp; + } + } + return NULL; +} + +int kvm_sw_breakpoints_active(CPUState *env) +{ + return !QTAILQ_EMPTY(&env->kvm_state->kvm_sw_breakpoints); +} + +struct kvm_set_guest_debug_data { + struct kvm_guest_debug dbg; + CPUState *env; + int err; +}; + +static void kvm_invoke_set_guest_debug(void *data) +{ + struct kvm_set_guest_debug_data *dbg_data = data; + CPUState *env = dbg_data->env; + + dbg_data->err = kvm_vcpu_ioctl(env, KVM_SET_GUEST_DEBUG, &dbg_data->dbg); +} + +int kvm_update_guest_debug(CPUState *env, unsigned long reinject_trap) +{ + struct kvm_set_guest_debug_data data; + + data.dbg.control = reinject_trap; + + if (env->singlestep_enabled) { + data.dbg.control |= KVM_GUESTDBG_ENABLE | KVM_GUESTDBG_SINGLESTEP; + } + kvm_arch_update_guest_debug(env, &data.dbg); + data.env = env; + + run_on_cpu(env, kvm_invoke_set_guest_debug, &data); + return data.err; +} + +int kvm_insert_breakpoint(CPUState *current_env, target_ulong addr, + target_ulong len, int type) +{ + struct kvm_sw_breakpoint *bp; + CPUState *env; + int err; + + if (type == GDB_BREAKPOINT_SW) { + bp = kvm_find_sw_breakpoint(current_env, addr); + if (bp) { + bp->use_count++; + return 0; + } + + bp = qemu_malloc(sizeof(struct kvm_sw_breakpoint)); + if (!bp) { + return -ENOMEM; + } + + bp->pc = addr; + bp->use_count = 1; + err = kvm_arch_insert_sw_breakpoint(current_env, bp); + if (err) { + free(bp); + return err; + } + + QTAILQ_INSERT_HEAD(¤t_env->kvm_state->kvm_sw_breakpoints, + bp, entry); + } else { + err = kvm_arch_insert_hw_breakpoint(addr, len, type); + if (err) { + return err; + } + } + + for (env = first_cpu; env != NULL; env = env->next_cpu) { + err = kvm_update_guest_debug(env, 0); + if (err) { + return err; + } + } + return 0; +} + +int kvm_remove_breakpoint(CPUState *current_env, target_ulong addr, + target_ulong len, int type) +{ + struct kvm_sw_breakpoint *bp; + CPUState *env; + int err; + + if (type == GDB_BREAKPOINT_SW) { + bp = kvm_find_sw_breakpoint(current_env, addr); + if (!bp) { + return -ENOENT; + } + + if (bp->use_count > 1) { + bp->use_count--; + return 0; + } + + err = kvm_arch_remove_sw_breakpoint(current_env, bp); + if (err) { + return err; + } + + QTAILQ_REMOVE(¤t_env->kvm_state->kvm_sw_breakpoints, bp, entry); + qemu_free(bp); + } else { + err = kvm_arch_remove_hw_breakpoint(addr, len, type); + if (err) { + return err; + } + } + + for (env = first_cpu; env != NULL; env = env->next_cpu) { + err = kvm_update_guest_debug(env, 0); + if (err) { + return err; + } + } + return 0; +} + +void kvm_remove_all_breakpoints(CPUState *current_env) +{ + struct kvm_sw_breakpoint *bp, *next; + KVMState *s = current_env->kvm_state; + CPUState *env; + + QTAILQ_FOREACH_SAFE(bp, &s->kvm_sw_breakpoints, entry, next) { + if (kvm_arch_remove_sw_breakpoint(current_env, bp) != 0) { + /* Try harder to find a CPU that currently sees the breakpoint. */ + for (env = first_cpu; env != NULL; env = env->next_cpu) { + if (kvm_arch_remove_sw_breakpoint(env, bp) == 0) { + break; + } + } + } + } + kvm_arch_remove_all_hw_breakpoints(); + + for (env = first_cpu; env != NULL; env = env->next_cpu) { + kvm_update_guest_debug(env, 0); + } +} + +#else /* !KVM_CAP_SET_GUEST_DEBUG */ + +int kvm_update_guest_debug(CPUState *env, unsigned long reinject_trap) +{ + return -EINVAL; +} + +int kvm_insert_breakpoint(CPUState *current_env, target_ulong addr, + target_ulong len, int type) +{ + return -EINVAL; +} + +int kvm_remove_breakpoint(CPUState *current_env, target_ulong addr, + target_ulong len, int type) +{ + return -EINVAL; +} + +void kvm_remove_all_breakpoints(CPUState *current_env) +{ +} +#endif /* !KVM_CAP_SET_GUEST_DEBUG */ + +int kvm_set_signal_mask(CPUState *env, const sigset_t *sigset) +{ + struct kvm_signal_mask *sigmask; + int r; + + if (!sigset) { + return kvm_vcpu_ioctl(env, KVM_SET_SIGNAL_MASK, NULL); + } + + sigmask = qemu_malloc(sizeof(*sigmask) + sizeof(*sigset)); + + sigmask->len = 8; + memcpy(sigmask->sigset, sigset, sizeof(*sigset)); + r = kvm_vcpu_ioctl(env, KVM_SET_SIGNAL_MASK, sigmask); + free(sigmask); + + return r; +} + +int kvm_set_ioeventfd_mmio_long(int fd, uint32_t addr, uint32_t val, bool assign) +{ +#ifdef KVM_IOEVENTFD + int ret; + struct kvm_ioeventfd iofd; + + iofd.datamatch = val; + iofd.addr = addr; + iofd.len = 4; + iofd.flags = KVM_IOEVENTFD_FLAG_DATAMATCH; + iofd.fd = fd; + + if (!kvm_enabled()) { + return -ENOSYS; + } + + if (!assign) { + iofd.flags |= KVM_IOEVENTFD_FLAG_DEASSIGN; + } + + ret = kvm_vm_ioctl(kvm_state, KVM_IOEVENTFD, &iofd); + + if (ret < 0) { + return -errno; + } + + return 0; +#else + return -ENOSYS; +#endif +} + +int kvm_set_ioeventfd_pio_word(int fd, uint16_t addr, uint16_t val, bool assign) +{ +#ifdef KVM_IOEVENTFD + struct kvm_ioeventfd kick = { + .datamatch = val, + .addr = addr, + .len = 2, + .flags = KVM_IOEVENTFD_FLAG_DATAMATCH | KVM_IOEVENTFD_FLAG_PIO, + .fd = fd, + }; + int r; + if (!kvm_enabled()) { + return -ENOSYS; + } + if (!assign) { + kick.flags |= KVM_IOEVENTFD_FLAG_DEASSIGN; + } + r = kvm_vm_ioctl(kvm_state, KVM_IOEVENTFD, &kick); + if (r < 0) { + return r; + } + return 0; +#else + return -ENOSYS; +#endif +} diff --git a/kvm-stub.c b/kvm-stub.c new file mode 100644 index 0000000..88682f2 --- /dev/null +++ b/kvm-stub.c @@ -0,0 +1,153 @@ +/* + * QEMU KVM stub + * + * Copyright Red Hat, Inc. 2010 + * + * Author: Paolo Bonzini + * + * 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.h" +#include "hw/hw.h" +#include "exec-all.h" +#include "gdbstub.h" +#include "kvm.h" + +int kvm_irqchip_in_kernel(void) +{ + return 0; +} + +int kvm_pit_in_kernel(void) +{ + return 0; +} + + +int kvm_init_vcpu(CPUState *env) +{ + return -ENOSYS; +} + +int kvm_log_start(target_phys_addr_t phys_addr, ram_addr_t size) +{ + return -ENOSYS; +} + +int kvm_log_stop(target_phys_addr_t phys_addr, ram_addr_t size) +{ + return -ENOSYS; +} + +int kvm_coalesce_mmio_region(target_phys_addr_t start, ram_addr_t size) +{ + return -ENOSYS; +} + +int kvm_uncoalesce_mmio_region(target_phys_addr_t start, ram_addr_t size) +{ + return -ENOSYS; +} + +int kvm_check_extension(KVMState *s, unsigned int extension) +{ + return 0; +} + +int kvm_init(void) +{ + return -ENOSYS; +} + +void kvm_flush_coalesced_mmio_buffer(void) +{ +} + +void kvm_cpu_synchronize_state(CPUState *env) +{ +} + +void kvm_cpu_synchronize_post_reset(CPUState *env) +{ +} + +void kvm_cpu_synchronize_post_init(CPUState *env) +{ +} + +int kvm_cpu_exec(CPUState *env) +{ + abort (); +} + +int kvm_has_sync_mmu(void) +{ + return 0; +} + +int kvm_has_vcpu_events(void) +{ + return 0; +} + +int kvm_has_robust_singlestep(void) +{ + return 0; +} + +int kvm_has_many_ioeventfds(void) +{ + return 0; +} + +void kvm_setup_guest_memory(void *start, size_t size) +{ +} + +int kvm_update_guest_debug(CPUState *env, unsigned long reinject_trap) +{ + tb_flush(env); + return 0; +} + +int kvm_insert_breakpoint(CPUState *current_env, target_ulong addr, + target_ulong len, int type) +{ + return -EINVAL; +} + +int kvm_remove_breakpoint(CPUState *current_env, target_ulong addr, + target_ulong len, int type) +{ + return -EINVAL; +} + +void kvm_remove_all_breakpoints(CPUState *current_env) +{ +} + +#ifndef _WIN32 +int kvm_set_signal_mask(CPUState *env, const sigset_t *sigset) +{ + abort(); +} +#endif + +int kvm_set_ioeventfd_pio_word(int fd, uint16_t addr, uint16_t val, bool assign) +{ + return -ENOSYS; +} + +int kvm_set_ioeventfd_mmio_long(int fd, uint32_t adr, uint32_t val, bool assign) +{ + return -ENOSYS; +} + +int kvm_on_sigbus(int code, void *addr) +{ + return 1; +} diff --git a/kvm.h b/kvm.h new file mode 100644 index 0000000..ca57517 --- /dev/null +++ b/kvm.h @@ -0,0 +1,201 @@ +/* + * QEMU KVM support + * + * Copyright IBM, Corp. 2008 + * + * Authors: + * Anthony Liguori + * + * 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_KVM_H +#define QEMU_KVM_H + +#include +#include "config-host.h" +#include "qemu-queue.h" + +#ifdef CONFIG_KVM +#include +#endif + +extern int kvm_allowed; + +#if defined CONFIG_KVM || !defined NEED_CPU_H +#define kvm_enabled() (kvm_allowed) +#else +#define kvm_enabled() (0) +#endif + +struct kvm_run; + +typedef struct KVMCapabilityInfo { + const char *name; + int value; +} KVMCapabilityInfo; + +#define KVM_CAP_INFO(CAP) { "KVM_CAP_" stringify(CAP), KVM_CAP_##CAP } +#define KVM_CAP_LAST_INFO { NULL, 0 } + +/* external API */ + +int kvm_init(void); + +int kvm_has_sync_mmu(void); +int kvm_has_vcpu_events(void); +int kvm_has_robust_singlestep(void); +int kvm_has_debugregs(void); +int kvm_has_xsave(void); +int kvm_has_xcrs(void); +int kvm_has_many_ioeventfds(void); + +#ifdef NEED_CPU_H +int kvm_init_vcpu(CPUState *env); + +int kvm_cpu_exec(CPUState *env); + +#if !defined(CONFIG_USER_ONLY) +int kvm_log_start(target_phys_addr_t phys_addr, ram_addr_t size); +int kvm_log_stop(target_phys_addr_t phys_addr, ram_addr_t size); + +void kvm_setup_guest_memory(void *start, size_t size); + +int kvm_coalesce_mmio_region(target_phys_addr_t start, ram_addr_t size); +int kvm_uncoalesce_mmio_region(target_phys_addr_t start, ram_addr_t size); +void kvm_flush_coalesced_mmio_buffer(void); +#endif + +int kvm_insert_breakpoint(CPUState *current_env, target_ulong addr, + target_ulong len, int type); +int kvm_remove_breakpoint(CPUState *current_env, target_ulong addr, + target_ulong len, int type); +void kvm_remove_all_breakpoints(CPUState *current_env); +int kvm_update_guest_debug(CPUState *env, unsigned long reinject_trap); +#ifndef _WIN32 +int kvm_set_signal_mask(CPUState *env, const sigset_t *sigset); +#endif + +int kvm_pit_in_kernel(void); +int kvm_irqchip_in_kernel(void); + +/* internal API */ + +struct KVMState; +typedef struct KVMState KVMState; + +int kvm_ioctl(KVMState *s, int type, ...); + +int kvm_vm_ioctl(KVMState *s, int type, ...); + +int kvm_vcpu_ioctl(CPUState *env, int type, ...); + +/* Arch specific hooks */ + +extern const KVMCapabilityInfo kvm_arch_required_capabilities[]; + +int kvm_arch_post_run(CPUState *env, struct kvm_run *run); + +int kvm_arch_handle_exit(CPUState *env, struct kvm_run *run); + +int kvm_arch_pre_run(CPUState *env, struct kvm_run *run); + +int kvm_arch_process_irqchip_events(CPUState *env); + +int kvm_arch_get_registers(CPUState *env); + +/* state subset only touched by the VCPU itself during runtime */ +#define KVM_PUT_RUNTIME_STATE 1 +/* state subset modified during VCPU reset */ +#define KVM_PUT_RESET_STATE 2 +/* full state set, modified during initialization or on vmload */ +#define KVM_PUT_FULL_STATE 3 + +int kvm_arch_put_registers(CPUState *env, int level); + +int kvm_arch_init(KVMState *s); + +int kvm_arch_init_vcpu(CPUState *env); + +void kvm_arch_reset_vcpu(CPUState *env); + +int kvm_on_sigbus_vcpu(CPUState *env, int code, void *addr); +int kvm_on_sigbus(int code, void *addr); + +struct kvm_guest_debug; +struct kvm_debug_exit_arch; + +struct kvm_sw_breakpoint { + target_ulong pc; + target_ulong saved_insn; + int use_count; + QTAILQ_ENTRY(kvm_sw_breakpoint) entry; +}; + +QTAILQ_HEAD(kvm_sw_breakpoint_head, kvm_sw_breakpoint); + +int kvm_arch_debug(struct kvm_debug_exit_arch *arch_info); + +struct kvm_sw_breakpoint *kvm_find_sw_breakpoint(CPUState *env, + target_ulong pc); + +int kvm_sw_breakpoints_active(CPUState *env); + +int kvm_arch_insert_sw_breakpoint(CPUState *current_env, + struct kvm_sw_breakpoint *bp); +int kvm_arch_remove_sw_breakpoint(CPUState *current_env, + struct kvm_sw_breakpoint *bp); +int kvm_arch_insert_hw_breakpoint(target_ulong addr, + target_ulong len, int type); +int kvm_arch_remove_hw_breakpoint(target_ulong addr, + target_ulong len, int type); +void kvm_arch_remove_all_hw_breakpoints(void); + +void kvm_arch_update_guest_debug(CPUState *env, struct kvm_guest_debug *dbg); + +bool kvm_arch_stop_on_emulation_error(CPUState *env); + +int kvm_check_extension(KVMState *s, unsigned int extension); + +uint32_t kvm_arch_get_supported_cpuid(CPUState *env, uint32_t function, + uint32_t index, int reg); +void kvm_cpu_synchronize_state(CPUState *env); +void kvm_cpu_synchronize_post_reset(CPUState *env); +void kvm_cpu_synchronize_post_init(CPUState *env); + +/* generic hooks - to be moved/refactored once there are more users */ + +static inline void cpu_synchronize_state(CPUState *env) +{ + if (kvm_enabled()) { + kvm_cpu_synchronize_state(env); + } +} + +static inline void cpu_synchronize_post_reset(CPUState *env) +{ + if (kvm_enabled()) { + kvm_cpu_synchronize_post_reset(env); + } +} + +static inline void cpu_synchronize_post_init(CPUState *env) +{ + if (kvm_enabled()) { + kvm_cpu_synchronize_post_init(env); + } +} + + +#if !defined(CONFIG_USER_ONLY) +int kvm_physical_memory_addr_from_ram(KVMState *s, ram_addr_t ram_addr, + target_phys_addr_t *phys_addr); +#endif + +#endif +int kvm_set_ioeventfd_mmio_long(int fd, uint32_t adr, uint32_t val, bool assign); + +int kvm_set_ioeventfd_pio_word(int fd, uint16_t adr, uint16_t val, bool assign); +#endif diff --git a/libfdt_env.h b/libfdt_env.h new file mode 100644 index 0000000..ee0419f --- /dev/null +++ b/libfdt_env.h @@ -0,0 +1,40 @@ +/* + * 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 . + * + * Copyright IBM Corp. 2008 + * Authors: Hollis Blanchard + * + */ + +#ifndef _LIBFDT_ENV_H +#define _LIBFDT_ENV_H + +#include +#include +#include +#include +#include + +#if __BYTE_ORDER == __BIG_ENDIAN +#define fdt32_to_cpu(x) (x) +#define cpu_to_fdt32(x) (x) +#define fdt64_to_cpu(x) (x) +#define cpu_to_fdt64(x) (x) +#else +#define fdt32_to_cpu(x) (bswap_32((x))) +#define cpu_to_fdt32(x) (bswap_32((x))) +#define fdt64_to_cpu(x) (bswap_64((x))) +#define cpu_to_fdt64(x) (bswap_64((x))) +#endif + +#endif /* _LIBFDT_ENV_H */ diff --git a/linux-aio.c b/linux-aio.c new file mode 100644 index 0000000..68f4b3d --- /dev/null +++ b/linux-aio.c @@ -0,0 +1,261 @@ +/* + * Linux native AIO support. + * + * Copyright (C) 2009 IBM, Corp. + * Copyright (C) 2009 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 "qemu-common.h" +#include "qemu-aio.h" +#include "block_int.h" +#include "block/raw-posix-aio.h" + +#include +#include + +/* + * Queue size (per-device). + * + * XXX: eventually we need to communicate this to the guest and/or make it + * tunable by the guest. If we get more outstanding requests at a time + * than this we will get EAGAIN from io_submit which is communicated to + * the guest as an I/O error. + */ +#define MAX_EVENTS 128 + +struct qemu_laiocb { + BlockDriverAIOCB common; + struct qemu_laio_state *ctx; + struct iocb iocb; + ssize_t ret; + size_t nbytes; + int async_context_id; + QLIST_ENTRY(qemu_laiocb) node; +}; + +struct qemu_laio_state { + io_context_t ctx; + int efd; + int count; + QLIST_HEAD(, qemu_laiocb) completed_reqs; +}; + +static inline ssize_t io_event_ret(struct io_event *ev) +{ + return (ssize_t)(((uint64_t)ev->res2 << 32) | ev->res); +} + +/* + * Completes an AIO request (calls the callback and frees the ACB). + * Be sure to be in the right AsyncContext before calling this function. + */ +static void qemu_laio_process_completion(struct qemu_laio_state *s, + struct qemu_laiocb *laiocb) +{ + int ret; + + s->count--; + + ret = laiocb->ret; + if (ret != -ECANCELED) { + if (ret == laiocb->nbytes) + ret = 0; + else if (ret >= 0) + ret = -EINVAL; + + laiocb->common.cb(laiocb->common.opaque, ret); + } + + qemu_aio_release(laiocb); +} + +/* + * Processes all queued AIO requests, i.e. requests that have return from OS + * but their callback was not called yet. Requests that cannot have their + * callback called in the current AsyncContext, remain in the queue. + * + * Returns 1 if at least one request could be completed, 0 otherwise. + */ +static int qemu_laio_process_requests(void *opaque) +{ + struct qemu_laio_state *s = opaque; + struct qemu_laiocb *laiocb, *next; + int res = 0; + + QLIST_FOREACH_SAFE (laiocb, &s->completed_reqs, node, next) { + if (laiocb->async_context_id == get_async_context_id()) { + qemu_laio_process_completion(s, laiocb); + QLIST_REMOVE(laiocb, node); + res = 1; + } + } + + return res; +} + +/* + * Puts a request in the completion queue so that its callback is called the + * next time when it's possible. If we already are in the right AsyncContext, + * the request is completed immediately instead. + */ +static void qemu_laio_enqueue_completed(struct qemu_laio_state *s, + struct qemu_laiocb* laiocb) +{ + if (laiocb->async_context_id == get_async_context_id()) { + qemu_laio_process_completion(s, laiocb); + } else { + QLIST_INSERT_HEAD(&s->completed_reqs, laiocb, node); + } +} + +static void qemu_laio_completion_cb(void *opaque) +{ + struct qemu_laio_state *s = opaque; + + while (1) { + struct io_event events[MAX_EVENTS]; + uint64_t val; + ssize_t ret; + struct timespec ts = { 0 }; + int nevents, i; + + do { + ret = read(s->efd, &val, sizeof(val)); + } while (ret == -1 && errno == EINTR); + + if (ret == -1 && errno == EAGAIN) + break; + + if (ret != 8) + break; + + do { + nevents = io_getevents(s->ctx, val, MAX_EVENTS, events, &ts); + } while (nevents == -EINTR); + + for (i = 0; i < nevents; i++) { + struct iocb *iocb = events[i].obj; + struct qemu_laiocb *laiocb = + container_of(iocb, struct qemu_laiocb, iocb); + + laiocb->ret = io_event_ret(&events[i]); + qemu_laio_enqueue_completed(s, laiocb); + } + } +} + +static int qemu_laio_flush_cb(void *opaque) +{ + struct qemu_laio_state *s = opaque; + + return (s->count > 0) ? 1 : 0; +} + +static void laio_cancel(BlockDriverAIOCB *blockacb) +{ + struct qemu_laiocb *laiocb = (struct qemu_laiocb *)blockacb; + struct io_event event; + int ret; + + if (laiocb->ret != -EINPROGRESS) + return; + + /* + * Note that as of Linux 2.6.31 neither the block device code nor any + * filesystem implements cancellation of AIO request. + * Thus the polling loop below is the normal code path. + */ + ret = io_cancel(laiocb->ctx->ctx, &laiocb->iocb, &event); + if (ret == 0) { + laiocb->ret = -ECANCELED; + return; + } + + /* + * We have to wait for the iocb to finish. + * + * The only way to get the iocb status update is by polling the io context. + * We might be able to do this slightly more optimal by removing the + * O_NONBLOCK flag. + */ + while (laiocb->ret == -EINPROGRESS) + qemu_laio_completion_cb(laiocb->ctx); +} + +static AIOPool laio_pool = { + .aiocb_size = sizeof(struct qemu_laiocb), + .cancel = laio_cancel, +}; + +BlockDriverAIOCB *laio_submit(BlockDriverState *bs, void *aio_ctx, int fd, + int64_t sector_num, QEMUIOVector *qiov, int nb_sectors, + BlockDriverCompletionFunc *cb, void *opaque, int type) +{ + struct qemu_laio_state *s = aio_ctx; + struct qemu_laiocb *laiocb; + struct iocb *iocbs; + off_t offset = sector_num * 512; + + laiocb = qemu_aio_get(&laio_pool, bs, cb, opaque); + if (!laiocb) + return NULL; + laiocb->nbytes = nb_sectors * 512; + laiocb->ctx = s; + laiocb->ret = -EINPROGRESS; + laiocb->async_context_id = get_async_context_id(); + + iocbs = &laiocb->iocb; + + switch (type) { + case QEMU_AIO_WRITE: + io_prep_pwritev(iocbs, fd, qiov->iov, qiov->niov, offset); + break; + case QEMU_AIO_READ: + io_prep_preadv(iocbs, fd, qiov->iov, qiov->niov, offset); + break; + default: + fprintf(stderr, "%s: invalid AIO request type 0x%x.\n", + __func__, type); + goto out_free_aiocb; + } + io_set_eventfd(&laiocb->iocb, s->efd); + s->count++; + + if (io_submit(s->ctx, 1, &iocbs) < 0) + goto out_dec_count; + return &laiocb->common; + +out_free_aiocb: + qemu_aio_release(laiocb); +out_dec_count: + s->count--; + return NULL; +} + +void *laio_init(void) +{ + struct qemu_laio_state *s; + + s = qemu_mallocz(sizeof(*s)); + QLIST_INIT(&s->completed_reqs); + s->efd = eventfd(0, 0); + if (s->efd == -1) + goto out_free_state; + fcntl(s->efd, F_SETFL, O_NONBLOCK); + + if (io_setup(MAX_EVENTS, &s->ctx) != 0) + goto out_close_efd; + + qemu_aio_set_fd_handler(s->efd, qemu_laio_completion_cb, NULL, + qemu_laio_flush_cb, qemu_laio_process_requests, s); + + return s; + +out_close_efd: + close(s->efd); +out_free_state: + qemu_free(s); + return NULL; +} diff --git a/linux-user/alpha/syscall.h b/linux-user/alpha/syscall.h new file mode 100644 index 0000000..15a0100 --- /dev/null +++ b/linux-user/alpha/syscall.h @@ -0,0 +1,253 @@ +/* default linux values for the selectors */ +#define __USER_DS (1) + +struct target_pt_regs { + abi_ulong r0; + abi_ulong r1; + abi_ulong r2; + abi_ulong r3; + abi_ulong r4; + abi_ulong r5; + abi_ulong r6; + abi_ulong r7; + abi_ulong r8; + abi_ulong r19; + abi_ulong r20; + abi_ulong r21; + abi_ulong r22; + abi_ulong r23; + abi_ulong r24; + abi_ulong r25; + abi_ulong r26; + abi_ulong r27; + abi_ulong r28; + abi_ulong hae; +/* JRP - These are the values provided to a0-a2 by PALcode */ + abi_ulong trap_a0; + abi_ulong trap_a1; + abi_ulong trap_a2; +/* These are saved by PAL-code: */ + abi_ulong ps; + abi_ulong pc; + abi_ulong gp; + abi_ulong r16; + abi_ulong r17; + abi_ulong r18; +/* Those is needed by qemu to temporary store the user stack pointer */ + abi_ulong usp; + abi_ulong unique; +}; + +#define UNAME_MACHINE "alpha" + +#undef TARGET_EDEADLK +#define TARGET_EDEADLK 11 +#undef TARGET_EAGAIN +#define TARGET_EAGAIN 35 +#undef TARGET_EINPROGRESS +#define TARGET_EINPROGRESS 36 +#undef TARGET_EALREADY +#define TARGET_EALREADY 37 +#undef TARGET_ENOTSOCK +#define TARGET_ENOTSOCK 38 +#undef TARGET_EDESTADDRREQ +#define TARGET_EDESTADDRREQ 39 +#undef TARGET_EMSGSIZE +#define TARGET_EMSGSIZE 40 +#undef TARGET_EPROTOTYPE +#define TARGET_EPROTOTYPE 41 +#undef TARGET_ENOPROTOOPT +#define TARGET_ENOPROTOOPT 42 +#undef TARGET_EPROTONOSUPPORT +#define TARGET_EPROTONOSUPPORT 43 +#undef TARGET_ESOCKTNOSUPPORT +#define TARGET_ESOCKTNOSUPPORT 44 +#undef TARGET_EOPNOTSUPP +#define TARGET_EOPNOTSUPP 45 +#undef TARGET_EPFNOSUPPORT +#define TARGET_EPFNOSUPPORT 46 +#undef TARGET_EAFNOSUPPORT +#define TARGET_EAFNOSUPPORT 47 +#undef TARGET_EADDRINUSE +#define TARGET_EADDRINUSE 48 +#undef TARGET_EADDRNOTAVAIL +#define TARGET_EADDRNOTAVAIL 49 +#undef TARGET_ENETDOWN +#define TARGET_ENETDOWN 50 +#undef TARGET_ENETUNREACH +#define TARGET_ENETUNREACH 51 +#undef TARGET_ENETRESET +#define TARGET_ENETRESET 52 +#undef TARGET_ECONNABORTED +#define TARGET_ECONNABORTED 53 +#undef TARGET_ECONNRESET +#define TARGET_ECONNRESET 54 +#undef TARGET_ENOBUFS +#define TARGET_ENOBUFS 55 +#undef TARGET_EISCONN +#define TARGET_EISCONN 56 +#undef TARGET_ENOTCONN +#define TARGET_ENOTCONN 57 +#undef TARGET_ESHUTDOWN +#define TARGET_ESHUTDOWN 58 +#undef TARGET_ETOOMANYREFS +#define TARGET_ETOOMANYREFS 59 +#undef TARGET_ETIMEDOUT +#define TARGET_ETIMEDOUT 60 +#undef TARGET_ECONNREFUSED +#define TARGET_ECONNREFUSED 61 +#undef TARGET_ELOOP +#define TARGET_ELOOP 62 +#undef TARGET_ENAMETOOLONG +#define TARGET_ENAMETOOLONG 63 +#undef TARGET_EHOSTDOWN +#define TARGET_EHOSTDOWN 64 +#undef TARGET_EHOSTUNREACH +#define TARGET_EHOSTUNREACH 65 +#undef TARGET_ENOTEMPTY +#define TARGET_ENOTEMPTY 66 +// Unused 67 +#undef TARGET_EUSERS +#define TARGET_EUSERS 68 +#undef TARGET_EDQUOT +#define TARGET_EDQUOT 69 +#undef TARGET_ESTALE +#define TARGET_ESTALE 70 +#undef TARGET_EREMOTE +#define TARGET_EREMOTE 71 +// Unused 72-76 +#undef TARGET_ENOLCK +#define TARGET_ENOLCK 77 +#undef TARGET_ENOSYS +#define TARGET_ENOSYS 78 +// Unused 79 +#undef TARGET_ENOMSG +#define TARGET_ENOMSG 80 +#undef TARGET_EIDRM +#define TARGET_EIDRM 81 +#undef TARGET_ENOSR +#define TARGET_ENOSR 82 +#undef TARGET_ETIME +#define TARGET_ETIME 83 +#undef TARGET_EBADMSG +#define TARGET_EBADMSG 84 +#undef TARGET_EPROTO +#define TARGET_EPROTO 85 +#undef TARGET_ENODATA +#define TARGET_ENODATA 86 +#undef TARGET_ENOSTR +#define TARGET_ENOSTR 87 +#undef TARGET_ECHRNG +#define TARGET_ECHRNG 88 +#undef TARGET_EL2NSYNC +#define TARGET_EL2NSYNC 89 +#undef TARGET_EL3HLT +#define TARGET_EL3HLT 90 +#undef TARGET_EL3RST +#define TARGET_EL3RST 91 +#undef TARGET_ENOPKG +#define TARGET_ENOPKG 92 +#undef TARGET_ELNRNG +#define TARGET_ELNRNG 93 +#undef TARGET_EUNATCH +#define TARGET_EUNATCH 94 +#undef TARGET_ENOCSI +#define TARGET_ENOCSI 95 +#undef TARGET_EL2HLT +#define TARGET_EL2HLT 96 +#undef TARGET_EBADE +#define TARGET_EBADE 97 +#undef TARGET_EBADR +#define TARGET_EBADR 98 +#undef TARGET_EXFULL +#define TARGET_EXFULL 99 +#undef TARGET_ENOANO +#define TARGET_ENOANO 100 +#undef TARGET_EBADRQC +#define TARGET_EBADRQC 101 +#undef TARGET_EBADSLT +#define TARGET_EBADSLT 102 +// Unused 103 +#undef TARGET_EBFONT +#define TARGET_EBFONT 104 +#undef TARGET_ENONET +#define TARGET_ENONET 105 +#undef TARGET_ENOLINK +#define TARGET_ENOLINK 106 +#undef TARGET_EADV +#define TARGET_EADV 107 +#undef TARGET_ESRMNT +#define TARGET_ESRMNT 108 +#undef TARGET_ECOMM +#define TARGET_ECOMM 109 +#undef TARGET_EMULTIHOP +#define TARGET_EMULTIHOP 110 +#undef TARGET_EDOTDOT +#define TARGET_EDOTDOT 111 +#undef TARGET_EOVERFLOW +#define TARGET_EOVERFLOW 112 +#undef TARGET_ENOTUNIQ +#define TARGET_ENOTUNIQ 113 +#undef TARGET_EBADFD +#define TARGET_EBADFD 114 +#undef TARGET_EREMCHG +#define TARGET_EREMCHG 115 +#undef TARGET_EILSEQ +#define TARGET_EILSEQ 116 + +// Same as default 117-121 + +#undef TARGET_ELIBACC +#define TARGET_ELIBACC 122 +#undef TARGET_ELIBBAD +#define TARGET_ELIBBAD 123 +#undef TARGET_ELIBSCN +#define TARGET_ELIBSCN 124 +#undef TARGET_ELIBMAX +#define TARGET_ELIBMAX 125 +#undef TARGET_ELIBEXEC +#define TARGET_ELIBEXEC 126 +#undef TARGET_ERESTART +#define TARGET_ERESTART 127 +#undef TARGET_ESTRPIPE +#define TARGET_ESTRPIPE 128 +#undef TARGET_ENOMEDIUM +#define TARGET_ENOMEDIUM 129 +#undef TARGET_EMEDIUMTYPE +#define TARGET_EMEDIUMTYPE 130 +#undef TARGET_ECANCELED +#define TARGET_ECANCELED 131 +#undef TARGET_ENOKEY +#define TARGET_ENOKEY 132 +#undef TARGET_EKEYEXPIRED +#define TARGET_EKEYEXPIRED 133 +#undef TARGET_EKEYREVOKED +#define TARGET_EKEYREVOKED 134 +#undef TARGET_EKEYREJECTED +#define TARGET_EKEYREJECTED 135 +#undef TARGET_EOWNERDEAD +#define TARGET_EOWNERDEAD 136 +#undef TARGET_ENOTRECOVERABLE +#define TARGET_ENOTRECOVERABLE 137 +#undef TARGET_ERFKILL +#define TARGET_ERFKILL 138 + +// For sys_osf_getsysinfo +#define TARGET_GSI_UACPROC 8 +#define TARGET_GSI_IEEE_FP_CONTROL 45 +#define TARGET_GSI_IEEE_STATE_AT_SIGNAL 46 +#define TARGET_GSI_PROC_TYPE 60 +#define TARGET_GSI_GET_HWRPB 101 + +// For sys_ofs_setsysinfo +#define TARGET_SSI_NVPAIRS 1 +#define TARGET_SSI_IEEE_FP_CONTROL 14 +#define TARGET_SSI_IEEE_STATE_AT_SIGNAL 15 +#define TARGET_SSI_IEEE_IGNORE_STATE_AT_SIGNAL 16 +#define TARGET_SSI_IEEE_RAISE_EXCEPTION 1001 + +#define TARGET_SSIN_UACPROC 6 + +#define TARGET_UAC_NOPRINT 1 +#define TARGET_UAC_NOFIX 2 +#define TARGET_UAC_SIGBUS 4 diff --git a/linux-user/alpha/syscall_nr.h b/linux-user/alpha/syscall_nr.h new file mode 100644 index 0000000..7182223 --- /dev/null +++ b/linux-user/alpha/syscall_nr.h @@ -0,0 +1,421 @@ +#define TARGET_NR_osf_syscall 0 /* not implemented */ +#define TARGET_NR_exit 1 +#define TARGET_NR_fork 2 +#define TARGET_NR_read 3 +#define TARGET_NR_write 4 +#define TARGET_NR_osf_old_open 5 /* not implemented */ +#define TARGET_NR_close 6 +#define TARGET_NR_osf_wait4 7 +#define TARGET_NR_osf_old_creat 8 /* not implemented */ +#define TARGET_NR_link 9 +#define TARGET_NR_unlink 10 +#define TARGET_NR_osf_execve 11 /* not implemented */ +#define TARGET_NR_chdir 12 +#define TARGET_NR_fchdir 13 +#define TARGET_NR_mknod 14 +#define TARGET_NR_chmod 15 +#define TARGET_NR_chown 16 +#define TARGET_NR_brk 17 +#define TARGET_NR_osf_getfsstat 18 /* not implemented */ +#define TARGET_NR_lseek 19 +#define TARGET_NR_getxpid 20 +#define TARGET_NR_osf_mount 21 +#define TARGET_NR_umount 22 +#define TARGET_NR_setuid 23 +#define TARGET_NR_getxuid 24 +#define TARGET_NR_exec_with_loader 25 /* not implemented */ +#define TARGET_NR_ptrace 26 +#define TARGET_NR_osf_nrecvmsg 27 /* not implemented */ +#define TARGET_NR_osf_nsendmsg 28 /* not implemented */ +#define TARGET_NR_osf_nrecvfrom 29 /* not implemented */ +#define TARGET_NR_osf_naccept 30 /* not implemented */ +#define TARGET_NR_osf_ngetpeername 31 /* not implemented */ +#define TARGET_NR_osf_ngetsockname 32 /* not implemented */ +#define TARGET_NR_access 33 +#define TARGET_NR_osf_chflags 34 /* not implemented */ +#define TARGET_NR_osf_fchflags 35 /* not implemented */ +#define TARGET_NR_sync 36 +#define TARGET_NR_kill 37 +#define TARGET_NR_osf_old_stat 38 /* not implemented */ +#define TARGET_NR_setpgid 39 +#define TARGET_NR_osf_old_lstat 40 /* not implemented */ +#define TARGET_NR_dup 41 +#define TARGET_NR_pipe 42 +#define TARGET_NR_osf_set_program_attributes 43 +#define TARGET_NR_osf_profil 44 /* not implemented */ +#define TARGET_NR_open 45 +#define TARGET_NR_osf_old_sigaction 46 /* not implemented */ +#define TARGET_NR_getxgid 47 +#define TARGET_NR_osf_sigprocmask 48 +#define TARGET_NR_osf_getlogin 49 /* not implemented */ +#define TARGET_NR_osf_setlogin 50 /* not implemented */ +#define TARGET_NR_acct 51 +#define TARGET_NR_sigpending 52 + +#define TARGET_NR_ioctl 54 +#define TARGET_NR_osf_reboot 55 /* not implemented */ +#define TARGET_NR_osf_revoke 56 /* not implemented */ +#define TARGET_NR_symlink 57 +#define TARGET_NR_readlink 58 +#define TARGET_NR_execve 59 +#define TARGET_NR_umask 60 +#define TARGET_NR_chroot 61 +#define TARGET_NR_osf_old_fstat 62 /* not implemented */ +#define TARGET_NR_getpgrp 63 +#define TARGET_NR_getpagesize 64 +#define TARGET_NR_osf_mremap 65 /* not implemented */ +#define TARGET_NR_vfork 66 +#define TARGET_NR_stat 67 +#define TARGET_NR_lstat 68 +#define TARGET_NR_osf_sbrk 69 /* not implemented */ +#define TARGET_NR_osf_sstk 70 /* not implemented */ +#define TARGET_NR_mmap 71 /* OSF/1 mmap is superset of Linux */ +#define TARGET_NR_osf_old_vadvise 72 /* not implemented */ +#define TARGET_NR_munmap 73 +#define TARGET_NR_mprotect 74 +#define TARGET_NR_madvise 75 +#define TARGET_NR_vhangup 76 +#define TARGET_NR_osf_kmodcall 77 /* not implemented */ +#define TARGET_NR_osf_mincore 78 /* not implemented */ +#define TARGET_NR_getgroups 79 +#define TARGET_NR_setgroups 80 +#define TARGET_NR_osf_old_getpgrp 81 /* not implemented */ +#define TARGET_NR_setpgrp 82 /* BSD alias for setpgid */ +#define TARGET_NR_osf_setitimer 83 +#define TARGET_NR_osf_old_wait 84 /* not implemented */ +#define TARGET_NR_osf_table 85 /* not implemented */ +#define TARGET_NR_osf_getitimer 86 +#define TARGET_NR_gethostname 87 +#define TARGET_NR_sethostname 88 +#define TARGET_NR_getdtablesize 89 +#define TARGET_NR_dup2 90 +#define TARGET_NR_fstat 91 +#define TARGET_NR_fcntl 92 +#define TARGET_NR_osf_select 93 +#define TARGET_NR_poll 94 +#define TARGET_NR_fsync 95 +#define TARGET_NR_setpriority 96 +#define TARGET_NR_socket 97 +#define TARGET_NR_connect 98 +#define TARGET_NR_accept 99 +#define TARGET_NR_getpriority 100 +#define TARGET_NR_send 101 +#define TARGET_NR_recv 102 +#define TARGET_NR_sigreturn 103 +#define TARGET_NR_bind 104 +#define TARGET_NR_setsockopt 105 +#define TARGET_NR_listen 106 +#define TARGET_NR_osf_plock 107 /* not implemented */ +#define TARGET_NR_osf_old_sigvec 108 /* not implemented */ +#define TARGET_NR_osf_old_sigblock 109 /* not implemented */ +#define TARGET_NR_osf_old_sigsetmask 110 /* not implemented */ +#define TARGET_NR_sigsuspend 111 +#define TARGET_NR_osf_sigstack 112 +#define TARGET_NR_recvmsg 113 +#define TARGET_NR_sendmsg 114 +#define TARGET_NR_osf_old_vtrace 115 /* not implemented */ +#define TARGET_NR_osf_gettimeofday 116 +#define TARGET_NR_osf_getrusage 117 +#define TARGET_NR_getsockopt 118 + +#define TARGET_NR_readv 120 +#define TARGET_NR_writev 121 +#define TARGET_NR_osf_settimeofday 122 +#define TARGET_NR_fchown 123 +#define TARGET_NR_fchmod 124 +#define TARGET_NR_recvfrom 125 +#define TARGET_NR_setreuid 126 +#define TARGET_NR_setregid 127 +#define TARGET_NR_rename 128 +#define TARGET_NR_truncate 129 +#define TARGET_NR_ftruncate 130 +#define TARGET_NR_flock 131 +#define TARGET_NR_setgid 132 +#define TARGET_NR_sendto 133 +#define TARGET_NR_shutdown 134 +#define TARGET_NR_socketpair 135 +#define TARGET_NR_mkdir 136 +#define TARGET_NR_rmdir 137 +#define TARGET_NR_osf_utimes 138 +#define TARGET_NR_osf_old_sigreturn 139 /* not implemented */ +#define TARGET_NR_osf_adjtime 140 /* not implemented */ +#define TARGET_NR_getpeername 141 +#define TARGET_NR_osf_gethostid 142 /* not implemented */ +#define TARGET_NR_osf_sethostid 143 /* not implemented */ +#define TARGET_NR_getrlimit 144 +#define TARGET_NR_setrlimit 145 +#define TARGET_NR_osf_old_killpg 146 /* not implemented */ +#define TARGET_NR_setsid 147 +#define TARGET_NR_quotactl 148 +#define TARGET_NR_osf_oldquota 149 /* not implemented */ +#define TARGET_NR_getsockname 150 + +#define TARGET_NR_osf_pid_block 153 /* not implemented */ +#define TARGET_NR_osf_pid_unblock 154 /* not implemented */ + +#define TARGET_NR_sigaction 156 +#define TARGET_NR_osf_sigwaitprim 157 /* not implemented */ +#define TARGET_NR_osf_nfssvc 158 /* not implemented */ +#define TARGET_NR_osf_getdirentries 159 +#define TARGET_NR_osf_statfs 160 +#define TARGET_NR_osf_fstatfs 161 + +#define TARGET_NR_osf_asynch_daemon 163 /* not implemented */ +#define TARGET_NR_osf_getfh 164 /* not implemented */ +#define TARGET_NR_osf_getdomainname 165 +#define TARGET_NR_setdomainname 166 + +#define TARGET_NR_osf_exportfs 169 /* not implemented */ + +#define TARGET_NR_osf_alt_plock 181 /* not implemented */ + +#define TARGET_NR_osf_getmnt 184 /* not implemented */ + +#define TARGET_NR_osf_alt_sigpending 187 /* not implemented */ +#define TARGET_NR_osf_alt_setsid 188 /* not implemented */ + +#define TARGET_NR_osf_swapon 199 +#define TARGET_NR_msgctl 200 +#define TARGET_NR_msgget 201 +#define TARGET_NR_msgrcv 202 +#define TARGET_NR_msgsnd 203 +#define TARGET_NR_semctl 204 +#define TARGET_NR_semget 205 +#define TARGET_NR_semop 206 +#define TARGET_NR_osf_utsname 207 +#define TARGET_NR_lchown 208 +#define TARGET_NR_osf_shmat 209 +#define TARGET_NR_shmctl 210 +#define TARGET_NR_shmdt 211 +#define TARGET_NR_shmget 212 +#define TARGET_NR_osf_mvalid 213 /* not implemented */ +#define TARGET_NR_osf_getaddressconf 214 /* not implemented */ +#define TARGET_NR_osf_msleep 215 /* not implemented */ +#define TARGET_NR_osf_mwakeup 216 /* not implemented */ +#define TARGET_NR_msync 217 +#define TARGET_NR_osf_signal 218 /* not implemented */ +#define TARGET_NR_osf_utc_gettime 219 /* not implemented */ +#define TARGET_NR_osf_utc_adjtime 220 /* not implemented */ + +#define TARGET_NR_osf_security 222 /* not implemented */ +#define TARGET_NR_osf_kloadcall 223 /* not implemented */ + +#define TARGET_NR_getpgid 233 +#define TARGET_NR_getsid 234 +#define TARGET_NR_sigaltstack 235 +#define TARGET_NR_osf_waitid 236 /* not implemented */ +#define TARGET_NR_osf_priocntlset 237 /* not implemented */ +#define TARGET_NR_osf_sigsendset 238 /* not implemented */ +#define TARGET_NR_osf_set_speculative 239 /* not implemented */ +#define TARGET_NR_osf_msfs_syscall 240 /* not implemented */ +#define TARGET_NR_osf_sysinfo 241 +#define TARGET_NR_osf_uadmin 242 /* not implemented */ +#define TARGET_NR_osf_fuser 243 /* not implemented */ +#define TARGET_NR_osf_proplist_syscall 244 +#define TARGET_NR_osf_ntp_adjtime 245 /* not implemented */ +#define TARGET_NR_osf_ntp_gettime 246 /* not implemented */ +#define TARGET_NR_osf_pathconf 247 /* not implemented */ +#define TARGET_NR_osf_fpathconf 248 /* not implemented */ + +#define TARGET_NR_osf_uswitch 250 /* not implemented */ +#define TARGET_NR_osf_usleep_thread 251 +#define TARGET_NR_osf_audcntl 252 /* not implemented */ +#define TARGET_NR_osf_audgen 253 /* not implemented */ +#define TARGET_NR_sysfs 254 +#define TARGET_NR_osf_subsys_info 255 /* not implemented */ +#define TARGET_NR_osf_getsysinfo 256 +#define TARGET_NR_osf_setsysinfo 257 +#define TARGET_NR_osf_afs_syscall 258 /* not implemented */ +#define TARGET_NR_osf_swapctl 259 /* not implemented */ +#define TARGET_NR_osf_memcntl 260 /* not implemented */ +#define TARGET_NR_osf_fdatasync 261 /* not implemented */ + + +/* + * Linux-specific system calls begin at 300 + */ +#define TARGET_NR_bdflush 300 +#define TARGET_NR_sethae 301 +#define TARGET_NR_mount 302 +#define TARGET_NR_old_adjtimex 303 +#define TARGET_NR_swapoff 304 +#define TARGET_NR_getdents 305 +#define TARGET_NR_create_module 306 +#define TARGET_NR_init_module 307 +#define TARGET_NR_delete_module 308 +#define TARGET_NR_get_kernel_syms 309 +#define TARGET_NR_syslog 310 +#define TARGET_NR_reboot 311 +#define TARGET_NR_clone 312 +#define TARGET_NR_uselib 313 +#define TARGET_NR_mlock 314 +#define TARGET_NR_munlock 315 +#define TARGET_NR_mlockall 316 +#define TARGET_NR_munlockall 317 +#define TARGET_NR_sysinfo 318 +#define TARGET_NR__sysctl 319 +/* 320 was sys_idle. */ +#define TARGET_NR_oldumount 321 +#define TARGET_NR_swapon 322 +#define TARGET_NR_times 323 +#define TARGET_NR_personality 324 +#define TARGET_NR_setfsuid 325 +#define TARGET_NR_setfsgid 326 +#define TARGET_NR_ustat 327 +#define TARGET_NR_statfs 328 +#define TARGET_NR_fstatfs 329 +#define TARGET_NR_sched_setparam 330 +#define TARGET_NR_sched_getparam 331 +#define TARGET_NR_sched_setscheduler 332 +#define TARGET_NR_sched_getscheduler 333 +#define TARGET_NR_sched_yield 334 +#define TARGET_NR_sched_get_priority_max 335 +#define TARGET_NR_sched_get_priority_min 336 +#define TARGET_NR_sched_rr_get_interval 337 +#define TARGET_NR_afs_syscall 338 +#define TARGET_NR_uname 339 +#define TARGET_NR_nanosleep 340 +#define TARGET_NR_mremap 341 +#define TARGET_NR_nfsservctl 342 +#define TARGET_NR_setresuid 343 +#define TARGET_NR_getresuid 344 +#define TARGET_NR_pciconfig_read 345 +#define TARGET_NR_pciconfig_write 346 +#define TARGET_NR_query_module 347 +#define TARGET_NR_prctl 348 +#define TARGET_NR_pread64 349 +#define TARGET_NR_pwrite64 350 +#define TARGET_NR_rt_sigreturn 351 +#define TARGET_NR_rt_sigaction 352 +#define TARGET_NR_rt_sigprocmask 353 +#define TARGET_NR_rt_sigpending 354 +#define TARGET_NR_rt_sigtimedwait 355 +#define TARGET_NR_rt_sigqueueinfo 356 +#define TARGET_NR_rt_sigsuspend 357 +#define TARGET_NR_select 358 +#define TARGET_NR_gettimeofday 359 +#define TARGET_NR_settimeofday 360 +#define TARGET_NR_getitimer 361 +#define TARGET_NR_setitimer 362 +#define TARGET_NR_utimes 363 +#define TARGET_NR_getrusage 364 +#define TARGET_NR_wait4 365 +#define TARGET_NR_adjtimex 366 +#define TARGET_NR_getcwd 367 +#define TARGET_NR_capget 368 +#define TARGET_NR_capset 369 +#define TARGET_NR_sendfile 370 +#define TARGET_NR_setresgid 371 +#define TARGET_NR_getresgid 372 +#define TARGET_NR_dipc 373 +#define TARGET_NR_pivot_root 374 +#define TARGET_NR_mincore 375 +#define TARGET_NR_pciconfig_iobase 376 +#define TARGET_NR_getdents64 377 +#define TARGET_NR_gettid 378 +#define TARGET_NR_readahead 379 +/* 380 is unused */ +#define TARGET_NR_tkill 381 +#define TARGET_NR_setxattr 382 +#define TARGET_NR_lsetxattr 383 +#define TARGET_NR_fsetxattr 384 +#define TARGET_NR_getxattr 385 +#define TARGET_NR_lgetxattr 386 +#define TARGET_NR_fgetxattr 387 +#define TARGET_NR_listxattr 388 +#define TARGET_NR_llistxattr 389 +#define TARGET_NR_flistxattr 390 +#define TARGET_NR_removexattr 391 +#define TARGET_NR_lremovexattr 392 +#define TARGET_NR_fremovexattr 393 +#define TARGET_NR_futex 394 +#define TARGET_NR_sched_setaffinity 395 +#define TARGET_NR_sched_getaffinity 396 +#define TARGET_NR_tuxcall 397 +#define TARGET_NR_io_setup 398 +#define TARGET_NR_io_destroy 399 +#define TARGET_NR_io_getevents 400 +#define TARGET_NR_io_submit 401 +#define TARGET_NR_io_cancel 402 +#define TARGET_NR_exit_group 405 +#define TARGET_NR_lookup_dcookie 406 +#define TARGET_NR_sys_epoll_create 407 +#define TARGET_NR_sys_epoll_ctl 408 +#define TARGET_NR_sys_epoll_wait 409 +#define TARGET_NR_remap_file_pages 410 +#define TARGET_NR_set_tid_address 411 +#define TARGET_NR_restart_syscall 412 +#define TARGET_NR_fadvise64 413 +#define TARGET_NR_timer_create 414 +#define TARGET_NR_timer_settime 415 +#define TARGET_NR_timer_gettime 416 +#define TARGET_NR_timer_getoverrun 417 +#define TARGET_NR_timer_delete 418 +#define TARGET_NR_clock_settime 419 +#define TARGET_NR_clock_gettime 420 +#define TARGET_NR_clock_getres 421 +#define TARGET_NR_clock_nanosleep 422 +#define TARGET_NR_semtimedop 423 +#define TARGET_NR_tgkill 424 +#define TARGET_NR_stat64 425 +#define TARGET_NR_lstat64 426 +#define TARGET_NR_fstat64 427 +#define TARGET_NR_vserver 428 +#define TARGET_NR_mbind 429 +#define TARGET_NR_get_mempolicy 430 +#define TARGET_NR_set_mempolicy 431 +#define TARGET_NR_mq_open 432 +#define TARGET_NR_mq_unlink 433 +#define TARGET_NR_mq_timedsend 434 +#define TARGET_NR_mq_timedreceive 435 +#define TARGET_NR_mq_notify 436 +#define TARGET_NR_mq_getsetattr 437 +#define TARGET_NR_waitid 438 +#define TARGET_NR_add_key 439 +#define TARGET_NR_request_key 440 +#define TARGET_NR_keyctl 441 +#define TARGET_NR_ioprio_set 442 +#define TARGET_NR_ioprio_get 443 +#define TARGET_NR_inotify_init 444 +#define TARGET_NR_inotify_add_watch 445 +#define TARGET_NR_inotify_rm_watch 446 +#define TARGET_NR_fdatasync 447 +#define TARGET_NR_kexec_load 448 +#define TARGET_NR_migrate_pages 449 +#define TARGET_NR_openat 450 +#define TARGET_NR_mkdirat 451 +#define TARGET_NR_mknodat 452 +#define TARGET_NR_fchownat 453 +#define TARGET_NR_futimesat 454 +#define TARGET_NR_fstatat64 455 +#define TARGET_NR_unlinkat 456 +#define TARGET_NR_renameat 457 +#define TARGET_NR_linkat 458 +#define TARGET_NR_symlinkat 459 +#define TARGET_NR_readlinkat 460 +#define TARGET_NR_fchmodat 461 +#define TARGET_NR_faccessat 462 +#define TARGET_NR_pselect6 463 +#define TARGET_NR_ppoll 464 +#define TARGET_NR_unshare 465 +#define TARGET_NR_set_robust_list 466 +#define TARGET_NR_get_robust_list 467 +#define TARGET_NR_splice 468 +#define TARGET_NR_sync_file_range 469 +#define TARGET_NR_tee 470 +#define TARGET_NR_vmsplice 471 +#define TARGET_NR_move_pages 472 +#define TARGET_NR_getcpu 473 +#define TARGET_NR_epoll_pwait 474 +#define TARGET_NR_utimensat 475 +#define TARGET_NR_signalfd 476 +#define TARGET_NR_timerfd 477 +#define TARGET_NR_eventfd 478 + +/* The following aliases are defined in order to match up with the + standard i386 syscalls implemented in syscalls.c. */ +#define TARGET_NR_chown32 TARGET_NR_chown +#define TARGET_NR_setuid32 TARGET_NR_setuid +#define TARGET_NR_setgid32 TARGET_NR_setgid +#define TARGET_NR_setfsuid32 TARGET_NR_setfsuid +#define TARGET_NR_setfsgid32 TARGET_NR_setfsgid diff --git a/linux-user/alpha/target_signal.h b/linux-user/alpha/target_signal.h new file mode 100644 index 0000000..94f15f6 --- /dev/null +++ b/linux-user/alpha/target_signal.h @@ -0,0 +1,56 @@ +#ifndef TARGET_SIGNAL_H +#define TARGET_SIGNAL_H + +#include "cpu.h" + +/* this struct defines a stack used during syscall handling */ + +typedef struct target_sigaltstack { + abi_ulong ss_sp; + abi_long ss_flags; + abi_ulong ss_size; +} target_stack_t; + + +/* + * sigaltstack controls + */ +#define TARGET_SS_ONSTACK 1 +#define TARGET_SS_DISABLE 2 + +#define TARGET_MINSIGSTKSZ 4096 +#define TARGET_SIGSTKSZ 16384 + +static inline abi_ulong get_sp_from_cpustate(CPUAlphaState *state) +{ + return state->ir[IR_SP]; +} + +/* From . */ +#define TARGET_GEN_INTOVF -1 /* integer overflow */ +#define TARGET_GEN_INTDIV -2 /* integer division by zero */ +#define TARGET_GEN_FLTOVF -3 /* fp overflow */ +#define TARGET_GEN_FLTDIV -4 /* fp division by zero */ +#define TARGET_GEN_FLTUND -5 /* fp underflow */ +#define TARGET_GEN_FLTINV -6 /* invalid fp operand */ +#define TARGET_GEN_FLTINE -7 /* inexact fp operand */ +#define TARGET_GEN_DECOVF -8 /* decimal overflow (for COBOL??) */ +#define TARGET_GEN_DECDIV -9 /* decimal division by zero */ +#define TARGET_GEN_DECINV -10 /* invalid decimal operand */ +#define TARGET_GEN_ROPRAND -11 /* reserved operand */ +#define TARGET_GEN_ASSERTERR -12 /* assertion error */ +#define TARGET_GEN_NULPTRERR -13 /* null pointer error */ +#define TARGET_GEN_STKOVF -14 /* stack overflow */ +#define TARGET_GEN_STRLENERR -15 /* string length error */ +#define TARGET_GEN_SUBSTRERR -16 /* substring error */ +#define TARGET_GEN_RANGERR -17 /* range error */ +#define TARGET_GEN_SUBRNG -18 +#define TARGET_GEN_SUBRNG1 -19 +#define TARGET_GEN_SUBRNG2 -20 +#define TARGET_GEN_SUBRNG3 -21 +#define TARGET_GEN_SUBRNG4 -22 +#define TARGET_GEN_SUBRNG5 -23 +#define TARGET_GEN_SUBRNG6 -24 +#define TARGET_GEN_SUBRNG7 -25 + +#endif /* TARGET_SIGNAL_H */ diff --git a/linux-user/alpha/termbits.h b/linux-user/alpha/termbits.h new file mode 100644 index 0000000..6406b6a --- /dev/null +++ b/linux-user/alpha/termbits.h @@ -0,0 +1,264 @@ +typedef unsigned char target_cc_t; +typedef unsigned int target_speed_t; +typedef unsigned int target_tcflag_t; + +#define TARGET_NCCS 19 +struct target_termios { + target_tcflag_t c_iflag; /* input mode flags */ + target_tcflag_t c_oflag; /* output mode flags */ + target_tcflag_t c_cflag; /* control mode flags */ + target_tcflag_t c_lflag; /* local mode flags */ + target_cc_t c_cc[TARGET_NCCS]; /* control characters */ + target_cc_t c_line; /* line discipline (== c_cc[19]) */ + target_speed_t c_ispeed; /* input speed */ + target_speed_t c_ospeed; /* output speed */ +}; + +/* c_cc characters */ +#define TARGET_VEOF 0 +#define TARGET_VEOL 1 +#define TARGET_VEOL2 2 +#define TARGET_VERASE 3 +#define TARGET_VWERASE 4 +#define TARGET_VKILL 5 +#define TARGET_VREPRINT 6 +#define TARGET_VSWTC 7 +#define TARGET_VINTR 8 +#define TARGET_VQUIT 9 +#define TARGET_VSUSP 10 +#define TARGET_VSTART 12 +#define TARGET_VSTOP 13 +#define TARGET_VLNEXT 14 +#define TARGET_VDISCARD 15 +#define TARGET_VMIN 16 +#define TARGET_VTIME 17 + +/* c_iflag bits */ +#define TARGET_IGNBRK 0000001 +#define TARGET_BRKINT 0000002 +#define TARGET_IGNPAR 0000004 +#define TARGET_PARMRK 0000010 +#define TARGET_INPCK 0000020 +#define TARGET_ISTRIP 0000040 +#define TARGET_INLCR 0000100 +#define TARGET_IGNCR 0000200 +#define TARGET_ICRNL 0000400 +#define TARGET_IXON 0001000 +#define TARGET_IXOFF 0002000 +#define TARGET_IXANY 0004000 +#define TARGET_IUCLC 0010000 +#define TARGET_IMAXBEL 0020000 +#define TARGET_IUTF8 0040000 + +/* c_oflag bits */ +#define TARGET_OPOST 0000001 +#define TARGET_ONLCR 0000002 +#define TARGET_OLCUC 0000004 + +#define TARGET_OCRNL 0000010 +#define TARGET_ONOCR 0000020 +#define TARGET_ONLRET 0000040 + +#define TARGET_OFILL 00000100 +#define TARGET_OFDEL 00000200 +#define TARGET_NLDLY 00001400 +#define TARGET_NL0 00000000 +#define TARGET_NL1 00000400 +#define TARGET_NL2 00001000 +#define TARGET_NL3 00001400 +#define TARGET_TABDLY 00006000 +#define TARGET_TAB0 00000000 +#define TARGET_TAB1 00002000 +#define TARGET_TAB2 00004000 +#define TARGET_TAB3 00006000 +#define TARGET_CRDLY 00030000 +#define TARGET_CR0 00000000 +#define TARGET_CR1 00010000 +#define TARGET_CR2 00020000 +#define TARGET_CR3 00030000 +#define TARGET_FFDLY 00040000 +#define TARGET_FF0 00000000 +#define TARGET_FF1 00040000 +#define TARGET_BSDLY 00100000 +#define TARGET_BS0 00000000 +#define TARGET_BS1 00100000 +#define TARGET_VTDLY 00200000 +#define TARGET_VT0 00000000 +#define TARGET_VT1 00200000 +#define TARGET_XTABS 01000000 /* Hmm.. Linux/i386 considers this part of TABDLY.. */ + +/* c_cflag bit meaning */ +#define TARGET_CBAUD 0000037 +#define TARGET_B0 0000000 /* hang up */ +#define TARGET_B50 0000001 +#define TARGET_B75 0000002 +#define TARGET_B110 0000003 +#define TARGET_B134 0000004 +#define TARGET_B150 0000005 +#define TARGET_B200 0000006 +#define TARGET_B300 0000007 +#define TARGET_B600 0000010 +#define TARGET_B1200 0000011 +#define TARGET_B1800 0000012 +#define TARGET_B2400 0000013 +#define TARGET_B4800 0000014 +#define TARGET_B9600 0000015 +#define TARGET_B19200 0000016 +#define TARGET_B38400 0000017 +#define TARGET_EXTA B19200 +#define TARGET_EXTB B38400 +#define TARGET_CBAUDEX 0000000 +#define TARGET_B57600 00020 +#define TARGET_B115200 00021 +#define TARGET_B230400 00022 +#define TARGET_B460800 00023 +#define TARGET_B500000 00024 +#define TARGET_B576000 00025 +#define TARGET_B921600 00026 +#define TARGET_B1000000 00027 +#define TARGET_B1152000 00030 +#define TARGET_B1500000 00031 +#define TARGET_B2000000 00032 +#define TARGET_B2500000 00033 +#define TARGET_B3000000 00034 +#define TARGET_B3500000 00035 +#define TARGET_B4000000 00036 + +#define TARGET_CSIZE 00001400 +#define TARGET_CS5 00000000 +#define TARGET_CS6 00000400 +#define TARGET_CS7 00001000 +#define TARGET_CS8 00001400 + +#define TARGET_CSTOPB 00002000 +#define TARGET_CREAD 00004000 +#define TARGET_PARENB 00010000 +#define TARGET_PARODD 00020000 +#define TARGET_HUPCL 00040000 + +#define TARGET_CLOCAL 00100000 +#define TARGET_CMSPAR 010000000000 /* mark or space (stick) parity */ +#define TARGET_CRTSCTS 020000000000 /* flow control */ + +/* c_lflag bits */ +#define TARGET_ISIG 0x00000080 +#define TARGET_ICANON 0x00000100 +#define TARGET_XCASE 0x00004000 +#define TARGET_ECHO 0x00000008 +#define TARGET_ECHOE 0x00000002 +#define TARGET_ECHOK 0x00000004 +#define TARGET_ECHONL 0x00000010 +#define TARGET_NOFLSH 0x80000000 +#define TARGET_TOSTOP 0x00400000 +#define TARGET_ECHOCTL 0x00000040 +#define TARGET_ECHOPRT 0x00000020 +#define TARGET_ECHOKE 0x00000001 +#define TARGET_FLUSHO 0x00800000 +#define TARGET_PENDIN 0x20000000 +#define TARGET_IEXTEN 0x00000400 + +#define TARGET_FIOCLEX TARGET_IO('f', 1) +#define TARGET_FIONCLEX TARGET_IO('f', 2) +#define TARGET_FIOASYNC TARGET_IOW('f', 125, int) +#define TARGET_FIONBIO TARGET_IOW('f', 126, int) +#define TARGET_FIONREAD TARGET_IOR('f', 127, int) +#define TARGET_TIOCINQ FIONREAD +#define TARGET_FIOQSIZE TARGET_IOR('f', 128, loff_t) + +#define TARGET_TIOCGETP TARGET_IOR('t', 8, struct target_sgttyb) +#define TARGET_TIOCSETP TARGET_IOW('t', 9, struct target_sgttyb) +#define TARGET_TIOCSETN TARGET_IOW('t', 10, struct target_sgttyb) /* TIOCSETP wo flush */ + +#define TARGET_TIOCSETC TARGET_IOW('t', 17, struct target_tchars) +#define TARGET_TIOCGETC TARGET_IOR('t', 18, struct target_tchars) +#define TARGET_TCGETS TARGET_IOR('t', 19, struct target_termios) +#define TARGET_TCSETS TARGET_IOW('t', 20, struct target_termios) +#define TARGET_TCSETSW TARGET_IOW('t', 21, struct target_termios) +#define TARGET_TCSETSF TARGET_IOW('t', 22, struct target_termios) + +#define TARGET_TCGETA TARGET_IOR('t', 23, struct target_termio) +#define TARGET_TCSETA TARGET_IOW('t', 24, struct target_termio) +#define TARGET_TCSETAW TARGET_IOW('t', 25, struct target_termio) +#define TARGET_TCSETAF TARGET_IOW('t', 28, struct target_termio) + +#define TARGET_TCSBRK TARGET_IO('t', 29) +#define TARGET_TCXONC TARGET_IO('t', 30) +#define TARGET_TCFLSH TARGET_IO('t', 31) + +#define TARGET_TIOCSWINSZ TARGET_IOW('t', 103, struct target_winsize) +#define TARGET_TIOCGWINSZ TARGET_IOR('t', 104, struct target_winsize) +#define TARGET_TIOCSTART TARGET_IO('t', 110) /* start output, like ^Q */ +#define TARGET_TIOCSTOP TARGET_IO('t', 111) /* stop output, like ^S */ +#define TARGET_TIOCOUTQ TARGET_IOR('t', 115, int) /* output queue size */ + +#define TARGET_TIOCGLTC TARGET_IOR('t', 116, struct target_ltchars) +#define TARGET_TIOCSLTC TARGET_IOW('t', 117, struct target_ltchars) +#define TARGET_TIOCSPGRP TARGET_IOW('t', 118, int) +#define TARGET_TIOCGPGRP TARGET_IOR('t', 119, int) + +#define TARGET_TIOCEXCL 0x540C +#define TARGET_TIOCNXCL 0x540D +#define TARGET_TIOCSCTTY 0x540E + +#define TARGET_TIOCSTI 0x5412 +#define TARGET_TIOCMGET 0x5415 +#define TARGET_TIOCMBIS 0x5416 +#define TARGET_TIOCMBIC 0x5417 +#define TARGET_TIOCMSET 0x5418 +# define TARGET_TIOCM_LE 0x001 +# define TARGET_TIOCM_DTR 0x002 +# define TARGET_TIOCM_RTS 0x004 +# define TARGET_TIOCM_ST 0x008 +# define TARGET_TIOCM_SR 0x010 +# define TARGET_TIOCM_CTS 0x020 +# define TARGET_TIOCM_CAR 0x040 +# define TARGET_TIOCM_RNG 0x080 +# define TARGET_TIOCM_DSR 0x100 +# define TARGET_TIOCM_CD TIOCM_CAR +# define TARGET_TIOCM_RI TIOCM_RNG +# define TARGET_TIOCM_OUT1 0x2000 +# define TARGET_TIOCM_OUT2 0x4000 +# define TARGET_TIOCM_LOOP 0x8000 + +#define TARGET_TIOCGSOFTCAR 0x5419 +#define TARGET_TIOCSSOFTCAR 0x541A +#define TARGET_TIOCLINUX 0x541C +#define TARGET_TIOCCONS 0x541D +#define TARGET_TIOCGSERIAL 0x541E +#define TARGET_TIOCSSERIAL 0x541F +#define TARGET_TIOCPKT 0x5420 +# define TARGET_TIOCPKT_DATA 0 +# define TARGET_TIOCPKT_FLUSHREAD 1 +# define TARGET_TIOCPKT_FLUSHWRITE 2 +# define TARGET_TIOCPKT_STOP 4 +# define TARGET_TIOCPKT_START 8 +# define TARGET_TIOCPKT_NOSTOP 16 +# define TARGET_TIOCPKT_DOSTOP 32 + + +#define TARGET_TIOCNOTTY 0x5422 +#define TARGET_TIOCSETD 0x5423 +#define TARGET_TIOCGETD 0x5424 +#define TARGET_TCSBRKP 0x5425 /* Needed for POSIX tcsendbreak() */ +#define TARGET_TIOCSBRK 0x5427 /* BSD compatibility */ +#define TARGET_TIOCCBRK 0x5428 /* BSD compatibility */ +#define TARGET_TIOCGSID 0x5429 /* Return the session ID of FD */ +#define TARGET_TIOCGPTN TARGET_IOR('T',0x30, unsigned int) /* Get Pty Number (of pty-mux device) */ +#define TARGET_TIOCSPTLCK TARGET_IOW('T',0x31, int) /* Lock/unlock Pty */ + +#define TARGET_TIOCSERCONFIG 0x5453 +#define TARGET_TIOCSERGWILD 0x5454 +#define TARGET_TIOCSERSWILD 0x5455 +#define TARGET_TIOCGLCKTRMIOS 0x5456 +#define TARGET_TIOCSLCKTRMIOS 0x5457 +#define TARGET_TIOCSERGSTRUCT 0x5458 /* For debugging only */ +#define TARGET_TIOCSERGETLSR 0x5459 /* Get line status register */ + /* ioctl (fd, TIOCSERGETLSR, &result) where result may be as below */ +# define TARGET_TIOCSER_TEMT 0x01 /* Transmitter physically empty */ +#define TARGET_TIOCSERGETMULTI 0x545A /* Get multiport config */ +#define TARGET_TIOCSERSETMULTI 0x545B /* Set multiport config */ + +#define TARGET_TIOCMIWAIT 0x545C /* wait for a change on serial input line(s) */ +#define TARGET_TIOCGICOUNT 0x545D /* read serial port inline interrupt counts */ +#define TARGET_TIOCGHAYESESP 0x545E /* Get Hayes ESP configuration */ +#define TARGET_TIOCSHAYESESP 0x545F /* Set Hayes ESP configuration */ diff --git a/linux-user/arm/nwfpe/double_cpdo.c b/linux-user/arm/nwfpe/double_cpdo.c new file mode 100644 index 0000000..8e9b28f --- /dev/null +++ b/linux-user/arm/nwfpe/double_cpdo.c @@ -0,0 +1,295 @@ +/* + NetWinder Floating Point Emulator + (c) Rebel.COM, 1998,1999 + + Direct questions, comments to Scott Bambrough + + 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 . +*/ + +#include "fpa11.h" +#include "softfloat.h" +#include "fpopcode.h" + +float64 float64_exp(float64 Fm); +float64 float64_ln(float64 Fm); +float64 float64_sin(float64 rFm); +float64 float64_cos(float64 rFm); +float64 float64_arcsin(float64 rFm); +float64 float64_arctan(float64 rFm); +float64 float64_log(float64 rFm); +float64 float64_tan(float64 rFm); +float64 float64_arccos(float64 rFm); +float64 float64_pow(float64 rFn,float64 rFm); +float64 float64_pol(float64 rFn,float64 rFm); + +unsigned int DoubleCPDO(const unsigned int opcode) +{ + FPA11 *fpa11 = GET_FPA11(); + float64 rFm, rFn = float64_zero; + unsigned int Fd, Fm, Fn, nRc = 1; + + //printk("DoubleCPDO(0x%08x)\n",opcode); + + Fm = getFm(opcode); + if (CONSTANT_FM(opcode)) + { + rFm = getDoubleConstant(Fm); + } + else + { + switch (fpa11->fType[Fm]) + { + case typeSingle: + rFm = float32_to_float64(fpa11->fpreg[Fm].fSingle, &fpa11->fp_status); + break; + + case typeDouble: + rFm = fpa11->fpreg[Fm].fDouble; + break; + + case typeExtended: + // !! patb + //printk("not implemented! why not?\n"); + //!! ScottB + // should never get here, if extended involved + // then other operand should be promoted then + // ExtendedCPDO called. + break; + + default: return 0; + } + } + + if (!MONADIC_INSTRUCTION(opcode)) + { + Fn = getFn(opcode); + switch (fpa11->fType[Fn]) + { + case typeSingle: + rFn = float32_to_float64(fpa11->fpreg[Fn].fSingle, &fpa11->fp_status); + break; + + case typeDouble: + rFn = fpa11->fpreg[Fn].fDouble; + break; + + default: return 0; + } + } + + Fd = getFd(opcode); + /* !! this switch isn't optimized; better (opcode & MASK_ARITHMETIC_OPCODE)>>24, sort of */ + switch (opcode & MASK_ARITHMETIC_OPCODE) + { + /* dyadic opcodes */ + case ADF_CODE: + fpa11->fpreg[Fd].fDouble = float64_add(rFn,rFm, &fpa11->fp_status); + break; + + case MUF_CODE: + case FML_CODE: + fpa11->fpreg[Fd].fDouble = float64_mul(rFn,rFm, &fpa11->fp_status); + break; + + case SUF_CODE: + fpa11->fpreg[Fd].fDouble = float64_sub(rFn,rFm, &fpa11->fp_status); + break; + + case RSF_CODE: + fpa11->fpreg[Fd].fDouble = float64_sub(rFm,rFn, &fpa11->fp_status); + break; + + case DVF_CODE: + case FDV_CODE: + fpa11->fpreg[Fd].fDouble = float64_div(rFn,rFm, &fpa11->fp_status); + break; + + case RDF_CODE: + case FRD_CODE: + fpa11->fpreg[Fd].fDouble = float64_div(rFm,rFn, &fpa11->fp_status); + break; + +#if 0 + case POW_CODE: + fpa11->fpreg[Fd].fDouble = float64_pow(rFn,rFm); + break; + + case RPW_CODE: + fpa11->fpreg[Fd].fDouble = float64_pow(rFm,rFn); + break; +#endif + + case RMF_CODE: + fpa11->fpreg[Fd].fDouble = float64_rem(rFn,rFm, &fpa11->fp_status); + break; + +#if 0 + case POL_CODE: + fpa11->fpreg[Fd].fDouble = float64_pol(rFn,rFm); + break; +#endif + + /* monadic opcodes */ + case MVF_CODE: + fpa11->fpreg[Fd].fDouble = rFm; + break; + + case MNF_CODE: + { + unsigned int *p = (unsigned int*)&rFm; +#ifdef HOST_WORDS_BIGENDIAN + p[0] ^= 0x80000000; +#else + p[1] ^= 0x80000000; +#endif + fpa11->fpreg[Fd].fDouble = rFm; + } + break; + + case ABS_CODE: + { + unsigned int *p = (unsigned int*)&rFm; +#ifdef HOST_WORDS_BIGENDIAN + p[0] &= 0x7fffffff; +#else + p[1] &= 0x7fffffff; +#endif + fpa11->fpreg[Fd].fDouble = rFm; + } + break; + + case RND_CODE: + case URD_CODE: + fpa11->fpreg[Fd].fDouble = float64_round_to_int(rFm, &fpa11->fp_status); + break; + + case SQT_CODE: + fpa11->fpreg[Fd].fDouble = float64_sqrt(rFm, &fpa11->fp_status); + break; + +#if 0 + case LOG_CODE: + fpa11->fpreg[Fd].fDouble = float64_log(rFm); + break; + + case LGN_CODE: + fpa11->fpreg[Fd].fDouble = float64_ln(rFm); + break; + + case EXP_CODE: + fpa11->fpreg[Fd].fDouble = float64_exp(rFm); + break; + + case SIN_CODE: + fpa11->fpreg[Fd].fDouble = float64_sin(rFm); + break; + + case COS_CODE: + fpa11->fpreg[Fd].fDouble = float64_cos(rFm); + break; + + case TAN_CODE: + fpa11->fpreg[Fd].fDouble = float64_tan(rFm); + break; + + case ASN_CODE: + fpa11->fpreg[Fd].fDouble = float64_arcsin(rFm); + break; + + case ACS_CODE: + fpa11->fpreg[Fd].fDouble = float64_arccos(rFm); + break; + + case ATN_CODE: + fpa11->fpreg[Fd].fDouble = float64_arctan(rFm); + break; +#endif + + case NRM_CODE: + break; + + default: + { + nRc = 0; + } + } + + if (0 != nRc) fpa11->fType[Fd] = typeDouble; + return nRc; +} + +#if 0 +float64 float64_exp(float64 rFm) +{ + return rFm; +//series +} + +float64 float64_ln(float64 rFm) +{ + return rFm; +//series +} + +float64 float64_sin(float64 rFm) +{ + return rFm; +//series +} + +float64 float64_cos(float64 rFm) +{ + return rFm; + //series +} + +#if 0 +float64 float64_arcsin(float64 rFm) +{ +//series +} + +float64 float64_arctan(float64 rFm) +{ + //series +} +#endif + +float64 float64_log(float64 rFm) +{ + return float64_div(float64_ln(rFm),getDoubleConstant(7)); +} + +float64 float64_tan(float64 rFm) +{ + return float64_div(float64_sin(rFm),float64_cos(rFm)); +} + +float64 float64_arccos(float64 rFm) +{ +return rFm; + //return float64_sub(halfPi,float64_arcsin(rFm)); +} + +float64 float64_pow(float64 rFn,float64 rFm) +{ + return float64_exp(float64_mul(rFm,float64_ln(rFn))); +} + +float64 float64_pol(float64 rFn,float64 rFm) +{ + return float64_arctan(float64_div(rFn,rFm)); +} +#endif diff --git a/linux-user/arm/nwfpe/extended_cpdo.c b/linux-user/arm/nwfpe/extended_cpdo.c new file mode 100644 index 0000000..880ce03 --- /dev/null +++ b/linux-user/arm/nwfpe/extended_cpdo.c @@ -0,0 +1,272 @@ +/* + NetWinder Floating Point Emulator + (c) Rebel.COM, 1998,1999 + + Direct questions, comments to Scott Bambrough + + 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 . +*/ + +#include "fpa11.h" +#include "softfloat.h" +#include "fpopcode.h" + +floatx80 floatx80_exp(floatx80 Fm); +floatx80 floatx80_ln(floatx80 Fm); +floatx80 floatx80_sin(floatx80 rFm); +floatx80 floatx80_cos(floatx80 rFm); +floatx80 floatx80_arcsin(floatx80 rFm); +floatx80 floatx80_arctan(floatx80 rFm); +floatx80 floatx80_log(floatx80 rFm); +floatx80 floatx80_tan(floatx80 rFm); +floatx80 floatx80_arccos(floatx80 rFm); +floatx80 floatx80_pow(floatx80 rFn,floatx80 rFm); +floatx80 floatx80_pol(floatx80 rFn,floatx80 rFm); + +unsigned int ExtendedCPDO(const unsigned int opcode) +{ + FPA11 *fpa11 = GET_FPA11(); + floatx80 rFm, rFn; + unsigned int Fd, Fm, Fn, nRc = 1; + + //printk("ExtendedCPDO(0x%08x)\n",opcode); + + Fm = getFm(opcode); + if (CONSTANT_FM(opcode)) + { + rFm = getExtendedConstant(Fm); + } + else + { + switch (fpa11->fType[Fm]) + { + case typeSingle: + rFm = float32_to_floatx80(fpa11->fpreg[Fm].fSingle, &fpa11->fp_status); + break; + + case typeDouble: + rFm = float64_to_floatx80(fpa11->fpreg[Fm].fDouble, &fpa11->fp_status); + break; + + case typeExtended: + rFm = fpa11->fpreg[Fm].fExtended; + break; + + default: return 0; + } + } + + if (!MONADIC_INSTRUCTION(opcode)) + { + Fn = getFn(opcode); + switch (fpa11->fType[Fn]) + { + case typeSingle: + rFn = float32_to_floatx80(fpa11->fpreg[Fn].fSingle, &fpa11->fp_status); + break; + + case typeDouble: + rFn = float64_to_floatx80(fpa11->fpreg[Fn].fDouble, &fpa11->fp_status); + break; + + case typeExtended: + rFn = fpa11->fpreg[Fn].fExtended; + break; + + default: return 0; + } + } + + Fd = getFd(opcode); + switch (opcode & MASK_ARITHMETIC_OPCODE) + { + /* dyadic opcodes */ + case ADF_CODE: + fpa11->fpreg[Fd].fExtended = floatx80_add(rFn,rFm, &fpa11->fp_status); + break; + + case MUF_CODE: + case FML_CODE: + fpa11->fpreg[Fd].fExtended = floatx80_mul(rFn,rFm, &fpa11->fp_status); + break; + + case SUF_CODE: + fpa11->fpreg[Fd].fExtended = floatx80_sub(rFn,rFm, &fpa11->fp_status); + break; + + case RSF_CODE: + fpa11->fpreg[Fd].fExtended = floatx80_sub(rFm,rFn, &fpa11->fp_status); + break; + + case DVF_CODE: + case FDV_CODE: + fpa11->fpreg[Fd].fExtended = floatx80_div(rFn,rFm, &fpa11->fp_status); + break; + + case RDF_CODE: + case FRD_CODE: + fpa11->fpreg[Fd].fExtended = floatx80_div(rFm,rFn, &fpa11->fp_status); + break; + +#if 0 + case POW_CODE: + fpa11->fpreg[Fd].fExtended = floatx80_pow(rFn,rFm); + break; + + case RPW_CODE: + fpa11->fpreg[Fd].fExtended = floatx80_pow(rFm,rFn); + break; +#endif + + case RMF_CODE: + fpa11->fpreg[Fd].fExtended = floatx80_rem(rFn,rFm, &fpa11->fp_status); + break; + +#if 0 + case POL_CODE: + fpa11->fpreg[Fd].fExtended = floatx80_pol(rFn,rFm); + break; +#endif + + /* monadic opcodes */ + case MVF_CODE: + fpa11->fpreg[Fd].fExtended = rFm; + break; + + case MNF_CODE: + rFm.high ^= 0x8000; + fpa11->fpreg[Fd].fExtended = rFm; + break; + + case ABS_CODE: + rFm.high &= 0x7fff; + fpa11->fpreg[Fd].fExtended = rFm; + break; + + case RND_CODE: + case URD_CODE: + fpa11->fpreg[Fd].fExtended = floatx80_round_to_int(rFm, &fpa11->fp_status); + break; + + case SQT_CODE: + fpa11->fpreg[Fd].fExtended = floatx80_sqrt(rFm, &fpa11->fp_status); + break; + +#if 0 + case LOG_CODE: + fpa11->fpreg[Fd].fExtended = floatx80_log(rFm); + break; + + case LGN_CODE: + fpa11->fpreg[Fd].fExtended = floatx80_ln(rFm); + break; + + case EXP_CODE: + fpa11->fpreg[Fd].fExtended = floatx80_exp(rFm); + break; + + case SIN_CODE: + fpa11->fpreg[Fd].fExtended = floatx80_sin(rFm); + break; + + case COS_CODE: + fpa11->fpreg[Fd].fExtended = floatx80_cos(rFm); + break; + + case TAN_CODE: + fpa11->fpreg[Fd].fExtended = floatx80_tan(rFm); + break; + + case ASN_CODE: + fpa11->fpreg[Fd].fExtended = floatx80_arcsin(rFm); + break; + + case ACS_CODE: + fpa11->fpreg[Fd].fExtended = floatx80_arccos(rFm); + break; + + case ATN_CODE: + fpa11->fpreg[Fd].fExtended = floatx80_arctan(rFm); + break; +#endif + + case NRM_CODE: + break; + + default: + { + nRc = 0; + } + } + + if (0 != nRc) fpa11->fType[Fd] = typeExtended; + return nRc; +} + +#if 0 +floatx80 floatx80_exp(floatx80 Fm) +{ +//series +} + +floatx80 floatx80_ln(floatx80 Fm) +{ +//series +} + +floatx80 floatx80_sin(floatx80 rFm) +{ +//series +} + +floatx80 floatx80_cos(floatx80 rFm) +{ +//series +} + +floatx80 floatx80_arcsin(floatx80 rFm) +{ +//series +} + +floatx80 floatx80_arctan(floatx80 rFm) +{ + //series +} + +floatx80 floatx80_log(floatx80 rFm) +{ + return floatx80_div(floatx80_ln(rFm),getExtendedConstant(7)); +} + +floatx80 floatx80_tan(floatx80 rFm) +{ + return floatx80_div(floatx80_sin(rFm),floatx80_cos(rFm)); +} + +floatx80 floatx80_arccos(floatx80 rFm) +{ + //return floatx80_sub(halfPi,floatx80_arcsin(rFm)); +} + +floatx80 floatx80_pow(floatx80 rFn,floatx80 rFm) +{ + return floatx80_exp(floatx80_mul(rFm,floatx80_ln(rFn))); +} + +floatx80 floatx80_pol(floatx80 rFn,floatx80 rFm) +{ + return floatx80_arctan(floatx80_div(rFn,rFm)); +} +#endif diff --git a/linux-user/arm/nwfpe/fpa11.c b/linux-user/arm/nwfpe/fpa11.c new file mode 100644 index 0000000..0a87c43 --- /dev/null +++ b/linux-user/arm/nwfpe/fpa11.c @@ -0,0 +1,237 @@ +/* + NetWinder Floating Point Emulator + (c) Rebel.COM, 1998,1999 + + Direct questions, comments to Scott Bambrough + + 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 . +*/ + +#include "fpa11.h" + +#include "fpopcode.h" + +//#include "fpmodule.h" +//#include "fpmodule.inl" + +//#include + +#include + +FPA11* qemufpa = NULL; +CPUARMState* user_registers; + +/* Reset the FPA11 chip. Called to initialize and reset the emulator. */ +void resetFPA11(void) +{ + int i; + FPA11 *fpa11 = GET_FPA11(); + + /* initialize the register type array */ + for (i=0;i<=7;i++) + { + fpa11->fType[i] = typeNone; + } + + /* FPSR: set system id to FP_EMULATOR, set AC, clear all other bits */ + fpa11->fpsr = FP_EMULATOR | BIT_AC; + + /* FPCR: set SB, AB and DA bits, clear all others */ +#ifdef MAINTAIN_FPCR + fpa11->fpcr = MASK_RESET; +#endif +} + +void SetRoundingMode(const unsigned int opcode) +{ + int rounding_mode; + FPA11 *fpa11 = GET_FPA11(); + +#ifdef MAINTAIN_FPCR + fpa11->fpcr &= ~MASK_ROUNDING_MODE; +#endif + switch (opcode & MASK_ROUNDING_MODE) + { + default: + case ROUND_TO_NEAREST: + rounding_mode = float_round_nearest_even; +#ifdef MAINTAIN_FPCR + fpa11->fpcr |= ROUND_TO_NEAREST; +#endif + break; + + case ROUND_TO_PLUS_INFINITY: + rounding_mode = float_round_up; +#ifdef MAINTAIN_FPCR + fpa11->fpcr |= ROUND_TO_PLUS_INFINITY; +#endif + break; + + case ROUND_TO_MINUS_INFINITY: + rounding_mode = float_round_down; +#ifdef MAINTAIN_FPCR + fpa11->fpcr |= ROUND_TO_MINUS_INFINITY; +#endif + break; + + case ROUND_TO_ZERO: + rounding_mode = float_round_to_zero; +#ifdef MAINTAIN_FPCR + fpa11->fpcr |= ROUND_TO_ZERO; +#endif + break; + } + set_float_rounding_mode(rounding_mode, &fpa11->fp_status); +} + +void SetRoundingPrecision(const unsigned int opcode) +{ + int rounding_precision; + FPA11 *fpa11 = GET_FPA11(); +#ifdef MAINTAIN_FPCR + fpa11->fpcr &= ~MASK_ROUNDING_PRECISION; +#endif + switch (opcode & MASK_ROUNDING_PRECISION) + { + case ROUND_SINGLE: + rounding_precision = 32; +#ifdef MAINTAIN_FPCR + fpa11->fpcr |= ROUND_SINGLE; +#endif + break; + + case ROUND_DOUBLE: + rounding_precision = 64; +#ifdef MAINTAIN_FPCR + fpa11->fpcr |= ROUND_DOUBLE; +#endif + break; + + case ROUND_EXTENDED: + rounding_precision = 80; +#ifdef MAINTAIN_FPCR + fpa11->fpcr |= ROUND_EXTENDED; +#endif + break; + + default: rounding_precision = 80; + } + set_floatx80_rounding_precision(rounding_precision, &fpa11->fp_status); +} + +/* Emulate the instruction in the opcode. */ +/* ??? This is not thread safe. */ +unsigned int EmulateAll(unsigned int opcode, FPA11* qfpa, CPUARMState* qregs) +{ + unsigned int nRc = 0; +// unsigned long flags; + FPA11 *fpa11; +// save_flags(flags); sti(); + + qemufpa=qfpa; + user_registers=qregs; + +#if 0 + fprintf(stderr,"emulating FP insn 0x%08x, PC=0x%08x\n", + opcode, qregs[REG_PC]); +#endif + fpa11 = GET_FPA11(); + + if (fpa11->initflag == 0) /* good place for __builtin_expect */ + { + resetFPA11(); + SetRoundingMode(ROUND_TO_NEAREST); + SetRoundingPrecision(ROUND_EXTENDED); + fpa11->initflag = 1; + } + + set_float_exception_flags(0, &fpa11->fp_status); + + if (TEST_OPCODE(opcode,MASK_CPRT)) + { + //fprintf(stderr,"emulating CPRT\n"); + /* Emulate conversion opcodes. */ + /* Emulate register transfer opcodes. */ + /* Emulate comparison opcodes. */ + nRc = EmulateCPRT(opcode); + } + else if (TEST_OPCODE(opcode,MASK_CPDO)) + { + //fprintf(stderr,"emulating CPDO\n"); + /* Emulate monadic arithmetic opcodes. */ + /* Emulate dyadic arithmetic opcodes. */ + nRc = EmulateCPDO(opcode); + } + else if (TEST_OPCODE(opcode,MASK_CPDT)) + { + //fprintf(stderr,"emulating CPDT\n"); + /* Emulate load/store opcodes. */ + /* Emulate load/store multiple opcodes. */ + nRc = EmulateCPDT(opcode); + } + else + { + /* Invalid instruction detected. Return FALSE. */ + nRc = 0; + } + +// restore_flags(flags); + if(nRc == 1 && get_float_exception_flags(&fpa11->fp_status)) + { + //printf("fef 0x%x\n",float_exception_flags); + nRc = -get_float_exception_flags(&fpa11->fp_status); + } + + //printf("returning %d\n",nRc); + return(nRc); +} + +#if 0 +unsigned int EmulateAll1(unsigned int opcode) +{ + switch ((opcode >> 24) & 0xf) + { + case 0xc: + case 0xd: + if ((opcode >> 20) & 0x1) + { + switch ((opcode >> 8) & 0xf) + { + case 0x1: return PerformLDF(opcode); break; + case 0x2: return PerformLFM(opcode); break; + default: return 0; + } + } + else + { + switch ((opcode >> 8) & 0xf) + { + case 0x1: return PerformSTF(opcode); break; + case 0x2: return PerformSFM(opcode); break; + default: return 0; + } + } + break; + + case 0xe: + if (opcode & 0x10) + return EmulateCPDO(opcode); + else + return EmulateCPRT(opcode); + break; + + default: return 0; + } +} +#endif diff --git a/linux-user/arm/nwfpe/fpa11.h b/linux-user/arm/nwfpe/fpa11.h new file mode 100644 index 0000000..f17647b --- /dev/null +++ b/linux-user/arm/nwfpe/fpa11.h @@ -0,0 +1,130 @@ +/* + NetWinder Floating Point Emulator + (c) Rebel.com, 1998-1999 + + Direct questions, comments to Scott Bambrough + + 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 . +*/ + +#ifndef __FPA11_H__ +#define __FPA11_H__ + +#include +#include +#include + +#include + +#define GET_FPA11() (qemufpa) + +/* + * The processes registers are always at the very top of the 8K + * stack+task struct. Use the same method as 'current' uses to + * reach them. + */ +extern CPUARMState *user_registers; + +#define GET_USERREG() (user_registers) + +/* Need task_struct */ +//#include + +/* includes */ +#include "fpsr.h" /* FP control and status register definitions */ +#include "softfloat.h" + +#define typeNone 0x00 +#define typeSingle 0x01 +#define typeDouble 0x02 +#define typeExtended 0x03 + +/* + * This must be no more and no less than 12 bytes. + */ +typedef union tagFPREG { + floatx80 fExtended; + float64 fDouble; + float32 fSingle; +} FPREG; + +/* + * FPA11 device model. + * + * This structure is exported to user space. Do not re-order. + * Only add new stuff to the end, and do not change the size of + * any element. Elements of this structure are used by user + * space, and must match struct user_fp in include/asm-arm/user.h. + * We include the byte offsets below for documentation purposes. + * + * The size of this structure and FPREG are checked by fpmodule.c + * on initialisation. If the rules have been broken, NWFPE will + * not initialise. + */ +typedef struct tagFPA11 { +/* 0 */ FPREG fpreg[8]; /* 8 floating point registers */ +/* 96 */ FPSR fpsr; /* floating point status register */ +/* 100 */ FPCR fpcr; /* floating point control register */ +/* 104 */ unsigned char fType[8]; /* type of floating point value held in + floating point registers. One of none + single, double or extended. */ +/* 112 */ int initflag; /* this is special. The kernel guarantees + to set it to 0 when a thread is launched, + so we can use it to detect whether this + instance of the emulator needs to be + initialised. */ + float_status fp_status; /* QEMU float emulator status */ +} FPA11; + +extern FPA11* qemufpa; + +void resetFPA11(void); +void SetRoundingMode(const unsigned int); +void SetRoundingPrecision(const unsigned int); + +static inline unsigned int readRegister(unsigned int reg) +{ + return (user_registers->regs[(reg)]); +} + +static inline void writeRegister(unsigned int x, unsigned int y) +{ +#if 0 + printf("writing %d to r%d\n",y,x); +#endif + user_registers->regs[(x)]=(y); +} + +static inline void writeConditionCodes(unsigned int x) +{ + cpsr_write(user_registers,x,CPSR_NZCV); +} + +#define REG_PC 15 + +unsigned int EmulateAll(unsigned int opcode, FPA11* qfpa, CPUARMState* qregs); + +unsigned int EmulateCPDO(const unsigned int); +unsigned int EmulateCPDT(const unsigned int); +unsigned int EmulateCPRT(const unsigned int); + +unsigned int SingleCPDO(const unsigned int opcode); +unsigned int DoubleCPDO(const unsigned int opcode); +unsigned int ExtendedCPDO(const unsigned int opcode); + + +/* included only for get_user/put_user macros */ +#include "qemu.h" + +#endif diff --git a/linux-user/arm/nwfpe/fpa11.inl b/linux-user/arm/nwfpe/fpa11.inl new file mode 100644 index 0000000..6c6f380 --- /dev/null +++ b/linux-user/arm/nwfpe/fpa11.inl @@ -0,0 +1,50 @@ +/* + NetWinder Floating Point Emulator + (c) Rebel.COM, 1998,1999 + + Direct questions, comments to Scott Bambrough + + 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 . +*/ + +#include "fpa11.h" + +/* Read and write floating point status register */ +static inline unsigned int readFPSR(void) +{ + FPA11 *fpa11 = GET_FPA11(); + return(fpa11->fpsr); +} + +static inline void writeFPSR(FPSR reg) +{ + FPA11 *fpa11 = GET_FPA11(); + /* the sysid byte in the status register is readonly */ + fpa11->fpsr = (fpa11->fpsr & MASK_SYSID) | (reg & ~MASK_SYSID); +} + +/* Read and write floating point control register */ +static inline FPCR readFPCR(void) +{ + FPA11 *fpa11 = GET_FPA11(); + /* clear SB, AB and DA bits before returning FPCR */ + return(fpa11->fpcr & ~MASK_RFC); +} + +static inline void writeFPCR(FPCR reg) +{ + FPA11 *fpa11 = GET_FPA11(); + fpa11->fpcr &= ~MASK_WFC; /* clear SB, AB and DA bits */ + fpa11->fpcr |= (reg & MASK_WFC); /* write SB, AB and DA bits */ +} diff --git a/linux-user/arm/nwfpe/fpa11_cpdo.c b/linux-user/arm/nwfpe/fpa11_cpdo.c new file mode 100644 index 0000000..5f4a6a4 --- /dev/null +++ b/linux-user/arm/nwfpe/fpa11_cpdo.c @@ -0,0 +1,112 @@ +/* + NetWinder Floating Point Emulator + (c) Rebel.COM, 1998,1999 + + Direct questions, comments to Scott Bambrough + + 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 . +*/ + +#include "fpa11.h" +#include "fpopcode.h" + +unsigned int EmulateCPDO(const unsigned int opcode) +{ + FPA11 *fpa11 = GET_FPA11(); + unsigned int Fd, nType, nDest, nRc = 1; + + //printk("EmulateCPDO(0x%08x)\n",opcode); + + /* Get the destination size. If not valid let Linux perform + an invalid instruction trap. */ + nDest = getDestinationSize(opcode); + if (typeNone == nDest) return 0; + + SetRoundingMode(opcode); + + /* Compare the size of the operands in Fn and Fm. + Choose the largest size and perform operations in that size, + in order to make use of all the precision of the operands. + If Fm is a constant, we just grab a constant of a size + matching the size of the operand in Fn. */ + if (MONADIC_INSTRUCTION(opcode)) + nType = nDest; + else + nType = fpa11->fType[getFn(opcode)]; + + if (!CONSTANT_FM(opcode)) + { + register unsigned int Fm = getFm(opcode); + if (nType < fpa11->fType[Fm]) + { + nType = fpa11->fType[Fm]; + } + } + + switch (nType) + { + case typeSingle : nRc = SingleCPDO(opcode); break; + case typeDouble : nRc = DoubleCPDO(opcode); break; + case typeExtended : nRc = ExtendedCPDO(opcode); break; + default : nRc = 0; + } + + /* If the operation succeeded, check to see if the result in the + destination register is the correct size. If not force it + to be. */ + Fd = getFd(opcode); + nType = fpa11->fType[Fd]; + if ((0 != nRc) && (nDest != nType)) + { + switch (nDest) + { + case typeSingle: + { + if (typeDouble == nType) + fpa11->fpreg[Fd].fSingle = + float64_to_float32(fpa11->fpreg[Fd].fDouble, &fpa11->fp_status); + else + fpa11->fpreg[Fd].fSingle = + floatx80_to_float32(fpa11->fpreg[Fd].fExtended, &fpa11->fp_status); + } + break; + + case typeDouble: + { + if (typeSingle == nType) + fpa11->fpreg[Fd].fDouble = + float32_to_float64(fpa11->fpreg[Fd].fSingle, &fpa11->fp_status); + else + fpa11->fpreg[Fd].fDouble = + floatx80_to_float64(fpa11->fpreg[Fd].fExtended, &fpa11->fp_status); + } + break; + + case typeExtended: + { + if (typeSingle == nType) + fpa11->fpreg[Fd].fExtended = + float32_to_floatx80(fpa11->fpreg[Fd].fSingle, &fpa11->fp_status); + else + fpa11->fpreg[Fd].fExtended = + float64_to_floatx80(fpa11->fpreg[Fd].fDouble, &fpa11->fp_status); + } + break; + } + + fpa11->fType[Fd] = nDest; + } + + return nRc; +} diff --git a/linux-user/arm/nwfpe/fpa11_cpdt.c b/linux-user/arm/nwfpe/fpa11_cpdt.c new file mode 100644 index 0000000..1346fd6 --- /dev/null +++ b/linux-user/arm/nwfpe/fpa11_cpdt.c @@ -0,0 +1,381 @@ +/* + NetWinder Floating Point Emulator + (c) Rebel.com, 1998-1999 + (c) Philip Blundell, 1998 + + Direct questions, comments to Scott Bambrough + + 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 . +*/ + +#include "fpa11.h" +#include "softfloat.h" +#include "fpopcode.h" +//#include "fpmodule.h" +//#include "fpmodule.inl" + +//#include + +static inline +void loadSingle(const unsigned int Fn, target_ulong addr) +{ + FPA11 *fpa11 = GET_FPA11(); + fpa11->fType[Fn] = typeSingle; + /* FIXME - handle failure of get_user() */ + get_user_u32(fpa11->fpreg[Fn].fSingle, addr); +} + +static inline +void loadDouble(const unsigned int Fn, target_ulong addr) +{ + FPA11 *fpa11 = GET_FPA11(); + unsigned int *p; + p = (unsigned int*)&fpa11->fpreg[Fn].fDouble; + fpa11->fType[Fn] = typeDouble; +#ifdef HOST_WORDS_BIGENDIAN + /* FIXME - handle failure of get_user() */ + get_user_u32(p[0], addr); /* sign & exponent */ + get_user_u32(p[1], addr + 4); +#else + /* FIXME - handle failure of get_user() */ + get_user_u32(p[0], addr + 4); + get_user_u32(p[1], addr); /* sign & exponent */ +#endif +} + +static inline +void loadExtended(const unsigned int Fn, target_ulong addr) +{ + FPA11 *fpa11 = GET_FPA11(); + unsigned int *p; + p = (unsigned int*)&fpa11->fpreg[Fn].fExtended; + fpa11->fType[Fn] = typeExtended; + /* FIXME - handle failure of get_user() */ + get_user_u32(p[0], addr); /* sign & exponent */ + get_user_u32(p[1], addr + 8); /* ls bits */ + get_user_u32(p[2], addr + 4); /* ms bits */ +} + +static inline +void loadMultiple(const unsigned int Fn, target_ulong addr) +{ + FPA11 *fpa11 = GET_FPA11(); + register unsigned int *p; + unsigned long x; + + p = (unsigned int*)&(fpa11->fpreg[Fn]); + /* FIXME - handle failure of get_user() */ + get_user_u32(x, addr); + fpa11->fType[Fn] = (x >> 14) & 0x00000003; + + switch (fpa11->fType[Fn]) + { + case typeSingle: + case typeDouble: + { + /* FIXME - handle failure of get_user() */ + get_user_u32(p[0], addr + 8); /* Single */ + get_user_u32(p[1], addr + 4); /* double msw */ + p[2] = 0; /* empty */ + } + break; + + case typeExtended: + { + /* FIXME - handle failure of get_user() */ + get_user_u32(p[1], addr + 8); + get_user_u32(p[2], addr + 4); /* msw */ + p[0] = (x & 0x80003fff); + } + break; + } +} + +static inline +void storeSingle(const unsigned int Fn, target_ulong addr) +{ + FPA11 *fpa11 = GET_FPA11(); + float32 val; + register unsigned int *p = (unsigned int*)&val; + + switch (fpa11->fType[Fn]) + { + case typeDouble: + val = float64_to_float32(fpa11->fpreg[Fn].fDouble, &fpa11->fp_status); + break; + + case typeExtended: + val = floatx80_to_float32(fpa11->fpreg[Fn].fExtended, &fpa11->fp_status); + break; + + default: val = fpa11->fpreg[Fn].fSingle; + } + + /* FIXME - handle put_user() failures */ + put_user_u32(p[0], addr); +} + +static inline +void storeDouble(const unsigned int Fn, target_ulong addr) +{ + FPA11 *fpa11 = GET_FPA11(); + float64 val; + register unsigned int *p = (unsigned int*)&val; + + switch (fpa11->fType[Fn]) + { + case typeSingle: + val = float32_to_float64(fpa11->fpreg[Fn].fSingle, &fpa11->fp_status); + break; + + case typeExtended: + val = floatx80_to_float64(fpa11->fpreg[Fn].fExtended, &fpa11->fp_status); + break; + + default: val = fpa11->fpreg[Fn].fDouble; + } + /* FIXME - handle put_user() failures */ +#ifdef HOST_WORDS_BIGENDIAN + put_user_u32(p[0], addr); /* msw */ + put_user_u32(p[1], addr + 4); /* lsw */ +#else + put_user_u32(p[1], addr); /* msw */ + put_user_u32(p[0], addr + 4); /* lsw */ +#endif +} + +static inline +void storeExtended(const unsigned int Fn, target_ulong addr) +{ + FPA11 *fpa11 = GET_FPA11(); + floatx80 val; + register unsigned int *p = (unsigned int*)&val; + + switch (fpa11->fType[Fn]) + { + case typeSingle: + val = float32_to_floatx80(fpa11->fpreg[Fn].fSingle, &fpa11->fp_status); + break; + + case typeDouble: + val = float64_to_floatx80(fpa11->fpreg[Fn].fDouble, &fpa11->fp_status); + break; + + default: val = fpa11->fpreg[Fn].fExtended; + } + + /* FIXME - handle put_user() failures */ + put_user_u32(p[0], addr); /* sign & exp */ + put_user_u32(p[1], addr + 8); + put_user_u32(p[2], addr + 4); /* msw */ +} + +static inline +void storeMultiple(const unsigned int Fn, target_ulong addr) +{ + FPA11 *fpa11 = GET_FPA11(); + register unsigned int nType, *p; + + p = (unsigned int*)&(fpa11->fpreg[Fn]); + nType = fpa11->fType[Fn]; + + switch (nType) + { + case typeSingle: + case typeDouble: + { + put_user_u32(p[0], addr + 8); /* single */ + put_user_u32(p[1], addr + 4); /* double msw */ + put_user_u32(nType << 14, addr); + } + break; + + case typeExtended: + { + put_user_u32(p[2], addr + 4); /* msw */ + put_user_u32(p[1], addr + 8); + put_user_u32((p[0] & 0x80003fff) | (nType << 14), addr); + } + break; + } +} + +static unsigned int PerformLDF(const unsigned int opcode) +{ + target_ulong pBase, pAddress, pFinal; + unsigned int nRc = 1, + write_back = WRITE_BACK(opcode); + + //printk("PerformLDF(0x%08x), Fd = 0x%08x\n",opcode,getFd(opcode)); + + pBase = readRegister(getRn(opcode)); + if (REG_PC == getRn(opcode)) + { + pBase += 8; + write_back = 0; + } + + pFinal = pBase; + if (BIT_UP_SET(opcode)) + pFinal += getOffset(opcode) * 4; + else + pFinal -= getOffset(opcode) * 4; + + if (PREINDEXED(opcode)) pAddress = pFinal; else pAddress = pBase; + + switch (opcode & MASK_TRANSFER_LENGTH) + { + case TRANSFER_SINGLE : loadSingle(getFd(opcode),pAddress); break; + case TRANSFER_DOUBLE : loadDouble(getFd(opcode),pAddress); break; + case TRANSFER_EXTENDED: loadExtended(getFd(opcode),pAddress); break; + default: nRc = 0; + } + + if (write_back) writeRegister(getRn(opcode),(unsigned int)pFinal); + return nRc; +} + +static unsigned int PerformSTF(const unsigned int opcode) +{ + target_ulong pBase, pAddress, pFinal; + unsigned int nRc = 1, + write_back = WRITE_BACK(opcode); + + //printk("PerformSTF(0x%08x), Fd = 0x%08x\n",opcode,getFd(opcode)); + SetRoundingMode(ROUND_TO_NEAREST); + + pBase = readRegister(getRn(opcode)); + if (REG_PC == getRn(opcode)) + { + pBase += 8; + write_back = 0; + } + + pFinal = pBase; + if (BIT_UP_SET(opcode)) + pFinal += getOffset(opcode) * 4; + else + pFinal -= getOffset(opcode) * 4; + + if (PREINDEXED(opcode)) pAddress = pFinal; else pAddress = pBase; + + switch (opcode & MASK_TRANSFER_LENGTH) + { + case TRANSFER_SINGLE : storeSingle(getFd(opcode),pAddress); break; + case TRANSFER_DOUBLE : storeDouble(getFd(opcode),pAddress); break; + case TRANSFER_EXTENDED: storeExtended(getFd(opcode),pAddress); break; + default: nRc = 0; + } + + if (write_back) writeRegister(getRn(opcode),(unsigned int)pFinal); + return nRc; +} + +static unsigned int PerformLFM(const unsigned int opcode) +{ + unsigned int i, Fd, + write_back = WRITE_BACK(opcode); + target_ulong pBase, pAddress, pFinal; + + pBase = readRegister(getRn(opcode)); + if (REG_PC == getRn(opcode)) + { + pBase += 8; + write_back = 0; + } + + pFinal = pBase; + if (BIT_UP_SET(opcode)) + pFinal += getOffset(opcode) * 4; + else + pFinal -= getOffset(opcode) * 4; + + if (PREINDEXED(opcode)) pAddress = pFinal; else pAddress = pBase; + + Fd = getFd(opcode); + for (i=getRegisterCount(opcode);i>0;i--) + { + loadMultiple(Fd,pAddress); + pAddress += 12; Fd++; + if (Fd == 8) Fd = 0; + } + + if (write_back) writeRegister(getRn(opcode),(unsigned int)pFinal); + return 1; +} + +static unsigned int PerformSFM(const unsigned int opcode) +{ + unsigned int i, Fd, + write_back = WRITE_BACK(opcode); + target_ulong pBase, pAddress, pFinal; + + pBase = readRegister(getRn(opcode)); + if (REG_PC == getRn(opcode)) + { + pBase += 8; + write_back = 0; + } + + pFinal = pBase; + if (BIT_UP_SET(opcode)) + pFinal += getOffset(opcode) * 4; + else + pFinal -= getOffset(opcode) * 4; + + if (PREINDEXED(opcode)) pAddress = pFinal; else pAddress = pBase; + + Fd = getFd(opcode); + for (i=getRegisterCount(opcode);i>0;i--) + { + storeMultiple(Fd,pAddress); + pAddress += 12; Fd++; + if (Fd == 8) Fd = 0; + } + + if (write_back) writeRegister(getRn(opcode),(unsigned int)pFinal); + return 1; +} + +#if 1 +unsigned int EmulateCPDT(const unsigned int opcode) +{ + unsigned int nRc = 0; + + //printk("EmulateCPDT(0x%08x)\n",opcode); + + if (LDF_OP(opcode)) + { + nRc = PerformLDF(opcode); + } + else if (LFM_OP(opcode)) + { + nRc = PerformLFM(opcode); + } + else if (STF_OP(opcode)) + { + nRc = PerformSTF(opcode); + } + else if (SFM_OP(opcode)) + { + nRc = PerformSFM(opcode); + } + else + { + nRc = 0; + } + + return nRc; +} +#endif diff --git a/linux-user/arm/nwfpe/fpa11_cprt.c b/linux-user/arm/nwfpe/fpa11_cprt.c new file mode 100644 index 0000000..be54e95 --- /dev/null +++ b/linux-user/arm/nwfpe/fpa11_cprt.c @@ -0,0 +1,283 @@ +/* + NetWinder Floating Point Emulator + (c) Rebel.COM, 1998,1999 + (c) Philip Blundell, 1999 + + Direct questions, comments to Scott Bambrough + + 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 . +*/ + +#include "fpa11.h" +#include "softfloat.h" +#include "fpopcode.h" +#include "fpa11.inl" +//#include "fpmodule.h" +//#include "fpmodule.inl" + +unsigned int PerformFLT(const unsigned int opcode); +unsigned int PerformFIX(const unsigned int opcode); + +static unsigned int +PerformComparison(const unsigned int opcode); + +unsigned int EmulateCPRT(const unsigned int opcode) +{ + unsigned int nRc = 1; + + //printk("EmulateCPRT(0x%08x)\n",opcode); + + if (opcode & 0x800000) + { + /* This is some variant of a comparison (PerformComparison will + sort out which one). Since most of the other CPRT + instructions are oddball cases of some sort or other it makes + sense to pull this out into a fast path. */ + return PerformComparison(opcode); + } + + /* Hint to GCC that we'd like a jump table rather than a load of CMPs */ + switch ((opcode & 0x700000) >> 20) + { + case FLT_CODE >> 20: nRc = PerformFLT(opcode); break; + case FIX_CODE >> 20: nRc = PerformFIX(opcode); break; + + case WFS_CODE >> 20: writeFPSR(readRegister(getRd(opcode))); break; + case RFS_CODE >> 20: writeRegister(getRd(opcode),readFPSR()); break; + +#if 0 /* We currently have no use for the FPCR, so there's no point + in emulating it. */ + case WFC_CODE >> 20: writeFPCR(readRegister(getRd(opcode))); + case RFC_CODE >> 20: writeRegister(getRd(opcode),readFPCR()); break; +#endif + + default: nRc = 0; + } + + return nRc; +} + +unsigned int PerformFLT(const unsigned int opcode) +{ + FPA11 *fpa11 = GET_FPA11(); + + unsigned int nRc = 1; + SetRoundingMode(opcode); + + switch (opcode & MASK_ROUNDING_PRECISION) + { + case ROUND_SINGLE: + { + fpa11->fType[getFn(opcode)] = typeSingle; + fpa11->fpreg[getFn(opcode)].fSingle = + int32_to_float32(readRegister(getRd(opcode)), &fpa11->fp_status); + } + break; + + case ROUND_DOUBLE: + { + fpa11->fType[getFn(opcode)] = typeDouble; + fpa11->fpreg[getFn(opcode)].fDouble = + int32_to_float64(readRegister(getRd(opcode)), &fpa11->fp_status); + } + break; + + case ROUND_EXTENDED: + { + fpa11->fType[getFn(opcode)] = typeExtended; + fpa11->fpreg[getFn(opcode)].fExtended = + int32_to_floatx80(readRegister(getRd(opcode)), &fpa11->fp_status); + } + break; + + default: nRc = 0; + } + + return nRc; +} + +unsigned int PerformFIX(const unsigned int opcode) +{ + FPA11 *fpa11 = GET_FPA11(); + unsigned int nRc = 1; + unsigned int Fn = getFm(opcode); + + SetRoundingMode(opcode); + + switch (fpa11->fType[Fn]) + { + case typeSingle: + { + writeRegister(getRd(opcode), + float32_to_int32(fpa11->fpreg[Fn].fSingle, &fpa11->fp_status)); + } + break; + + case typeDouble: + { + //printf("F%d is 0x%" PRIx64 "\n",Fn,fpa11->fpreg[Fn].fDouble); + writeRegister(getRd(opcode), + float64_to_int32(fpa11->fpreg[Fn].fDouble, &fpa11->fp_status)); + } + break; + + case typeExtended: + { + writeRegister(getRd(opcode), + floatx80_to_int32(fpa11->fpreg[Fn].fExtended, &fpa11->fp_status)); + } + break; + + default: nRc = 0; + } + + return nRc; +} + + +static __inline unsigned int +PerformComparisonOperation(floatx80 Fn, floatx80 Fm) +{ + FPA11 *fpa11 = GET_FPA11(); + unsigned int flags = 0; + + /* test for less than condition */ + if (floatx80_lt(Fn,Fm, &fpa11->fp_status)) + { + flags |= CC_NEGATIVE; + } + + /* test for equal condition */ + if (floatx80_eq(Fn,Fm, &fpa11->fp_status)) + { + flags |= CC_ZERO; + } + + /* test for greater than or equal condition */ + if (floatx80_lt(Fm,Fn, &fpa11->fp_status)) + { + flags |= CC_CARRY; + } + + writeConditionCodes(flags); + return 1; +} + +/* This instruction sets the flags N, Z, C, V in the FPSR. */ + +static unsigned int PerformComparison(const unsigned int opcode) +{ + FPA11 *fpa11 = GET_FPA11(); + unsigned int Fn, Fm; + floatx80 rFn, rFm; + int e_flag = opcode & 0x400000; /* 1 if CxFE */ + int n_flag = opcode & 0x200000; /* 1 if CNxx */ + unsigned int flags = 0; + + //printk("PerformComparison(0x%08x)\n",opcode); + + Fn = getFn(opcode); + Fm = getFm(opcode); + + /* Check for unordered condition and convert all operands to 80-bit + format. + ?? Might be some mileage in avoiding this conversion if possible. + Eg, if both operands are 32-bit, detect this and do a 32-bit + comparison (cheaper than an 80-bit one). */ + switch (fpa11->fType[Fn]) + { + case typeSingle: + //printk("single.\n"); + if (float32_is_any_nan(fpa11->fpreg[Fn].fSingle)) + goto unordered; + rFn = float32_to_floatx80(fpa11->fpreg[Fn].fSingle, &fpa11->fp_status); + break; + + case typeDouble: + //printk("double.\n"); + if (float64_is_any_nan(fpa11->fpreg[Fn].fDouble)) + goto unordered; + rFn = float64_to_floatx80(fpa11->fpreg[Fn].fDouble, &fpa11->fp_status); + break; + + case typeExtended: + //printk("extended.\n"); + if (floatx80_is_any_nan(fpa11->fpreg[Fn].fExtended)) + goto unordered; + rFn = fpa11->fpreg[Fn].fExtended; + break; + + default: return 0; + } + + if (CONSTANT_FM(opcode)) + { + //printk("Fm is a constant: #%d.\n",Fm); + rFm = getExtendedConstant(Fm); + if (floatx80_is_any_nan(rFm)) + goto unordered; + } + else + { + //printk("Fm = r%d which contains a ",Fm); + switch (fpa11->fType[Fm]) + { + case typeSingle: + //printk("single.\n"); + if (float32_is_any_nan(fpa11->fpreg[Fm].fSingle)) + goto unordered; + rFm = float32_to_floatx80(fpa11->fpreg[Fm].fSingle, &fpa11->fp_status); + break; + + case typeDouble: + //printk("double.\n"); + if (float64_is_any_nan(fpa11->fpreg[Fm].fDouble)) + goto unordered; + rFm = float64_to_floatx80(fpa11->fpreg[Fm].fDouble, &fpa11->fp_status); + break; + + case typeExtended: + //printk("extended.\n"); + if (floatx80_is_any_nan(fpa11->fpreg[Fm].fExtended)) + goto unordered; + rFm = fpa11->fpreg[Fm].fExtended; + break; + + default: return 0; + } + } + + if (n_flag) + { + rFm.high ^= 0x8000; + } + + return PerformComparisonOperation(rFn,rFm); + + unordered: + /* ?? The FPA data sheet is pretty vague about this, in particular + about whether the non-E comparisons can ever raise exceptions. + This implementation is based on a combination of what it says in + the data sheet, observation of how the Acorn emulator actually + behaves (and how programs expect it to) and guesswork. */ + flags |= CC_OVERFLOW; + flags &= ~(CC_ZERO | CC_NEGATIVE); + + if (BIT_AC & readFPSR()) flags |= CC_CARRY; + + if (e_flag) float_raise(float_flag_invalid, &fpa11->fp_status); + + writeConditionCodes(flags); + return 1; +} diff --git a/linux-user/arm/nwfpe/fpopcode.c b/linux-user/arm/nwfpe/fpopcode.c new file mode 100644 index 0000000..240061d --- /dev/null +++ b/linux-user/arm/nwfpe/fpopcode.c @@ -0,0 +1,112 @@ +/* + NetWinder Floating Point Emulator + (c) Rebel.COM, 1998,1999 + + Direct questions, comments to Scott Bambrough + + 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 . +*/ + +#include "fpa11.h" +#include "softfloat.h" +#include "fpopcode.h" +#include "fpsr.h" +//#include "fpmodule.h" +//#include "fpmodule.inl" + +const floatx80 floatx80Constant[] = { + { 0x0000000000000000ULL, 0x0000}, /* extended 0.0 */ + { 0x8000000000000000ULL, 0x3fff}, /* extended 1.0 */ + { 0x8000000000000000ULL, 0x4000}, /* extended 2.0 */ + { 0xc000000000000000ULL, 0x4000}, /* extended 3.0 */ + { 0x8000000000000000ULL, 0x4001}, /* extended 4.0 */ + { 0xa000000000000000ULL, 0x4001}, /* extended 5.0 */ + { 0x8000000000000000ULL, 0x3ffe}, /* extended 0.5 */ + { 0xa000000000000000ULL, 0x4002} /* extended 10.0 */ +}; + +const float64 float64Constant[] = { + 0x0000000000000000ULL, /* double 0.0 */ + 0x3ff0000000000000ULL, /* double 1.0 */ + 0x4000000000000000ULL, /* double 2.0 */ + 0x4008000000000000ULL, /* double 3.0 */ + 0x4010000000000000ULL, /* double 4.0 */ + 0x4014000000000000ULL, /* double 5.0 */ + 0x3fe0000000000000ULL, /* double 0.5 */ + 0x4024000000000000ULL /* double 10.0 */ +}; + +const float32 float32Constant[] = { + 0x00000000, /* single 0.0 */ + 0x3f800000, /* single 1.0 */ + 0x40000000, /* single 2.0 */ + 0x40400000, /* single 3.0 */ + 0x40800000, /* single 4.0 */ + 0x40a00000, /* single 5.0 */ + 0x3f000000, /* single 0.5 */ + 0x41200000 /* single 10.0 */ +}; + +unsigned int getRegisterCount(const unsigned int opcode) +{ + unsigned int nRc; + + switch (opcode & MASK_REGISTER_COUNT) + { + case 0x00000000: nRc = 4; break; + case 0x00008000: nRc = 1; break; + case 0x00400000: nRc = 2; break; + case 0x00408000: nRc = 3; break; + default: nRc = 0; + } + + return(nRc); +} + +unsigned int getDestinationSize(const unsigned int opcode) +{ + unsigned int nRc; + + switch (opcode & MASK_DESTINATION_SIZE) + { + case 0x00000000: nRc = typeSingle; break; + case 0x00000080: nRc = typeDouble; break; + case 0x00080000: nRc = typeExtended; break; + default: nRc = typeNone; + } + + return(nRc); +} + +/* condition code lookup table + index into the table is test code: EQ, NE, ... LT, GT, AL, NV + bit position in short is condition code: NZCV */ +static const unsigned short aCC[16] = { + 0xF0F0, // EQ == Z set + 0x0F0F, // NE + 0xCCCC, // CS == C set + 0x3333, // CC + 0xFF00, // MI == N set + 0x00FF, // PL + 0xAAAA, // VS == V set + 0x5555, // VC + 0x0C0C, // HI == C set && Z clear + 0xF3F3, // LS == C clear || Z set + 0xAA55, // GE == (N==V) + 0x55AA, // LT == (N!=V) + 0x0A05, // GT == (!Z && (N==V)) + 0xF5FA, // LE == (Z || (N!=V)) + 0xFFFF, // AL always + 0 // NV +}; diff --git a/linux-user/arm/nwfpe/fpopcode.h b/linux-user/arm/nwfpe/fpopcode.h new file mode 100644 index 0000000..e7d1009 --- /dev/null +++ b/linux-user/arm/nwfpe/fpopcode.h @@ -0,0 +1,390 @@ +/* + NetWinder Floating Point Emulator + (c) Rebel.COM, 1998,1999 + + Direct questions, comments to Scott Bambrough + + 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 . +*/ + +#ifndef __FPOPCODE_H__ +#define __FPOPCODE_H__ + +/* +ARM Floating Point Instruction Classes +| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +|c o n d|1 1 0 P|U|u|W|L| Rn |v| Fd |0|0|0|1| o f f s e t | CPDT +|c o n d|1 1 0 P|U|w|W|L| Rn |x| Fd |0|0|0|1| o f f s e t | CPDT +| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | +|c o n d|1 1 1 0|a|b|c|d|e| Fn |j| Fd |0|0|0|1|f|g|h|0|i| Fm | CPDO +|c o n d|1 1 1 0|a|b|c|L|e| Fn | Rd |0|0|0|1|f|g|h|1|i| Fm | CPRT +|c o n d|1 1 1 0|a|b|c|1|e| Fn |1|1|1|1|0|0|0|1|f|g|h|1|i| Fm | comparisons +| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | + +CPDT data transfer instructions + LDF, STF, LFM, SFM + +CPDO dyadic arithmetic instructions + ADF, MUF, SUF, RSF, DVF, RDF, + POW, RPW, RMF, FML, FDV, FRD, POL + +CPDO monadic arithmetic instructions + MVF, MNF, ABS, RND, SQT, LOG, LGN, EXP, + SIN, COS, TAN, ASN, ACS, ATN, URD, NRM + +CPRT joint arithmetic/data transfer instructions + FIX (arithmetic followed by load/store) + FLT (load/store followed by arithmetic) + CMF, CNF CMFE, CNFE (comparisons) + WFS, RFS (write/read floating point status register) + WFC, RFC (write/read floating point control register) + +cond condition codes +P pre/post index bit: 0 = postindex, 1 = preindex +U up/down bit: 0 = stack grows down, 1 = stack grows up +W write back bit: 1 = update base register (Rn) +L load/store bit: 0 = store, 1 = load +Rn base register +Rd destination/source register +Fd floating point destination register +Fn floating point source register +Fm floating point source register or floating point constant + +uv transfer length (TABLE 1) +wx register count (TABLE 2) +abcd arithmetic opcode (TABLES 3 & 4) +ef destination size (rounding precision) (TABLE 5) +gh rounding mode (TABLE 6) +j dyadic/monadic bit: 0 = dyadic, 1 = monadic +i constant bit: 1 = constant (TABLE 6) +*/ + +/* +TABLE 1 ++-------------------------+---+---+---------+---------+ +| Precision | u | v | FPSR.EP | length | ++-------------------------+---+---+---------+---------+ +| Single | 0 ü 0 | x | 1 words | +| Double | 1 ü 1 | x | 2 words | +| Extended | 1 ü 1 | x | 3 words | +| Packed decimal | 1 ü 1 | 0 | 3 words | +| Expanded packed decimal | 1 ü 1 | 1 | 4 words | ++-------------------------+---+---+---------+---------+ +Note: x = don't care +*/ + +/* +TABLE 2 ++---+---+---------------------------------+ +| w | x | Number of registers to transfer | ++---+---+---------------------------------+ +| 0 ü 1 | 1 | +| 1 ü 0 | 2 | +| 1 ü 1 | 3 | +| 0 ü 0 | 4 | ++---+---+---------------------------------+ +*/ + +/* +TABLE 3: Dyadic Floating Point Opcodes ++---+---+---+---+----------+-----------------------+-----------------------+ +| a | b | c | d | Mnemonic | Description | Operation | ++---+---+---+---+----------+-----------------------+-----------------------+ +| 0 | 0 | 0 | 0 | ADF | Add | Fd := Fn + Fm | +| 0 | 0 | 0 | 1 | MUF | Multiply | Fd := Fn * Fm | +| 0 | 0 | 1 | 0 | SUF | Subtract | Fd := Fn - Fm | +| 0 | 0 | 1 | 1 | RSF | Reverse subtract | Fd := Fm - Fn | +| 0 | 1 | 0 | 0 | DVF | Divide | Fd := Fn / Fm | +| 0 | 1 | 0 | 1 | RDF | Reverse divide | Fd := Fm / Fn | +| 0 | 1 | 1 | 0 | POW | Power | Fd := Fn ^ Fm | +| 0 | 1 | 1 | 1 | RPW | Reverse power | Fd := Fm ^ Fn | +| 1 | 0 | 0 | 0 | RMF | Remainder | Fd := IEEE rem(Fn/Fm) | +| 1 | 0 | 0 | 1 | FML | Fast Multiply | Fd := Fn * Fm | +| 1 | 0 | 1 | 0 | FDV | Fast Divide | Fd := Fn / Fm | +| 1 | 0 | 1 | 1 | FRD | Fast reverse divide | Fd := Fm / Fn | +| 1 | 1 | 0 | 0 | POL | Polar angle (ArcTan2) | Fd := arctan2(Fn,Fm) | +| 1 | 1 | 0 | 1 | | undefined instruction | trap | +| 1 | 1 | 1 | 0 | | undefined instruction | trap | +| 1 | 1 | 1 | 1 | | undefined instruction | trap | ++---+---+---+---+----------+-----------------------+-----------------------+ +Note: POW, RPW, POL are deprecated, and are available for backwards + compatibility only. +*/ + +/* +TABLE 4: Monadic Floating Point Opcodes ++---+---+---+---+----------+-----------------------+-----------------------+ +| a | b | c | d | Mnemonic | Description | Operation | ++---+---+---+---+----------+-----------------------+-----------------------+ +| 0 | 0 | 0 | 0 | MVF | Move | Fd := Fm | +| 0 | 0 | 0 | 1 | MNF | Move negated | Fd := - Fm | +| 0 | 0 | 1 | 0 | ABS | Absolute value | Fd := abs(Fm) | +| 0 | 0 | 1 | 1 | RND | Round to integer | Fd := int(Fm) | +| 0 | 1 | 0 | 0 | SQT | Square root | Fd := sqrt(Fm) | +| 0 | 1 | 0 | 1 | LOG | Log base 10 | Fd := log10(Fm) | +| 0 | 1 | 1 | 0 | LGN | Log base e | Fd := ln(Fm) | +| 0 | 1 | 1 | 1 | EXP | Exponent | Fd := e ^ Fm | +| 1 | 0 | 0 | 0 | SIN | Sine | Fd := sin(Fm) | +| 1 | 0 | 0 | 1 | COS | Cosine | Fd := cos(Fm) | +| 1 | 0 | 1 | 0 | TAN | Tangent | Fd := tan(Fm) | +| 1 | 0 | 1 | 1 | ASN | Arc Sine | Fd := arcsin(Fm) | +| 1 | 1 | 0 | 0 | ACS | Arc Cosine | Fd := arccos(Fm) | +| 1 | 1 | 0 | 1 | ATN | Arc Tangent | Fd := arctan(Fm) | +| 1 | 1 | 1 | 0 | URD | Unnormalized round | Fd := int(Fm) | +| 1 | 1 | 1 | 1 | NRM | Normalize | Fd := norm(Fm) | ++---+---+---+---+----------+-----------------------+-----------------------+ +Note: LOG, LGN, EXP, SIN, COS, TAN, ASN, ACS, ATN are deprecated, and are + available for backwards compatibility only. +*/ + +/* +TABLE 5 ++-------------------------+---+---+ +| Rounding Precision | e | f | ++-------------------------+---+---+ +| IEEE Single precision | 0 ü 0 | +| IEEE Double precision | 0 ü 1 | +| IEEE Extended precision | 1 ü 0 | +| undefined (trap) | 1 ü 1 | ++-------------------------+---+---+ +*/ + +/* +TABLE 5 ++---------------------------------+---+---+ +| Rounding Mode | g | h | ++---------------------------------+---+---+ +| Round to nearest (default) | 0 ü 0 | +| Round toward plus infinity | 0 ü 1 | +| Round toward negative infinity | 1 ü 0 | +| Round toward zero | 1 ü 1 | ++---------------------------------+---+---+ +*/ + +/* +=== +=== Definitions for load and store instructions +=== +*/ + +/* bit masks */ +#define BIT_PREINDEX 0x01000000 +#define BIT_UP 0x00800000 +#define BIT_WRITE_BACK 0x00200000 +#define BIT_LOAD 0x00100000 + +/* masks for load/store */ +#define MASK_CPDT 0x0c000000 /* data processing opcode */ +#define MASK_OFFSET 0x000000ff +#define MASK_TRANSFER_LENGTH 0x00408000 +#define MASK_REGISTER_COUNT MASK_TRANSFER_LENGTH +#define MASK_COPROCESSOR 0x00000f00 + +/* Tests for transfer length */ +#define TRANSFER_SINGLE 0x00000000 +#define TRANSFER_DOUBLE 0x00008000 +#define TRANSFER_EXTENDED 0x00400000 +#define TRANSFER_PACKED MASK_TRANSFER_LENGTH + +/* Get the coprocessor number from the opcode. */ +#define getCoprocessorNumber(opcode) ((opcode & MASK_COPROCESSOR) >> 8) + +/* Get the offset from the opcode. */ +#define getOffset(opcode) (opcode & MASK_OFFSET) + +/* Tests for specific data transfer load/store opcodes. */ +#define TEST_OPCODE(opcode,mask) (((opcode) & (mask)) == (mask)) + +#define LOAD_OP(opcode) TEST_OPCODE((opcode),MASK_CPDT | BIT_LOAD) +#define STORE_OP(opcode) ((opcode & (MASK_CPDT | BIT_LOAD)) == MASK_CPDT) + +#define LDF_OP(opcode) (LOAD_OP(opcode) && (getCoprocessorNumber(opcode) == 1)) +#define LFM_OP(opcode) (LOAD_OP(opcode) && (getCoprocessorNumber(opcode) == 2)) +#define STF_OP(opcode) (STORE_OP(opcode) && (getCoprocessorNumber(opcode) == 1)) +#define SFM_OP(opcode) (STORE_OP(opcode) && (getCoprocessorNumber(opcode) == 2)) + +#define PREINDEXED(opcode) ((opcode & BIT_PREINDEX) != 0) +#define POSTINDEXED(opcode) ((opcode & BIT_PREINDEX) == 0) +#define BIT_UP_SET(opcode) ((opcode & BIT_UP) != 0) +#define BIT_UP_CLEAR(opcode) ((opcode & BIT_DOWN) == 0) +#define WRITE_BACK(opcode) ((opcode & BIT_WRITE_BACK) != 0) +#define LOAD(opcode) ((opcode & BIT_LOAD) != 0) +#define STORE(opcode) ((opcode & BIT_LOAD) == 0) + +/* +=== +=== Definitions for arithmetic instructions +=== +*/ +/* bit masks */ +#define BIT_MONADIC 0x00008000 +#define BIT_CONSTANT 0x00000008 + +#define CONSTANT_FM(opcode) ((opcode & BIT_CONSTANT) != 0) +#define MONADIC_INSTRUCTION(opcode) ((opcode & BIT_MONADIC) != 0) + +/* instruction identification masks */ +#define MASK_CPDO 0x0e000000 /* arithmetic opcode */ +#define MASK_ARITHMETIC_OPCODE 0x00f08000 +#define MASK_DESTINATION_SIZE 0x00080080 + +/* dyadic arithmetic opcodes. */ +#define ADF_CODE 0x00000000 +#define MUF_CODE 0x00100000 +#define SUF_CODE 0x00200000 +#define RSF_CODE 0x00300000 +#define DVF_CODE 0x00400000 +#define RDF_CODE 0x00500000 +#define POW_CODE 0x00600000 +#define RPW_CODE 0x00700000 +#define RMF_CODE 0x00800000 +#define FML_CODE 0x00900000 +#define FDV_CODE 0x00a00000 +#define FRD_CODE 0x00b00000 +#define POL_CODE 0x00c00000 +/* 0x00d00000 is an invalid dyadic arithmetic opcode */ +/* 0x00e00000 is an invalid dyadic arithmetic opcode */ +/* 0x00f00000 is an invalid dyadic arithmetic opcode */ + +/* monadic arithmetic opcodes. */ +#define MVF_CODE 0x00008000 +#define MNF_CODE 0x00108000 +#define ABS_CODE 0x00208000 +#define RND_CODE 0x00308000 +#define SQT_CODE 0x00408000 +#define LOG_CODE 0x00508000 +#define LGN_CODE 0x00608000 +#define EXP_CODE 0x00708000 +#define SIN_CODE 0x00808000 +#define COS_CODE 0x00908000 +#define TAN_CODE 0x00a08000 +#define ASN_CODE 0x00b08000 +#define ACS_CODE 0x00c08000 +#define ATN_CODE 0x00d08000 +#define URD_CODE 0x00e08000 +#define NRM_CODE 0x00f08000 + +/* +=== +=== Definitions for register transfer and comparison instructions +=== +*/ + +#define MASK_CPRT 0x0e000010 /* register transfer opcode */ +#define MASK_CPRT_CODE 0x00f00000 +#define FLT_CODE 0x00000000 +#define FIX_CODE 0x00100000 +#define WFS_CODE 0x00200000 +#define RFS_CODE 0x00300000 +#define WFC_CODE 0x00400000 +#define RFC_CODE 0x00500000 +#define CMF_CODE 0x00900000 +#define CNF_CODE 0x00b00000 +#define CMFE_CODE 0x00d00000 +#define CNFE_CODE 0x00f00000 + +/* +=== +=== Common definitions +=== +*/ + +/* register masks */ +#define MASK_Rd 0x0000f000 +#define MASK_Rn 0x000f0000 +#define MASK_Fd 0x00007000 +#define MASK_Fm 0x00000007 +#define MASK_Fn 0x00070000 + +/* condition code masks */ +#define CC_MASK 0xf0000000 +#define CC_NEGATIVE 0x80000000 +#define CC_ZERO 0x40000000 +#define CC_CARRY 0x20000000 +#define CC_OVERFLOW 0x10000000 +#define CC_EQ 0x00000000 +#define CC_NE 0x10000000 +#define CC_CS 0x20000000 +#define CC_HS CC_CS +#define CC_CC 0x30000000 +#define CC_LO CC_CC +#define CC_MI 0x40000000 +#define CC_PL 0x50000000 +#define CC_VS 0x60000000 +#define CC_VC 0x70000000 +#define CC_HI 0x80000000 +#define CC_LS 0x90000000 +#define CC_GE 0xa0000000 +#define CC_LT 0xb0000000 +#define CC_GT 0xc0000000 +#define CC_LE 0xd0000000 +#define CC_AL 0xe0000000 +#define CC_NV 0xf0000000 + +/* rounding masks/values */ +#define MASK_ROUNDING_MODE 0x00000060 +#define ROUND_TO_NEAREST 0x00000000 +#define ROUND_TO_PLUS_INFINITY 0x00000020 +#define ROUND_TO_MINUS_INFINITY 0x00000040 +#define ROUND_TO_ZERO 0x00000060 + +#define MASK_ROUNDING_PRECISION 0x00080080 +#define ROUND_SINGLE 0x00000000 +#define ROUND_DOUBLE 0x00000080 +#define ROUND_EXTENDED 0x00080000 + +/* Get the condition code from the opcode. */ +#define getCondition(opcode) (opcode >> 28) + +/* Get the source register from the opcode. */ +#define getRn(opcode) ((opcode & MASK_Rn) >> 16) + +/* Get the destination floating point register from the opcode. */ +#define getFd(opcode) ((opcode & MASK_Fd) >> 12) + +/* Get the first source floating point register from the opcode. */ +#define getFn(opcode) ((opcode & MASK_Fn) >> 16) + +/* Get the second source floating point register from the opcode. */ +#define getFm(opcode) (opcode & MASK_Fm) + +/* Get the destination register from the opcode. */ +#define getRd(opcode) ((opcode & MASK_Rd) >> 12) + +/* Get the rounding mode from the opcode. */ +#define getRoundingMode(opcode) ((opcode & MASK_ROUNDING_MODE) >> 5) + +extern const floatx80 floatx80Constant[]; +extern const float64 float64Constant[]; +extern const float32 float32Constant[]; + +static inline floatx80 getExtendedConstant(const unsigned int nIndex) +{ + return floatx80Constant[nIndex]; +} + +static inline float64 getDoubleConstant(const unsigned int nIndex) +{ + return float64Constant[nIndex]; +} + +static inline float32 getSingleConstant(const unsigned int nIndex) +{ + return float32Constant[nIndex]; +} + +unsigned int getRegisterCount(const unsigned int opcode); +unsigned int getDestinationSize(const unsigned int opcode); + +#endif diff --git a/linux-user/arm/nwfpe/fpsr.h b/linux-user/arm/nwfpe/fpsr.h new file mode 100644 index 0000000..859dcd5 --- /dev/null +++ b/linux-user/arm/nwfpe/fpsr.h @@ -0,0 +1,107 @@ +/* + NetWinder Floating Point Emulator + (c) Rebel.com, 1998-1999 + + Direct questions, comments to Scott Bambrough + + 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 . +*/ + +#ifndef __FPSR_H__ +#define __FPSR_H__ + +/* +The FPSR is a 32 bit register consisting of 4 parts, each exactly +one byte. + + SYSTEM ID + EXCEPTION TRAP ENABLE BYTE + SYSTEM CONTROL BYTE + CUMULATIVE EXCEPTION FLAGS BYTE + +The FPCR is a 32 bit register consisting of bit flags. +*/ + +/* SYSTEM ID +------------ +Note: the system id byte is read only */ + +typedef unsigned int FPSR; /* type for floating point status register */ +typedef unsigned int FPCR; /* type for floating point control register */ + +#define MASK_SYSID 0xff000000 +#define BIT_HARDWARE 0x80000000 +#define FP_EMULATOR 0x01000000 /* System ID for emulator */ +#define FP_ACCELERATOR 0x81000000 /* System ID for FPA11 */ + +/* EXCEPTION TRAP ENABLE BYTE +----------------------------- */ + +#define MASK_TRAP_ENABLE 0x00ff0000 +#define MASK_TRAP_ENABLE_STRICT 0x001f0000 +#define BIT_IXE 0x00100000 /* inexact exception enable */ +#define BIT_UFE 0x00080000 /* underflow exception enable */ +#define BIT_OFE 0x00040000 /* overflow exception enable */ +#define BIT_DZE 0x00020000 /* divide by zero exception enable */ +#define BIT_IOE 0x00010000 /* invalid operation exception enable */ + +/* SYSTEM CONTROL BYTE +---------------------- */ + +#define MASK_SYSTEM_CONTROL 0x0000ff00 +#define MASK_TRAP_STRICT 0x00001f00 + +#define BIT_AC 0x00001000 /* use alternative C-flag definition + for compares */ +#define BIT_EP 0x00000800 /* use expanded packed decimal format */ +#define BIT_SO 0x00000400 /* select synchronous operation of FPA */ +#define BIT_NE 0x00000200 /* NaN exception bit */ +#define BIT_ND 0x00000100 /* no denormalized numbers bit */ + +/* CUMULATIVE EXCEPTION FLAGS BYTE +---------------------------------- */ + +#define MASK_EXCEPTION_FLAGS 0x000000ff +#define MASK_EXCEPTION_FLAGS_STRICT 0x0000001f + +#define BIT_IXC 0x00000010 /* inexact exception flag */ +#define BIT_UFC 0x00000008 /* underflow exception flag */ +#define BIT_OFC 0x00000004 /* overfloat exception flag */ +#define BIT_DZC 0x00000002 /* divide by zero exception flag */ +#define BIT_IOC 0x00000001 /* invalid operation exception flag */ + +/* Floating Point Control Register +----------------------------------*/ + +#define BIT_RU 0x80000000 /* rounded up bit */ +#define BIT_IE 0x10000000 /* inexact bit */ +#define BIT_MO 0x08000000 /* mantissa overflow bit */ +#define BIT_EO 0x04000000 /* exponent overflow bit */ +#define BIT_SB 0x00000800 /* store bounce */ +#define BIT_AB 0x00000400 /* arithmetic bounce */ +#define BIT_RE 0x00000200 /* rounding exception */ +#define BIT_DA 0x00000100 /* disable FPA */ + +#define MASK_OP 0x00f08010 /* AU operation code */ +#define MASK_PR 0x00080080 /* AU precision */ +#define MASK_S1 0x00070000 /* AU source register 1 */ +#define MASK_S2 0x00000007 /* AU source register 2 */ +#define MASK_DS 0x00007000 /* AU destination register */ +#define MASK_RM 0x00000060 /* AU rounding mode */ +#define MASK_ALU 0x9cfff2ff /* only ALU can write these bits */ +#define MASK_RESET 0x00000d00 /* bits set on reset, all others cleared */ +#define MASK_WFC MASK_RESET +#define MASK_RFC ~MASK_RESET + +#endif diff --git a/linux-user/arm/nwfpe/single_cpdo.c b/linux-user/arm/nwfpe/single_cpdo.c new file mode 100644 index 0000000..26168e2 --- /dev/null +++ b/linux-user/arm/nwfpe/single_cpdo.c @@ -0,0 +1,252 @@ +/* + NetWinder Floating Point Emulator + (c) Rebel.COM, 1998,1999 + + Direct questions, comments to Scott Bambrough + + 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 . +*/ + +#include "fpa11.h" +#include "softfloat.h" +#include "fpopcode.h" + +float32 float32_exp(float32 Fm); +float32 float32_ln(float32 Fm); +float32 float32_sin(float32 rFm); +float32 float32_cos(float32 rFm); +float32 float32_arcsin(float32 rFm); +float32 float32_arctan(float32 rFm); +float32 float32_log(float32 rFm); +float32 float32_tan(float32 rFm); +float32 float32_arccos(float32 rFm); +float32 float32_pow(float32 rFn,float32 rFm); +float32 float32_pol(float32 rFn,float32 rFm); + +unsigned int SingleCPDO(const unsigned int opcode) +{ + FPA11 *fpa11 = GET_FPA11(); + float32 rFm, rFn = float32_zero; + unsigned int Fd, Fm, Fn, nRc = 1; + + Fm = getFm(opcode); + if (CONSTANT_FM(opcode)) + { + rFm = getSingleConstant(Fm); + } + else + { + switch (fpa11->fType[Fm]) + { + case typeSingle: + rFm = fpa11->fpreg[Fm].fSingle; + break; + + default: return 0; + } + } + + if (!MONADIC_INSTRUCTION(opcode)) + { + Fn = getFn(opcode); + switch (fpa11->fType[Fn]) + { + case typeSingle: + rFn = fpa11->fpreg[Fn].fSingle; + break; + + default: return 0; + } + } + + Fd = getFd(opcode); + switch (opcode & MASK_ARITHMETIC_OPCODE) + { + /* dyadic opcodes */ + case ADF_CODE: + fpa11->fpreg[Fd].fSingle = float32_add(rFn,rFm, &fpa11->fp_status); + break; + + case MUF_CODE: + case FML_CODE: + fpa11->fpreg[Fd].fSingle = float32_mul(rFn,rFm, &fpa11->fp_status); + break; + + case SUF_CODE: + fpa11->fpreg[Fd].fSingle = float32_sub(rFn,rFm, &fpa11->fp_status); + break; + + case RSF_CODE: + fpa11->fpreg[Fd].fSingle = float32_sub(rFm,rFn, &fpa11->fp_status); + break; + + case DVF_CODE: + case FDV_CODE: + fpa11->fpreg[Fd].fSingle = float32_div(rFn,rFm, &fpa11->fp_status); + break; + + case RDF_CODE: + case FRD_CODE: + fpa11->fpreg[Fd].fSingle = float32_div(rFm,rFn, &fpa11->fp_status); + break; + +#if 0 + case POW_CODE: + fpa11->fpreg[Fd].fSingle = float32_pow(rFn,rFm); + break; + + case RPW_CODE: + fpa11->fpreg[Fd].fSingle = float32_pow(rFm,rFn); + break; +#endif + + case RMF_CODE: + fpa11->fpreg[Fd].fSingle = float32_rem(rFn,rFm, &fpa11->fp_status); + break; + +#if 0 + case POL_CODE: + fpa11->fpreg[Fd].fSingle = float32_pol(rFn,rFm); + break; +#endif + + /* monadic opcodes */ + case MVF_CODE: + fpa11->fpreg[Fd].fSingle = rFm; + break; + + case MNF_CODE: + fpa11->fpreg[Fd].fSingle = float32_chs(rFm); + break; + + case ABS_CODE: + fpa11->fpreg[Fd].fSingle = float32_abs(rFm); + break; + + case RND_CODE: + case URD_CODE: + fpa11->fpreg[Fd].fSingle = float32_round_to_int(rFm, &fpa11->fp_status); + break; + + case SQT_CODE: + fpa11->fpreg[Fd].fSingle = float32_sqrt(rFm, &fpa11->fp_status); + break; + +#if 0 + case LOG_CODE: + fpa11->fpreg[Fd].fSingle = float32_log(rFm); + break; + + case LGN_CODE: + fpa11->fpreg[Fd].fSingle = float32_ln(rFm); + break; + + case EXP_CODE: + fpa11->fpreg[Fd].fSingle = float32_exp(rFm); + break; + + case SIN_CODE: + fpa11->fpreg[Fd].fSingle = float32_sin(rFm); + break; + + case COS_CODE: + fpa11->fpreg[Fd].fSingle = float32_cos(rFm); + break; + + case TAN_CODE: + fpa11->fpreg[Fd].fSingle = float32_tan(rFm); + break; + + case ASN_CODE: + fpa11->fpreg[Fd].fSingle = float32_arcsin(rFm); + break; + + case ACS_CODE: + fpa11->fpreg[Fd].fSingle = float32_arccos(rFm); + break; + + case ATN_CODE: + fpa11->fpreg[Fd].fSingle = float32_arctan(rFm); + break; +#endif + + case NRM_CODE: + break; + + default: + { + nRc = 0; + } + } + + if (0 != nRc) fpa11->fType[Fd] = typeSingle; + return nRc; +} + +#if 0 +float32 float32_exp(float32 Fm) +{ +//series +} + +float32 float32_ln(float32 Fm) +{ +//series +} + +float32 float32_sin(float32 rFm) +{ +//series +} + +float32 float32_cos(float32 rFm) +{ +//series +} + +float32 float32_arcsin(float32 rFm) +{ +//series +} + +float32 float32_arctan(float32 rFm) +{ + //series +} + +float32 float32_arccos(float32 rFm) +{ + //return float32_sub(halfPi,float32_arcsin(rFm)); +} + +float32 float32_log(float32 rFm) +{ + return float32_div(float32_ln(rFm),getSingleConstant(7)); +} + +float32 float32_tan(float32 rFm) +{ + return float32_div(float32_sin(rFm),float32_cos(rFm)); +} + +float32 float32_pow(float32 rFn,float32 rFm) +{ + return float32_exp(float32_mul(rFm,float32_ln(rFn))); +} + +float32 float32_pol(float32 rFn,float32 rFm) +{ + return float32_arctan(float32_div(rFn,rFm)); +} +#endif diff --git a/linux-user/arm/syscall.h b/linux-user/arm/syscall.h new file mode 100644 index 0000000..003d424 --- /dev/null +++ b/linux-user/arm/syscall.h @@ -0,0 +1,42 @@ + +/* this struct defines the way the registers are stored on the + stack during a system call. */ + +struct target_pt_regs { + abi_long uregs[18]; +}; + +#define ARM_cpsr uregs[16] +#define ARM_pc uregs[15] +#define ARM_lr uregs[14] +#define ARM_sp uregs[13] +#define ARM_ip uregs[12] +#define ARM_fp uregs[11] +#define ARM_r10 uregs[10] +#define ARM_r9 uregs[9] +#define ARM_r8 uregs[8] +#define ARM_r7 uregs[7] +#define ARM_r6 uregs[6] +#define ARM_r5 uregs[5] +#define ARM_r4 uregs[4] +#define ARM_r3 uregs[3] +#define ARM_r2 uregs[2] +#define ARM_r1 uregs[1] +#define ARM_r0 uregs[0] +#define ARM_ORIG_r0 uregs[17] + +#define ARM_SYSCALL_BASE 0x900000 +#define ARM_THUMB_SYSCALL 0 + +#define ARM_NR_BASE 0xf0000 +#define ARM_NR_cacheflush (ARM_NR_BASE + 2) +#define ARM_NR_set_tls (ARM_NR_BASE + 5) + +#define ARM_NR_semihosting 0x123456 +#define ARM_NR_thumb_semihosting 0xAB + +#if defined(TARGET_WORDS_BIGENDIAN) +#define UNAME_MACHINE "armv5teb" +#else +#define UNAME_MACHINE "armv5tel" +#endif diff --git a/linux-user/arm/syscall_nr.h b/linux-user/arm/syscall_nr.h new file mode 100644 index 0000000..79a216a --- /dev/null +++ b/linux-user/arm/syscall_nr.h @@ -0,0 +1,367 @@ +/* + * This file contains the system call numbers. + */ + +#define TARGET_NR_restart_syscall ( 0) +#define TARGET_NR_exit ( 1) +#define TARGET_NR_fork ( 2) +#define TARGET_NR_read ( 3) +#define TARGET_NR_write ( 4) +#define TARGET_NR_open ( 5) +#define TARGET_NR_close ( 6) +#define TARGET_NR_waitpid ( 7) /* removed */ +#define TARGET_NR_creat ( 8) +#define TARGET_NR_link ( 9) +#define TARGET_NR_unlink ( 10) +#define TARGET_NR_execve ( 11) +#define TARGET_NR_chdir ( 12) +#define TARGET_NR_time ( 13) +#define TARGET_NR_mknod ( 14) +#define TARGET_NR_chmod ( 15) +#define TARGET_NR_lchown ( 16) +#define TARGET_NR_break ( 17) /* removed */ + /* 18 was sys_stat */ +#define TARGET_NR_lseek ( 19) +#define TARGET_NR_getpid ( 20) +#define TARGET_NR_mount ( 21) +#define TARGET_NR_umount ( 22) +#define TARGET_NR_setuid ( 23) +#define TARGET_NR_getuid ( 24) +#define TARGET_NR_stime ( 25) +#define TARGET_NR_ptrace ( 26) +#define TARGET_NR_alarm ( 27) + +#define TARGET_NR_pause ( 29) +#define TARGET_NR_utime ( 30) +#define TARGET_NR_stty ( 31) /* removed */ +#define TARGET_NR_gtty ( 32) /* removed */ +#define TARGET_NR_access ( 33) +#define TARGET_NR_nice ( 34) +#define TARGET_NR_ftime ( 35) /* removed */ +#define TARGET_NR_sync ( 36) +#define TARGET_NR_kill ( 37) +#define TARGET_NR_rename ( 38) +#define TARGET_NR_mkdir ( 39) +#define TARGET_NR_rmdir ( 40) +#define TARGET_NR_dup ( 41) +#define TARGET_NR_pipe ( 42) +#define TARGET_NR_times ( 43) +#define TARGET_NR_prof ( 44) /* removed */ +#define TARGET_NR_brk ( 45) +#define TARGET_NR_setgid ( 46) +#define TARGET_NR_getgid ( 47) +#define TARGET_NR_signal ( 48) /* removed */ +#define TARGET_NR_geteuid ( 49) +#define TARGET_NR_getegid ( 50) +#define TARGET_NR_acct ( 51) +#define TARGET_NR_umount2 ( 52) +#define TARGET_NR_lock ( 53) /* removed */ +#define TARGET_NR_ioctl ( 54) +#define TARGET_NR_fcntl ( 55) +#define TARGET_NR_mpx ( 56) /* removed */ +#define TARGET_NR_setpgid ( 57) +#define TARGET_NR_ulimit ( 58) /* removed */ + /* 59 was sys_olduname */ +#define TARGET_NR_umask ( 60) +#define TARGET_NR_chroot ( 61) +#define TARGET_NR_ustat ( 62) +#define TARGET_NR_dup2 ( 63) +#define TARGET_NR_getppid ( 64) +#define TARGET_NR_getpgrp ( 65) +#define TARGET_NR_setsid ( 66) +#define TARGET_NR_sigaction ( 67) +#define TARGET_NR_sgetmask ( 68) /* removed */ +#define TARGET_NR_ssetmask ( 69) /* removed */ +#define TARGET_NR_setreuid ( 70) +#define TARGET_NR_setregid ( 71) +#define TARGET_NR_sigsuspend ( 72) +#define TARGET_NR_sigpending ( 73) +#define TARGET_NR_sethostname ( 74) +#define TARGET_NR_setrlimit ( 75) +#define TARGET_NR_getrlimit ( 76) /* Back compat 2GB limited rlimit */ +#define TARGET_NR_getrusage ( 77) +#define TARGET_NR_gettimeofday ( 78) +#define TARGET_NR_settimeofday ( 79) +#define TARGET_NR_getgroups ( 80) +#define TARGET_NR_setgroups ( 81) +#define TARGET_NR_select ( 82) +#define TARGET_NR_symlink ( 83) + /* 84 was sys_lstat */ +#define TARGET_NR_readlink ( 85) +#define TARGET_NR_uselib ( 86) +#define TARGET_NR_swapon ( 87) +#define TARGET_NR_reboot ( 88) +#define TARGET_NR_readdir ( 89) +#define TARGET_NR_mmap ( 90) +#define TARGET_NR_munmap ( 91) +#define TARGET_NR_truncate ( 92) +#define TARGET_NR_ftruncate ( 93) +#define TARGET_NR_fchmod ( 94) +#define TARGET_NR_fchown ( 95) +#define TARGET_NR_getpriority ( 96) +#define TARGET_NR_setpriority ( 97) +#define TARGET_NR_profil ( 98) /* removed */ +#define TARGET_NR_statfs ( 99) +#define TARGET_NR_fstatfs (100) +#define TARGET_NR_ioperm (101) +#define TARGET_NR_socketcall (102) +#define TARGET_NR_syslog (103) +#define TARGET_NR_setitimer (104) +#define TARGET_NR_getitimer (105) +#define TARGET_NR_stat (106) +#define TARGET_NR_lstat (107) +#define TARGET_NR_fstat (108) + /* 109 was sys_uname */ + /* 110 was sys_iopl */ +#define TARGET_NR_vhangup (111) +#define TARGET_NR_idle (112) +#define TARGET_NR_syscall (113) /* syscall to call a syscall! */ +#define TARGET_NR_wait4 (114) +#define TARGET_NR_swapoff (115) +#define TARGET_NR_sysinfo (116) +#define TARGET_NR_ipc (117) +#define TARGET_NR_fsync (118) +#define TARGET_NR_sigreturn (119) +#define TARGET_NR_clone (120) +#define TARGET_NR_setdomainname (121) +#define TARGET_NR_uname (122) +#define TARGET_NR_modify_ldt (123) +#define TARGET_NR_adjtimex (124) +#define TARGET_NR_mprotect (125) +#define TARGET_NR_sigprocmask (126) +#define TARGET_NR_create_module (127) /* removed */ +#define TARGET_NR_init_module (128) +#define TARGET_NR_delete_module (129) +#define TARGET_NR_get_kernel_syms (130) /* removed */ +#define TARGET_NR_quotactl (131) +#define TARGET_NR_getpgid (132) +#define TARGET_NR_fchdir (133) +#define TARGET_NR_bdflush (134) +#define TARGET_NR_sysfs (135) +#define TARGET_NR_personality (136) +#define TARGET_NR_afs_syscall (137) /* Syscall for Andrew File System */ +#define TARGET_NR_setfsuid (138) +#define TARGET_NR_setfsgid (139) +#define TARGET_NR__llseek (140) +#define TARGET_NR_getdents (141) +#define TARGET_NR__newselect (142) +#define TARGET_NR_flock (143) +#define TARGET_NR_msync (144) +#define TARGET_NR_readv (145) +#define TARGET_NR_writev (146) +#define TARGET_NR_getsid (147) +#define TARGET_NR_fdatasync (148) +#define TARGET_NR__sysctl (149) +#define TARGET_NR_mlock (150) +#define TARGET_NR_munlock (151) +#define TARGET_NR_mlockall (152) +#define TARGET_NR_munlockall (153) +#define TARGET_NR_sched_setparam (154) +#define TARGET_NR_sched_getparam (155) +#define TARGET_NR_sched_setscheduler (156) +#define TARGET_NR_sched_getscheduler (157) +#define TARGET_NR_sched_yield (158) +#define TARGET_NR_sched_get_priority_max (159) +#define TARGET_NR_sched_get_priority_min (160) +#define TARGET_NR_sched_rr_get_interval (161) +#define TARGET_NR_nanosleep (162) +#define TARGET_NR_mremap (163) +#define TARGET_NR_setresuid (164) +#define TARGET_NR_getresuid (165) +#define TARGET_NR_vm86 (166) /* removed */ +#define TARGET_NR_query_module (167) /* removed */ +#define TARGET_NR_poll (168) +#define TARGET_NR_nfsservctl (169) +#define TARGET_NR_setresgid (170) +#define TARGET_NR_getresgid (171) +#define TARGET_NR_prctl (172) +#define TARGET_NR_rt_sigreturn (173) +#define TARGET_NR_rt_sigaction (174) +#define TARGET_NR_rt_sigprocmask (175) +#define TARGET_NR_rt_sigpending (176) +#define TARGET_NR_rt_sigtimedwait (177) +#define TARGET_NR_rt_sigqueueinfo (178) +#define TARGET_NR_rt_sigsuspend (179) +#define TARGET_NR_pread (180) +#define TARGET_NR_pwrite (181) +#define TARGET_NR_chown (182) +#define TARGET_NR_getcwd (183) +#define TARGET_NR_capget (184) +#define TARGET_NR_capset (185) +#define TARGET_NR_sigaltstack (186) +#define TARGET_NR_sendfile (187) + /* 188 reserved */ + /* 189 reserved */ +#define TARGET_NR_vfork (190) +#define TARGET_NR_ugetrlimit (191) /* SuS compliant getrlimit */ +#define TARGET_NR_mmap2 (192) +#define TARGET_NR_truncate64 (193) +#define TARGET_NR_ftruncate64 (194) +#define TARGET_NR_stat64 (195) +#define TARGET_NR_lstat64 (196) +#define TARGET_NR_fstat64 (197) +#define TARGET_NR_lchown32 (198) +#define TARGET_NR_getuid32 (199) +#define TARGET_NR_getgid32 (200) +#define TARGET_NR_geteuid32 (201) +#define TARGET_NR_getegid32 (202) +#define TARGET_NR_setreuid32 (203) +#define TARGET_NR_setregid32 (204) +#define TARGET_NR_getgroups32 (205) +#define TARGET_NR_setgroups32 (206) +#define TARGET_NR_fchown32 (207) +#define TARGET_NR_setresuid32 (208) +#define TARGET_NR_getresuid32 (209) +#define TARGET_NR_setresgid32 (210) +#define TARGET_NR_getresgid32 (211) +#define TARGET_NR_chown32 (212) +#define TARGET_NR_setuid32 (213) +#define TARGET_NR_setgid32 (214) +#define TARGET_NR_setfsuid32 (215) +#define TARGET_NR_setfsgid32 (216) +#define TARGET_NR_getdents64 (217) +#define TARGET_NR_pivot_root (218) +#define TARGET_NR_mincore (219) +#define TARGET_NR_madvise (220) +#define TARGET_NR_fcntl64 (221) + /* 222 for tux */ + /* 223 is unused */ +#define TARGET_NR_gettid (224) +#define TARGET_NR_readahead (225) +#define TARGET_NR_setxattr (226) +#define TARGET_NR_lsetxattr (227) +#define TARGET_NR_fsetxattr (228) +#define TARGET_NR_getxattr (229) +#define TARGET_NR_lgetxattr (230) +#define TARGET_NR_fgetxattr (231) +#define TARGET_NR_listxattr (232) +#define TARGET_NR_llistxattr (233) +#define TARGET_NR_flistxattr (234) +#define TARGET_NR_removexattr (235) +#define TARGET_NR_lremovexattr (236) +#define TARGET_NR_fremovexattr (237) +#define TARGET_NR_tkill (238) +#define TARGET_NR_sendfile64 (239) +#define TARGET_NR_futex (240) +#define TARGET_NR_sched_setaffinity (241) +#define TARGET_NR_sched_getaffinity (242) +#define TARGET_NR_io_setup (243) +#define TARGET_NR_io_destroy (244) +#define TARGET_NR_io_getevents (245) +#define TARGET_NR_io_submit (246) +#define TARGET_NR_io_cancel (247) +#define TARGET_NR_exit_group (248) +#define TARGET_NR_lookup_dcookie (249) +#define TARGET_NR_epoll_create (250) +#define TARGET_NR_epoll_ctl (251) +#define TARGET_NR_epoll_wait (252) +#define TARGET_NR_remap_file_pages (253) + /* 254 for set_thread_area */ + /* 255 for get_thread_area */ + /* 256 for set_tid_address */ +#define TARGET_NR_set_tid_address 256 +#define TARGET_NR_timer_create 257 +#define TARGET_NR_timer_settime 258 +#define TARGET_NR_timer_gettime 259 +#define TARGET_NR_timer_getoverrun 260 +#define TARGET_NR_timer_delete 261 +#define TARGET_NR_clock_settime 262 +#define TARGET_NR_clock_gettime 263 +#define TARGET_NR_clock_getres 264 +#define TARGET_NR_clock_nanosleep 265 +#define TARGET_NR_statfs64 266 +#define TARGET_NR_fstatfs64 267 +#define TARGET_NR_tgkill 268 +#define TARGET_NR_utimes 269 +#define TARGET_NR_arm_fadvise64_64 270 +#define TARGET_NR_pciconfig_iobase 271 +#define TARGET_NR_pciconfig_read 272 +#define TARGET_NR_pciconfig_write 273 +#define TARGET_NR_mq_open 274 +#define TARGET_NR_mq_unlink 275 +#define TARGET_NR_mq_timedsend 276 +#define TARGET_NR_mq_timedreceive 277 +#define TARGET_NR_mq_notify 278 +#define TARGET_NR_mq_getsetattr 279 +#define TARGET_NR_waitid 280 +#define TARGET_NR_socket 281 +#define TARGET_NR_bind 282 +#define TARGET_NR_connect 283 +#define TARGET_NR_listen 284 +#define TARGET_NR_accept 285 +#define TARGET_NR_getsockname 286 +#define TARGET_NR_getpeername 287 +#define TARGET_NR_socketpair 288 +#define TARGET_NR_send 289 +#define TARGET_NR_sendto 290 +#define TARGET_NR_recv 291 +#define TARGET_NR_recvfrom 292 +#define TARGET_NR_shutdown 293 +#define TARGET_NR_setsockopt 294 +#define TARGET_NR_getsockopt 295 +#define TARGET_NR_sendmsg 296 +#define TARGET_NR_recvmsg 297 +#define TARGET_NR_semop 298 +#define TARGET_NR_semget 299 +#define TARGET_NR_semctl 300 +#define TARGET_NR_msgsnd 301 +#define TARGET_NR_msgrcv 302 +#define TARGET_NR_msgget 303 +#define TARGET_NR_msgctl 304 +#define TARGET_NR_shmat 305 +#define TARGET_NR_shmdt 306 +#define TARGET_NR_shmget 307 +#define TARGET_NR_shmctl 308 +#define TARGET_NR_add_key 309 +#define TARGET_NR_request_key 310 +#define TARGET_NR_keyctl 311 +#define TARGET_NR_semtimedop 312 +#define TARGET_NR_vserver 313 +#define TARGET_NR_ioprio_set 314 +#define TARGET_NR_ioprio_get 315 +#define TARGET_NR_inotify_init 316 +#define TARGET_NR_inotify_add_watch 317 +#define TARGET_NR_inotify_rm_watch 318 +#define TARGET_NR_mbind 319 +#define TARGET_NR_get_mempolicy 320 +#define TARGET_NR_set_mempolicy 321 +#define TARGET_NR_openat (322) +#define TARGET_NR_mkdirat (323) +#define TARGET_NR_mknodat (324) +#define TARGET_NR_fchownat (325) +#define TARGET_NR_futimesat (326) +#define TARGET_NR_fstatat64 (327) +#define TARGET_NR_unlinkat (328) +#define TARGET_NR_renameat (329) +#define TARGET_NR_linkat (330) +#define TARGET_NR_symlinkat (331) +#define TARGET_NR_readlinkat (332) +#define TARGET_NR_fchmodat (333) +#define TARGET_NR_faccessat (334) +#define TARGET_NR_pselect6 (335) + /* 336 for ppoll */ +#define TARGET_NR_unshare (337) +#define TARGET_NR_set_robust_list (338) +#define TARGET_NR_get_robust_list (339) +#define TARGET_NR_splice (340) +#define TARGET_NR_arm_sync_file_range (341) +#define TARGET_NR_sync_file_range2 TARGET_NR_arm_sync_file_range +#define TARGET_NR_tee (342) +#define TARGET_NR_vmsplice (343) +#define TARGET_NR_move_pages (344) +#define TARGET_NR_getcpu (345) + /* 346 for epoll_pwait */ +#define TARGET_NR_kexec_load (347) +#define TARGET_NR_utimensat (348) +#define TARGET_NR_signalfd (349) +#define TARGET_NR_timerfd (350) +#define TARGET_NR_eventfd (351) +#define TARGET_NR_fallocate (352) +#define TARGET_NR_timerfd_settime (353) +#define TARGET_NR_timerfd_gettime (354) +#define TARGET_NR_signalfd4 (355) +#define TARGET_NR_eventfd2 (356) +#define TARGET_NR_epoll_create1 (357) +#define TARGET_NR_dup3 (358) +#define TARGET_NR_pipe2 (359) +#define TARGET_NR_inotify_init1 (360) diff --git a/linux-user/arm/target_signal.h b/linux-user/arm/target_signal.h new file mode 100644 index 0000000..2b32813 --- /dev/null +++ b/linux-user/arm/target_signal.h @@ -0,0 +1,29 @@ +#ifndef TARGET_SIGNAL_H +#define TARGET_SIGNAL_H + +#include "cpu.h" + +/* this struct defines a stack used during syscall handling */ + +typedef struct target_sigaltstack { + abi_ulong ss_sp; + abi_long ss_flags; + abi_ulong ss_size; +} target_stack_t; + + +/* + * sigaltstack controls + */ +#define TARGET_SS_ONSTACK 1 +#define TARGET_SS_DISABLE 2 + +#define TARGET_MINSIGSTKSZ 2048 +#define TARGET_SIGSTKSZ 8192 + +static inline abi_ulong get_sp_from_cpustate(CPUARMState *state) +{ + return state->regs[13]; +} + +#endif /* TARGET_SIGNAL_H */ diff --git a/linux-user/arm/termbits.h b/linux-user/arm/termbits.h new file mode 100644 index 0000000..7772df1 --- /dev/null +++ b/linux-user/arm/termbits.h @@ -0,0 +1,216 @@ +/* from asm/termbits.h */ +/* NOTE: exactly the same as i386 */ + +#define TARGET_NCCS 19 + +struct target_termios { + unsigned int c_iflag; /* input mode flags */ + unsigned int c_oflag; /* output mode flags */ + unsigned int c_cflag; /* control mode flags */ + unsigned int c_lflag; /* local mode flags */ + unsigned char c_line; /* line discipline */ + unsigned char c_cc[TARGET_NCCS]; /* control characters */ +}; + +/* c_iflag bits */ +#define TARGET_IGNBRK 0000001 +#define TARGET_BRKINT 0000002 +#define TARGET_IGNPAR 0000004 +#define TARGET_PARMRK 0000010 +#define TARGET_INPCK 0000020 +#define TARGET_ISTRIP 0000040 +#define TARGET_INLCR 0000100 +#define TARGET_IGNCR 0000200 +#define TARGET_ICRNL 0000400 +#define TARGET_IUCLC 0001000 +#define TARGET_IXON 0002000 +#define TARGET_IXANY 0004000 +#define TARGET_IXOFF 0010000 +#define TARGET_IMAXBEL 0020000 +#define TARGET_IUTF8 0040000 + +/* c_oflag bits */ +#define TARGET_OPOST 0000001 +#define TARGET_OLCUC 0000002 +#define TARGET_ONLCR 0000004 +#define TARGET_OCRNL 0000010 +#define TARGET_ONOCR 0000020 +#define TARGET_ONLRET 0000040 +#define TARGET_OFILL 0000100 +#define TARGET_OFDEL 0000200 +#define TARGET_NLDLY 0000400 +#define TARGET_NL0 0000000 +#define TARGET_NL1 0000400 +#define TARGET_CRDLY 0003000 +#define TARGET_CR0 0000000 +#define TARGET_CR1 0001000 +#define TARGET_CR2 0002000 +#define TARGET_CR3 0003000 +#define TARGET_TABDLY 0014000 +#define TARGET_TAB0 0000000 +#define TARGET_TAB1 0004000 +#define TARGET_TAB2 0010000 +#define TARGET_TAB3 0014000 +#define TARGET_XTABS 0014000 +#define TARGET_BSDLY 0020000 +#define TARGET_BS0 0000000 +#define TARGET_BS1 0020000 +#define TARGET_VTDLY 0040000 +#define TARGET_VT0 0000000 +#define TARGET_VT1 0040000 +#define TARGET_FFDLY 0100000 +#define TARGET_FF0 0000000 +#define TARGET_FF1 0100000 + +/* c_cflag bit meaning */ +#define TARGET_CBAUD 0010017 +#define TARGET_B0 0000000 /* hang up */ +#define TARGET_B50 0000001 +#define TARGET_B75 0000002 +#define TARGET_B110 0000003 +#define TARGET_B134 0000004 +#define TARGET_B150 0000005 +#define TARGET_B200 0000006 +#define TARGET_B300 0000007 +#define TARGET_B600 0000010 +#define TARGET_B1200 0000011 +#define TARGET_B1800 0000012 +#define TARGET_B2400 0000013 +#define TARGET_B4800 0000014 +#define TARGET_B9600 0000015 +#define TARGET_B19200 0000016 +#define TARGET_B38400 0000017 +#define TARGET_EXTA B19200 +#define TARGET_EXTB B38400 +#define TARGET_CSIZE 0000060 +#define TARGET_CS5 0000000 +#define TARGET_CS6 0000020 +#define TARGET_CS7 0000040 +#define TARGET_CS8 0000060 +#define TARGET_CSTOPB 0000100 +#define TARGET_CREAD 0000200 +#define TARGET_PARENB 0000400 +#define TARGET_PARODD 0001000 +#define TARGET_HUPCL 0002000 +#define TARGET_CLOCAL 0004000 +#define TARGET_CBAUDEX 0010000 +#define TARGET_B57600 0010001 +#define TARGET_B115200 0010002 +#define TARGET_B230400 0010003 +#define TARGET_B460800 0010004 +#define TARGET_CIBAUD 002003600000 /* input baud rate (not used) */ +#define TARGET_CMSPAR 010000000000 /* mark or space (stick) parity */ +#define TARGET_CRTSCTS 020000000000 /* flow control */ + +/* c_lflag bits */ +#define TARGET_ISIG 0000001 +#define TARGET_ICANON 0000002 +#define TARGET_XCASE 0000004 +#define TARGET_ECHO 0000010 +#define TARGET_ECHOE 0000020 +#define TARGET_ECHOK 0000040 +#define TARGET_ECHONL 0000100 +#define TARGET_NOFLSH 0000200 +#define TARGET_TOSTOP 0000400 +#define TARGET_ECHOCTL 0001000 +#define TARGET_ECHOPRT 0002000 +#define TARGET_ECHOKE 0004000 +#define TARGET_FLUSHO 0010000 +#define TARGET_PENDIN 0040000 +#define TARGET_IEXTEN 0100000 + +/* c_cc character offsets */ +#define TARGET_VINTR 0 +#define TARGET_VQUIT 1 +#define TARGET_VERASE 2 +#define TARGET_VKILL 3 +#define TARGET_VEOF 4 +#define TARGET_VTIME 5 +#define TARGET_VMIN 6 +#define TARGET_VSWTC 7 +#define TARGET_VSTART 8 +#define TARGET_VSTOP 9 +#define TARGET_VSUSP 10 +#define TARGET_VEOL 11 +#define TARGET_VREPRINT 12 +#define TARGET_VDISCARD 13 +#define TARGET_VWERASE 14 +#define TARGET_VLNEXT 15 +#define TARGET_VEOL2 16 + +/* ioctls */ + +#define TARGET_TCGETS 0x5401 +#define TARGET_TCSETS 0x5402 +#define TARGET_TCSETSW 0x5403 +#define TARGET_TCSETSF 0x5404 +#define TARGET_TCGETA 0x5405 +#define TARGET_TCSETA 0x5406 +#define TARGET_TCSETAW 0x5407 +#define TARGET_TCSETAF 0x5408 +#define TARGET_TCSBRK 0x5409 +#define TARGET_TCXONC 0x540A +#define TARGET_TCFLSH 0x540B + +#define TARGET_TIOCEXCL 0x540C +#define TARGET_TIOCNXCL 0x540D +#define TARGET_TIOCSCTTY 0x540E +#define TARGET_TIOCGPGRP 0x540F +#define TARGET_TIOCSPGRP 0x5410 +#define TARGET_TIOCOUTQ 0x5411 +#define TARGET_TIOCSTI 0x5412 +#define TARGET_TIOCGWINSZ 0x5413 +#define TARGET_TIOCSWINSZ 0x5414 +#define TARGET_TIOCMGET 0x5415 +#define TARGET_TIOCMBIS 0x5416 +#define TARGET_TIOCMBIC 0x5417 +#define TARGET_TIOCMSET 0x5418 +#define TARGET_TIOCGSOFTCAR 0x5419 +#define TARGET_TIOCSSOFTCAR 0x541A +#define TARGET_FIONREAD 0x541B +#define TARGET_TIOCINQ TARGET_FIONREAD +#define TARGET_TIOCLINUX 0x541C +#define TARGET_TIOCCONS 0x541D +#define TARGET_TIOCGSERIAL 0x541E +#define TARGET_TIOCSSERIAL 0x541F +#define TARGET_TIOCPKT 0x5420 +#define TARGET_FIONBIO 0x5421 +#define TARGET_TIOCNOTTY 0x5422 +#define TARGET_TIOCSETD 0x5423 +#define TARGET_TIOCGETD 0x5424 +#define TARGET_TCSBRKP 0x5425 /* Needed for POSIX tcsendbreak() */ +#define TARGET_TIOCTTYGSTRUCT 0x5426 /* For debugging only */ +#define TARGET_TIOCSBRK 0x5427 /* BSD compatibility */ +#define TARGET_TIOCCBRK 0x5428 /* BSD compatibility */ +#define TARGET_TIOCGSID 0x5429 /* Return the session ID of FD */ +#define TARGET_TIOCGPTN TARGET_IOR('T',0x30, unsigned int) /* Get Pty Number (of pty-mux device) */ +#define TARGET_TIOCSPTLCK TARGET_IOW('T',0x31, int) /* Lock/unlock Pty */ + +#define TARGET_FIONCLEX 0x5450 /* these numbers need to be adjusted. */ +#define TARGET_FIOCLEX 0x5451 +#define TARGET_FIOASYNC 0x5452 +#define TARGET_TIOCSERCONFIG 0x5453 +#define TARGET_TIOCSERGWILD 0x5454 +#define TARGET_TIOCSERSWILD 0x5455 +#define TARGET_TIOCGLCKTRMIOS 0x5456 +#define TARGET_TIOCSLCKTRMIOS 0x5457 +#define TARGET_TIOCSERGSTRUCT 0x5458 /* For debugging only */ +#define TARGET_TIOCSERGETLSR 0x5459 /* Get line status register */ +#define TARGET_TIOCSERGETMULTI 0x545A /* Get multiport config */ +#define TARGET_TIOCSERSETMULTI 0x545B /* Set multiport config */ + +#define TARGET_TIOCMIWAIT 0x545C /* wait for a change on serial input line(s) */ +#define TARGET_TIOCGICOUNT 0x545D /* read serial port inline interrupt counts */ +#define TARGET_TIOCGHAYESESP 0x545E /* Get Hayes ESP configuration */ +#define TARGET_TIOCSHAYESESP 0x545F /* Set Hayes ESP configuration */ + +/* Used for packet mode */ +#define TARGET_TIOCPKT_DATA 0 +#define TARGET_TIOCPKT_FLUSHREAD 1 +#define TARGET_TIOCPKT_FLUSHWRITE 2 +#define TARGET_TIOCPKT_STOP 4 +#define TARGET_TIOCPKT_START 8 +#define TARGET_TIOCPKT_NOSTOP 16 +#define TARGET_TIOCPKT_DOSTOP 32 + +#define TARGET_TIOCSER_TEMT 0x01 /* Transmitter physically empty */ diff --git a/linux-user/cpu-uname.c b/linux-user/cpu-uname.c new file mode 100644 index 0000000..23afede --- /dev/null +++ b/linux-user/cpu-uname.c @@ -0,0 +1,72 @@ +/* + * cpu to uname machine name map + * + * Copyright (c) 2009 Loïc Minier + * + * 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 . + */ + +#include + +#include "qemu.h" +//#include "qemu-common.h" +#include "cpu-uname.h" + +/* return highest utsname machine name for emulated instruction set + * + * NB: the default emulated CPU ("any") might not match any existing CPU, e.g. + * on ARM it has all features turned on, so there is no perfect arch string to + * return here */ +const char *cpu_to_uname_machine(void *cpu_env) +{ +#ifdef TARGET_ARM + /* utsname machine name on linux arm is CPU arch name + endianness, e.g. + * armv7l; to get a list of CPU arch names from the linux source, use: + * grep arch_name: -A1 linux/arch/arm/mm/proc-*.S + * see arch/arm/kernel/setup.c: setup_processor() + * + * to test by CPU id, compare cpu_env->cp15.c0_cpuid to ARM_CPUID_* + * defines and to test by CPU feature, use arm_feature(cpu_env, + * ARM_FEATURE_*) */ + + /* in theory, endianness is configurable on some ARM CPUs, but this isn't + * used in user mode emulation */ +#ifdef TARGET_WORDS_BIGENDIAN +#define utsname_suffix "b" +#else +#define utsname_suffix "l" +#endif + if (arm_feature(cpu_env, ARM_FEATURE_V7)) + return "armv7" utsname_suffix; + if (arm_feature(cpu_env, ARM_FEATURE_V6)) + return "armv6" utsname_suffix; + /* earliest emulated CPU is ARMv5TE; qemu can emulate the 1026, but not its + * Jazelle support */ + return "armv5te" utsname_suffix; +#elif defined(TARGET_X86_64) + return "x86-64"; +#elif defined(TARGET_I386) + /* see arch/x86/kernel/cpu/bugs.c: check_bugs(), 386, 486, 586, 686 */ + uint32_t cpuid_version = ((CPUX86State *)cpu_env)->cpuid_version; + int family = ((cpuid_version >> 8) & 0x0f) + ((cpuid_version >> 20) & 0xff); + if (family == 4) + return "i486"; + if (family == 5) + return "i586"; + return "i686"; +#else + /* default is #define-d in each arch/ subdir */ + return UNAME_MACHINE; +#endif +} diff --git a/linux-user/cpu-uname.h b/linux-user/cpu-uname.h new file mode 100644 index 0000000..32492de --- /dev/null +++ b/linux-user/cpu-uname.h @@ -0,0 +1 @@ +const char *cpu_to_uname_machine(void *cpu_env); diff --git a/linux-user/cris/syscall.h b/linux-user/cris/syscall.h new file mode 100644 index 0000000..24f92ba --- /dev/null +++ b/linux-user/cris/syscall.h @@ -0,0 +1,36 @@ + +#define UNAME_MACHINE "cris" + +/* pt_regs not only specifices the format in the user-struct during + * ptrace but is also the frame format used in the kernel prologue/epilogues + * themselves + */ + +struct target_pt_regs { + unsigned long orig_r10; + /* pushed by movem r13, [sp] in SAVE_ALL. */ + unsigned long r0; + unsigned long r1; + unsigned long r2; + unsigned long r3; + unsigned long r4; + unsigned long r5; + unsigned long r6; + unsigned long r7; + unsigned long r8; + unsigned long r9; + unsigned long r10; + unsigned long r11; + unsigned long r12; + unsigned long r13; + unsigned long acr; + unsigned long srs; + unsigned long mof; + unsigned long spc; + unsigned long ccs; + unsigned long srp; + unsigned long erp; /* This is actually the debugged process' PC */ + /* For debugging purposes; saved only when needed. */ + unsigned long exs; + unsigned long eda; +}; diff --git a/linux-user/cris/syscall_nr.h b/linux-user/cris/syscall_nr.h new file mode 100644 index 0000000..6132817 --- /dev/null +++ b/linux-user/cris/syscall_nr.h @@ -0,0 +1,335 @@ +/* + * This file contains the system call numbers, and stub macros for libc. + */ + +#define TARGET_NR_restart_syscall 0 +#define TARGET_NR_exit 1 +#define TARGET_NR_fork 2 +#define TARGET_NR_read 3 +#define TARGET_NR_write 4 +#define TARGET_NR_open 5 +#define TARGET_NR_close 6 +#define TARGET_NR_waitpid 7 +#define TARGET_NR_creat 8 +#define TARGET_NR_link 9 +#define TARGET_NR_unlink 10 +#define TARGET_NR_execve 11 +#define TARGET_NR_chdir 12 +#define TARGET_NR_time 13 +#define TARGET_NR_mknod 14 +#define TARGET_NR_chmod 15 +#define TARGET_NR_lchown 16 +#define TARGET_NR_break 17 +#define TARGET_NR_oldstat 18 +#define TARGET_NR_lseek 19 +#define TARGET_NR_getpid 20 +#define TARGET_NR_mount 21 +#define TARGET_NR_umount 22 +#define TARGET_NR_setuid 23 +#define TARGET_NR_getuid 24 +#define TARGET_NR_stime 25 +#define TARGET_NR_ptrace 26 +#define TARGET_NR_alarm 27 +#define TARGET_NR_oldfstat 28 +#define TARGET_NR_pause 29 +#define TARGET_NR_utime 30 +#define TARGET_NR_stty 31 +#define TARGET_NR_gtty 32 +#define TARGET_NR_access 33 +#define TARGET_NR_nice 34 +#define TARGET_NR_ftime 35 +#define TARGET_NR_sync 36 +#define TARGET_NR_kill 37 +#define TARGET_NR_rename 38 +#define TARGET_NR_mkdir 39 +#define TARGET_NR_rmdir 40 +#define TARGET_NR_dup 41 +#define TARGET_NR_pipe 42 +#define TARGET_NR_times 43 +#define TARGET_NR_prof 44 +#define TARGET_NR_brk 45 +#define TARGET_NR_setgid 46 +#define TARGET_NR_getgid 47 +#define TARGET_NR_signal 48 +#define TARGET_NR_geteuid 49 +#define TARGET_NR_getegid 50 +#define TARGET_NR_acct 51 +#define TARGET_NR_umount2 52 +#define TARGET_NR_lock 53 +#define TARGET_NR_ioctl 54 +#define TARGET_NR_fcntl 55 +#define TARGET_NR_mpx 56 +#define TARGET_NR_setpgid 57 +#define TARGET_NR_ulimit 58 +#define TARGET_NR_oldolduname 59 +#define TARGET_NR_umask 60 +#define TARGET_NR_chroot 61 +#define TARGET_NR_ustat 62 +#define TARGET_NR_dup2 63 +#define TARGET_NR_getppid 64 +#define TARGET_NR_getpgrp 65 +#define TARGET_NR_setsid 66 +#define TARGET_NR_sigaction 67 +#define TARGET_NR_sgetmask 68 +#define TARGET_NR_ssetmask 69 +#define TARGET_NR_setreuid 70 +#define TARGET_NR_setregid 71 +#define TARGET_NR_sigsuspend 72 +#define TARGET_NR_sigpending 73 +#define TARGET_NR_sethostname 74 +#define TARGET_NR_setrlimit 75 +#define TARGET_NR_getrlimit 76 +#define TARGET_NR_getrusage 77 +#define TARGET_NR_gettimeofday 78 +#define TARGET_NR_settimeofday 79 +#define TARGET_NR_getgroups 80 +#define TARGET_NR_setgroups 81 +#define TARGET_NR_select 82 +#define TARGET_NR_symlink 83 +#define TARGET_NR_oldlstat 84 +#define TARGET_NR_readlink 85 +#define TARGET_NR_uselib 86 +#define TARGET_NR_swapon 87 +#define TARGET_NR_reboot 88 +#define TARGET_NR_readdir 89 +#define TARGET_NR_mmap 90 +#define TARGET_NR_munmap 91 +#define TARGET_NR_truncate 92 +#define TARGET_NR_ftruncate 93 +#define TARGET_NR_fchmod 94 +#define TARGET_NR_fchown 95 +#define TARGET_NR_getpriority 96 +#define TARGET_NR_setpriority 97 +#define TARGET_NR_profil 98 +#define TARGET_NR_statfs 99 +#define TARGET_NR_fstatfs 100 +#define TARGET_NR_ioperm 101 +#define TARGET_NR_socketcall 102 +#define TARGET_NR_syslog 103 +#define TARGET_NR_setitimer 104 +#define TARGET_NR_getitimer 105 +#define TARGET_NR_stat 106 +#define TARGET_NR_lstat 107 +#define TARGET_NR_fstat 108 +#define TARGET_NR_olduname 109 +#define TARGET_NR_iopl 110 +#define TARGET_NR_vhangup 111 +#define TARGET_NR_idle 112 +#define TARGET_NR_vm86 113 +#define TARGET_NR_wait4 114 +#define TARGET_NR_swapoff 115 +#define TARGET_NR_sysinfo 116 +#define TARGET_NR_ipc 117 +#define TARGET_NR_fsync 118 +#define TARGET_NR_sigreturn 119 +#define TARGET_NR_clone 120 +#define TARGET_NR_setdomainname 121 +#define TARGET_NR_uname 122 +#define TARGET_NR_modify_ldt 123 +#define TARGET_NR_adjtimex 124 +#define TARGET_NR_mprotect 125 +#define TARGET_NR_sigprocmask 126 +#define TARGET_NR_create_module 127 +#define TARGET_NR_init_module 128 +#define TARGET_NR_delete_module 129 +#define TARGET_NR_get_kernel_syms 130 +#define TARGET_NR_quotactl 131 +#define TARGET_NR_getpgid 132 +#define TARGET_NR_fchdir 133 +#define TARGET_NR_bdflush 134 +#define TARGET_NR_sysfs 135 +#define TARGET_NR_personality 136 +#define TARGET_NR_afs_syscall 137 /* Syscall for Andrew File System */ +#define TARGET_NR_setfsuid 138 +#define TARGET_NR_setfsgid 139 +#define TARGET_NR__llseek 140 +#define TARGET_NR_getdents 141 +#define TARGET_NR__newselect 142 +#define TARGET_NR_flock 143 +#define TARGET_NR_msync 144 +#define TARGET_NR_readv 145 +#define TARGET_NR_writev 146 +#define TARGET_NR_getsid 147 +#define TARGET_NR_fdatasync 148 +#define TARGET_NR__sysctl 149 +#define TARGET_NR_mlock 150 +#define TARGET_NR_munlock 151 +#define TARGET_NR_mlockall 152 +#define TARGET_NR_munlockall 153 +#define TARGET_NR_sched_setparam 154 +#define TARGET_NR_sched_getparam 155 +#define TARGET_NR_sched_setscheduler 156 +#define TARGET_NR_sched_getscheduler 157 +#define TARGET_NR_sched_yield 158 +#define TARGET_NR_sched_get_priority_max 159 +#define TARGET_NR_sched_get_priority_min 160 +#define TARGET_NR_sched_rr_get_interval 161 +#define TARGET_NR_nanosleep 162 +#define TARGET_NR_mremap 163 +#define TARGET_NR_setresuid 164 +#define TARGET_NR_getresuid 165 + +#define TARGET_NR_query_module 167 +#define TARGET_NR_poll 168 +#define TARGET_NR_nfsservctl 169 +#define TARGET_NR_setresgid 170 +#define TARGET_NR_getresgid 171 +#define TARGET_NR_prctl 172 +#define TARGET_NR_rt_sigreturn 173 +#define TARGET_NR_rt_sigaction 174 +#define TARGET_NR_rt_sigprocmask 175 +#define TARGET_NR_rt_sigpending 176 +#define TARGET_NR_rt_sigtimedwait 177 +#define TARGET_NR_rt_sigqueueinfo 178 +#define TARGET_NR_rt_sigsuspend 179 +#define TARGET_NR_pread64 180 +#define TARGET_NR_pwrite64 181 +#define TARGET_NR_chown 182 +#define TARGET_NR_getcwd 183 +#define TARGET_NR_capget 184 +#define TARGET_NR_capset 185 +#define TARGET_NR_sigaltstack 186 +#define TARGET_NR_sendfile 187 +#define TARGET_NR_getpmsg 188 /* some people actually want streams */ +#define TARGET_NR_putpmsg 189 /* some people actually want streams */ +#define TARGET_NR_vfork 190 +#define TARGET_NR_ugetrlimit 191 /* SuS compliant getrlimit */ +#define TARGET_NR_mmap2 192 +#define TARGET_NR_truncate64 193 +#define TARGET_NR_ftruncate64 194 +#define TARGET_NR_stat64 195 +#define TARGET_NR_lstat64 196 +#define TARGET_NR_fstat64 197 +#define TARGET_NR_lchown32 198 +#define TARGET_NR_getuid32 199 +#define TARGET_NR_getgid32 200 +#define TARGET_NR_geteuid32 201 +#define TARGET_NR_getegid32 202 +#define TARGET_NR_setreuid32 203 +#define TARGET_NR_setregid32 204 +#define TARGET_NR_getgroups32 205 +#define TARGET_NR_setgroups32 206 +#define TARGET_NR_fchown32 207 +#define TARGET_NR_setresuid32 208 +#define TARGET_NR_getresuid32 209 +#define TARGET_NR_setresgid32 210 +#define TARGET_NR_getresgid32 211 +#define TARGET_NR_chown32 212 +#define TARGET_NR_setuid32 213 +#define TARGET_NR_setgid32 214 +#define TARGET_NR_setfsuid32 215 +#define TARGET_NR_setfsgid32 216 +#define TARGET_NR_pivot_root 217 +#define TARGET_NR_mincore 218 +#define TARGET_NR_madvise 219 +#define TARGET_NR_getdents64 220 +#define TARGET_NR_fcntl64 221 +/* 223 is unused */ +#define TARGET_NR_gettid 224 +#define TARGET_NR_readahead 225 +#define TARGET_NR_setxattr 226 +#define TARGET_NR_lsetxattr 227 +#define TARGET_NR_fsetxattr 228 +#define TARGET_NR_getxattr 229 +#define TARGET_NR_lgetxattr 230 +#define TARGET_NR_fgetxattr 231 +#define TARGET_NR_listxattr 232 +#define TARGET_NR_llistxattr 233 +#define TARGET_NR_flistxattr 234 +#define TARGET_NR_removexattr 235 +#define TARGET_NR_lremovexattr 236 +#define TARGET_NR_fremovexattr 237 +#define TARGET_NR_tkill 238 +#define TARGET_NR_sendfile64 239 +#define TARGET_NR_futex 240 +#define TARGET_NR_sched_setaffinity 241 +#define TARGET_NR_sched_getaffinity 242 +#define TARGET_NR_set_thread_area 243 +#define TARGET_NR_get_thread_area 244 +#define TARGET_NR_io_setup 245 +#define TARGET_NR_io_destroy 246 +#define TARGET_NR_io_getevents 247 +#define TARGET_NR_io_submit 248 +#define TARGET_NR_io_cancel 249 +#define TARGET_NR_fadvise64 250 +#define TARGET_NR_exit_group 252 +#define TARGET_NR_lookup_dcookie 253 +#define TARGET_NR_epoll_create 254 +#define TARGET_NR_epoll_ctl 255 +#define TARGET_NR_epoll_wait 256 +#define TARGET_NR_remap_file_pages 257 +#define TARGET_NR_set_tid_address 258 +#define TARGET_NR_timer_create 259 +#define TARGET_NR_timer_settime (TARGET_NR_timer_create+1) +#define TARGET_NR_timer_gettime (TARGET_NR_timer_create+2) +#define TARGET_NR_timer_getoverrun (TARGET_NR_timer_create+3) +#define TARGET_NR_timer_delete (TARGET_NR_timer_create+4) +#define TARGET_NR_clock_settime (TARGET_NR_timer_create+5) +#define TARGET_NR_clock_gettime (TARGET_NR_timer_create+6) +#define TARGET_NR_clock_getres (TARGET_NR_timer_create+7) +#define TARGET_NR_clock_nanosleep (TARGET_NR_timer_create+8) +#define TARGET_NR_statfs64 268 +#define TARGET_NR_fstatfs64 269 +#define TARGET_NR_tgkill 270 +#define TARGET_NR_utimes 271 +#define TARGET_NR_fadvise64_64 272 +#define TARGET_NR_vserver 273 +#define TARGET_NR_mbind 274 +#define TARGET_NR_get_mempolicy 275 +#define TARGET_NR_set_mempolicy 276 +#define TARGET_NR_mq_open 277 +#define TARGET_NR_mq_unlink (TARGET_NR_mq_open+1) +#define TARGET_NR_mq_timedsend (TARGET_NR_mq_open+2) +#define TARGET_NR_mq_timedreceive (TARGET_NR_mq_open+3) +#define TARGET_NR_mq_notify (TARGET_NR_mq_open+4) +#define TARGET_NR_mq_getsetattr (TARGET_NR_mq_open+5) +#define TARGET_NR_kexec_load 283 +#define TARGET_NR_waitid 284 +/* #define TARGET_NR_sys_setaltroot 285 */ +#define TARGET_NR_add_key 286 +#define TARGET_NR_request_key 287 +#define TARGET_NR_keyctl 288 +#define TARGET_NR_ioprio_set 289 +#define TARGET_NR_ioprio_get 290 +#define TARGET_NR_inotify_init 291 +#define TARGET_NR_inotify_add_watch 292 +#define TARGET_NR_inotify_rm_watch 293 +#define TARGET_NR_migrate_pages 294 +#define TARGET_NR_openat 295 +#define TARGET_NR_mkdirat 296 +#define TARGET_NR_mknodat 297 +#define TARGET_NR_fchownat 298 +#define TARGET_NR_futimesat 299 +#define TARGET_NR_fstatat64 300 +#define TARGET_NR_unlinkat 301 +#define TARGET_NR_renameat 302 +#define TARGET_NR_linkat 303 +#define TARGET_NR_symlinkat 304 +#define TARGET_NR_readlinkat 305 +#define TARGET_NR_fchmodat 306 +#define TARGET_NR_faccessat 307 +#define TARGET_NR_pselect6 308 +#define TARGET_NR_ppoll 309 +#define TARGET_NR_unshare 310 +#define TARGET_NR_set_robust_list 311 +#define TARGET_NR_get_robust_list 312 +#define TARGET_NR_splice 313 +#define TARGET_NR_sync_file_range 314 +#define TARGET_NR_tee 315 +#define TARGET_NR_vmsplice 316 +#define TARGET_NR_move_pages 317 +#define TARGET_NR_getcpu 318 +#define TARGET_NR_epoll_pwait 319 +#define TARGET_NR_utimensat 320 +#define TARGET_NR_signalfd 321 +#define TARGET_NR_timerfd_create 322 +#define TARGET_NR_eventfd 323 +#define TARGET_NR_fallocate 324 +#define TARGET_NR_timerfd_settime 325 +#define TARGET_NR_timerfd_gettime 326 +#define TARGET_NR_signalfd4 327 +#define TARGET_NR_eventfd2 328 +#define TARGET_NR_epoll_create1 329 +#define TARGET_NR_dup3 330 +#define TARGET_NR_pipe2 331 +#define TARGET_NR_inotify_init1 332 diff --git a/linux-user/cris/target_signal.h b/linux-user/cris/target_signal.h new file mode 100644 index 0000000..5611840 --- /dev/null +++ b/linux-user/cris/target_signal.h @@ -0,0 +1,29 @@ +#ifndef TARGET_SIGNAL_H +#define TARGET_SIGNAL_H + +#include "cpu.h" + +/* this struct defines a stack used during syscall handling */ + +typedef struct target_sigaltstack { + abi_ulong ss_sp; + abi_ulong ss_size; + abi_long ss_flags; +} target_stack_t; + + +/* + * sigaltstack controls + */ +#define TARGET_SS_ONSTACK 1 +#define TARGET_SS_DISABLE 2 + +#define TARGET_MINSIGSTKSZ 2048 +#define TARGET_SIGSTKSZ 8192 + +static inline abi_ulong get_sp_from_cpustate(CPUCRISState *state) +{ + return state->regs[14]; +} + +#endif /* TARGET_SIGNAL_H */ diff --git a/linux-user/cris/termbits.h b/linux-user/cris/termbits.h new file mode 100644 index 0000000..fc82ca0 --- /dev/null +++ b/linux-user/cris/termbits.h @@ -0,0 +1,213 @@ +/* from asm/termbits.h */ + +#define TARGET_NCCS 19 + +struct target_termios { + unsigned int c_iflag; /* input mode flags */ + unsigned int c_oflag; /* output mode flags */ + unsigned int c_cflag; /* control mode flags */ + unsigned int c_lflag; /* local mode flags */ + unsigned char c_line; /* line discipline */ + unsigned char c_cc[TARGET_NCCS]; /* control characters */ +}; + +/* c_iflag bits */ +#define TARGET_IGNBRK 0000001 +#define TARGET_BRKINT 0000002 +#define TARGET_IGNPAR 0000004 +#define TARGET_PARMRK 0000010 +#define TARGET_INPCK 0000020 +#define TARGET_ISTRIP 0000040 +#define TARGET_INLCR 0000100 +#define TARGET_IGNCR 0000200 +#define TARGET_ICRNL 0000400 +#define TARGET_IUCLC 0001000 +#define TARGET_IXON 0002000 +#define TARGET_IXANY 0004000 +#define TARGET_IXOFF 0010000 +#define TARGET_IMAXBEL 0020000 + +/* c_oflag bits */ +#define TARGET_OPOST 0000001 +#define TARGET_OLCUC 0000002 +#define TARGET_ONLCR 0000004 +#define TARGET_OCRNL 0000010 +#define TARGET_ONOCR 0000020 +#define TARGET_ONLRET 0000040 +#define TARGET_OFILL 0000100 +#define TARGET_OFDEL 0000200 +#define TARGET_NLDLY 0000400 +#define TARGET_NL0 0000000 +#define TARGET_NL1 0000400 +#define TARGET_CRDLY 0003000 +#define TARGET_CR0 0000000 +#define TARGET_CR1 0001000 +#define TARGET_CR2 0002000 +#define TARGET_CR3 0003000 +#define TARGET_TABDLY 0014000 +#define TARGET_TAB0 0000000 +#define TARGET_TAB1 0004000 +#define TARGET_TAB2 0010000 +#define TARGET_TAB3 0014000 +#define TARGET_XTABS 0014000 +#define TARGET_BSDLY 0020000 +#define TARGET_BS0 0000000 +#define TARGET_BS1 0020000 +#define TARGET_VTDLY 0040000 +#define TARGET_VT0 0000000 +#define TARGET_VT1 0040000 +#define TARGET_FFDLY 0100000 +#define TARGET_FF0 0000000 +#define TARGET_FF1 0100000 + +/* c_cflag bit meaning */ +#define TARGET_CBAUD 0010017 +#define TARGET_B0 0000000 /* hang up */ +#define TARGET_B50 0000001 +#define TARGET_B75 0000002 +#define TARGET_B110 0000003 +#define TARGET_B134 0000004 +#define TARGET_B150 0000005 +#define TARGET_B200 0000006 +#define TARGET_B300 0000007 +#define TARGET_B600 0000010 +#define TARGET_B1200 0000011 +#define TARGET_B1800 0000012 +#define TARGET_B2400 0000013 +#define TARGET_B4800 0000014 +#define TARGET_B9600 0000015 +#define TARGET_B19200 0000016 +#define TARGET_B38400 0000017 +#define TARGET_EXTA B19200 +#define TARGET_EXTB B38400 +#define TARGET_CSIZE 0000060 +#define TARGET_CS5 0000000 +#define TARGET_CS6 0000020 +#define TARGET_CS7 0000040 +#define TARGET_CS8 0000060 +#define TARGET_CSTOPB 0000100 +#define TARGET_CREAD 0000200 +#define TARGET_PARENB 0000400 +#define TARGET_PARODD 0001000 +#define TARGET_HUPCL 0002000 +#define TARGET_CLOCAL 0004000 +#define TARGET_CBAUDEX 0010000 +#define TARGET_B57600 0010001 +#define TARGET_B115200 0010002 +#define TARGET_B230400 0010003 +#define TARGET_B460800 0010004 +#define TARGET_CIBAUD 002003600000 /* input baud rate (not used) */ +#define TARGET_CRTSCTS 020000000000 /* flow control */ + +/* c_lflag bits */ +#define TARGET_ISIG 0000001 +#define TARGET_ICANON 0000002 +#define TARGET_XCASE 0000004 +#define TARGET_ECHO 0000010 +#define TARGET_ECHOE 0000020 +#define TARGET_ECHOK 0000040 +#define TARGET_ECHONL 0000100 +#define TARGET_NOFLSH 0000200 +#define TARGET_TOSTOP 0000400 +#define TARGET_ECHOCTL 0001000 +#define TARGET_ECHOPRT 0002000 +#define TARGET_ECHOKE 0004000 +#define TARGET_FLUSHO 0010000 +#define TARGET_PENDIN 0040000 +#define TARGET_IEXTEN 0100000 + +/* c_cc character offsets */ +#define TARGET_VINTR 0 +#define TARGET_VQUIT 1 +#define TARGET_VERASE 2 +#define TARGET_VKILL 3 +#define TARGET_VEOF 4 +#define TARGET_VTIME 5 +#define TARGET_VMIN 6 +#define TARGET_VSWTC 7 +#define TARGET_VSTART 8 +#define TARGET_VSTOP 9 +#define TARGET_VSUSP 10 +#define TARGET_VEOL 11 +#define TARGET_VREPRINT 12 +#define TARGET_VDISCARD 13 +#define TARGET_VWERASE 14 +#define TARGET_VLNEXT 15 +#define TARGET_VEOL2 16 + +/* ioctls */ + +#define TARGET_TCGETS 0x5401 +#define TARGET_TCSETS 0x5402 +#define TARGET_TCSETSW 0x5403 +#define TARGET_TCSETSF 0x5404 +#define TARGET_TCGETA 0x5405 +#define TARGET_TCSETA 0x5406 +#define TARGET_TCSETAW 0x5407 +#define TARGET_TCSETAF 0x5408 +#define TARGET_TCSBRK 0x5409 +#define TARGET_TCXONC 0x540A +#define TARGET_TCFLSH 0x540B + +#define TARGET_TIOCEXCL 0x540C +#define TARGET_TIOCNXCL 0x540D +#define TARGET_TIOCSCTTY 0x540E +#define TARGET_TIOCGPGRP 0x540F +#define TARGET_TIOCSPGRP 0x5410 +#define TARGET_TIOCOUTQ 0x5411 +#define TARGET_TIOCSTI 0x5412 +#define TARGET_TIOCGWINSZ 0x5413 +#define TARGET_TIOCSWINSZ 0x5414 +#define TARGET_TIOCMGET 0x5415 +#define TARGET_TIOCMBIS 0x5416 +#define TARGET_TIOCMBIC 0x5417 +#define TARGET_TIOCMSET 0x5418 +#define TARGET_TIOCGSOFTCAR 0x5419 +#define TARGET_TIOCSSOFTCAR 0x541A +#define TARGET_FIONREAD 0x541B +#define TARGET_TIOCINQ TARGET_FIONREAD +#define TARGET_TIOCLINUX 0x541C +#define TARGET_TIOCCONS 0x541D +#define TARGET_TIOCGSERIAL 0x541E +#define TARGET_TIOCSSERIAL 0x541F +#define TARGET_TIOCPKT 0x5420 +#define TARGET_FIONBIO 0x5421 +#define TARGET_TIOCNOTTY 0x5422 +#define TARGET_TIOCSETD 0x5423 +#define TARGET_TIOCGETD 0x5424 +#define TARGET_TCSBRKP 0x5425 /* Needed for POSIX tcsendbreak() */ +#define TARGET_TIOCTTYGSTRUCT 0x5426 /* For debugging only */ +#define TARGET_TIOCSBRK 0x5427 /* BSD compatibility */ +#define TARGET_TIOCCBRK 0x5428 /* BSD compatibility */ +#define TARGET_TIOCGSID 0x5429 /* Return the session ID of FD */ +#define TARGET_TIOCGPTN TARGET_IOR('T',0x30, unsigned int) /* Get Pty Number (of pty-mux device) */ +#define TARGET_TIOCSPTLCK TARGET_IOW('T',0x31, int) /* Lock/unlock Pty */ + +#define TARGET_FIONCLEX 0x5450 /* these numbers need to be adjusted. */ +#define TARGET_FIOCLEX 0x5451 +#define TARGET_FIOASYNC 0x5452 +#define TARGET_TIOCSERCONFIG 0x5453 +#define TARGET_TIOCSERGWILD 0x5454 +#define TARGET_TIOCSERSWILD 0x5455 +#define TARGET_TIOCGLCKTRMIOS 0x5456 +#define TARGET_TIOCSLCKTRMIOS 0x5457 +#define TARGET_TIOCSERGSTRUCT 0x5458 /* For debugging only */ +#define TARGET_TIOCSERGETLSR 0x5459 /* Get line status register */ +#define TARGET_TIOCSERGETMULTI 0x545A /* Get multiport config */ +#define TARGET_TIOCSERSETMULTI 0x545B /* Set multiport config */ + +#define TARGET_TIOCMIWAIT 0x545C /* wait for a change on serial input line(s) */ +#define TARGET_TIOCGICOUNT 0x545D /* read serial port inline interrupt counts */ +#define TARGET_TIOCGHAYESESP 0x545E /* Get Hayes ESP configuration */ +#define TARGET_TIOCSHAYESESP 0x545F /* Set Hayes ESP configuration */ + +/* Used for packet mode */ +#define TARGET_TIOCPKT_DATA 0 +#define TARGET_TIOCPKT_FLUSHREAD 1 +#define TARGET_TIOCPKT_FLUSHWRITE 2 +#define TARGET_TIOCPKT_STOP 4 +#define TARGET_TIOCPKT_START 8 +#define TARGET_TIOCPKT_NOSTOP 16 +#define TARGET_TIOCPKT_DOSTOP 32 + +#define TARGET_TIOCSER_TEMT 0x01 /* Transmitter physically empty */ diff --git a/linux-user/elfload.c b/linux-user/elfload.c new file mode 100644 index 0000000..08c44d8 --- /dev/null +++ b/linux-user/elfload.c @@ -0,0 +1,2496 @@ +/* This is the Linux kernel elf-loading code, ported into user space */ +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "qemu.h" +#include "disas.h" + +#ifdef _ARCH_PPC64 +#undef ARCH_DLINFO +#undef ELF_PLATFORM +#undef ELF_HWCAP +#undef ELF_CLASS +#undef ELF_DATA +#undef ELF_ARCH +#endif + +#define ELF_OSABI ELFOSABI_SYSV + +/* from personality.h */ + +/* + * Flags for bug emulation. + * + * These occupy the top three bytes. + */ +enum { + ADDR_NO_RANDOMIZE = 0x0040000, /* disable randomization of VA space */ + FDPIC_FUNCPTRS = 0x0080000, /* userspace function ptrs point to + descriptors (signal handling) */ + MMAP_PAGE_ZERO = 0x0100000, + ADDR_COMPAT_LAYOUT = 0x0200000, + READ_IMPLIES_EXEC = 0x0400000, + ADDR_LIMIT_32BIT = 0x0800000, + SHORT_INODE = 0x1000000, + WHOLE_SECONDS = 0x2000000, + STICKY_TIMEOUTS = 0x4000000, + ADDR_LIMIT_3GB = 0x8000000, +}; + +/* + * Personality types. + * + * These go in the low byte. Avoid using the top bit, it will + * conflict with error returns. + */ +enum { + PER_LINUX = 0x0000, + PER_LINUX_32BIT = 0x0000 | ADDR_LIMIT_32BIT, + PER_LINUX_FDPIC = 0x0000 | FDPIC_FUNCPTRS, + PER_SVR4 = 0x0001 | STICKY_TIMEOUTS | MMAP_PAGE_ZERO, + PER_SVR3 = 0x0002 | STICKY_TIMEOUTS | SHORT_INODE, + PER_SCOSVR3 = 0x0003 | STICKY_TIMEOUTS | WHOLE_SECONDS | SHORT_INODE, + PER_OSR5 = 0x0003 | STICKY_TIMEOUTS | WHOLE_SECONDS, + PER_WYSEV386 = 0x0004 | STICKY_TIMEOUTS | SHORT_INODE, + PER_ISCR4 = 0x0005 | STICKY_TIMEOUTS, + PER_BSD = 0x0006, + PER_SUNOS = 0x0006 | STICKY_TIMEOUTS, + PER_XENIX = 0x0007 | STICKY_TIMEOUTS | SHORT_INODE, + PER_LINUX32 = 0x0008, + PER_LINUX32_3GB = 0x0008 | ADDR_LIMIT_3GB, + PER_IRIX32 = 0x0009 | STICKY_TIMEOUTS,/* IRIX5 32-bit */ + PER_IRIXN32 = 0x000a | STICKY_TIMEOUTS,/* IRIX6 new 32-bit */ + PER_IRIX64 = 0x000b | STICKY_TIMEOUTS,/* IRIX6 64-bit */ + PER_RISCOS = 0x000c, + PER_SOLARIS = 0x000d | STICKY_TIMEOUTS, + PER_UW7 = 0x000e | STICKY_TIMEOUTS | MMAP_PAGE_ZERO, + PER_OSF4 = 0x000f, /* OSF/1 v4 */ + PER_HPUX = 0x0010, + PER_MASK = 0x00ff, +}; + +/* + * Return the base personality without flags. + */ +#define personality(pers) (pers & PER_MASK) + +/* this flag is uneffective under linux too, should be deleted */ +#ifndef MAP_DENYWRITE +#define MAP_DENYWRITE 0 +#endif + +/* should probably go in elf.h */ +#ifndef ELIBBAD +#define ELIBBAD 80 +#endif + +#ifdef TARGET_WORDS_BIGENDIAN +#define ELF_DATA ELFDATA2MSB +#else +#define ELF_DATA ELFDATA2LSB +#endif + +typedef target_ulong target_elf_greg_t; +#ifdef USE_UID16 +typedef uint16_t target_uid_t; +typedef uint16_t target_gid_t; +#else +typedef uint32_t target_uid_t; +typedef uint32_t target_gid_t; +#endif +typedef int32_t target_pid_t; + +#ifdef TARGET_I386 + +#define ELF_PLATFORM get_elf_platform() + +static const char *get_elf_platform(void) +{ + static char elf_platform[] = "i386"; + int family = (thread_env->cpuid_version >> 8) & 0xff; + if (family > 6) + family = 6; + if (family >= 3) + elf_platform[1] = '0' + family; + return elf_platform; +} + +#define ELF_HWCAP get_elf_hwcap() + +static uint32_t get_elf_hwcap(void) +{ + return thread_env->cpuid_features; +} + +#ifdef TARGET_X86_64 +#define ELF_START_MMAP 0x2aaaaab000ULL +#define elf_check_arch(x) ( ((x) == ELF_ARCH) ) + +#define ELF_CLASS ELFCLASS64 +#define ELF_ARCH EM_X86_64 + +static inline void init_thread(struct target_pt_regs *regs, struct image_info *infop) +{ + regs->rax = 0; + regs->rsp = infop->start_stack; + regs->rip = infop->entry; +} + +#define ELF_NREG 27 +typedef target_elf_greg_t target_elf_gregset_t[ELF_NREG]; + +/* + * Note that ELF_NREG should be 29 as there should be place for + * TRAPNO and ERR "registers" as well but linux doesn't dump + * those. + * + * See linux kernel: arch/x86/include/asm/elf.h + */ +static void elf_core_copy_regs(target_elf_gregset_t *regs, const CPUState *env) +{ + (*regs)[0] = env->regs[15]; + (*regs)[1] = env->regs[14]; + (*regs)[2] = env->regs[13]; + (*regs)[3] = env->regs[12]; + (*regs)[4] = env->regs[R_EBP]; + (*regs)[5] = env->regs[R_EBX]; + (*regs)[6] = env->regs[11]; + (*regs)[7] = env->regs[10]; + (*regs)[8] = env->regs[9]; + (*regs)[9] = env->regs[8]; + (*regs)[10] = env->regs[R_EAX]; + (*regs)[11] = env->regs[R_ECX]; + (*regs)[12] = env->regs[R_EDX]; + (*regs)[13] = env->regs[R_ESI]; + (*regs)[14] = env->regs[R_EDI]; + (*regs)[15] = env->regs[R_EAX]; /* XXX */ + (*regs)[16] = env->eip; + (*regs)[17] = env->segs[R_CS].selector & 0xffff; + (*regs)[18] = env->eflags; + (*regs)[19] = env->regs[R_ESP]; + (*regs)[20] = env->segs[R_SS].selector & 0xffff; + (*regs)[21] = env->segs[R_FS].selector & 0xffff; + (*regs)[22] = env->segs[R_GS].selector & 0xffff; + (*regs)[23] = env->segs[R_DS].selector & 0xffff; + (*regs)[24] = env->segs[R_ES].selector & 0xffff; + (*regs)[25] = env->segs[R_FS].selector & 0xffff; + (*regs)[26] = env->segs[R_GS].selector & 0xffff; +} + +#else + +#define ELF_START_MMAP 0x80000000 + +/* + * This is used to ensure we don't load something for the wrong architecture. + */ +#define elf_check_arch(x) ( ((x) == EM_386) || ((x) == EM_486) ) + +/* + * These are used to set parameters in the core dumps. + */ +#define ELF_CLASS ELFCLASS32 +#define ELF_ARCH EM_386 + +static inline void init_thread(struct target_pt_regs *regs, + struct image_info *infop) +{ + regs->esp = infop->start_stack; + regs->eip = infop->entry; + + /* SVR4/i386 ABI (pages 3-31, 3-32) says that when the program + starts %edx contains a pointer to a function which might be + registered using `atexit'. This provides a mean for the + dynamic linker to call DT_FINI functions for shared libraries + that have been loaded before the code runs. + + A value of 0 tells we have no such handler. */ + regs->edx = 0; +} + +#define ELF_NREG 17 +typedef target_elf_greg_t target_elf_gregset_t[ELF_NREG]; + +/* + * Note that ELF_NREG should be 19 as there should be place for + * TRAPNO and ERR "registers" as well but linux doesn't dump + * those. + * + * See linux kernel: arch/x86/include/asm/elf.h + */ +static void elf_core_copy_regs(target_elf_gregset_t *regs, const CPUState *env) +{ + (*regs)[0] = env->regs[R_EBX]; + (*regs)[1] = env->regs[R_ECX]; + (*regs)[2] = env->regs[R_EDX]; + (*regs)[3] = env->regs[R_ESI]; + (*regs)[4] = env->regs[R_EDI]; + (*regs)[5] = env->regs[R_EBP]; + (*regs)[6] = env->regs[R_EAX]; + (*regs)[7] = env->segs[R_DS].selector & 0xffff; + (*regs)[8] = env->segs[R_ES].selector & 0xffff; + (*regs)[9] = env->segs[R_FS].selector & 0xffff; + (*regs)[10] = env->segs[R_GS].selector & 0xffff; + (*regs)[11] = env->regs[R_EAX]; /* XXX */ + (*regs)[12] = env->eip; + (*regs)[13] = env->segs[R_CS].selector & 0xffff; + (*regs)[14] = env->eflags; + (*regs)[15] = env->regs[R_ESP]; + (*regs)[16] = env->segs[R_SS].selector & 0xffff; +} +#endif + +#define USE_ELF_CORE_DUMP +#define ELF_EXEC_PAGESIZE 4096 + +#endif + +#ifdef TARGET_ARM + +#define ELF_START_MMAP 0x80000000 + +#define elf_check_arch(x) ( (x) == EM_ARM ) + +#define ELF_CLASS ELFCLASS32 +#define ELF_ARCH EM_ARM + +static inline void init_thread(struct target_pt_regs *regs, + struct image_info *infop) +{ + abi_long stack = infop->start_stack; + memset(regs, 0, sizeof(*regs)); + regs->ARM_cpsr = 0x10; + if (infop->entry & 1) + regs->ARM_cpsr |= CPSR_T; + regs->ARM_pc = infop->entry & 0xfffffffe; + regs->ARM_sp = infop->start_stack; + /* FIXME - what to for failure of get_user()? */ + get_user_ual(regs->ARM_r2, stack + 8); /* envp */ + get_user_ual(regs->ARM_r1, stack + 4); /* envp */ + /* XXX: it seems that r0 is zeroed after ! */ + regs->ARM_r0 = 0; + /* For uClinux PIC binaries. */ + /* XXX: Linux does this only on ARM with no MMU (do we care ?) */ + regs->ARM_r10 = infop->start_data; +} + +#define ELF_NREG 18 +typedef target_elf_greg_t target_elf_gregset_t[ELF_NREG]; + +static void elf_core_copy_regs(target_elf_gregset_t *regs, const CPUState *env) +{ + (*regs)[0] = tswapl(env->regs[0]); + (*regs)[1] = tswapl(env->regs[1]); + (*regs)[2] = tswapl(env->regs[2]); + (*regs)[3] = tswapl(env->regs[3]); + (*regs)[4] = tswapl(env->regs[4]); + (*regs)[5] = tswapl(env->regs[5]); + (*regs)[6] = tswapl(env->regs[6]); + (*regs)[7] = tswapl(env->regs[7]); + (*regs)[8] = tswapl(env->regs[8]); + (*regs)[9] = tswapl(env->regs[9]); + (*regs)[10] = tswapl(env->regs[10]); + (*regs)[11] = tswapl(env->regs[11]); + (*regs)[12] = tswapl(env->regs[12]); + (*regs)[13] = tswapl(env->regs[13]); + (*regs)[14] = tswapl(env->regs[14]); + (*regs)[15] = tswapl(env->regs[15]); + + (*regs)[16] = tswapl(cpsr_read((CPUState *)env)); + (*regs)[17] = tswapl(env->regs[0]); /* XXX */ +} + +#define USE_ELF_CORE_DUMP +#define ELF_EXEC_PAGESIZE 4096 + +enum +{ + ARM_HWCAP_ARM_SWP = 1 << 0, + ARM_HWCAP_ARM_HALF = 1 << 1, + ARM_HWCAP_ARM_THUMB = 1 << 2, + ARM_HWCAP_ARM_26BIT = 1 << 3, + ARM_HWCAP_ARM_FAST_MULT = 1 << 4, + ARM_HWCAP_ARM_FPA = 1 << 5, + ARM_HWCAP_ARM_VFP = 1 << 6, + ARM_HWCAP_ARM_EDSP = 1 << 7, + ARM_HWCAP_ARM_JAVA = 1 << 8, + ARM_HWCAP_ARM_IWMMXT = 1 << 9, + ARM_HWCAP_ARM_THUMBEE = 1 << 10, + ARM_HWCAP_ARM_NEON = 1 << 11, + ARM_HWCAP_ARM_VFPv3 = 1 << 12, + ARM_HWCAP_ARM_VFPv3D16 = 1 << 13, +}; + +#define ELF_HWCAP (ARM_HWCAP_ARM_SWP | ARM_HWCAP_ARM_HALF \ + | ARM_HWCAP_ARM_THUMB | ARM_HWCAP_ARM_FAST_MULT \ + | ARM_HWCAP_ARM_FPA | ARM_HWCAP_ARM_VFP \ + | ARM_HWCAP_ARM_NEON | ARM_HWCAP_ARM_VFPv3 ) + +#endif + +#ifdef TARGET_SPARC +#ifdef TARGET_SPARC64 + +#define ELF_START_MMAP 0x80000000 + +#ifndef TARGET_ABI32 +#define elf_check_arch(x) ( (x) == EM_SPARCV9 || (x) == EM_SPARC32PLUS ) +#else +#define elf_check_arch(x) ( (x) == EM_SPARC32PLUS || (x) == EM_SPARC ) +#endif + +#define ELF_CLASS ELFCLASS64 +#define ELF_ARCH EM_SPARCV9 + +#define STACK_BIAS 2047 + +static inline void init_thread(struct target_pt_regs *regs, + struct image_info *infop) +{ +#ifndef TARGET_ABI32 + regs->tstate = 0; +#endif + regs->pc = infop->entry; + regs->npc = regs->pc + 4; + regs->y = 0; +#ifdef TARGET_ABI32 + regs->u_regs[14] = infop->start_stack - 16 * 4; +#else + if (personality(infop->personality) == PER_LINUX32) + regs->u_regs[14] = infop->start_stack - 16 * 4; + else + regs->u_regs[14] = infop->start_stack - 16 * 8 - STACK_BIAS; +#endif +} + +#else +#define ELF_START_MMAP 0x80000000 + +#define elf_check_arch(x) ( (x) == EM_SPARC ) + +#define ELF_CLASS ELFCLASS32 +#define ELF_ARCH EM_SPARC + +static inline void init_thread(struct target_pt_regs *regs, + struct image_info *infop) +{ + regs->psr = 0; + regs->pc = infop->entry; + regs->npc = regs->pc + 4; + regs->y = 0; + regs->u_regs[14] = infop->start_stack - 16 * 4; +} + +#endif +#endif + +#ifdef TARGET_PPC + +#define ELF_START_MMAP 0x80000000 + +#if defined(TARGET_PPC64) && !defined(TARGET_ABI32) + +#define elf_check_arch(x) ( (x) == EM_PPC64 ) + +#define ELF_CLASS ELFCLASS64 + +#else + +#define elf_check_arch(x) ( (x) == EM_PPC ) + +#define ELF_CLASS ELFCLASS32 + +#endif + +#define ELF_ARCH EM_PPC + +/* Feature masks for the Aux Vector Hardware Capabilities (AT_HWCAP). + See arch/powerpc/include/asm/cputable.h. */ +enum { + QEMU_PPC_FEATURE_32 = 0x80000000, + QEMU_PPC_FEATURE_64 = 0x40000000, + QEMU_PPC_FEATURE_601_INSTR = 0x20000000, + QEMU_PPC_FEATURE_HAS_ALTIVEC = 0x10000000, + QEMU_PPC_FEATURE_HAS_FPU = 0x08000000, + QEMU_PPC_FEATURE_HAS_MMU = 0x04000000, + QEMU_PPC_FEATURE_HAS_4xxMAC = 0x02000000, + QEMU_PPC_FEATURE_UNIFIED_CACHE = 0x01000000, + QEMU_PPC_FEATURE_HAS_SPE = 0x00800000, + QEMU_PPC_FEATURE_HAS_EFP_SINGLE = 0x00400000, + QEMU_PPC_FEATURE_HAS_EFP_DOUBLE = 0x00200000, + QEMU_PPC_FEATURE_NO_TB = 0x00100000, + QEMU_PPC_FEATURE_POWER4 = 0x00080000, + QEMU_PPC_FEATURE_POWER5 = 0x00040000, + QEMU_PPC_FEATURE_POWER5_PLUS = 0x00020000, + QEMU_PPC_FEATURE_CELL = 0x00010000, + QEMU_PPC_FEATURE_BOOKE = 0x00008000, + QEMU_PPC_FEATURE_SMT = 0x00004000, + QEMU_PPC_FEATURE_ICACHE_SNOOP = 0x00002000, + QEMU_PPC_FEATURE_ARCH_2_05 = 0x00001000, + QEMU_PPC_FEATURE_PA6T = 0x00000800, + QEMU_PPC_FEATURE_HAS_DFP = 0x00000400, + QEMU_PPC_FEATURE_POWER6_EXT = 0x00000200, + QEMU_PPC_FEATURE_ARCH_2_06 = 0x00000100, + QEMU_PPC_FEATURE_HAS_VSX = 0x00000080, + QEMU_PPC_FEATURE_PSERIES_PERFMON_COMPAT = 0x00000040, + + QEMU_PPC_FEATURE_TRUE_LE = 0x00000002, + QEMU_PPC_FEATURE_PPC_LE = 0x00000001, +}; + +#define ELF_HWCAP get_elf_hwcap() + +static uint32_t get_elf_hwcap(void) +{ + CPUState *e = thread_env; + uint32_t features = 0; + + /* We don't have to be terribly complete here; the high points are + Altivec/FP/SPE support. Anything else is just a bonus. */ +#define GET_FEATURE(flag, feature) \ + do {if (e->insns_flags & flag) features |= feature; } while(0) + GET_FEATURE(PPC_64B, QEMU_PPC_FEATURE_64); + GET_FEATURE(PPC_FLOAT, QEMU_PPC_FEATURE_HAS_FPU); + GET_FEATURE(PPC_ALTIVEC, QEMU_PPC_FEATURE_HAS_ALTIVEC); + GET_FEATURE(PPC_SPE, QEMU_PPC_FEATURE_HAS_SPE); + GET_FEATURE(PPC_SPE_SINGLE, QEMU_PPC_FEATURE_HAS_EFP_SINGLE); + GET_FEATURE(PPC_SPE_DOUBLE, QEMU_PPC_FEATURE_HAS_EFP_DOUBLE); + GET_FEATURE(PPC_BOOKE, QEMU_PPC_FEATURE_BOOKE); + GET_FEATURE(PPC_405_MAC, QEMU_PPC_FEATURE_HAS_4xxMAC); +#undef GET_FEATURE + + return features; +} + +/* + * The requirements here are: + * - keep the final alignment of sp (sp & 0xf) + * - make sure the 32-bit value at the first 16 byte aligned position of + * AUXV is greater than 16 for glibc compatibility. + * AT_IGNOREPPC is used for that. + * - for compatibility with glibc ARCH_DLINFO must always be defined on PPC, + * even if DLINFO_ARCH_ITEMS goes to zero or is undefined. + */ +#define DLINFO_ARCH_ITEMS 5 +#define ARCH_DLINFO \ + do { \ + NEW_AUX_ENT(AT_DCACHEBSIZE, 0x20); \ + NEW_AUX_ENT(AT_ICACHEBSIZE, 0x20); \ + NEW_AUX_ENT(AT_UCACHEBSIZE, 0); \ + /* \ + * Now handle glibc compatibility. \ + */ \ + NEW_AUX_ENT(AT_IGNOREPPC, AT_IGNOREPPC); \ + NEW_AUX_ENT(AT_IGNOREPPC, AT_IGNOREPPC); \ + } while (0) + +static inline void init_thread(struct target_pt_regs *_regs, struct image_info *infop) +{ + _regs->gpr[1] = infop->start_stack; +#if defined(TARGET_PPC64) && !defined(TARGET_ABI32) + _regs->gpr[2] = ldq_raw(infop->entry + 8) + infop->load_addr; + infop->entry = ldq_raw(infop->entry) + infop->load_addr; +#endif + _regs->nip = infop->entry; +} + +/* See linux kernel: arch/powerpc/include/asm/elf.h. */ +#define ELF_NREG 48 +typedef target_elf_greg_t target_elf_gregset_t[ELF_NREG]; + +static void elf_core_copy_regs(target_elf_gregset_t *regs, const CPUState *env) +{ + int i; + target_ulong ccr = 0; + + for (i = 0; i < ARRAY_SIZE(env->gpr); i++) { + (*regs)[i] = tswapl(env->gpr[i]); + } + + (*regs)[32] = tswapl(env->nip); + (*regs)[33] = tswapl(env->msr); + (*regs)[35] = tswapl(env->ctr); + (*regs)[36] = tswapl(env->lr); + (*regs)[37] = tswapl(env->xer); + + for (i = 0; i < ARRAY_SIZE(env->crf); i++) { + ccr |= env->crf[i] << (32 - ((i + 1) * 4)); + } + (*regs)[38] = tswapl(ccr); +} + +#define USE_ELF_CORE_DUMP +#define ELF_EXEC_PAGESIZE 4096 + +#endif + +#ifdef TARGET_MIPS + +#define ELF_START_MMAP 0x80000000 + +#define elf_check_arch(x) ( (x) == EM_MIPS ) + +#ifdef TARGET_MIPS64 +#define ELF_CLASS ELFCLASS64 +#else +#define ELF_CLASS ELFCLASS32 +#endif +#define ELF_ARCH EM_MIPS + +static inline void init_thread(struct target_pt_regs *regs, + struct image_info *infop) +{ + regs->cp0_status = 2 << CP0St_KSU; + regs->cp0_epc = infop->entry; + regs->regs[29] = infop->start_stack; +} + +/* See linux kernel: arch/mips/include/asm/elf.h. */ +#define ELF_NREG 45 +typedef target_elf_greg_t target_elf_gregset_t[ELF_NREG]; + +/* See linux kernel: arch/mips/include/asm/reg.h. */ +enum { +#ifdef TARGET_MIPS64 + TARGET_EF_R0 = 0, +#else + TARGET_EF_R0 = 6, +#endif + TARGET_EF_R26 = TARGET_EF_R0 + 26, + TARGET_EF_R27 = TARGET_EF_R0 + 27, + TARGET_EF_LO = TARGET_EF_R0 + 32, + TARGET_EF_HI = TARGET_EF_R0 + 33, + TARGET_EF_CP0_EPC = TARGET_EF_R0 + 34, + TARGET_EF_CP0_BADVADDR = TARGET_EF_R0 + 35, + TARGET_EF_CP0_STATUS = TARGET_EF_R0 + 36, + TARGET_EF_CP0_CAUSE = TARGET_EF_R0 + 37 +}; + +/* See linux kernel: arch/mips/kernel/process.c:elf_dump_regs. */ +static void elf_core_copy_regs(target_elf_gregset_t *regs, const CPUState *env) +{ + int i; + + for (i = 0; i < TARGET_EF_R0; i++) { + (*regs)[i] = 0; + } + (*regs)[TARGET_EF_R0] = 0; + + for (i = 1; i < ARRAY_SIZE(env->active_tc.gpr); i++) { + (*regs)[TARGET_EF_R0 + i] = tswapl(env->active_tc.gpr[i]); + } + + (*regs)[TARGET_EF_R26] = 0; + (*regs)[TARGET_EF_R27] = 0; + (*regs)[TARGET_EF_LO] = tswapl(env->active_tc.LO[0]); + (*regs)[TARGET_EF_HI] = tswapl(env->active_tc.HI[0]); + (*regs)[TARGET_EF_CP0_EPC] = tswapl(env->active_tc.PC); + (*regs)[TARGET_EF_CP0_BADVADDR] = tswapl(env->CP0_BadVAddr); + (*regs)[TARGET_EF_CP0_STATUS] = tswapl(env->CP0_Status); + (*regs)[TARGET_EF_CP0_CAUSE] = tswapl(env->CP0_Cause); +} + +#define USE_ELF_CORE_DUMP +#define ELF_EXEC_PAGESIZE 4096 + +#endif /* TARGET_MIPS */ + +#ifdef TARGET_MICROBLAZE + +#define ELF_START_MMAP 0x80000000 + +#define elf_check_arch(x) ( (x) == EM_MICROBLAZE || (x) == EM_MICROBLAZE_OLD) + +#define ELF_CLASS ELFCLASS32 +#define ELF_ARCH EM_MICROBLAZE + +static inline void init_thread(struct target_pt_regs *regs, + struct image_info *infop) +{ + regs->pc = infop->entry; + regs->r1 = infop->start_stack; + +} + +#define ELF_EXEC_PAGESIZE 4096 + +#define USE_ELF_CORE_DUMP +#define ELF_NREG 38 +typedef target_elf_greg_t target_elf_gregset_t[ELF_NREG]; + +/* See linux kernel: arch/mips/kernel/process.c:elf_dump_regs. */ +static void elf_core_copy_regs(target_elf_gregset_t *regs, const CPUState *env) +{ + int i, pos = 0; + + for (i = 0; i < 32; i++) { + (*regs)[pos++] = tswapl(env->regs[i]); + } + + for (i = 0; i < 6; i++) { + (*regs)[pos++] = tswapl(env->sregs[i]); + } +} + +#endif /* TARGET_MICROBLAZE */ + +#ifdef TARGET_SH4 + +#define ELF_START_MMAP 0x80000000 + +#define elf_check_arch(x) ( (x) == EM_SH ) + +#define ELF_CLASS ELFCLASS32 +#define ELF_ARCH EM_SH + +static inline void init_thread(struct target_pt_regs *regs, + struct image_info *infop) +{ + /* Check other registers XXXXX */ + regs->pc = infop->entry; + regs->regs[15] = infop->start_stack; +} + +/* See linux kernel: arch/sh/include/asm/elf.h. */ +#define ELF_NREG 23 +typedef target_elf_greg_t target_elf_gregset_t[ELF_NREG]; + +/* See linux kernel: arch/sh/include/asm/ptrace.h. */ +enum { + TARGET_REG_PC = 16, + TARGET_REG_PR = 17, + TARGET_REG_SR = 18, + TARGET_REG_GBR = 19, + TARGET_REG_MACH = 20, + TARGET_REG_MACL = 21, + TARGET_REG_SYSCALL = 22 +}; + +static inline void elf_core_copy_regs(target_elf_gregset_t *regs, + const CPUState *env) +{ + int i; + + for (i = 0; i < 16; i++) { + (*regs[i]) = tswapl(env->gregs[i]); + } + + (*regs)[TARGET_REG_PC] = tswapl(env->pc); + (*regs)[TARGET_REG_PR] = tswapl(env->pr); + (*regs)[TARGET_REG_SR] = tswapl(env->sr); + (*regs)[TARGET_REG_GBR] = tswapl(env->gbr); + (*regs)[TARGET_REG_MACH] = tswapl(env->mach); + (*regs)[TARGET_REG_MACL] = tswapl(env->macl); + (*regs)[TARGET_REG_SYSCALL] = 0; /* FIXME */ +} + +#define USE_ELF_CORE_DUMP +#define ELF_EXEC_PAGESIZE 4096 + +#endif + +#ifdef TARGET_CRIS + +#define ELF_START_MMAP 0x80000000 + +#define elf_check_arch(x) ( (x) == EM_CRIS ) + +#define ELF_CLASS ELFCLASS32 +#define ELF_ARCH EM_CRIS + +static inline void init_thread(struct target_pt_regs *regs, + struct image_info *infop) +{ + regs->erp = infop->entry; +} + +#define ELF_EXEC_PAGESIZE 8192 + +#endif + +#ifdef TARGET_M68K + +#define ELF_START_MMAP 0x80000000 + +#define elf_check_arch(x) ( (x) == EM_68K ) + +#define ELF_CLASS ELFCLASS32 +#define ELF_ARCH EM_68K + +/* ??? Does this need to do anything? + #define ELF_PLAT_INIT(_r) */ + +static inline void init_thread(struct target_pt_regs *regs, + struct image_info *infop) +{ + regs->usp = infop->start_stack; + regs->sr = 0; + regs->pc = infop->entry; +} + +/* See linux kernel: arch/m68k/include/asm/elf.h. */ +#define ELF_NREG 20 +typedef target_elf_greg_t target_elf_gregset_t[ELF_NREG]; + +static void elf_core_copy_regs(target_elf_gregset_t *regs, const CPUState *env) +{ + (*regs)[0] = tswapl(env->dregs[1]); + (*regs)[1] = tswapl(env->dregs[2]); + (*regs)[2] = tswapl(env->dregs[3]); + (*regs)[3] = tswapl(env->dregs[4]); + (*regs)[4] = tswapl(env->dregs[5]); + (*regs)[5] = tswapl(env->dregs[6]); + (*regs)[6] = tswapl(env->dregs[7]); + (*regs)[7] = tswapl(env->aregs[0]); + (*regs)[8] = tswapl(env->aregs[1]); + (*regs)[9] = tswapl(env->aregs[2]); + (*regs)[10] = tswapl(env->aregs[3]); + (*regs)[11] = tswapl(env->aregs[4]); + (*regs)[12] = tswapl(env->aregs[5]); + (*regs)[13] = tswapl(env->aregs[6]); + (*regs)[14] = tswapl(env->dregs[0]); + (*regs)[15] = tswapl(env->aregs[7]); + (*regs)[16] = tswapl(env->dregs[0]); /* FIXME: orig_d0 */ + (*regs)[17] = tswapl(env->sr); + (*regs)[18] = tswapl(env->pc); + (*regs)[19] = 0; /* FIXME: regs->format | regs->vector */ +} + +#define USE_ELF_CORE_DUMP +#define ELF_EXEC_PAGESIZE 8192 + +#endif + +#ifdef TARGET_ALPHA + +#define ELF_START_MMAP (0x30000000000ULL) + +#define elf_check_arch(x) ( (x) == ELF_ARCH ) + +#define ELF_CLASS ELFCLASS64 +#define ELF_ARCH EM_ALPHA + +static inline void init_thread(struct target_pt_regs *regs, + struct image_info *infop) +{ + regs->pc = infop->entry; + regs->ps = 8; + regs->usp = infop->start_stack; +} + +#define ELF_EXEC_PAGESIZE 8192 + +#endif /* TARGET_ALPHA */ + +#ifndef ELF_PLATFORM +#define ELF_PLATFORM (NULL) +#endif + +#ifndef ELF_HWCAP +#define ELF_HWCAP 0 +#endif + +#ifdef TARGET_ABI32 +#undef ELF_CLASS +#define ELF_CLASS ELFCLASS32 +#undef bswaptls +#define bswaptls(ptr) bswap32s(ptr) +#endif + +#include "elf.h" + +struct exec +{ + unsigned int a_info; /* Use macros N_MAGIC, etc for access */ + unsigned int a_text; /* length of text, in bytes */ + unsigned int a_data; /* length of data, in bytes */ + unsigned int a_bss; /* length of uninitialized data area, in bytes */ + unsigned int a_syms; /* length of symbol table data in file, in bytes */ + unsigned int a_entry; /* start address */ + unsigned int a_trsize; /* length of relocation info for text, in bytes */ + unsigned int a_drsize; /* length of relocation info for data, in bytes */ +}; + + +#define N_MAGIC(exec) ((exec).a_info & 0xffff) +#define OMAGIC 0407 +#define NMAGIC 0410 +#define ZMAGIC 0413 +#define QMAGIC 0314 + +/* Necessary parameters */ +#define TARGET_ELF_EXEC_PAGESIZE TARGET_PAGE_SIZE +#define TARGET_ELF_PAGESTART(_v) ((_v) & ~(unsigned long)(TARGET_ELF_EXEC_PAGESIZE-1)) +#define TARGET_ELF_PAGEOFFSET(_v) ((_v) & (TARGET_ELF_EXEC_PAGESIZE-1)) + +#define DLINFO_ITEMS 12 + +static inline void memcpy_fromfs(void * to, const void * from, unsigned long n) +{ + memcpy(to, from, n); +} + +#ifdef BSWAP_NEEDED +static void bswap_ehdr(struct elfhdr *ehdr) +{ + bswap16s(&ehdr->e_type); /* Object file type */ + bswap16s(&ehdr->e_machine); /* Architecture */ + bswap32s(&ehdr->e_version); /* Object file version */ + bswaptls(&ehdr->e_entry); /* Entry point virtual address */ + bswaptls(&ehdr->e_phoff); /* Program header table file offset */ + bswaptls(&ehdr->e_shoff); /* Section header table file offset */ + bswap32s(&ehdr->e_flags); /* Processor-specific flags */ + bswap16s(&ehdr->e_ehsize); /* ELF header size in bytes */ + bswap16s(&ehdr->e_phentsize); /* Program header table entry size */ + bswap16s(&ehdr->e_phnum); /* Program header table entry count */ + bswap16s(&ehdr->e_shentsize); /* Section header table entry size */ + bswap16s(&ehdr->e_shnum); /* Section header table entry count */ + bswap16s(&ehdr->e_shstrndx); /* Section header string table index */ +} + +static void bswap_phdr(struct elf_phdr *phdr, int phnum) +{ + int i; + for (i = 0; i < phnum; ++i, ++phdr) { + bswap32s(&phdr->p_type); /* Segment type */ + bswap32s(&phdr->p_flags); /* Segment flags */ + bswaptls(&phdr->p_offset); /* Segment file offset */ + bswaptls(&phdr->p_vaddr); /* Segment virtual address */ + bswaptls(&phdr->p_paddr); /* Segment physical address */ + bswaptls(&phdr->p_filesz); /* Segment size in file */ + bswaptls(&phdr->p_memsz); /* Segment size in memory */ + bswaptls(&phdr->p_align); /* Segment alignment */ + } +} + +static void bswap_shdr(struct elf_shdr *shdr, int shnum) +{ + int i; + for (i = 0; i < shnum; ++i, ++shdr) { + bswap32s(&shdr->sh_name); + bswap32s(&shdr->sh_type); + bswaptls(&shdr->sh_flags); + bswaptls(&shdr->sh_addr); + bswaptls(&shdr->sh_offset); + bswaptls(&shdr->sh_size); + bswap32s(&shdr->sh_link); + bswap32s(&shdr->sh_info); + bswaptls(&shdr->sh_addralign); + bswaptls(&shdr->sh_entsize); + } +} + +static void bswap_sym(struct elf_sym *sym) +{ + bswap32s(&sym->st_name); + bswaptls(&sym->st_value); + bswaptls(&sym->st_size); + bswap16s(&sym->st_shndx); +} +#else +static inline void bswap_ehdr(struct elfhdr *ehdr) { } +static inline void bswap_phdr(struct elf_phdr *phdr, int phnum) { } +static inline void bswap_shdr(struct elf_shdr *shdr, int shnum) { } +static inline void bswap_sym(struct elf_sym *sym) { } +#endif + +#ifdef USE_ELF_CORE_DUMP +static int elf_core_dump(int, const CPUState *); +#endif /* USE_ELF_CORE_DUMP */ +static void load_symbols(struct elfhdr *hdr, int fd, abi_ulong load_bias); + +/* Verify the portions of EHDR within E_IDENT for the target. + This can be performed before bswapping the entire header. */ +static bool elf_check_ident(struct elfhdr *ehdr) +{ + return (ehdr->e_ident[EI_MAG0] == ELFMAG0 + && ehdr->e_ident[EI_MAG1] == ELFMAG1 + && ehdr->e_ident[EI_MAG2] == ELFMAG2 + && ehdr->e_ident[EI_MAG3] == ELFMAG3 + && ehdr->e_ident[EI_CLASS] == ELF_CLASS + && ehdr->e_ident[EI_DATA] == ELF_DATA + && ehdr->e_ident[EI_VERSION] == EV_CURRENT); +} + +/* Verify the portions of EHDR outside of E_IDENT for the target. + This has to wait until after bswapping the header. */ +static bool elf_check_ehdr(struct elfhdr *ehdr) +{ + return (elf_check_arch(ehdr->e_machine) + && ehdr->e_ehsize == sizeof(struct elfhdr) + && ehdr->e_phentsize == sizeof(struct elf_phdr) + && ehdr->e_shentsize == sizeof(struct elf_shdr) + && (ehdr->e_type == ET_EXEC || ehdr->e_type == ET_DYN)); +} + +/* + * 'copy_elf_strings()' copies argument/envelope strings from user + * memory to free pages in kernel mem. These are in a format ready + * to be put directly into the top of new user memory. + * + */ +static abi_ulong copy_elf_strings(int argc,char ** argv, void **page, + abi_ulong p) +{ + char *tmp, *tmp1, *pag = NULL; + int len, offset = 0; + + if (!p) { + return 0; /* bullet-proofing */ + } + while (argc-- > 0) { + tmp = argv[argc]; + if (!tmp) { + fprintf(stderr, "VFS: argc is wrong"); + exit(-1); + } + tmp1 = tmp; + while (*tmp++); + len = tmp - tmp1; + if (p < len) { /* this shouldn't happen - 128kB */ + return 0; + } + while (len) { + --p; --tmp; --len; + if (--offset < 0) { + offset = p % TARGET_PAGE_SIZE; + pag = (char *)page[p/TARGET_PAGE_SIZE]; + if (!pag) { + pag = (char *)malloc(TARGET_PAGE_SIZE); + memset(pag, 0, TARGET_PAGE_SIZE); + page[p/TARGET_PAGE_SIZE] = pag; + if (!pag) + return 0; + } + } + if (len == 0 || offset == 0) { + *(pag + offset) = *tmp; + } + else { + int bytes_to_copy = (len > offset) ? offset : len; + tmp -= bytes_to_copy; + p -= bytes_to_copy; + offset -= bytes_to_copy; + len -= bytes_to_copy; + memcpy_fromfs(pag + offset, tmp, bytes_to_copy + 1); + } + } + } + return p; +} + +static abi_ulong setup_arg_pages(abi_ulong p, struct linux_binprm *bprm, + struct image_info *info) +{ + abi_ulong stack_base, size, error, guard; + int i; + + /* Create enough stack to hold everything. If we don't use + it for args, we'll use it for something else. */ + size = guest_stack_size; + if (size < MAX_ARG_PAGES*TARGET_PAGE_SIZE) { + size = MAX_ARG_PAGES*TARGET_PAGE_SIZE; + } + guard = TARGET_PAGE_SIZE; + if (guard < qemu_real_host_page_size) { + guard = qemu_real_host_page_size; + } + + error = target_mmap(0, size + guard, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (error == -1) { + perror("mmap stack"); + exit(-1); + } + + /* We reserve one extra page at the top of the stack as guard. */ + target_mprotect(error, guard, PROT_NONE); + + info->stack_limit = error + guard; + stack_base = info->stack_limit + size - MAX_ARG_PAGES*TARGET_PAGE_SIZE; + p += stack_base; + + for (i = 0 ; i < MAX_ARG_PAGES ; i++) { + if (bprm->page[i]) { + info->rss++; + /* FIXME - check return value of memcpy_to_target() for failure */ + memcpy_to_target(stack_base, bprm->page[i], TARGET_PAGE_SIZE); + free(bprm->page[i]); + } + stack_base += TARGET_PAGE_SIZE; + } + return p; +} + +/* Map and zero the bss. We need to explicitly zero any fractional pages + after the data section (i.e. bss). */ +static void zero_bss(abi_ulong elf_bss, abi_ulong last_bss, int prot) +{ + uintptr_t host_start, host_map_start, host_end; + + last_bss = TARGET_PAGE_ALIGN(last_bss); + + /* ??? There is confusion between qemu_real_host_page_size and + qemu_host_page_size here and elsewhere in target_mmap, which + may lead to the end of the data section mapping from the file + not being mapped. At least there was an explicit test and + comment for that here, suggesting that "the file size must + be known". The comment probably pre-dates the introduction + of the fstat system call in target_mmap which does in fact + find out the size. What isn't clear is if the workaround + here is still actually needed. For now, continue with it, + but merge it with the "normal" mmap that would allocate the bss. */ + + host_start = (uintptr_t) g2h(elf_bss); + host_end = (uintptr_t) g2h(last_bss); + host_map_start = (host_start + qemu_real_host_page_size - 1); + host_map_start &= -qemu_real_host_page_size; + + if (host_map_start < host_end) { + void *p = mmap((void *)host_map_start, host_end - host_map_start, + prot, MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (p == MAP_FAILED) { + perror("cannot mmap brk"); + exit(-1); + } + + /* Since we didn't use target_mmap, make sure to record + the validity of the pages with qemu. */ + page_set_flags(elf_bss & TARGET_PAGE_MASK, last_bss, prot|PAGE_VALID); + } + + if (host_start < host_map_start) { + memset((void *)host_start, 0, host_map_start - host_start); + } +} + +static abi_ulong create_elf_tables(abi_ulong p, int argc, int envc, + struct elfhdr *exec, + struct image_info *info, + struct image_info *interp_info) +{ + abi_ulong sp; + int size; + abi_ulong u_platform; + const char *k_platform; + const int n = sizeof(elf_addr_t); + + sp = p; + u_platform = 0; + k_platform = ELF_PLATFORM; + if (k_platform) { + size_t len = strlen(k_platform) + 1; + sp -= (len + n - 1) & ~(n - 1); + u_platform = sp; + /* FIXME - check return value of memcpy_to_target() for failure */ + memcpy_to_target(sp, k_platform, len); + } + /* + * Force 16 byte _final_ alignment here for generality. + */ + sp = sp &~ (abi_ulong)15; + size = (DLINFO_ITEMS + 1) * 2; + if (k_platform) + size += 2; +#ifdef DLINFO_ARCH_ITEMS + size += DLINFO_ARCH_ITEMS * 2; +#endif + size += envc + argc + 2; + size += 1; /* argc itself */ + size *= n; + if (size & 15) + sp -= 16 - (size & 15); + + /* This is correct because Linux defines + * elf_addr_t as Elf32_Off / Elf64_Off + */ +#define NEW_AUX_ENT(id, val) do { \ + sp -= n; put_user_ual(val, sp); \ + sp -= n; put_user_ual(id, sp); \ + } while(0) + + NEW_AUX_ENT (AT_NULL, 0); + + /* There must be exactly DLINFO_ITEMS entries here. */ + NEW_AUX_ENT(AT_PHDR, (abi_ulong)(info->load_addr + exec->e_phoff)); + NEW_AUX_ENT(AT_PHENT, (abi_ulong)(sizeof (struct elf_phdr))); + NEW_AUX_ENT(AT_PHNUM, (abi_ulong)(exec->e_phnum)); + NEW_AUX_ENT(AT_PAGESZ, (abi_ulong)(TARGET_PAGE_SIZE)); + NEW_AUX_ENT(AT_BASE, (abi_ulong)(interp_info ? interp_info->load_addr : 0)); + NEW_AUX_ENT(AT_FLAGS, (abi_ulong)0); + NEW_AUX_ENT(AT_ENTRY, info->entry); + NEW_AUX_ENT(AT_UID, (abi_ulong) getuid()); + NEW_AUX_ENT(AT_EUID, (abi_ulong) geteuid()); + NEW_AUX_ENT(AT_GID, (abi_ulong) getgid()); + NEW_AUX_ENT(AT_EGID, (abi_ulong) getegid()); + NEW_AUX_ENT(AT_HWCAP, (abi_ulong) ELF_HWCAP); + NEW_AUX_ENT(AT_CLKTCK, (abi_ulong) sysconf(_SC_CLK_TCK)); + if (k_platform) + NEW_AUX_ENT(AT_PLATFORM, u_platform); +#ifdef ARCH_DLINFO + /* + * ARCH_DLINFO must come last so platform specific code can enforce + * special alignment requirements on the AUXV if necessary (eg. PPC). + */ + ARCH_DLINFO; +#endif +#undef NEW_AUX_ENT + + info->saved_auxv = sp; + + sp = loader_build_argptr(envc, argc, sp, p, 0); + return sp; +} + +/* Load an ELF image into the address space. + + IMAGE_NAME is the filename of the image, to use in error messages. + IMAGE_FD is the open file descriptor for the image. + + BPRM_BUF is a copy of the beginning of the file; this of course + contains the elf file header at offset 0. It is assumed that this + buffer is sufficiently aligned to present no problems to the host + in accessing data at aligned offsets within the buffer. + + On return: INFO values will be filled in, as necessary or available. */ + +static void load_elf_image(const char *image_name, int image_fd, + struct image_info *info, char **pinterp_name, + char bprm_buf[BPRM_BUF_SIZE]) +{ + struct elfhdr *ehdr = (struct elfhdr *)bprm_buf; + struct elf_phdr *phdr; + abi_ulong load_addr, load_bias, loaddr, hiaddr, error; + int i, retval; + const char *errmsg; + + /* First of all, some simple consistency checks */ + errmsg = "Invalid ELF image for this architecture"; + if (!elf_check_ident(ehdr)) { + goto exit_errmsg; + } + bswap_ehdr(ehdr); + if (!elf_check_ehdr(ehdr)) { + goto exit_errmsg; + } + + i = ehdr->e_phnum * sizeof(struct elf_phdr); + if (ehdr->e_phoff + i <= BPRM_BUF_SIZE) { + phdr = (struct elf_phdr *)(bprm_buf + ehdr->e_phoff); + } else { + phdr = (struct elf_phdr *) alloca(i); + retval = pread(image_fd, phdr, i, ehdr->e_phoff); + if (retval != i) { + goto exit_read; + } + } + bswap_phdr(phdr, ehdr->e_phnum); + + /* Find the maximum size of the image and allocate an appropriate + amount of memory to handle that. */ + loaddr = -1, hiaddr = 0; + for (i = 0; i < ehdr->e_phnum; ++i) { + if (phdr[i].p_type == PT_LOAD) { + abi_ulong a = phdr[i].p_vaddr; + if (a < loaddr) { + loaddr = a; + } + a += phdr[i].p_memsz; + if (a > hiaddr) { + hiaddr = a; + } + } + } + + load_addr = loaddr; + if (ehdr->e_type == ET_DYN) { + /* The image indicates that it can be loaded anywhere. Find a + location that can hold the memory space required. If the + image is pre-linked, LOADDR will be non-zero. Since we do + not supply MAP_FIXED here we'll use that address if and + only if it remains available. */ + load_addr = target_mmap(loaddr, hiaddr - loaddr, PROT_NONE, + MAP_PRIVATE | MAP_ANON | MAP_NORESERVE, + -1, 0); + if (load_addr == -1) { + goto exit_perror; + } + } else if (pinterp_name != NULL) { + /* This is the main executable. Make sure that the low + address does not conflict with MMAP_MIN_ADDR or the + QEMU application itself. */ +#if defined(CONFIG_USE_GUEST_BASE) + /* + * In case where user has not explicitly set the guest_base, we + * probe here that should we set it automatically. + */ + if (!have_guest_base && !reserved_va) { + unsigned long host_start, real_start, host_size; + + /* Round addresses to page boundaries. */ + loaddr &= qemu_host_page_mask; + hiaddr = HOST_PAGE_ALIGN(hiaddr); + + if (loaddr < mmap_min_addr) { + host_start = HOST_PAGE_ALIGN(mmap_min_addr); + } else { + host_start = loaddr; + if (host_start != loaddr) { + errmsg = "Address overflow loading ELF binary"; + goto exit_errmsg; + } + } + host_size = hiaddr - loaddr; + while (1) { + /* Do not use mmap_find_vma here because that is limited to the + guest address space. We are going to make the + guest address space fit whatever we're given. */ + real_start = (unsigned long) + mmap((void *)host_start, host_size, PROT_NONE, + MAP_ANONYMOUS | MAP_PRIVATE | MAP_NORESERVE, -1, 0); + if (real_start == (unsigned long)-1) { + goto exit_perror; + } + if (real_start == host_start) { + break; + } + /* That address didn't work. Unmap and try a different one. + The address the host picked because is typically right at + the top of the host address space and leaves the guest with + no usable address space. Resort to a linear search. We + already compensated for mmap_min_addr, so this should not + happen often. Probably means we got unlucky and host + address space randomization put a shared library somewhere + inconvenient. */ + munmap((void *)real_start, host_size); + host_start += qemu_host_page_size; + if (host_start == loaddr) { + /* Theoretically possible if host doesn't have any suitably + aligned areas. Normally the first mmap will fail. */ + errmsg = "Unable to find space for application"; + goto exit_errmsg; + } + } + qemu_log("Relocating guest address space from 0x" + TARGET_ABI_FMT_lx " to 0x%lx\n", loaddr, real_start); + guest_base = real_start - loaddr; + } +#endif + } + load_bias = load_addr - loaddr; + + info->load_bias = load_bias; + info->load_addr = load_addr; + info->entry = ehdr->e_entry + load_bias; + info->start_code = -1; + info->end_code = 0; + info->start_data = -1; + info->end_data = 0; + info->brk = 0; + + for (i = 0; i < ehdr->e_phnum; i++) { + struct elf_phdr *eppnt = phdr + i; + if (eppnt->p_type == PT_LOAD) { + abi_ulong vaddr, vaddr_po, vaddr_ps, vaddr_ef, vaddr_em; + int elf_prot = 0; + + if (eppnt->p_flags & PF_R) elf_prot = PROT_READ; + if (eppnt->p_flags & PF_W) elf_prot |= PROT_WRITE; + if (eppnt->p_flags & PF_X) elf_prot |= PROT_EXEC; + + vaddr = load_bias + eppnt->p_vaddr; + vaddr_po = TARGET_ELF_PAGEOFFSET(vaddr); + vaddr_ps = TARGET_ELF_PAGESTART(vaddr); + + error = target_mmap(vaddr_ps, eppnt->p_filesz + vaddr_po, + elf_prot, MAP_PRIVATE | MAP_FIXED, + image_fd, eppnt->p_offset - vaddr_po); + if (error == -1) { + goto exit_perror; + } + + vaddr_ef = vaddr + eppnt->p_filesz; + vaddr_em = vaddr + eppnt->p_memsz; + + /* If the load segment requests extra zeros (e.g. bss), map it. */ + if (vaddr_ef < vaddr_em) { + zero_bss(vaddr_ef, vaddr_em, elf_prot); + } + + /* Find the full program boundaries. */ + if (elf_prot & PROT_EXEC) { + if (vaddr < info->start_code) { + info->start_code = vaddr; + } + if (vaddr_ef > info->end_code) { + info->end_code = vaddr_ef; + } + } + if (elf_prot & PROT_WRITE) { + if (vaddr < info->start_data) { + info->start_data = vaddr; + } + if (vaddr_ef > info->end_data) { + info->end_data = vaddr_ef; + } + if (vaddr_em > info->brk) { + info->brk = vaddr_em; + } + } + } else if (eppnt->p_type == PT_INTERP && pinterp_name) { + char *interp_name; + + if (*pinterp_name) { + errmsg = "Multiple PT_INTERP entries"; + goto exit_errmsg; + } + interp_name = malloc(eppnt->p_filesz); + if (!interp_name) { + goto exit_perror; + } + + if (eppnt->p_offset + eppnt->p_filesz <= BPRM_BUF_SIZE) { + memcpy(interp_name, bprm_buf + eppnt->p_offset, + eppnt->p_filesz); + } else { + retval = pread(image_fd, interp_name, eppnt->p_filesz, + eppnt->p_offset); + if (retval != eppnt->p_filesz) { + goto exit_perror; + } + } + if (interp_name[eppnt->p_filesz - 1] != 0) { + errmsg = "Invalid PT_INTERP entry"; + goto exit_errmsg; + } + *pinterp_name = interp_name; + } + } + + if (info->end_data == 0) { + info->start_data = info->end_code; + info->end_data = info->end_code; + info->brk = info->end_code; + } + + if (qemu_log_enabled()) { + load_symbols(ehdr, image_fd, load_bias); + } + + close(image_fd); + return; + + exit_read: + if (retval >= 0) { + errmsg = "Incomplete read of file header"; + goto exit_errmsg; + } + exit_perror: + errmsg = strerror(errno); + exit_errmsg: + fprintf(stderr, "%s: %s\n", image_name, errmsg); + exit(-1); +} + +static void load_elf_interp(const char *filename, struct image_info *info, + char bprm_buf[BPRM_BUF_SIZE]) +{ + int fd, retval; + + fd = open(path(filename), O_RDONLY); + if (fd < 0) { + goto exit_perror; + } + + retval = read(fd, bprm_buf, BPRM_BUF_SIZE); + if (retval < 0) { + goto exit_perror; + } + if (retval < BPRM_BUF_SIZE) { + memset(bprm_buf + retval, 0, BPRM_BUF_SIZE - retval); + } + + load_elf_image(filename, fd, info, NULL, bprm_buf); + return; + + exit_perror: + fprintf(stderr, "%s: %s\n", filename, strerror(errno)); + exit(-1); +} + +static int symfind(const void *s0, const void *s1) +{ + struct elf_sym *key = (struct elf_sym *)s0; + struct elf_sym *sym = (struct elf_sym *)s1; + int result = 0; + if (key->st_value < sym->st_value) { + result = -1; + } else if (key->st_value >= sym->st_value + sym->st_size) { + result = 1; + } + return result; +} + +static const char *lookup_symbolxx(struct syminfo *s, target_ulong orig_addr) +{ +#if ELF_CLASS == ELFCLASS32 + struct elf_sym *syms = s->disas_symtab.elf32; +#else + struct elf_sym *syms = s->disas_symtab.elf64; +#endif + + // binary search + struct elf_sym key; + struct elf_sym *sym; + + key.st_value = orig_addr; + + sym = bsearch(&key, syms, s->disas_num_syms, sizeof(*syms), symfind); + if (sym != NULL) { + return s->disas_strtab + sym->st_name; + } + + return ""; +} + +/* FIXME: This should use elf_ops.h */ +static int symcmp(const void *s0, const void *s1) +{ + struct elf_sym *sym0 = (struct elf_sym *)s0; + struct elf_sym *sym1 = (struct elf_sym *)s1; + return (sym0->st_value < sym1->st_value) + ? -1 + : ((sym0->st_value > sym1->st_value) ? 1 : 0); +} + +/* Best attempt to load symbols from this ELF object. */ +static void load_symbols(struct elfhdr *hdr, int fd, abi_ulong load_bias) +{ + int i, shnum, nsyms, sym_idx = 0, str_idx = 0; + struct elf_shdr *shdr; + char *strings; + struct syminfo *s; + struct elf_sym *syms, *new_syms; + + shnum = hdr->e_shnum; + i = shnum * sizeof(struct elf_shdr); + shdr = (struct elf_shdr *)alloca(i); + if (pread(fd, shdr, i, hdr->e_shoff) != i) { + return; + } + + bswap_shdr(shdr, shnum); + for (i = 0; i < shnum; ++i) { + if (shdr[i].sh_type == SHT_SYMTAB) { + sym_idx = i; + str_idx = shdr[i].sh_link; + goto found; + } + } + + /* There will be no symbol table if the file was stripped. */ + return; + + found: + /* Now know where the strtab and symtab are. Snarf them. */ + s = malloc(sizeof(*s)); + if (!s) { + return; + } + + i = shdr[str_idx].sh_size; + s->disas_strtab = strings = malloc(i); + if (!strings || pread(fd, strings, i, shdr[str_idx].sh_offset) != i) { + free(s); + free(strings); + return; + } + + i = shdr[sym_idx].sh_size; + syms = malloc(i); + if (!syms || pread(fd, syms, i, shdr[sym_idx].sh_offset) != i) { + free(s); + free(strings); + free(syms); + return; + } + + nsyms = i / sizeof(struct elf_sym); + for (i = 0; i < nsyms; ) { + bswap_sym(syms + i); + /* Throw away entries which we do not need. */ + if (syms[i].st_shndx == SHN_UNDEF + || syms[i].st_shndx >= SHN_LORESERVE + || ELF_ST_TYPE(syms[i].st_info) != STT_FUNC) { + if (i < --nsyms) { + syms[i] = syms[nsyms]; + } + } else { +#if defined(TARGET_ARM) || defined (TARGET_MIPS) + /* The bottom address bit marks a Thumb or MIPS16 symbol. */ + syms[i].st_value &= ~(target_ulong)1; +#endif + syms[i].st_value += load_bias; + i++; + } + } + + /* Attempt to free the storage associated with the local symbols + that we threw away. Whether or not this has any effect on the + memory allocation depends on the malloc implementation and how + many symbols we managed to discard. */ + new_syms = realloc(syms, nsyms * sizeof(*syms)); + if (new_syms == NULL) { + free(s); + free(syms); + free(strings); + return; + } + syms = new_syms; + + qsort(syms, nsyms, sizeof(*syms), symcmp); + + s->disas_num_syms = nsyms; +#if ELF_CLASS == ELFCLASS32 + s->disas_symtab.elf32 = syms; +#else + s->disas_symtab.elf64 = syms; +#endif + s->lookup_symbol = lookup_symbolxx; + s->next = syminfos; + syminfos = s; +} + +int load_elf_binary(struct linux_binprm * bprm, struct target_pt_regs * regs, + struct image_info * info) +{ + struct image_info interp_info; + struct elfhdr elf_ex; + char *elf_interpreter = NULL; + + info->start_mmap = (abi_ulong)ELF_START_MMAP; + info->mmap = 0; + info->rss = 0; + + load_elf_image(bprm->filename, bprm->fd, info, + &elf_interpreter, bprm->buf); + + /* ??? We need a copy of the elf header for passing to create_elf_tables. + If we do nothing, we'll have overwritten this when we re-use bprm->buf + when we load the interpreter. */ + elf_ex = *(struct elfhdr *)bprm->buf; + + bprm->p = copy_elf_strings(1, &bprm->filename, bprm->page, bprm->p); + bprm->p = copy_elf_strings(bprm->envc,bprm->envp,bprm->page,bprm->p); + bprm->p = copy_elf_strings(bprm->argc,bprm->argv,bprm->page,bprm->p); + if (!bprm->p) { + fprintf(stderr, "%s: %s\n", bprm->filename, strerror(E2BIG)); + exit(-1); + } + + /* Do this so that we can load the interpreter, if need be. We will + change some of these later */ + bprm->p = setup_arg_pages(bprm->p, bprm, info); + + if (elf_interpreter) { + load_elf_interp(elf_interpreter, &interp_info, bprm->buf); + + /* If the program interpreter is one of these two, then assume + an iBCS2 image. Otherwise assume a native linux image. */ + + if (strcmp(elf_interpreter, "/usr/lib/libc.so.1") == 0 + || strcmp(elf_interpreter, "/usr/lib/ld.so.1") == 0) { + info->personality = PER_SVR4; + + /* Why this, you ask??? Well SVr4 maps page 0 as read-only, + and some applications "depend" upon this behavior. Since + we do not have the power to recompile these, we emulate + the SVr4 behavior. Sigh. */ + target_mmap(0, qemu_host_page_size, PROT_READ | PROT_EXEC, + MAP_FIXED | MAP_PRIVATE, -1, 0); + } + } + + bprm->p = create_elf_tables(bprm->p, bprm->argc, bprm->envc, &elf_ex, + info, (elf_interpreter ? &interp_info : NULL)); + info->start_stack = bprm->p; + + /* If we have an interpreter, set that as the program's entry point. + Copy the load_addr as well, to help PPC64 interpret the entry + point as a function descriptor. Do this after creating elf tables + so that we copy the original program entry point into the AUXV. */ + if (elf_interpreter) { + info->load_addr = interp_info.load_addr; + info->entry = interp_info.entry; + free(elf_interpreter); + } + +#ifdef USE_ELF_CORE_DUMP + bprm->core_dump = &elf_core_dump; +#endif + + return 0; +} + +#ifdef USE_ELF_CORE_DUMP +/* + * Definitions to generate Intel SVR4-like core files. + * These mostly have the same names as the SVR4 types with "target_elf_" + * tacked on the front to prevent clashes with linux definitions, + * and the typedef forms have been avoided. This is mostly like + * the SVR4 structure, but more Linuxy, with things that Linux does + * not support and which gdb doesn't really use excluded. + * + * Fields we don't dump (their contents is zero) in linux-user qemu + * are marked with XXX. + * + * Core dump code is copied from linux kernel (fs/binfmt_elf.c). + * + * Porting ELF coredump for target is (quite) simple process. First you + * define USE_ELF_CORE_DUMP in target ELF code (where init_thread() for + * the target resides): + * + * #define USE_ELF_CORE_DUMP + * + * Next you define type of register set used for dumping. ELF specification + * says that it needs to be array of elf_greg_t that has size of ELF_NREG. + * + * typedef target_elf_greg_t; + * #define ELF_NREG + * typedef taret_elf_greg_t target_elf_gregset_t[ELF_NREG]; + * + * Last step is to implement target specific function that copies registers + * from given cpu into just specified register set. Prototype is: + * + * static void elf_core_copy_regs(taret_elf_gregset_t *regs, + * const CPUState *env); + * + * Parameters: + * regs - copy register values into here (allocated and zeroed by caller) + * env - copy registers from here + * + * Example for ARM target is provided in this file. + */ + +/* An ELF note in memory */ +struct memelfnote { + const char *name; + size_t namesz; + size_t namesz_rounded; + int type; + size_t datasz; + void *data; + size_t notesz; +}; + +struct target_elf_siginfo { + int si_signo; /* signal number */ + int si_code; /* extra code */ + int si_errno; /* errno */ +}; + +struct target_elf_prstatus { + struct target_elf_siginfo pr_info; /* Info associated with signal */ + short pr_cursig; /* Current signal */ + target_ulong pr_sigpend; /* XXX */ + target_ulong pr_sighold; /* XXX */ + target_pid_t pr_pid; + target_pid_t pr_ppid; + target_pid_t pr_pgrp; + target_pid_t pr_sid; + struct target_timeval pr_utime; /* XXX User time */ + struct target_timeval pr_stime; /* XXX System time */ + struct target_timeval pr_cutime; /* XXX Cumulative user time */ + struct target_timeval pr_cstime; /* XXX Cumulative system time */ + target_elf_gregset_t pr_reg; /* GP registers */ + int pr_fpvalid; /* XXX */ +}; + +#define ELF_PRARGSZ (80) /* Number of chars for args */ + +struct target_elf_prpsinfo { + char pr_state; /* numeric process state */ + char pr_sname; /* char for pr_state */ + char pr_zomb; /* zombie */ + char pr_nice; /* nice val */ + target_ulong pr_flag; /* flags */ + target_uid_t pr_uid; + target_gid_t pr_gid; + target_pid_t pr_pid, pr_ppid, pr_pgrp, pr_sid; + /* Lots missing */ + char pr_fname[16]; /* filename of executable */ + char pr_psargs[ELF_PRARGSZ]; /* initial part of arg list */ +}; + +/* Here is the structure in which status of each thread is captured. */ +struct elf_thread_status { + QTAILQ_ENTRY(elf_thread_status) ets_link; + struct target_elf_prstatus prstatus; /* NT_PRSTATUS */ +#if 0 + elf_fpregset_t fpu; /* NT_PRFPREG */ + struct task_struct *thread; + elf_fpxregset_t xfpu; /* ELF_CORE_XFPREG_TYPE */ +#endif + struct memelfnote notes[1]; + int num_notes; +}; + +struct elf_note_info { + struct memelfnote *notes; + struct target_elf_prstatus *prstatus; /* NT_PRSTATUS */ + struct target_elf_prpsinfo *psinfo; /* NT_PRPSINFO */ + + QTAILQ_HEAD(thread_list_head, elf_thread_status) thread_list; +#if 0 + /* + * Current version of ELF coredump doesn't support + * dumping fp regs etc. + */ + elf_fpregset_t *fpu; + elf_fpxregset_t *xfpu; + int thread_status_size; +#endif + int notes_size; + int numnote; +}; + +struct vm_area_struct { + abi_ulong vma_start; /* start vaddr of memory region */ + abi_ulong vma_end; /* end vaddr of memory region */ + abi_ulong vma_flags; /* protection etc. flags for the region */ + QTAILQ_ENTRY(vm_area_struct) vma_link; +}; + +struct mm_struct { + QTAILQ_HEAD(, vm_area_struct) mm_mmap; + int mm_count; /* number of mappings */ +}; + +static struct mm_struct *vma_init(void); +static void vma_delete(struct mm_struct *); +static int vma_add_mapping(struct mm_struct *, abi_ulong, + abi_ulong, abi_ulong); +static int vma_get_mapping_count(const struct mm_struct *); +static struct vm_area_struct *vma_first(const struct mm_struct *); +static struct vm_area_struct *vma_next(struct vm_area_struct *); +static abi_ulong vma_dump_size(const struct vm_area_struct *); +static int vma_walker(void *priv, abi_ulong start, abi_ulong end, + unsigned long flags); + +static void fill_elf_header(struct elfhdr *, int, uint16_t, uint32_t); +static void fill_note(struct memelfnote *, const char *, int, + unsigned int, void *); +static void fill_prstatus(struct target_elf_prstatus *, const TaskState *, int); +static int fill_psinfo(struct target_elf_prpsinfo *, const TaskState *); +static void fill_auxv_note(struct memelfnote *, const TaskState *); +static void fill_elf_note_phdr(struct elf_phdr *, int, off_t); +static size_t note_size(const struct memelfnote *); +static void free_note_info(struct elf_note_info *); +static int fill_note_info(struct elf_note_info *, long, const CPUState *); +static void fill_thread_info(struct elf_note_info *, const CPUState *); +static int core_dump_filename(const TaskState *, char *, size_t); + +static int dump_write(int, const void *, size_t); +static int write_note(struct memelfnote *, int); +static int write_note_info(struct elf_note_info *, int); + +#ifdef BSWAP_NEEDED +static void bswap_prstatus(struct target_elf_prstatus *prstatus) +{ + prstatus->pr_info.si_signo = tswapl(prstatus->pr_info.si_signo); + prstatus->pr_info.si_code = tswapl(prstatus->pr_info.si_code); + prstatus->pr_info.si_errno = tswapl(prstatus->pr_info.si_errno); + prstatus->pr_cursig = tswap16(prstatus->pr_cursig); + prstatus->pr_sigpend = tswapl(prstatus->pr_sigpend); + prstatus->pr_sighold = tswapl(prstatus->pr_sighold); + prstatus->pr_pid = tswap32(prstatus->pr_pid); + prstatus->pr_ppid = tswap32(prstatus->pr_ppid); + prstatus->pr_pgrp = tswap32(prstatus->pr_pgrp); + prstatus->pr_sid = tswap32(prstatus->pr_sid); + /* cpu times are not filled, so we skip them */ + /* regs should be in correct format already */ + prstatus->pr_fpvalid = tswap32(prstatus->pr_fpvalid); +} + +static void bswap_psinfo(struct target_elf_prpsinfo *psinfo) +{ + psinfo->pr_flag = tswapl(psinfo->pr_flag); + psinfo->pr_uid = tswap16(psinfo->pr_uid); + psinfo->pr_gid = tswap16(psinfo->pr_gid); + psinfo->pr_pid = tswap32(psinfo->pr_pid); + psinfo->pr_ppid = tswap32(psinfo->pr_ppid); + psinfo->pr_pgrp = tswap32(psinfo->pr_pgrp); + psinfo->pr_sid = tswap32(psinfo->pr_sid); +} + +static void bswap_note(struct elf_note *en) +{ + bswap32s(&en->n_namesz); + bswap32s(&en->n_descsz); + bswap32s(&en->n_type); +} +#else +static inline void bswap_prstatus(struct target_elf_prstatus *p) { } +static inline void bswap_psinfo(struct target_elf_prpsinfo *p) {} +static inline void bswap_note(struct elf_note *en) { } +#endif /* BSWAP_NEEDED */ + +/* + * Minimal support for linux memory regions. These are needed + * when we are finding out what memory exactly belongs to + * emulated process. No locks needed here, as long as + * thread that received the signal is stopped. + */ + +static struct mm_struct *vma_init(void) +{ + struct mm_struct *mm; + + if ((mm = qemu_malloc(sizeof (*mm))) == NULL) + return (NULL); + + mm->mm_count = 0; + QTAILQ_INIT(&mm->mm_mmap); + + return (mm); +} + +static void vma_delete(struct mm_struct *mm) +{ + struct vm_area_struct *vma; + + while ((vma = vma_first(mm)) != NULL) { + QTAILQ_REMOVE(&mm->mm_mmap, vma, vma_link); + qemu_free(vma); + } + qemu_free(mm); +} + +static int vma_add_mapping(struct mm_struct *mm, abi_ulong start, + abi_ulong end, abi_ulong flags) +{ + struct vm_area_struct *vma; + + if ((vma = qemu_mallocz(sizeof (*vma))) == NULL) + return (-1); + + vma->vma_start = start; + vma->vma_end = end; + vma->vma_flags = flags; + + QTAILQ_INSERT_TAIL(&mm->mm_mmap, vma, vma_link); + mm->mm_count++; + + return (0); +} + +static struct vm_area_struct *vma_first(const struct mm_struct *mm) +{ + return (QTAILQ_FIRST(&mm->mm_mmap)); +} + +static struct vm_area_struct *vma_next(struct vm_area_struct *vma) +{ + return (QTAILQ_NEXT(vma, vma_link)); +} + +static int vma_get_mapping_count(const struct mm_struct *mm) +{ + return (mm->mm_count); +} + +/* + * Calculate file (dump) size of given memory region. + */ +static abi_ulong vma_dump_size(const struct vm_area_struct *vma) +{ + /* if we cannot even read the first page, skip it */ + if (!access_ok(VERIFY_READ, vma->vma_start, TARGET_PAGE_SIZE)) + return (0); + + /* + * Usually we don't dump executable pages as they contain + * non-writable code that debugger can read directly from + * target library etc. However, thread stacks are marked + * also executable so we read in first page of given region + * and check whether it contains elf header. If there is + * no elf header, we dump it. + */ + if (vma->vma_flags & PROT_EXEC) { + char page[TARGET_PAGE_SIZE]; + + copy_from_user(page, vma->vma_start, sizeof (page)); + if ((page[EI_MAG0] == ELFMAG0) && + (page[EI_MAG1] == ELFMAG1) && + (page[EI_MAG2] == ELFMAG2) && + (page[EI_MAG3] == ELFMAG3)) { + /* + * Mappings are possibly from ELF binary. Don't dump + * them. + */ + return (0); + } + } + + return (vma->vma_end - vma->vma_start); +} + +static int vma_walker(void *priv, abi_ulong start, abi_ulong end, + unsigned long flags) +{ + struct mm_struct *mm = (struct mm_struct *)priv; + + vma_add_mapping(mm, start, end, flags); + return (0); +} + +static void fill_note(struct memelfnote *note, const char *name, int type, + unsigned int sz, void *data) +{ + unsigned int namesz; + + namesz = strlen(name) + 1; + note->name = name; + note->namesz = namesz; + note->namesz_rounded = roundup(namesz, sizeof (int32_t)); + note->type = type; + note->datasz = roundup(sz, sizeof (int32_t));; + note->data = data; + + /* + * We calculate rounded up note size here as specified by + * ELF document. + */ + note->notesz = sizeof (struct elf_note) + + note->namesz_rounded + note->datasz; +} + +static void fill_elf_header(struct elfhdr *elf, int segs, uint16_t machine, + uint32_t flags) +{ + (void) memset(elf, 0, sizeof(*elf)); + + (void) memcpy(elf->e_ident, ELFMAG, SELFMAG); + elf->e_ident[EI_CLASS] = ELF_CLASS; + elf->e_ident[EI_DATA] = ELF_DATA; + elf->e_ident[EI_VERSION] = EV_CURRENT; + elf->e_ident[EI_OSABI] = ELF_OSABI; + + elf->e_type = ET_CORE; + elf->e_machine = machine; + elf->e_version = EV_CURRENT; + elf->e_phoff = sizeof(struct elfhdr); + elf->e_flags = flags; + elf->e_ehsize = sizeof(struct elfhdr); + elf->e_phentsize = sizeof(struct elf_phdr); + elf->e_phnum = segs; + + bswap_ehdr(elf); +} + +static void fill_elf_note_phdr(struct elf_phdr *phdr, int sz, off_t offset) +{ + phdr->p_type = PT_NOTE; + phdr->p_offset = offset; + phdr->p_vaddr = 0; + phdr->p_paddr = 0; + phdr->p_filesz = sz; + phdr->p_memsz = 0; + phdr->p_flags = 0; + phdr->p_align = 0; + + bswap_phdr(phdr, 1); +} + +static size_t note_size(const struct memelfnote *note) +{ + return (note->notesz); +} + +static void fill_prstatus(struct target_elf_prstatus *prstatus, + const TaskState *ts, int signr) +{ + (void) memset(prstatus, 0, sizeof (*prstatus)); + prstatus->pr_info.si_signo = prstatus->pr_cursig = signr; + prstatus->pr_pid = ts->ts_tid; + prstatus->pr_ppid = getppid(); + prstatus->pr_pgrp = getpgrp(); + prstatus->pr_sid = getsid(0); + + bswap_prstatus(prstatus); +} + +static int fill_psinfo(struct target_elf_prpsinfo *psinfo, const TaskState *ts) +{ + char *filename, *base_filename; + unsigned int i, len; + + (void) memset(psinfo, 0, sizeof (*psinfo)); + + len = ts->info->arg_end - ts->info->arg_start; + if (len >= ELF_PRARGSZ) + len = ELF_PRARGSZ - 1; + if (copy_from_user(&psinfo->pr_psargs, ts->info->arg_start, len)) + return -EFAULT; + for (i = 0; i < len; i++) + if (psinfo->pr_psargs[i] == 0) + psinfo->pr_psargs[i] = ' '; + psinfo->pr_psargs[len] = 0; + + psinfo->pr_pid = getpid(); + psinfo->pr_ppid = getppid(); + psinfo->pr_pgrp = getpgrp(); + psinfo->pr_sid = getsid(0); + psinfo->pr_uid = getuid(); + psinfo->pr_gid = getgid(); + + filename = strdup(ts->bprm->filename); + base_filename = strdup(basename(filename)); + (void) strncpy(psinfo->pr_fname, base_filename, + sizeof(psinfo->pr_fname)); + free(base_filename); + free(filename); + + bswap_psinfo(psinfo); + return (0); +} + +static void fill_auxv_note(struct memelfnote *note, const TaskState *ts) +{ + elf_addr_t auxv = (elf_addr_t)ts->info->saved_auxv; + elf_addr_t orig_auxv = auxv; + abi_ulong val; + void *ptr; + int i, len; + + /* + * Auxiliary vector is stored in target process stack. It contains + * {type, value} pairs that we need to dump into note. This is not + * strictly necessary but we do it here for sake of completeness. + */ + + /* find out lenght of the vector, AT_NULL is terminator */ + i = len = 0; + do { + get_user_ual(val, auxv); + i += 2; + auxv += 2 * sizeof (elf_addr_t); + } while (val != AT_NULL); + len = i * sizeof (elf_addr_t); + + /* read in whole auxv vector and copy it to memelfnote */ + ptr = lock_user(VERIFY_READ, orig_auxv, len, 0); + if (ptr != NULL) { + fill_note(note, "CORE", NT_AUXV, len, ptr); + unlock_user(ptr, auxv, len); + } +} + +/* + * Constructs name of coredump file. We have following convention + * for the name: + * qemu__-